mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 00:37:36 +00:00
169 lines
8.5 KiB
Go
169 lines
8.5 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
)
|
|
|
|
func TestDemoModeMiddleware(t *testing.T) {
|
|
// Create a simple handler that records if it was called
|
|
handlerCalled := false
|
|
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
handlerCalled = true
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("OK"))
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
demoMode bool
|
|
method string
|
|
path string
|
|
upgradeHeader string
|
|
wantCalled bool
|
|
wantStatus int
|
|
wantDemoHeader bool
|
|
}{
|
|
// Demo mode disabled - all requests pass through
|
|
{"demo off GET", false, http.MethodGet, "/api/users", "", true, http.StatusOK, false},
|
|
{"demo off POST", false, http.MethodPost, "/api/users", "", true, http.StatusOK, false},
|
|
{"demo off PUT", false, http.MethodPut, "/api/users/1", "", true, http.StatusOK, false},
|
|
{"demo off DELETE", false, http.MethodDelete, "/api/users/1", "", true, http.StatusOK, false},
|
|
{"demo off PATCH", false, http.MethodPatch, "/api/users/1", "", true, http.StatusOK, false},
|
|
|
|
// Demo mode enabled - read-only methods allowed
|
|
{"demo on GET", true, http.MethodGet, "/api/users", "", true, http.StatusOK, true},
|
|
{"demo on HEAD", true, http.MethodHead, "/api/users", "", true, http.StatusOK, true},
|
|
{"demo on OPTIONS", true, http.MethodOptions, "/api/users", "", true, http.StatusOK, true},
|
|
|
|
// Demo mode enabled - WebSocket upgrades allowed
|
|
{"demo on websocket GET", true, http.MethodGet, "/api/ws", "websocket", true, http.StatusOK, true},
|
|
{"demo on websocket case insensitive", true, http.MethodGet, "/api/ws", "WebSocket", true, http.StatusOK, true},
|
|
{"demo on websocket uppercase", true, http.MethodGet, "/api/ws", "WEBSOCKET", true, http.StatusOK, true},
|
|
// WebSocket upgrade with POST method (tests websocket branch after GET/HEAD/OPTIONS check)
|
|
{"demo on websocket POST", true, http.MethodPost, "/api/ws", "websocket", true, http.StatusOK, true},
|
|
|
|
// Demo mode enabled - auth endpoints allowed (POST)
|
|
{"demo on login", true, http.MethodPost, "/api/login", "", true, http.StatusOK, true},
|
|
{"demo on oidc provider login", true, http.MethodPost, "/api/oidc/acme/login", "", true, http.StatusOK, true},
|
|
{"demo on oidc provider callback", true, http.MethodPost, "/api/oidc/acme/callback", "", true, http.StatusOK, true},
|
|
{"demo on logout", true, http.MethodPost, "/api/logout", "", true, http.StatusOK, true},
|
|
|
|
// Demo mode enabled - commercial surfaces are hidden centrally
|
|
{"demo on hidden license status", true, http.MethodGet, "/api/license/status", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden commercial posture", true, http.MethodGet, "/api/license/commercial-posture", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden license entitlements", true, http.MethodGet, "/api/license/entitlements", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden monitored system ledger", true, http.MethodGet, "/api/license/monitored-system-ledger", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden monitored system explanation", true, http.MethodPost, "/api/license/monitored-system-ledger/explain", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden monitored system preview", true, http.MethodPost, "/api/license/monitored-system-ledger/preview", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden truenas draft preview", true, http.MethodPost, "/api/truenas/connections/preview", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden truenas saved preview", true, http.MethodPost, "/api/truenas/connections/conn-1/preview", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden vmware draft preview", true, http.MethodPost, "/api/vmware/connections/preview", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden vmware saved preview", true, http.MethodPost, "/api/vmware/connections/conn-1/preview", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden diagnostics", true, http.MethodGet, "/api/diagnostics", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden diagnostics token prepare", true, http.MethodPost, "/api/diagnostics/docker/prepare-token", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden logs stream", true, http.MethodGet, "/api/logs/stream", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden logs download", true, http.MethodGet, "/api/logs/download", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden logs level read", true, http.MethodGet, "/api/logs/level", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden logs level write", true, http.MethodPost, "/api/logs/level", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden checkout start", true, http.MethodGet, "/auth/license-purchase-start", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden license activate", true, http.MethodPost, "/api/license/activate", "", false, http.StatusNotFound, true},
|
|
{"demo on hidden purchase start", true, http.MethodGet, licensePurchaseStartPath, "", false, http.StatusNotFound, true},
|
|
{"demo on hidden trial activation", true, http.MethodGet, "/auth/trial-activate", "", false, http.StatusNotFound, true},
|
|
{"demo on runtime capabilities", true, http.MethodGet, "/api/license/runtime-capabilities", "", true, http.StatusOK, true},
|
|
|
|
// Demo mode enabled - modification requests blocked
|
|
{"demo on POST", true, http.MethodPost, "/api/users", "", false, http.StatusForbidden, true},
|
|
{"demo on PUT", true, http.MethodPut, "/api/users/1", "", false, http.StatusForbidden, true},
|
|
{"demo on DELETE", true, http.MethodDelete, "/api/users/1", "", false, http.StatusForbidden, true},
|
|
{"demo on PATCH", true, http.MethodPatch, "/api/users/1", "", false, http.StatusForbidden, true},
|
|
|
|
// Demo mode enabled - partial path matches should not be allowed
|
|
{"demo on login prefix", true, http.MethodPost, "/api/login/extra", "", false, http.StatusForbidden, true},
|
|
{"demo on legacy oidc alias", true, http.MethodPost, "/api/oidc/login", "", false, http.StatusForbidden, true},
|
|
{"demo on oidc prefix", true, http.MethodPost, "/api/oidc/acme/loginx", "", false, http.StatusForbidden, true},
|
|
|
|
// Demo mode enabled - POST to other paths blocked
|
|
{"demo on POST settings", true, http.MethodPost, "/api/settings", "", false, http.StatusForbidden, true},
|
|
{"demo on POST config", true, http.MethodPost, "/api/config", "", false, http.StatusForbidden, true},
|
|
{"demo on DELETE docker host", true, http.MethodDelete, "/api/docker-hosts/1", "", false, http.StatusForbidden, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
handlerCalled = false
|
|
|
|
cfg := &config.Config{
|
|
DemoMode: tt.demoMode,
|
|
}
|
|
|
|
middleware := DemoModeMiddleware(cfg, nextHandler)
|
|
|
|
req := httptest.NewRequest(tt.method, tt.path, nil)
|
|
if tt.upgradeHeader != "" {
|
|
req.Header.Set("Upgrade", tt.upgradeHeader)
|
|
}
|
|
|
|
rr := httptest.NewRecorder()
|
|
middleware.ServeHTTP(rr, req)
|
|
|
|
// Check if handler was called
|
|
if handlerCalled != tt.wantCalled {
|
|
t.Errorf("handler called = %v, want %v", handlerCalled, tt.wantCalled)
|
|
}
|
|
|
|
// Check status code
|
|
if rr.Code != tt.wantStatus {
|
|
t.Errorf("status = %d, want %d", rr.Code, tt.wantStatus)
|
|
}
|
|
|
|
// Check X-Demo-Mode header
|
|
demoHeader := rr.Header().Get("X-Demo-Mode")
|
|
hasDemoHeader := demoHeader == "true"
|
|
if hasDemoHeader != tt.wantDemoHeader {
|
|
t.Errorf("X-Demo-Mode header present = %v, want %v", hasDemoHeader, tt.wantDemoHeader)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDemoModeMiddleware_BlockedResponse(t *testing.T) {
|
|
// Verify the error response format when a request is blocked
|
|
cfg := &config.Config{
|
|
DemoMode: true,
|
|
}
|
|
|
|
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
t.Error("handler should not be called for blocked request")
|
|
})
|
|
|
|
middleware := DemoModeMiddleware(cfg, nextHandler)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/api/users", nil)
|
|
rr := httptest.NewRecorder()
|
|
middleware.ServeHTTP(rr, req)
|
|
|
|
// Check Content-Type header
|
|
contentType := rr.Header().Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
t.Errorf("Content-Type = %q, want %q", contentType, "application/json")
|
|
}
|
|
|
|
// Parse and verify response body
|
|
var response map[string]string
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
|
|
t.Fatalf("failed to parse response body: %v", err)
|
|
}
|
|
|
|
if response["error"] != "Demo mode enabled" {
|
|
t.Errorf("error = %q, want %q", response["error"], "Demo mode enabled")
|
|
}
|
|
|
|
if response["message"] == "" {
|
|
t.Error("message should not be empty")
|
|
}
|
|
}
|