From 05d143d2e8ea7f9885b4ed0cb1bb1f760188e0c1 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sat, 4 Jul 2026 14:44:52 -0400 Subject: [PATCH] fix(mypage): populate unread_present_count from ViewerPresents The home-screen crate button reads this count via MyPageTask (parsed into Data.MyPage.data.unread_mail_count) and MyPageItemHome.cs:148 routes it into SetUnreadGiftCount to render the "N" bubble on the gift button. It was hardcoded 0, so tutorial gifts (5 presents sitting in the viewer's inbox after signup) showed no badge until the crate was opened. Add IViewerRepository.CountUnclaimedPresentsAsync and call it from MyPageController.Index. Regression test seeds a viewer with tutorial presents and asserts unread_present_count > 0. Co-Authored-By: Claude Opus 4.7 --- .../Repositories/Viewer/IViewerRepository.cs | 8 +++++++ .../Repositories/Viewer/ViewerRepository.cs | 7 ++++++ .../Controllers/MyPageController.cs | 2 +- .../Controllers/MyPageControllerTests.cs | 23 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/SVSim.Database/Repositories/Viewer/IViewerRepository.cs b/SVSim.Database/Repositories/Viewer/IViewerRepository.cs index cea51a27..a45cebbd 100644 --- a/SVSim.Database/Repositories/Viewer/IViewerRepository.cs +++ b/SVSim.Database/Repositories/Viewer/IViewerRepository.cs @@ -104,4 +104,12 @@ public interface IViewerRepository /// Batch-loads owned guild-emblem ids for a viewer. Used by /guild/emblem_list. /// Task> GetEmblemListAsync(long viewerId, CancellationToken ct = default); + + /// + /// Counts the viewer's unclaimed rows. Drives + /// /mypage/index.unread_present_count, which the client casts to + /// Data.MyPage.data.unread_mail_count to render the home-screen crate badge + /// (MyPageItemHome.cs:148 → SetUnreadGiftCount). + /// + Task CountUnclaimedPresentsAsync(long viewerId, CancellationToken ct = default); } diff --git a/SVSim.Database/Repositories/Viewer/ViewerRepository.cs b/SVSim.Database/Repositories/Viewer/ViewerRepository.cs index 9fa22386..5c5ae367 100644 --- a/SVSim.Database/Repositories/Viewer/ViewerRepository.cs +++ b/SVSim.Database/Repositories/Viewer/ViewerRepository.cs @@ -396,6 +396,13 @@ public class ViewerRepository : IViewerRepository return viewer?.Emblems.Select(e => (long)e.Id).ToList() ?? new List(); } + public Task CountUnclaimedPresentsAsync(long viewerId, CancellationToken ct = default) + { + return _dbContext.Set() + .Where(p => p.ViewerId == viewerId && p.Status == PresentStatus.Unclaimed) + .CountAsync(ct); + } + private async Task BuildDefaultViewer(string displayName, int initialTutorialState = 1) { Models.Viewer viewer = new Models.Viewer diff --git a/SVSim.EmulatedEntrypoint/Controllers/MyPageController.cs b/SVSim.EmulatedEntrypoint/Controllers/MyPageController.cs index 9f284a74..1c0d1629 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/MyPageController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/MyPageController.cs @@ -100,7 +100,7 @@ public class MyPageController : SVSimController CanGiveDailyLoginBonus = _loginBonus.IsDue(viewer), UnreceivedMissionRewardCount = 0, // TODO(mypage-stub): viewer mission progress ReceiveFriendApplyCount = 0, // TODO(mypage-stub): viewer friend-request inbox - UnreadPresentCount = 0, // TODO(mypage-stub): viewer presents/mail + UnreadPresentCount = await _viewerRepository.CountUnclaimedPresentsAsync(viewer.Id, HttpContext.RequestAborted), FriendBattleInviteCount = 0, // TODO(mypage-stub): viewer room-invite count GuildNotification = await BuildGuildNotificationAsync(viewer.Id, HttpContext.RequestAborted), LastAnnounceId = 0, // TODO(mypage-stub): globals announcement metadata diff --git a/SVSim.UnitTests/Controllers/MyPageControllerTests.cs b/SVSim.UnitTests/Controllers/MyPageControllerTests.cs index a3fb9733..004f64de 100644 --- a/SVSim.UnitTests/Controllers/MyPageControllerTests.cs +++ b/SVSim.UnitTests/Controllers/MyPageControllerTests.cs @@ -54,4 +54,27 @@ public class MyPageControllerTests var root = JsonDocument.Parse(await resp.Content.ReadAsStringAsync()).RootElement; Assert.That(root.GetProperty("can_give_daily_login_bonus").GetBoolean(), Is.False); } + + [Test] + public async Task MyPage_unread_present_count_reflects_unclaimed_viewer_presents() + { + // Drives the home-screen crate badge — MyPageTask parses `unread_present_count` into + // Data.MyPage.data.unread_mail_count, which MyPageItemHome.SetUnreadGiftCount reads to + // show the "N" bubble on the gift button. Stubbed to 0 would hide it even when the + // tutorial gift has 5 rewards sitting unclaimed. + using var factory = new SVSimTestFactory(); + await factory.SeedGlobalsAsync(); + long viewerId = await factory.SeedViewerAsync(); + await factory.SeedTutorialPresentsAsync(viewerId); + using var client = factory.CreateAuthenticatedClient(viewerId); + + var resp = await client.PostAsync("/mypage/index", + new StringContent(MyPageRequestJson, Encoding.UTF8, "application/json")); + Assert.That(resp.StatusCode, Is.EqualTo(HttpStatusCode.OK), await resp.Content.ReadAsStringAsync()); + + var root = JsonDocument.Parse(await resp.Content.ReadAsStringAsync()).RootElement; + int count = root.GetProperty("unread_present_count").GetInt32(); + Assert.That(count, Is.GreaterThan(0), + "unread_present_count must be the viewer's live Unclaimed ViewerPresent count — a stub 0 hides the crate badge."); + } }