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<ProfileValidationException>(() =>
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<ProfileLoadFailedEventArgs> 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<ProfileValidationException>(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<ProfileMigrationException>(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("<ProfileSchemaVersion>", 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<TenantProfile>(firstPath, autoLoad: false);
var second = new ProfileStore<TenantProfile>(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<TenantProfile>(firstPath);
var reloadedSecond = new ProfileStore<TenantProfile>(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);
}
}
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<ProfileValidationException>(() =>
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<ProfileLoadFailedEventArgs> 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<ProfileValidationException>(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<ProfileMigrationException>(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("<ProfileSchemaVersion>", 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<TenantProfile>(firstPath, autoLoad: false);
var second = new ProfileStore<TenantProfile>(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<TenantProfile>(firstPath);
var reloadedSecond = new ProfileStore<TenantProfile>(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);
}
}