Files
edr-platform/apps/edr-freight-api/src/modules/exports/export-request.util.spec.ts
Nathnael ce90be5c88 fix(reports): stop the 'first N rows' option failing on large exports
The export path used one number for two different things: the format's hard
row cap, and the caller's explicit 'give me the first N rows'. Because
resolveExportCap() returned min(requested, formatCap) and runAll() then threw
when the result reached it, picking 'Records: First 100' in the export dialog
400'd on any report with more than 100 rows — the user asked to be truncated
and got an error instead.

Splits them: formatRowCap() is the hard, non-caller-controllable ceiling that
still throws when exceeded (a silently short file hides missing rows), while
resolveRowLimit() is the deliberate truncation and is honoured by slicing.
Verified against a 223-row dataset: limit=5 now returns 5 rows, and no limit
returns all 223.
2026-08-20 05:28:51 +00:00

88 lines
2.8 KiB
TypeScript

import {
EXPORT_MIME,
formatRowCap,
pickByKey,
resolveExportFormat,
resolveRowLimit,
} from './export-request.util';
import { CSV_ROW_CAP, PDF_ROW_CAP, XLSX_ROW_CAP } from './tabular-export.service';
describe('resolveExportFormat', () => {
it('only \'pdf\' exports as pdf', () => {
expect(resolveExportFormat('pdf')).toBe('pdf');
});
it('\'csv\' exports as csv', () => {
expect(resolveExportFormat('csv')).toBe('csv');
});
it.each([undefined, 'xlsx', 'doc', ''])('%p falls back to xlsx', (raw) => {
expect(resolveExportFormat(raw)).toBe('xlsx');
});
});
describe('formatRowCap', () => {
it('is the format\'s hard ceiling and is not caller-controllable', () => {
expect(formatRowCap('xlsx')).toBe(XLSX_ROW_CAP);
expect(formatRowCap('csv')).toBe(CSV_ROW_CAP);
expect(formatRowCap('pdf')).toBe(PDF_ROW_CAP);
});
});
describe('resolveRowLimit', () => {
it('no limit means "everything, up to the cap"', () => {
expect(resolveRowLimit('xlsx', undefined)).toBeUndefined();
});
it('an explicit limit is the caller asking to be truncated — kept as-is', () => {
// Distinct from the cap: 100 here must yield 100 rows, not a 400, even
// when the unfiltered result is far larger.
expect(resolveRowLimit('pdf', '100')).toBe(100);
});
it('an explicit limit over the format cap is clamped down', () => {
expect(resolveRowLimit('pdf', String(PDF_ROW_CAP + 1000))).toBe(PDF_ROW_CAP);
expect(resolveRowLimit('csv', String(CSV_ROW_CAP + 1))).toBe(CSV_ROW_CAP);
});
it.each(['0', '-5', 'not-a-number', ''])('non-positive/invalid limit %p means no limit', (raw) => {
expect(resolveRowLimit('xlsx', raw)).toBeUndefined();
});
});
describe('EXPORT_MIME', () => {
it('every format has a content type and a matching extension', () => {
expect(EXPORT_MIME.csv.ext).toBe('csv');
expect(EXPORT_MIME.xlsx.ext).toBe('xlsx');
expect(EXPORT_MIME.pdf.type).toBe('application/pdf');
});
});
describe('pickByKey', () => {
const columns = [
{ key: 'a', label: 'A', type: 'string' as const },
{ key: 'b', label: 'B', type: 'number' as const },
{ key: 'c', label: 'C', type: 'money' as const },
];
it('missing fields returns every column', () => {
expect(pickByKey(columns, undefined)).toEqual(columns);
});
it('empty fields string returns every column', () => {
expect(pickByKey(columns, '')).toEqual(columns);
});
it('a known subset filters to just those, in the source\'s own order', () => {
expect(pickByKey(columns, 'c,a')).toEqual([columns[0], columns[2]]);
});
it('unknown keys are dropped, not passed through', () => {
expect(pickByKey(columns, 'a,ghost')).toEqual([columns[0]]);
});
it('all-unknown keys falls back to everything instead of a blank sheet', () => {
expect(pickByKey(columns, 'ghost,also-ghost')).toEqual(columns);
});
});