mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
81 lines
3.3 KiB
TypeScript
81 lines
3.3 KiB
TypeScript
// tests/integration/platform/docs-i18n.test.ts
|
|||
|
|
import { rmSync, writeFileSync } from "node:fs";
|
||
|
|
import { dirname, join } from "node:path";
|
||
|
|
import { fileURLToPath } from "node:url";
|
||
|
|
import yaml from "js-yaml";
|
||
|
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||
|
|
import { buildTestApp, type TestApp } from "../test-server";
|
||
|
|
|
||
|
|
// The docs route resolves locale specs relative to apps/api/src (next to
|
||
|
|
// openapi.yaml). Write a small German fixture spec there so serve-by-lang has
|
||
|
|
// a real file to pick up, then clean it up. This avoids committing a full
|
||
|
|
// generated locale spec into the repo just for the test.
|
||
|
|
const apiSrc = join(dirname(fileURLToPath(import.meta.url)), "../../../apps/api/src");
|
||
|
|
const deSpecPath = join(apiSrc, "openapi.de.yaml");
|
||
|
|
|
||
|
|
const DE_SPEC = {
|
||
|
|
openapi: "3.1.0",
|
||
|
|
info: { title: "SnapOtter API", version: "2.0.0", description: "Deutsche Beschreibung." },
|
||
|
|
tags: [{ name: "Tools", description: "Werkzeuge." }],
|
||
|
|
paths: {
|
||
|
|
"/api/v1/tools/image/resize": {
|
||
|
|
post: { tags: ["Tools"], summary: "Groesse aendern", description: "Bild skalieren." },
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"x-i18n": { locale: "de", entries: {} },
|
||
|
|
};
|
||
|
|
|
||
|
|
describe("API docs i18n serving", () => {
|
||
|
|
let testApp: TestApp;
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
writeFileSync(deSpecPath, yaml.dump(DE_SPEC, { lineWidth: -1 }), "utf8");
|
||
|
|
testApp = await buildTestApp();
|
||
|
|
});
|
||
|
|
|
||
|
|
afterAll(async () => {
|
||
|
|
rmSync(deSpecPath, { force: true });
|
||
|
|
await testApp.cleanup();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("serves the German spec for ?lang=de", async () => {
|
||
|
|
const res = await testApp.app.inject({ method: "GET", url: "/api/v1/openapi.yaml?lang=de" });
|
||
|
|
expect(res.statusCode).toBe(200);
|
||
|
|
expect(res.headers["content-type"]).toContain("text/yaml");
|
||
|
|
expect(res.body).toContain("Groesse aendern");
|
||
|
|
expect(res.body).toContain("locale: de");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("falls back to English for an unsupported lang", async () => {
|
||
|
|
const res = await testApp.app.inject({ method: "GET", url: "/api/v1/openapi.yaml?lang=zz" });
|
||
|
|
expect(res.statusCode).toBe(200);
|
||
|
|
// English spec has the English summary, not the German one.
|
||
|
|
expect(res.body).toContain("openapi: 3.1.0");
|
||
|
|
expect(res.body).not.toContain("Groesse aendern");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("falls back to English for a supported locale with no spec file", async () => {
|
||
|
|
// fr is supported but no openapi.fr.yaml exists.
|
||
|
|
const res = await testApp.app.inject({ method: "GET", url: "/api/v1/openapi.yaml?lang=fr" });
|
||
|
|
expect(res.statusCode).toBe(200);
|
||
|
|
expect(res.body).not.toContain("Groesse aendern");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("keeps the default (no lang) response ASCII-only", async () => {
|
||
|
|
const res = await testApp.app.inject({ method: "GET", url: "/api/v1/openapi.yaml" });
|
||
|
|
const offending = [...res.body].find((ch) => ch.charCodeAt(0) > 0x7f);
|
||
|
|
expect(offending).toBeUndefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("serves a localized llms.de.txt using translated tool strings", async () => {
|
||
|
|
const res = await testApp.app.inject({ method: "GET", url: "/llms.de.txt" });
|
||
|
|
expect(res.statusCode).toBe(200);
|
||
|
|
expect(res.headers["content-type"]).toContain("text/plain");
|
||
|
|
expect(res.body).toContain("## Tools");
|
||
|
|
// Tag prose comes from the German fixture spec.
|
||
|
|
expect(res.body).toContain("Werkzeuge.");
|
||
|
|
// Tool lines come from shared i18n; the Resize tool id is present with a mode.
|
||
|
|
expect(res.body).toContain("(resize, sync)");
|
||
|
|
});
|
||
|
|
});
|