import { Prisma, PrismaClient } from "@prisma/client"; import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { PrismaService } from "../../apps/api/src/database/prisma.service"; const databaseUrl = process.env.DATABASE_URL ?? "postgresql://drift:drift_test_only@127.0.0.1:55432/drift_bottle_test?schema=public"; const prisma = new PrismaClient({ datasources: { db: { url: databaseUrl } } }); async function expectUniqueViolation(operation: Promise) { await expect(operation).rejects.toMatchObject({ code: "P2002", } satisfies Partial); } async function createAccount(suffix: string) { return prisma.account.create({ data: { phoneCiphertext: Buffer.from(`ciphertext-${suffix}`), phoneHmac: `hmac-${suffix}`, anonymousProfile: { create: { nickname: `漂友-${suffix}` } }, }, }); } async function createBottle(authorId: string, suffix: string) { return prisma.bottle.create({ data: { authorId, contentText: `测试瓶子-${suffix}`, status: "IN_POOL" }, }); } describe("database authority constraints", () => { beforeAll(async () => prisma.$connect()); beforeEach(async () => { await prisma.$executeRawUnsafe(`TRUNCATE TABLE "accounts" CASCADE`); }); afterAll(async () => prisma.$disconnect()); it("rejects a second conversation for the same source bottle with P2002", async () => { const author = await createAccount("conversation-author"); const bottle = await createBottle(author.id, "conversation"); await prisma.conversation.create({ data: { sourceBottleId: bottle.id } }); await expectUniqueViolation( prisma.conversation.create({ data: { sourceBottleId: bottle.id } }), ); }); it("rejects duplicate bottle pick history with P2002", async () => { const author = await createAccount("history-author"); const picker = await createAccount("history-picker"); const bottle = await createBottle(author.id, "history"); await prisma.bottlePickHistory.create({ data: { bottleId: bottle.id, pickerId: picker.id }, }); await expectUniqueViolation( prisma.bottlePickHistory.create({ data: { bottleId: bottle.id, pickerId: picker.id }, }), ); }); it("rejects duplicate directed blocks with P2002", async () => { const blocker = await createAccount("blocker"); const blocked = await createAccount("blocked"); await prisma.block.create({ data: { blockerId: blocker.id, blockedId: blocked.id }, }); await expectUniqueViolation( prisma.block.create({ data: { blockerId: blocker.id, blockedId: blocked.id }, }), ); }); it("rejects duplicate client message ids within a conversation with P2002", async () => { const author = await createAccount("client-message-author"); const bottle = await createBottle(author.id, "client-message"); const conversation = await prisma.conversation.create({ data: { sourceBottleId: bottle.id }, }); await prisma.message.create({ data: { conversationId: conversation.id, senderId: author.id, clientMsgId: "client-1", seq: 1, contentText: "一", }, }); await expectUniqueViolation( prisma.message.create({ data: { conversationId: conversation.id, senderId: author.id, clientMsgId: "client-1", seq: 2, contentText: "二", }, }), ); }); it("rejects duplicate message sequences within a conversation with P2002", async () => { const author = await createAccount("sequence-author"); const bottle = await createBottle(author.id, "sequence"); const conversation = await prisma.conversation.create({ data: { sourceBottleId: bottle.id }, }); await prisma.message.create({ data: { conversationId: conversation.id, senderId: author.id, clientMsgId: "client-a", seq: 1, contentText: "一", }, }); await expectUniqueViolation( prisma.message.create({ data: { conversationId: conversation.id, senderId: author.id, clientMsgId: "client-b", seq: 1, contentText: "二", }, }), ); }); }); describe("PrismaService lifecycle", () => { it("connects and disconnects through module lifecycle hooks", async () => { const service = new PrismaService({ datasources: { db: { url: databaseUrl } }, }); await service.onModuleInit(); await expect(service.$queryRaw`SELECT 1`).resolves.toBeDefined(); await service.onModuleDestroy(); }); });