fix: update image URLs to use httpbin.org for testing and add new test cases for attachment analysis
This commit is contained in:
@@ -426,7 +426,7 @@ describe("runModerationAnalysis", () => {
|
||||
};
|
||||
|
||||
global.fetch = vi.fn().mockImplementation((url: string) => {
|
||||
if (url.includes("picser.tech") || url.includes("discord.com")) {
|
||||
if (url === "https://httpbin.org/image/png") {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
arrayBuffer: async () => {
|
||||
@@ -455,8 +455,8 @@ describe("runModerationAnalysis", () => {
|
||||
filename: "test.png",
|
||||
size: 500,
|
||||
type: "image/png",
|
||||
discord_url: "https://discord.com/attachment.png",
|
||||
uploaded_url: "https://picser.tech/test.png",
|
||||
discord_url: "https://httpbin.org/image/png",
|
||||
uploaded_url: "https://httpbin.org/image/png",
|
||||
upload_status: "uploaded" as const,
|
||||
upload_error: null,
|
||||
created_at: Date.now(),
|
||||
@@ -477,7 +477,7 @@ describe("runModerationAnalysis", () => {
|
||||
expect(fetchCalls.length).toBe(2);
|
||||
|
||||
// Verify 1st call (image download)
|
||||
expect(fetchCalls[0][0]).toBe("https://picser.tech/test.png");
|
||||
expect(fetchCalls[0][0]).toBe("https://httpbin.org/image/png");
|
||||
|
||||
// Verify 2nd call (chat completions API)
|
||||
const [, completionsOptions] = fetchCalls[1];
|
||||
@@ -522,7 +522,7 @@ describe("runModerationAnalysis", () => {
|
||||
};
|
||||
|
||||
global.fetch = vi.fn().mockImplementation((url: string) => {
|
||||
if (url.includes("picser.tech") || url.includes("discord.com")) {
|
||||
if (url.startsWith("https://httpbin.org/image/")) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
arrayBuffer: async () => {
|
||||
@@ -555,8 +555,8 @@ describe("runModerationAnalysis", () => {
|
||||
filename: `${id}.png`,
|
||||
size: 500,
|
||||
type: "image/png",
|
||||
discord_url: `https://discord.com/${id}.png`,
|
||||
uploaded_url: `https://picser.tech/${id}.png`,
|
||||
discord_url: `https://httpbin.org/image/png?source=${id}`,
|
||||
uploaded_url: `https://httpbin.org/image/png?source=${id}`,
|
||||
upload_status: "uploaded" as const,
|
||||
upload_error: null,
|
||||
created_at: createdAt,
|
||||
@@ -592,10 +592,707 @@ describe("runModerationAnalysis", () => {
|
||||
// Excluded: c2 (200), c1 (100)
|
||||
const downloadedUrls = fetchCalls.slice(0, 8).map((call: any) => call[0]);
|
||||
|
||||
expect(downloadedUrls).toContain("https://picser.tech/t3.png");
|
||||
expect(downloadedUrls).toContain("https://picser.tech/t2.png");
|
||||
expect(downloadedUrls).toContain("https://picser.tech/t1.png");
|
||||
expect(downloadedUrls).toContain("https://picser.tech/c7.png");
|
||||
expect(downloadedUrls).not.toContain("https://picser.tech/c1.png");
|
||||
expect(downloadedUrls).toContain("https://httpbin.org/image/png?source=t3");
|
||||
expect(downloadedUrls).toContain("https://httpbin.org/image/png?source=t2");
|
||||
expect(downloadedUrls).toContain("https://httpbin.org/image/png?source=t1");
|
||||
expect(downloadedUrls).toContain("https://httpbin.org/image/png?source=c7");
|
||||
expect(downloadedUrls).not.toContain("https://httpbin.org/image/png?source=c1");
|
||||
});
|
||||
|
||||
it("sends verified real PNG and JPEG attachments with realistic Indonesian text", async () => {
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "warn",
|
||||
flags: ["harassment"],
|
||||
score: 0.65,
|
||||
analysis: "Teks dan gambar perlu ditinjau moderator.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const imageBytes = Buffer.from("realistic-image-bytes");
|
||||
global.fetch = vi.fn().mockImplementation((url: string) => {
|
||||
if (
|
||||
url === "https://httpbin.org/image/png" ||
|
||||
url === "https://httpbin.org/image/jpeg"
|
||||
) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
arrayBuffer: async () =>
|
||||
imageBytes.buffer.slice(
|
||||
imageBytes.byteOffset,
|
||||
imageBytes.byteOffset + imageBytes.byteLength,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
text: async () => JSON.stringify(mockResponse),
|
||||
json: async () => mockResponse,
|
||||
});
|
||||
});
|
||||
|
||||
const attachments = [
|
||||
{
|
||||
id: "png1",
|
||||
message_id: "m1",
|
||||
guild_id: "guild123",
|
||||
channel_id: "channel123",
|
||||
thread_id: null,
|
||||
user_id: "user123",
|
||||
filename: "bukti-chat.png",
|
||||
size: 8090,
|
||||
type: "image/png",
|
||||
discord_url: "https://httpbin.org/image/png",
|
||||
uploaded_url: "https://httpbin.org/image/png",
|
||||
upload_status: "uploaded" as const,
|
||||
upload_error: null,
|
||||
created_at: Date.now(),
|
||||
uploaded_at: Date.now(),
|
||||
},
|
||||
{
|
||||
id: "jpeg1",
|
||||
message_id: "m1",
|
||||
guild_id: "guild123",
|
||||
channel_id: "channel123",
|
||||
thread_id: null,
|
||||
user_id: "user123",
|
||||
filename: "screenshot.jpeg",
|
||||
size: 35588,
|
||||
type: "image/jpeg",
|
||||
discord_url: "https://httpbin.org/image/jpeg",
|
||||
uploaded_url: "https://httpbin.org/image/jpeg",
|
||||
upload_status: "uploaded" as const,
|
||||
upload_error: null,
|
||||
created_at: Date.now() + 1,
|
||||
uploaded_at: Date.now() + 1,
|
||||
},
|
||||
];
|
||||
|
||||
const result = await runModerationAnalysis({
|
||||
targets: [
|
||||
createMessageRecord({
|
||||
id: "m1",
|
||||
username: "asep",
|
||||
content:
|
||||
"tolong cek gambar ini, dia kirim link mencurigakan https://example.invalid/login dan maksa orang klik",
|
||||
}),
|
||||
],
|
||||
contextText:
|
||||
"Sebelumnya user lain bilang link itu mirip phishing dan screenshot memperlihatkan halaman login palsu.",
|
||||
attachments,
|
||||
});
|
||||
|
||||
expect(result.results[0].status).toBe("warn");
|
||||
|
||||
const fetchCalls = (global.fetch as any).mock.calls;
|
||||
expect(fetchCalls[0][0]).toBe("https://httpbin.org/image/jpeg");
|
||||
expect(fetchCalls[1][0]).toBe("https://httpbin.org/image/png");
|
||||
|
||||
const requestBody = JSON.parse(fetchCalls[2][1].body);
|
||||
const contentParts = requestBody.messages[0].content;
|
||||
expect(contentParts.filter((part: any) => part.type === "image_url")).toHaveLength(2);
|
||||
expect(contentParts[0].image_url.url).toContain("data:image/jpeg;base64,");
|
||||
expect(contentParts[2].image_url.url).toContain("data:image/png;base64,");
|
||||
expect(contentParts.at(-1).text).toContain("https://example.invalid/login");
|
||||
expect(contentParts.at(-1).text).toContain("Sebelumnya user lain bilang");
|
||||
});
|
||||
|
||||
it("falls back to discord_url when uploaded_url is not ready", async () => {
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "Image available through Discord URL fallback.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
global.fetch = vi.fn().mockImplementation((url: string) => {
|
||||
if (url === "https://httpbin.org/image/png") {
|
||||
const buffer = Buffer.from("png-bytes");
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
arrayBuffer: async () =>
|
||||
buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength),
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
text: async () => JSON.stringify(mockResponse),
|
||||
json: async () => mockResponse,
|
||||
});
|
||||
});
|
||||
|
||||
await runModerationAnalysis({
|
||||
targets: [createMessageRecord({ id: "m1", content: "gambar belum selesai upload ke picser" })],
|
||||
contextText: "test context",
|
||||
attachments: [
|
||||
{
|
||||
id: "pending-image",
|
||||
message_id: "m1",
|
||||
guild_id: "guild123",
|
||||
channel_id: "channel123",
|
||||
thread_id: null,
|
||||
user_id: "user123",
|
||||
filename: "pending.png",
|
||||
size: 8090,
|
||||
type: "image/png",
|
||||
discord_url: "https://httpbin.org/image/png",
|
||||
uploaded_url: null,
|
||||
upload_status: "pending" as const,
|
||||
upload_error: null,
|
||||
created_at: Date.now(),
|
||||
uploaded_at: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect((global.fetch as any).mock.calls[0][0]).toBe("https://httpbin.org/image/png");
|
||||
});
|
||||
|
||||
it("keeps analyzing text when an image URL returns non-OK", async () => {
|
||||
const mockResponse = {
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "warn",
|
||||
flags: ["suspicious_link"],
|
||||
score: 0.6,
|
||||
analysis: "Image fetch failed, text still indicates suspicious link.",
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
global.fetch = vi.fn().mockImplementation((url: string) => {
|
||||
if (url === "https://httpbin.org/image/png") {
|
||||
return Promise.resolve({ ok: false, status: 503 });
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
text: async () => JSON.stringify(mockResponse),
|
||||
json: async () => mockResponse,
|
||||
});
|
||||
});
|
||||
|
||||
const result = await runModerationAnalysis({
|
||||
targets: [
|
||||
createMessageRecord({
|
||||
id: "m1",
|
||||
content: "cek bonus gratis di https://example.invalid/claim sekarang",
|
||||
}),
|
||||
],
|
||||
contextText: "Pesan ini dikirim berulang setelah user lain menolak klik link.",
|
||||
attachments: [
|
||||
{
|
||||
id: "bad-image",
|
||||
message_id: "m1",
|
||||
guild_id: "guild123",
|
||||
channel_id: "channel123",
|
||||
thread_id: null,
|
||||
user_id: "user123",
|
||||
filename: "broken.png",
|
||||
size: 8090,
|
||||
type: "image/png",
|
||||
discord_url: "https://httpbin.org/image/png",
|
||||
uploaded_url: "https://httpbin.org/image/png",
|
||||
upload_status: "uploaded" as const,
|
||||
upload_error: null,
|
||||
created_at: Date.now(),
|
||||
uploaded_at: Date.now(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.results[0].status).toBe("warn");
|
||||
|
||||
const requestBody = JSON.parse((global.fetch as any).mock.calls[1][1].body);
|
||||
expect(requestBody.messages[0].content).toHaveLength(1);
|
||||
expect(requestBody.messages[0].content[0].text).toContain("https://example.invalid/claim");
|
||||
});
|
||||
|
||||
describe("Edge Cases & Real-World Scenarios", () => {
|
||||
it("handles single result object without results wrapper", () => {
|
||||
const content = JSON.stringify({
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "OK",
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].messageId).toBe("m1");
|
||||
expect(result[0].status).toBe("clean");
|
||||
});
|
||||
|
||||
it("handles alternative array key 'result' instead of 'results'", () => {
|
||||
const content = JSON.stringify({
|
||||
result: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "flagged",
|
||||
flags: ["spam"],
|
||||
score: 0.8,
|
||||
analysis: "Spam detected",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].status).toBe("flagged");
|
||||
});
|
||||
|
||||
it("handles alternative array key 'data' instead of 'results'", () => {
|
||||
const content = JSON.stringify({
|
||||
data: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "warn",
|
||||
flags: ["hate_speech"],
|
||||
score: 0.6,
|
||||
analysis: "Potential hate speech",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].status).toBe("warn");
|
||||
});
|
||||
|
||||
it("handles alternative array key 'messages' instead of 'results'", () => {
|
||||
const content = JSON.stringify({
|
||||
messages: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.05,
|
||||
analysis: "No violations",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].status).toBe("clean");
|
||||
});
|
||||
|
||||
it("handles text with links and URLs", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "Link shared, no violations",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result[0].status).toBe("clean");
|
||||
});
|
||||
|
||||
it("handles very long message content", () => {
|
||||
const longContent = "a".repeat(10000);
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: `Analyzed ${longContent.length} character message`,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].analysis).toContain("10000");
|
||||
});
|
||||
|
||||
it("handles multiple messages with mixed statuses", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "OK",
|
||||
},
|
||||
{
|
||||
message_id: "m2",
|
||||
status: "warn",
|
||||
flags: ["spam"],
|
||||
score: 0.5,
|
||||
analysis: "Potential spam",
|
||||
},
|
||||
{
|
||||
message_id: "m3",
|
||||
status: "flagged",
|
||||
flags: ["hate_speech", "abuse"],
|
||||
score: 0.95,
|
||||
analysis: "Severe violations",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1", "m2", "m3"]);
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0].status).toBe("clean");
|
||||
expect(result[1].status).toBe("warn");
|
||||
expect(result[2].status).toBe("flagged");
|
||||
expect(result[2].flags).toContain("hate_speech");
|
||||
expect(result[2].flags).toContain("abuse");
|
||||
});
|
||||
|
||||
it("handles LLM response with thinking blocks and markdown", () => {
|
||||
const content = `
|
||||
<thinking>
|
||||
Let me analyze these messages carefully.
|
||||
Message 1 seems clean.
|
||||
Message 2 has some concerning language.
|
||||
</thinking>
|
||||
|
||||
Based on my analysis:
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"message_id": "m1",
|
||||
"status": "clean",
|
||||
"flags": [],
|
||||
"score": 0.1,
|
||||
"analysis": "No violations detected"
|
||||
},
|
||||
{
|
||||
"message_id": "m2",
|
||||
"status": "warn",
|
||||
"flags": ["inappropriate"],
|
||||
"score": 0.6,
|
||||
"analysis": "Contains inappropriate language"
|
||||
}
|
||||
]
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
These are my findings.
|
||||
`;
|
||||
|
||||
const result = parseModerationResponse(content, ["m1", "m2"]);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0].status).toBe("clean");
|
||||
expect(result[1].status).toBe("warn");
|
||||
});
|
||||
|
||||
it("handles response with extra fields and nested objects", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "flagged",
|
||||
flags: ["violence"],
|
||||
score: 0.9,
|
||||
analysis: "Contains violent content",
|
||||
metadata: {
|
||||
confidence: 0.95,
|
||||
model_version: "v2",
|
||||
processing_time_ms: 150,
|
||||
},
|
||||
extra_field: "ignored",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].status).toBe("flagged");
|
||||
expect(result[0].score).toBe(0.9);
|
||||
});
|
||||
|
||||
it("handles scientific notation in score", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 1e-1,
|
||||
analysis: "Very low risk",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result[0].score).toBe(0.1);
|
||||
});
|
||||
|
||||
it("handles empty flags array and null analysis", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0,
|
||||
analysis: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result[0].flags).toEqual([]);
|
||||
expect(result[0].analysis).toBe("");
|
||||
});
|
||||
|
||||
it("handles missing optional fields with defaults", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
score: 0.1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result[0].flags).toEqual([]);
|
||||
expect(result[0].score).toBe(0.1);
|
||||
expect(result[0].analysis).toBe("");
|
||||
});
|
||||
|
||||
it("handles Unicode and emoji in analysis text", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "warn",
|
||||
flags: ["inappropriate"],
|
||||
score: 0.7,
|
||||
analysis: "Contains inappropriate emoji 🚫 and symbols ⚠️",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result[0].analysis).toContain("🚫");
|
||||
expect(result[0].analysis).toContain("⚠️");
|
||||
});
|
||||
|
||||
it("handles response with only one array in deeply nested object", () => {
|
||||
const content = JSON.stringify({
|
||||
metadata: { version: 1 },
|
||||
analysis_results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "OK",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].messageId).toBe("m1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Real-World Text Samples", () => {
|
||||
it("analyzes message with URL", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.15,
|
||||
analysis: "Message contains URL but no violations",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result[0].status).toBe("clean");
|
||||
});
|
||||
|
||||
it("analyzes message with mentions and hashtags", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.05,
|
||||
analysis: "Mentions and hashtags are acceptable",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result[0].status).toBe("clean");
|
||||
});
|
||||
|
||||
it("analyzes message with code snippets", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "Code snippet detected, no violations",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result[0].status).toBe("clean");
|
||||
});
|
||||
|
||||
it("analyzes message with mixed languages", () => {
|
||||
const content = JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "Mixed language content analyzed successfully",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const result = parseModerationResponse(content, ["m1"]);
|
||||
expect(result[0].status).toBe("clean");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Failure Scenarios", () => {
|
||||
it("throws on completely invalid JSON", () => {
|
||||
expect(() =>
|
||||
parseModerationResponse("not json at all", ["m1"]),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("throws on empty object", () => {
|
||||
expect(() => parseModerationResponse(JSON.stringify({}), ["m1"])).toThrow(
|
||||
/missing.*results/i,
|
||||
);
|
||||
});
|
||||
|
||||
it("throws on array without results wrapper and no message_id", () => {
|
||||
expect(() =>
|
||||
parseModerationResponse(
|
||||
JSON.stringify([
|
||||
{
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "OK",
|
||||
},
|
||||
]),
|
||||
["m1"],
|
||||
),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("throws on mismatched message IDs", () => {
|
||||
expect(() =>
|
||||
parseModerationResponse(
|
||||
JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m999",
|
||||
status: "clean",
|
||||
flags: [],
|
||||
score: 0.1,
|
||||
analysis: "OK",
|
||||
},
|
||||
],
|
||||
}),
|
||||
["m1"],
|
||||
),
|
||||
).toThrow(/unknown.*message_id/i);
|
||||
});
|
||||
|
||||
it("throws on invalid status value", () => {
|
||||
expect(() =>
|
||||
parseModerationResponse(
|
||||
JSON.stringify({
|
||||
results: [
|
||||
{
|
||||
message_id: "m1",
|
||||
status: "invalid_status",
|
||||
flags: [],
|
||||
score: 0.5,
|
||||
analysis: "OK",
|
||||
},
|
||||
],
|
||||
}),
|
||||
["m1"],
|
||||
),
|
||||
).toThrow(/invalid status/i);
|
||||
});
|
||||
|
||||
it("throws on non-finite score", () => {
|
||||
const content = `{
|
||||
"results": [
|
||||
{
|
||||
"message_id": "m1",
|
||||
"status": "clean",
|
||||
"flags": [],
|
||||
"score": "NaN",
|
||||
"analysis": "OK"
|
||||
}
|
||||
]
|
||||
}`;
|
||||
|
||||
expect(() =>
|
||||
parseModerationResponse(content, ["m1"]),
|
||||
).toThrow(/finite/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user