fix: treat District 'content not found' as waiting, not as a failure

A film that is announced but has no showtimes yet answers 400 with code 32, which
is exactly the state you are monitoring for before a release. Counting that as a
check failure meant pointing District at an upcoming film produced a false BROKEN
alert every six minutes until the listing appeared.

Code 32 now reports closed and carries a note through to the status endpoint and
the daily heartbeat, so a typo'd content id still surfaces rather than looking
like a quiet 'not open yet' forever. Other 400s, including the missing-parameter
error, still fail loudly.
This commit is contained in:
Aculix Technologies committed 2026-09-08 14:57:31 +05:30
1 parent 9442bca696
commit 67e97f5a6a
5 files changed
+75 -6

No files matched your search

+22
View File
@@ -190,3 +190,25 @@ test("retirement takes precedence over the heartbeat", () => {
assert.equal(d.alerts.length, 1);
assert.ok(d.alerts[0].title.includes("retired"));
});
test("a 'not listed yet' note rides through to lastGood and the heartbeat", () => {
const d = decideProvider(defaultProviderState(),
{ kind: "closed", now: T0, note: "not listed yet (District: content not found)" }, CFG, P);
assert.equal(d.alerts.length, 0, "waiting for a listing is not an alert");
assert.equal(d.baseState.lastGood.kind, "closed");
assert.match(d.baseState.lastGood.note, /not listed yet/);
const hb = decideGlobal(defaultState(), CFG, HB, [
{ label: "District", kind: "closed", note: "not listed yet (District: content not found)" },
{ label: "BookMyShow", kind: "closed" },
]);
assert.match(hb.alerts[0].body, /District: closed, not listed yet/,
"the daily heartbeat must say why District sees nothing, or silence is ambiguous");
assert.match(hb.alerts[0].body, /BookMyShow: closed$/m);
});
test("clearing the note is itself a state change, so the status endpoint stops lying", () => {
const withNote = decideProvider(defaultProviderState(), { kind: "closed", now: T0, note: "not listed yet" }, CFG, P).baseState;
const after = decideProvider(withNote, { kind: "closed", now: T0 }, CFG, P);
assert.equal(after.baseState.lastGood.note, null, "note is dropped once the listing appears");
});
+28
View File
@@ -104,3 +104,31 @@ test("bookmyshow: a not-yet-on-sale response has no widgets and extracts to noth
assert.deepEqual(bms.extract(closed, CFG, B_PARAMS),
{ matched: [], other: [], cinemaCount: 0, sessionCount: 0, showDates: [] });
});
// The 400 branch decides between "waiting for a listing" and "something is wrong",
// so it is worth pinning without a network.
test("district: 400 code 32 is a waiting state, other 400s are failures", async () => {
const realFetch = globalThis.fetch;
const stub = (status, body) => {
globalThis.fetch = async () => ({ status, json: async () => body });
};
const p = { movieCode: "X", contentId: "1", cityKey: "c", lat: "1", lng: "2" };
try {
stub(400, { errors: [], msg: "Content not found", code: 32 });
const waiting = await district.check({}, p, "2026-09-25");
assert.equal(waiting.kind, "closed");
assert.match(waiting.note, /not listed yet/, "the reason must survive for the heartbeat");
stub(400, { errors: [], msg: "Mandatory search params are missing", code: 3 });
await assert.rejects(() => district.check({}, p, "2026-09-25"), /Mandatory search params/,
"a real config error must still raise the failure streak");
stub(500, {});
await assert.rejects(() => district.check({}, p, "2026-09-25"), /HTTP 500/);
stub(204, {});
assert.equal((await district.check({}, p, "2026-09-25")).kind, "closed");
} finally {
globalThis.fetch = realFetch;
}
});