Files
dc-recorder/tests/moderation/conversationContext.test.ts
2026-05-14 19:16:46 +07:00

147 lines
4.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { buildConversationPromptMessages } from "../../src/moderation/conversationContext";
import type { MessageRecord } from "../../src/moderation/types";
function message(
id: string,
content: string,
created_at: number,
): MessageRecord {
return {
id,
guild_id: "g1",
channel_id: "c1",
thread_id: null,
user_id: `u-${id}`,
username: `user-${id}`,
avatar_url: null,
content,
edited_content: null,
created_at,
edited_at: null,
deleted_at: null,
type: "text",
metadata: null,
ai_status: "pending",
};
}
describe("buildConversationPromptMessages", () => {
it("marks target messages and keeps chronological order", () => {
const lines = buildConversationPromptMessages({
contextBefore: [message("a", "hello", 1)],
targets: [message("b", "bad?", 2)],
maxTokens: 1000,
});
expect(lines).toContain(
"[context] id=a time=1970-01-01T00:00:00.001Z user=user-a: hello",
);
expect(lines).toContain(
"[target] id=b time=1970-01-01T00:00:00.002Z user=user-b: bad?",
);
const indexA = lines.findIndex((line) => line.includes("id=a"));
const indexB = lines.findIndex((line) => line.includes("id=b"));
expect(indexA).toBeLessThan(indexB);
});
it("uses edited content when present", () => {
const target = message("b", "original", 2);
target.edited_content = "edited";
const lines = buildConversationPromptMessages({
contextBefore: [],
targets: [target],
maxTokens: 1000,
});
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
});
});