Exercise 3: Testing Real Delete Behaviour — Possible Solution =============================================================== Assumes deleteUserKeepingPosts from Chapter 1 has been changed to take the client as its first argument: deleteUserKeepingPosts(db, userId). // test/users.int.test.ts import { it, expect, beforeEach, afterAll } from "vitest"; import { prisma } from "../lib/prisma"; import { deleteUserKeepingPosts } from "../src/users"; import { resetDatabase } from "./reset"; beforeEach(resetDatabase); afterAll(() => prisma.$disconnect()); async function makeAuthorWithPosts() { return prisma.user.create({ data: { email: "grace@test.local", name: "Grace", profile: { create: { bio: "Admiral" } }, posts: { create: [ { title: "One", slug: "one" }, { title: "Two", slug: "two" }, ], }, }, }); } it("won't delete a user who still has posts", async () => { const grace = await makeAuthorWithPosts(); await expect(prisma.user.delete({ where: { id: grace.id } })) .rejects.toMatchObject({ code: "P2003" }); expect(await prisma.post.count({ where: { authorId: grace.id } })).toBe(2); }); it("moves posts to the placeholder account and removes the profile", async () => { const grace = await makeAuthorWithPosts(); const moved = await deleteUserKeepingPosts(prisma, grace.id); expect(moved).toBe(2); const ghost = await prisma.user.findUniqueOrThrow({ where: { email: "deleted@blog.local" } }); expect(await prisma.post.count({ where: { authorId: ghost.id } })).toBe(2); expect(await prisma.user.findUnique({ where: { id: grace.id } })).toBeNull(); expect(await prisma.profile.count()).toBe(0); // removed by onDelete: Cascade }); Why a mock couldn't catch either behaviour: Both results come from the schema's relation rules, which live in the database. The P2003 happens because Post.author is required with the default onDelete rule, so the database refuses to leave posts without an author. The profile disappears because Profile.user has onDelete: Cascade. A mocked client knows nothing about foreign keys or cascades: user.delete would simply return whatever you told it to, and profile.count would return whatever you configured. WHY THIS WORKS AS AN ANSWER ------------------------------ Each test starts from an empty database, builds exactly the data it needs, and checks the database's actual state afterwards rather than the function's return value alone. Together they show why deleteUserKeepingPosts exists (a plain delete is refused) and that it works as intended, including a side effect (the cascaded profile) that is easy to forget.