mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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:
@@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user