ec27228a8e
Em dashes (—) and en dashes (–) had spread across comments, docs, tests, and a few UI strings, reading as AI-generated boilerplate rather than house style. Replaced each with punctuation matching its context: colon for explanatory clauses, comma for asides, plain hyphen for numeric/legal ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for paired-dash asides. messages/en.json and messages/sv.json were fixed by hand together to keep sv/en in sync. Left untouched where the dash is the functional subject rather than decorative punctuation: date-range-parser.ts's separator regex, charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the agent system-prompt files that already instruct against em dashes, and a golden iXBRL test fixture compared byte-for-byte. Also fixes two bugs surfaced along the way: an off-by-one in ApiKeysPanel's scope-label split (a leftover from an earlier partial pass), and a charset-repair test that had lost the literal en-dash it exists to verify. Regenerated the agent atom seed migration (skills:generate) since 27 SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes, with an explicit carve-out for the functional-dash cases above. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
128 lines
3.7 KiB
TypeScript
128 lines
3.7 KiB
TypeScript
/**
|
|
* Unit tests for gnubok_audit_package.
|
|
*
|
|
* Verifies registration, scope mapping, the estimate-only path, and the
|
|
* size-limit guard. The full archive generation is exercised by
|
|
* lib/reports/full-archive-export tests.
|
|
*/
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { tools } from '../server'
|
|
import { TOOL_SCOPE_MAP } from '@/lib/auth/api-keys'
|
|
|
|
vi.mock('@/lib/reports/full-archive-export', () => ({
|
|
generateFullArchive: vi.fn(),
|
|
estimateArchiveSize: vi.fn(),
|
|
}))
|
|
|
|
import {
|
|
generateFullArchive,
|
|
estimateArchiveSize,
|
|
} from '@/lib/reports/full-archive-export'
|
|
|
|
describe('gnubok_audit_package: registration', () => {
|
|
it('is registered', () => {
|
|
const tool = tools.find((t) => t.name === 'gnubok_audit_package')
|
|
expect(tool).toBeDefined()
|
|
expect(tool?.annotations.idempotentHint).toBe(true)
|
|
})
|
|
|
|
it('requires fiscal_period_id', () => {
|
|
const tool = tools.find((t) => t.name === 'gnubok_audit_package')!
|
|
const schema = tool.inputSchema as { required?: string[] }
|
|
expect(schema.required).toContain('fiscal_period_id')
|
|
})
|
|
|
|
it('is mapped to reports:read scope', () => {
|
|
expect(TOOL_SCOPE_MAP.gnubok_audit_package).toBe('reports:read')
|
|
})
|
|
})
|
|
|
|
function makePeriodMock(period: Record<string, unknown> | null) {
|
|
return {
|
|
from: vi.fn().mockReturnValue({
|
|
select: vi.fn().mockReturnValue({
|
|
eq: vi.fn().mockReturnValue({
|
|
eq: vi.fn().mockReturnValue({
|
|
single: vi.fn().mockResolvedValue({
|
|
data: period,
|
|
error: period ? null : { message: 'not found' },
|
|
}),
|
|
}),
|
|
}),
|
|
}),
|
|
}),
|
|
storage: {
|
|
from: vi.fn(),
|
|
},
|
|
} as never
|
|
}
|
|
|
|
describe('gnubok_audit_package: execute', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('estimate_only=true returns size estimate without uploading', async () => {
|
|
vi.mocked(estimateArchiveSize).mockResolvedValue({
|
|
total_bytes: 5 * 1024 * 1024,
|
|
breakdown: {} as never,
|
|
} as never)
|
|
|
|
const tool = tools.find((t) => t.name === 'gnubok_audit_package')!
|
|
const supabase = makePeriodMock({
|
|
id: 'p1', name: '2026',
|
|
period_start: '2026-01-01', period_end: '2026-12-31',
|
|
})
|
|
|
|
const result = (await tool.execute(
|
|
{ fiscal_period_id: 'p1', estimate_only: true },
|
|
'company-1',
|
|
'user-1',
|
|
supabase,
|
|
)) as {
|
|
estimate_only: boolean
|
|
download_url: string | null
|
|
size_bytes: number
|
|
within_limit: boolean
|
|
}
|
|
|
|
expect(result.estimate_only).toBe(true)
|
|
expect(result.download_url).toBeNull()
|
|
expect(result.size_bytes).toBe(5 * 1024 * 1024)
|
|
expect(result.within_limit).toBe(true)
|
|
expect(generateFullArchive).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('throws when archive would exceed size limit and include_documents=true', async () => {
|
|
vi.mocked(estimateArchiveSize).mockResolvedValue({
|
|
total_bytes: 100 * 1024 * 1024, // > 80 MB limit
|
|
breakdown: {} as never,
|
|
} as never)
|
|
|
|
const tool = tools.find((t) => t.name === 'gnubok_audit_package')!
|
|
const supabase = makePeriodMock({
|
|
id: 'p1', name: '2026',
|
|
period_start: '2026-01-01', period_end: '2026-12-31',
|
|
})
|
|
|
|
await expect(
|
|
tool.execute(
|
|
{ fiscal_period_id: 'p1' },
|
|
'company-1', 'user-1', supabase,
|
|
),
|
|
).rejects.toThrow(/exceed.*MB/)
|
|
})
|
|
|
|
it('throws when fiscal period is not found', async () => {
|
|
const tool = tools.find((t) => t.name === 'gnubok_audit_package')!
|
|
const supabase = makePeriodMock(null)
|
|
|
|
await expect(
|
|
tool.execute(
|
|
{ fiscal_period_id: 'nonexistent' },
|
|
'company-1', 'user-1', supabase,
|
|
),
|
|
).rejects.toThrow(/Fiscal period not found/)
|
|
})
|
|
})
|