UNPKG

@lokalise/node-api

Version:
188 lines (166 loc) 4.77 kB
import { readFile } from "node:fs/promises"; import { MockAgent, setGlobalDispatcher } from "undici"; import type { IncomingHttpHeaders } from "undici/types/header.js"; import type { Interceptable, MockInterceptor, } from "undici/types/mock-interceptor.js"; import { getVersion } from "../src/lokalise/pkg.js"; import type { HttpMethod } from "../src/types/http_method.js"; const packageVersion = await getVersion(); const mockAgent = new MockAgent(); setGlobalDispatcher(mockAgent); mockAgent.disableNetConnect(); type StubParams = Partial<{ uri: string; fixture: string; method: HttpMethod; query: Record<string, unknown>; body: Record<string, unknown>; reqHeaders: Record<string, string>; respHeaders: IncomingHttpHeaders; status: number; doFail: boolean; rootUrl: string; skipApiToken: boolean; version: string; data: string; delay: number; }>; export class Stub { private readonly uriPath: string; private readonly fixturePath: string; private readonly httpMethod: HttpMethod; private readonly requestHeaders: Record<string, string> | undefined; private readonly requestBody: string | undefined; private readonly responseHeaders: IncomingHttpHeaders | undefined; private readonly statusCode: number; private readonly doFail: boolean; private readonly rootUrl: string; private readonly version: string; private readonly data: string | undefined; private readonly delay: number; constructor(params: StubParams) { if (!params.uri) { throw new Error("The 'uri' parameter is required."); } const { uri, fixture, method = "GET", query, body, reqHeaders, respHeaders, status = 200, doFail = false, rootUrl = "https://api.lokalise.com", skipApiToken = false, version = "api2", data, delay, } = params; this.uriPath = this.buildUriPath(uri, query); this.fixturePath = fixture ? `./fixtures/${fixture}` : ""; this.httpMethod = method; this.requestHeaders = this.buildRequestHeaders( skipApiToken, body, reqHeaders, ); this.responseHeaders = respHeaders; this.requestBody = body ? JSON.stringify(body) : undefined; this.statusCode = status; this.doFail = doFail; this.rootUrl = rootUrl; this.version = version; this.data = data; this.delay = delay ?? 0; } async setStub() { const mockPool = mockAgent.get(this.rootUrl); try { await this.setupMock(mockPool); } catch (error) { throw new Error( `Failed to set up mock for URI: ${this.uriPath} - ${error.message}`, ); } } private async setupMock(mockPool: Interceptable) { const mockOpts = this.buildMockOptions(); const respOpts = this.buildResponseOptions(); await this.respond(mockPool, mockOpts, this.doFail, respOpts); } private async respond( mockPool: Interceptable, mockOpts: MockInterceptor.Options, isError: boolean, respOpts: MockInterceptor.MockResponseOptions, ) { if (isError) { mockPool.intercept(mockOpts).replyWithError(new Error("Fail")); } else { const responseData = this.data ?? (await this.readFixture()); const pool = mockPool .intercept({ ...mockOpts, body: this.requestBody }) .reply(this.statusCode, responseData, respOpts); if (this.delay > 0) { pool.delay(this.delay); } } } private async readFixture(): Promise<Record<string, unknown> | string> { if (!this.fixturePath) { return ""; } try { const fileContent = await readFile( new URL(this.fixturePath, import.meta.url), ); return JSON.parse(fileContent.toString()); } catch (error) { console.error( `Error reading fixture file at ${this.fixturePath}:`, error, ); throw error; } } private buildRequestHeaders( skipApiToken: boolean, body: Record<string, unknown> | undefined, reqHeaders?: Record<string, string>, ): Record<string, string> { return { Accept: "application/json", "User-Agent": `node-lokalise-api/${packageVersion}`, ...(skipApiToken ? {} : { "x-api-token": String(process.env.API_KEY) }), ...(body ? { "Content-type": "application/json" } : {}), ...reqHeaders, }; } private buildUriPath(uri: string, query?: Record<string, unknown>): string { if (!query) { return uri; } const stringifiedQuery = Object.fromEntries( Object.entries(query) .filter(([, value]) => value !== undefined && value !== null) .map(([key, value]) => [key, String(value)]), ); return `${uri}?${new URLSearchParams(stringifiedQuery).toString()}`; } private buildMockOptions(): MockInterceptor.Options { return { method: this.httpMethod, path: `/${this.version}/${this.uriPath}`, headers: this.requestHeaders, }; } private buildResponseOptions(): MockInterceptor.MockResponseOptions { return { headers: this.responseHeaders, }; } }