Download apps/api/src/__tests__/analysis-upload-mode.test.ts from Lazywords/Rl-Auto: direct link, hf CLI and curl.
- Browser
- Download file 7.02 kB
-
https://huggingface.co/spaces/Lazywords/Rl-Auto/resolve/main/apps/api/src/__tests__/analysis-upload-mode.test.ts
- Command line
-
hf download hf://spaces/Lazywords/Rl-Auto/apps/api/src/__tests__/analysis-upload-mode.test.ts
-
curl -L -o analysis-upload-mode.test.ts https://huggingface.co/spaces/Lazywords/Rl-Auto/resolve/main/apps/api/src/__tests__/analysis-upload-mode.test.ts
7.02 kB
| import Fastify from "fastify"; | |
| import fastifyMultipart from "@fastify/multipart"; | |
| import JSZip from "jszip"; | |
| import { mkdtempSync, rmSync } from "node:fs"; | |
| import { tmpdir } from "node:os"; | |
| import { join } from "node:path"; | |
| import { randomUUID } from "node:crypto"; | |
| import { afterEach, describe, expect, it } from "vitest"; | |
| import { registerAnalysisRoutes } from "../routes/analysis.js"; | |
| import type { DetectedMode, ItemRow, JobRow, Store } from "../queue/store.js"; | |
| function now() { | |
| return Date.now(); | |
| } | |
| function createFakeStore(): Store { | |
| const jobs = new Map<string, JobRow>(); | |
| const items = new Map<string, ItemRow>(); | |
| return { | |
| close() {}, | |
| createJob(optionsSnapshot: object) { | |
| const ts = now(); | |
| const job: JobRow = { | |
| id: randomUUID(), | |
| status: "pending", | |
| options_snapshot: JSON.stringify(optionsSnapshot), | |
| created_at: ts, | |
| updated_at: ts, | |
| }; | |
| jobs.set(job.id, job); | |
| return job; | |
| }, | |
| getJob(id: string) { | |
| return jobs.get(id) ?? null; | |
| }, | |
| setJobStatus(id, status) { | |
| const job = jobs.get(id); | |
| if (job) jobs.set(id, { ...job, status, updated_at: now() }); | |
| }, | |
| updateJobOptions(id, optionsSnapshot) { | |
| const job = jobs.get(id); | |
| if (job) { | |
| jobs.set(id, { | |
| ...job, | |
| options_snapshot: JSON.stringify(optionsSnapshot), | |
| updated_at: now(), | |
| }); | |
| } | |
| }, | |
| listResumableJobs() { | |
| return []; | |
| }, | |
| recoverInterruptedJob() {}, | |
| createItem(input: { | |
| jobId: string; | |
| ord: number; | |
| filename: string; | |
| uploadPath: string; | |
| detectedMode: DetectedMode; | |
| }) { | |
| const ts = now(); | |
| const item: ItemRow = { | |
| id: randomUUID(), | |
| job_id: input.jobId, | |
| ord: input.ord, | |
| filename: input.filename, | |
| upload_path: input.uploadPath, | |
| detected_mode: input.detectedMode, | |
| status: "queued", | |
| evidence_path: null, | |
| task_package_path: null, | |
| judge_result_path: null, | |
| export_zip_path: null, | |
| error_preview: null, | |
| error_details_path: null, | |
| attempt_count: 0, | |
| last_stage: null, | |
| created_at: ts, | |
| updated_at: ts, | |
| }; | |
| items.set(item.id, item); | |
| return item; | |
| }, | |
| getItem(id) { | |
| return items.get(id) ?? null; | |
| }, | |
| listItemsByJob(jobId) { | |
| return [...items.values()] | |
| .filter((item) => item.job_id === jobId) | |
| .sort((a, b) => a.ord - b.ord); | |
| }, | |
| setItemStatus(id, status, errorPreview, meta) { | |
| const item = items.get(id); | |
| if (item) { | |
| items.set(id, { | |
| ...item, | |
| status, | |
| error_preview: errorPreview ?? item.error_preview, | |
| last_stage: meta?.lastStage ?? item.last_stage, | |
| error_details_path: meta?.errorDetailsPath ?? item.error_details_path, | |
| updated_at: now(), | |
| }); | |
| } | |
| }, | |
| setItemPaths(id, paths) { | |
| const item = items.get(id); | |
| if (item) { | |
| items.set(id, { | |
| ...item, | |
| evidence_path: paths.evidencePath ?? item.evidence_path, | |
| task_package_path: paths.taskPackagePath ?? item.task_package_path, | |
| judge_result_path: paths.judgeResultPath ?? item.judge_result_path, | |
| export_zip_path: paths.exportZipPath ?? item.export_zip_path, | |
| updated_at: now(), | |
| }); | |
| } | |
| }, | |
| takeNextQueuedItem() { | |
| return [...items.values()].find((item) => item.status === "queued") ?? null; | |
| }, | |
| resetItemForRetry(id) { | |
| const item = items.get(id); | |
| if (item) items.set(id, { ...item, status: "queued", updated_at: now() }); | |
| }, | |
| }; | |
| } | |
| async function buildApiLookingZip(): Promise<Buffer> { | |
| const zip = new JSZip(); | |
| zip.file("network.json", JSON.stringify([{ url: "https://example.test/api" }])); | |
| return zip.generateAsync({ type: "nodebuffer" }); | |
| } | |
| function multipartFileThenRequestedMode(file: Buffer) { | |
| const boundary = "----rl-auto-test-" + randomUUID(); | |
| const payload = Buffer.concat([ | |
| Buffer.from( | |
| `--${boundary}\r\n` + | |
| `Content-Disposition: form-data; name="files"; filename="api-looking.zip"\r\n` + | |
| "Content-Type: application/zip\r\n\r\n", | |
| ), | |
| file, | |
| Buffer.from( | |
| `\r\n--${boundary}\r\n` + | |
| 'Content-Disposition: form-data; name="requestedMode"\r\n\r\n' + | |
| `mcp\r\n` + | |
| `--${boundary}--\r\n`, | |
| ), | |
| ]); | |
| return { | |
| payload, | |
| contentType: `multipart/form-data; boundary=${boundary}`, | |
| }; | |
| } | |
| describe("analysis upload requestedMode", () => { | |
| let tmpRoot: string | null = null; | |
| afterEach(() => { | |
| if (tmpRoot) rmSync(tmpRoot, { recursive: true, force: true }); | |
| tmpRoot = null; | |
| }); | |
| it("uses requestedMode for every uploaded file even when the field arrives after files", async () => { | |
| tmpRoot = mkdtempSync(join(tmpdir(), "rl-auto-upload-mode-")); | |
| const app = Fastify(); | |
| await app.register(fastifyMultipart, { | |
| limits: { fileSize: 10 * 1024 * 1024, files: 10 }, | |
| }); | |
| await registerAnalysisRoutes(app, { | |
| store: createFakeStore(), | |
| config: { | |
| uploadDir: tmpRoot, | |
| maxBatchFiles: 10, | |
| maxUploadMB: 10, | |
| } as any, | |
| runner: {} as any, | |
| }); | |
| const { payload, contentType } = multipartFileThenRequestedMode( | |
| await buildApiLookingZip(), | |
| ); | |
| const res = await app.inject({ | |
| method: "POST", | |
| url: "/api/analysis/jobs", | |
| headers: { "content-type": contentType }, | |
| payload, | |
| }); | |
| await app.close(); | |
| expect(res.statusCode).toBe(200); | |
| expect(res.json()).toMatchObject({ | |
| code: 0, | |
| data: { | |
| items: [expect.objectContaining({ detectedMode: "mcp" })], | |
| }, | |
| }); | |
| }); | |
| it("clamps oversized numeric options when starting a job", async () => { | |
| tmpRoot = mkdtempSync(join(tmpdir(), "rl-auto-options-")); | |
| const store = createFakeStore(); | |
| const job = await store.createJob({}); | |
| const scheduledJobs: string[] = []; | |
| const app = Fastify(); | |
| await registerAnalysisRoutes(app, { | |
| store, | |
| config: { | |
| uploadDir: tmpRoot, | |
| maxBatchFiles: 10, | |
| maxUploadMB: 10, | |
| } as any, | |
| runner: { | |
| scheduleJob(id: string) { | |
| scheduledJobs.push(id); | |
| }, | |
| cancelJob() {}, | |
| } as any, | |
| }); | |
| const res = await app.inject({ | |
| method: "POST", | |
| url: `/api/analysis/jobs/${job.id}/start`, | |
| headers: { "content-type": "application/json" }, | |
| payload: { | |
| optionsSnapshot: { | |
| requestedMode: "api", | |
| concurrency: 99, | |
| temperature: 9, | |
| topP: 9, | |
| maxTokens: 999999, | |
| }, | |
| }, | |
| }); | |
| await app.close(); | |
| expect(res.statusCode).toBe(200); | |
| expect(scheduledJobs).toEqual([job.id]); | |
| expect(JSON.parse((await store.getJob(job.id))?.options_snapshot || "{}")).toMatchObject({ | |
| concurrency: 8, | |
| temperature: 2, | |
| topP: 1, | |
| maxTokens: 131072, | |
| }); | |
| }); | |
| }); | |