// @vitest-environment node
import { describe, expect, it } from "vitest";
import {
attachPendingAttachments,
createPendingAttachment,
getOwnedAttachment,
} from "@/lib/db/attachments";
const gateRow = {
id: "attachment-1",
original_name: "brief.txt",
content_type: "text/plain",
byte_size: "5",
gate_file_id: "file-opaque_123",
object_key: null,
};
describe("attachment repository", () => {
it("creates pending metadata with an opaque Gate file ID and no object key", async () => {
const database = new RecordingDatabase([[gateRow]]);
await expect(
createPendingAttachment(database, "user-1", {
name: "brief.txt",
contentType: "text/plain",
size: 5,
gateFileId: "file-opaque_123",
}),
).resolves.toEqual({
id: "attachment-1",
gateFileId: "file-opaque_123",
objectKey: null,
name: "brief.txt",
contentType: "text/plain",
size: 5,
});
expect(database.queries[0].text).toContain("gate_file_id");
expect(database.queries[0].values).toEqual([
"user-1",
"brief.txt",
"text/plain",
5,
"file-opaque_123",
]);
});
it("reads Gate and legacy rows only through the ownership predicate", async () => {
const legacyRow = { ...gateRow, gate_file_id: null, object_key: "legacy/private-key" };
const database = new RecordingDatabase([[legacyRow]]);
await expect(getOwnedAttachment(database, "user-1", "attachment-1")).resolves.toMatchObject({
gateFileId: null,
objectKey: "legacy/private-key",
});
expect(database.queries[0].text).toContain("id = $1 AND user_id = $2");
expect(database.queries[0].values).toEqual(["attachment-1", "user-1"]);
});
it("links only distinct pending Gate attachment IDs for the owner", async () => {
const database = new RecordingDatabase([[gateRow]]);
await expect(
attachPendingAttachments(database, "user-1", "message-1", ["attachment-1"]),
).resolves.toEqual([expect.objectContaining({ gateFileId: "file-opaque_123" })]);
expect(database.queries[0]).toMatchObject({
values: [["attachment-1"], "user-1", "message-1"],
});
expect(database.queries[0].text).toContain("user_id = $2");
expect(database.queries[0].text).toContain("state = 'pending'");
});
});
class RecordingDatabase {
queries: Array<{ text: string; values?: readonly unknown[] }> = [];
constructor(private readonly results: unknown[][]) {}
async query<T>(text: string, values?: readonly unknown[]) {
this.queries.push({ text, values });
return { rows: (this.results.shift() ?? []) as T[] };
}
}