fix: make OCR portable and reliable across AMD64 and ARM64 (#519)

* fix: make OCR portable and reliable

* fix: harden OCR installation portability

* fix: pin OCR partials across downloads

* fix: make OCR execution reliably asynchronous

* fix: harden OCR portability and docs routes

* fix: preserve decoder and docs safeguards
This commit is contained in:
SnapOtter
2026-07-15 03:34:24 +08:00
committed by GitHub
parent 58121f205f
commit 991c981529
409 changed files with 67151 additions and 8076 deletions
+71 -32
View File
@@ -1,40 +1,19 @@
// 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 rateLimit from "@fastify/rate-limit";
import Fastify from "fastify";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { env } from "../../../apps/api/src/config.js";
import { docsRoutes } from "../../../apps/api/src/routes/docs.js";
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();
});
@@ -42,7 +21,7 @@ describe("API docs i18n serving", () => {
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("Größe ändern");
expect(res.body).toContain("locale: de");
});
@@ -51,14 +30,14 @@ describe("API docs i18n serving", () => {
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");
expect(res.body).not.toContain("Größe ändern");
});
it("falls back to English for a supported locale with no spec file", async () => {
// fr is supported but no openapi.fr.yaml exists.
it("serves another committed locale spec without falling back to English", async () => {
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");
expect(res.body).toContain("locale: fr");
expect(res.body).not.toContain("locale: de");
});
it("keeps the default (no lang) response ASCII-only", async () => {
@@ -72,9 +51,69 @@ describe("API docs i18n serving", () => {
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.");
// Tag prose comes from the committed German spec.
expect(res.body).toContain("Datei-Verarbeitungstools");
// Tool lines come from shared i18n; the Resize tool id is present with a mode.
expect(res.body).toContain("Größe ändern - Größe nach Pixeln");
expect(res.body).toContain("(resize, sync)");
});
it("rate-limits request-time localized docs work even when global limiting is disabled", async () => {
const app = Fastify();
await app.register(rateLimit, {
max: 50_000,
timeWindow: "1 minute",
allowList: (request) => !request.url.startsWith("/api/"),
});
await docsRoutes(app);
await app.ready();
try {
for (const url of ["/api/v1/openapi.yaml?lang=de", "/llms.de.txt"]) {
for (let request = 0; request < 60; request++) {
const response = await app.inject({ method: "GET", url });
expect(response.statusCode).toBe(200);
expect(response.headers["x-ratelimit-limit"]).toBe("60");
}
const limited = await app.inject({ method: "GET", url });
expect(limited.statusCode).toBe(429);
const head = await app.inject({ method: "HEAD", url });
expect(head.statusCode).toBe(404);
}
} finally {
await app.close();
}
});
it("does not loosen a stricter operator rate limit", async () => {
const previousLimit = env.RATE_LIMIT_PER_MIN;
env.RATE_LIMIT_PER_MIN = 2;
const app = Fastify();
try {
await app.register(rateLimit, {
max: env.RATE_LIMIT_PER_MIN,
timeWindow: "1 minute",
allowList: (request) => !request.url.startsWith("/api/"),
});
await docsRoutes(app);
await app.ready();
for (const url of ["/api/v1/openapi.yaml?lang=de", "/llms.de.txt"]) {
for (let request = 0; request < 2; request++) {
const response = await app.inject({ method: "GET", url });
expect(response.statusCode).toBe(200);
expect(response.headers["x-ratelimit-limit"]).toBe("2");
}
const limited = await app.inject({ method: "GET", url });
expect(limited.statusCode).toBe(429);
}
} finally {
env.RATE_LIMIT_PER_MIN = previousLimit;
await app.close();
}
});
});