e11f70b347
* refactor: optimize page loading and data fetching * fix: resolve recurring production runtime errors * feat: add MCP company and customer updates * fix: handle year-end tax adjustments * feat: harden annual report compliance * fix: expand invoice logo and font support * fix: sanitize API route error responses * fix: sanitize user-facing error messages * feat: persist onboarding and tax assessment notices * fix: reduce cloud backup audit churn * feat: refine invoice editor layout * fix: show saved tax adjustments in INK2 * fix: complete annual report API mappings * docs: record operational safeguards and decisions * fix: harden annual report review findings * fix: adjust column span for description based on VAT registration * New css class name
112 lines
3.8 KiB
TypeScript
112 lines
3.8 KiB
TypeScript
import { randomUUID } from 'node:crypto'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { getPool, withUserContext } from './setup'
|
|
import { insertAuthUser, seedCompany } from './fixtures'
|
|
|
|
async function insertNotice(params: {
|
|
companyId: string
|
|
userId: string
|
|
fiscalPeriodId: string
|
|
decisionDate?: string
|
|
paymentDueDate?: string
|
|
}): Promise<string> {
|
|
const id = randomUUID()
|
|
await getPool().query(
|
|
`INSERT INTO public.tax_assessment_notices
|
|
(id, company_id, user_id, fiscal_period_id, decision_type, decision_date, payment_due_date)
|
|
VALUES ($1, $2, $3, $4, 'final', $5, $6)`,
|
|
[
|
|
id,
|
|
params.companyId,
|
|
params.userId,
|
|
params.fiscalPeriodId,
|
|
params.decisionDate ?? '2026-07-01',
|
|
params.paymentDueDate ?? '2026-10-12',
|
|
],
|
|
)
|
|
return id
|
|
}
|
|
|
|
describe('tax_assessment_notices constraints and RLS', () => {
|
|
it('isolates notices by company membership', async () => {
|
|
const owner = await seedCompany()
|
|
const noticeId = await insertNotice(owner)
|
|
const strangerId = await insertAuthUser()
|
|
|
|
const ownerView = await withUserContext(owner.userId, (client) =>
|
|
client.query<{ id: string }>(
|
|
`SELECT id FROM public.tax_assessment_notices WHERE id = $1`,
|
|
[noticeId],
|
|
),
|
|
)
|
|
expect(ownerView.rows).toHaveLength(1)
|
|
|
|
const strangerView = await withUserContext(strangerId, (client) =>
|
|
client.query<{ id: string }>(
|
|
`SELECT id FROM public.tax_assessment_notices WHERE id = $1`,
|
|
[noticeId],
|
|
),
|
|
)
|
|
expect(strangerView.rows).toHaveLength(0)
|
|
})
|
|
|
|
it('rejects inserts into another company', async () => {
|
|
const owner = await seedCompany()
|
|
const strangerId = await insertAuthUser()
|
|
|
|
await expect(withUserContext(strangerId, (client) =>
|
|
client.query(
|
|
`INSERT INTO public.tax_assessment_notices
|
|
(company_id, user_id, fiscal_period_id, decision_type, decision_date, payment_due_date)
|
|
VALUES ($1, $2, $3, 'final', '2026-07-01', '2026-10-12')`,
|
|
[owner.companyId, strangerId, owner.fiscalPeriodId],
|
|
),
|
|
)).rejects.toThrow(/row-level security|policy/i)
|
|
})
|
|
|
|
it('requires the exact due date to be on or after the decision date', async () => {
|
|
const owner = await seedCompany()
|
|
await expect(insertNotice({
|
|
...owner,
|
|
decisionDate: '2026-07-01',
|
|
paymentDueDate: '2026-06-30',
|
|
})).rejects.toThrow(/due_after_decision|check constraint/i)
|
|
})
|
|
|
|
it('prevents deleting a notice referenced by a generated deadline', async () => {
|
|
const owner = await seedCompany()
|
|
const noticeId = await insertNotice(owner)
|
|
await getPool().query(
|
|
`INSERT INTO public.deadlines
|
|
(user_id, company_id, title, due_date, deadline_type, source,
|
|
tax_deadline_type, tax_period, tax_assessment_notice_id)
|
|
VALUES ($1, $2, 'Kvarskatt', '2026-10-12', 'tax', 'system',
|
|
'kvarskatt', $3, $4)`,
|
|
[owner.userId, owner.companyId, `notice:${noticeId}`, noticeId],
|
|
)
|
|
|
|
await expect(
|
|
getPool().query(`DELETE FROM public.tax_assessment_notices WHERE id = $1`, [noticeId]),
|
|
).rejects.toThrow(/foreign key|violates/i)
|
|
})
|
|
})
|
|
|
|
describe('company_settings initial setup state', () => {
|
|
it('accepts supported paths and rejects unknown values', async () => {
|
|
const owner = await seedCompany()
|
|
await getPool().query(
|
|
`INSERT INTO public.company_settings
|
|
(user_id, company_id, onboarding_complete, initial_setup_path)
|
|
VALUES ($1, $2, true, 'migration')`,
|
|
[owner.userId, owner.companyId],
|
|
)
|
|
|
|
await expect(
|
|
getPool().query(
|
|
`UPDATE public.company_settings SET initial_setup_path = 'unknown' WHERE company_id = $1`,
|
|
[owner.companyId],
|
|
),
|
|
).rejects.toThrow(/initial_setup_path_check|check constraint/i)
|
|
})
|
|
})
|