using Xunit; namespace XFEExtension.NetCore.AutoConfig.Tests; public sealed class AdvancedPersistenceTests { [Fact] public async Task VersionMigrationRenamesFieldAndPersistsCurrentVersion() { var directory = CreateTestDirectory(); try { MigratingJsonProfile.ProfilePath = Path.Combine(directory, "migration"); var path = MigratingJsonProfile.ProfilePath + ".json"; await File.WriteAllTextAsync(path, "{\"InstanceLegacyName\":\"migrated\"}"); MigratingJsonProfile.LoadProfile(); Assert.Equal("migrated", MigratingJsonProfile.DisplayName); Assert.Equal(7, MigratingJsonProfile.Revision); Assert.Equal(0, MigratingJsonProfile.Current.LoadedProfileVersion); await MigratingJsonProfile.FlushAsync(); var persisted = await File.ReadAllTextAsync(path); Assert.Contains("\"$xfeProfileVersion\":2", persisted); Assert.Contains("InstanceDisplayName", persisted); Assert.DoesNotContain("InstanceLegacyName", persisted); } finally { MigratingJsonProfile.DeleteProfile(); DeleteDirectory(directory); } } [Fact] public void ValidationFailureRejectsImportAndKeepsCurrentInstance() { MigratingJsonProfile.Current = new MigratingJsonProfile(); MigratingJsonProfile.Current.InstanceDisplayName = "accepted"; var original = MigratingJsonProfile.Current; var exception = Assert.Throws(() => MigratingJsonProfile.ImportProfile("{\"$xfeProfileVersion\":2,\"InstanceDisplayName\":\"\"}")); Assert.Same(original, MigratingJsonProfile.Current); Assert.Equal("accepted", MigratingJsonProfile.DisplayName); Assert.Contains("DisplayName", exception.ValidationError); } [Fact] public async Task ValidationFailureOnLoadKeepsCurrentAndOriginalFile() { var directory = CreateTestDirectory(); ProfileLoadFailedEventArgs? observed = null; EventHandler handler = (_, args) => observed = args; XFEProfile.ProfileLoadFailed += handler; try { MigratingJsonProfile.Current = new MigratingJsonProfile(); MigratingJsonProfile.Current.InstanceDisplayName = "accepted"; MigratingJsonProfile.ProfilePath = Path.Combine(directory, "invalid"); var path = MigratingJsonProfile.ProfilePath + ".json"; await File.WriteAllTextAsync(path, "{\"$xfeProfileVersion\":2,\"InstanceDisplayName\":\"\",\"InstanceRevision\":7}"); var original = MigratingJsonProfile.Current; MigratingJsonProfile.LoadProfile(); Assert.Same(original, MigratingJsonProfile.Current); Assert.Equal("accepted", MigratingJsonProfile.DisplayName); Assert.IsType(original.LastLoadException); Assert.True(File.Exists(path)); Assert.NotNull(observed); Assert.Null(observed!.BackupPath); } finally { XFEProfile.ProfileLoadFailed -= handler; MigratingJsonProfile.DeleteProfile(); DeleteDirectory(directory); } } [Fact] public async Task MigrationFailureKeepsFileAndCurrentDefaults() { var directory = CreateTestDirectory(); try { MigratingJsonProfile.Current = new MigratingJsonProfile(); MigratingJsonProfile.ProfilePath = Path.Combine(directory, "future"); var path = MigratingJsonProfile.ProfilePath + ".json"; await File.WriteAllTextAsync(path, "{\"$xfeProfileVersion\":99,\"InstanceDisplayName\":\"future\"}"); MigratingJsonProfile.LoadProfile(); Assert.Equal("default", MigratingJsonProfile.DisplayName); Assert.IsType(MigratingJsonProfile.Current.LastLoadException); Assert.True(File.Exists(path)); Assert.Empty(Directory.GetFiles(directory, "*.corrupt-*")); } finally { MigratingJsonProfile.DeleteProfile(); DeleteDirectory(directory); } } [Fact] public void AllBuiltInFormatsEmitVersionMetadata() { VersionedXfeProfile.Current.InstanceValue = "xfe"; VersionedXmlProfile.Current.InstanceValue = "xml"; MigratingJsonProfile.Current.InstanceDisplayName = "json"; var xfe = VersionedXfeProfile.ExportProfile(); var xml = VersionedXmlProfile.ExportProfile(); var json = MigratingJsonProfile.ExportProfile(); Assert.Contains("$xfeProfileVersion", xfe); Assert.Contains("xfeProfileVersion=\"1\"", xml); Assert.DoesNotContain("", xml); Assert.Contains("\"$xfeProfileVersion\":2", json); Assert.DoesNotContain("\"ProfileSchemaVersion\"", json); VersionedXfeProfile.Current.InstanceValue = "changed"; VersionedXmlProfile.Current.InstanceValue = "changed"; VersionedXfeProfile.ImportProfile(xfe); VersionedXmlProfile.ImportProfile(xml); Assert.Equal("xfe", VersionedXfeProfile.Value); Assert.Equal("xml", VersionedXmlProfile.Value); } [Fact] public void JsonOptionsApplyNamingPolicyAndConvertersOnRoundTrip() { JsonOptionsProfile.Current.InstanceTheme = ProfileTheme.Dark; var content = JsonOptionsProfile.ExportProfile(); JsonOptionsProfile.Current.InstanceTheme = ProfileTheme.Light; JsonOptionsProfile.ImportProfile(content); Assert.Contains("\"instanceTheme\": \"dark\"", content); Assert.Contains(Environment.NewLine, content); Assert.Equal(ProfileTheme.Dark, JsonOptionsProfile.Theme); JsonOptionsProfile.ImportProfile("{/* supported comment */\"instanceTheme\":\"light\",}"); Assert.Equal(ProfileTheme.Light, JsonOptionsProfile.Theme); } [Fact] public async Task ProfileStoreMaintainsIndependentInstancesForSameType() { var directory = CreateTestDirectory(); try { var firstPath = Path.Combine(directory, "tenant-a.json"); var secondPath = Path.Combine(directory, "tenant-b.json"); var first = new ProfileStore(firstPath, autoLoad: false); var second = new ProfileStore(secondPath, autoLoad: false); first.Update(profile => { profile.InstanceName = "A"; profile.InstanceValues.Add("one"); }); second.Update(profile => { profile.InstanceName = "B"; profile.InstanceValues.Add("two"); }); await Task.WhenAll(first.SaveAsync(), second.SaveAsync()); var reloadedFirst = new ProfileStore(firstPath); var reloadedSecond = new ProfileStore(secondPath); Assert.NotSame(reloadedFirst.Current, reloadedSecond.Current); Assert.Equal("A", reloadedFirst.Read(profile => profile.InstanceName)); Assert.Equal("B", reloadedSecond.Read(profile => profile.InstanceName)); Assert.Equal(["one"], reloadedFirst.Read(profile => profile.InstanceValues.ToArray())); Assert.Equal(["two"], reloadedSecond.Read(profile => profile.InstanceValues.ToArray())); Assert.Same(reloadedFirst.Current, reloadedFirst.Current.InstanceValues.CurrentProfile); Assert.Same(reloadedSecond.Current, reloadedSecond.Current.InstanceValues.CurrentProfile); } finally { DeleteDirectory(directory); } } [Fact] public void GeneratorAutomaticallyBindsProfileCollections() { AutoBindingProfile.Current = new AutoBindingProfile(); Assert.Same(AutoBindingProfile.Current, AutoBindingProfile.Numbers.CurrentProfile); Assert.Same(AutoBindingProfile.Current, AutoBindingProfile.Lookup.CurrentProfile); AutoBindingProfile.Current.InstanceNumbers = [1, 2]; Assert.Same(AutoBindingProfile.Current, AutoBindingProfile.Numbers.CurrentProfile); } private static string CreateTestDirectory() { var directory = Path.Combine(Path.GetTempPath(), "XFEAutoConfigTests", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(directory); return directory; } private static void DeleteDirectory(string directory) { if (Directory.Exists(directory)) Directory.Delete(directory, true); } }