Aetheria/tests/Aetheria.Shared.Tests/EconomyCommandTests.cs
2026-10-23 07:15:59 +02:00

573 lines
25 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using GameCult.Caching;
using Xunit;
public sealed class EconomyCommandTests
{
private sealed class Lambda : EconomyBatch
{
private readonly Action<EconomyAuthor> _author;
public Lambda(Action<EconomyAuthor> author) => _author = author;
public override void Author(EconomyAuthor author) => _author(author);
}
private static void Flush(EconomyFixture f) => f.Cache.FlushAsync().GetAwaiter().GetResult();
private static CultCache Read(EconomyFixture f) => AetheriaStores.Open(f.CatalogPath, registry: TestCatalog.Registry());
private static int Author(EconomyFixture f, bool apply, out string output, params Action<EconomyAuthor>[] batches)
{
Flush(f);
var writer = new StringWriter();
var code = EconomyCommand.Author(f.CatalogPath, batches.Select(b => (EconomyBatch) new Lambda(b)).ToList(), apply, writer, TestCatalog.Registry());
output = writer.ToString();
return code;
}
// A commodity and its recipe, run by the fixture's mill unless a kind is given.
private static void Good(EconomyFixture f, EconomyAuthor a, string id, string lore = "a good", string[] fills = null, (string Id, int Count)[] inputs = null,
CultRecordRef<StationKindData>? kind = null)
{
var key = new CultRecordKey(EconomyFixture.Key(id));
if (inputs == null) a.Put(new SimpleCommodityData { Name = id }, key);
else a.Put(new CompoundCommodityData { Name = id }, key);
a.Put(new RecipeData
{
Output = EconomyFixture.Item(key.Value), StationKind = kind ?? f.Mill, LoreHook = lore,
Inputs = (inputs ?? Array.Empty<(string, int)>()).Select(i => new CommodityCount { Commodity = EconomyFixture.Item(EconomyFixture.Key(i.Id)), Count = i.Count }).ToList(),
Fills = (fills ?? Array.Empty<string>()).ToList()
}, RecipeData.KeyOf(key));
}
[Fact]
public void ABatchWithAShapeFaultIsRefusedAndTheCatalogStaysByteIdentical()
{
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var code = Author(f, true, out var output, a => Good(f, a, "tar", lore: " "));
Assert.Equal(1, code);
Assert.Contains("L-lore", output);
Assert.Contains("refused", output);
Assert.Contains(" L-lore recipe:commodity:tar: ", output);
Assert.Contains(" recipe:commodity:tar: A commodity's recipe needs a lore hook.", output);
Assert.Contains("1 shape faults", output);
Assert.Contains("commodity:ore depth 0", output);
Assert.True(output.IndexOf("commodity:ore depth", StringComparison.Ordinal) < output.IndexOf("commodity:plate depth", StringComparison.Ordinal));
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void TheGateRefusesALoopThatCannotSustainItself()
{
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var code = Author(f, true, out var output, a =>
{
Good(f, a, "loopa", inputs: new[] { ("loopb", 1) }, fills: new[] { "Plate" });
Good(f, a, "loopb", inputs: new[] { ("loopa", 1) });
});
Assert.Equal(1, code);
Assert.Contains("L-productive", output);
Assert.Contains("L-source-sink", output);
Assert.True(output.IndexOf("L-productive (", StringComparison.Ordinal) < output.IndexOf("L-source-sink (", StringComparison.Ordinal));
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void ADryRunWritesNothingAndPrintsWhatItWouldDo()
{
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var code = Author(f, false, out var output, a => Good(f, a, "coal"), a => a.Edit<SimpleCommodityData>("ore", ore => ore.Description = "grey"));
Assert.Equal(0, code);
Assert.Contains("added commodity:coal", output);
Assert.Contains("added recipe:commodity:coal", output);
Assert.Contains("changed commodity:ore", output);
Assert.Contains("Dry run", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void ACleanBatchAppliesAndTheCatalogThenReportsNoFault()
{
using var f = EconomyFixture.Healthy();
var code = Author(f, true, out var output, a =>
{
Good(f, a, "coal");
Good(f, a, "steel", inputs: new[] { ("coal", 2) }, fills: new[] { "Plate" });
});
Assert.Equal(0, code);
Assert.Contains("0 faults", output);
Assert.Contains("Wrote 4 records to Aetheria.cc", output);
using var after = Read(f);
Assert.NotNull(after.Get<RecipeData>(RecipeData.KeyOf(new CultRecordKey("commodity:steel"))));
Assert.Empty(EconomyChecks.Check(after).Faults);
}
[Fact]
public void ApplyConvergesAndTouchesOnlyWhatItNames()
{
using var f = EconomyFixture.Healthy();
Action<EconomyAuthor> coal = a => Good(f, a, "coal", fills: new[] { "Plate" });
Assert.Equal(0, Author(f, true, out _, coal));
var bytes = File.ReadAllBytes(f.CatalogPath);
Assert.Equal(0, Author(f, true, out var again, coal));
Assert.Contains("0 added, 0 changed, 0 removed, 2 unchanged", again);
Assert.Contains("Nothing to write.", again);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
Assert.Equal(0, Author(f, true, out var revised, a => Good(f, a, "coal", lore: "black rock", fills: new[] { "Plate" })));
Assert.Contains("0 added, 1 changed, 0 removed, 1 unchanged", revised);
using var after = Read(f);
Assert.Equal("black rock", after.Get<RecipeData>(RecipeData.KeyOf(new CultRecordKey("commodity:coal"))).LoreHook);
Assert.Equal("a good", after.Get<RecipeData>(RecipeData.KeyOf(new CultRecordKey("commodity:ore"))).LoreHook);
}
[Fact]
public void AFaultOnARecordTheBatchDidNotChangeDoesNotBlock()
{
using var f = EconomyFixture.Healthy();
f.Good("tar", lore: " ");
var code = Author(f, true, out var output, a => Good(f, a, "tar", lore: " "), a => Good(f, a, "coal"));
Assert.Equal(0, code);
Assert.Contains("L-lore", output);
Assert.Contains("2 added, 0 changed, 0 removed, 2 unchanged", output);
using var after = Read(f);
Assert.NotNull(after.Get<RecipeData>(RecipeData.KeyOf(new CultRecordKey("commodity:coal"))));
}
[Fact]
public void TheTrialCopyIsRemoved()
{
using var f = EconomyFixture.Healthy();
var before = Directory.GetDirectories(Path.GetTempPath(), "aetherdb-economy-*").Length;
Author(f, false, out _, a => Good(f, a, "coal"));
Author(f, true, out _, a => Good(f, a, "tar", lore: ""));
Assert.Equal(before, Directory.GetDirectories(Path.GetTempPath(), "aetherdb-economy-*").Length);
}
[Fact]
public void ACompletenessFaultIsReportedAndDoesNotBlockApply()
{
using var f = EconomyFixture.Healthy();
var code = Author(f, true, out var output, a => Good(f, a, "dust"));
Assert.Equal(0, code);
Assert.Contains("L-source-sink", output);
using var after = Read(f);
Assert.NotNull(after.Get<RecipeData>(RecipeData.KeyOf(new CultRecordKey("commodity:dust"))));
}
[Fact]
public void ACommodityWrittenWithoutARecipeIsCompletenessNotARefusal()
{
using var f = EconomyFixture.Healthy();
var code = Author(f, true, out var output, a => a.Put(new SimpleCommodityData { Name = "bare" }, new CultRecordKey("commodity:bare")));
Assert.Equal(0, code);
Assert.Contains("L-recipe", output);
}
[Fact]
public void AuthorsAKindWithStandIns()
{
using var f = EconomyFixture.Healthy();
var forge = StationKindData.KeyOf("forge");
var code = Author(f, true, out _, a =>
{
a.Put(new StationKindData { Name = "Forge", StandIns = new List<StationKindStandIn> { new StationKindStandIn { Faction = f.Maker, Kind = f.Mill } } }, forge);
Good(f, a, "slab", kind: new CultRecordRef<StationKindData>(forge), fills: new[] { "Plate" });
});
Assert.Equal(0, code);
using var after = Read(f);
Assert.Single(after.Get<StationKindData>(forge).StandIns);
Assert.DoesNotContain(EconomyChecks.Check(after).Faults, x => x.Invariant == EconomyChecks.LOneKindList);
}
[Fact]
public void AKindThatStandsInForItselfIsRefusedAndNothingIsWritten()
{
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var forge = StationKindData.KeyOf("forge");
var code = Author(f, true, out var output, a => a.Put(new StationKindData
{
Name = "Forge", StandIns = new List<StationKindStandIn> { new StationKindStandIn { Faction = f.Maker, Kind = new CultRecordRef<StationKindData>(forge) } }
}, forge));
Assert.Equal(1, code);
Assert.Contains("refused", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void AnEditNamingNoItemIsARefusalNotAThrow()
{
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var code = Author(f, true, out var output, a => a.Edit<SimpleCommodityData>("unobtainium", i => i.Price = 3));
Assert.Equal(1, code);
Assert.Contains("The catalog holds no SimpleCommodityData named 'unobtainium'.", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void AnEditNamingTwoItemsIsARefusalAndNothingIsWritten()
{
using var f = EconomyFixture.Healthy();
f.Cache.Upsert(typeof(SimpleCommodityData), new SimpleCommodityData { Name = "twin" }, new CultRecordKey("commodity:twin-a"));
f.Cache.Upsert(typeof(SimpleCommodityData), new SimpleCommodityData { Name = "twin" }, new CultRecordKey("commodity:twin-b"));
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var code = Author(f, true, out var output, a => a.Edit<SimpleCommodityData>("twin", i => i.Price = 3));
Assert.Equal(1, code);
Assert.Contains("2 SimpleCommodityData records are named 'twin'", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void AnEditChangesOnlyTheNamedItem()
{
using var f = EconomyFixture.Healthy();
var code = Author(f, true, out _, a => a.Edit<SimpleCommodityData>("ore", ore => { ore.Description = "grey"; ore.Price = 7; ore.Mass = 2; }));
Assert.Equal(0, code);
using var after = Read(f);
var ore = after.Get<SimpleCommodityData>(new CultRecordKey("commodity:ore"));
Assert.Equal(("grey", 7, 2f), (ore.Description, ore.Price, ore.Mass));
Assert.Null(after.Get<CompoundCommodityData>(new CultRecordKey("commodity:plate")).Description);
}
[Fact]
public void ReportPrintsFaultsAndDepthAndFailsOnAnyFault()
{
using var f = EconomyFixture.Healthy();
var healthy = new StringWriter();
Assert.Equal(0, EconomyCommand.Report(f.Cache, healthy));
var text = healthy.ToString();
Assert.Contains("0 faults", text);
Assert.Contains("commodity:plate depth 1", text);
Assert.Contains("commodity:ore <- nothing | Mill, cycle 1, workforce 0 | a good", text);
Assert.Contains("commodity:plate <- 2 x commodity:ore | Mill, cycle 1, workforce 0 | a good", text);
Assert.Contains("gear:laser <- 2 x Plate | Mill", text);
Assert.True(text.IndexOf("commodity:ore <-", StringComparison.Ordinal) < text.IndexOf("commodity:plate <-", StringComparison.Ordinal));
Assert.True(text.IndexOf("commodity:plate <-", StringComparison.Ordinal) < text.IndexOf("gear:laser <-", StringComparison.Ordinal));
f.EditRecipe("commodity:plate", r => r.LoreHook = "");
var broken = new StringWriter();
Assert.Equal(1, EconomyCommand.Report(f.Cache, broken));
Assert.Contains("L-lore", broken.ToString());
}
[Fact]
public void AnUnknownSubcommandFails() => Assert.Equal(1, EconomyCommand.Run(new[] { "bogus" }));
private static CultRecordKey K(string id) => new CultRecordKey(EconomyFixture.Key(id));
private static string FaultsOf(CultCache catalog, string invariant) =>
string.Join(";", EconomyChecks.Check(catalog).Faults.Where(x => x.Invariant == invariant).Select(x => x.Record));
[Fact]
public void ACycleClosedByALaterBatchIsRefusedEvenThoughTheFaultNamesAnEarlierBatchsRecord()
{
using var f = EconomyFixture.Healthy();
Assert.Equal(0, Author(f, true, out _, a =>
{
a.Put(new CompoundCommodityData { Name = "loopb" }, K("loopb"));
Good(f, a, "loopa", inputs: new[] { ("loopb", 1) });
}));
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var code = Author(f, true, out var output, a => Good(f, a, "loopb", inputs: new[] { ("loopa", 2) }));
Assert.Equal(1, code);
Assert.Contains("refused", output);
Assert.Contains("L-productive", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void AnInPlaceEditThroughTheCatalogIsReportedChangedAndCheckedBeforeItIsWritten()
{
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var plate = RecipeData.KeyOf(K("plate"));
var code = Author(f, true, out var output, a =>
{
var held = a.Catalog.Get<RecipeData>(plate);
held.LoreHook = " ";
a.Put(held, plate);
});
Assert.Equal(1, code);
Assert.Contains("changed recipe:commodity:plate", output);
Assert.Contains("L-lore", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void ABatchRunsOnceAndWhatIsWrittenIsWhatWasChecked()
{
using var f = EconomyFixture.Healthy();
var runs = 0;
var code = Author(f, true, out _, a =>
{
runs++;
Good(f, a, "tin", lore: runs == 1 ? "a good" : " ");
});
Assert.Equal(0, code);
Assert.Equal(1, runs);
using var after = Read(f);
Assert.Equal("a good", after.Get<RecipeData>(RecipeData.KeyOf(K("tin"))).LoreHook);
Assert.DoesNotContain(EconomyChecks.Check(after).Faults, x => x.Invariant == EconomyChecks.LLore);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ABatchThatWritesTheRealCatalogIsRefusedByName(bool apply)
{
using var f = EconomyFixture.Healthy();
var code = Author(f, apply, out var output, a => File.AppendAllText(f.CatalogPath, "x"));
Assert.Equal(1, code);
Assert.Contains("Aetheria.cc changed during the trial", output);
Assert.DoesNotContain("Dry run", output);
}
[Fact]
public void ABatchThatThrowsIsARefusalNamingItWithNoStackTrace()
{
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var code = Author(f, true, out var output, a => Good(f, a, "zinc"), a => throw new IOException("boom"));
Assert.Equal(1, code);
Assert.Contains("The batch Lambda is refused: boom", output);
Assert.DoesNotContain(" at ", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ANullOutsideMarketRowsOrRowIsANamedFaultNotAThrow(bool rowsNull)
{
using var f = EconomyFixture.Healthy();
f.Cache.Upsert(typeof(OutsideMarketData), new OutsideMarketData { Name = "gate", Rows = rowsNull ? null : new List<OutsideMarketRow> { null } }, OutsideMarketData.KeyOf("gate"));
var report = EconomyChecks.Check(f.Cache);
Assert.Contains(report.Faults, x => x.Invariant == EconomyChecks.LRecipe && x.Record == "outside-market:gate");
Assert.Equal(1, EconomyCommand.Report(f.Cache, new StringWriter()));
}
[Fact]
public void ANullEntryInFillsIsAnLRecipeFault()
{
using var f = EconomyFixture.Healthy();
f.EditRecipe("commodity:plate", r => r.Fills.Add(null));
Assert.Contains(EconomyChecks.Check(f.Cache).Faults, x => x.Invariant == EconomyChecks.LRecipe && x.Record == "recipe:commodity:plate" && x.Message.Contains("Fills"));
}
[Fact]
public void FixingOneFaultOnARecordDoesNotHideANewOneThere()
{
using var f = EconomyFixture.Healthy();
f.EditRecipe("commodity:plate", r => r.Upkeep.Add(null));
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var key = RecipeData.KeyOf(new CultRecordKey(EconomyFixture.Key("plate")));
var code = Author(f, true, out var output, a => { var r = a.Catalog.Get<RecipeData>(key); r.Upkeep.RemoveAll(x => x == null); r.Cycle = 0; a.Put(r, key); });
Assert.Equal(1, code);
Assert.Contains("Cycle is not a finite number above zero", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
private static EconomyFixture HeldLoop()
{
var f = EconomyFixture.Healthy();
f.Good("loopa", inputs: new[] { ("loopb", 1) });
f.Good("loopb", inputs: new[] { ("loopa", 2) });
return f;
}
[Theory]
[InlineData("worsen")]
[InlineData("move")]
[InlineData("move-lower")]
[InlineData("grow")]
[InlineData("disjoint")]
public void AHeldUnproductiveCatalogRefusesABatchThatWorsensMovesOrGrowsTheCycle(string how)
{
using var f = HeldLoop();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
Action<EconomyAuthor> batch = how switch
{
"worsen" => a => Good(f, a, "loopb", inputs: new[] { ("loopa", 50) }),
"move" => a => { Good(f, a, "loopb"); Good(f, a, "loopz", inputs: new[] { ("loopa", 9) }); Good(f, a, "loopa", inputs: new[] { ("loopz", 1) }); },
"move-lower" => a => { Good(f, a, "loopb"); Good(f, a, "loopz", inputs: new[] { ("loopa", 1) }); Good(f, a, "loopa", inputs: new[] { ("loopz", 1) }); },
"grow" => a => { Good(f, a, "loopy", inputs: new[] { ("loopa", 7) }); Good(f, a, "loopa", inputs: new[] { ("loopb", 1), ("loopy", 7) }); },
_ => a => { Good(f, a, "loopc", inputs: new[] { ("loopd", 5) }); Good(f, a, "loopd", inputs: new[] { ("loopc", 5) }); }
};
var code = Author(f, true, out var output, batch);
Assert.Equal(1, code);
Assert.Contains("L-productive", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ANewUnproductiveCycleBelowTheHeldRadiusIsRefusedAloneOrBesideALoweringOfTheHeldCycle(bool alsoLowerHeld)
{
using var f = EconomyFixture.Healthy();
f.Good("loopa", inputs: new[] { ("loopb", 2) });
f.Good("loopb", inputs: new[] { ("loopa", 4) });
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var code = Author(f, true, out var output, a =>
{
if (alsoLowerHeld) Good(f, a, "loopb", inputs: new[] { ("loopa", 2) });
Good(f, a, "loopc", inputs: new[] { ("loopd", 1) });
Good(f, a, "loopd", inputs: new[] { ("loope", 1) });
Good(f, a, "loope", inputs: new[] { ("loopc", 2) });
});
Assert.Equal(1, code);
Assert.Contains("L-productive", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void AHeldCycleWhoseRadiusFallsStillApplies()
{
using var f = HeldLoop();
var code = Author(f, true, out var output, a => Good(f, a, "loopb", inputs: new[] { ("loopa", 1) }));
Assert.Equal(0, code);
Assert.Contains("L-productive", output);
Assert.Contains("Wrote 1 records", output);
}
[Fact]
public void AHeldUnproductiveCatalogStillAppliesABatchThatLeavesTheCycleAlone()
{
using var f = HeldLoop();
var code = Author(f, true, out var output, a => Good(f, a, "coal"));
Assert.Equal(0, code);
Assert.Contains("Wrote 2 records", output);
}
private static void Seed(EconomyFixture f, string id)
{
var key = new CultRecordKey(EconomyFixture.Key(id));
f.Cache.Upsert(typeof(SimpleCommodityData), new SimpleCommodityData { Name = id }, key);
f.Cache.Upsert(typeof(RecipeData), new RecipeData { Output = EconomyFixture.Item(key.Value), StationKind = f.Mill, LoreHook = "a good", Inputs = new List<CommodityCount>() }, RecipeData.KeyOf(key));
}
[Fact]
public void RemovingACommodityAndItsRecipeIsListedAndApplies()
{
using var f = EconomyFixture.Healthy();
Seed(f, "tar");
var key = new CultRecordKey(EconomyFixture.Key("tar"));
var code = Author(f, true, out var output, a => { a.Catalog.Remove(RecipeData.KeyOf(key)); a.Catalog.Remove(key); });
Assert.Equal(0, code);
Assert.Contains("removed commodity:tar", output);
Assert.Contains("removed recipe:commodity:tar", output);
Assert.Contains("2 removed", output);
Assert.Contains("Wrote 2 records", output);
Assert.DoesNotContain("Nothing to write", output);
using var after = Read(f);
Assert.Null(after.Get<RecipeData>(RecipeData.KeyOf(key)));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void RemovingARecipeAloneIsARefusedLRecipeFault(bool alongsideAnAddition)
{
using var f = EconomyFixture.Healthy();
Seed(f, "tar");
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
var key = new CultRecordKey(EconomyFixture.Key("tar"));
var code = Author(f, true, out var output, a => { a.Catalog.Remove(RecipeData.KeyOf(key)); if (alongsideAnAddition) Good(f, a, "coal"); });
Assert.Equal(1, code);
Assert.Contains("L-recipe commodity:tar", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void ANewCommodityWithNoRecipeYetIsStillPending()
{
using var f = EconomyFixture.Healthy();
var code = Author(f, true, out var output, a => a.Put(new SimpleCommodityData { Name = "coal" }, new CultRecordKey(EconomyFixture.Key("coal"))));
Assert.Equal(0, code);
Assert.Contains("Wrote 1 records", output);
}
[Fact]
public void AReadOnlyCatalogIsARefusalOnEveryPlatformAndStaysIntact()
{
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
File.SetAttributes(f.CatalogPath, FileAttributes.ReadOnly);
try
{
var code = Author(f, true, out var output, a => Good(f, a, "coal"));
Assert.Equal(1, code);
Assert.Contains("read-only", output);
Assert.DoesNotContain(" at ", output);
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
finally { File.SetAttributes(f.CatalogPath, FileAttributes.Normal); }
}
[Fact]
public void ACatalogHeldOpenWithoutDeleteSharingIsANamedRefusalAndStaysIntact()
{
if (!OperatingSystem.IsWindows()) return;
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
using (new FileStream(f.CatalogPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
var code = Author(f, true, out var output, a => Good(f, a, "coal"));
Assert.Equal(1, code);
Assert.Contains("close Unity", output);
Assert.DoesNotContain(" at ", output);
}
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
Assert.Empty(Directory.GetFiles(Path.GetDirectoryName(f.CatalogPath), "*.economy-*"));
}
[Fact]
public void ACatalogHeldOpenForWritingIsANamedRefusalAndStaysIntact()
{
if (!OperatingSystem.IsWindows()) return;
using var f = EconomyFixture.Healthy();
Flush(f);
var bytes = File.ReadAllBytes(f.CatalogPath);
using (new FileStream(f.CatalogPath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
{
var code = Author(f, true, out var output, a => Good(f, a, "coal"));
Assert.Equal(1, code);
Assert.Contains("close Unity", output);
Assert.DoesNotContain(" at ", output);
}
Assert.Equal(bytes, File.ReadAllBytes(f.CatalogPath));
}
[Fact]
public void TheSwapKeepsTheCatalogsFileMode()
{
if (OperatingSystem.IsWindows()) return;
using var f = EconomyFixture.Healthy();
Flush(f);
var mode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead;
File.SetUnixFileMode(f.CatalogPath, mode);
Assert.Equal(0, Author(f, true, out _, a => Good(f, a, "coal")));
Assert.Equal(mode, File.GetUnixFileMode(f.CatalogPath));
}
}