Download packages/api/src/__tests__/queue.test.ts from Atmint/cpns: direct link, hf CLI and curl.
- Browser
- Download file 7.75 kB
-
https://huggingface.co/spaces/Atmint/cpns/resolve/main/packages/api/src/__tests__/queue.test.ts
- Command line
-
hf download hf://spaces/Atmint/cpns/packages/api/src/__tests__/queue.test.ts
-
curl -L -o queue.test.ts https://huggingface.co/spaces/Atmint/cpns/resolve/main/packages/api/src/__tests__/queue.test.ts
7.75 kB
| import { describe, expect, it, beforeAll } from "bun:test"; | |
| let GenerationJobCancelledError: any, | |
| computeSectionSplit: any, | |
| splitIntoShards: any, | |
| normalizeQuestions: any, | |
| runWithConcurrency: any, | |
| decryptInputFromDb: any; | |
| beforeAll(async () => { | |
| process.env.DATABASE_URL = "postgres://localhost:5432/test"; | |
| process.env.BETTER_AUTH_SECRET = "a".repeat(32); | |
| process.env.BETTER_AUTH_URL = "http://localhost:3000"; | |
| process.env.CORS_ORIGIN = "http://localhost:5173"; | |
| process.env.API_KEY_ENCRYPTION_KEY = "z".repeat(32); | |
| process.env.REDIS_URL = "redis://localhost:6379"; | |
| process.env.SMTP_HOST = "localhost"; | |
| process.env.SMTP_USER = "test"; | |
| process.env.SMTP_PASS = "test"; | |
| process.env.SMTP_FROM = "test@test.com"; | |
| const mod = await import("../queue"); | |
| GenerationJobCancelledError = mod.GenerationJobCancelledError; | |
| computeSectionSplit = mod.computeSectionSplit; | |
| splitIntoShards = mod.splitIntoShards; | |
| normalizeQuestions = mod.normalizeQuestions; | |
| runWithConcurrency = mod.runWithConcurrency; | |
| decryptInputFromDb = mod.decryptInputFromDb; | |
| }); | |
| describe("GenerationJobCancelledError", () => { | |
| it("creates error with correct name", () => { | |
| const err = new GenerationJobCancelledError(); | |
| expect(err).toBeInstanceOf(Error); | |
| expect(err.name).toBe("GenerationJobCancelledError"); | |
| expect(err.message).toBe("JOB_CANCELLED"); | |
| }); | |
| }); | |
| describe("computeSectionSplit", () => { | |
| it("returns single section when no sections selected", () => { | |
| const result = computeSectionSplit([], 10); | |
| expect(result).toEqual([{ section: "READING", count: 10 }]); | |
| }); | |
| it("returns single section when count < 20 even with multiple sections", () => { | |
| const result = computeSectionSplit(["READING", "LISTENING"], 15); | |
| expect(result).toEqual([{ section: "READING", count: 15 }]); | |
| }); | |
| it("distributes evenly when count >= 20 and multiple sections", () => { | |
| const result = computeSectionSplit(["READING", "LISTENING"], 20); | |
| expect(result).toHaveLength(2); | |
| expect(result[0]!).toEqual({ section: "READING", count: 10 }); | |
| expect(result[1]!).toEqual({ section: "LISTENING", count: 10 }); | |
| }); | |
| it("distributes remainder (first section gets extra)", () => { | |
| const result = computeSectionSplit(["READING", "LISTENING", "WRITING"], 22); | |
| expect(result).toHaveLength(3); | |
| expect(result[0]!).toEqual({ section: "READING", count: 8 }); // 7 + 1 | |
| expect(result[1]!).toEqual({ section: "LISTENING", count: 7 }); // 7 + 1 | |
| expect(result[2]!).toEqual({ section: "WRITING", count: 7 }); // 7 | |
| }); | |
| it("handles single section with high count", () => { | |
| const result = computeSectionSplit(["READING"], 40); | |
| expect(result).toEqual([{ section: "READING", count: 40 }]); | |
| }); | |
| }); | |
| describe("splitIntoShards", () => { | |
| it("creates one shard when count <= max per shard", () => { | |
| const result = splitIntoShards([{ section: "READING", count: 5 }]); | |
| expect(result).toHaveLength(1); | |
| expect(result[0]!).toMatchObject({ | |
| section: "READING", count: 5, sectionIndex: 0, shardIndex: 0, shardCount: 1, | |
| }); | |
| }); | |
| it("splits into multiple shards when count exceeds max", () => { | |
| const result = splitIntoShards([{ section: "READING", count: 20 }]); | |
| expect(result).toHaveLength(3); // ceil(20/8) = 3 | |
| expect(result[0]!.count).toBe(8); | |
| expect(result[2]!.count).toBe(4); // last shard has remainder | |
| result.forEach((shard) => { | |
| expect(shard.section).toBe("READING"); | |
| expect(shard.shardCount).toBe(3); | |
| }); | |
| }); | |
| it("handles multiple sections with sharding", () => { | |
| const result = splitIntoShards([ | |
| { section: "READING", count: 10 }, | |
| { section: "LISTENING", count: 10 }, | |
| ]); | |
| expect(result).toHaveLength(4); // ceil(10/8) + ceil(10/8) = 2 + 2 | |
| expect(result[0]!.section).toBe("READING"); | |
| expect(result[0]!.sectionIndex).toBe(0); | |
| expect(result[2]!.section).toBe("LISTENING"); | |
| expect(result[2]!.sectionIndex).toBe(1); | |
| }); | |
| }); | |
| describe("normalizeQuestions", () => { | |
| const model = "gpt-4"; | |
| it("transforms GenerationResult questions to PersistableQuestion", () => { | |
| const result = { | |
| questions: [{ | |
| format: "multiple_choice", | |
| passageText: "A".repeat(50), | |
| questionText: "Question?", | |
| options: [{ key: "A", text: "Opt" }], | |
| correctAnswer: "A", | |
| explanation: "Because", | |
| difficulty: 3, | |
| skillTags: ["reading"], | |
| }], | |
| meta: { model, tokensUsed: 100, durationMs: 1000, mode: "quick" as const }, | |
| }; | |
| const normalized = normalizeQuestions("READING", model, result as any); | |
| expect(normalized).toHaveLength(1); | |
| expect(normalized[0]!).toMatchObject({ | |
| section: "READING", | |
| format: "multiple_choice", | |
| correctAnswer: "A", | |
| difficulty: 3, | |
| aiModel: model, | |
| }); | |
| }); | |
| it("sets options to null when not present", () => { | |
| const result = { | |
| questions: [{ | |
| format: "fill_blank", | |
| passageText: "A".repeat(50), | |
| questionText: "Fill ___", | |
| correctAnswer: "answer", | |
| explanation: "explain", | |
| difficulty: 2, | |
| skillTags: ["grammar"], | |
| }], | |
| meta: { model, tokensUsed: 50, durationMs: 500, mode: "quick" as const }, | |
| }; | |
| const normalized = normalizeQuestions("READING", model, result as any); | |
| expect(normalized[0]!.options).toBeNull(); | |
| }); | |
| it("turns off isCaseSensitive on correctAnswer when caseSensitive is false", () => { | |
| const result = { | |
| questions: [{ | |
| isCaseSensitive: false, | |
| format: "fill_blank", | |
| passageText: "A".repeat(50), | |
| questionText: "Fill ___", | |
| correctAnswer: "Answer", | |
| explanation: "explain", | |
| difficulty: 2, | |
| skillTags: ["grammar"], | |
| }], | |
| meta: { model, tokensUsed: 50, durationMs: 500, mode: "quick" as const }, | |
| }; | |
| const normalized = normalizeQuestions("READING", model, result as any); | |
| expect(normalized[0]!.correctAnswer).toBe("Answer"); | |
| }); | |
| }); | |
| describe("runWithConcurrency", () => { | |
| it("processes all items with concurrency", async () => { | |
| const items = [1, 2, 3, 4, 5]; | |
| const results = await runWithConcurrency(items, 3, async (item) => item * 2); | |
| expect(results).toEqual([2, 4, 6, 8, 10]); | |
| }); | |
| it("maintains order of results", async () => { | |
| const items = ["a", "b", "c"]; | |
| const results = await runWithConcurrency(items, 2, async (item, idx) => `${item}-${idx}`); | |
| expect(results).toEqual(["a-0", "b-1", "c-2"]); | |
| }); | |
| it("handles empty array", async () => { | |
| const results = await runWithConcurrency([], 3, async (item) => item); | |
| expect(results).toEqual([]); | |
| }); | |
| it("propagates errors from worker", async () => { | |
| const items = [1, 2, 3]; | |
| expect( | |
| runWithConcurrency(items, 2, async (item) => { | |
| if (item === 2) throw new Error("Item 2 failed"); | |
| return item; | |
| }), | |
| ).rejects.toThrow("Item 2 failed"); | |
| }); | |
| }); | |
| describe("decryptInputFromDb", () => { | |
| it("passes through unencrypted apiKey (legacy data)", () => { | |
| const input = { | |
| examType: "IELTS", | |
| section: "READING", | |
| apiKeyConfig: { | |
| baseUrl: "https://api.openai.com/v1", | |
| apiKey: "sk-legacy-key", | |
| model: "gpt-4", | |
| }, | |
| }; | |
| const result = decryptInputFromDb(input as any); | |
| expect(result.apiKeyConfig.apiKey).toBe("sk-legacy-key"); | |
| }); | |
| it("handles missing apiKey in config", () => { | |
| const input = { | |
| examType: "IELTS", | |
| section: "READING", | |
| apiKeyConfig: { | |
| baseUrl: "https://api.openai.com/v1", | |
| model: "gpt-4", | |
| }, | |
| }; | |
| const result = decryptInputFromDb(input as any); | |
| expect(result.apiKeyConfig.apiKey).toBeUndefined(); | |
| }); | |
| }); | |