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."); + } }