Files
netfelix-audio-fix/server/lib/__tests__/validate.test.ts
Felix Förtsch 1de5b8a89e
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m30s
address audit findings: subtitle rescan decisions, scan limit, parseId, setup gate
worked through AUDIT.md. triage:
- finding 2 (subtitle rescan wipes decisions): confirmed. /:id/rescan now
  snapshots custom_titles and calls reanalyze() after the stream delete/
  insert, mirroring the review rescan flow. exported reanalyze + titleKey
  from review.ts so both routes share the logic.
- finding 3 (scan limit accepts NaN/negatives): confirmed. extracted
  parseScanLimit into a pure helper, added unit tests covering NaN,
  negatives, floats, infinity, numeric strings. invalid input 400s and
  releases the scan_running lock.
- finding 4 (parseId lenient): confirmed. tightened the regex to /^\d+$/
  so "42abc", "abc42", "+42", "42.0" all return null. rewrote the test
  that codified the old lossy behaviour.
- finding 5 (setup_complete set before jellyfin test passes): confirmed.
  the /jellyfin endpoint still persists url+key unconditionally, but now
  only flips setup_complete=1 on a successful connection test.
- finding 6 (swallowed errors): partial. the mqtt restart and version-
  fetch swallows are intentional best-effort with downstream surfaces
  (getMqttStatus, UI fallback). only the scan.ts db-update swallow was
  a real visibility gap — logs via logError now.
- finding 1 (auth): left as-is. redacting secrets on GET without auth
  on POST is security theater; real fix is an auth layer, which is a
  design decision not a bugfix. audit removed from the tree.
- lint fail on ffmpeg.test.ts: formatted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 17:41:36 +02:00

39 lines
1.3 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { isOneOf, parseId } from "../validate";
describe("parseId", () => {
test("returns the integer for valid numeric strings", () => {
expect(parseId("42")).toBe(42);
expect(parseId("1")).toBe(1);
});
test("returns null for invalid, negative, zero, or missing ids", () => {
expect(parseId("0")).toBe(null);
expect(parseId("-1")).toBe(null);
expect(parseId("abc")).toBe(null);
expect(parseId("")).toBe(null);
expect(parseId(undefined)).toBe(null);
});
test("rejects mixed alphanumeric strings (strict — route params must be wholly numeric)", () => {
expect(parseId("42abc")).toBe(null);
expect(parseId("abc42")).toBe(null);
expect(parseId("42 ")).toBe(null);
expect(parseId("+42")).toBe(null);
expect(parseId("42.0")).toBe(null);
});
});
describe("isOneOf", () => {
test("narrows to allowed string literals", () => {
expect(isOneOf("keep", ["keep", "remove"] as const)).toBe(true);
expect(isOneOf("remove", ["keep", "remove"] as const)).toBe(true);
});
test("rejects disallowed values and non-strings", () => {
expect(isOneOf("delete", ["keep", "remove"] as const)).toBe(false);
expect(isOneOf(null, ["keep", "remove"] as const)).toBe(false);
expect(isOneOf(42, ["keep", "remove"] as const)).toBe(false);
});
});