mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 12:15:42 +00:00
c4ca169e2b
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
61 lines
1.4 KiB
Go
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)
|
|
}
|
|
}
|