From b63ebe8fdbbd7fe37caf20520fee27f50e5f25d8 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sun, 19 Jul 2026 23:28:17 -0700 Subject: [PATCH] FN-8417: verify GitHub translation cache reuse Add route-level coverage for durable GitHub import translation reuse.\n\n- Mock translation and rate-limit services at the GitHub route boundary\n- Verify identical auto-translate requests return cached results without further model or rate-limit cost\n- Confirm cached translations are recorded once\n\nFiles changed:\n .../dashboard/src/__tests__/routes-github.test.ts | 80 ++++++++++++++++++++++\n 1 file changed, 80 insertions(+) Fusion-Task-Id: FN-8417 Fusion-Task-Lineage: 685d753a-d711-4625-809b-14921ce14b6b Co-authored-by: Fusion (runfusion.ai) --- .../src/__tests__/routes-github.test.ts | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/packages/dashboard/src/__tests__/routes-github.test.ts b/packages/dashboard/src/__tests__/routes-github.test.ts index 49d51276ee..dd021cfc60 100644 --- a/packages/dashboard/src/__tests__/routes-github.test.ts +++ b/packages/dashboard/src/__tests__/routes-github.test.ts @@ -51,6 +51,8 @@ const { mockExecFile, mockGetCachedImportTranslation, mockResolveTargetLocale, + mockTranslateText, + mockCheckTranslateRateLimit, } = vi.hoisted(() => ({ mockPerformUpdateCheck: vi.fn(), mockClearUpdateCheckCache: vi.fn(), @@ -58,8 +60,19 @@ const { mockExecFile: vi.fn(), mockGetCachedImportTranslation: vi.fn(), mockResolveTargetLocale: vi.fn(), + mockTranslateText: vi.fn(), + mockCheckTranslateRateLimit: vi.fn(), })); +vi.mock("../ai-translate.js", async () => { + const actual = await vi.importActual("../ai-translate.js"); + return { + ...actual, + translateText: mockTranslateText, + checkTranslateRateLimit: mockCheckTranslateRateLimit, + }; +}); + vi.mock("../update-check.js", async () => { const actual = await vi.importActual("../update-check.js"); return { @@ -587,6 +600,73 @@ describe("POST /github/issues/fetch", () => { }); }); +/* +FNXC:GitHubImportTranslate 2026-07-20-00:00: +FN-8417 requires the HTTP reopen boundary, not just the translation service, to +prove that a second identical auto-translate request uses the durable cache. +The route must reserve rate-limit budget only for uncached model work. +*/ +describe("POST /github/issues/auto-translate", () => { + let store: TaskStore; + + beforeEach(() => { + const translations = new Map(); + store = createMockStore({ + getSettings: vi.fn().mockResolvedValue({ + githubImportAutoTranslate: true, + importTranslateTargetLocale: "en", + }), + getImportTranslation: vi.fn(async (key) => translations.get(JSON.stringify(key)) ?? null), + recordImportTranslation: vi.fn(async (key, value) => { + translations.set(JSON.stringify(key), { + ...value, + detectedLocale: value.detectedLocale ?? null, + recordedAt: "2026-07-20T00:00:00.000Z", + }); + }), + }); + mockTranslateText.mockResolvedValue({ title: "Translated title", body: "Translated body" }); + mockCheckTranslateRateLimit.mockReturnValue(true); + }); + + function buildApp() { + const app = express(); + app.use(express.json()); + app.use("/api", createApiRoutes(store)); + return app; + } + + it("auto-translate serves a second identical POST from cache without model or rate-limit cost", async () => { + const body = JSON.stringify({ + owner: "owner", + repo: "repo", + targetLocale: "en", + items: [{ number: 17, title: "Error del servidor", body: "El servidor devuelve un error cuando el usuario intenta guardar los cambios en la configuracion. No se puede completar la operacion porque el sistema no responde. Por favor revise los registros del servidor para mas informacion sobre este problema.", state: "open" }], + }); + + const first = await REQUEST(buildApp(), "POST", "/api/github/issues/auto-translate", body, { + "Content-Type": "application/json", + }); + const second = await REQUEST(buildApp(), "POST", "/api/github/issues/auto-translate", body, { + "Content-Type": "application/json", + }); + + expect(first.status).toBe(200); + expect(second.status).toBe(200); + expect(mockTranslateText).toHaveBeenCalledTimes(1); + expect(first.body.translations[17]).toEqual({ title: "Translated title", body: "Translated body" }); + expect(second.body.translations[17]).toEqual({ title: "Translated title", body: "Translated body" }); + expect(mockCheckTranslateRateLimit).toHaveBeenCalledTimes(1); + expect(mockCheckTranslateRateLimit).toHaveBeenCalledWith(expect.any(String), 1); + expect(store.recordImportTranslation).toHaveBeenCalledTimes(1); + }); +}); + describe("POST /github/issues/import", () => { let store: TaskStore; let getIssueSpy: ReturnType;