Files
pulse/internal/websocket/hub_multitenant_test.go
T
rcourtman c4ca169e2b feat: add multi-tenant isolation foundation (disabled by default)
Implements multi-tenant infrastructure for organization-based data isolation.
Feature is gated behind PULSE_MULTI_TENANT_ENABLED env var and requires
Enterprise license - no impact on existing users.

Core components:
- TenantMiddleware: extracts org ID, validates access, 501/402 responses
- AuthorizationChecker: token/user access validation for organizations
- MultiTenantChecker: WebSocket upgrade gating with license check
- Per-tenant audit logging via LogAuditEventForTenant
- Organization model with membership support

Gating behavior:
- Feature flag disabled: 501 Not Implemented for non-default orgs
- Flag enabled, no license: 402 Payment Required
- Default org always works regardless of flag/license

Documentation added: docs/MULTI_TENANT.md
2026-01-23 21:42:27 +00:00

61 lines
1.4 KiB
Go

package websocket
import (
"context"
"net/http"
"net/http/httptest"
"testing"
)
type fakeMultiTenantChecker struct {
result MultiTenantCheckResult
}
func (f fakeMultiTenantChecker) CheckMultiTenant(ctx context.Context, orgID string) MultiTenantCheckResult {
return f.result
}
func TestHandleWebSocket_MultiTenantDisabled(t *testing.T) {
hub := NewHub(nil)
hub.SetMultiTenantChecker(fakeMultiTenantChecker{
result: MultiTenantCheckResult{
Allowed: false,
FeatureEnabled: false,
Licensed: false,
Reason: "disabled",
},
})
req := httptest.NewRequest(http.MethodGet, "http://example.com/ws", nil)
req.Header.Set("X-Pulse-Org-ID", "tenant-a")
rec := httptest.NewRecorder()
hub.HandleWebSocket(rec, req)
if rec.Code != http.StatusNotImplemented {
t.Fatalf("expected status %d, got %d", http.StatusNotImplemented, rec.Code)
}
}
func TestHandleWebSocket_MultiTenantUnlicensed(t *testing.T) {
hub := NewHub(nil)
hub.SetMultiTenantChecker(fakeMultiTenantChecker{
result: MultiTenantCheckResult{
Allowed: false,
FeatureEnabled: true,
Licensed: false,
Reason: "unlicensed",
},
})
req := httptest.NewRequest(http.MethodGet, "http://example.com/ws", nil)
req.Header.Set("X-Pulse-Org-ID", "tenant-a")
rec := httptest.NewRecorder()
hub.HandleWebSocket(rec, req)
if rec.Code != http.StatusPaymentRequired {
t.Fatalf("expected status %d, got %d", http.StatusPaymentRequired, rec.Code)
}
}