diff --git a/prisma/seed/idempotent.ts b/prisma/seed/idempotent.ts index f4eb6b2e..c7b729b1 100644 --- a/prisma/seed/idempotent.ts +++ b/prisma/seed/idempotent.ts @@ -51,6 +51,17 @@ export async function resetSeedScope(prisma: PrismaClient): Promise { }, }, { review_comment: { account_id: { in: userIds } } }, + // 아래에서 지울 시드 리뷰의 댓글 신고 — 남기면 SET NULL로 대상 없는 PENDING이 된다 + { + review_comment: { + review: { + OR: [ + { account_id: { in: userIds } }, + { store_id: { in: storeIds } }, + ], + }, + }, + }, ], }, }); @@ -98,6 +109,10 @@ export async function resetSeedScope(prisma: PrismaClient): Promise { await prisma.reviewMedia.deleteMany({ where: { review: { order_item_id: { in: orderItemIds } } }, }); + // 댓글은 작성자가 시드 밖 계정이어도 리뷰 FK(RESTRICT)에 걸린다 + await prisma.reviewComment.deleteMany({ + where: { review: { order_item_id: { in: orderItemIds } } }, + }); await prisma.review.deleteMany({ where: { order_item_id: { in: orderItemIds } }, }); @@ -118,6 +133,15 @@ export async function resetSeedScope(prisma: PrismaClient): Promise { await prisma.reviewMedia.deleteMany({ where: { review: { account_id: { in: userIds } } }, }); + // 시드 유저 리뷰의 댓글(리뷰 FK)과 시드 유저가 남의 리뷰에 단 댓글(계정 FK) — 둘 다 RESTRICT + await prisma.reviewComment.deleteMany({ + where: { + OR: [ + { account_id: { in: userIds } }, + { review: { account_id: { in: userIds } } }, + ], + }, + }); await prisma.review.deleteMany({ where: { account_id: { in: userIds } } }); // 찜 diff --git a/src/test/seed-reset-scope.spec.ts b/src/test/seed-reset-scope.spec.ts new file mode 100644 index 00000000..e025f9a6 --- /dev/null +++ b/src/test/seed-reset-scope.spec.ts @@ -0,0 +1,125 @@ +import { seedCategories } from '../../prisma/seed/categories'; +import { resetSeedScope } from '../../prisma/seed/idempotent'; +import { seedOrders } from '../../prisma/seed/orders'; +import { seedRegions } from '../../prisma/seed/regions'; +import { seedReviews } from '../../prisma/seed/reviews'; +import { seedStores } from '../../prisma/seed/stores'; +import { seedUsers, type SeededUser } from '../../prisma/seed/users'; + +import type { PrismaClient } from '@/generated/prisma/client'; +import { disconnectTestPrismaClient } from '@/test/db/prisma-test-client'; +import { closeTruncateConnection, truncateAll } from '@/test/db/truncate'; +import { + createAccount, + createOrderItem, + createReview, + createReviewReport, +} from '@/test/factories'; +import { createTestingModuleWithRealDb } from '@/test/modules/testing-module.builder'; + +// 로컬에서 시드 데이터에 붙여 둔 행이 있어도 재시드(정리 → 재삽입)가 FK에 막히면 안 된다 +describe('resetSeedScope (real DB)', () => { + let prisma: PrismaClient; + + beforeAll(async () => { + ({ prisma } = await createTestingModuleWithRealDb({ providers: [] })); + }); + + afterAll(async () => { + await closeTruncateConnection(); + await disconnectTestPrismaClient(); + }); + + beforeEach(async () => { + await truncateAll(); + }); + + /** 시드 main과 같은 순서로 리뷰까지 채운다 */ + async function seedThroughReviews(): Promise { + const users = await seedUsers(prisma); + await seedRegions(prisma); + const categories = await seedCategories(prisma); + const stores = await seedStores(prisma, { categories }); + const orders = await seedOrders(prisma, { users, stores }); + await seedReviews(prisma, { users, stores, orders }); + return users; + } + + function writeComment(reviewId: bigint, accountId: bigint) { + return prisma.reviewComment.create({ + data: { review_id: reviewId, account_id: accountId, content: '댓글' }, + }); + } + + it('시드 리뷰에 다른 계정이 단 댓글이 있어도 정리하고 다시 시드한다', async () => { + const users = await seedThroughReviews(); + const seedReview = await prisma.review.findFirstOrThrow({ + where: { account_id: { in: users.map((u) => u.id) } }, + }); + const outsider = await createAccount(prisma, { account_type: 'USER' }); + const comment = await writeComment(seedReview.id, outsider.id); + + await resetSeedScope(prisma); + await seedThroughReviews(); + + expect( + await prisma.reviewComment.findUnique({ where: { id: comment.id } }), + ).toBeNull(); + expect( + await prisma.account.findUnique({ where: { id: outsider.id } }), + ).not.toBeNull(); + }); + + it('시드 리뷰에 달린 외부 댓글의 외부 신고도 지워 대상 없는 신고가 남지 않는다', async () => { + const users = await seedThroughReviews(); + const seedReview = await prisma.review.findFirstOrThrow({ + where: { account_id: { in: users.map((u) => u.id) } }, + }); + const outsider = await createAccount(prisma, { account_type: 'USER' }); + const comment = await writeComment(seedReview.id, outsider.id); + const report = await createReviewReport(prisma, { + review_comment_id: comment.id, + }); + + await resetSeedScope(prisma); + + expect( + await prisma.reviewReport.findUnique({ where: { id: report.id } }), + ).toBeNull(); + }); + + it('시드 사용자가 시드 밖 리뷰에 단 댓글은 지우고 그 리뷰는 남긴다', async () => { + const [seedUser] = await seedThroughReviews(); + const outsideReview = await createReview(prisma); + const comment = await writeComment(outsideReview.id, seedUser.id); + + await resetSeedScope(prisma); + + expect( + await prisma.reviewComment.findUnique({ where: { id: comment.id } }), + ).toBeNull(); + expect( + await prisma.review.findUnique({ where: { id: outsideReview.id } }), + ).not.toBeNull(); + }); + + it('시드 주문 밖에 있는 시드 사용자 리뷰의 댓글도 지운다', async () => { + const [seedUser] = await seedThroughReviews(); + const outsideItem = await createOrderItem(prisma); + const review = await createReview(prisma, { + order_item_id: outsideItem.id, + account_id: seedUser.id, + }); + const outsider = await createAccount(prisma, { account_type: 'USER' }); + const comment = await writeComment(review.id, outsider.id); + + await resetSeedScope(prisma); + + expect( + await prisma.review.findUnique({ where: { id: review.id } }), + ).toBeNull(); + expect( + await prisma.reviewComment.findUnique({ where: { id: comment.id } }), + ).toBeNull(); + }); +});