chore(bootstrap): refresh stale GlobalsImporter references in docs/test names
This commit is contained in:
@@ -10,9 +10,8 @@ namespace SVSim.Bootstrap.Importers;
|
|||||||
/// Idempotent upsert of the six card-id-keyed tables from load-index seeds:
|
/// Idempotent upsert of the six card-id-keyed tables from load-index seeds:
|
||||||
/// SpotCards, ReprintedCards, UnlimitedRestrictions, LoadingExclusionCards,
|
/// SpotCards, ReprintedCards, UnlimitedRestrictions, LoadingExclusionCards,
|
||||||
/// MaintenanceCards, FeatureMaintenances. Loads the Cards FK set once for orphan warnings.
|
/// MaintenanceCards, FeatureMaintenances. Loads the Cards FK set once for orphan warnings.
|
||||||
/// Rows missing from a seed are LEFT INTACT (consistent with prior GlobalsImporter behavior)
|
/// Rows missing from a seed are LEFT INTACT — a partial seed shouldn't silently delete entries.
|
||||||
/// for the five card-id-keyed tables; FeatureMaintenances clears-and-rewrites because its
|
/// FeatureMaintenances clears-and-rewrites because its synthetic ordinal Id has no natural-key semantics.
|
||||||
/// synthetic ordinal Id has no natural-key semantics.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CardListsImporter
|
public class CardListsImporter
|
||||||
{
|
{
|
||||||
@@ -136,10 +135,10 @@ public class CardListsImporter
|
|||||||
var seed = SeedLoader.LoadList<FeatureMaintenanceSeed>(Path.Combine(seedDir, "feature-maintenances.json"));
|
var seed = SeedLoader.LoadList<FeatureMaintenanceSeed>(Path.Combine(seedDir, "feature-maintenances.json"));
|
||||||
if (seed.Count == 0) return 0;
|
if (seed.Count == 0) return 0;
|
||||||
// FeatureMaintenances has a synthetic int Id assigned by the extractor (1-based ordinal).
|
// FeatureMaintenances has a synthetic int Id assigned by the extractor (1-based ordinal).
|
||||||
// The original GlobalsImporter.ImportFeatureMaintenances added rows without dedup; since the
|
// FeatureMaintenances use a synthetic ordinal id from the extractor; we clear-and-rewrite to
|
||||||
// seed is regenerated on every extract, clear-and-rewrite keeps re-runs idempotent and
|
// keep re-runs idempotent and match "the latest seed is authoritative". Pre-existing rows
|
||||||
// matches "the latest seed is authoritative". Pre-existing rows with seed-absent ids are
|
// with seed-absent ids are dropped here (acceptable: only synthetic ordinals, no FKs
|
||||||
// dropped here (acceptable: only synthetic ordinals, no FKs reference this table).
|
// reference this table).
|
||||||
var existing = await context.FeatureMaintenances.ToListAsync();
|
var existing = await context.FeatureMaintenances.ToListAsync();
|
||||||
context.FeatureMaintenances.RemoveRange(existing);
|
context.FeatureMaintenances.RemoveRange(existing);
|
||||||
int created = 0;
|
int created = 0;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace SVSim.Bootstrap.Importers;
|
|||||||
/// Reads <see cref="RotationConfig"/> from the GameConfigs table (populated by
|
/// Reads <see cref="RotationConfig"/> from the GameConfigs table (populated by
|
||||||
/// <see cref="RotationConfigImporter"/>) and flips <c>CardSet.IsInRotation</c> to match.
|
/// <see cref="RotationConfigImporter"/>) and flips <c>CardSet.IsInRotation</c> to match.
|
||||||
/// Must run after RotationConfigImporter and CardImporter — CardSets missing from the DB
|
/// 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>
|
/// </summary>
|
||||||
public class RotationFlagUpdater
|
public class RotationFlagUpdater
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public static class Program
|
|||||||
await using var context = new SVSimDbContext(NullLogger<SVSimDbContext>.Instance, dbOptions);
|
await using var context = new SVSimDbContext(NullLogger<SVSimDbContext>.Instance, dbOptions);
|
||||||
|
|
||||||
// Bootstrap applies pending migrations first — migrations are now DDL-only, all data
|
// 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;
|
// is loaded by importers below. This means a freshly migrated DB is structure-only;
|
||||||
// every importer is idempotent so re-running is safe.
|
// every importer is idempotent so re-running is safe.
|
||||||
Console.WriteLine("[Bootstrap] Applying pending migrations...");
|
Console.WriteLine("[Bootstrap] Applying pending migrations...");
|
||||||
@@ -75,10 +75,8 @@ public static class Program
|
|||||||
|
|
||||||
if (!opts.SkipGlobals)
|
if (!opts.SkipGlobals)
|
||||||
{
|
{
|
||||||
// Per-domain seed pipeline. The legacy GlobalsImporter that parsed prod-captured
|
// Per-domain seed pipeline. Each importer reads a per-table JSON seed file under
|
||||||
// /load/index, /mypage/index, /deck/info wire payloads directly is gone — capture
|
// SVSim.Bootstrap/Data/seeds/ produced by an extractor in data_dumps/extract/.
|
||||||
// → seed transformation lives in data_dumps/extract/*; importers below just
|
|
||||||
// deserialise the per-table JSON files in SVSim.Bootstrap/Data/seeds/.
|
|
||||||
//
|
//
|
||||||
// RotationConfigImporter writes the Rotation GameConfig section that RotationFlagUpdater
|
// RotationConfigImporter writes the Rotation GameConfig section that RotationFlagUpdater
|
||||||
// reads; CardImporter ran earlier in the !SkipCards block so CardSets are populated.
|
// reads; CardImporter ran earlier in the !SkipCards block so CardSets are populated.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace SVSim.Database.Models.Config;
|
|||||||
/// Mapped to the wire-shape <c>SpecialRotationSchedule</c> at the controller seam.
|
/// Mapped to the wire-shape <c>SpecialRotationSchedule</c> at the controller seam.
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Shipped defaults reproduce the 2026-05-23 prod capture so a fresh install ships with the
|
/// 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>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ConfigSection("MyRotationSchedule")]
|
[ConfigSection("MyRotationSchedule")]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace SVSim.Database.Models;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// One row per AI opponent shown on the practice (solo-play) opponent select screen.
|
/// 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
|
/// 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
|
/// baked-in master CSV `ai/practice_ai_setting`; if it doesn't, the client's
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace SVSim.Database.Models;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// One row per basic_puzzle group (puzzle_master_id). Static catalog seeded by
|
/// 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.
|
/// See docs/api-spec/endpoints/post-login/basic-puzzle/info.md.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PuzzleGroupEntry : BaseEntity<int>
|
public class PuzzleGroupEntry : BaseEntity<int>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace SVSim.Database.Models;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// One row per basic_puzzle mission (e.g. "Clear all Round 1 puzzles"). Static catalog
|
/// 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"/>.
|
/// 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.
|
/// See docs/api-spec/endpoints/post-login/basic-puzzle/mission.md.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public interface IGlobalsRepository
|
|||||||
Task<List<BattlefieldEntry>> GetBattlefields(bool onlyOpen);
|
Task<List<BattlefieldEntry>> GetBattlefields(bool onlyOpen);
|
||||||
Task<List<RankInfoEntry>> GetRankInfo();
|
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<MyRotationSettingEntry>> GetMyRotationSettings();
|
||||||
Task<List<MyRotationAbilityEntry>> GetMyRotationAbilities();
|
Task<List<MyRotationAbilityEntry>> GetMyRotationAbilities();
|
||||||
Task<List<AvatarAbilityEntry>> GetAvatarAbilities();
|
Task<List<AvatarAbilityEntry>> GetAvatarAbilities();
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class LoadController : SVSimController
|
|||||||
|
|
||||||
// Defense-in-depth: client unconditionally accesses RotationCardSetList[1] and [Count-1]
|
// 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
|
// (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
|
// 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.
|
// least loads. Removing this is only safe once viewer-side bootstrap is unconditional.
|
||||||
private static readonly List<CardSetIdentifier> StubRotationSets = new()
|
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
|
// Globals — one cached fetch per slice. The Rotation/Challenge/DefaultLoadout sections
|
||||||
// come via IGameConfigService (DB → appsettings → ShippedDefaults). Other repo methods
|
// 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 rotation = _config.Get<RotationConfig>();
|
||||||
var challenge = _config.Get<ChallengeConfig>();
|
var challenge = _config.Get<ChallengeConfig>();
|
||||||
var defaultLoadout = _config.Get<DefaultLoadoutConfig>();
|
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
|
/// 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
|
/// being currently open (Wizard/MyRotationAllInfo.cs:45), so a default-initialised
|
||||||
/// <c>DateTime.MinValue</c> pair here hides the button. Config defaults reproduce the
|
/// <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>
|
/// </summary>
|
||||||
private SpecialRotationSchedule BuildMyRotationSchedules()
|
private SpecialRotationSchedule BuildMyRotationSchedules()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class PracticeController : SVSimController
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// /practice/info — returns the AI opponent catalog. Response data is a JSON array
|
/// /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,
|
/// 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>
|
/// </summary>
|
||||||
[HttpPost("info")]
|
[HttpPost("info")]
|
||||||
public async Task<List<PracticeOpponent>> Info(BaseRequest request)
|
public async Task<List<PracticeOpponent>> Info(BaseRequest request)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ namespace SVSim.EmulatedEntrypoint.Infrastructure;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shared System.Text.Json options for deserializing jsonb-passthrough columns into typed DTOs.
|
/// 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
|
/// The seed-driven globals JSON was written with snake_case_lower keys (see SVSim.Bootstrap
|
||||||
/// GlobalsImporter — jsonb columns store the original capture verbatim). Deserialize-back must
|
/// 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`.
|
/// 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
|
/// AllowReadingFromString handles prod's PHP-backend convention of emitting numeric values
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ public class MyPageIndexResponse
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// banner is consumed by per-entry parsing inside a TryGetValue guard
|
/// banner is consumed by per-entry parsing inside a TryGetValue guard
|
||||||
/// (Wizard/MyPageBannerBase.BannerInfo.Parse iterates the array if present). We always emit
|
/// (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>
|
/// </summary>
|
||||||
[JsonPropertyName("banner")]
|
[JsonPropertyName("banner")]
|
||||||
[Key("banner")]
|
[Key("banner")]
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ public class LoadControllerTests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task Index_surfaces_seeded_globals_after_bootstrap()
|
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
|
// Counts and spot-checked values come from the 2026-05-23 capture; if a recapture lands
|
||||||
// with different cardinalities, update the assertions alongside.
|
// with different cardinalities, update the assertions alongside.
|
||||||
using var factory = new SVSimTestFactory();
|
using var factory = new SVSimTestFactory();
|
||||||
@@ -278,7 +278,7 @@ public class LoadControllerTests
|
|||||||
Assert.That(mri.GetProperty("abilities").EnumerateObject().Count(), Is.EqualTo(6));
|
Assert.That(mri.GetProperty("abilities").EnumerateObject().Count(), Is.EqualTo(6));
|
||||||
|
|
||||||
// my_rotation_info.schedules drives the client's "Custom Rotation" button visibility
|
// 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
|
// 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
|
// the button. Assert the captured 2024→2030 free_battle window round-trips through the
|
||||||
// MyRotationScheduleConfig section.
|
// MyRotationScheduleConfig section.
|
||||||
@@ -299,7 +299,7 @@ public class LoadControllerTests
|
|||||||
Assert.That(pri.GetProperty("id").GetString(), Is.EqualTo("1"));
|
Assert.That(pri.GetProperty("id").GetString(), Is.EqualTo("1"));
|
||||||
|
|
||||||
// rotation_card_set_id_list: now comes from the real CardSets table — six entries after
|
// 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
|
// 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.
|
// (3 entries). That's still ≥ 2 so the client won't crash.
|
||||||
Assert.That(root.GetProperty("rotation_card_set_id_list").GetArrayLength(),
|
Assert.That(root.GetProperty("rotation_card_set_id_list").GetArrayLength(),
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ namespace SVSim.UnitTests.Controllers;
|
|||||||
/// Drives the importer + controller against the full production pack seed (35 packs). Guards
|
/// Drives the importer + controller against the full production pack seed (35 packs). Guards
|
||||||
/// against regressions in either layer caused by future seed refreshes.
|
/// against regressions in either layer caused by future seed refreshes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PackControllerProdCaptureTests
|
public class PackControllerFullCatalogTests
|
||||||
{
|
{
|
||||||
[Test]
|
[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
|
// 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,
|
// 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"));
|
File.Copy(prodSeed, Path.Combine(tempSeedDir, "packs.json"));
|
||||||
|
|
||||||
using var factory = new SVSimTestFactory();
|
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.
|
// then re-run PackImporter against the prod seed to overwrite the fixture-loaded packs.
|
||||||
await factory.SeedGlobalsAsync();
|
await factory.SeedGlobalsAsync();
|
||||||
using (var scope = factory.Services.CreateScope())
|
using (var scope = factory.Services.CreateScope())
|
||||||
@@ -30,7 +30,7 @@ public class PracticeControllerTests
|
|||||||
public async Task Info_returns_non_empty_opponent_array()
|
public async Task Info_returns_non_empty_opponent_array()
|
||||||
{
|
{
|
||||||
using var factory = new SVSimTestFactory();
|
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.
|
// PracticeOpponents table — empty by default in tests, so seed first.
|
||||||
await factory.SeedGlobalsAsync();
|
await factory.SeedGlobalsAsync();
|
||||||
long viewerId = await factory.SeedViewerAsync();
|
long viewerId = await factory.SeedViewerAsync();
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ using SVSim.UnitTests.Infrastructure;
|
|||||||
|
|
||||||
namespace SVSim.UnitTests.Importers;
|
namespace SVSim.UnitTests.Importers;
|
||||||
|
|
||||||
public class GlobalsImporterPackTests
|
public class PackSeedingPipelineTests
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public async Task ImportAll_loads_pack_catalog_from_fixture()
|
public async Task SeedGlobals_loads_pack_catalog_from_fixture()
|
||||||
{
|
{
|
||||||
using var factory = new SVSimTestFactory();
|
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();
|
using var scope = factory.Services.CreateScope();
|
||||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||||
@@ -28,7 +28,7 @@ public class GlobalsImporterPackTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[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();
|
using var factory = new SVSimTestFactory();
|
||||||
await factory.SeedGlobalsAsync();
|
await factory.SeedGlobalsAsync();
|
||||||
@@ -46,7 +46,7 @@ public class GlobalsImporterPackTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task ImportAll_is_idempotent_on_rerun()
|
public async Task SeedGlobals_is_idempotent_on_rerun()
|
||||||
{
|
{
|
||||||
using var factory = new SVSimTestFactory();
|
using var factory = new SVSimTestFactory();
|
||||||
await factory.SeedGlobalsAsync();
|
await factory.SeedGlobalsAsync();
|
||||||
@@ -5,7 +5,7 @@ using SVSim.UnitTests.Infrastructure;
|
|||||||
|
|
||||||
namespace SVSim.UnitTests.Importers;
|
namespace SVSim.UnitTests.Importers;
|
||||||
|
|
||||||
public class GlobalsImporterPuzzleTests
|
public class PuzzleSeedingPipelineTests
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public async Task ImportsAllPuzzleGroupsAndPuzzles()
|
public async Task ImportsAllPuzzleGroupsAndPuzzles()
|
||||||
@@ -66,7 +66,7 @@ public class GameConfigurationJsonbTests
|
|||||||
{
|
{
|
||||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||||
var rotation = await db.GameConfigs.FirstAsync(s => s.SectionName == "Rotation");
|
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.
|
// path will use.
|
||||||
var value = JsonSerializer.Deserialize<RotationConfig>(rotation.ValueJson)!;
|
var value = JsonSerializer.Deserialize<RotationConfig>(rotation.ValueJson)!;
|
||||||
value.TsRotationId = "99999";
|
value.TsRotationId = "99999";
|
||||||
@@ -130,7 +130,7 @@ public class GameConfigurationJsonbTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[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();
|
using var factory = new SVSimTestFactory();
|
||||||
await factory.SeedGlobalsAsync(); // imports load-index which has ts_rotation_id="10015"
|
await factory.SeedGlobalsAsync(); // imports load-index which has ts_rotation_id="10015"
|
||||||
@@ -141,12 +141,12 @@ public class GameConfigurationJsonbTests
|
|||||||
var rotation = JsonSerializer.Deserialize<RotationConfig>(
|
var rotation = JsonSerializer.Deserialize<RotationConfig>(
|
||||||
(await db.GameConfigs.FirstAsync(s => s.SectionName == "Rotation")).ValueJson)!;
|
(await db.GameConfigs.FirstAsync(s => s.SectionName == "Rotation")).ValueJson)!;
|
||||||
Assert.That(rotation.TsRotationId, Is.EqualTo("10015"),
|
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.
|
// PackRates is NOT in the load-index capture; its row must keep ShippedDefaults values.
|
||||||
var packRates = JsonSerializer.Deserialize<PackRateConfig>(
|
var packRates = JsonSerializer.Deserialize<PackRateConfig>(
|
||||||
(await db.GameConfigs.FirstAsync(s => s.SectionName == "PackRates")).ValueJson)!;
|
(await db.GameConfigs.FirstAsync(s => s.SectionName == "PackRates")).ValueJson)!;
|
||||||
Assert.That(packRates.AnimatedRate, Is.EqualTo(0.08).Within(1e-9),
|
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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ using SVSim.UnitTests.Infrastructure;
|
|||||||
namespace SVSim.UnitTests.Repositories;
|
namespace SVSim.UnitTests.Repositories;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// End-to-end tests for the prod-captured globals path: SeedGlobalsAsync invokes
|
/// End-to-end tests for the seed-driven globals path: SeedGlobalsAsync runs the per-domain
|
||||||
/// SVSim.Bootstrap.GlobalsImporter against the test SQLite DB, then we verify each
|
/// importers against the test SQLite DB, then we verify each
|
||||||
/// IGlobalsRepository method returns the expected count + a spot-checked field value.
|
/// 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,
|
/// Counts come from the 2026-05-23 prod capture; if a recapture lands with different cardinalities,
|
||||||
|
|||||||
Reference in New Issue
Block a user