Pin Brand's same-maker tie-break with a test and a mutant
Brand()'s ordering by record key was only exercised indirectly, through a test that gives each candidate product a different maker. Add a test with two FactionProductData for the same maker and same design, distinct record keys, asserting Brand returns the one first in record-key order. Add the corresponding mutant (OrderBy -> OrderByDescending at Brand's ordering) to mutation_tests.py so a regression there gets caught. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fc1c0526fe
commit
5e9634efcd
2 changed files with 33 additions and 0 deletions
|
|
@ -316,6 +316,30 @@ public sealed class LoadoutTests : IDisposable
|
|||
Assert.Contains("slot 0,0: no available product of Lamp", Assert.Single(failures));
|
||||
}
|
||||
|
||||
// Census's duplicate (maker, design) detector exists precisely because this should never happen in an
|
||||
// authored catalog; Brand()'s tie-break is what a runtime item falls back on if it ever does, so it must stay
|
||||
// deterministic rather than however the cache happens to enumerate.
|
||||
[Fact]
|
||||
public void BrandPicksFirstProductInKeyOrderForSameMakerAndDesign()
|
||||
{
|
||||
using var cache = AetheriaStores.Open(Catalog, catalogWritable: true);
|
||||
var maker = cache.Upsert(new Faction { Name = "TieBreaker", ShortName = "TIE" });
|
||||
var lamp = new CultRecordRef<CraftedItemData>(cache.RefOf(cache.GetByName<GearData>("Lamp")).Key);
|
||||
cache.Commit(batch => batch.Upsert(typeof(FactionProductData), new FactionProductData { Name = "lamp-b", Design = lamp, Manufacturer = maker }, new CultRecordKey("lamp-b")));
|
||||
cache.Commit(batch => batch.Upsert(typeof(FactionProductData), new FactionProductData { Name = "lamp-a", Design = lamp, Manufacturer = maker }, new CultRecordKey("lamp-a")));
|
||||
|
||||
var items = new ItemManager(cache, new ProvenanceLedger(), RunSaveTests.TestSettings(), _ => { });
|
||||
var lotId = items.Lots.Add(new Lot
|
||||
{
|
||||
Design = cache.RefOf<ItemData>(cache.GetByName<GearData>("Lamp")),
|
||||
Origin = new Produced { Faction = maker, Station = 1, Facility = 2, Inputs = Array.Empty<int>() },
|
||||
Quality = .5f, Roles = new List<RoleFill>()
|
||||
});
|
||||
var instance = (EquippableItem) items.CreateInstance(lotId);
|
||||
var (_, product) = items.Brand(instance);
|
||||
Assert.Equal("lamp-a", product.Name);
|
||||
}
|
||||
|
||||
// F2: this test must not enumerate reloaded instances through EntitySerializer.Items, the GC root walk under
|
||||
// test. It hand-mints one real lot per EntityPack root (Hull, Equipment, CargoBays, DockingBays, CargoContents,
|
||||
// Children, DockingBayContents) so the expected lot ids are known independently of any enumerator, and reads
|
||||
|
|
|
|||
|
|
@ -122,6 +122,15 @@ MUTATIONS: list[Mutation] = [
|
|||
test="LoadoutTests.FirstAvailableProductInKeyOrderBuildsTheSlot",
|
||||
expect="red",
|
||||
),
|
||||
# --- BrandPicksFirstProductInKeyOrderForSameMakerAndDesign: Brand's own tie-break must be ascending key order ---
|
||||
Mutation(
|
||||
rule="Brand's tie-break among a maker's products for one design is ascending record-key order",
|
||||
file="Assets/Scripts/ServerShared/ItemManager.cs",
|
||||
anchor=" .OrderBy(p => ItemData.RefOf(p).Key.Value, StringComparer.Ordinal)",
|
||||
mutated=" .OrderByDescending(p => ItemData.RefOf(p).Key.Value, StringComparer.Ordinal)",
|
||||
test="LoadoutTests.BrandPicksFirstProductInKeyOrderForSameMakerAndDesign",
|
||||
expect="red",
|
||||
),
|
||||
# --- StatsReadTheLot: stats must read the lot, including its per-role fills ---
|
||||
Mutation(
|
||||
rule="Evaluate reads the lot's per-role fill for a role stat",
|
||||
|
|
|
|||
Loading…
Reference in a new issue