Files
app/src/__tests__/nas-document-manager.test.ts
BOHA dd8c699420 fix(nas): issued-order PDFs overwrite in place - no more _1 duplicate copies
saveIssuedPdf ran through uniquePath, so whenever two archive calls raced (the fire-and-forget ?save=1 after a save vs. the /file fallback, or two quick saves) the second write landed as <number>_1.pdf - and those copies were invisible to the reader and cleaner (exact-name match), accumulating forever. Now the issued archive writes to the deterministic Vydane/YYYY/MM/<number>.pdf and overwrites (same model as the offers manager), and cleanIssued also sweeps stale <number>_N.pdf duplicates so existing junk self-heals on the next save/delete. Document numbers are digit-only (never contain _), so the suffix match cannot hit a different document. uniquePath remains for received-invoice uploads where name collisions are legitimate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 17:55:27 +02:00

82 lines
3.2 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from "vitest";
import fs from "fs";
import os from "os";
import path from "path";
import { NasDocumentManager } from "../services/nas-financials-manager";
/**
* Generic NAS archival manager backing both the invoices NAS (NAS_INVOICES) and
* the orders NAS (NAS_ORDERS). Uses an isolated temp dir via the constructor
* seam so it is deterministic and passes even where the real NAS (Z:) is not
* mounted. Layout: {base}/Vydané/YYYY/MM/<n>.pdf — diacritics preserved.
*/
describe("NasDocumentManager issued-PDF round-trip", () => {
let tmpBase: string;
let nas: NasDocumentManager;
beforeEach(() => {
tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "nas-doc-"));
nas = new NasDocumentManager(tmpBase);
});
afterEach(() => {
fs.rmSync(tmpBase, { recursive: true, force: true });
});
it("saves an issued PDF under Vydané/YYYY/MM/<number>.pdf and reads it back", () => {
const buf = Buffer.from("%PDF-1.4 fake order pdf bytes", "utf8");
const rel = nas.saveIssuedPdf("25P0001", 2026, 6, buf);
expect(rel).toBe("Vydané/2026/06/25P0001.pdf");
const onDisk = path.join(tmpBase, "Vydané", "2026", "06", "25P0001.pdf");
expect(fs.existsSync(onDisk)).toBe(true);
const read = nas.readIssued(rel!);
expect(read).not.toBeNull();
expect(read!.fileName).toBe("25P0001.pdf");
expect(read!.data.equals(buf)).toBe(true);
});
it("cleanIssued removes a previously-saved PDF across year/month folders", () => {
const buf = Buffer.from("pdf", "utf8");
const rel = nas.saveIssuedPdf("25P0002", 2026, 6, buf);
expect(nas.readIssued(rel!)).not.toBeNull();
nas.cleanIssued("25P0002");
expect(nas.readIssued(rel!)).toBeNull();
});
it("an unconfigured (empty basePath) manager is not configured and saves nothing", () => {
const blank = new NasDocumentManager("");
expect(blank.isConfigured()).toBe(false);
expect(
blank.saveIssuedPdf("25P0003", 2026, 6, Buffer.from("x")),
).toBeNull();
});
it("re-saving the same number OVERWRITES in place — never a _1 duplicate", () => {
const first = nas.saveIssuedPdf("25P0004", 2026, 6, Buffer.from("v1"));
const second = nas.saveIssuedPdf("25P0004", 2026, 6, Buffer.from("v2"));
// Same canonical path both times, newest content wins.
expect(second).toBe(first);
const dir = path.join(tmpBase, "Vydané", "2026", "06");
expect(fs.readdirSync(dir)).toEqual(["25P0004.pdf"]);
expect(nas.readIssued(second!)!.data.toString()).toBe("v2");
});
it("cleanIssued also sweeps stale _N duplicates left by the old racy save", () => {
nas.saveIssuedPdf("25P0005", 2026, 6, Buffer.from("canonical"));
// Simulate duplicates the pre-fix uniquePath save created during races.
const dir = path.join(tmpBase, "Vydané", "2026", "06");
fs.writeFileSync(path.join(dir, "25P0005_1.pdf"), "stale");
fs.writeFileSync(path.join(dir, "25P0005_2.pdf"), "stale");
// A DIFFERENT number must not be touched by the suffix match.
nas.saveIssuedPdf("25P0006", 2026, 6, Buffer.from("other"));
nas.cleanIssued("25P0005");
expect(fs.readdirSync(dir).sort()).toEqual(["25P0006.pdf"]);
});
});