From ce35b335d08be8c4659ffe1ee8f265f7a96ab950 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Thu, 14 May 2026 19:16:46 +0700 Subject: [PATCH] test: cover conversation context edge cases --- src/moderation/messageStore.ts | 1 - tests/moderation/conversationContext.test.ts | 84 ++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/moderation/messageStore.ts b/src/moderation/messageStore.ts index 832d86c..69fcd1f 100644 --- a/src/moderation/messageStore.ts +++ b/src/moderation/messageStore.ts @@ -3,7 +3,6 @@ import { getDatabase } from "../database/drizzle"; import { attachmentsTable, messagesTable } from "../database/schema"; import { createChildLogger } from "../logger"; import type { - AIStatus, AttachmentRecord, MessageQuery, MessageRecord, diff --git a/tests/moderation/conversationContext.test.ts b/tests/moderation/conversationContext.test.ts index 8cbb76f..a224ac5 100644 --- a/tests/moderation/conversationContext.test.ts +++ b/tests/moderation/conversationContext.test.ts @@ -59,4 +59,88 @@ describe("buildConversationPromptMessages", () => { expect(lines.some((line) => line.includes("edited"))).toBe(true); expect(lines.some((line) => line.includes("original"))).toBe(false); }); + + it("empty targets returns only fitting context or empty string if no context", () => { + // Case 1: No context, no targets + const lines1 = buildConversationPromptMessages({ + contextBefore: [], + targets: [], + maxTokens: 1000, + }); + expect(lines1).toEqual([]); + + // Case 2: Context but no targets + const lines2 = buildConversationPromptMessages({ + contextBefore: [message("a", "hello", 1)], + targets: [], + maxTokens: 1000, + }); + expect(lines2).toHaveLength(1); + expect(lines2[0]).toContain("[context]"); + }); + + it("maxTokens budget includes target lines even when targets exceed budget", () => { + // Create targets that exceed budget + const longContent = "x".repeat(500); // ~125 tokens + const targets = [ + message("t1", longContent, 1), + message("t2", longContent, 2), + message("t3", longContent, 3), + ]; + + const lines = buildConversationPromptMessages({ + contextBefore: [message("c1", "context", 0)], + targets, + maxTokens: 200, // Only 200 tokens, but targets alone are ~375 + }); + + // All targets should be included + expect(lines.some((line) => line.includes("id=t1"))).toBe(true); + expect(lines.some((line) => line.includes("id=t2"))).toBe(true); + expect(lines.some((line) => line.includes("id=t3"))).toBe(true); + + // Context should be excluded due to budget + expect(lines.some((line) => line.includes("id=c1"))).toBe(false); + }); + + it("most recent context is kept when context budget is tight", () => { + // Create multiple context messages with different timestamps + // Use longer content to ensure they consume meaningful tokens + const contextBefore = [ + message( + "c1", + "oldest context with some extra words to make it longer", + 1000, + ), + message( + "c2", + "middle context with some extra words to make it longer", + 2000, + ), + message( + "c3", + "newest context with some extra words to make it longer", + 3000, + ), + ]; + + const lines = buildConversationPromptMessages({ + contextBefore, + targets: [message("t1", "target message", 4000)], + maxTokens: 90, // Very tight budget: target ~35 tokens, room for ~55 tokens of context (fits only c3) + }); + + // Should include target + expect(lines.some((line) => line.includes("id=t1"))).toBe(true); + + // Should include newest context (c3) but not oldest (c1) + // With tight budget, only the most recent context should fit + expect(lines.some((line) => line.includes("id=c3"))).toBe(true); + expect(lines.some((line) => line.includes("id=c1"))).toBe(false); + + // Verify chronological order is maintained + const indexT1 = lines.findIndex((line) => line.includes("id=t1")); + const indexC3 = lines.findIndex((line) => line.includes("id=c3")); + expect(indexC3).toBeLessThan(indexT1); // context before target + }); });