Files
admin-web/src/modules/self-operated/turnoverNavigation.test.ts
T

39 lines
3.2 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { inventoryView, turnoverRoute, validateTurnover, yesterdayBeijing } from './turnoverNavigation'
import type { TurnoverQuery } from './turnoverTypes'
const valid: TurnoverQuery = { sourceAccount: 'primary', warehouseCode: 'W', groupBy: 'SKU', endDate: '2026-10-12', baseWindowDays: 30, focus: 'ALL', sortBy: 'PHYSICAL_DESC', current: 1, size: 20, includeOverview: true }
describe('inventory view and turnover query contract', () => {
it('normalizes repeated owner keys and validates the bounded selection', () => {
expect(turnoverRoute({ customerCodes: ['B', 'A', 'A'] }).customerCodes).toEqual(['A', 'B'])
expect(turnoverRoute({ customerCodes: 'A' }).customerCodes).toEqual(['A'])
expect(turnoverRoute({}).customerCodes).toEqual([])
for (const value of ['', [null], Array(101).fill('A')]) expect(() => turnoverRoute({ customerCodes: value })).toThrow()
expect(() => turnoverRoute({ customerCode: 'A' })).toThrow()
expect(() => inventoryView({ view: 'DETAIL', customerCodes: ['A'] })).toThrow()
})
it('parses only explicit booleans for the zero-inventory filter', () => {
expect(turnoverRoute({ hideZeroInventory: 'false' })).toMatchObject({ hideZeroInventory: false })
expect(turnoverRoute({ hideZeroInventory: 'true' })).toMatchObject({ hideZeroInventory: true })
for (const value of ['0', '1', 'yes', ['false']]) expect(() => turnoverRoute({ hideZeroInventory: value })).toThrow()
expect(() => inventoryView({ view: 'DETAIL', hideZeroInventory: 'true' })).toThrow()
})
it('defaults to turnover but preserves strict dashboard and legacy detail links', () => {
expect(inventoryView({ warehouseCode: 'W', sku: 'X' })).toBe('TURNOVER')
for (const q of [{ from: 'overview' }, { drilldownFrom: 'DASHBOARD' }, { salesWindowDays: '10' }, { attention: 'FROZEN_STOCK' }, { view: 'DETAIL' }]) expect(inventoryView(q)).toBe('DETAIL')
})
it('rejects conflicting views and unknown or duplicate parameters', () => {
for (const q of [{ view: 'TURNOVER', from: 'overview' }, { view: 'TURNOVER', attention: 'ALL' }, { view: 'DETAIL', groupBy: 'SKU' }, { view: ['TURNOVER'] }, { view: 'OTHER' }]) expect(() => inventoryView(q)).toThrow()
expect(() => turnoverRoute({ salesRunId: 'invented' })).toThrow()
expect(() => turnoverRoute({ groupBy: ['OWNER', 'SKU'] })).toThrow()
})
it('validates independent focus, threshold, group, date and pagination boundaries', () => {
expect(() => validateTurnover(valid, '2026-10-12')).not.toThrow()
for (const patch of [{ endDate: '2026-02-30' }, { endDate: '2026-10-13' }, { groupBy: 'OWNER', sku: 'X' }, { groupBy: 'OWNER', sortBy: 'COVERAGE_DESC' }, { groupBy: 'OWNER', sortBy: 'UNIT_VOLUME_DESC' }, { focus: 'COVERAGE_GT' }, { coverageDaysThreshold: 1.5 }, { size: 101 }, { current: 0 }, { baseWindowDays: 367 }]) expect(() => validateTurnover({ ...valid, ...patch } as TurnoverQuery, '2026-10-12')).toThrow()
expect(() => validateTurnover({ ...valid, focus: 'COVERAGE_LE', coverageDaysThreshold: 45 }, '2026-10-12')).not.toThrow()
})
it('uses Beijing yesterday across UTC day boundaries', () => {
expect(yesterdayBeijing(new Date('2026-10-12T16:01:00Z'))).toBe('2026-10-12')
})
})