import { randomUUID } from "node:crypto"; import { eq } from "drizzle-orm"; import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest"; import { companies, companyMemberships, createDb, principalPermissionGrants, } from "@paperclipai/db"; import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase, } from "./helpers/embedded-postgres.js"; import { accessService } from "../services/access.js"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip; async function createCompanyWithOwner(db: ReturnType) { const company = await db .insert(companies) .values({ name: `Access Service ${randomUUID()}`, issuePrefix: `AS${randomUUID().slice(0, 6).toUpperCase()}`, }) .returning() .then((rows) => rows[0]!); const owner = await db .insert(companyMemberships) .values({ companyId: company.id, principalType: "user", principalId: `owner-${randomUUID()}`, status: "active", membershipRole: "owner", }) .returning() .then((rows) => rows[0]!); return { company, owner }; } describeEmbeddedPostgres("access service", () => { let db!: ReturnType; let tempDb: Awaited> | null = null; beforeAll(async () => { tempDb = await startEmbeddedPostgresTestDatabase("paperclip-access-service-"); db = createDb(tempDb.connectionString); }, 20_000); afterEach(async () => { await db.delete(principalPermissionGrants); await db.delete(companyMemberships); await db.delete(companies); }); afterAll(async () => { await tempDb?.cleanup(); }); it("rejects combined access updates that would demote the last active owner", async () => { const { company, owner } = await createCompanyWithOwner(db); const access = accessService(db); await expect( access.updateMemberAndPermissions( company.id, owner.id, { membershipRole: "admin", grants: [] }, "admin-user", ), ).rejects.toThrow("Cannot remove the last active owner"); const unchanged = await db .select() .from(companyMemberships) .where(eq(companyMemberships.id, owner.id)) .then((rows) => rows[0]!); expect(unchanged.membershipRole).toBe("owner"); }); it("rejects role-only updates that would suspend the last active owner", async () => { const { company, owner } = await createCompanyWithOwner(db); const access = accessService(db); await expect( access.updateMember(company.id, owner.id, { status: "suspended" }), ).rejects.toThrow("Cannot remove the last active owner"); const unchanged = await db .select() .from(companyMemberships) .where(eq(companyMemberships.id, owner.id)) .then((rows) => rows[0]!); expect(unchanged.status).toBe("active"); }); });