import assert from 'node:assert/strict' import Fastify from 'fastify' process.env.AUTH_REQUIRED='true';process.env.AUTH_JWT_SECRET='own211-synthetic-secret';process.env.AUTH_ISSUER='https://own211.invalid';process.env.NODE_ENV='test' const {default:authPlugin,requireApiAuth}=await import('./auth.js') const app=Fastify();await app.register(authPlugin);app.addHook('preHandler',requireApiAuth);let calls=0 for(const url of ['/api/servers','/api/gre/tunnels','/api/servers/:id/ospf'])app.get(url,async()=>{calls++;return{ok:true}}) app.patch('/api/gre/tunnels',async()=>{calls++;return{ok:true}}) app.post('/api/servers/:id/ospf/optimize/preview',async()=>{calls++;return{ok:true}}) app.get('/api/auth/config',async()=>({public:true}));app.get('/health',async()=>({public:true}));await app.ready() const token=(permissions:string[])=>app.jwt.sign({sub:'own211',apps:['mm'],permissions,iss:process.env.AUTH_ISSUER},{expiresIn:'5m'}) let cases=0 for(const url of ['/api/servers','/api/%73ervers','/%61pi/servers','/%61%70%69/%73ervers'])for(const method of ['GET','HEAD'] as const){const before=calls,r=await app.inject({method,url});assert.equal(r.statusCode,401,url);assert.equal(calls,before);cases++} for(const url of ['/api/gre/tunnels','/api/%67re/tunnels','/%61pi/gre/tunnels'])for(const method of ['GET','HEAD','PATCH'] as const)for(const role of ['dashboard','reader','writer']){const before=calls,permissions=role==='dashboard'?['mm:dashboard:read']:role==='reader'?['mm:network:read']:['mm:network:write'],allowed=role==='writer'||(role==='reader'&&method!=='PATCH'),r=await app.inject({method,url,headers:{authorization:'Bearer '+token(permissions)}});assert.equal(r.statusCode,allowed?200:403,url+' '+method+' '+role);assert.equal(calls,before+(allowed?1:0));cases++} for(const method of ['GET','POST'] as const){const url=method==='GET'?'/api/servers/1/ospf':'/api/servers/1/ospf/optimize/preview';assert.equal((await app.inject({method,url,headers:{authorization:'Bearer '+token(['mm:network:read'])}})).statusCode,200);assert.equal((await app.inject({method,url,headers:{authorization:'Bearer '+token(['mm:servers:read'])}})).statusCode,403);cases+=2} for(const url of ['/health','/api/auth/config','/%61pi/auth/config']){assert.equal((await app.inject({url})).statusCode,200);cases++} await app.close();console.log('Canonical API auth: '+cases+' cases, denied handlers not called, HEAD and OSPF matched templates preserved')