You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
377 lines
10 KiB
377 lines
10 KiB
import { describe, expect, test, beforeAll, afterAll } from "bun:test";
|
|
import { dbGlobal } from "drizzle-pkg/lib/db";
|
|
import { agentDocuments } from "drizzle-pkg/lib/schema/agent-document";
|
|
import { eq } from "drizzle-orm";
|
|
import {
|
|
createDocument,
|
|
getDocumentById,
|
|
listDocuments,
|
|
updateDocument,
|
|
deleteDocument,
|
|
generateSummary,
|
|
deserializeTags,
|
|
serializeTags,
|
|
} from "./index";
|
|
|
|
const TEST_USER_A = 9999001;
|
|
const TEST_USER_B = 9999002;
|
|
|
|
async function cleanup() {
|
|
await dbGlobal
|
|
.delete(agentDocuments)
|
|
.where(eq(agentDocuments.userId, TEST_USER_A));
|
|
await dbGlobal
|
|
.delete(agentDocuments)
|
|
.where(eq(agentDocuments.userId, TEST_USER_B));
|
|
}
|
|
|
|
beforeAll(async () => {
|
|
await cleanup();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await cleanup();
|
|
});
|
|
|
|
describe("generateSummary", () => {
|
|
test("returns null when content is short", () => {
|
|
expect(generateSummary("short content")).toBeNull();
|
|
});
|
|
|
|
test("returns truncated summary when content exceeds maxChars", () => {
|
|
const long = "a".repeat(300);
|
|
const summary = generateSummary(long, 200);
|
|
expect(summary).toBe("a".repeat(200) + "...");
|
|
});
|
|
|
|
test("respects custom maxChars", () => {
|
|
const long = "a".repeat(100);
|
|
const summary = generateSummary(long, 50);
|
|
expect(summary).toBe("a".repeat(50) + "...");
|
|
});
|
|
});
|
|
|
|
describe("serializeTags / deserializeTags", () => {
|
|
test("serialize null returns null", () => {
|
|
expect(serializeTags(null)).toBeNull();
|
|
});
|
|
|
|
test("serialize empty array returns null", () => {
|
|
expect(serializeTags([])).toBeNull();
|
|
});
|
|
|
|
test("serialize and deserialize roundtrip", () => {
|
|
const tags = ["bug", "feature", "urgent"];
|
|
const serialized = serializeTags(tags);
|
|
expect(serialized).toBe(JSON.stringify(tags));
|
|
expect(deserializeTags(serialized)).toEqual(tags);
|
|
});
|
|
|
|
test("deserialize null returns empty array", () => {
|
|
expect(deserializeTags(null)).toEqual([]);
|
|
});
|
|
|
|
test("deserialize invalid JSON returns empty array", () => {
|
|
expect(deserializeTags("not json")).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("createDocument", () => {
|
|
test("creates document with all fields", async () => {
|
|
const doc = await createDocument({
|
|
userId: TEST_USER_A,
|
|
type: "memory",
|
|
title: "Test Memory",
|
|
content: "This is test memory content",
|
|
tags: ["test", "memory"],
|
|
});
|
|
|
|
expect(doc.id).toBeDefined();
|
|
expect(doc.userId).toBe(TEST_USER_A);
|
|
expect(doc.type).toBe("memory");
|
|
expect(doc.title).toBe("Test Memory");
|
|
expect(doc.content).toBe("This is test memory content");
|
|
expect(doc.tags).toBe(JSON.stringify(["test", "memory"]));
|
|
expect(doc.createdAt).toBeDefined();
|
|
expect(doc.updatedAt).toBeDefined();
|
|
});
|
|
|
|
test("creates document with default type 'document'", async () => {
|
|
const doc = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Default Type Doc",
|
|
content: "content",
|
|
});
|
|
|
|
expect(doc.type).toBe("document");
|
|
});
|
|
|
|
test("auto-generates summary for long content", async () => {
|
|
const longContent = "x".repeat(300);
|
|
const doc = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Long Content Doc",
|
|
content: longContent,
|
|
});
|
|
|
|
expect(doc.summary).toBe("x".repeat(200) + "...");
|
|
});
|
|
|
|
test("summary is null for short content", async () => {
|
|
const doc = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Short Content Doc",
|
|
content: "short",
|
|
});
|
|
|
|
expect(doc.summary).toBeNull();
|
|
});
|
|
|
|
test("stores sessionId when provided", async () => {
|
|
const doc = await createDocument({
|
|
userId: TEST_USER_A,
|
|
sessionId: "session-123",
|
|
title: "With Session",
|
|
content: "content",
|
|
});
|
|
|
|
expect(doc.sessionId).toBe("session-123");
|
|
});
|
|
});
|
|
|
|
describe("getDocumentById", () => {
|
|
test("returns document for correct user", async () => {
|
|
const created = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Get Test",
|
|
content: "content",
|
|
});
|
|
|
|
const fetched = await getDocumentById(created.id, TEST_USER_A);
|
|
expect(fetched).not.toBeNull();
|
|
expect(fetched!.title).toBe("Get Test");
|
|
});
|
|
|
|
test("returns null for wrong user (permission isolation)", async () => {
|
|
const created = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Isolation Test",
|
|
content: "content",
|
|
});
|
|
|
|
const fetched = await getDocumentById(created.id, TEST_USER_B);
|
|
expect(fetched).toBeNull();
|
|
});
|
|
|
|
test("returns null for non-existent id", async () => {
|
|
const fetched = await getDocumentById(99999999, TEST_USER_A);
|
|
expect(fetched).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("listDocuments", () => {
|
|
test("returns documents for user only", async () => {
|
|
await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "List A1",
|
|
content: "content1",
|
|
});
|
|
await createDocument({
|
|
userId: TEST_USER_B,
|
|
title: "List B1",
|
|
content: "content2",
|
|
});
|
|
|
|
const result = await listDocuments({ userId: TEST_USER_A });
|
|
expect(result.items.every((item) => item.userId === TEST_USER_A)).toBe(true);
|
|
expect(result.items.some((item) => item.title === "List A1")).toBe(true);
|
|
expect(result.items.some((item) => item.title === "List B1")).toBe(false);
|
|
});
|
|
|
|
test("list results exclude content field", async () => {
|
|
const result = await listDocuments({ userId: TEST_USER_A });
|
|
expect(result.items.length).toBeGreaterThan(0);
|
|
for (const item of result.items) {
|
|
expect(item).not.toHaveProperty("content");
|
|
}
|
|
});
|
|
|
|
test("filters by type", async () => {
|
|
await createDocument({
|
|
userId: TEST_USER_A,
|
|
type: "memory",
|
|
title: "Memory Type Filter",
|
|
content: "content",
|
|
});
|
|
|
|
const result = await listDocuments({ userId: TEST_USER_A, type: "memory" });
|
|
expect(result.items.every((item) => item.type === "memory")).toBe(true);
|
|
});
|
|
|
|
test("keyword search matches title", async () => {
|
|
await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "UniqueKeywordDoc",
|
|
content: "some content",
|
|
});
|
|
|
|
const result = await listDocuments({
|
|
userId: TEST_USER_A,
|
|
keyword: "UniqueKeyword",
|
|
});
|
|
expect(result.items.some((item) => item.title === "UniqueKeywordDoc")).toBe(true);
|
|
});
|
|
|
|
test("keyword search matches content", async () => {
|
|
await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Content Search Title",
|
|
content: "VerySpecialContentToken12345",
|
|
});
|
|
|
|
const result = await listDocuments({
|
|
userId: TEST_USER_A,
|
|
keyword: "VerySpecialContentToken",
|
|
});
|
|
expect(result.items.some((item) => item.title === "Content Search Title")).toBe(true);
|
|
});
|
|
|
|
test("pagination works correctly", async () => {
|
|
const result = await listDocuments({
|
|
userId: TEST_USER_A,
|
|
page: 1,
|
|
pageSize: 2,
|
|
});
|
|
|
|
expect(result.items.length).toBeLessThanOrEqual(2);
|
|
expect(result.page).toBe(1);
|
|
expect(result.pageSize).toBe(2);
|
|
expect(typeof result.total).toBe("number");
|
|
expect(typeof result.hasMore).toBe("boolean");
|
|
});
|
|
|
|
test("returns correct total count", async () => {
|
|
const result = await listDocuments({ userId: TEST_USER_A });
|
|
expect(result.total).toBe(result.items.length + (result.hasMore ? result.pageSize * (result.page - 1) : 0));
|
|
});
|
|
});
|
|
|
|
describe("updateDocument", () => {
|
|
test("updates title successfully", async () => {
|
|
const created = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Before Update",
|
|
content: "content",
|
|
});
|
|
|
|
const updated = await updateDocument(created.id, TEST_USER_A, {
|
|
title: "After Update",
|
|
});
|
|
|
|
expect(updated).not.toBeNull();
|
|
expect(updated!.title).toBe("After Update");
|
|
});
|
|
|
|
test("updates content and regenerates summary", async () => {
|
|
const created = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Content Update Test",
|
|
content: "short",
|
|
});
|
|
|
|
expect(created.summary).toBeNull();
|
|
|
|
const longContent = "y".repeat(300);
|
|
const updated = await updateDocument(created.id, TEST_USER_A, {
|
|
content: longContent,
|
|
});
|
|
|
|
expect(updated).not.toBeNull();
|
|
expect(updated!.content).toBe(longContent);
|
|
expect(updated!.summary).toBe("y".repeat(200) + "...");
|
|
});
|
|
|
|
test("returns null for wrong user", async () => {
|
|
const created = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Update Isolation",
|
|
content: "content",
|
|
});
|
|
|
|
const updated = await updateDocument(created.id, TEST_USER_B, {
|
|
title: "Hacked",
|
|
});
|
|
|
|
expect(updated).toBeNull();
|
|
});
|
|
|
|
test("returns null for non-existent document", async () => {
|
|
const updated = await updateDocument(99999999, TEST_USER_A, {
|
|
title: "Nope",
|
|
});
|
|
|
|
expect(updated).toBeNull();
|
|
});
|
|
|
|
test("updates tags", async () => {
|
|
const created = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Tags Update",
|
|
content: "content",
|
|
});
|
|
|
|
const updated = await updateDocument(created.id, TEST_USER_A, {
|
|
tags: ["new-tag", "updated"],
|
|
});
|
|
|
|
expect(updated).not.toBeNull();
|
|
expect(updated!.tags).toBe(JSON.stringify(["new-tag", "updated"]));
|
|
});
|
|
|
|
test("no-op update returns existing document", async () => {
|
|
const created = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Noop Test",
|
|
content: "content",
|
|
});
|
|
|
|
const updated = await updateDocument(created.id, TEST_USER_A, {});
|
|
expect(updated).not.toBeNull();
|
|
expect(updated!.title).toBe("Noop Test");
|
|
});
|
|
});
|
|
|
|
describe("deleteDocument", () => {
|
|
test("deletes document for correct user", async () => {
|
|
const created = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Delete Me",
|
|
content: "content",
|
|
});
|
|
|
|
const deleted = await deleteDocument(created.id, TEST_USER_A);
|
|
expect(deleted).toBe(true);
|
|
|
|
const fetched = await getDocumentById(created.id, TEST_USER_A);
|
|
expect(fetched).toBeNull();
|
|
});
|
|
|
|
test("returns false for wrong user", async () => {
|
|
const created = await createDocument({
|
|
userId: TEST_USER_A,
|
|
title: "Delete Isolation",
|
|
content: "content",
|
|
});
|
|
|
|
const deleted = await deleteDocument(created.id, TEST_USER_B);
|
|
expect(deleted).toBe(false);
|
|
|
|
const fetched = await getDocumentById(created.id, TEST_USER_A);
|
|
expect(fetched).not.toBeNull();
|
|
});
|
|
|
|
test("returns false for non-existent document", async () => {
|
|
const deleted = await deleteDocument(99999999, TEST_USER_A);
|
|
expect(deleted).toBe(false);
|
|
});
|
|
});
|
|
|