48 lines
2.8 KiB
TypeScript
48 lines
2.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
createMemoryDb, migrateSchema, closeDb, createUser, getUserById,
|
|
getUserPermissions, setUserAccess, updateUser, deleteUser,
|
|
createRefreshSession, revokeRefreshSession, revokeSessionById, revokeAllSessionsForUser,
|
|
enablePortalRevocations,
|
|
} from '../src/index.js'
|
|
|
|
const target = 'https://mm.example.invalid/_internal/mcp/portal/revocations'
|
|
describe('integrated portal revocation outbox', () => {
|
|
it('opts in explicitly and retains deletion event independently of user FK', () => {
|
|
const { db, sqlite } = createMemoryDb(); migrateSchema(sqlite)
|
|
try {
|
|
const user = createUser(db, {email:'owned@example.invalid',name:'owned',passwordHash:'synthetic',isAdmin:false})
|
|
setUserAccess(db,user.id,['mm'],['old'])
|
|
expect(sqlite.prepare('SELECT count(*) AS n FROM portal_revocation_outbox').get()).toEqual({n:0})
|
|
enablePortalRevocations(db,target)
|
|
setUserAccess(db,user.id,['mm'],['new']); deleteUser(db,user.id)
|
|
const rows = sqlite.prepare('SELECT body FROM portal_revocation_outbox ORDER BY sequence').all() as {body:string}[]
|
|
expect(rows.map(row=>JSON.parse(row.body).type)).toEqual(['permissions.changed','user.disabled'])
|
|
for (const row of rows) expect(JSON.parse(row.body)).toMatchObject({version:2,userId:user.id})
|
|
expect(getUserById(db,user.id)).toBeUndefined()
|
|
} finally { closeDb(sqlite) }
|
|
})
|
|
it('enqueue failure prevents success of every genuine access/disable/delete/session mutation', () => {
|
|
const { db, sqlite } = createMemoryDb(); migrateSchema(sqlite)
|
|
try {
|
|
const user = createUser(db,{email:'atomic@example.invalid',name:'owned',passwordHash:'synthetic',isAdmin:false})
|
|
setUserAccess(db,user.id,['mm'],['old'])
|
|
const session = createRefreshSession(db,user.id,'owned-refresh',new Date(Date.now()+60000))
|
|
enablePortalRevocations(db,target)
|
|
sqlite.exec("CREATE TRIGGER deny_revocation BEFORE INSERT ON portal_revocation_outbox BEGIN SELECT RAISE(ABORT,'OWNED_ENQUEUE_FAILURE'); END")
|
|
for (const mutation of [
|
|
()=>setUserAccess(db,user.id,[],[]), ()=>updateUser(db,user.id,{isAdmin:true}),
|
|
()=>updateUser(db,user.id,{disabled:true}), ()=>deleteUser(db,user.id),
|
|
()=>revokeSessionById(db,session), ()=>revokeRefreshSession(db,'owned-refresh'),
|
|
()=>revokeAllSessionsForUser(db,user.id),
|
|
]) {
|
|
expect(mutation).toThrow('OWNED_ENQUEUE_FAILURE')
|
|
expect(getUserById(db,user.id)).toMatchObject({isAdmin:false,disabled:false})
|
|
expect(getUserPermissions(db,user.id)).toEqual(['old'])
|
|
expect(sqlite.prepare('SELECT revoked_at FROM refresh_sessions WHERE id=?').get(session)).toEqual({revoked_at:null})
|
|
expect(sqlite.prepare('SELECT count(*) AS n FROM portal_revocation_outbox').get()).toEqual({n:0})
|
|
}
|
|
} finally { closeDb(sqlite) }
|
|
})
|
|
})
|