chore(bootstrap): refresh stale GlobalsImporter references in docs/test names

This commit is contained in:
gamer147
2026-05-26 16:44:54 -04:00
parent c02991a5c2
commit 141f34f817
19 changed files with 41 additions and 44 deletions

View File

@@ -10,9 +10,8 @@ namespace SVSim.Bootstrap.Importers;
/// Idempotent upsert of the six card-id-keyed tables from load-index seeds:
/// SpotCards, ReprintedCards, UnlimitedRestrictions, LoadingExclusionCards,
/// MaintenanceCards, FeatureMaintenances. Loads the Cards FK set once for orphan warnings.
/// Rows missing from a seed are LEFT INTACT (consistent with prior GlobalsImporter behavior)
/// for the five card-id-keyed tables; FeatureMaintenances clears-and-rewrites because its
/// synthetic ordinal Id has no natural-key semantics.
/// Rows missing from a seed are LEFT INTACT — a partial seed shouldn't silently delete entries.
/// FeatureMaintenances clears-and-rewrites because its synthetic ordinal Id has no natural-key semantics.
/// </summary>
public class CardListsImporter
{
@@ -136,10 +135,10 @@ public class CardListsImporter
var seed = SeedLoader.LoadList<FeatureMaintenanceSeed>(Path.Combine(seedDir, "feature-maintenances.json"));
if (seed.Count == 0) return 0;
// FeatureMaintenances has a synthetic int Id assigned by the extractor (1-based ordinal).
// The original GlobalsImporter.ImportFeatureMaintenances added rows without dedup; since the
// seed is regenerated on every extract, clear-and-rewrite keeps re-runs idempotent and
// matches "the latest seed is authoritative". Pre-existing rows with seed-absent ids are
// dropped here (acceptable: only synthetic ordinals, no FKs reference this table).
// FeatureMaintenances use a synthetic ordinal id from the extractor; we clear-and-rewrite to
// keep re-runs idempotent and match "the latest seed is authoritative". Pre-existing rows
// with seed-absent ids are dropped here (acceptable: only synthetic ordinals, no FKs
// reference this table).
var existing = await context.FeatureMaintenances.ToListAsync();
context.FeatureMaintenances.RemoveRange(existing);
int created = 0;

View File

@@ -9,7 +9,7 @@ namespace SVSim.Bootstrap.Importers;
/// Reads <see cref="RotationConfig"/> from the GameConfigs table (populated by
/// <see cref="RotationConfigImporter"/>) and flips <c>CardSet.IsInRotation</c> to match.
/// Must run after RotationConfigImporter and CardImporter — CardSets missing from the DB
/// can't be promoted (the original GlobalsImporter behavior; we log a warning instead of failing).
/// can't be promoted (we log a warning instead of failing — the rotation flag flip is non-fatal).
/// </summary>
public class RotationFlagUpdater
{

View File

@@ -43,7 +43,7 @@ public static class Program
await using var context = new SVSimDbContext(NullLogger<SVSimDbContext>.Instance, dbOptions);
// Bootstrap applies pending migrations first — migrations are now DDL-only, all data
// (reference tables, cards, card cosmetic rewards, prod-captured globals, game config)
// (reference tables, cards, card cosmetic rewards, per-table seed globals, game config)
// is loaded by importers below. This means a freshly migrated DB is structure-only;
// every importer is idempotent so re-running is safe.
Console.WriteLine("[Bootstrap] Applying pending migrations...");
@@ -75,10 +75,8 @@ public static class Program
if (!opts.SkipGlobals)
{
// Per-domain seed pipeline. The legacy GlobalsImporter that parsed prod-captured
// /load/index, /mypage/index, /deck/info wire payloads directly is gone — capture
// → seed transformation lives in data_dumps/extract/*; importers below just
// deserialise the per-table JSON files in SVSim.Bootstrap/Data/seeds/.
// Per-domain seed pipeline. Each importer reads a per-table JSON seed file under
// SVSim.Bootstrap/Data/seeds/ produced by an extractor in data_dumps/extract/.
//
// RotationConfigImporter writes the Rotation GameConfig section that RotationFlagUpdater
// reads; CardImporter ran earlier in the !SkipCards block so CardSets are populated.

View File

@@ -8,7 +8,7 @@ namespace SVSim.Database.Models.Config;
/// Mapped to the wire-shape <c>SpecialRotationSchedule</c> at the controller seam.
/// <para>
/// Shipped defaults reproduce the 2026-05-23 prod capture so a fresh install ships with the
/// feature enabled. GlobalsImporter overwrites the DB section from any newer capture.
/// feature enabled. RotationConfigImporter overwrites the DB section from any newer seed.
/// </para>
/// </summary>
[ConfigSection("MyRotationSchedule")]

View File

@@ -4,7 +4,7 @@ namespace SVSim.Database.Models;
/// <summary>
/// One row per AI opponent shown on the practice (solo-play) opponent select screen.
/// Populated from prod /practice/info captures by SVSim.Bootstrap.GlobalsImporter.
/// Populated from seeds/practice-opponents.json by SVSim.Bootstrap.PracticeOpponentImporter.
///
/// The (<see cref="ClassId"/>, <see cref="AiDeckLevel"/>) pair MUST exist in the client's
/// baked-in master CSV `ai/practice_ai_setting`; if it doesn't, the client's

View File

@@ -4,7 +4,7 @@ namespace SVSim.Database.Models;
/// <summary>
/// One row per basic_puzzle group (puzzle_master_id). Static catalog seeded by
/// SVSim.Bootstrap.GlobalsImporter from prod-captures/basic-puzzle-info-*.json.
/// SVSim.Bootstrap.PuzzleImporter from seeds/puzzle-groups.json.
/// See docs/api-spec/endpoints/post-login/basic-puzzle/info.md.
/// </summary>
public class PuzzleGroupEntry : BaseEntity<int>

View File

@@ -4,7 +4,7 @@ namespace SVSim.Database.Models;
/// <summary>
/// One row per basic_puzzle mission (e.g. "Clear all Round 1 puzzles"). Static catalog
/// seeded by SVSim.Bootstrap from prod-captures/basic-puzzle-mission-*.json. The wire has no
/// seeded by SVSim.Bootstrap from seeds/puzzle-missions.json. The wire has no
/// stable id; importer assigns 1-based by capture order via the inherited <see cref="BaseEntity{TKey}.Id"/>.
/// See docs/api-spec/endpoints/post-login/basic-puzzle/mission.md.
/// </summary>

View File

@@ -8,7 +8,7 @@ public interface IGlobalsRepository
Task<List<BattlefieldEntry>> GetBattlefields(bool onlyOpen);
Task<List<RankInfoEntry>> GetRankInfo();
// Prod-captured globals — populated by SVSim.Bootstrap.GlobalsImporter.
// Seed-driven globals — populated by per-domain importers in SVSim.Bootstrap.
Task<List<MyRotationSettingEntry>> GetMyRotationSettings();
Task<List<MyRotationAbilityEntry>> GetMyRotationAbilities();
Task<List<AvatarAbilityEntry>> GetAvatarAbilities();

View File

@@ -30,7 +30,7 @@ public class LoadController : SVSimController
// Defense-in-depth: client unconditionally accesses RotationCardSetList[1] and [Count-1]
// (LoadDetail.cs:184), so a list with < 2 entries crashes /load/index parsing. With both
// CardImport and GlobalsImporter run, the real list has ~6 entries. If something goes wrong
// CardImporter and per-domain seed importers run, the real list has ~6 entries. If something goes wrong
// upstream (empty DB, bootstrap not yet run, etc.), fall back to this stub so the client at
// least loads. Removing this is only safe once viewer-side bootstrap is unconditional.
private static readonly List<CardSetIdentifier> StubRotationSets = new()
@@ -147,7 +147,7 @@ public class LoadController : SVSimController
// Globals — one cached fetch per slice. The Rotation/Challenge/DefaultLoadout sections
// come via IGameConfigService (DB → appsettings → ShippedDefaults). Other repo methods
// come from SVSim.Bootstrap.GlobalsImporter seeding.
// come from the per-domain seed importers in SVSim.Bootstrap.
var rotation = _config.Get<RotationConfig>();
var challenge = _config.Get<ChallengeConfig>();
var defaultLoadout = _config.Get<DefaultLoadoutConfig>();
@@ -324,7 +324,7 @@ public class LoadController : SVSimController
/// The client gates the Custom Rotation format-selector button on <c>FreeBattle</c>'s window
/// being currently open (Wizard/MyRotationAllInfo.cs:45), so a default-initialised
/// <c>DateTime.MinValue</c> pair here hides the button. Config defaults reproduce the
/// 2026-05-23 prod capture; GlobalsImporter overwrites from newer captures.
/// 2026-05-23 prod capture; the rotation-config seed file overwrites from newer captures.
/// </summary>
private SpecialRotationSchedule BuildMyRotationSchedules()
{

View File

@@ -24,7 +24,7 @@ public class PracticeController : SVSimController
/// <summary>
/// /practice/info — returns the AI opponent catalog. Response data is a JSON array
/// directly (not wrapped in an object), per spec. Backed by PracticeOpponents table,
/// seeded by SVSim.Bootstrap from prod-captures/practice-info-*.json.
/// seeded by SVSim.Bootstrap from seeds/practice-opponents.json.
/// </summary>
[HttpPost("info")]
public async Task<List<PracticeOpponent>> Info(BaseRequest request)

View File

@@ -6,8 +6,8 @@ namespace SVSim.EmulatedEntrypoint.Infrastructure;
/// <summary>
/// Shared System.Text.Json options for deserializing jsonb-passthrough columns into typed DTOs.
///
/// The prod-captured globals JSON was seeded with snake_case_lower keys (see SVSim.Bootstrap
/// GlobalsImporter — jsonb columns store the original capture verbatim). Deserialize-back must
/// The seed-driven globals JSON was written with snake_case_lower keys (see SVSim.Bootstrap
/// per-domain importers — jsonb columns store the original wire-shape verbatim). Deserialize-back must
/// use the same naming policy so e.g. `card_pool_name` maps onto `CardPoolName`.
///
/// AllowReadingFromString handles prod's PHP-backend convention of emitting numeric values

View File

@@ -105,7 +105,7 @@ public class MyPageIndexResponse
/// <summary>
/// banner is consumed by per-entry parsing inside a TryGetValue guard
/// (Wizard/MyPageBannerBase.BannerInfo.Parse iterates the array if present). We always emit
/// the list — empty when no rows have been imported. See SVSim.Bootstrap.GlobalsImporter.ImportBanners.
/// the list — empty when no rows have been imported. See SVSim.Bootstrap.MyPageGlobalsImporter.ImportBannersAsync.
/// </summary>
[JsonPropertyName("banner")]
[Key("banner")]

View File

@@ -229,7 +229,7 @@ public class LoadControllerTests
[Test]
public async Task Index_surfaces_seeded_globals_after_bootstrap()
{
// Verifies the end-to-end seed → repo → controller wiring for the prod-captured globals.
// Verifies the end-to-end seed → repo → controller wiring for the load-index globals.
// Counts and spot-checked values come from the 2026-05-23 capture; if a recapture lands
// with different cardinalities, update the assertions alongside.
using var factory = new SVSimTestFactory();
@@ -278,7 +278,7 @@ public class LoadControllerTests
Assert.That(mri.GetProperty("abilities").EnumerateObject().Count(), Is.EqualTo(6));
// my_rotation_info.schedules drives the client's "Custom Rotation" button visibility
// (Wizard/MyRotationAllInfo.cs:45 — IsMyRotationEnable). GlobalsImporter sources the
// (Wizard/MyRotationAllInfo.cs:45 — IsMyRotationEnable). RotationConfigImporter sources the
// window from the prod capture; default-initialised DateTime.MinValue values would hide
// the button. Assert the captured 2024→2030 free_battle window round-trips through the
// MyRotationScheduleConfig section.
@@ -299,7 +299,7 @@ public class LoadControllerTests
Assert.That(pri.GetProperty("id").GetString(), Is.EqualTo("1"));
// rotation_card_set_id_list: now comes from the real CardSets table — six entries after
// GlobalsImporter flags IsInRotation on the rotation_card_set_id_list seeded ids. But
// RotationFlagUpdater flags IsInRotation on the rotation_card_set_ids seeded list. But
// CardImport isn't run in tests, so the table is empty and we fall back to StubRotationSets
// (3 entries). That's still ≥ 2 so the client won't crash.
Assert.That(root.GetProperty("rotation_card_set_id_list").GetArrayLength(),

View File

@@ -12,10 +12,10 @@ namespace SVSim.UnitTests.Controllers;
/// Drives the importer + controller against the full production pack seed (35 packs). Guards
/// against regressions in either layer caused by future seed refreshes.
/// </summary>
public class PackControllerProdCaptureTests
public class PackControllerFullCatalogTests
{
[Test]
public async Task Info_returns_full_35_pack_catalog_from_prod_capture()
public async Task Info_returns_full_35_pack_catalog_from_production_seed()
{
// The production seed (packs.json) is overlaid by a 3-pack test fixture in the default test
// output dir (see SVSim.UnitTests.csproj). For this test we need the FULL 35-pack catalog,
@@ -29,7 +29,7 @@ public class PackControllerProdCaptureTests
File.Copy(prodSeed, Path.Combine(tempSeedDir, "packs.json"));
using var factory = new SVSimTestFactory();
// Run the default seed pipeline first so GlobalsImporter populates surrounding tables,
// Run the default seed pipeline first so the per-domain importers populate surrounding tables,
// then re-run PackImporter against the prod seed to overwrite the fixture-loaded packs.
await factory.SeedGlobalsAsync();
using (var scope = factory.Services.CreateScope())

View File

@@ -30,7 +30,7 @@ public class PracticeControllerTests
public async Task Info_returns_non_empty_opponent_array()
{
using var factory = new SVSimTestFactory();
// Practice opponents are bootstrapped from prod-captures/practice-info-*.json into the
// Practice opponents are bootstrapped from seeds/practice-opponents.json into the
// PracticeOpponents table — empty by default in tests, so seed first.
await factory.SeedGlobalsAsync();
long viewerId = await factory.SeedViewerAsync();

View File

@@ -6,13 +6,13 @@ using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Importers;
public class GlobalsImporterPackTests
public class PackSeedingPipelineTests
{
[Test]
public async Task ImportAll_loads_pack_catalog_from_fixture()
public async Task SeedGlobals_loads_pack_catalog_from_fixture()
{
using var factory = new SVSimTestFactory();
await factory.SeedGlobalsAsync(); // uses prod-captures fixture dir copied into test output
await factory.SeedGlobalsAsync(); // uses test-fixture seed overlay copied into the test output dir (see SVSim.UnitTests.csproj)
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
@@ -28,7 +28,7 @@ public class GlobalsImporterPackTests
}
[Test]
public async Task ImportAll_persists_child_gachas_with_correct_types_and_costs()
public async Task SeedGlobals_persists_child_gachas_with_correct_types_and_costs()
{
using var factory = new SVSimTestFactory();
await factory.SeedGlobalsAsync();
@@ -46,7 +46,7 @@ public class GlobalsImporterPackTests
}
[Test]
public async Task ImportAll_is_idempotent_on_rerun()
public async Task SeedGlobals_is_idempotent_on_rerun()
{
using var factory = new SVSimTestFactory();
await factory.SeedGlobalsAsync();

View File

@@ -5,7 +5,7 @@ using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Importers;
public class GlobalsImporterPuzzleTests
public class PuzzleSeedingPipelineTests
{
[Test]
public async Task ImportsAllPuzzleGroupsAndPuzzles()

View File

@@ -66,7 +66,7 @@ public class GameConfigurationJsonbTests
{
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
var rotation = await db.GameConfigs.FirstAsync(s => s.SectionName == "Rotation");
// Hydrate, mutate, re-serialise — same pattern GlobalsImporter and any admin-write
// Hydrate, mutate, re-serialise — the pattern RotationConfigImporter and any admin-write
// path will use.
var value = JsonSerializer.Deserialize<RotationConfig>(rotation.ValueJson)!;
value.TsRotationId = "99999";
@@ -130,7 +130,7 @@ public class GameConfigurationJsonbTests
}
[Test]
public async Task GlobalsImporter_updates_Rotation_without_clobbering_other_sections()
public async Task RotationConfigImporter_updates_Rotation_without_clobbering_other_sections()
{
using var factory = new SVSimTestFactory();
await factory.SeedGlobalsAsync(); // imports load-index which has ts_rotation_id="10015"
@@ -141,12 +141,12 @@ public class GameConfigurationJsonbTests
var rotation = JsonSerializer.Deserialize<RotationConfig>(
(await db.GameConfigs.FirstAsync(s => s.SectionName == "Rotation")).ValueJson)!;
Assert.That(rotation.TsRotationId, Is.EqualTo("10015"),
"GlobalsImporter should set Rotation.TsRotationId from the prod capture.");
"RotationConfigImporter should set Rotation.TsRotationId from the seed.");
// PackRates is NOT in the load-index capture; its row must keep ShippedDefaults values.
var packRates = JsonSerializer.Deserialize<PackRateConfig>(
(await db.GameConfigs.FirstAsync(s => s.SectionName == "PackRates")).ValueJson)!;
Assert.That(packRates.AnimatedRate, Is.EqualTo(0.08).Within(1e-9),
"GlobalsImporter must not clobber PackRates while updating Rotation.");
"RotationConfigImporter must not clobber PackRates while updating Rotation.");
}
}

View File

@@ -5,8 +5,8 @@ using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Repositories;
/// <summary>
/// End-to-end tests for the prod-captured globals path: SeedGlobalsAsync invokes
/// SVSim.Bootstrap.GlobalsImporter against the test SQLite DB, then we verify each
/// End-to-end tests for the seed-driven globals path: SeedGlobalsAsync runs the per-domain
/// importers against the test SQLite DB, then we verify each
/// IGlobalsRepository method returns the expected count + a spot-checked field value.
///
/// Counts come from the 2026-05-23 prod capture; if a recapture lands with different cardinalities,