686434f5c3
- delete server/services/jellyfin.ts, webhook.ts, mqtt.ts and their tests - strip jellyfin/mqtt imports and startup calls from index.tsx and settings.ts - remove /jellyfin, /mqtt, /mqtt/status, /mqtt/test, /jellyfin/webhook-plugin endpoints from settings router - clean ENV_MAP and isEnvConfigured of jellyfin/mqtt keys - add db/index.ts migrations for series_key, duration_seconds, scan_status, scan_error, last_scanned_at (new columns absent on older dev DBs) - move idx_media_items_series_key out of SCHEMA into migrate() so it runs after the column is added - fix all test fixtures: drop jellyfin_id/series_jellyfin_id column refs, update MediaItem/MediaStream object literals to match current types Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
97 lines
3.5 KiB
TypeScript
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, type, name, file_path, container) VALUES (?, 'Movie', ?, ?, 'mkv')",
|
|
)
|
|
.run(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);
|
|
});
|
|
});
|