Files
CapaKraken/packages/application/src/__tests__/read-workbook.test.ts
T
Hartmut c7d36ecbbd
CI / Assistant Split Regression (push) Successful in 11m15s
CI / Lint (push) Successful in 9m38s
CI / Typecheck (push) Successful in 11m19s
CI / Unit Tests (push) Successful in 9m48s
CI / Build (push) Successful in 8m19s
CI / E2E Tests (push) Successful in 5m54s
CI / Fresh-Linux Docker Deploy (push) Failing after 6m45s
CI / Release Images (push) Has been skipped
CI / Architecture Guardrails (push) Successful in 9m17s
test(application): extend ExcelJS read-workbook timeouts to 30s
The 'rejects worksheets that exceed the row limit' test took 6599ms on
the QNAP act_runner, overflowing the default 5000ms vitest timeout.
Writing and parsing MAX_DISPO_WORKBOOK_ROWS+1 rows via ExcelJS is slow
on constrained hardware. Extend timeout for all three writeWorkbook-
dependent tests (row limit, column limit) to 30s, matching the fix
already applied to excel.test.ts and workbook-export.test.ts.
2026-04-13 05:24:07 +02:00

140 lines
4.7 KiB
TypeScript

import { existsSync } from "node:fs";
import { cp, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, describe, expect, it } from "vitest";
import {
MAX_DISPO_WORKBOOK_BYTES,
MAX_DISPO_WORKBOOK_COLUMNS,
MAX_DISPO_WORKBOOK_ROWS,
readWorksheetMatrix,
} from "../use-cases/dispo-import/read-workbook.js";
const referenceWorkbookPath = fileURLToPath(
new URL("../../../../samples/Dispov2/MandatoryDispoCategories_V3.xlsx", import.meta.url),
);
const chargeabilityWorkbookPath = fileURLToPath(
new URL(
"../../../../samples/Dispov2/20260309_Bi-Weekly_Chargeability_Reporting_Content_Production_V0.943_4Hartmut.xlsx",
import.meta.url,
),
);
const planningWorkbookPath = fileURLToPath(
new URL("../../../../samples/Dispov2/DISPO_2026.xlsx", import.meta.url),
);
// Sample xlsx fixtures are gitignored (NDA-protected real data). Skip when absent (CI).
const hasSamples =
existsSync(referenceWorkbookPath) &&
existsSync(chargeabilityWorkbookPath) &&
existsSync(planningWorkbookPath);
const itIfSamples = hasSamples ? it : it.skip;
const tempDirectories: string[] = [];
afterEach(async () => {
await Promise.all(
tempDirectories.splice(0).map(async (directory) => {
await rm(directory, { recursive: true, force: true });
}),
);
});
async function makeTempDirectory(): Promise<string> {
const directory = await mkdtemp(path.join(os.tmpdir(), "capakraken-read-workbook-"));
tempDirectories.push(directory);
return directory;
}
async function writeWorkbook(
filePath: string,
rows: unknown[][],
sheetName = "Sheet1",
): Promise<void> {
const ExcelJS = await import("exceljs");
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet(sheetName);
for (const row of rows) {
worksheet.addRow(row);
}
await workbook.xlsx.writeFile(filePath);
}
describe("readWorksheetMatrix", () => {
itIfSamples("reads trusted xlsx worksheets through the hardened reader", async () => {
const rows = await readWorksheetMatrix(referenceWorkbookPath, "EID-Attr");
expect(rows.length).toBeGreaterThan(0);
expect(rows.some((row) => row.length > 0)).toBe(true);
});
itIfSamples(
"tolerates workbook tables that contain unsupported exceljs date group filters",
async () => {
const rows = await readWorksheetMatrix(chargeabilityWorkbookPath, "ChgFC");
expect(rows.length).toBeGreaterThan(300);
expect(rows[0]?.length).toBeGreaterThan(5);
},
);
itIfSamples(
"accepts real dispo planning worksheets within the supported width envelope",
async () => {
const rows = await readWorksheetMatrix(planningWorkbookPath, "Dispo");
expect(rows.length).toBeGreaterThan(500);
expect(rows.some((row) => row.length > 256)).toBe(true);
expect(rows.every((row) => row.length <= MAX_DISPO_WORKBOOK_COLUMNS)).toBe(true);
},
);
itIfSamples("rejects legacy .xls workbook paths", async () => {
const directory = await makeTempDirectory();
const legacyPath = path.join(directory, "legacy-input.xls");
await cp(referenceWorkbookPath, legacyPath);
await expect(readWorksheetMatrix(legacyPath, "EID-Attr")).rejects.toThrow(
"Only .xlsx workbooks are supported for dispo imports",
);
});
it("rejects oversized workbook files before parsing", async () => {
const directory = await makeTempDirectory();
const oversizedPath = path.join(directory, "oversized.xlsx");
await writeFile(oversizedPath, Buffer.alloc(MAX_DISPO_WORKBOOK_BYTES + 1, 0));
await expect(readWorksheetMatrix(oversizedPath, "Sheet1")).rejects.toThrow(
"Workbook file exceeds the",
);
});
it("rejects worksheets that exceed the row limit", async () => {
const directory = await makeTempDirectory();
const workbookPath = path.join(directory, "too-many-rows.xlsx");
await writeWorkbook(
workbookPath,
Array.from({ length: MAX_DISPO_WORKBOOK_ROWS + 1 }, (_, index) => [`row-${index + 1}`]),
);
await expect(readWorksheetMatrix(workbookPath, "Sheet1")).rejects.toThrow(
`exceeds the ${MAX_DISPO_WORKBOOK_ROWS} row import limit`,
);
}, 30000);
it("rejects worksheets that exceed the column limit", async () => {
const directory = await makeTempDirectory();
const workbookPath = path.join(directory, "too-many-columns.xlsx");
await writeWorkbook(workbookPath, [
Array.from({ length: MAX_DISPO_WORKBOOK_COLUMNS + 1 }, (_, index) => `col-${index + 1}`),
]);
await expect(readWorksheetMatrix(workbookPath, "Sheet1")).rejects.toThrow(
`exceeds the ${MAX_DISPO_WORKBOOK_COLUMNS} column import limit`,
);
}, 30000);
});