XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEExtension.NetCore.AutoConfig

【DLL】自动实现配置文件的存储

公开
关注 0 Fork 0 Star 0
UTF-8
using Xunit;

namespace XFEExtension.NetCore.AutoConfig.Tests;

public sealed class PersistenceTests
{
    [Fact]
    public void StaticPartialPropertyPreservesInitializer()
    {
        Assert.Equal("partial-default", PartialPropertyDefaultProfile.Value);
        var separateInstance = new PartialPropertyDefaultProfile();
        Assert.Equal("partial-default", separateInstance.InstanceValue);
        Assert.NotSame(PartialPropertyDefaultProfile.Values, separateInstance.InstanceValues);
    }

    [Fact]
    public async Task AllBuiltInFormatsRoundTrip()
    {
        var directory = CreateTestDirectory();
        try
        {
            await AssertRoundTripAsync(
                Path.Combine(directory, nameof(XfeRoundTripProfile)),
                value => XfeRoundTripProfile.Value = value,
                () => XfeRoundTripProfile.Value,
                () => XfeRoundTripProfile.SaveProfileAsync(),
                XfeRoundTripProfile.LoadProfile,
                path => XfeRoundTripProfile.ProfilePath = path);
            await AssertRoundTripAsync(
                Path.Combine(directory, nameof(JsonRoundTripProfile)),
                value => JsonRoundTripProfile.Value = value,
                () => JsonRoundTripProfile.Value,
                () => JsonRoundTripProfile.SaveProfileAsync(),
                JsonRoundTripProfile.LoadProfile,
                path => JsonRoundTripProfile.ProfilePath = path);
            await AssertRoundTripAsync(
                Path.Combine(directory, nameof(XmlRoundTripProfile)),
                value => XmlRoundTripProfile.Value = value,
                () => XmlRoundTripProfile.Value,
                () => XmlRoundTripProfile.SaveProfileAsync(),
                XmlRoundTripProfile.LoadProfile,
                path => XmlRoundTripProfile.ProfilePath = path);
            await AssertRoundTripAsync(
                Path.Combine(directory, nameof(PartialPropertyProfile)),
                value => PartialPropertyProfile.Value = value,
                () => PartialPropertyProfile.Value,
                () => PartialPropertyProfile.SaveProfileAsync(),
                PartialPropertyProfile.LoadProfile,
                path => PartialPropertyProfile.ProfilePath = path);
        }
        finally
        {
            DeleteDirectory(directory);
        }
    }

    [Fact]
    public void XmlUsesProfilePropertyNamesWithoutInstancePrefix()
    {
        XmlRoundTripProfile.Current.InstanceValue = "canonical";

        var xml = XmlRoundTripProfile.ExportProfile();
        PartialPropertyProfile.Value = "partial-canonical";
        var partialXml = PartialPropertyProfile.ExportProfile();

        Assert.Contains("<Value>canonical</Value>", xml);
        Assert.DoesNotContain("<InstanceValue>", xml);
        Assert.Contains("<Value>partial-canonical</Value>", partialXml);
        Assert.DoesNotContain("<InstanceValue>", partialXml);
    }

    [Fact]
    public async Task NaturalPartialPropertyRequestsAutomaticSave()
    {
        var directory = CreateTestDirectory();
        try
        {
            PartialPropertyProfile.ProfilePath = Path.Combine(directory, nameof(PartialPropertyProfile));
            PartialPropertyProfile.Current.AutoSaveDelay = TimeSpan.FromMilliseconds(10);

            PartialPropertyProfile.Value = "auto-saved";
            await PartialPropertyProfile.FlushAsync();

            var xml = await File.ReadAllTextAsync(PartialPropertyProfile.ProfilePath + ".xml");
            Assert.Contains("<Value>auto-saved</Value>", xml);
        }
        finally
        {
            PartialPropertyProfile.DeleteProfile();
            DeleteDirectory(directory);
        }
    }

    [Fact]
    public void ProfilePathAttributeAndDefaultPathUseExpectedPaths()
    {
        Assert.Equal("custom/settings", AttributePathProfile.ProfilePath);

        var previousPath = XFEProfile.ProfilesDefaultPath;
        var root = Path.Combine(Path.GetTempPath(), "XFEAutoConfigTests", Guid.NewGuid().ToString("N"));
        try
        {
            XFEProfile.ProfilesDefaultPath = root;
            Assert.Equal(Path.Combine(root, nameof(DefaultPathProfile)), DefaultPathProfile.ProfilePath);
            Assert.Equal(0, DefaultPathProfile.Value);
        }
        finally
        {
            XFEProfile.ProfilesDefaultPath = previousPath;
        }
    }

    [Fact]
    public async Task FlushPersistsPendingSaveAndHonorsCancellation()
    {
        var directory = CreateTestDirectory();
        try
        {
            FlushProfile.ProfilePath = Path.Combine(directory, nameof(FlushProfile));
            FlushProfile.Current.AutoSaveDelay = TimeSpan.FromSeconds(30);
            FlushProfile.Value = "flushed";

            await FlushProfile.FlushAsync();

            var profilePath = FlushProfile.ProfilePath + ".xpf";
            Assert.True(File.Exists(profilePath));
            Assert.Contains("flushed", await File.ReadAllTextAsync(profilePath));
            await Assert.ThrowsAnyAsync<OperationCanceledException>(() => FlushProfile.SaveProfileAsync(new CancellationToken(true)));
        }
        finally
        {
            FlushProfile.DeleteProfile();
            DeleteDirectory(directory);
        }
    }

    [Fact]
    public void CorruptProfileIsPreservedAndDefaultsRemainUsable()
    {
        var directory = CreateTestDirectory();
        ProfileLoadFailedEventArgs? observedFailure = null;
        EventHandler<ProfileLoadFailedEventArgs> handler = (_, args) => observedFailure = args;
        XFEProfile.ProfileLoadFailed += handler;
        try
        {
            CorruptJsonProfile.ProfilePath = Path.Combine(directory, nameof(CorruptJsonProfile));
            var profilePath = CorruptJsonProfile.ProfilePath + ".json";
            File.WriteAllText(profilePath, "{not-json");

            CorruptJsonProfile.LoadProfile();

            Assert.Equal("default", CorruptJsonProfile.Value);
            Assert.NotNull(CorruptJsonProfile.Current.LastLoadException);
            Assert.NotNull(observedFailure);
            Assert.Equal(typeof(CorruptJsonProfile), observedFailure!.ProfileType);
            Assert.False(File.Exists(profilePath));
            Assert.NotNull(observedFailure.BackupPath);
            Assert.True(File.Exists(observedFailure.BackupPath));
        }
        finally
        {
            XFEProfile.ProfileLoadFailed -= handler;
            DeleteDirectory(directory);
        }
    }

    [Fact]
    public async Task SaveFailureIsExposedAndPathChangesTakeEffect()
    {
        var directory = CreateTestDirectory();
        try
        {
            var blockingFile = Path.Combine(directory, "not-a-directory");
            await File.WriteAllTextAsync(blockingFile, "block");
            FailingSaveProfile.ProfilePath = Path.Combine(blockingFile, "profile");
            FailingSaveProfile.Value = "cannot-save";

            await Assert.ThrowsAnyAsync<IOException>(() => FailingSaveProfile.SaveProfileAsync());
            Assert.IsType<IOException>(FailingSaveProfile.Current.LastSaveException, exactMatch: false);

            var firstPath = Path.Combine(directory, "first", nameof(PathChangeProfile));
            var secondPath = Path.Combine(directory, "second", nameof(PathChangeProfile));
            PathChangeProfile.ProfilePath = firstPath;
            PathChangeProfile.Value = 1;
            await PathChangeProfile.SaveProfileAsync();
            PathChangeProfile.ProfilePath = secondPath;
            PathChangeProfile.Value = 2;
            await PathChangeProfile.SaveProfileAsync();

            Assert.True(File.Exists(firstPath + ".xpf"));
            Assert.True(File.Exists(secondPath + ".xpf"));
        }
        finally
        {
            DeleteDirectory(directory);
        }
    }

    [Fact]
    public async Task UnixPermissionDenialIsSurfaced()
    {
        if (OperatingSystem.IsWindows())
            return;

        var directory = CreateTestDirectory();
        try
        {
            FailingSaveProfile.ProfilePath = Path.Combine(directory, nameof(FailingSaveProfile));
            File.SetUnixFileMode(directory, UnixFileMode.UserRead | UnixFileMode.UserExecute);

            await Assert.ThrowsAsync<UnauthorizedAccessException>(() => FailingSaveProfile.SaveProfileAsync());
            Assert.IsType<UnauthorizedAccessException>(FailingSaveProfile.Current.LastSaveException);
        }
        finally
        {
            File.SetUnixFileMode(directory, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
            DeleteDirectory(directory);
        }
    }

    private static async Task AssertRoundTripAsync(string path, Action<string> setValue, Func<string> getValue, Func<Task> save, Action load, Action<string> setPath)
    {
        setPath(path);
        setValue("persisted");
        await save();
        setValue("changed");
        load();
        Assert.Equal("persisted", getValue());
    }

    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);
    }
}