From 6856e76326ba986c72ce8ca63e9a14ab890aa1b3 Mon Sep 17 00:00:00 2001 From: chanwoo7 Date: Sun, 4 Oct 2026 23:49:45 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20mutation=20=EC=9D=91=EB=8B=B5=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=82=AD=EC=A0=9C=EB=90=9C=20=EC=8A=AC=EB=A1=AF=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nested include는 soft-delete Extension 밖인데 upsertProductCustomTemplate·setCustomTemplateActive 응답 include의 text_tokens에만 where가 없어, 판매자가 지운 슬롯이 두 mutation 응답에 다시 섞임(같은 파일의 나머지 text_tokens include 4곳은 activeWhere 명시) - 두 include에 where: activeWhere 추가 - 회귀: 삭제 슬롯이 있는 템플릿에서 두 mutation 응답 textTokens가 활성 슬롯만 2건(수정 전 삭제 슬롯 id가 섞여 2건 모두 실패 확인) --- .../repositories/product.repository.ts | 2 + ...uct-seller-custom-template.service.spec.ts | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/features/product/repositories/product.repository.ts b/src/features/product/repositories/product.repository.ts index 43a7df09..89ad152f 100644 --- a/src/features/product/repositories/product.repository.ts +++ b/src/features/product/repositories/product.repository.ts @@ -882,6 +882,7 @@ export class ProductRepository { }, include: { text_tokens: { + where: activeWhere, orderBy: { sort_order: 'asc' }, }, }, @@ -922,6 +923,7 @@ export class ProductRepository { }, include: { text_tokens: { + where: activeWhere, orderBy: { sort_order: 'asc' }, }, }, diff --git a/src/features/product/services/product-seller-custom-template.service.spec.ts b/src/features/product/services/product-seller-custom-template.service.spec.ts index 0eb77fbc..b8368f90 100644 --- a/src/features/product/services/product-seller-custom-template.service.spec.ts +++ b/src/features/product/services/product-seller-custom-template.service.spec.ts @@ -63,6 +63,22 @@ describe('SellerCustomTemplateService (real DB)', () => { }); } + /** 활성 슬롯 A + soft-delete된 슬롯 B. */ + async function createTokensWithDeleted(templateId: bigint) { + const live = await prisma.productCustomTextToken.create({ + data: { template_id: templateId, token_key: 'A', default_text: 'a' }, + }); + await prisma.productCustomTextToken.create({ + data: { + template_id: templateId, + token_key: 'B', + default_text: 'b', + deleted_at: new Date(), + }, + }); + return live; + } + describe('sellerUpsertProductCustomTemplate', () => { it('없는 productId면 404', async () => { const { accountId } = await setupSellerWithProduct(); @@ -100,6 +116,22 @@ describe('SellerCustomTemplateService (real DB)', () => { }); expect(templates).toHaveLength(1); }); + + it('응답 textTokens에서 soft-delete된 슬롯은 제외한다', async () => { + const { accountId, product } = await setupSellerWithProduct(); + const tpl = await createTemplate(product.id); + const live = await createTokensWithDeleted(tpl.id); + + const result = await service.sellerUpsertProductCustomTemplate( + accountId, + { + productId: product.id.toString(), + baseImageUrl: ownedUploadUrl('PRODUCT_IMAGE', accountId, 'b.png'), + }, + ); + + expect(result.textTokens.map((t) => t.id)).toEqual([live.id.toString()]); + }); }); describe('sellerSetProductCustomTemplateActive', () => { @@ -145,6 +177,19 @@ describe('SellerCustomTemplateService (real DB)', () => { }); expect(auditLogs).toHaveLength(1); }); + + it('응답 textTokens에서 soft-delete된 슬롯은 제외한다', async () => { + const { accountId, product } = await setupSellerWithProduct(); + const tpl = await createTemplate(product.id); + const live = await createTokensWithDeleted(tpl.id); + + const result = await service.sellerSetProductCustomTemplateActive( + accountId, + { templateId: tpl.id.toString(), isActive: false }, + ); + + expect(result.textTokens.map((t) => t.id)).toEqual([live.id.toString()]); + }); }); describe('sellerUpsertProductCustomTextToken', () => {