feat(load-index): populate battle_pass_level_info from IBattlePassService
Wire IBattlePassService.GetLevelCurveAsync into LoadController so /load/index emits the 100-entry battle_pass_level_info dict when levels are seeded. Also adds BattlePassRepository.ResetLevelCurveCache() to bust the process-level static cache in tests that seed levels after earlier HTTP calls have primed it with an empty list, and updates SVSimTestFactory.SeedGlobalsAsync + the stale Index_surfaces_seeded_globals_after_bootstrap assertion accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,4 +50,11 @@ public sealed class BattlePassRepository : IBattlePassRepository
|
||||
}
|
||||
finally { _curveCacheLock.Release(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops the process-level level-curve cache. Tests that seed BattlePassLevels after the
|
||||
/// cache has already been populated (by an earlier test's HTTP call) must call this before
|
||||
/// re-seeding so the next read fetches fresh rows.
|
||||
/// </summary>
|
||||
public static void ResetLevelCurveCache() => _curveCache = null;
|
||||
}
|
||||
|
||||
@@ -46,10 +46,12 @@ public class LoadController : SVSimController
|
||||
private readonly IGlobalsRepository _globalsRepository;
|
||||
private readonly ICardAcquisitionService _acquisition;
|
||||
private readonly IGameConfigService _config;
|
||||
private readonly IBattlePassService _battlePass;
|
||||
|
||||
public LoadController(IViewerRepository viewerRepository, ICardRepository cardRepository,
|
||||
ICollectionRepository collectionRepository, IGlobalsRepository globalsRepository,
|
||||
ICardAcquisitionService acquisition, IGameConfigService config)
|
||||
ICardAcquisitionService acquisition, IGameConfigService config,
|
||||
IBattlePassService battlePass)
|
||||
{
|
||||
_viewerRepository = viewerRepository;
|
||||
_cardRepository = cardRepository;
|
||||
@@ -57,6 +59,7 @@ public class LoadController : SVSimController
|
||||
_globalsRepository = globalsRepository;
|
||||
_acquisition = acquisition;
|
||||
_config = config;
|
||||
_battlePass = battlePass;
|
||||
}
|
||||
|
||||
[HttpPost("index")]
|
||||
@@ -194,9 +197,8 @@ public class LoadController : SVSimController
|
||||
LootBoxRegulations = new LootBoxRegulations(),
|
||||
GatheringInfo = new GatheringInfo(),
|
||||
IsBattlePassPeriod = rotation.IsBattlePassPeriod,
|
||||
// Optional per spec (load-index.md:228). We have BattlePassLevelEntry rows seeded, but
|
||||
// no per-viewer Battle Pass progression yet — emit null until that subsystem lands.
|
||||
BattlePassLevelInfo = null,
|
||||
BattlePassLevelInfo = (await _battlePass.GetLevelCurveAsync(CancellationToken.None))
|
||||
as Dictionary<string, BattlePassLevel>,
|
||||
SpecialCrystalInfos = new List<SpecialCrystalInfo>(),
|
||||
AvatarRotationInfo = await BuildAvatarInfoAsync(),
|
||||
MyRotationInfo = await BuildMyRotationInfoAsync(),
|
||||
|
||||
@@ -21,6 +21,8 @@ public class LoadControllerTests
|
||||
private const string IndexRequestJson =
|
||||
"""{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","carrier":"steam","card_master_hash":""}""";
|
||||
|
||||
private static string SeedDir => Path.Combine(AppContext.BaseDirectory, "Data", "seeds");
|
||||
|
||||
/// <summary>
|
||||
/// Wire keys (from <c>[Key("...")]</c> / mirrored <c>[JsonPropertyName]</c>) for fields the
|
||||
/// client reads UNCONDITIONALLY in <c>LoadDetail.ConvertJsonData</c> (no <c>Keys.Contains</c>
|
||||
@@ -308,8 +310,12 @@ public class LoadControllerTests
|
||||
// Optional/absent fields stay absent when nothing meaningful to surface
|
||||
Assert.That(root.TryGetProperty("daily_login_bonus", out _), Is.False,
|
||||
"daily_login_bonus optional per spec; emit null when no active campaign");
|
||||
Assert.That(root.TryGetProperty("battle_pass_level_info", out _), Is.False,
|
||||
"battle_pass_level_info optional per spec; emit null until viewer pass state is wired");
|
||||
|
||||
// battle_pass_level_info is present when levels are seeded — 100-entry dict keyed by level string.
|
||||
Assert.That(root.TryGetProperty("battle_pass_level_info", out var bpli), Is.True,
|
||||
"battle_pass_level_info must be present once BattlePassLevels rows are seeded");
|
||||
Assert.That(bpli.ValueKind, Is.EqualTo(JsonValueKind.Object));
|
||||
Assert.That(bpli.EnumerateObject().Count(), Is.EqualTo(100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -373,4 +379,32 @@ public class LoadControllerTests
|
||||
"skin 407 should have been backfilled by /load/index");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoadIndex_emits_battle_pass_level_info_with_100_entries_when_period_active()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
await new SVSim.Bootstrap.Importers.BattlePassImporter().ImportAsync(db, SeedDir);
|
||||
}
|
||||
// Bust the process-level level-curve cache so the next /load/index call reads the
|
||||
// freshly-seeded rows rather than a stale empty list from an earlier test's HTTP call.
|
||||
SVSim.Database.Repositories.BattlePass.BattlePassRepository.ResetLevelCurveCache();
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var response = await client.PostAsync("/load/index",
|
||||
new StringContent(IndexRequestJson, System.Text.Encoding.UTF8, "application/json"));
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(System.Net.HttpStatusCode.OK), body);
|
||||
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(body);
|
||||
var levels = doc.RootElement.GetProperty("battle_pass_level_info");
|
||||
Assert.That(levels.ValueKind, Is.EqualTo(System.Text.Json.JsonValueKind.Object));
|
||||
Assert.That(levels.GetProperty("1").GetProperty("level").GetString(), Is.EqualTo("1"));
|
||||
Assert.That(levels.GetProperty("1").GetProperty("required_point").GetString(), Is.EqualTo("0"));
|
||||
Assert.That(levels.GetProperty("100").GetProperty("required_point").GetString(), Is.EqualTo("49500"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using SVSim.Bootstrap.Importers;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.Database.Repositories.BattlePass;
|
||||
using SVSim.Database.Repositories.Deck;
|
||||
using SVSim.Database.Repositories.Viewer;
|
||||
using SVSim.EmulatedEntrypoint;
|
||||
@@ -196,6 +197,8 @@ internal sealed class SVSimTestFactory : WebApplicationFactory<Program>
|
||||
await new AvatarAbilityImporter().ImportAsync(ctx, seedDir);
|
||||
await new ArenaSeasonImporter().ImportAsync(ctx, seedDir);
|
||||
await new BattlePassImporter().ImportAsync(ctx, seedDir);
|
||||
// Reset the process-level level-curve cache so the next HTTP call reads freshly-seeded rows.
|
||||
BattlePassRepository.ResetLevelCurveCache();
|
||||
await new BattlePassSeasonImporter().ImportAsync(ctx, seedDir);
|
||||
await new BattlePassRewardImporter().ImportAsync(ctx, seedDir);
|
||||
await new DailyLoginBonusImporter().ImportAsync(ctx, seedDir);
|
||||
|
||||
Reference in New Issue
Block a user