18 lines
2.9 KiB
TypeScript
18 lines
2.9 KiB
TypeScript
import { once } from 'node:events'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { createAdminServer } from '../../apps/backend/src/admin.js'
|
|
import { Game, type Clock, type IdGenerator, type RandomProvider } from '../../packages/domain/src/index.js'
|
|
import { generateFloor } from '../../packages/dungeon-generator/src/index.js'
|
|
|
|
const servers:ReturnType<typeof createAdminServer>[]=[]
|
|
afterEach(async()=>{for(const server of servers.splice(0))if(server.listening){server.close();await once(server,'close')}})
|
|
function harness(allowed=true){let id=0;const clock:Clock={now:()=>1_000},random:RandomProvider={next:()=>0},ids:IdGenerator={next:prefix=>`${prefix}-${++id}`};const game=new Game({clock,random,ids,generateFloor,resurrectionRewardId:'resurrection'},'admin-test');game.spawn({type:'spawn-requested',externalEventId:'spawn',twitchUserId:'u1',displayName:'One',broadcasterId:'b',followerVerified:true});game.bindExtension('u1');const server=createAdminServer({game,password:'test-admin-password',csrfToken:'test-csrf-token',isAllowedAddress:()=>allowed,onStateChange:()=>{},status:async()=>({uptimeSeconds:10,twitchReady:true,twitchMode:'synthetic',oauthConfigured:false,oauthAuthorized:false})});servers.push(server);return{game,server}}
|
|
async function start(server:ReturnType<typeof createAdminServer>){server.listen(0,'127.0.0.1');await once(server,'listening');const address=server.address();if(!address||typeof address==='string')throw new Error('No admin address');return`http://127.0.0.1:${address.port}`}
|
|
const authorization=`Basic ${Buffer.from('admin:test-admin-password').toString('base64')}`
|
|
|
|
describe('private admin panel',()=>{
|
|
it('rejects non-private clients before authentication',async()=>{const {server}=harness(false),base=await start(server);expect((await fetch(base,{headers:{authorization}})).status).toBe(403)})
|
|
it('requires authentication and does not expose its password',async()=>{const {server}=harness(),base=await start(server);expect((await fetch(base)).status).toBe(401);const page=await (await fetch(base,{headers:{authorization}})).text();expect(page).toContain('Twungeon Admin');expect(page).toContain('One');expect(page).not.toContain('test-admin-password')})
|
|
it('requires CSRF tokens and applies administrative actions',async()=>{const {game,server}=harness(),base=await start(server);const invalid=await fetch(`${base}/action`,{method:'POST',headers:{authorization,'content-type':'application/x-www-form-urlencoded'},body:'action=disconnect&userId=u1'});expect(invalid.status).toBe(403);const valid=await fetch(`${base}/action`,{method:'POST',headers:{authorization,'content-type':'application/x-www-form-urlencoded'},body:new URLSearchParams({_csrf:'test-csrf-token',action:'disconnect',userId:'u1'}),redirect:'manual'});expect(valid.status).toBe(303);expect(game.snapshot().players[0]).toMatchObject({connectionState:'disconnected',ap:0,guard:2})})
|
|
})
|