Files
GMW/services/discord-gateway/tests/fallbackResultClassifier.test.ts
T
asepharyana 842610b1af fix(ai-moderation): bedah delay attachment ~330s -> target <20s
Root cause (trace msg 1541417073245290638):
- Race-guard upload-pending balik results:[] diperlakukan sbg SUKSES
  -> row yatam 'processing' sampai cleanup 300s mengembalikan
- Vision gagal 3x utk GIF besar (SSE truncation) tanpa fallback

Fix:
- Sinyal eksplisit uploadPending dari worker race guard
- Classifier murni classifyIndividualWorkerResult(): upload_pending ->
  requeue pending + reschedule segera (250ms), bukan error palsu;
  empty-results ok:true kini error transien (bug silent-success mati)
- llmVision fallback stream:false sekali saat SSE truncation
- Safety-net cleanup stuck processing 300s -> 120s
2026-08-24 22:05:28 +07:00

80 lines
2.7 KiB
TypeScript

// ═══════════════════════════════════════════════════════════════════════════
// classifyIndividualWorkerResult — upload-pending vs success vs incomplete vs error
// ═══════════════════════════════════════════════════════════════════════════
// Bug (2026-08-24): the worker's upload-pending race guard returned
// {ok:true, results:[]}; the processor treated it as a completed moderation,
// leaving messages stuck in `processing` until the 300s cleanup reverted them.
import { describe, expect, it } from "vitest";
import { classifyIndividualWorkerResult } from "../src/modules/ai-moderation/fallbackResultClassifier.js";
describe("classifyIndividualWorkerResult", () => {
it("classifies the upload-pending race guard signal FIRST", () => {
expect(
classifyIndividualWorkerResult({
ok: true,
results: [],
uploadPending: true,
}),
).toBe("upload_pending");
});
it("classifies a normal verdict as success", () => {
expect(
classifyIndividualWorkerResult({
ok: true,
results: [{ status: "clean", flags: [] }],
}),
).toBe("success");
});
it("classifies analysis_incomplete as incomplete", () => {
expect(
classifyIndividualWorkerResult({
ok: true,
results: [{ status: "error", flags: ["analysis_incomplete"] }],
}),
).toBe("incomplete");
});
it("accepts flags as JSON string (DB round-trip shape)", () => {
expect(
classifyIndividualWorkerResult({
ok: true,
results: [
{ status: "error", flags: JSON.stringify(["analysis_incomplete"]) },
],
}),
).toBe("incomplete");
});
it("classifies ok:false as error", () => {
expect(
classifyIndividualWorkerResult({
ok: false,
results: [],
error: "boom",
}),
).toBe("error");
});
it("THE BUG: empty results with ok:true is an ERROR, not a success", () => {
// Old code silently succeeded here → stuck `processing` rows.
expect(classifyIndividualWorkerResult({ ok: true, results: [] })).toBe(
"error",
);
expect(
classifyIndividualWorkerResult({ ok: true, results: [undefined] }),
).toBe("error");
expect(classifyIndividualWorkerResult({})).toBe("error");
});
it("treats a result with no status as unexplainable (error)", () => {
expect(
classifyIndividualWorkerResult({
ok: true,
results: [{ flags: [] }],
}),
).toBe("error");
});
});