aegida-console / lib / db / attachments.test.ts
attachments.test.ts
Raw
// @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[] };
  }
}