4a54467599
* fix(transaction): enforce valid date range for transactions and add database constraint * fix(transaction): implement server-side validation for transaction dates and enhance error handling
81 lines
3.0 KiB
TypeScript
81 lines
3.0 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
||
import { formatAmount, formatWholeKr, formatDateTime, formatDate, isSaneDateString } from '@/lib/utils'
|
||
|
||
// Intl sv-SE groups thousands with a non-breaking / narrow space (U+00A0 or
|
||
// U+202F depending on ICU version) and may render negatives with U+2212. Both
|
||
// vary across Node builds, so normalize them to plain ASCII before asserting —
|
||
// the test cares about format shape, not the exact whitespace codepoint.
|
||
const norm = (s: string) => s.replace(/\s/g, ' ').replace(/−/g, '-')
|
||
|
||
describe('formatAmount', () => {
|
||
it('renders two decimals with sv-SE grouping and no currency symbol', () => {
|
||
expect(norm(formatAmount(1234.5))).toBe('1 234,50')
|
||
expect(norm(formatAmount(0))).toBe('0,00')
|
||
expect(norm(formatAmount(-1234.56))).toBe('-1 234,56')
|
||
})
|
||
|
||
it('does not include "kr" or the SEK symbol', () => {
|
||
expect(formatAmount(100)).not.toMatch(/kr|SEK/)
|
||
})
|
||
})
|
||
|
||
describe('formatWholeKr', () => {
|
||
it('rounds to whole krona with grouping, no decimals', () => {
|
||
expect(norm(formatWholeKr(1234.56))).toBe('1 235')
|
||
expect(norm(formatWholeKr(999.4))).toBe('999')
|
||
expect(norm(formatWholeKr(0))).toBe('0')
|
||
})
|
||
})
|
||
|
||
describe('formatDateTime', () => {
|
||
it('renders ISO-ordered date and time', () => {
|
||
expect(formatDateTime('2026-05-11T14:30:00')).toBe('2026-05-11 14:30')
|
||
})
|
||
|
||
it('accepts a Date instance', () => {
|
||
expect(formatDateTime(new Date('2026-01-02T09:05:00'))).toBe('2026-01-02 09:05')
|
||
})
|
||
|
||
it('stays date-aligned with formatDate on the date portion', () => {
|
||
const iso = '2026-12-31T23:59:00'
|
||
expect(formatDateTime(iso).startsWith(formatDate(iso))).toBe(true)
|
||
})
|
||
})
|
||
|
||
describe('formatDate', () => {
|
||
it('formats a valid ISO date', () => {
|
||
expect(formatDate('2026-05-11')).toBe('2026-05-11')
|
||
})
|
||
|
||
it('fails closed on a malformed date instead of throwing', () => {
|
||
// Regression: a 6-digit year ('202403-02-05', the real incident) parses to
|
||
// an Invalid Date; date-fns format() throws on that and used to crash the
|
||
// whole transactions route via the error boundary. It must degrade, not throw.
|
||
expect(() => formatDate('202403-02-05')).not.toThrow()
|
||
expect(formatDate('202403-02-05')).toBe('—')
|
||
expect(formatDate('not-a-date')).toBe('—')
|
||
})
|
||
})
|
||
|
||
describe('isSaneDateString', () => {
|
||
it('accepts real, in-range YYYY-MM-DD dates', () => {
|
||
expect(isSaneDateString('2026-06-04')).toBe(true)
|
||
expect(isSaneDateString('2024-03-02')).toBe(true)
|
||
})
|
||
|
||
it('rejects the 6-digit-year corruption from native date inputs', () => {
|
||
expect(isSaneDateString('202403-02-05')).toBe(false)
|
||
})
|
||
|
||
it('rejects impossible and out-of-range dates', () => {
|
||
expect(isSaneDateString('2024-13-40')).toBe(false) // month 13 / day 40
|
||
expect(isSaneDateString('1899-12-31')).toBe(false) // below floor
|
||
expect(isSaneDateString('2101-01-01')).toBe(false) // above ceiling
|
||
})
|
||
|
||
it('rejects empty and non-date strings', () => {
|
||
expect(isSaneDateString('')).toBe(false)
|
||
expect(isSaneDateString('not-a-date')).toBe(false)
|
||
})
|
||
})
|