feat(viewer-repo): add LoadForMatchContextAsync for battle-node ctx build

Focused AsNoTracking load with Info.SelectedEmblem/SelectedDegree includes
for the new MatchContextBuilder. Single test locks the include graph.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-01 12:37:44 -04:00
parent 0e8f5427c3
commit 89b3d23bde
3 changed files with 66 additions and 0 deletions

View File

@@ -20,4 +20,11 @@ public interface IViewerRepository
/// viewer's UDID to the target, then deletes the anonymous viewer.
/// </summary>
Task MergeAnonymousViewerInto(long anonymousViewerId, long targetViewerId);
/// <summary>
/// Focused load for building a battle-node <c>MatchContext</c>: viewer + Info + Info's
/// equipped Emblem/Degree nav refs. Read-only (AsNoTracking). Returns null if the viewer
/// doesn't exist.
/// </summary>
Task<Models.Viewer?> LoadForMatchContextAsync(long viewerId);
}

View File

@@ -228,6 +228,15 @@ public class ViewerRepository : IViewerRepository
await _dbContext.SaveChangesAsync();
}
public async Task<Models.Viewer?> LoadForMatchContextAsync(long viewerId)
{
return await _dbContext.Set<Models.Viewer>()
.AsNoTracking()
.Include(v => v.Info.SelectedEmblem)
.Include(v => v.Info.SelectedDegree)
.FirstOrDefaultAsync(v => v.Id == viewerId);
}
private async Task<Models.Viewer> BuildDefaultViewer(string displayName, int initialTutorialState = 1)
{
Models.Viewer viewer = new Models.Viewer

View File

@@ -0,0 +1,50 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SVSim.Database;
using SVSim.Database.Models;
using SVSim.Database.Repositories.Viewer;
using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Repositories;
[TestFixture]
public class ViewerRepositoryMatchContextTests
{
[Test]
public async Task LoadForMatchContext_includes_info_selected_emblem_and_degree()
{
await using var factory = new SVSimTestFactory();
var vid = await factory.SeedViewerAsync();
int emblemId, degreeId;
using (var seedScope = factory.Services.CreateScope())
{
var db = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
// Use existing rows from the reference-data seed — inserting EmblemEntry/DegreeEntry
// with a hardcoded Id collides with the seeded catalog (UNIQUE constraint).
var emblem = await db.Emblems.FirstAsync();
var degree = await db.Degrees.FirstAsync();
emblemId = emblem.Id;
degreeId = degree.Id;
var viewer = await db.Viewers.FindAsync(vid);
viewer!.Info.CountryCode = "KOR";
viewer.Info.IsOfficial = true;
viewer.Info.SelectedEmblem = emblem;
viewer.Info.SelectedDegree = degree;
viewer.DisplayName = "DraftedPlayer";
await db.SaveChangesAsync();
}
using var scope = factory.Services.CreateScope();
var repo = scope.ServiceProvider.GetRequiredService<IViewerRepository>();
var loaded = await repo.LoadForMatchContextAsync(vid);
Assert.That(loaded, Is.Not.Null);
Assert.That(loaded!.DisplayName, Is.EqualTo("DraftedPlayer"));
Assert.That(loaded.Info.CountryCode, Is.EqualTo("KOR"));
Assert.That(loaded.Info.IsOfficial, Is.True);
Assert.That(loaded.Info.SelectedEmblem.Id, Is.EqualTo(emblemId));
Assert.That(loaded.Info.SelectedDegree.Id, Is.EqualTo(degreeId));
}
}