///
/* eslint-disable @convex-dev/no-filter-in-query */
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { type TestConvex } from "convex-test";
import schema from "./schema.js";
import { api } from "./_generated/api.js";
import { initConvexTest } from "./setup.test.js";
import type { Id } from "./_generated/dataModel.js";
type ConvexTest = TestConvex;
describe("namespaces", () => {
async function setupTestNamespace(t: ConvexTest) {
const namespace = await t.mutation(api.namespaces.getOrCreate, {
namespace: "test-namespace",
status: "ready",
modelId: "test-model",
dimension: 128,
filterNames: [],
});
return namespace.namespaceId;
}
function testEntryArgs(namespaceId: Id<"namespaces">, key: string) {
return {
namespaceId,
key,
importance: 0.5,
filterValues: [],
contentHash: `hash-${key}`,
title: `Entry ${key}`,
};
}
function testChunk() {
return {
content: { text: "test chunk" },
embedding: Array.from({ length: 128 }, () => Math.random()),
searchableText: "test chunk",
};
}
beforeEach(async () => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
test("deleteNamespaceSync deletes all entries then the namespace", async () => {
const t = initConvexTest();
const namespaceId = await setupTestNamespace(t);
// Create multiple entries so the pagination loop must handle them all
const entryIds: Id<"entries">[] = [];
for (let i = 0; i < 3; i++) {
const result = await t.mutation(api.entries.add, {
entry: testEntryArgs(namespaceId, `key-${i}`),
allChunks: [testChunk()],
});
expect(result.status).toBe("ready");
entryIds.push(result.entryId);
}
// Verify entries exist
const entriesBefore = await t.run(async (ctx) => {
return ctx.db
.query("entries")
.filter((q) => q.eq(q.field("namespaceId"), namespaceId))
.collect();
});
expect(entriesBefore).toHaveLength(3);
// This should delete all entries and then the namespace.
// Before the fix, this threw "cannot delete, has entries" because
// the pagination loop checked isDone before processing the page,
// skipping deletion when entries fit in a single page.
await t.action(api.namespaces.deleteNamespaceSync, {
namespaceId,
});
// Verify all entries are deleted
const entriesAfter = await t.run(async (ctx) => {
return ctx.db
.query("entries")
.filter((q) => q.eq(q.field("namespaceId"), namespaceId))
.collect();
});
expect(entriesAfter).toHaveLength(0);
// Verify namespace is deleted
const namespaceAfter = await t.run(async (ctx) => {
return ctx.db.get("namespaces", namespaceId);
});
expect(namespaceAfter).toBeNull();
});
});