* 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
175 lines
6.4 KiB
TypeScript
175 lines
6.4 KiB
TypeScript
import { randomUUID } from 'node:crypto'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { getPool, withUserContext } from './setup'
|
|
import {
|
|
insertAuthUser,
|
|
insertCompany,
|
|
insertCompanyMember,
|
|
insertFiscalPeriod,
|
|
seedCompany,
|
|
} from './fixtures'
|
|
|
|
async function setActiveCompany(userId: string, companyId: string): Promise<void> {
|
|
await getPool().query(
|
|
`INSERT INTO public.user_preferences (user_id, active_company_id)
|
|
VALUES ($1, $2)
|
|
ON CONFLICT (user_id) DO UPDATE SET active_company_id = EXCLUDED.active_company_id`,
|
|
[userId, companyId],
|
|
)
|
|
}
|
|
|
|
async function insertAdjustment(params: {
|
|
companyId: string
|
|
userId: string
|
|
fiscalPeriodId: string
|
|
}): Promise<string> {
|
|
const id = randomUUID()
|
|
await getPool().query(
|
|
`INSERT INTO public.fiscal_period_tax_adjustments
|
|
(id, company_id, user_id, fiscal_period_id, adjustment_type, source,
|
|
source_key, description, account_number, amount, included)
|
|
VALUES ($1, $2, $3, $4, 'non_deductible_expense', 'detected',
|
|
'account:6992', 'Ej avdragsgill kostnad', '6992', 2994, true)`,
|
|
[id, params.companyId, params.userId, params.fiscalPeriodId],
|
|
)
|
|
return id
|
|
}
|
|
|
|
describe('fiscal_period_tax_adjustments constraints and RLS', () => {
|
|
it('uses the standard UUID default and locks the fiscal period in the guard', async () => {
|
|
const result = await getPool().query<{ default_expression: string; function_definition: string }>(
|
|
`SELECT
|
|
pg_get_expr(d.adbin, d.adrelid) AS default_expression,
|
|
pg_get_functiondef('public.guard_fiscal_period_tax_adjustment()'::regprocedure)
|
|
AS function_definition
|
|
FROM pg_attrdef d
|
|
JOIN pg_attribute a
|
|
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
|
|
WHERE d.adrelid = 'public.fiscal_period_tax_adjustments'::regclass
|
|
AND a.attname = 'id'`,
|
|
)
|
|
|
|
expect(result.rows[0]?.default_expression).toContain('uuid_generate_v4')
|
|
expect(result.rows[0]?.function_definition).toMatch(/FOR UPDATE/i)
|
|
})
|
|
|
|
it('isolates adjustments by company membership', async () => {
|
|
const owner = await seedCompany()
|
|
const adjustmentId = await insertAdjustment(owner)
|
|
const strangerId = await insertAuthUser()
|
|
|
|
const ownerView = await withUserContext(owner.userId, (client) =>
|
|
client.query(
|
|
'SELECT id FROM public.fiscal_period_tax_adjustments WHERE id = $1',
|
|
[adjustmentId],
|
|
),
|
|
)
|
|
expect(ownerView.rows).toHaveLength(1)
|
|
|
|
const strangerView = await withUserContext(strangerId, (client) =>
|
|
client.query(
|
|
'SELECT id FROM public.fiscal_period_tax_adjustments WHERE id = $1',
|
|
[adjustmentId],
|
|
),
|
|
)
|
|
expect(strangerView.rows).toHaveLength(0)
|
|
})
|
|
|
|
it('rejects a company that does not own the fiscal period', async () => {
|
|
const first = await seedCompany()
|
|
const second = await seedCompany()
|
|
|
|
await expect(
|
|
insertAdjustment({
|
|
companyId: first.companyId,
|
|
userId: first.userId,
|
|
fiscalPeriodId: second.fiscalPeriodId,
|
|
}),
|
|
).rejects.toThrow(/does not match fiscal period company/i)
|
|
})
|
|
|
|
it('allows an owner to insert an adjustment for the active company', async () => {
|
|
const owner = await seedCompany()
|
|
await setActiveCompany(owner.userId, owner.companyId)
|
|
|
|
await withUserContext(owner.userId, async (client) => {
|
|
const result = await client.query(
|
|
`INSERT INTO public.fiscal_period_tax_adjustments
|
|
(company_id, user_id, fiscal_period_id, adjustment_type, source,
|
|
source_key, description, account_number, amount, included)
|
|
VALUES ($1, $2, $3, 'non_deductible_expense', 'detected',
|
|
'account:6992', 'Ej avdragsgill kostnad', '6992', 2994, true)
|
|
RETURNING id`,
|
|
[owner.companyId, owner.userId, owner.fiscalPeriodId],
|
|
)
|
|
expect(result.rows).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
it('blocks a viewer from inserting an adjustment directly under RLS', async () => {
|
|
const owner = await seedCompany()
|
|
const viewerId = await insertAuthUser()
|
|
await insertCompanyMember({ companyId: owner.companyId, userId: viewerId, role: 'viewer' })
|
|
await setActiveCompany(viewerId, owner.companyId)
|
|
|
|
await withUserContext(viewerId, async (client) => {
|
|
await expect(
|
|
client.query(
|
|
`INSERT INTO public.fiscal_period_tax_adjustments
|
|
(company_id, user_id, fiscal_period_id, adjustment_type, source,
|
|
source_key, description, account_number, amount, included)
|
|
VALUES ($1, $2, $3, 'non_deductible_expense', 'detected',
|
|
'account:6992', 'Ej avdragsgill kostnad', '6992', 2994, true)`,
|
|
[owner.companyId, viewerId, owner.fiscalPeriodId],
|
|
),
|
|
).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
it('blocks writes to a member company that is not the active company', async () => {
|
|
const owner = await seedCompany()
|
|
const otherCompanyId = await insertCompany({ createdBy: owner.userId })
|
|
await insertCompanyMember({ companyId: otherCompanyId, userId: owner.userId, role: 'owner' })
|
|
const otherPeriodId = await insertFiscalPeriod({
|
|
userId: owner.userId,
|
|
companyId: otherCompanyId,
|
|
})
|
|
await setActiveCompany(owner.userId, owner.companyId)
|
|
|
|
await withUserContext(owner.userId, async (client) => {
|
|
await expect(
|
|
client.query(
|
|
`INSERT INTO public.fiscal_period_tax_adjustments
|
|
(company_id, user_id, fiscal_period_id, adjustment_type, source,
|
|
source_key, description, account_number, amount, included)
|
|
VALUES ($1, $2, $3, 'non_deductible_expense', 'detected',
|
|
'account:6992', 'Ej avdragsgill kostnad', '6992', 2994, true)`,
|
|
[otherCompanyId, owner.userId, otherPeriodId],
|
|
),
|
|
).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
it('blocks adjustment changes after the fiscal period is locked', async () => {
|
|
const owner = await seedCompany()
|
|
const adjustmentId = await insertAdjustment(owner)
|
|
await getPool().query(
|
|
'UPDATE public.fiscal_periods SET locked_at = now() WHERE id = $1',
|
|
[owner.fiscalPeriodId],
|
|
)
|
|
|
|
await expect(
|
|
getPool().query(
|
|
'UPDATE public.fiscal_period_tax_adjustments SET amount = 3000 WHERE id = $1',
|
|
[adjustmentId],
|
|
),
|
|
).rejects.toThrow(/locked for tax adjustments/i)
|
|
await expect(
|
|
getPool().query(
|
|
'DELETE FROM public.fiscal_period_tax_adjustments WHERE id = $1',
|
|
[adjustmentId],
|
|
),
|
|
).rejects.toThrow(/locked for tax adjustments/i)
|
|
})
|
|
})
|