The nine Capture tables (files, jobs, folders, shares, links, quota, cookies, profiles, library resume points) leave app/models.py for the unit's own module; app.tables registers every unit's tables for the migrations, the test database and the ownership check. No schema change.
71 lines
3.8 KiB
TypeScript
71 lines
3.8 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
// A bound that has to hold in two languages at once. The backend truncates a user-supplied display
|
|
// name to `DISPLAY_TEXT_MAX` on its way into a varchar column of the same width (the columns are
|
|
// declared with it); the upload dialog stops the user typing at the same number, so the truncation
|
|
// is never a silent surprise. Neither side can see the other's copy, so the gate reads both.
|
|
//
|
|
// Widening one side alone is the failure this catches: the column and the Python constant move
|
|
// together, the input keeps clipping at the old number, and the field simply stops accepting
|
|
// keystrokes with no message — visible only by reading two files in two languages.
|
|
//
|
|
// Read as TEXT, like `i18n/backendIds.contract.test.ts` does, rather than by importing the module:
|
|
// the api slice pulls in i18n, which touches localStorage at load and would drag a DOM into a check
|
|
// that is pure string comparison.
|
|
const src = (u: string) => readFileSync(new URL(u, import.meta.url), "utf-8");
|
|
|
|
describe("display-name bound", () => {
|
|
it("matches the backend's DISPLAY_TEXT_MAX", () => {
|
|
const backend = src("../../../../backend/app/models.py").match(
|
|
/^DISPLAY_TEXT_MAX\s*=\s*(\d+)$/m,
|
|
);
|
|
const frontend = src("./downloads.ts").match(/^export const DISPLAY_NAME_MAX = (\d+);$/m);
|
|
expect(backend, "backend/app/models.py no longer defines DISPLAY_TEXT_MAX").toBeTruthy();
|
|
expect(frontend, "lib/api/downloads.ts no longer exports DISPLAY_NAME_MAX").toBeTruthy();
|
|
expect(Number(frontend![1])).toBe(Number(backend![1]));
|
|
});
|
|
|
|
it("is the width the columns are actually declared with", () => {
|
|
// The mutation guard for the check above: both constants could agree on a number neither
|
|
// column uses. `String(DISPLAY_TEXT_MAX)` is how the three display columns are declared, so a
|
|
// literal creeping back into one of them fails here. `User.display_name` is a core table; the
|
|
// job's two are Capture's.
|
|
const models =
|
|
src("../../../../backend/app/models.py") + src("../../../../backend/app/downloads/models.py");
|
|
const declared = [...models.matchAll(/display_(?:name|uploader): Mapped.*?String\((.*?)\)/g)];
|
|
expect(declared).toHaveLength(3);
|
|
for (const m of declared) expect(m[1]).toBe("DISPLAY_TEXT_MAX");
|
|
});
|
|
});
|
|
|
|
// The viewer reads what the server decides (backend downloads/preview.py): the preview kinds it may
|
|
// be told and the header that says a text was cut. Each side names them in its own language, so the
|
|
// gate reads both: a kind the server adds that the viewer does not know would fall to "no preview".
|
|
describe("file preview", () => {
|
|
it("knows the kinds the server hands out", () => {
|
|
const backend = src("../../../../backend/app/downloads/preview.py").match(
|
|
/^PreviewKind = Literal\[(.*)\]$/m,
|
|
);
|
|
const frontend = src("./downloads.ts").match(/^export type FilePreviewKind = (.*);$/m);
|
|
expect(backend, "downloads/preview.py no longer defines PreviewKind").toBeTruthy();
|
|
expect(frontend, "lib/api/downloads.ts no longer exports FilePreviewKind").toBeTruthy();
|
|
const kinds = (s: string, sep: string) =>
|
|
s
|
|
.split(sep)
|
|
.map((k) => k.trim().replace(/"/g, ""))
|
|
.sort();
|
|
expect(kinds(frontend![1]!, "|")).toEqual(kinds(backend![1]!, ","));
|
|
});
|
|
|
|
it("reads the header the server cuts a text with", () => {
|
|
const backend = src("../../../../backend/app/routes/files.py").match(
|
|
/^TRUNCATED_HEADER = "(.*)"$/m,
|
|
);
|
|
const frontend = src("./files.ts").match(/^const PREVIEW_TRUNCATED_HEADER = "(.*)";$/m);
|
|
expect(backend, "routes/files.py no longer defines TRUNCATED_HEADER").toBeTruthy();
|
|
expect(frontend, "lib/api/files.ts no longer defines PREVIEW_TRUNCATED_HEADER").toBeTruthy();
|
|
expect(frontend![1]).toBe(backend![1]);
|
|
});
|
|
});
|