i18n-ai-translate
Version:
AI-powered localization CLI, Node library, and GitHub Action. Translate i18next JSON, Gettext PO, Java .properties, and iOS .strings with ChatGPT, Claude, Gemini, or local Ollama models.
92 lines (77 loc) • 3 kB
text/typescript
import ChatPool from "../chat_pool";
import Engine from "../enums/engine";
import RateLimiter from "../rate_limiter";
const mkPool = (concurrency: number): ChatPool =>
ChatPool.create({
chatParams: {} as any,
concurrency,
engine: Engine.ChatGPT,
model: "gpt-4.1",
rateLimiter: new RateLimiter(0, false),
});
describe("ChatPool", () => {
it("creates N triples and reports the correct size", () => {
const pool = mkPool(3);
expect(pool.size).toBe(3);
expect(pool.all()).toHaveLength(3);
});
it("each triple contains three distinct chat instances", () => {
const pool = mkPool(2);
for (const triple of pool.all()) {
expect(triple.generateTranslationChat).toBeDefined();
expect(triple.verifyTranslationChat).toBeDefined();
expect(triple.verifyStylingChat).toBeDefined();
expect(triple.generateTranslationChat).not.toBe(
triple.verifyTranslationChat,
);
}
});
it("different triples are distinct instances", () => {
const pool = mkPool(3);
const all = pool.all();
expect(all[0].generateTranslationChat).not.toBe(
all[1].generateTranslationChat,
);
expect(all[1].generateTranslationChat).not.toBe(
all[2].generateTranslationChat,
);
});
it("run() never hands the same triple to two concurrent tasks", async () => {
const pool = mkPool(2);
const inUse = new Set<unknown>();
let maxConcurrent = 0;
let active = 0;
const task = (): Promise<void> =>
pool.run(async (chats) => {
expect(inUse.has(chats)).toBe(false);
inUse.add(chats);
active++;
maxConcurrent = Math.max(maxConcurrent, active);
// Yield once so other waiters get a chance to acquire.
await Promise.resolve();
active--;
inUse.delete(chats);
});
await Promise.all([task(), task(), task(), task()]);
expect(maxConcurrent).toBeLessThanOrEqual(2);
});
it("serializes work beyond pool capacity", async () => {
const pool = mkPool(1);
const order: number[] = [];
const task = (n: number): Promise<void> =>
pool.run(async () => {
order.push(n);
await Promise.resolve();
});
await Promise.all([task(1), task(2), task(3)]);
expect(order).toEqual([1, 2, 3]);
});
it("releases triples after errors so the pool does not deadlock", async () => {
const pool = mkPool(1);
await expect(
pool.run(() => Promise.reject(new Error("boom"))),
).rejects.toThrow("boom");
// Should still be acquirable after the failed task.
await expect(pool.run(() => Promise.resolve("ok"))).resolves.toBe("ok");
});
});