Files
Pulse/internal/api/isolation_test.go
rcourtman 9072b8eaa8 feat: enhance API router with multi-tenant authorization
Router & Middleware:
- Add auth context middleware for user/token extraction
- Add tenant middleware with authorization checking
- Refactor middleware chain ordering for proper isolation
- Add router helpers for common patterns

Authentication & SSO:
- Enhance auth with tenant-aware context
- Update OIDC, SAML, and SSO handlers for multi-tenant
- Add RBAC handler improvements
- Add security enhancements

New Test Coverage:
- API foundation tests
- Auth and authorization tests
- Router state and general tests
- SSO handler CRUD tests
- WebSocket isolation tests
- Resource handler tests
2026-01-24 22:42:23 +00:00

96 lines
2.8 KiB
Go

package api
import (
"context"
"net/http/httptest"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestStateIsolation_Permanent verifies that tenant context is respected by router helper.
func TestStateIsolation_Permanent(t *testing.T) {
// Setup config with MT enabled
cfg := &config.Config{
MultiTenantEnabled: true,
}
// Create router with nil monitors initially
router := &Router{
config: cfg,
}
_ = router // Suppress unused
// Simulate request with Tenant Context
req := httptest.NewRequest("GET", "/api/state", nil)
ctx := context.WithValue(req.Context(), OrgIDContextKey, "tenant-1")
req = req.WithContext(ctx)
// Verify GetOrgID helper correctly extracts it
assert.Equal(t, "tenant-1", GetOrgID(req.Context()))
}
func TestGetTenantMonitor_Permanent(t *testing.T) {
cfg := &config.Config{MultiTenantEnabled: true}
defaultMon := &monitoring.Monitor{}
router := &Router{
config: cfg,
monitor: defaultMon,
}
// Case 1: Default Context -> Default Monitor
req := httptest.NewRequest("GET", "/", nil)
mon := router.getTenantMonitor(req.Context())
assert.Equal(t, defaultMon, mon)
// Case 2: Tenant Context but NO MT Monitor -> Default Fallback
ctx := context.WithValue(req.Context(), OrgIDContextKey, "tenant-1")
mon = router.getTenantMonitor(ctx)
assert.Equal(t, defaultMon, mon, "Should fallback to default monitor if mtMonitor is nil/failed")
}
func TestResourceIsolation_Permanent(t *testing.T) {
handlers := NewResourceHandlers()
// Create snapshots with nodes
// Note: FromNode uses n.ID as the Resource ID directly
defaultState := models.StateSnapshot{
Nodes: []models.Node{{ID: "node-1", Name: "proxmox-default"}},
}
tenantAState := models.StateSnapshot{
Nodes: []models.Node{{ID: "node-2", Name: "proxmox-tenant-a"}},
}
// Populate Default
handlers.PopulateFromSnapshot(defaultState)
// Populate Tenant A
handlers.PopulateFromSnapshotForTenant("tenant-a", tenantAState)
// Verify Default Store
defaultStore := handlers.Store()
node, ok := defaultStore.Get("node-1")
require.True(t, ok, "Default node 'node-1' should be present in default store")
assert.Equal(t, "proxmox-default", node.Name)
_, ok = defaultStore.Get("node-2")
assert.False(t, ok, "Tenant A node 'node-2' should not be in default store")
// Verify Tenant A Store
storeA := handlers.getStoreForTenant("tenant-a")
node, ok = storeA.Get("node-2")
require.True(t, ok, "Tenant A node 'node-2' should be present in Tenant A store")
assert.Equal(t, "proxmox-tenant-a", node.Name)
_, ok = storeA.Get("node-1")
assert.False(t, ok, "Default node 'node-1' should not be in Tenant A store")
}