Commit Graph

7 Commits

Author SHA1 Message Date
gamer147
b1397e3a3e refactor(repositories): move static caches into IMemoryCache, enable within-fixture parallelism
BattlePassRepository._curveCache and MissionCatalogRepository._maxLevelCache
were private-static fields populated lazily on first read from whatever
DbContext happened to be in scope. In production "one DbContext lineage
per process" makes that fine. Under parallel test execution each
SVSimTestFactory owns its own SQLite :memory: DB, so the first reader's
DB (often empty, in tests that don't seed BP) poisoned the cache for
concurrent readers from a seeded DB — assertions like "BP level info
must be present after seeding" failed because the process-static cache
returned an empty list populated by the other test's empty DB.

The first patch attempted a `BypassCacheForTests` static flag, which is
exactly the kind of test-only seam that rots the production code: future
caches get the same flag, repos accumulate hidden knobs, and the
underlying invariant ("a cache populated from arbitrary scope serves
arbitrary scope") goes unaddressed.

Instead, move both caches into the DI-registered IMemoryCache.
AddMemoryCache() registers it as singleton-per-service-provider:
production has one provider → one IMemoryCache → identical caching
semantics to before. Each WebApplicationFactory builds its own
provider → its own IMemoryCache → cache is naturally scoped per fixture,
no cross-test bleed possible.

The ResetLevelCurveCache() method and its three call sites
(SVSimTestFactory.SeedGlobalsAsync, BattlePassServiceTests,
LoadControllerTests) are deleted — a fresh factory owns a fresh empty
cache, no manual invalidation needed.

With this and the previous StoryService fixture-instance fix in place,
ParallelScope.All works: 776/776 in 57s wall clock (down from 59s on
Fixtures, 2m13s pre-parallelism).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 00:41:09 -04:00
gamer147
c7dfd43daa review(bp): doc fixes + sales_period_info non-nullable + drop checked cast + TODO
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 00:00:24 -04:00
gamer147
d877febcb8 review(bp): move SaveChanges into repo with race protection; JST constant
GetOrCreateProgressAsync now persists the new row itself and catches
DbUpdateException on unique-constraint violations — concurrent /info
calls no longer throw 500s. BattlePassService no longer calls
SaveChangesAsync after the get-or-create. FormatWireDate uses a named
JstOffset constant instead of an inline TimeSpan.FromHours(9).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:22:48 -04:00
gamer147
8a35f8c40b feat(bp): /battle_pass/info — service + controller + 3 tests
Also fixes BattlePassRepository.GetActiveSeasonAsync to use client-side
DateTimeOffset filtering (SQLite provider cannot translate DateTimeOffset
comparisons in LINQ WHERE/ORDER BY clauses).
2026-05-26 23:14:26 -04:00
gamer147
7abdfe27cb review(bp): drop fragile cast, thread CT, internalize cache reset
- IndexResponse.BattlePassLevelInfo widened to IReadOnlyDictionary<string,BattlePassLevel>?
  so any IReadOnlyDictionary impl (FrozenDictionary, wrapper, etc.) serializes correctly
  instead of silently null-ing via a failed as-cast
- LoadController.Index now takes CancellationToken ct and threads it to GetLevelCurveAsync
  instead of CancellationToken.None
- BattlePassRepository.ResetLevelCurveCache changed from public to internal; added
  InternalsVisibleTo("SVSim.UnitTests") to SVSim.Database.csproj (was absent)
2026-05-26 23:01:26 -04:00
gamer147
9bec1df52f 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>
2026-05-26 22:56:41 -04:00
gamer147
1420c60486 feat(bp): repositories + identity generation for runtime-inserted tables
Add ValueGeneratedOnAdd to ViewerBattlePassProgress.Id and
ViewerBattlePassClaims.Id so Postgres generates IDENTITY values at
runtime. Regenerate AddBattlePass migration in-place to include the
IdentityByDefaultColumn annotations. Add IBattlePassRepository /
BattlePassRepository (season lookup + level-curve cache) and
IViewerBattlePassRepository / ViewerBattlePassRepository
(get-or-create progress, claim reads/writes).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:40:45 -04:00