import { beforeEach, describe, expect, it } from 'vitest'
import type { AccessControl, IamAdapter } from '../../../core/types'
import { runAdapterCompliance } from '../../__compliance__/compliance'
import { IamMemoryAdapter } from '../index'
// Shared adapter compliance suite - every adapter must pass.
runAdapterCompliance('IamMemoryAdapter', () => new IamMemoryAdapter())
type A = 'read' | 'write'
type R = 'post' | 'comment'
type Ro = 'viewer' | 'editor'
type S = 'org-1'
describe('IamMemoryAdapter', () => {
let adapter: IamMemoryAdapter
beforeEach(() => {
adapter = new IamMemoryAdapter()
})
describe('IamAdapter.IPolicyStore', () => {
const policy: AccessControl.IPolicy = {
id: 'p1',
name: 'Test AccessControl.IPolicy',
algorithm: 'deny-overrides',
rules: [],
}
it('starts empty', async () => {
expect(await adapter.listPolicies()).toEqual([])
})
it('savePolicy + listPolicies', async () => {
await adapter.savePolicy(policy)
expect(await adapter.listPolicies()).toEqual([policy])
})
it('getPolicy returns policy or null', async () => {
expect(await adapter.getPolicy('p1')).toBeNull()
await adapter.savePolicy(policy)
expect(await adapter.getPolicy('p1')).toEqual(policy)
})
it('deletePolicy removes policy', async () => {
await adapter.savePolicy(policy)
await adapter.deletePolicy('p1')
expect(await adapter.listPolicies()).toEqual([])
})
it('savePolicy overwrites existing', async () => {
await adapter.savePolicy(policy)
const updated = { ...policy, name: 'Updated' }
await adapter.savePolicy(updated)
expect((await adapter.getPolicy('p1'))!.name).toBe('Updated')
})
})
describe('IamAdapter.IRoleStore', () => {
const role: AccessControl.IRole = {
id: 'viewer',
name: 'Viewer',
permissions: [{ action: 'read', resource: 'post' }],
}
it('starts empty', async () => {
expect(await adapter.listRoles()).toEqual([])
})
it('saveRole + listRoles', async () => {
await adapter.saveRole(role)
expect(await adapter.listRoles()).toEqual([role])
})
it('getRole returns role or null', async () => {
expect(await adapter.getRole('viewer')).toBeNull()
await adapter.saveRole(role)
expect(await adapter.getRole('viewer')).toEqual(role)
})
it('deleteRole removes role', async () => {
await adapter.saveRole(role)
await adapter.deleteRole('viewer')
expect(await adapter.listRoles()).toEqual([])
})
})
describe('IamAdapter.ISubjectStore', () => {
it('getSubjectRoles returns empty for unknown subject', async () => {
expect(await adapter.getSubjectRoles('unknown')).toEqual([])
})
it('assignRole + getSubjectRoles', async () => {
await adapter.assignRole('user-1', 'viewer')
expect(await adapter.getSubjectRoles('user-1')).toEqual(['viewer'])
})
it('assignRole deduplicates roles in getSubjectRoles', async () => {
await adapter.assignRole('user-1', 'viewer')
await adapter.assignRole('user-1', 'viewer')
const roles = await adapter.getSubjectRoles('user-1')
expect(roles).toEqual(['viewer'])
})
it('revokeRole removes role', async () => {
await adapter.assignRole('user-1', 'viewer')
await adapter.assignRole('user-1', 'editor')
await adapter.revokeRole('user-1', 'viewer')
expect(await adapter.getSubjectRoles('user-1')).toEqual(['editor'])
})
it('revokeRole is no-op for unknown subject', async () => {
await adapter.revokeRole('unknown', 'viewer') // should not throw
})
it('getSubjectScopedRoles returns scoped assignments', async () => {
await adapter.assignRole('user-1', 'editor', 'org-1')
await adapter.assignRole('user-1', 'viewer') // no scope
const scoped = await adapter.getSubjectScopedRoles('user-1')
expect(scoped).toEqual([{ role: 'editor', scope: 'org-1' }])
})
it('getSubjectAttributes returns empty for unknown subject', async () => {
expect(await adapter.getSubjectAttributes('unknown')).toEqual({})
})
it('setSubjectAttributes merges attributes', async () => {
await adapter.setSubjectAttributes('user-1', { a: 1, b: 'two' })
await adapter.setSubjectAttributes('user-1', { c: true })
expect(await adapter.getSubjectAttributes('user-1')).toEqual({ a: 1, b: 'two', c: true })
})
})
describe('constructor init', () => {
it('initializes from init object', async () => {
const adapter = new IamMemoryAdapter({
policies: [{ id: 'p1', name: 'P', algorithm: 'deny-overrides', rules: [] }],
roles: [{ id: 'viewer', name: 'Viewer', permissions: [] }],
assignments: { 'user-1': ['viewer'] },
attributes: { 'user-1': { level: 5 } },
})
expect(await adapter.listPolicies()).toEqual([{ id: 'p1', name: 'P', algorithm: 'deny-overrides', rules: [] }])
expect(await adapter.listRoles()).toEqual([{ id: 'viewer', name: 'Viewer', permissions: [] }])
expect(await adapter.getSubjectRoles('user-1')).toEqual(['viewer'])
expect(await adapter.getSubjectAttributes('user-1')).toEqual({ level: 5 })
})
})
})