D2: verify GET /api/jobs returns error field for failed jobs

Verification tests for outcome-contract clause
failed-job-error-exposed-for-ui-banner.

- db.test.ts: listJobs includes error for failed jobs, null for non-failed
- jobs-list-endpoint.test.ts: route-level integration with real DB,
  exercises GET handler end-to-end

D2 already merged (f03b757+); non-blocking verification only.
This commit is contained in:
Giancarmine Salucci
2026-07-09 04:24:16 +02:00
parent 76b8a466c2
commit f05c772952
2 changed files with 99 additions and 0 deletions
+18
View File
@@ -174,6 +174,24 @@ describe('listJobs', () => {
it('returns an array (possibly empty)', () => { it('returns an array (possibly empty)', () => {
expect(Array.isArray(listJobs())).toBe(true); expect(Array.isArray(listJobs())).toBe(true);
}); });
it('includes error field for failed jobs — D2 error banner data contract', () => {
const job = createJob('src', 'Failed Job', 'auto');
updateJob({ id: job.id, status: 'failed', error: 'simulated transcription failure for outcome-contract probe' });
const jobs = listJobs();
const found = jobs.find((j) => j.id === job.id);
expect(found).toBeDefined();
expect(found!.error).toBe('simulated transcription failure for outcome-contract probe');
});
it('returns null error for non-failed jobs', () => {
const job = createJob('src', 'Done Job', 'auto');
updateJob({ id: job.id, status: 'done', error: null });
const jobs = listJobs();
const found = jobs.find((j) => j.id === job.id);
expect(found).toBeDefined();
expect(found!.error).toBeNull();
});
}); });
// ── Push subscriptions ──────────────────────────────────────────────────────── // ── Push subscriptions ────────────────────────────────────────────────────────
+81
View File
@@ -0,0 +1,81 @@
import { describe, it, expect, afterAll, vi } from 'vitest';
import { join } from 'path';
import { tmpdir } from 'os';
import { rm } from 'fs/promises';
/**
* Integration test for GET /api/jobs — verifies the error field is
* exposed for failed jobs and null for non-failed jobs, exercising
* the full db→API path with a real SQLite database.
*
* Covers outcome-contract clause failed-job-error-exposed-for-ui-banner.
* D2 already merged (f03b757+); this is non-blocking verification.
*/
const TEST_DATA_DIR = join(tmpdir(), 'whisper-pwa-jobs-list-int-' + process.pid);
vi.stubEnv('DATA_DIR', TEST_DATA_DIR);
const { createJob, getJob, updateJob, listJobs } = await import('$lib/server/db.js');
const { GET } = await import('$lib/../routes/api/jobs/+server.js');
afterAll(async () => {
await rm(TEST_DATA_DIR, { recursive: true, force: true });
});
describe('GET /api/jobs — error field exposure', () => {
it('includes error field for a failed job', async () => {
const job = createJob('probe-upload.webm', 'UI error probe', 'auto');
updateJob({
id: job.id,
status: 'failed',
error: 'simulated transcription failure for outcome-contract probe'
});
const res = await GET();
expect(res.status).toBe(200);
const body = await res.json();
expect(Array.isArray(body)).toBe(true);
const found = body.find((j: any) => j.id === job.id);
expect(found).toBeDefined();
expect(found.status).toBe('failed');
expect(found.error).toBe('simulated transcription failure for outcome-contract probe');
});
it('returns null error for a non-failed job', async () => {
const job = createJob('probe-done.webm', 'Done probe', 'auto');
updateJob({ id: job.id, status: 'done', error: null });
const res = await GET();
expect(res.status).toBe(200);
const body = await res.json();
const found = body.find((j: any) => j.id === job.id);
expect(found).toBeDefined();
expect(found.error).toBeNull();
});
it('preserves other job fields when error is present', async () => {
const job = createJob('my-recording.webm', 'My Recording', 'aggressive');
updateJob({
id: job.id,
status: 'failed',
error: 'transcription failed',
progress: 50,
whisperJobId: 'whisper-123'
});
const res = await GET();
const body = await res.json();
const found = body.find((j: any) => j.id === job.id);
expect(found).toBeDefined();
expect(found.status).toBe('failed');
expect(found.error).toBe('transcription failed');
expect(found.title).toBe('My Recording');
expect(found.source).toBe('my-recording.webm');
expect(found.audioMode).toBe('aggressive');
expect(found.progress).toBe(50);
expect(found.whisperJobId).toBe('whisper-123');
});
});