Newer
Older
Sunfish / tests / Sunfish.Tests / HostTests.cs
@Ivan Dominguez Ivan Dominguez 21 hours ago 6 KB 3.0
using System.Net;
using System.Net.Sockets;
using Sunfish.Config;
using Sunfish.Host;

namespace Sunfish.Tests;

public class ShareSettingsTests
{
    [Fact]
    public void SetsOptionsByIniKey()
    {
        var share = new ShareConfig { Name = "x" };
        Assert.Null(ShareSettings.TrySet(share, "upload", "yes"));
        Assert.Null(ShareSettings.TrySet(share, "Location", "fotos/"));
        Assert.Null(ShareSettings.TrySet(share, "Users", "ana, pepe"));
        Assert.True(share.Upload);
        Assert.Equal("/fotos", share.Location);
        Assert.Equal(["ana", "pepe"], share.Users);

        Assert.NotNull(ShareSettings.TrySet(share, "Upload", "quizas"));
        Assert.NotNull(ShareSettings.TrySet(share, "Inventada", "true"));
        Assert.NotNull(ShareSettings.TrySet(share, "Location", "/$sunfish"));
    }

    [Fact]
    public void SuggestsFreeNamesAndLocations()
    {
        var config = new SunfishConfig();
        config.Shares.Add(new ShareConfig { Name = "fotos", Location = "/fotos", Path = "x" });
        Assert.Equal("fotos-2", ShareSettings.SuggestName(config, "fotos"));
        Assert.Equal("/fotos-2", ShareSettings.SuggestLocation(config, "fotos"));
        Assert.Equal("/Mis fotos", ShareSettings.SuggestLocation(config, "Mis fotos"));
    }

    [Fact]
    public void ValidatesBeforeSaving()
    {
        using var dir = new TempDir();
        var config = new SunfishConfig();
        config.Shares.Add(new ShareConfig { Name = "fotos", Location = "/fotos", Path = dir.Path });

        Assert.Null(ShareSettings.Validate(config, new ShareConfig { Name = "otro", Location = "/otro", Path = dir.Path }, null));
        Assert.NotNull(ShareSettings.Validate(config, new ShareConfig { Name = "FOTOS", Location = "/x", Path = dir.Path }, null));
        Assert.NotNull(ShareSettings.Validate(config, new ShareConfig { Name = "otro", Location = "/Fotos", Path = dir.Path }, null));
        Assert.NotNull(ShareSettings.Validate(config, new ShareConfig { Name = "otro", Location = "/y", Path = Path.Combine(dir.Path, "no") }, null));
        Assert.NotNull(ShareSettings.Validate(config, new ShareConfig { Name = "con espacio", Location = "/y", Path = dir.Path }, null));
        // Editarse a si mismo no choca con su propio nombre ni location.
        Assert.Null(ShareSettings.Validate(config, new ShareConfig { Name = "fotos", Location = "/fotos", Path = dir.Path }, "fotos"));
        // Un share desactivado puede compartir location.
        Assert.Null(ShareSettings.Validate(config, new ShareConfig { Name = "otro", Location = "/fotos", Path = dir.Path, Enabled = false }, null));
    }

    [Fact]
    public void CloneIsIndependent()
    {
        var config = SunfishConfig.Parse("# comentario\n[Share a]\nLocation = /a\nPath = C:\\a\n");
        var copy = config.Clone();
        copy.Shares[0].Upload = true;
        Assert.False(config.Shares[0].Upload);
        Assert.Contains("# comentario", copy.ToText());
    }
}

public class HostTests
{
    private static int FreePort()
    {
        var l = new TcpListener(IPAddress.Loopback, 0);
        l.Start();
        int port = ((IPEndPoint)l.LocalEndpoint).Port;
        l.Stop();
        return port;
    }

    [Fact]
    public async Task ApplyRestartsOnPortChange()
    {
        var config = new SunfishConfig();
        config.Server.Bind = [IPAddress.Loopback];
        config.Server.HttpPort = FreePort();
        config.Server.Ftp = false;
        config.Server.Announce = false;
        await using var host = new SunfishHost(config);
        host.Start();
        int first = host.HttpPort;

        var copy = config.Clone();
        copy.Server.HttpPort = FreePort();
        Assert.True(await host.ApplyAsync(copy));
        Assert.True(host.HttpRunning);
        Assert.Equal(copy.Server.HttpPort, host.HttpPort);
        Assert.NotEqual(first, host.HttpPort);
    }

    [Fact]
    public async Task ApplyReportsBusyPorts()
    {
        var blocker = new TcpListener(IPAddress.Loopback, 0);
        blocker.Start();
        try
        {
            var config = new SunfishConfig();
            config.Server.Bind = [IPAddress.Loopback];
            config.Server.HttpPort = FreePort();
            config.Server.Ftp = false;
            config.Server.Announce = false;
            await using var host = new SunfishHost(config);
            host.Start();
            var copy = config.Clone();
            copy.Server.HttpPort = ((IPEndPoint)blocker.LocalEndpoint).Port;
            Assert.False(await host.ApplyAsync(copy));
            Assert.False(host.HttpRunning);
        }
        finally
        {
            blocker.Stop();
        }
    }

    [Fact]
    public async Task ReloadsTheFileWhenAnotherProgramChangesIt()
    {
        using var dir = new TempDir();
        string path = Path.Combine(dir.Path, "sunfish.conf");
        var config = new SunfishConfig();
        config.Shares.Add(new ShareConfig { Name = "a", Location = "/a", Path = dir.Path });
        config.Save(path);

        await using var host = new SunfishHost(SunfishConfig.Load(path));
        host.WatchConfigFile();
        var changed = new TaskCompletionSource();
        host.Changed += () => changed.TrySetResult();

        File.AppendAllText(path, "\n[Share b]\nLocation = /b\nPath = " + dir.Path + "\n");
        await changed.Task.WaitAsync(TimeSpan.FromSeconds(10));
        Assert.Equal(["a", "b"], host.Config.Shares.Select(s => s.Name));
    }

    [Fact]
    public async Task OverridesSurviveReloads()
    {
        using var dir = new TempDir();
        string path = Path.Combine(dir.Path, "sunfish.conf");
        new SunfishConfig().Save(path);
        await using var host = new SunfishHost(SunfishConfig.Load(path)) { Overrides = c => c.Server.HttpPort = 9999 };
        host.WatchConfigFile();
        var changed = new TaskCompletionSource();
        host.Changed += () => changed.TrySetResult();

        File.AppendAllText(path, "\n[Server]\nName = Otro\n");
        await changed.Task.WaitAsync(TimeSpan.FromSeconds(10));
        Assert.Equal("Otro", host.Config.Server.Name);
        Assert.Equal(9999, host.Config.Server.HttpPort);
    }

    [Fact]
    public async Task OwnSavesAreNotReloadedTwice()
    {
        using var dir = new TempDir();
        string path = Path.Combine(dir.Path, "sunfish.conf");
        new SunfishConfig().Save(path);
        await using var host = new SunfishHost(SunfishConfig.Load(path));
        host.WatchConfigFile();
        int changes = 0;
        host.Changed += () => Interlocked.Increment(ref changes);

        var copy = host.Config.Clone();
        copy.Server.Name = "Nuevo";
        await host.SaveAndApplyAsync(copy);
        await Task.Delay(1200);
        Assert.Equal(1, changes);
        Assert.Equal("Nuevo", host.Config.Server.Name);
    }
}