diff --git a/tests/Aetheria.Shared.Tests/ConsumablesFirstSetCommandTests.cs b/tests/Aetheria.Shared.Tests/ConsumablesFirstSetCommandTests.cs new file mode 100644 index 000000000..db8e59f32 --- /dev/null +++ b/tests/Aetheria.Shared.Tests/ConsumablesFirstSetCommandTests.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Linq; +using GameCult.Caching; +using GameCult.Caching.MessagePack; +using Xunit; + +// AetherDb's consumables-first-set command (aetheria-release, cut consumables-first-set), run on a scratch copy of the +// shipped catalog with the two designs and their four products removed from it. +public sealed class ConsumablesFirstSetCommandTests : IDisposable +{ + private static readonly string[] DesignNames = { "Thruster Overdrive", "Coolant Vent" }; + + private readonly string _root = Path.Combine(Path.GetTempPath(), "consumables-command-" + Guid.NewGuid().ToString("N")); + private string CatalogPath => Path.Combine(_root, "GameData", "Aetheria.cc"); + + public ConsumablesFirstSetCommandTests() + { + Directory.CreateDirectory(Path.Combine(_root, "GameData")); + Directory.CreateDirectory(Path.Combine(_root, "Assets", "Resources")); + var shipped = AetherDb.FindRoot(); + File.Copy(Path.Combine(shipped, "GameData", "Aetheria.cc"), CatalogPath); + File.Copy(Path.Combine(shipped, "Assets", "Resources", "Settings.asset"), Path.Combine(_root, "Assets", "Resources", "Settings.asset")); + // Stripped through a registry scoped to the shipped assembly, as PiratesFactionCommandTests does. + using var cache = new CultCache(TestCatalog.Registry()); + cache.AddBackingStore(new SingleFileMessagePackBackingStore(CatalogPath), AetheriaStores.CatalogTypes); + var designs = cache.GetAll().Where(c => DesignNames.Contains(c.Name)).ToArray(); + Assert.Equal(DesignNames.Length, designs.Length); + var keys = designs.Select(d => cache.RefOf(d).Key).ToArray(); + foreach (var product in cache.GetAll().Where(p => keys.Any(k => p.Design.Key.Equals(k))).ToArray()) + Assert.True(cache.Remove(cache.RefOf(product).Key)); + foreach (var key in keys) Assert.True(cache.Remove(key)); + cache.FlushAsync().Wait(); + } + + public void Dispose() => Directory.Delete(_root, true); + + private (ConsumableItemData[] Designs, FactionProductData[] Products) Read() + { + var db = AetherDb.Open(root: _root); + try + { + var designs = db.Cache.GetAll().Where(c => DesignNames.Contains(c.Name)).ToArray(); + var products = db.Cache.GetAll().Where(p => designs.Any(d => p.Design.Key.Equals(db.Cache.RefOf(d).Key))).ToArray(); + return (designs, products); + } + finally { db.Cache.Dispose(); } + } + + [Fact] + public void A_dry_run_writes_nothing_and_apply_lands_the_designs_once() + { + var before = File.ReadAllBytes(CatalogPath); + Assert.Empty(Read().Designs); + + Assert.Equal(0, Program.ConsumablesFirstSetCatalog(apply: false, root: _root)); + Assert.Equal(before, File.ReadAllBytes(CatalogPath)); + Assert.Empty(Read().Designs); + + Assert.Equal(0, Program.ConsumablesFirstSetCatalog(apply: true, root: _root)); + var landed = File.ReadAllBytes(CatalogPath); + Assert.NotEqual(before, landed); + var (designs, products) = Read(); + Assert.Equal(DesignNames.OrderBy(n => n, StringComparer.Ordinal), designs.Select(d => d.Name).OrderBy(n => n, StringComparer.Ordinal)); + Assert.Equal(4, products.Length); + + // A second apply finds both designs authored and writes nothing; so does a dry run. + Assert.Equal(0, Program.ConsumablesFirstSetCatalog(apply: true, root: _root)); + Assert.Equal(landed, File.ReadAllBytes(CatalogPath)); + Assert.Equal(0, Program.ConsumablesFirstSetCatalog(apply: false, root: _root)); + Assert.Equal(landed, File.ReadAllBytes(CatalogPath)); + var (designsAgain, productsAgain) = Read(); + Assert.Equal(2, designsAgain.Length); + Assert.Equal(4, productsAgain.Length); + } +} diff --git a/tests/Aetheria.Shared.Tests/ConsumablesFirstSetTests.cs b/tests/Aetheria.Shared.Tests/ConsumablesFirstSetTests.cs index 9befc3292..c96209cca 100644 --- a/tests/Aetheria.Shared.Tests/ConsumablesFirstSetTests.cs +++ b/tests/Aetheria.Shared.Tests/ConsumablesFirstSetTests.cs @@ -14,6 +14,8 @@ public sealed class ConsumablesFirstSetTests { private const string OverdriveName = "Thruster Overdrive"; private const string VentName = "Coolant Vent"; + private const float SpecificHeat = -1f; + private const float Conductivity = -1f; private const float Tick = .25f; // exact in floats, so a duration of 6 s is 24 ticks and 15 s is 60 private static ConsumableItemData Design(CultCache cache, string name) => Assert.Single(cache.GetAll(), c => c.Name == name); @@ -25,6 +27,7 @@ public sealed class ConsumablesFirstSetTests var term = Assert.Single(stat.Terms); Assert.Equal(StatSource.Quality, term.Source); Assert.Equal(role, term.Role); + Assert.Equal(1f, term.Exponent); // linear in the role's quality: a design's range is the whole of what quality changes } private static void AssertModifier(BehaviorData behavior, string target, string stat, float min, float max, string role) @@ -85,7 +88,9 @@ public sealed class ConsumablesFirstSetTests foreach (var design in new[] { overdrive, vent }) { CultRecordRefs.Validate(design); // every modifier resolves and every role a stat reads is declared - Assert.True(design.SpecificHeat > 0f && design.Conductivity > 0f); + // The thermal constants are the catalog's 1x1 tool's (the Targeting Computer), pinned so a re-author cannot drift them. + Assert.Equal(SpecificHeat, design.SpecificHeat); + Assert.Equal(Conductivity, design.Conductivity); var products = cache.GetAll().Where(p => p.Design.Key.Equals(cache.RefOf(design).Key)).ToArray(); Assert.Equal(2, products.Length);