mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test: massive test coverage expansion (+1,437 tests, 22 new files)
Expand test coverage across all layers via 14 parallel agents: Unit tests (3,378 total, +534): - First-ever AI sidecar tests (157 tests covering bridge lifecycle, all 12 tool modules) - API route infrastructure (auth, pipeline, batch, settings, teams, roles, audit, api-keys, files, docs) - Lib coverage improvements (audit 7%->95%, worker-pool 33%->100%) - Web store/lib gap fills (features-store, tool-registry) Integration tests (4,403 total, +903): - Expanded 19 tool test files with parameter variations, format edge cases, boundary values - Cross-format matrix: 290 tests covering 14 tools x 17 formats - Adversarial/edge cases: 63 tests for extreme inputs, concurrent requests, corrupted files E2E-Docker (125 new tests): - Expanded 8 spec files + 1 new file covering all 49 tools - Added HEIC/format handling, auth failures, download verification GUI E2E (expanded 28 spec files): - Navigation, responsive layout, keyboard shortcuts - All 51 tool UIs with settings, processing, display modes - Batch/pipeline workflows, settings/RBAC, visual regression - Resilience, accessibility (ARIA, contrast, focus), performance budgets
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Unit tests for the progress tracking module.
|
||||
*
|
||||
* Tests updateJobProgress, updateSingleFileProgress, recoverStaleJobs,
|
||||
* and the in-memory pub/sub listener system.
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
// Mock DB
|
||||
vi.mock("../../../apps/api/src/db/index.js", () => ({
|
||||
db: {
|
||||
select: () => ({
|
||||
from: () => ({
|
||||
where: () => ({ get: () => null }),
|
||||
}),
|
||||
}),
|
||||
insert: () => ({ values: () => ({ run: vi.fn() }) }),
|
||||
update: () => ({
|
||||
set: () => ({
|
||||
where: () => ({ run: () => ({ changes: 0 }) }),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
schema: {
|
||||
jobs: { id: {}, status: {} },
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("../../../apps/api/src/config.js", () => ({
|
||||
env: { WORKSPACE_PATH: "/tmp/test" },
|
||||
}));
|
||||
|
||||
import type { JobProgress } from "../../../apps/api/src/routes/progress.js";
|
||||
import {
|
||||
recoverStaleJobs,
|
||||
updateJobProgress,
|
||||
updateSingleFileProgress,
|
||||
} from "../../../apps/api/src/routes/progress.js";
|
||||
|
||||
describe("updateJobProgress", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("stores progress that can be sent to SSE listeners", () => {
|
||||
const progress: JobProgress = {
|
||||
jobId: "test-job-1",
|
||||
status: "processing",
|
||||
totalFiles: 5,
|
||||
completedFiles: 2,
|
||||
failedFiles: 0,
|
||||
errors: [],
|
||||
};
|
||||
|
||||
// Should not throw
|
||||
expect(() => updateJobProgress(progress)).not.toThrow();
|
||||
});
|
||||
|
||||
it("handles completed status", () => {
|
||||
const progress: JobProgress = {
|
||||
jobId: "test-job-2",
|
||||
status: "completed",
|
||||
totalFiles: 3,
|
||||
completedFiles: 3,
|
||||
failedFiles: 0,
|
||||
errors: [],
|
||||
};
|
||||
|
||||
expect(() => updateJobProgress(progress)).not.toThrow();
|
||||
});
|
||||
|
||||
it("handles failed status with errors", () => {
|
||||
const progress: JobProgress = {
|
||||
jobId: "test-job-3",
|
||||
status: "failed",
|
||||
totalFiles: 2,
|
||||
completedFiles: 2,
|
||||
failedFiles: 2,
|
||||
errors: [
|
||||
{ filename: "a.png", error: "corrupt" },
|
||||
{ filename: "b.png", error: "too large" },
|
||||
],
|
||||
};
|
||||
|
||||
expect(() => updateJobProgress(progress)).not.toThrow();
|
||||
});
|
||||
|
||||
it("handles progress with currentFile", () => {
|
||||
const progress: JobProgress = {
|
||||
jobId: "test-job-4",
|
||||
status: "processing",
|
||||
totalFiles: 5,
|
||||
completedFiles: 1,
|
||||
failedFiles: 0,
|
||||
errors: [],
|
||||
currentFile: "photo.png",
|
||||
};
|
||||
|
||||
expect(() => updateJobProgress(progress)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateSingleFileProgress", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("handles processing phase", () => {
|
||||
expect(() =>
|
||||
updateSingleFileProgress({
|
||||
jobId: "single-1",
|
||||
phase: "processing",
|
||||
percent: 50,
|
||||
stage: "Downloading model...",
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("handles complete phase", () => {
|
||||
expect(() =>
|
||||
updateSingleFileProgress({
|
||||
jobId: "single-2",
|
||||
phase: "complete",
|
||||
percent: 100,
|
||||
stage: "Done",
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("handles failed phase with error", () => {
|
||||
expect(() =>
|
||||
updateSingleFileProgress({
|
||||
jobId: "single-3",
|
||||
phase: "failed",
|
||||
percent: 0,
|
||||
error: "Model not found",
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("handles progress with result data", () => {
|
||||
expect(() =>
|
||||
updateSingleFileProgress({
|
||||
jobId: "single-4",
|
||||
phase: "complete",
|
||||
percent: 100,
|
||||
result: { text: "OCR result" },
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("recoverStaleJobs", () => {
|
||||
it("does not throw when called", () => {
|
||||
expect(() => recoverStaleJobs()).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("JobProgress type shape", () => {
|
||||
it("supports all required fields", () => {
|
||||
const p: JobProgress = {
|
||||
jobId: "shape-test",
|
||||
status: "processing",
|
||||
totalFiles: 1,
|
||||
completedFiles: 0,
|
||||
failedFiles: 0,
|
||||
errors: [],
|
||||
};
|
||||
expect(p.jobId).toBe("shape-test");
|
||||
expect(p.status).toBe("processing");
|
||||
expect(p.totalFiles).toBe(1);
|
||||
expect(p.currentFile).toBeUndefined();
|
||||
});
|
||||
|
||||
it("supports optional currentFile and type", () => {
|
||||
const p: JobProgress = {
|
||||
jobId: "shape-test-2",
|
||||
type: "batch",
|
||||
status: "completed",
|
||||
totalFiles: 3,
|
||||
completedFiles: 3,
|
||||
failedFiles: 0,
|
||||
errors: [],
|
||||
currentFile: "last.png",
|
||||
};
|
||||
expect(p.type).toBe("batch");
|
||||
expect(p.currentFile).toBe("last.png");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user