📄 core/tests/MessageOptionsTests/LoadJsonShould.cs
using System;
using System.IO;
using Microsoft.Extensions.Options;
using Shouldly;
using Xunit;

namespace Cfa.Tests.MessageOptionsTests;

public sealed class LoadJsonShould
{
    [Fact]
    public void ReportMalformedOptionalUserConfigurationInsteadOfIgnoringIt()
    {
        // Arrange
        var user = Path.GetTempFileName();
        try
        {
            File.WriteAllText(user, "{ invalid");

            // Act
            var load = () => MessageOptions.LoadLayeredJson(null, false, user);

            // Assert
            load.ShouldThrow<InvalidDataException>();
        }
        finally
        {
            File.Delete(user);
        }
    }

    [Fact]
    public void OverlayTheSelectedFileOnUserSettingsUsingConfigurationKeyPrecedence()
    {
        // Arrange
        var user = Path.GetTempFileName();
        var selected = Path.GetTempFileName();
        var local = Path.GetTempFileName();
        try
        {
            File.WriteAllText(
                user,
                """
                {"messageRoutes":[{"publisher":{"method":{"type":"Bus","name":"Send"},"messageArgument":2},
                "registrations":[{"method":{"type":"Receiver","name":"On"}}]}]}
                """
            );
            File.WriteAllText(selected, """{"messageRoutes":[{"publisher":{"method":{"name":"Publish"}}}]}""");
            File.WriteAllText(
                local,
                """{"messageRoutes":[{"publisher":{"method":{"type":"LocalBus","name":"LocalSend"}}}]}"""
            );

            // Act
            var options = MessageOptions.LoadLayeredJson(selected, false, user, local);
            var userOnly = MessageOptions.LoadLayeredJson(null, false, user);
            var localOnly = MessageOptions.LoadLayeredJson(null, false, user, local);

            // Assert
            options.MessageRoutes.ShouldHaveSingleItem().Publisher.Method.Name.ShouldBe("Publish");
            options.MessageRoutes[0].Publisher.Method.Type.ShouldBe("LocalBus");
            localOnly.MessageRoutes[0].Publisher.Method.Name.ShouldBe("LocalSend");
            options.MessageRoutes[0].Publisher.MessageArgument.ShouldBe(2);
            options.MessageRoutes[0].Registrations.ShouldHaveSingleItem().Method.Name.ShouldBe("On");
            userOnly.MessageRoutes[0].Publisher.Method.Name.ShouldBe("Send");
        }
        finally
        {
            File.Delete(user);
            File.Delete(selected);
            File.Delete(local);
        }
    }

    [Fact]
    public void AllowMissingUserAndOptionalLocalFilesButRequireExplicitFiles()
    {
        // Arrange
        var missing = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString(), ".cfa.json");

        // Act
        var options = MessageOptions.LoadLayeredJson(missing, true, missing);
        var required = () =>
            MessageOptions.LoadLayeredJson(
                missing,
                false,
                Path.Combine(Path.GetTempPath(), "absent-user-config-" + Guid.NewGuid())
            );

        // Assert
        options.MessageRoutes.ShouldBeEmpty();
        required.ShouldThrow<FileNotFoundException>();
    }

    [Theory]
    [InlineData("""{"messageRoutes":[{}]}""")]
    [InlineData("""{"messageRoutes":[{"publisher":{"method":{"type":"Bus","name":"Send"},"messageArgument":-1}}]}""")]
    public void RejectIncompleteRoutes(string json)
    {
        // Arrange
        var path = Path.GetTempFileName();
        try
        {
            File.WriteAllText(path, json);

            // Act
            var load = () => MessageOptions.LoadLayeredJson(path, false, path + ".missing");

            // Assert
            load.ShouldThrow<OptionsValidationException>();
        }
        finally
        {
            File.Delete(path);
        }
    }

    [Fact]
    public void ReportInvalidNestedSettings()
    {
        // Arrange
        var path = Path.GetTempFileName();
        try
        {
            File.WriteAllText(
                path,
                """
                {
                  "messageRoutes": [{
                    "publisher": {
                      "method": { "type": " ", "name": "", "genericArity": -1, "parameterCount": -1 },
                      "messageArgument": -1
                    },
                    "registrations": [{
                      "method": { "type": "Receiver", "name": "On" },
                      "messageTypeArgument": -1,
                      "handlerArgument": -1
                    }]
                  }]
                }
                """
            );

            // Act
            var load = () => MessageOptions.LoadLayeredJson(path, false, path + ".missing");

            // Assert
            var error = load.ShouldThrow<OptionsValidationException>();
            error.Message.ShouldContain("Type");
            error.Message.ShouldContain("Name");
            error.Message.ShouldContain("GenericArity");
            error.Message.ShouldContain("ParameterCount");
            error.Message.ShouldContain("MessageArgument");
            error.Message.ShouldContain("MessageTypeArgument");
            error.Message.ShouldContain("HandlerArgument");
        }
        finally
        {
            File.Delete(path);
        }
    }
}