From 0b4147496811bc6761c53288304978f083b01c0f Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 26 May 2026 13:52:12 -0400 Subject: [PATCH] fix(practice-opponents): guard duplicate ids + cover edge cases --- .../Importers/PracticeOpponentImporter.cs | 7 ++- .../PracticeOpponentImporterTests.cs | 53 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/SVSim.Bootstrap/Importers/PracticeOpponentImporter.cs b/SVSim.Bootstrap/Importers/PracticeOpponentImporter.cs index 81e6a1f..e6ea0ca 100644 --- a/SVSim.Bootstrap/Importers/PracticeOpponentImporter.cs +++ b/SVSim.Bootstrap/Importers/PracticeOpponentImporter.cs @@ -43,7 +43,12 @@ public class PracticeOpponentImporter entry.IsMaintenance = s.IsMaintenance; entry.IsCampaignPractice = s.IsCampaignPractice; - if (ex is null) { context.PracticeOpponents.Add(entry); created++; } + if (ex is null) + { + context.PracticeOpponents.Add(entry); + existing[s.PracticeId] = entry; + created++; + } else updated++; } diff --git a/SVSim.UnitTests/Importers/PracticeOpponentImporterTests.cs b/SVSim.UnitTests/Importers/PracticeOpponentImporterTests.cs index 3f1ed68..2bd8597 100644 --- a/SVSim.UnitTests/Importers/PracticeOpponentImporterTests.cs +++ b/SVSim.UnitTests/Importers/PracticeOpponentImporterTests.cs @@ -38,4 +38,57 @@ public class PracticeOpponentImporterTests Assert.That(after, Is.EqualTo(before)); } + + [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(); + + // Pre-seed an opponent the production seed file doesn't contain. + const int legacyId = 9999; + db.PracticeOpponents.Add(new SVSim.Database.Models.PracticeOpponentEntry + { + Id = legacyId, + TextId = "legacy", + ClassId = 1, + CharaId = 1, + DegreeId = 1, + AiDeckLevel = 1, + AiLogicLevel = 1, + AiMaxLife = 20, + Battle3dFieldId = "1", + }); + await db.SaveChangesAsync(); + + await new PracticeOpponentImporter().ImportAsync(db, SeedDir); + + var legacy = await db.PracticeOpponents.FindAsync(legacyId); + Assert.That(legacy, Is.Not.Null, "seed-missing row must be left intact"); + Assert.That(legacy!.TextId, Is.EqualTo("legacy"), "pre-existing values must not be wiped"); + } + + [Test] + public async Task Skips_rows_with_zero_practice_id() + { + using var factory = new SVSimTestFactory(); + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + // Write a one-row seed with PracticeId=0 to a temp dir and confirm it doesn't insert. + string tmp = Path.Combine(Path.GetTempPath(), $"seed-{Guid.NewGuid()}"); + Directory.CreateDirectory(tmp); + try + { + File.WriteAllText(Path.Combine(tmp, "practice-opponents.json"), + "[{\"practice_id\":0,\"text_id\":\"junk\",\"class_id\":1}]"); + + await new PracticeOpponentImporter().ImportAsync(db, tmp); + + int count = await db.PracticeOpponents.CountAsync(); + Assert.That(count, Is.EqualTo(0), "rows with practice_id=0 must not be inserted"); + } + finally { Directory.Delete(tmp, true); } + } }