diff --git a/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs b/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs new file mode 100644 index 000000000..f5b0d5851 --- /dev/null +++ b/tests/Aetheria.Shared.Tests/PiratesFactionCommandTests.cs @@ -0,0 +1,89 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using System.IO; +using System.Linq; +using Xunit; + +// AetherDb's pirates-faction command (aetheria-release, cut pirates-record), run on a scratch copy of the shipped catalog +// with the Pirates removed from it. +public sealed class PiratesFactionCommandTests : IDisposable +{ + private readonly string _root = Path.Combine(Path.GetTempPath(), "pirates-command-" + Guid.NewGuid().ToString("N")); + private string CatalogPath => Path.Combine(_root, "GameData", "Aetheria.cc"); + + public PiratesFactionCommandTests() + { + Directory.CreateDirectory(Path.Combine(_root, "GameData")); + File.Copy(Path.Combine(AetherDb.FindRoot(), "GameData", "Aetheria.cc"), CatalogPath); + var db = AetherDb.Open(catalogWritable: true, root: _root); + var pirates = db.Cache.GetAll().Single(faction => faction.Name == "Pirates"); + var ours = db.Cache.RefOf(pirates); + foreach (var product in db.Cache.GetAll().Where(product => product.Manufacturer.Equals(ours)).ToArray()) + Assert.True(db.Cache.Remove(db.Cache.RefOf(product).Key)); + Assert.True(db.Cache.Remove(ours.Key)); + db.Cache.Dispose(); + } + + public void Dispose() => Directory.Delete(_root, true); + + private (Faction[] Pirates, FactionProductData[] Products) Read() + { + var db = AetherDb.Open(root: _root); + try + { + var pirates = db.Cache.GetAll().Where(faction => faction.Name == "Pirates").ToArray(); + var products = pirates.Length == 0 ? new FactionProductData[0] + : db.Cache.GetAll().Where(product => product.Manufacturer.Equals(db.Cache.RefOf(pirates[0]))).ToArray(); + return (pirates, products); + } + finally { db.Cache.Dispose(); } + } + + [Fact] + public void A_dry_run_writes_nothing_and_apply_lands_the_record_once() + { + var before = File.ReadAllBytes(CatalogPath); + Assert.Equal(0, Program.PiratesFaction(apply: false, root: _root)); + Assert.Equal(before, File.ReadAllBytes(CatalogPath)); + Assert.Empty(Read().Pirates); + + Assert.Equal(0, Program.PiratesFaction(apply: true, root: _root)); + var landed = File.ReadAllBytes(CatalogPath); + var (pirates, products) = Read(); + Assert.Single(pirates); + Assert.Equal(8, products.Length); + + // A second apply finds the faction and changes nothing. + Assert.Equal(0, Program.PiratesFaction(apply: true, root: _root)); + Assert.Equal(landed, File.ReadAllBytes(CatalogPath)); + } + + [Fact] + public void An_existing_Pirates_faction_is_left_as_it_is() + { + var db = AetherDb.Open(catalogWritable: true, root: _root); + db.Cache.Commit(batch => batch.Upsert(typeof(Faction), new Faction { Name = "Pirates", ShortName = "Mine", Description = "authored by hand" })); + db.Cache.Dispose(); + + Assert.Equal(0, Program.PiratesFaction(apply: true, root: _root)); + var (pirates, products) = Read(); + Assert.Equal("authored by hand", Assert.Single(pirates).Description); + Assert.Empty(products); + } + + [Fact] + public void A_missing_design_refuses_and_lands_nothing() + { + var db = AetherDb.Open(catalogWritable: true, root: _root); + var autocannon = db.Cache.GetAll().Single(weapon => weapon.Name == "Autocannon"); + autocannon.Name = "Autocannon (renamed)"; + db.Cache.Commit(batch => batch.Upsert(typeof(WeaponItemData), autocannon, db.Cache.RefOf(autocannon).Key)); + db.Cache.Dispose(); + + Assert.Throws(() => Program.PiratesFaction(apply: true, root: _root)); + Assert.Empty(Read().Pirates); + } +} diff --git a/tools/AetherDb/Program.cs b/tools/AetherDb/Program.cs index d1b488c7a..de1319acf 100644 --- a/tools/AetherDb/Program.cs +++ b/tools/AetherDb/Program.cs @@ -38,7 +38,9 @@ public static class Program case "targeting-catalog-6d": return TargetingCatalog6d(args.Contains("apply")); case "ship-authoring": return ShipAuthoringCommands.Run(args.Skip(1).ToArray()); case "field-kinds": return FieldKindsCatalog(args.Contains("apply")); - case "pirates-faction": return PiratesFaction(args.Contains("apply")); + case "pirates-faction": + Console.OutputEncoding = new System.Text.UTF8Encoding(false); + return PiratesFaction(args.Contains("apply")); default: Console.WriteLine("commands: census, factions, station-fit, hardpoint-fit, loadout [seed], save, settings, settings-dump, dangling [clear ]... [apply], shield-migrate [apply], brownout-migrate [apply], roles-migrate [apply], firing-arc-migrate [apply], targeting-catalog [apply], targeting-catalog-6c [apply], targeting-catalog-6d [apply], ship-authoring create|inspect|validate|compose, field-kinds [apply], pirates-faction [apply]"); return 1; @@ -1514,10 +1516,9 @@ public static class Program // only, written here as \U escapes so no editor or shell mangles them; the dry run prints them raw and as code points. // // Dry run unless passed "apply", same contract as the other *-migrate commands. - private static int PiratesFaction(bool apply) + public static int PiratesFaction(bool apply, string root = null) { - Console.OutputEncoding = new System.Text.UTF8Encoding(false); - var db = AetherDb.Open(catalogWritable: apply); + var db = AetherDb.Open(catalogWritable: apply, root: root); var existing = db.Cache.GetAll().FirstOrDefault(f => f.Name == "Pirates"); if (existing != null) @@ -1571,6 +1572,7 @@ public static class Program }).ToArray(); Console.WriteLine($"New faction \"{pirates.Name}\": influence {pirates.InfluenceDistance}, names \"{nameFile.Name}\", allegiance 1.0 to {pirates.Allegiance.Count} factions."); + Console.WriteLine($" colours: primary ({pirates.PrimaryColor.x:0.00}, {pirates.PrimaryColor.y:0.00}, {pirates.PrimaryColor.z:0.00}) primer grey, secondary ({pirates.SecondaryColor.x:0.00}, {pirates.SecondaryColor.y:0.00}, {pirates.SecondaryColor.z:0.00}) hazard orange"); Console.WriteLine($" {pirates.Description}"); Console.WriteLine($"\nNew products ({products.Length}):"); foreach (var (design, name, description, roles) in products)