Files
netfelix-audio-fix/server/api/__tests__/review-unsort-reopen.test.ts
T
felixfoertsch 0fd3624d9f
Build and Push Docker Image / build (push) Successful in 4m3s
pipeline: uniform column headers, auto-process queue toggle, reopen → inbox
column headers are now a fixed three-row layout (title / subtitle / button
row). every column always reserves all three rows so headers line up
regardless of contents; actions render disabled when their column is
empty instead of disappearing, which keeps the header height stable as
state changes.

the processing column gets a new "Auto-process Queue" checkbox that
mirrors the inbox's "Auto-process Inbox" toggle. backend adds an
auto_process_queue config, a maybeStartQueueProcessor() helper, a
POST /api/settings/auto-process-queue endpoint, and a hook in
enqueueAudioJob so approvals drain the queue hands-off when the toggle
is on.

reopen-all and per-item reopen now send items to the Inbox (sorted=0)
instead of back to Review. the done column's label and tooltip become
"← Back to inbox" to match, and the clear button moves to the right
slot so the header pattern (left=backward, right=forward) stays
consistent across columns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:57:13 +02:00

97 lines
3.5 KiB
TypeScript

import { Database } from "bun:sqlite";
import { describe, expect, test } from "bun:test";
import { SCHEMA } from "../../db/schema";
import { reopenAllDone, unsortAll } from "../review";
function makeDb(): Database {
const db = new Database(":memory:");
for (const stmt of SCHEMA.split(";")) {
const trimmed = stmt.trim();
if (trimmed) db.run(trimmed);
}
return db;
}
function seedPlan(db: Database, id: number, opts: { sorted?: 0 | 1; status?: string; isNoop?: 0 | 1 } = {}) {
const { sorted = 1, status = "pending", isNoop = 0 } = opts;
db
.prepare(
"INSERT INTO media_items (id, jellyfin_id, type, name, file_path, container) VALUES (?, ?, 'Movie', ?, ?, 'mkv')",
)
.run(id, `jf-${id}`, `Item ${id}`, `/x/${id}.mkv`);
db
.prepare(
"INSERT INTO review_plans (item_id, status, is_noop, auto_class, sorted, apple_compat, job_type) VALUES (?, ?, ?, 'auto_heuristic', ?, 'direct_play', 'copy')",
)
.run(id, status, isNoop, sorted);
}
describe("unsortAll", () => {
test("flips sorted=1 pending plans back to sorted=0, skips is_noop and non-pending", () => {
const db = makeDb();
seedPlan(db, 1, { sorted: 1, status: "pending" });
seedPlan(db, 2, { sorted: 1, status: "pending" });
seedPlan(db, 3, { sorted: 0, status: "pending" }); // already in inbox
seedPlan(db, 4, { sorted: 1, status: "approved" }); // queued
seedPlan(db, 5, { sorted: 1, status: "pending", isNoop: 1 }); // noop
const count = unsortAll(db);
expect(count).toBe(2);
const rows = db.prepare("SELECT item_id, sorted, status FROM review_plans ORDER BY item_id").all() as {
item_id: number;
sorted: number;
status: string;
}[];
expect(rows).toEqual([
{ item_id: 1, sorted: 0, status: "pending" },
{ item_id: 2, sorted: 0, status: "pending" },
{ item_id: 3, sorted: 0, status: "pending" },
{ item_id: 4, sorted: 1, status: "approved" },
{ item_id: 5, sorted: 1, status: "pending" },
]);
});
test("noop when review column is empty", () => {
const db = makeDb();
expect(unsortAll(db)).toBe(0);
});
});
describe("reopenAllDone", () => {
test("flips done/error plans back to pending + inbox (sorted=0) and drops their jobs", () => {
const db = makeDb();
seedPlan(db, 1, { status: "done" });
seedPlan(db, 2, { status: "error" });
seedPlan(db, 3, { status: "approved" }); // untouched
db.prepare("INSERT INTO jobs (item_id, command, job_type, status) VALUES (1, 'ffmpeg', 'copy', 'done')").run();
db.prepare("INSERT INTO jobs (item_id, command, job_type, status) VALUES (2, 'ffmpeg', 'copy', 'error')").run();
db.prepare("INSERT INTO jobs (item_id, command, job_type, status) VALUES (3, 'ffmpeg', 'copy', 'pending')").run();
const count = reopenAllDone(db);
expect(count).toBe(2);
const statuses = db
.prepare("SELECT item_id, status, sorted, reviewed_at FROM review_plans ORDER BY item_id")
.all() as {
item_id: number;
status: string;
sorted: number;
reviewed_at: string | null;
}[];
expect(statuses[0]).toMatchObject({ status: "pending", sorted: 0, reviewed_at: null });
expect(statuses[1]).toMatchObject({ status: "pending", sorted: 0, reviewed_at: null });
// Untouched plan keeps its pre-existing sorted=1 (default for the seed).
expect(statuses[2]).toMatchObject({ status: "approved", sorted: 1 });
const jobs = db.prepare("SELECT item_id, status FROM jobs ORDER BY item_id").all();
expect(jobs).toEqual([{ item_id: 3, status: "pending" }]);
});
test("noop when nothing is done or errored", () => {
const db = makeDb();
seedPlan(db, 1, { status: "pending" });
expect(reopenAllDone(db)).toBe(0);
});
});