mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-06-27 19:05:48 +08:00
- .githooks/pre-push: runs vue-tsc before every push; blocks TypeScript regressions locally without any manual developer setup - postinstall: git config core.hooksPath .githooks activates the hook automatically on yarn install (yarn 4, enableScripts: true) - tests/smoke/apiHandlers.smoke.test.ts: import-level smoke tests for all bug-report handlers + 3 core admin handlers — catches broken exports and top-level runtime errors without a DB or running server - tests/server/bugReport.test.ts: 16 unit tests covering comment validation, contact-string building, model schema fields, status enum, and patch logic Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
115 lines
4.0 KiB
TypeScript
115 lines
4.0 KiB
TypeScript
/**
|
|
* Unit tests for BugReport logic: validation, contact-string building,
|
|
* and model schema integrity. No database connection required.
|
|
*/
|
|
import { describe, it } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
|
|
import { BugReport } from '~~/server/models/BugReport'
|
|
|
|
// ── Contact-string helpers (mirror of index.post.ts logic) ───────────────────
|
|
|
|
function buildContact(user: { name?: string; email: string }, override?: string): string {
|
|
return String(
|
|
override || [user.name, user.email].filter(Boolean).join(' — ')
|
|
).slice(0, 200)
|
|
}
|
|
|
|
function validateComment(raw: unknown): string {
|
|
const comment = String(raw ?? '').trim()
|
|
if (!comment) throw new Error('comment_required')
|
|
return comment.slice(0, 4000)
|
|
}
|
|
|
|
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
|
|
describe('BugReport — comment validation', () => {
|
|
it('accepts a non-empty comment', () => {
|
|
assert.equal(validateComment('App crashed when I pressed PTT'), 'App crashed when I pressed PTT')
|
|
})
|
|
|
|
it('trims whitespace before validating', () => {
|
|
assert.equal(validateComment(' hello '), 'hello')
|
|
})
|
|
|
|
it('rejects empty string', () => {
|
|
assert.throws(() => validateComment(''), { message: 'comment_required' })
|
|
})
|
|
|
|
it('rejects whitespace-only string', () => {
|
|
assert.throws(() => validateComment(' '), { message: 'comment_required' })
|
|
})
|
|
|
|
it('rejects null/undefined', () => {
|
|
assert.throws(() => validateComment(null), { message: 'comment_required' })
|
|
assert.throws(() => validateComment(undefined), { message: 'comment_required' })
|
|
})
|
|
|
|
it('truncates comment at 4000 chars', () => {
|
|
const long = 'x'.repeat(5000)
|
|
assert.equal(validateComment(long).length, 4000)
|
|
})
|
|
})
|
|
|
|
describe('BugReport — contact string', () => {
|
|
it('joins name and email with em dash separator', () => {
|
|
assert.equal(buildContact({ name: 'Max', email: 'max@example.com' }), 'Max — max@example.com')
|
|
})
|
|
|
|
it('omits name when absent', () => {
|
|
assert.equal(buildContact({ email: 'max@example.com' }), 'max@example.com')
|
|
})
|
|
|
|
it('uses override string when provided', () => {
|
|
assert.equal(buildContact({ email: 'x@y.com' }, 'Custom Name — x@y.com'), 'Custom Name — x@y.com')
|
|
})
|
|
|
|
it('truncates contact at 200 chars', () => {
|
|
const long = 'x'.repeat(300)
|
|
assert.equal(buildContact({ email: 'a@b.com' }, long).length, 200)
|
|
})
|
|
})
|
|
|
|
describe('BugReport — model schema', () => {
|
|
it('model can be imported without a DB connection', () => {
|
|
assert.ok(BugReport, 'BugReport model must be importable')
|
|
})
|
|
|
|
it('schema defines expected fields', () => {
|
|
const paths = BugReport.schema.paths
|
|
assert.ok('comment' in paths, 'schema must have comment')
|
|
assert.ok('contact' in paths, 'schema must have contact')
|
|
assert.ok('status' in paths, 'schema must have status')
|
|
assert.ok('screenshot' in paths, 'schema must have screenshot')
|
|
assert.ok('createdAt' in paths, 'schema must have createdAt')
|
|
})
|
|
|
|
it('status field only allows open or resolved', () => {
|
|
const statusPath = BugReport.schema.path('status') as any
|
|
const enumValues: string[] = statusPath?.options?.enum ?? []
|
|
assert.deepEqual(enumValues.sort(), ['open', 'resolved'])
|
|
})
|
|
|
|
it('status defaults to open', () => {
|
|
const statusPath = BugReport.schema.path('status') as any
|
|
assert.equal(statusPath?.options?.default, 'open')
|
|
})
|
|
})
|
|
|
|
describe('BugReport — patch status validation', () => {
|
|
const validStatuses = ['open', 'resolved']
|
|
const invalidStatuses = ['', 'done', 'closed', 'pending', undefined, null]
|
|
|
|
it('accepts valid statuses', () => {
|
|
for (const s of validStatuses) {
|
|
assert.ok(s === 'open' || s === 'resolved', `${s} should be valid`)
|
|
}
|
|
})
|
|
|
|
it('rejects invalid statuses', () => {
|
|
for (const s of invalidStatuses) {
|
|
assert.ok(s !== 'open' && s !== 'resolved', `${s} should be invalid`)
|
|
}
|
|
})
|
|
})
|