feat: 建立漂流瓶权威数据模型
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
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<unknown>) {
|
||||
await expect(operation).rejects.toMatchObject({
|
||||
code: "P2002",
|
||||
} satisfies Partial<Prisma.PrismaClientKnownRequestError>);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user