UNPKG

lokalise-file-exchange

Version:
211 lines (186 loc) 5.63 kB
import { LokaliseError } from "../../../lib/errors/LokaliseError.js"; import { LokaliseFileExchange } from "../../../lib/services/LokaliseFileExchange.js"; import { FakeLokaliseUpload } from "../../fixtures/fake_classes/FakeLokaliseUpload.js"; import type { TestableLokaliseFileExchange } from "../../fixtures/fake_interfaces/TestableLokaliseFileExchange.js"; import type { Interceptable } from "../../setup.js"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, MockAgent, setGlobalDispatcher, vi, } from "../../setup.js"; describe("LokaliseUpload: uploadSingleFile()", () => { const projectId = "803826145ba90b42d5d860.46800099"; const apiKey = process.env.API_KEY as string; let mockAgent: MockAgent; let mockPool: Interceptable; const mockParams = { filename: "test.json", lang_iso: "en", data: "base64encodedcontent", }; beforeAll(() => { mockAgent = new MockAgent(); setGlobalDispatcher(mockAgent); mockAgent.disableNetConnect(); }); afterAll(() => { mockAgent.close(); }); afterEach(() => { vi.restoreAllMocks(); }); beforeEach(() => { mockPool = mockAgent.get("https://api.lokalise.com"); }); describe("Successful Upload", () => { it("should successfully upload a file and return a queued process", async () => { const uploader = new FakeLokaliseUpload({ apiKey }, { projectId }); const processId = "123abc"; mockPool .intercept({ path: `/api2/projects/${projectId}/files/upload`, method: "POST", body: JSON.stringify(mockParams), }) .reply(200, { project_id: projectId, branch: "master", process: { process_id: processId, type: "file-import", status: "queued", message: "", created_by: 20181, created_by_email: "test@example.com", created_at: "2023-09-21 11:33:19 (Etc/UTC)", created_at_timestamp: 1695295999, }, }); const process = await uploader.uploadSingleFile(mockParams); expect(process.process_id).toEqual(processId); expect(process.status).toEqual("queued"); }); }); describe("Retry Logic", () => { it("should retry on 429 errors with exponential backoff", async () => { const processId = "123abc"; const mockResponse = { process_id: processId, type: "file-import", status: "queued", message: "", created_by: 20181, created_by_email: "test@example.com", created_at: "2023-09-21 11:33:19 (Etc/UTC)", created_at_timestamp: 1695295999, }; const retries = 3; const sleepTime = 1; const uploader = new FakeLokaliseUpload( { apiKey }, { projectId, retryParams: { maxRetries: retries, initialSleepTime: sleepTime }, }, ); const sleepSpy = vi .spyOn( LokaliseFileExchange as unknown as TestableLokaliseFileExchange, "sleep", ) .mockResolvedValue(undefined); let callCount = 0; mockPool .intercept({ path: `/api2/projects/${projectId}/files/upload`, method: "POST", body: JSON.stringify(mockParams), }) .reply(() => { callCount++; if (callCount < retries) { return { statusCode: 429, data: JSON.stringify({ message: "Too Many Requests", code: 429 }), }; } return { statusCode: 200, data: JSON.stringify({ process: mockResponse }), }; }) .times(retries); const result = await uploader.uploadSingleFile(mockParams); expect(result).toEqual(mockResponse); expect(callCount).toBe(retries); expect(sleepSpy).toHaveBeenCalledTimes(retries - 1); expect(sleepSpy).toHaveBeenNthCalledWith(1, sleepTime * 2 ** 0); expect(sleepSpy).toHaveBeenNthCalledWith(2, sleepTime * 2 ** 1); }); it("should throw a LokaliseError after exceeding retries on 408 errors", async () => { const retries = 3; const sleepTime = 1; const uploader = new FakeLokaliseUpload( { apiKey }, { projectId, retryParams: { maxRetries: retries, initialSleepTime: sleepTime }, }, ); const sleepSpy = vi .spyOn( LokaliseFileExchange as unknown as TestableLokaliseFileExchange, "sleep", ) .mockResolvedValue(undefined); mockPool .intercept({ path: `/api2/projects/${projectId}/files/upload`, method: "POST", body: JSON.stringify(mockParams), }) .reply(408, { message: "Request Timeout", code: 408 }) .times(retries + 1); try { await uploader.uploadSingleFile(mockParams); } catch (e) { expect(e).toBeInstanceOf(LokaliseError); expect(e.message).toEqual("Maximum retries reached: Request Timeout"); expect(e.code).toEqual(408); expect(e.details).toEqual({ reason: "server error without details" }); } expect(sleepSpy).toHaveBeenCalledTimes(retries); expect(sleepSpy).toHaveBeenNthCalledWith(1, sleepTime * 2 ** 0); expect(sleepSpy).toHaveBeenNthCalledWith(2, sleepTime * 2 ** 1); expect(sleepSpy).toHaveBeenNthCalledWith(3, sleepTime * 2 ** 2); }); }); describe("Error Handling", () => { it("should throw a LokaliseError for known error responses", async () => { const mockError = { message: "Unauthorized", code: 403, }; mockPool .intercept({ path: `/api2/projects/${projectId}/files/upload`, method: "POST", body: JSON.stringify(mockParams), }) .reply(403, mockError); const uploader = new FakeLokaliseUpload({ apiKey }, { projectId }); await expect(uploader.uploadSingleFile(mockParams)).rejects.toThrow( new LokaliseError(mockError.message, mockError.code, { reason: "server error without details", }), ); }); }); });