Pulse/internal/api/demo_mode_commercial_test.go
rcourtman 9c498b55c6 Prove demo commercial API preview redaction
Add backend regression coverage for the public-demo commercial route policy so monitored-system ledger reads, ledger preview/explain requests, and provider preview probes are hidden with the same generic 404 while adjacent platform list/add/test routes remain outside the commercial policy.
2026-04-09 09:55:54 +01:00

198 lines
6.8 KiB
Go

package api
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestPublicDemoCommercialRouteInventoryCoverage(t *testing.T) {
literalRoutes, _, _ := parseRouterRoutes(t)
var actualCommercialRoutes []string
for _, route := range literalRoutes {
if routeBelongsToPublicDemoCommercialBoundary(route) {
actualCommercialRoutes = append(actualCommercialRoutes, route)
}
}
actualSet := sliceToSet(t, actualCommercialRoutes, "public demo commercial boundary routes")
expectedSet := sliceToSet(
t,
publicDemoCommercialRouterRouteInventory(),
"public demo commercial router route inventory",
)
if missing := setDifference(actualSet, expectedSet); len(missing) > 0 {
t.Fatalf(
"commercial routes missing demo policy coverage: %s",
strings.Join(sortedKeys(missing), ", "),
)
}
if stale := setDifference(expectedSet, actualSet); len(stale) > 0 {
t.Fatalf(
"demo commercial router route inventory contains routes outside the boundary family: %s",
strings.Join(sortedKeys(stale), ", "),
)
}
}
func TestSanitizeRuntimeCapabilitiesPayloadForPublicDemo(t *testing.T) {
sanitized := sanitizeRuntimeCapabilitiesPayloadForPublicDemo(RuntimeCapabilitiesPayload{
Capabilities: []string{"relay", "ai_patrol"},
Limits: []LimitStatus{
{
Key: maxMonitoredSystemsLicenseGateKey,
Limit: 5,
Current: 16,
State: "enforced",
},
},
HostedMode: true,
MaxHistoryDays: 90,
})
if len(sanitized.Capabilities) != 2 {
t.Fatalf("capabilities=%v, want original capabilities preserved", sanitized.Capabilities)
}
if len(sanitized.Limits) != 1 {
t.Fatalf("limits=%v, want one sanitized limit", sanitized.Limits)
}
if sanitized.Limits[0].Limit != 0 || sanitized.Limits[0].Current != 0 || sanitized.Limits[0].State != "ok" {
t.Fatalf("sanitized limit=%+v, want limit=0 current=0 state=ok", sanitized.Limits[0])
}
if sanitized.MaxHistoryDays != 90 || !sanitized.HostedMode {
t.Fatalf("non-commercial runtime capability fields should be preserved, got max_history_days=%d hosted_mode=%v", sanitized.MaxHistoryDays, sanitized.HostedMode)
}
}
func TestPublicDemoCommercialPolicyForRequestHidesUsagePreviewRoutes(t *testing.T) {
hiddenRoutes := []struct {
name string
method string
path string
}{
{name: "ledger read", method: http.MethodGet, path: "/api/license/monitored-system-ledger"},
{name: "ledger explain", method: http.MethodPost, path: "/api/license/monitored-system-ledger/explain"},
{name: "ledger preview", method: http.MethodPost, path: "/api/license/monitored-system-ledger/preview"},
{name: "truenas draft preview", method: http.MethodPost, path: "/api/truenas/connections/preview"},
{name: "truenas saved preview", method: http.MethodPost, path: "/api/truenas/connections/conn-1/preview"},
{name: "vmware draft preview", method: http.MethodPost, path: "/api/vmware/connections/preview"},
{name: "vmware saved preview", method: http.MethodPost, path: "/api/vmware/connections/conn-1/preview"},
}
for _, tc := range hiddenRoutes {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest(tc.method, tc.path, nil)
exposure, ok := publicDemoCommercialPolicyForRequest(req)
if !ok {
t.Fatalf("%s %s did not match public demo commercial policy", tc.method, tc.path)
}
if exposure != publicDemoCommercialExposureHidden {
t.Fatalf("%s %s exposure=%q, want %q", tc.method, tc.path, exposure, publicDemoCommercialExposureHidden)
}
})
}
allowedRoutes := []struct {
name string
method string
path string
}{
{name: "truenas list", method: http.MethodGet, path: "/api/truenas/connections"},
{name: "truenas add", method: http.MethodPost, path: "/api/truenas/connections"},
{name: "truenas saved test", method: http.MethodPost, path: "/api/truenas/connections/conn-1/test"},
{name: "vmware list", method: http.MethodGet, path: "/api/vmware/connections"},
{name: "vmware add", method: http.MethodPost, path: "/api/vmware/connections"},
{name: "vmware saved test", method: http.MethodPost, path: "/api/vmware/connections/conn-1/test"},
{name: "runtime capabilities", method: http.MethodGet, path: "/api/license/runtime-capabilities"},
}
for _, tc := range allowedRoutes {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest(tc.method, tc.path, nil)
if exposure, ok := publicDemoCommercialPolicyForRequest(req); ok {
t.Fatalf("%s %s unexpectedly matched public demo commercial policy with exposure=%q", tc.method, tc.path, exposure)
}
})
}
}
func routeBelongsToPublicDemoCommercialBoundary(route string) bool {
switch {
case route == "/auth/trial-activate":
return true
case route == licensePurchaseStartPath:
return true
case route == "GET "+licensePurchaseStartPath:
return true
case route == "GET /api/license/runtime-capabilities":
return false
case strings.HasPrefix(route, "/api/license/"):
return true
case strings.HasPrefix(route, "GET /api/license/"):
return true
case strings.HasPrefix(route, "POST /api/license/"):
return true
case route == "/api/truenas/connections/preview":
return true
case route == "/api/truenas/connections/":
return true
case route == "POST /api/truenas/connections/preview":
return true
case route == "POST /api/truenas/connections/{id}/preview":
return true
case route == "/api/vmware/connections/preview":
return true
case route == "/api/vmware/connections/":
return true
case route == "POST /api/vmware/connections/preview":
return true
case route == "POST /api/vmware/connections/{id}/preview":
return true
case strings.HasPrefix(route, "GET /api/upgrade-metrics/"):
return true
case strings.HasPrefix(route, "POST /api/upgrade-metrics/"):
return true
case strings.HasPrefix(route, "PUT /api/upgrade-metrics/"):
return true
case route == "GET /api/admin/upgrade-metrics-funnel":
return true
case route == "GET /api/admin/orgs/{id}/billing-state":
return true
case route == "PUT /api/admin/orgs/{id}/billing-state":
return true
default:
return false
}
}
func publicDemoCommercialRouterRouteInventory() []string {
routes := make([]string, 0, len(publicDemoCommercialPolicies))
seen := map[string]struct{}{}
for _, policy := range publicDemoCommercialPolicies {
route := publicDemoCommercialRouterRoute(policy.route)
if _, ok := seen[route]; ok {
continue
}
seen[route] = struct{}{}
routes = append(routes, route)
}
return routes
}
func publicDemoCommercialRouterRoute(route string) string {
switch route {
case "POST /api/truenas/connections/preview":
return "/api/truenas/connections/preview"
case "POST /api/truenas/connections/{id}/preview":
return "/api/truenas/connections/"
case "POST /api/vmware/connections/preview":
return "/api/vmware/connections/preview"
case "POST /api/vmware/connections/{id}/preview":
return "/api/vmware/connections/"
default:
return route
}
}