Economy checks: gearRoles reads roles of non-commodity recipes; pin use order and batch list

Mutation testing found IsRoleRecipe redundant in the gear-role set (RolesOf is empty for a
design without roles) and the message's use order, join and the unknown-batch count unpinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-27 09:19:00 +01:00
commit 8d23a378d6
3 changed files with 22 additions and 4 deletions

View file

@ -420,7 +420,7 @@ public static class EconomyChecks
bool IsCommodityKey(string key) => recipes.TryGetValue(key, out var r) && Recipes.IsCommodity(catalog.Get(r.Output));
var consumerRoles = new HashSet<string>(recipes.Values.Where(r => Recipes.IsConsumerGood(catalog, r))
.SelectMany(r => Recipes.RolesOf(catalog.Get(r.Output))).Select(role => role.Name), StringComparer.Ordinal);
var gearRoles = new HashSet<string>(recipes.Values.Where(r => !Recipes.IsCommodity(catalog.Get(r.Output)) && Recipes.IsRoleRecipe(catalog, r))
var gearRoles = new HashSet<string>(recipes.Values.Where(r => !Recipes.IsCommodity(catalog.Get(r.Output)))
.SelectMany(r => Recipes.RolesOf(catalog.Get(r.Output))).Select(role => role.Name), StringComparer.Ordinal);
var upkeep = new HashSet<string>(recipes.Values.SelectMany(r => r.Upkeep).Select(c => c.Commodity.Key.Value), StringComparer.Ordinal);
var takenBy = recipes.Values.Where(r => IsCommodityKey(r.Output.Key.Value))

View file

@ -381,7 +381,7 @@ public sealed class EconomyChecksTests
f.Hull("tug", HullType.Ship, new[] { ("Plate", 1) });
var fault = Assert.Single(EconomyChecks.ConsumerDemand(f.Cache));
Assert.Equal("recipe:commodity:coke", fault.Record);
Assert.Contains("upkeep", fault.Message);
Assert.Contains("'coke' leads into upkeep but no use leads into a consumer good.", fault.Message);
f.Good("pot", roles: new[] { ("Casing", 1) });
f.EditRecipe("commodity:coke", r => r.Fills.Add("Casing"));
@ -412,6 +412,22 @@ public sealed class EconomyChecksTests
Assert.Empty(EconomyChecks.ConsumerDemand(f.Cache));
}
[Fact]
public void ConsumerDemandNamesEveryUseThatMadeTheGoodShared()
{
using var f = new EconomyFixture();
f.Good("ore");
f.Good("acid", inputs: new[] { ("ore", 1) });
f.Good("warhead", inputs: new[] { ("acid", 1) }, fills: new[] { "Warhead" });
f.Good("roundb", inputs: new[] { ("acid", 1) });
f.Good("rounda", inputs: new[] { ("acid", 1) });
f.Gear("mine", new[] { ("Warhead", 1) });
Rifle(f, "riflea", "rounda");
Rifle(f, "rifleb", "roundb");
var fault = Assert.Single(EconomyChecks.ConsumerDemand(f.Cache));
Assert.Equal("'acid' leads into Warhead, rounda and roundb but no use leads into a consumer good.", fault.Message);
}
[Fact]
public void ConsumerDemandTreatsTheGateAsNoUse()
{

View file

@ -274,9 +274,11 @@ public sealed class EconomyCommandTests
public void AnUnknownBatchNameIsRefusedWithoutEchoingIt()
{
var output = new StringWriter();
Assert.Equal(1, EconomyCommand.Run(new[] { "author", "CANARY-9f2e-batch" }, output));
Assert.Equal(1, EconomyCommand.Run(new[] { "author", "CANARY-9f2e-batch", "other-canary" }, output));
var text = output.ToString();
Assert.Contains("Batches:", text);
Assert.Contains("2 of the names given match no batch.", text);
foreach (var batch in typeof(EconomyBatch).Assembly.GetTypes().Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(EconomyBatch))))
Assert.Contains(batch.Name, text);
Assert.DoesNotContain("CANARY", text);
Assert.DoesNotContain("9f2e", text);
}