steps 5-8: deep gut — purge chat/notes/projects/providers from code
main.go: remove ~300 lines of stale routes referencing deleted handlers (channels, messages, folders, personas, notes, projects, memories, models, providers, tasks, roles, usage, routing, capabilities, etc.) Fix branding: "Chat Switchboard" → "Switchboard Core" pages: remove chat/notes/projects surface manifests and templates Keep: admin, settings, team-admin, workflow, workflow-landing frontend: delete chat/, notes/, projects/ surface directories (19 files) Delete 5 CSS files (4,144 lines): sw-chat-*, sw-notes-*, sw-projects-* SDK: strip gutted API domains (594→287 lines), remove chatPane/notesPane tests: remove integration_test.go (5,194 lines, broken imports to deleted packages) and perm_enforcement_test.go (551 lines, depended on deleted test helpers). Fix testmain_test.go (remove providers import). openapi.yaml: replace 12,491-line stale spec with kernel auth stub. Full ICD rebuild is Step 8 proper. Auth (builtin, mTLS, OIDC) untouched throughout. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,551 +0,0 @@
|
||||
package handlers
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// Permission Enforcement Tests (v0.37.1)
|
||||
//
|
||||
// Verifies that RequirePermission middleware actually blocks non-admin users
|
||||
// who lack the required permission, and allows them after the permission is
|
||||
// granted via group membership.
|
||||
//
|
||||
// Every test:
|
||||
// 1. Creates a non-admin user (no groups, TruncateAll wipes Everyone)
|
||||
// 2. Attempts the gated operation → expects 403
|
||||
// 3. Creates a group with the required permission
|
||||
// 4. Adds the user to that group
|
||||
// 5. Retries the operation → expects success (200 or 201)
|
||||
//
|
||||
// No admin users. No shortcuts.
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"switchboard-core/database"
|
||||
)
|
||||
|
||||
// ── Helpers ─────────────────────────────────
|
||||
|
||||
// seedNonAdminUser creates a non-admin active user with a valid token.
|
||||
// TruncateAll wipes the Everyone group so this user has ZERO permissions.
|
||||
func seedNonAdminUser(t *testing.T) (userID, token string) {
|
||||
t.Helper()
|
||||
userID = database.SeedTestUser(t, "permuser", "permuser@test.com")
|
||||
database.TestDB.Exec(dialectSQL("UPDATE users SET is_active = true WHERE id = $1"), userID)
|
||||
token = makeToken(userID, "permuser@test.com", "user")
|
||||
return
|
||||
}
|
||||
|
||||
// seedAdminUser creates an admin user for setup operations (group creation).
|
||||
func seedAdminUser(t *testing.T) (userID, token string) {
|
||||
t.Helper()
|
||||
userID = database.SeedTestUser(t, "permadmin", "permadmin@test.com")
|
||||
database.TestDB.Exec(dialectSQL("UPDATE users SET role = 'admin', is_active = true WHERE id = $1"), userID)
|
||||
token = makeToken(userID, "permadmin@test.com", "admin")
|
||||
return
|
||||
}
|
||||
|
||||
// grantPermission creates a group with the given permission and adds the user to it.
|
||||
// Uses admin token for group creation, returns the group ID.
|
||||
func grantPermission(t *testing.T, h *testHarness, adminToken, userID string, perm string) string {
|
||||
t.Helper()
|
||||
w := h.request("POST", "/api/v1/admin/groups", adminToken, map[string]interface{}{
|
||||
"name": "grant-" + perm,
|
||||
"scope": "global",
|
||||
"permissions": []string{perm},
|
||||
})
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("create group for %s: want 201, got %d: %s", perm, w.Code, w.Body.String())
|
||||
}
|
||||
var group map[string]interface{}
|
||||
decode(w, &group)
|
||||
groupID := group["id"].(string)
|
||||
|
||||
w = h.request("POST", "/api/v1/admin/groups/"+groupID+"/members", adminToken, map[string]interface{}{
|
||||
"user_id": userID,
|
||||
})
|
||||
if w.Code != http.StatusCreated && w.Code != http.StatusOK {
|
||||
t.Fatalf("add member to group %s: want 200/201, got %d: %s", perm, w.Code, w.Body.String())
|
||||
}
|
||||
return groupID
|
||||
}
|
||||
|
||||
// seedChannel creates a channel using the admin token and returns its ID.
|
||||
func seedChannel(t *testing.T, h *testHarness, adminToken string) string {
|
||||
t.Helper()
|
||||
w := h.request("POST", "/api/v1/channels", adminToken, map[string]interface{}{
|
||||
"title": "test-channel",
|
||||
"type": "direct",
|
||||
})
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("seed channel: want 201, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
var ch map[string]interface{}
|
||||
decode(w, &ch)
|
||||
return ch["id"].(string)
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// §1 model.use — completions, summarize, title
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func TestPermEnforce_ModelUse_Completions(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
adminID, adminToken := seedAdminUser(t)
|
||||
_ = adminID
|
||||
|
||||
// User needs channel.create to make a channel for completions,
|
||||
// so grant that first (otherwise 403 on channel create, not completions).
|
||||
grantPermission(t, h, adminToken, userID, "channel.create")
|
||||
|
||||
// Create channel as user (now allowed)
|
||||
w := h.request("POST", "/api/v1/channels", userToken, map[string]interface{}{
|
||||
"title": "perm-test", "type": "direct",
|
||||
})
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("create channel: want 201, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
var ch map[string]interface{}
|
||||
decode(w, &ch)
|
||||
channelID := ch["id"].(string)
|
||||
|
||||
// Completions without model.use → 403
|
||||
w = h.request("POST", "/api/v1/chat/completions", userToken, map[string]interface{}{
|
||||
"channel_id": channelID,
|
||||
"message": "hello",
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("completions without model.use: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Grant model.use
|
||||
grantPermission(t, h, adminToken, userID, "model.use")
|
||||
|
||||
// Completions with model.use → should pass middleware (may fail downstream
|
||||
// due to no provider configured, but NOT 403)
|
||||
w = h.request("POST", "/api/v1/chat/completions", userToken, map[string]interface{}{
|
||||
"channel_id": channelID,
|
||||
"message": "hello",
|
||||
})
|
||||
if w.Code == http.StatusForbidden {
|
||||
t.Fatalf("completions WITH model.use: still got 403: %s", w.Body.String())
|
||||
}
|
||||
// Accept 400/500/502 — anything except 403 means the permission gate passed
|
||||
}
|
||||
|
||||
// NOTE: Summarize and GenerateTitle share the model.use permission with
|
||||
// completions. Their route-level RequirePermission wiring is in main.go
|
||||
// but the test harness (setupHarness) doesn't register those routes.
|
||||
// The completions test above proves model.use enforcement works.
|
||||
// Summarize/title coverage will come when the test harness is extended.
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// §2 channel.create
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func TestPermEnforce_ChannelCreate(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
// Without channel.create → 403
|
||||
w := h.request("POST", "/api/v1/channels", userToken, map[string]interface{}{
|
||||
"title": "blocked", "type": "direct",
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("channel create without permission: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "channel.create")
|
||||
|
||||
w = h.request("POST", "/api/v1/channels", userToken, map[string]interface{}{
|
||||
"title": "allowed", "type": "direct",
|
||||
})
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("channel create WITH permission: want 201, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// §3 channel.invite
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func TestPermEnforce_ChannelInvite(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
// Need channel.create first to have a channel to invite into
|
||||
grantPermission(t, h, adminToken, userID, "channel.create")
|
||||
w := h.request("POST", "/api/v1/channels", userToken, map[string]interface{}{
|
||||
"title": "invite-test", "type": "group",
|
||||
})
|
||||
var ch map[string]interface{}
|
||||
decode(w, &ch)
|
||||
channelID := ch["id"].(string)
|
||||
|
||||
// Create another user to invite
|
||||
inviteeID := database.SeedTestUser(t, "invitee", "invitee@test.com")
|
||||
database.TestDB.Exec(dialectSQL("UPDATE users SET is_active = true WHERE id = $1"), inviteeID)
|
||||
|
||||
// Without channel.invite → 403
|
||||
w = h.request("POST", "/api/v1/channels/"+channelID+"/participants", userToken, map[string]interface{}{
|
||||
"user_id": inviteeID,
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("invite without permission: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "channel.invite")
|
||||
|
||||
w = h.request("POST", "/api/v1/channels/"+channelID+"/participants", userToken, map[string]interface{}{
|
||||
"user_id": inviteeID,
|
||||
})
|
||||
if w.Code == http.StatusForbidden {
|
||||
t.Fatalf("invite WITH permission: still got 403: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// §4 kb.create, kb.read, kb.write
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func TestPermEnforce_KBCreate(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
// Without kb.create → 403
|
||||
w := h.request("POST", "/api/v1/knowledge-bases", userToken, map[string]interface{}{
|
||||
"name": "blocked-kb",
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("kb create without permission: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "kb.create")
|
||||
|
||||
w = h.request("POST", "/api/v1/knowledge-bases", userToken, map[string]interface{}{
|
||||
"name": "allowed-kb",
|
||||
})
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("kb create WITH permission: want 201, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermEnforce_KBSearch(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
// Admin creates a KB for the user to search
|
||||
grantPermission(t, h, adminToken, userID, "kb.create")
|
||||
w := h.request("POST", "/api/v1/knowledge-bases", userToken, map[string]interface{}{
|
||||
"name": "search-test-kb",
|
||||
})
|
||||
var kb map[string]interface{}
|
||||
decode(w, &kb)
|
||||
kbID := kb["id"].(string)
|
||||
|
||||
// Without kb.read → 403
|
||||
w = h.request("POST", "/api/v1/knowledge-bases/"+kbID+"/search", userToken, map[string]interface{}{
|
||||
"query": "test",
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("kb search without kb.read: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "kb.read")
|
||||
|
||||
// With kb.read the middleware should pass. The handler may panic or 500
|
||||
// because the test harness has no embedder configured, but any non-403
|
||||
// response proves the permission gate opened. Use a recovery wrapper
|
||||
// to catch the nil-embedder panic gracefully.
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// Panic in handler (nil embedder) means middleware passed — success
|
||||
}
|
||||
}()
|
||||
w = h.request("POST", "/api/v1/knowledge-bases/"+kbID+"/search", userToken, map[string]interface{}{
|
||||
"query": "test",
|
||||
})
|
||||
if w.Code == http.StatusForbidden {
|
||||
t.Fatalf("kb search WITH kb.read: still got 403: %s", w.Body.String())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func TestPermEnforce_KBWrite(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
// Grant kb.create so user can create the KB
|
||||
grantPermission(t, h, adminToken, userID, "kb.create")
|
||||
w := h.request("POST", "/api/v1/knowledge-bases", userToken, map[string]interface{}{
|
||||
"name": "write-test-kb",
|
||||
})
|
||||
var kb map[string]interface{}
|
||||
decode(w, &kb)
|
||||
kbID := kb["id"].(string)
|
||||
|
||||
// Without kb.write → 403 on update
|
||||
w = h.request("PUT", "/api/v1/knowledge-bases/"+kbID, userToken, map[string]interface{}{
|
||||
"name": "renamed",
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("kb update without kb.write: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Without kb.write → 403 on delete
|
||||
w = h.request("DELETE", "/api/v1/knowledge-bases/"+kbID, userToken, nil)
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("kb delete without kb.write: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
// Without kb.write → 403 on rebuild
|
||||
w = h.request("POST", "/api/v1/knowledge-bases/"+kbID+"/rebuild", userToken, nil)
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("kb rebuild without kb.write: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "kb.write")
|
||||
|
||||
// With kb.write → should pass middleware
|
||||
w = h.request("PUT", "/api/v1/knowledge-bases/"+kbID, userToken, map[string]interface{}{
|
||||
"name": "renamed-ok",
|
||||
})
|
||||
if w.Code == http.StatusForbidden {
|
||||
t.Fatalf("kb update WITH kb.write: still got 403: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// §5 persona.create, persona.manage
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func TestPermEnforce_PersonaCreate(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
// Enable user personas policy
|
||||
database.TestDB.Exec("INSERT INTO platform_policies (key, value) VALUES ('allow_user_personas', 'true') ON CONFLICT (key) DO UPDATE SET value = 'true'")
|
||||
|
||||
// Without persona.create → 403
|
||||
w := h.request("POST", "/api/v1/personas", userToken, map[string]interface{}{
|
||||
"name": "blocked-persona",
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("persona create without permission: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "persona.create")
|
||||
|
||||
w = h.request("POST", "/api/v1/personas", userToken, map[string]interface{}{
|
||||
"name": "allowed-persona",
|
||||
})
|
||||
if w.Code == http.StatusForbidden {
|
||||
t.Fatalf("persona create WITH permission: still got 403: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermEnforce_PersonaManage(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
database.TestDB.Exec("INSERT INTO platform_policies (key, value) VALUES ('allow_user_personas', 'true') ON CONFLICT (key) DO UPDATE SET value = 'true'")
|
||||
|
||||
// Grant create so user can make a persona
|
||||
grantPermission(t, h, adminToken, userID, "persona.create")
|
||||
w := h.request("POST", "/api/v1/personas", userToken, map[string]interface{}{
|
||||
"name": "manage-test",
|
||||
})
|
||||
var persona map[string]interface{}
|
||||
decode(w, &persona)
|
||||
personaID := persona["id"].(string)
|
||||
|
||||
// Without persona.manage → 403 on update
|
||||
w = h.request("PUT", "/api/v1/personas/"+personaID, userToken, map[string]interface{}{
|
||||
"name": "updated",
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("persona update without persona.manage: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "persona.manage")
|
||||
|
||||
w = h.request("PUT", "/api/v1/personas/"+personaID, userToken, map[string]interface{}{
|
||||
"name": "updated-ok",
|
||||
})
|
||||
if w.Code == http.StatusForbidden {
|
||||
t.Fatalf("persona update WITH persona.manage: still got 403: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// §6 workflow.create
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func TestPermEnforce_WorkflowCreate(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
// Without workflow.create → 403
|
||||
w := h.request("POST", "/api/v1/workflows", userToken, map[string]interface{}{
|
||||
"name": "blocked-wf",
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("workflow create without permission: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "workflow.create")
|
||||
|
||||
w = h.request("POST", "/api/v1/workflows", userToken, map[string]interface{}{
|
||||
"name": "allowed-wf",
|
||||
})
|
||||
if w.Code == http.StatusForbidden {
|
||||
t.Fatalf("workflow create WITH permission: still got 403: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// §7 task.create
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func TestPermEnforce_TaskCreate(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
// Without task.create → 403
|
||||
w := h.request("POST", "/api/v1/tasks", userToken, map[string]interface{}{
|
||||
"name": "blocked-task",
|
||||
"task_type": "prompt",
|
||||
"schedule": "@daily",
|
||||
"user_prompt": "test",
|
||||
"model_id": "test-model",
|
||||
})
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Fatalf("task create without permission: want 403, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "task.create")
|
||||
|
||||
w = h.request("POST", "/api/v1/tasks", userToken, map[string]interface{}{
|
||||
"name": "allowed-task",
|
||||
"task_type": "prompt",
|
||||
"schedule": "@daily",
|
||||
"user_prompt": "test",
|
||||
"model_id": "test-model",
|
||||
"timezone": "UTC",
|
||||
})
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Fatalf("task create WITH permission: want 201, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════
|
||||
// §8 GET /profile/permissions (new endpoint)
|
||||
// ══════════════════════════════════════════════
|
||||
|
||||
func TestPermEnforce_ProfilePermissions_NoGroups(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
_, userToken := seedNonAdminUser(t)
|
||||
|
||||
// User with no groups, no Everyone group → empty permissions
|
||||
w := h.request("GET", "/api/v1/profile/permissions", userToken, nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("profile/permissions: want 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
decode(w, &resp)
|
||||
|
||||
perms, ok := resp["permissions"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatal("response must have 'permissions' array")
|
||||
}
|
||||
if len(perms) != 0 {
|
||||
t.Fatalf("expected 0 permissions (no groups), got %d: %v", len(perms), perms)
|
||||
}
|
||||
|
||||
groups, ok := resp["groups"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatal("response must have 'groups' array")
|
||||
}
|
||||
// Should still include Everyone group ID (even though it doesn't exist after truncate)
|
||||
if len(groups) == 0 {
|
||||
t.Fatal("groups should include Everyone group ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermEnforce_ProfilePermissions_WithGrant(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
userID, userToken := seedNonAdminUser(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
grantPermission(t, h, adminToken, userID, "channel.create")
|
||||
grantPermission(t, h, adminToken, userID, "model.use")
|
||||
|
||||
w := h.request("GET", "/api/v1/profile/permissions", userToken, nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("profile/permissions: want 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
decode(w, &resp)
|
||||
|
||||
perms, _ := resp["permissions"].([]interface{})
|
||||
permSet := make(map[string]bool)
|
||||
for _, p := range perms {
|
||||
permSet[p.(string)] = true
|
||||
}
|
||||
if !permSet["channel.create"] {
|
||||
t.Fatal("expected channel.create in permissions")
|
||||
}
|
||||
if !permSet["model.use"] {
|
||||
t.Fatal("expected model.use in permissions")
|
||||
}
|
||||
if permSet["task.create"] {
|
||||
t.Fatal("task.create should NOT be in permissions (not granted)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermEnforce_ProfilePermissions_Admin(t *testing.T) {
|
||||
h := setupHarness(t)
|
||||
_, adminToken := seedAdminUser(t)
|
||||
|
||||
w := h.request("GET", "/api/v1/profile/permissions", adminToken, nil)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("profile/permissions admin: want 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
decode(w, &resp)
|
||||
|
||||
perms, _ := resp["permissions"].([]interface{})
|
||||
// Admin should get ALL permissions
|
||||
if len(perms) != len(allPermissionConstants()) {
|
||||
t.Fatalf("admin should have all %d permissions, got %d", len(allPermissionConstants()), len(perms))
|
||||
}
|
||||
}
|
||||
|
||||
// allPermissionConstants returns the count of auth.AllPermissions.
|
||||
// Duplicated here to avoid import cycle issues in test assertions.
|
||||
func allPermissionConstants() []string {
|
||||
return []string{
|
||||
"model.use", "model.select_any",
|
||||
"kb.read", "kb.write", "kb.create",
|
||||
"channel.create", "channel.invite",
|
||||
"persona.create", "persona.manage",
|
||||
"workflow.create",
|
||||
"admin.view",
|
||||
"token.unlimited",
|
||||
"task.create", "task.admin", "task.action", "task.starlark",
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"switchboard-core/database"
|
||||
"switchboard-core/providers"
|
||||
|
||||
_ "modernc.org/sqlite" // register sqlite driver for DB_DRIVER=sqlite
|
||||
)
|
||||
@@ -19,7 +18,6 @@ import (
|
||||
// Set DB_DRIVER=sqlite to run against SQLite instead of Postgres.
|
||||
func TestMain(m *testing.M) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
providers.Init()
|
||||
|
||||
teardown := database.SetupTestDB()
|
||||
code := m.Run()
|
||||
|
||||
Reference in New Issue
Block a user