Files
SVSimServer/SVSim.UnitTests/Importers/PuzzleImporterTests.cs
gamer147 0da8ebe1c1 refactor(bootstrap): migrate basic puzzles to seed files
Replaces GlobalsImporter's ImportPuzzleGroups/Puzzles/Missions methods (plus the
DeriveTargetPuzzleGroupId regex helper) with a dedicated PuzzleImporter that
reads three flat seed JSONs (puzzle-groups, puzzles, puzzle-missions) produced
by the Python extractor. Groups run before puzzles to satisfy the FK; missions
upsert by sequential id. Wired into Program.cs and SVSimTestFactory after
PaymentItemImporter so existing GlobalsImporterPuzzleTests continue to pass
unchanged via SeedGlobalsAsync. The original prod-capture JSONs are deleted now
that the seeds are authoritative.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 14:16:32 -04:00

155 lines
6.1 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SVSim.Bootstrap.Importers;
using SVSim.Database;
using SVSim.Database.Models;
using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Importers;
public class PuzzleImporterTests
{
private static string SeedDir => Path.Combine(AppContext.BaseDirectory, "Data", "seeds");
[Test]
public async Task ImportsGroups_PuzzlesAndMissions_from_seed_files()
{
using var factory = new SVSimTestFactory();
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
var importer = new PuzzleImporter();
await importer.ImportGroupsAsync(db, SeedDir);
await importer.ImportPuzzlesAsync(db, SeedDir);
await importer.ImportMissionsAsync(db, SeedDir);
int groupCount = await db.PuzzleGroups.CountAsync();
int puzzleCount = await db.Puzzles.CountAsync();
int missionCount = await db.PuzzleMissions.CountAsync();
Assert.That(groupCount, Is.GreaterThan(0), "seed must contain groups");
Assert.That(puzzleCount, Is.GreaterThan(0), "seed must contain puzzles");
Assert.That(missionCount, Is.GreaterThan(0), "seed must contain missions");
// Every puzzle's GroupId must reference an existing group (FK satisfied).
var groupIds = await db.PuzzleGroups.Select(g => g.Id).ToListAsync();
var groupIdSet = new HashSet<int>(groupIds);
var puzzleGroupIds = await db.Puzzles.Select(p => p.GroupId).Distinct().ToListAsync();
foreach (var gid in puzzleGroupIds)
{
Assert.That(groupIdSet, Does.Contain(gid),
$"puzzle references unknown group_id={gid}");
}
}
[Test]
public async Task Is_idempotent_on_rerun()
{
using var factory = new SVSimTestFactory();
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
var importer = new PuzzleImporter();
await importer.ImportGroupsAsync(db, SeedDir);
await importer.ImportPuzzlesAsync(db, SeedDir);
await importer.ImportMissionsAsync(db, SeedDir);
int g1 = await db.PuzzleGroups.CountAsync();
int p1 = await db.Puzzles.CountAsync();
int m1 = await db.PuzzleMissions.CountAsync();
await importer.ImportGroupsAsync(db, SeedDir);
await importer.ImportPuzzlesAsync(db, SeedDir);
await importer.ImportMissionsAsync(db, SeedDir);
Assert.That(await db.PuzzleGroups.CountAsync(), Is.EqualTo(g1));
Assert.That(await db.Puzzles.CountAsync(), Is.EqualTo(p1));
Assert.That(await db.PuzzleMissions.CountAsync(), Is.EqualTo(m1));
}
[Test]
public async Task Leaves_existing_rows_untouched_when_missing_from_seed()
{
using var factory = new SVSimTestFactory();
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
const int legacyGroupId = 99999;
const int legacyPuzzleId = 99998;
const int legacyMissionId = 99997;
db.PuzzleGroups.Add(new PuzzleGroupEntry
{
Id = legacyGroupId,
BasicTitleTextId = "legacy_group",
DifficultyNameListJson = "{\"legacy\":\"1\"}",
});
db.Puzzles.Add(new PuzzleEntry
{
Id = legacyPuzzleId,
GroupId = legacyGroupId,
PuzzleDifficulty = 5,
ReleaseConditionTextId = "legacy_puzzle",
});
db.PuzzleMissions.Add(new PuzzleMissionEntry
{
Id = legacyMissionId,
MissionName = "legacy_mission",
AchievedMessage = "legacy_achieved",
RequireNumber = 42,
});
await db.SaveChangesAsync();
var importer = new PuzzleImporter();
await importer.ImportGroupsAsync(db, SeedDir);
await importer.ImportPuzzlesAsync(db, SeedDir);
await importer.ImportMissionsAsync(db, SeedDir);
var g = await db.PuzzleGroups.FindAsync(legacyGroupId);
Assert.That(g, Is.Not.Null);
Assert.That(g!.BasicTitleTextId, Is.EqualTo("legacy_group"));
var p = await db.Puzzles.FindAsync(legacyPuzzleId);
Assert.That(p, Is.Not.Null);
Assert.That(p!.PuzzleDifficulty, Is.EqualTo(5));
var m = await db.PuzzleMissions.FindAsync(legacyMissionId);
Assert.That(m, Is.Not.Null);
Assert.That(m!.MissionName, Is.EqualTo("legacy_mission"));
Assert.That(m.RequireNumber, Is.EqualTo(42));
}
[Test]
public async Task Skips_rows_with_zero_id()
{
using var factory = new SVSimTestFactory();
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
string tmp = Path.Combine(Path.GetTempPath(), $"seed-{Guid.NewGuid()}");
Directory.CreateDirectory(tmp);
try
{
File.WriteAllText(Path.Combine(tmp, "puzzle-groups.json"),
"[{\"id\":0,\"basic_title_text_id\":\"junk\"}]");
File.WriteAllText(Path.Combine(tmp, "puzzles.json"),
"[{\"id\":0,\"group_id\":1,\"puzzle_difficulty\":1}]");
File.WriteAllText(Path.Combine(tmp, "puzzle-missions.json"),
"[{\"id\":0,\"mission_name\":\"junk\"}]");
var importer = new PuzzleImporter();
await importer.ImportGroupsAsync(db, tmp);
await importer.ImportPuzzlesAsync(db, tmp);
await importer.ImportMissionsAsync(db, tmp);
Assert.That(await db.PuzzleGroups.CountAsync(), Is.EqualTo(0),
"rows with id=0 must not be inserted into groups");
Assert.That(await db.Puzzles.CountAsync(), Is.EqualTo(0),
"rows with id=0 must not be inserted into puzzles");
Assert.That(await db.PuzzleMissions.CountAsync(), Is.EqualTo(0),
"rows with id=0 must not be inserted into missions");
}
finally { Directory.Delete(tmp, true); }
}
}