Add comprehensive testing covering 420 unit/handler tests across 24 Vitest files, 9 Playwright E2E specs, accessibility scans, and visual regression. Includes GitHub Actions CI, Husky pre-push hook, and TESTING.md docs.
112 lines
4.3 KiB
JavaScript
112 lines
4.3 KiB
JavaScript
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')
|
|
})
|
|
})
|
|
})
|