16 lines
2.6 KiB
TypeScript
16 lines
2.6 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
import { server } from '../../apps/backend/src/server.js'
|
|
|
|
let base=''
|
|
beforeAll(async()=>{await new Promise<void>(resolve=>server.listen(0,'127.0.0.1',resolve));const address=server.address();if(!address||typeof address==='string')throw new Error('no address');base=`http://127.0.0.1:${address.port}`})
|
|
afterAll(async()=>{await new Promise<void>((resolve,reject)=>server.close(e=>e?reject(e):resolve()))})
|
|
async function post(path:string,value:unknown,token?:string){return fetch(`${base}${path}`,{method:'POST',headers:{'content-type':'application/json',...(token?{authorization:`Bearer ${token}`}:{})},body:JSON.stringify(value)})}
|
|
|
|
describe('backend boundary',()=>{
|
|
it('AT-001 exposes a secret-free readiness response',async()=>{const data=await fetch(`${base}/health`).then(r=>r.json());expect(data).toMatchObject({status:'ok',ready:true,twitchMode:'synthetic'});expect(JSON.stringify(data)).not.toMatch(/secret|token/i)})
|
|
it('AT-005 establishes a session without creating a character',async()=>{const r=await post('/api/extension/session',{token:'dev:nobody:Nobody'});expect(r.status).toBe(200);const data=await r.json();expect(data.state.viewer).toBeNull()})
|
|
it('AT-011 binds the same stable chat and Extension identity',async()=>{const id=`viewer-${Date.now()}`;expect((await post('/api/dev/spawn',{command:'!spawn',externalEventId:`e-${id}`,twitchUserId:id,displayName:'Viewer',followerVerified:true})).status).toBe(200);const auth=await (await post('/api/extension/session',{token:`dev:${id}:Viewer`})).json();expect(auth.twitchUserId).toBe(id);expect(auth.state.viewer.extensionBound).toBe(true);const s=auth.state,phase=s.phase;expect(phase.kind).toBe('player');const command=await post('/api/commands',{requestId:`r-${id}`,runId:s.runId,floorId:s.floor.floorId,phaseId:phase.phaseId,command:{type:'pass'}},auth.token);expect(command.status).toBe(200)})
|
|
it('AT-012 rejects unauthenticated controls',async()=>{const s=await fetch(`${base}/api/state`).then(r=>r.json()),phase=s.phase;const r=await post('/api/commands',{requestId:'unauth',runId:s.runId,floorId:s.floor.floorId,phaseId:phase.phaseId,command:{type:'pass'}});expect(r.status).toBe(409);expect((await r.json()).reason).toBe('UNAUTHENTICATED')})
|
|
it('AT-025 normalizes Channel Points redemptions and rejects unknown owners safely',async()=>{const r=await post('/api/dev/redemption',{externalEventId:'redemption-unknown',twitchUserId:'missing-viewer',rewardId:'local-resurrection'});expect(r.status).toBe(409);expect((await r.json()).reason).toBe('UNKNOWN_PLAYER')})
|
|
})
|