commit 0657e73d75a650fff1646389c838a6ade500dda6
parent 20685d1914f71966f58a9a253c9dddde63ef8277
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sun, 20 Sep 2026 19:16:17 -0400
Prove a patch spares the fields it does not name
The round-trip assertions only checked name/handling/url, which survive a patch
trivially: parseChannelForm REQUIRES the first two, and the serializer re-posts
the third like any other. None of them exercise the thing that could actually go
wrong — updateChannelAction deletes every CHANNEL_FORM_FIELDS key from the
baseline before layering the parse result on, so a field the patch fails to
re-post is silently CLEARED, not preserved.
So the fixture is seeded with two of those keys and both are asserted after an
unrelated downloadFilter patch, and again after the clear. `keepLatest: 0` is
the sharp one: 0 is the explicit "disabled" sentinel, so a serializer testing
the value for truthiness rather than for null/undefined would drop it and the
channel would silently fall back to inherit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/editor/e2e/ops-api.spec.ts b/editor/e2e/ops-api.spec.ts
@@ -23,6 +23,7 @@ import {
readJson,
resetData,
resolvePath,
+ writeChannelConfig,
writeSettings,
} from "./helpers";
@@ -170,8 +171,28 @@ test("channel-config round-trips a download filter and refuses a bad regex", asy
await settings();
const SLUG = "test-filter";
const CONFIG = `test-transcripts/channels/${SLUG}/config.json`;
+ // TWO FIELDS THE CLEAR-THEN-LAYER PATH ACTUALLY THREATENS. name/handling/url
+ // survive a patch trivially — they are re-posted by the serializer because
+ // parseChannelForm requires the first two and CHANNEL_FORM_FIELDS spares
+ // none of the rest. `cookieMode` and `keepLatest` are in CHANNEL_FORM_FIELDS,
+ // so updateChannelAction deletes them from the baseline before layering, and
+ // a patch that failed to re-post them would silently clear both.
+ //
+ // `keepLatest: 0` is the sharp one: 0 is the explicit "disabled" sentinel, and
+ // a serializer that tested the value for truthiness rather than for
+ // null/undefined would drop it and read as "inherit" on the next save.
+ await writeChannelConfig(SLUG, {
+ handling: "youtube",
+ name: "Test Title Filter",
+ url: "https://www.youtube.com/@example/videos",
+ downloadFilter: { include: "guest" },
+ cookieMode: "always",
+ keepLatest: 0,
+ });
const before = await readJson<Record<string, unknown>>(CONFIG);
expect(before.downloadFilter).toEqual({ include: "guest" });
+ expect(before.cookieMode).toBe("always");
+ expect(before.keepLatest).toBe(0);
// THE SAME SENTENCE THE FORM SHOWS. title-filter.spec.ts reads this off the
// page after typing "elf(" into the exclude input; the route reaches it
@@ -202,10 +223,12 @@ test("channel-config round-trips a download filter and refuses a bad regex", asy
});
// A PATCH IS A PATCH. updateChannelAction clears every form-managed key
// before layering the parse result on, so a route that posted only the patch
- // would have silently dropped handling/url/name here.
+ // would have silently dropped every one of these.
expect(after.name).toBe(before.name);
expect(after.handling).toBe(before.handling);
expect(after.url).toBe(before.url);
+ expect(after.cookieMode).toBe("always");
+ expect(after.keepLatest).toBe(0);
// "" clears a field, exactly as clearing the input does.
const cleared = await ops(request, "channel-config", {
@@ -213,9 +236,11 @@ test("channel-config round-trips a download filter and refuses a bad regex", asy
patch: { downloadFilterInclude: "", downloadFilterExclude: "" },
});
expect(cleared.body).toEqual({ ok: true });
- expect(
- "downloadFilter" in (await readJson<Record<string, unknown>>(CONFIG)),
- ).toBe(false);
+ const emptied = await readJson<Record<string, unknown>>(CONFIG);
+ expect("downloadFilter" in emptied).toBe(false);
+ // Clearing one field clears ONLY that field.
+ expect(emptied.cookieMode).toBe("always");
+ expect(emptied.keepLatest).toBe(0);
// The read route sees the same config, and says whether the media is there.
await generateReport(page, SLUG);