Files
accounted/lib/auth/__tests__/scope-catalog.test.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

100 lines
3.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
ALL_SCOPES,
API_KEY_SCOPES,
SCOPE_GROUPS,
TOOL_COUNT_BY_SCOPE,
TOOL_SCOPE_MAP,
scopeKind,
type ApiKeyScope,
} from '../scope-catalog'
import * as apiKeys from '../api-keys'
describe('SCOPE_GROUPS', () => {
it('covers every scope in API_KEY_SCOPES exactly once', () => {
const occurrences = new Map<ApiKeyScope, number>()
for (const group of SCOPE_GROUPS) {
for (const scope of group.scopes) {
occurrences.set(scope, (occurrences.get(scope) ?? 0) + 1)
}
}
const missing = ALL_SCOPES.filter((s) => !occurrences.has(s))
const duplicated = [...occurrences].filter(([, n]) => n > 1).map(([s]) => s)
expect(missing).toEqual([])
expect(duplicated).toEqual([])
})
it('only references scopes that exist in the catalogue', () => {
for (const group of SCOPE_GROUPS) {
for (const scope of group.scopes) {
expect(scope in API_KEY_SCOPES).toBe(true)
}
}
})
it('has unique domains and lists the read scope first', () => {
const domains = SCOPE_GROUPS.map((g) => g.domain)
expect(new Set(domains).size).toBe(domains.length)
for (const group of SCOPE_GROUPS) {
const readIndex = group.scopes.findIndex((s) => scopeKind(s) === 'read')
if (readIndex !== -1) expect(readIndex).toBe(0)
}
})
})
describe('TOOL_COUNT_BY_SCOPE', () => {
it('has an entry for every scope and none for anything else', () => {
expect(Object.keys(TOOL_COUNT_BY_SCOPE).sort()).toEqual([...ALL_SCOPES].sort())
})
it('equals the number of TOOL_SCOPE_MAP entries mapped to each scope', () => {
for (const scope of ALL_SCOPES) {
const expected = Object.values(TOOL_SCOPE_MAP).filter((s) => s === scope).length
expect(TOOL_COUNT_BY_SCOPE[scope], scope).toBe(expected)
}
const total = Object.values(TOOL_COUNT_BY_SCOPE).reduce((a, b) => a + b, 0)
expect(total).toBe(Object.keys(TOOL_SCOPE_MAP).length)
})
it('only maps tools to scopes that exist', () => {
for (const [tool, scope] of Object.entries(TOOL_SCOPE_MAP)) {
expect(scope in API_KEY_SCOPES, tool).toBe(true)
}
})
})
describe('API_KEY_SCOPES labels', () => {
it('carries no hand-written tool counts (they are derived)', () => {
for (const [scope, meta] of Object.entries(API_KEY_SCOPES)) {
expect(meta.description, scope).not.toMatch(/\(\d+ verktyg\)/)
expect(meta.label, scope).not.toMatch(/\(\d+ verktyg\)/)
}
})
it('formats every label as "Område: verb"', () => {
for (const meta of Object.values(API_KEY_SCOPES)) {
expect(meta.label).toMatch(/^[^:]+: .+$/)
}
})
})
describe('scopeKind', () => {
it('treats :read as read and everything else as an elevated grant', () => {
expect(scopeKind('transactions:read')).toBe('read')
expect(scopeKind('transactions:write')).toBe('write')
expect(scopeKind('webhooks:manage')).toBe('write')
expect(scopeKind('pending_operations:approve')).toBe('write')
expect(scopeKind('reconciliation:signoff')).toBe('write')
})
})
describe('api-keys re-exports', () => {
it('exposes the same catalogue objects so server imports keep working', () => {
expect(apiKeys.API_KEY_SCOPES).toBe(API_KEY_SCOPES)
expect(apiKeys.SCOPE_GROUPS).toBe(SCOPE_GROUPS)
expect(apiKeys.TOOL_SCOPE_MAP).toBe(TOOL_SCOPE_MAP)
expect(apiKeys.TOOL_COUNT_BY_SCOPE).toBe(TOOL_COUNT_BY_SCOPE)
expect(apiKeys.ALL_SCOPES).toBe(ALL_SCOPES)
})
})