import { describe, it, expect } from 'vitest' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' const updatesDir = resolve(import.meta.dirname, '../../../server/api/updates') describe('Updates API auth guards', () => { describe('index.post.js (create)', () => { const source = readFileSync(resolve(updatesDir, 'index.post.js'), 'utf-8') it('requires auth via requireAuth(event)', () => { expect(source).toContain('requireAuth(event)') }) it('does not wrap requireAuth in a try/catch (auth is mandatory)', () => { // The public GET routes wrap requireAuth in try/catch to make it optional. // The create route must NOT do that — auth failure should halt the request. const lines = source.split('\n') const authLine = lines.findIndex(l => l.includes('requireAuth(event)')) expect(authLine).toBeGreaterThan(-1) // Check the line before requireAuth is not a try { const preceding = lines.slice(Math.max(0, authLine - 2), authLine).join(' ') expect(preceding).not.toMatch(/try\s*\{/) }) }) describe('[id].patch.js (edit)', () => { const source = readFileSync(resolve(updatesDir, '[id].patch.js'), 'utf-8') it('requires auth via requireAuth(event)', () => { expect(source).toContain('requireAuth(event)') }) it('does not wrap requireAuth in a try/catch (auth is mandatory)', () => { const lines = source.split('\n') const authLine = lines.findIndex(l => l.includes('requireAuth(event)')) expect(authLine).toBeGreaterThan(-1) const preceding = lines.slice(Math.max(0, authLine - 2), authLine).join(' ') expect(preceding).not.toMatch(/try\s*\{/) }) it('verifies ownership by comparing update.author with authenticated member ID', () => { expect(source).toContain('update.author.toString() !== memberId') }) it('throws 403 when user is not the author', () => { expect(source).toContain('statusCode: 403') }) }) describe('[id].delete.js (delete)', () => { const source = readFileSync(resolve(updatesDir, '[id].delete.js'), 'utf-8') it('requires auth via requireAuth(event)', () => { expect(source).toContain('requireAuth(event)') }) it('does not wrap requireAuth in a try/catch (auth is mandatory)', () => { const lines = source.split('\n') const authLine = lines.findIndex(l => l.includes('requireAuth(event)')) expect(authLine).toBeGreaterThan(-1) const preceding = lines.slice(Math.max(0, authLine - 2), authLine).join(' ') expect(preceding).not.toMatch(/try\s*\{/) }) it('verifies ownership by comparing update.author with authenticated member ID', () => { expect(source).toContain('update.author.toString() !== memberId') }) it('throws 403 when user is not the author', () => { expect(source).toContain('statusCode: 403') }) }) describe('index.get.js (list — public)', () => { const source = readFileSync(resolve(updatesDir, 'index.get.js'), 'utf-8') it('does NOT enforce requireAuth (public access allowed)', () => { // The route uses requireAuth inside a try/catch so unauthenticated // users can still access it — auth failure is caught and ignored. const lines = source.split('\n') const authLine = lines.findIndex(l => l.includes('requireAuth(event)')) // If requireAuth is present, it must be wrapped in try/catch if (authLine > -1) { const preceding = lines.slice(Math.max(0, authLine - 2), authLine).join(' ') expect(preceding).toMatch(/try\s*\{/) } // Either way, the route must not throw on unauthenticated access }) it('does not call requireAdmin', () => { expect(source).not.toContain('requireAdmin') }) }) describe('[id].get.js (get — public)', () => { const source = readFileSync(resolve(updatesDir, '[id].get.js'), 'utf-8') it('does NOT enforce requireAuth (public access allowed)', () => { const lines = source.split('\n') const authLine = lines.findIndex(l => l.includes('requireAuth(event)')) if (authLine > -1) { const preceding = lines.slice(Math.max(0, authLine - 2), authLine).join(' ') expect(preceding).toMatch(/try\s*\{/) } }) it('does not call requireAdmin', () => { expect(source).not.toContain('requireAdmin') }) }) })