Przeglądaj źródła

fix(store): 소모품 보관함(GET /api/member/me/items)에 ConsumableOnly 필터 — 지위아이템 혼입 제거

감사 확정 #4: 소모품 탭이 지위아이템(배지/닉네임·댓글 이펙트)까지 반환 + 페이지네이션 어긋남.
- Items.List.Query 에 ConsumableOnly(기본 false) 추가, Handler 가 true 시 Kind>=ChatBanLift(소모품 5~11)만 필터
- ItemsList(api/member/me/items, 소모품 보관함) → ConsumableOnly:true. MembersItemsList(api/members/me/items, 전체) → 기본 false 유지(지위+소모품)
- total/페이지 계산도 소모품 기준으로 정합
- 회귀 테스트 List_ConsumableOnly_ExcludesStatusItems 추가. MemberItem 30/30 그린, 빌드 0에러

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
KIM-JINO5 2 tygodni temu
rodzic
commit
5fe3a969dd

+ 6 - 0
Application/Features/Api/Member/Items/List/Handler.cs

@@ -1,5 +1,6 @@
 using Application.Abstractions.Data;
 using Application.Abstractions.Messaging;
+using Domain.Entities.Store.ValueObject;
 using Microsoft.EntityFrameworkCore;
 
 namespace Application.Features.Api.Member.Items.List;
@@ -24,6 +25,11 @@ internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
             query = query.Where(i => i.ExpiresAt == null || i.ExpiresAt > now);
         }
 
+        if (request.ConsumableOnly)
+        {
+            query = query.Where(i => i.Kind >= ItemKind.ChatBanLift);
+        }
+
         var total = await query.CountAsync(ct);
 
         var list = await query

+ 1 - 1
Application/Features/Api/Member/Items/List/Query.cs

@@ -2,4 +2,4 @@ using Application.Abstractions.Messaging;
 
 namespace Application.Features.Api.Member.Items.List;
 
-public sealed record Query(int MemberID, bool IncludeUsed = false, bool IncludeExpired = false, int Page = 1, int PerPage = 20) : IQuery<Response>;
+public sealed record Query(int MemberID, bool IncludeUsed = false, bool IncludeExpired = false, int Page = 1, int PerPage = 20, bool ConsumableOnly = false) : IQuery<Response>;

+ 21 - 0
Tests/Application.Tests/MemberItemTests.cs

@@ -427,6 +427,27 @@ public sealed class MemberItemTests
         Assert.IsFalse(withUsed.List.Any(r => r.ID == revoked.ID), "회수분은 항상 제외");
     }
 
+    [TestMethod]
+    public async Task List_ConsumableOnly_ExcludesStatusItems()
+    {
+        // 기대 행위: ConsumableOnly=true (소모품 보관함) 는 소모품(Kind>=ChatBanLift)만, 지위아이템(Badge/NicknameEffect)은 제외.
+        using var db = TestDb.Create();
+        var memberID = await TestDb.CreateMemberAsync(db);
+
+        var consumable = await SeedMemberItemAsync(db, memberID, ItemKind.ChatBanLift);
+        await SeedMemberItemAsync(db, memberID, ItemKind.Badge, durationDays: 30);
+        await SeedMemberItemAsync(db, memberID, ItemKind.NicknameEffect);
+
+        var handler = new ListHandler(db);
+
+        var all = await handler.Handle(new ListQuery(memberID), default);
+        Assert.AreEqual(3, all.Total, "기본(전체) 조회는 소모품+지위 3건");
+
+        var consumableOnly = await handler.Handle(new ListQuery(memberID, ConsumableOnly: true), default);
+        Assert.AreEqual(1, consumableOnly.Total, "ConsumableOnly 는 소모품(ChatBanLift) 1건만");
+        Assert.AreEqual(consumable.ID, consumableOnly.List[0].ID);
+    }
+
     // ── 도메인: 지위 아이템 (Badge/NicknameEffect/CommentEffect) ────
 
     [TestMethod]

+ 1 - 1
Web.Api/Endpoints/Member/ItemsList.cs

@@ -19,7 +19,7 @@ internal sealed class ItemsList : IEndpoint
             CancellationToken ct
         ) => {
             var memberID = user.GetRequiredMemberID();
-            var query = new Application.Features.Api.Member.Items.List.Query(memberID, includeUsed ?? false, Page: page ?? 1, PerPage: perPage ?? 20);
+            var query = new Application.Features.Api.Member.Items.List.Query(memberID, includeUsed ?? false, Page: page ?? 1, PerPage: perPage ?? 20, ConsumableOnly: true);
             var result = await sender.Send(query, ct);
 
             return ApiResponse.Ok(result);