260 lines
11 KiB
C#
260 lines
11 KiB
C#
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Data.Sqlite;
|
|
using Xunit;
|
|
using ytLive.Models;
|
|
using ytLive.Services;
|
|
using ytLive.ViewModels;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
public class SocialBarTests
|
|
{
|
|
private static string TempDbPath()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"ytLlive-social-test-{System.Guid.NewGuid():N}.db");
|
|
SqliteConnection.ClearAllPools();
|
|
if (File.Exists(path)) File.Delete(path);
|
|
return path;
|
|
}
|
|
|
|
/// <summary>Resolves every fediverse domain as Mastodon — the heal test only
|
|
/// cares that a missing stored software name gets filled in and persisted.</summary>
|
|
private sealed class MastodonValidator : ISocialValidator
|
|
{
|
|
public Task<string?> ResolveFediverseSoftwareAsync(string domain, CancellationToken ct)
|
|
=> Task.FromResult<string?>("mastodon");
|
|
|
|
public Task<SocialLookupResult> LookupAsync(SocialService service, string handleOrUrl, CancellationToken ct)
|
|
=> throw new System.NotSupportedException();
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialEntry_FediverseSoftware_SettableUpdatesLogo()
|
|
{
|
|
var entry = new SocialEntry
|
|
{
|
|
Service = SocialService.Fediverse,
|
|
Handle = "@gramps@llamachile.tube",
|
|
ProfileUrl = "https://llamachile.tube/@gramps",
|
|
};
|
|
var honeycomb = entry.LogoData;
|
|
entry.FediverseSoftware = "mastodon";
|
|
Assert.Equal(SocialServiceIcons.LogoDataForFediverse("mastodon"), entry.LogoData);
|
|
Assert.NotEqual(honeycomb, entry.LogoData);
|
|
}
|
|
|
|
/// <summary>The single integration test for this branch: a fediverse entry
|
|
/// persisted with a NULL software name is loaded, healed via the real validator
|
|
/// seam, and the resolved name is persisted back — surviving a second load.</summary>
|
|
[Fact]
|
|
public async Task Socials_HealMissingFediverseSoftware_RoundTripsThroughDb()
|
|
{
|
|
var path = TempDbPath();
|
|
try
|
|
{
|
|
var socials = new SocialsConfig();
|
|
socials.Entries.Add(new SocialEntry
|
|
{
|
|
Service = SocialService.Fediverse,
|
|
Handle = "@gramps@llamachile.tube",
|
|
ProfileUrl = "https://llamachile.tube/@gramps",
|
|
});
|
|
using (var store = new LayoutStore(path))
|
|
store.Save(new[] { new Scene { Name = "Live" } }, null, socials);
|
|
|
|
SocialsConfig? loaded;
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
store.Load();
|
|
loaded = store.Socials;
|
|
Assert.NotNull(loaded);
|
|
Assert.Null(loaded!.Entries[0].FediverseSoftware);
|
|
}
|
|
|
|
var healed = await MainViewModel.HealFediverseSoftwareAsync(loaded!, new MastodonValidator());
|
|
Assert.Single(healed);
|
|
Assert.Equal("mastodon", healed["@gramps@llamachile.tube"]);
|
|
loaded.Entries[0].FediverseSoftware = healed["@gramps@llamachile.tube"];
|
|
|
|
using (var store = new LayoutStore(path))
|
|
store.Save(new[] { new Scene { Name = "Live" } }, null, loaded);
|
|
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
store.Load();
|
|
var again = store.Socials;
|
|
Assert.NotNull(again);
|
|
Assert.Equal("mastodon", again!.Entries[0].FediverseSoftware);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SqliteConnection.ClearAllPools();
|
|
if (File.Exists(path)) File.Delete(path);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialsConfig_RoundTrip_PersistsEntriesAndBarSettings()
|
|
{
|
|
var path = TempDbPath();
|
|
try
|
|
{
|
|
var socials = new SocialsConfig
|
|
{
|
|
BarPosition = SocialBarPosition.Top,
|
|
BarEnabled = false,
|
|
};
|
|
socials.Entries.Add(new SocialEntry
|
|
{
|
|
Service = SocialService.YouTube,
|
|
Handle = "mychannel",
|
|
ProfileUrl = "https://www.youtube.com/@mychannel",
|
|
});
|
|
socials.Entries.Add(new SocialEntry
|
|
{
|
|
Service = SocialService.Twitch,
|
|
Handle = "streamer123",
|
|
ProfileUrl = "https://www.twitch.tv/streamer123",
|
|
});
|
|
socials.Entries.Add(new SocialEntry
|
|
{
|
|
Service = SocialService.Fediverse,
|
|
Handle = "@gramps@llamachile.tube",
|
|
ProfileUrl = "https://llamachile.tube/@gramps",
|
|
FediverseSoftware = "peertube",
|
|
});
|
|
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
var scene = new Scene { Name = "Live" };
|
|
scene.HasSocialBar = true;
|
|
store.Save(new[] { scene }, null, socials);
|
|
}
|
|
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
var scenes = store.Load();
|
|
Assert.NotNull(store.Socials);
|
|
Assert.Equal(3, store.Socials!.Entries.Count);
|
|
Assert.Equal(SocialService.YouTube, store.Socials.Entries[0].Service);
|
|
Assert.Equal("mychannel", store.Socials.Entries[0].Handle);
|
|
Assert.Equal("https://www.youtube.com/@mychannel", store.Socials.Entries[0].ProfileUrl);
|
|
Assert.Equal(SocialService.Twitch, store.Socials.Entries[1].Service);
|
|
Assert.Equal(SocialService.Fediverse, store.Socials.Entries[2].Service);
|
|
Assert.Equal("@gramps@llamachile.tube", store.Socials.Entries[2].Handle);
|
|
Assert.Equal("peertube", store.Socials.Entries[2].FediverseSoftware);
|
|
Assert.Equal(SocialBarPosition.Top, store.Socials.BarPosition);
|
|
Assert.False(store.Socials.BarEnabled);
|
|
Assert.True(scenes[0].HasSocialBar);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SqliteConnection.ClearAllPools();
|
|
if (File.Exists(path)) File.Delete(path);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialsConfig_EmptyEntries_NotPersisted()
|
|
{
|
|
var path = TempDbPath();
|
|
try
|
|
{
|
|
var socials = new SocialsConfig();
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
store.Save(new[] { new Scene { Name = "Live" } }, null, socials);
|
|
}
|
|
|
|
using (var store = new LayoutStore(path))
|
|
{
|
|
Assert.Null(store.Socials);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
SqliteConnection.ClearAllPools();
|
|
if (File.Exists(path)) File.Delete(path);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialsConfig_DefaultBarEnabled_IsOn()
|
|
{
|
|
Assert.True(new SocialsConfig().BarEnabled);
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialServiceIcons_CanonicalUrlFor_AllServices()
|
|
{
|
|
Assert.Equal("https://www.youtube.com/@test", SocialServiceIcons.CanonicalUrlFor(SocialService.YouTube, "test"));
|
|
Assert.Equal("https://www.youtube.com/@test", SocialServiceIcons.CanonicalUrlFor(SocialService.YouTube, "@test"));
|
|
Assert.Equal("https://x.com/user", SocialServiceIcons.CanonicalUrlFor(SocialService.X, "user"));
|
|
Assert.Equal("https://github.com/dev", SocialServiceIcons.CanonicalUrlFor(SocialService.GitHub, "dev"));
|
|
Assert.Equal("https://example.com", SocialServiceIcons.CanonicalUrlFor(SocialService.Website, "example.com"));
|
|
Assert.Equal("https://example.com", SocialServiceIcons.CanonicalUrlFor(SocialService.Website, "https://example.com"));
|
|
Assert.Equal("https://site.tld/@user", SocialServiceIcons.CanonicalUrlFor(SocialService.Link, "@user@site.tld"));
|
|
Assert.Equal("https://site.tld/@user", SocialServiceIcons.CanonicalUrlFor(SocialService.Fediverse, "@user@site.tld"));
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialServiceIcons_LogoData_AllServices()
|
|
{
|
|
foreach (var service in Enum.GetValues<SocialService>())
|
|
Assert.False(string.IsNullOrWhiteSpace(SocialServiceIcons.LogoDataFor(service)), service.ToString());
|
|
Assert.NotEmpty(SocialServiceIcons.LockedIconData);
|
|
Assert.NotEmpty(SocialServiceIcons.DoNotIconData);
|
|
Assert.NotEqual(SocialServiceIcons.LogoDataFor(SocialService.YouTube), SocialServiceIcons.LogoDataFor(SocialService.Twitch));
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialServiceIcons_LogoDataForFediverse_KnownAndUnknownSoftware()
|
|
{
|
|
Assert.Equal(SocialServiceIcons.LogoDataForFediverse("peertube"), SocialServiceIcons.LogoDataForFediverse("PeerTube"));
|
|
Assert.Equal(SocialServiceIcons.LogoDataForFediverse("mastodon"), SocialServiceIcons.LogoDataForFediverse("MASTODON"));
|
|
Assert.NotEqual(
|
|
SocialServiceIcons.LogoDataForFediverse("peertube"),
|
|
SocialServiceIcons.LogoDataForFediverse("mastodon"));
|
|
Assert.Equal(SocialServiceIcons.LogoDataForFediverse(null), SocialServiceIcons.LogoDataForFediverse("gotosocial"));
|
|
Assert.NotEqual(SocialServiceIcons.LogoDataForFediverse("peertube"), SocialServiceIcons.LogoDataForFediverse("unknown"));
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialEntry_FediverseLogo_ComesFromSoftwareName()
|
|
{
|
|
var entry = new SocialEntry
|
|
{
|
|
Service = SocialService.Fediverse,
|
|
Handle = "@gramps@llamachile.tube",
|
|
ProfileUrl = "https://llamachile.tube/@gramps",
|
|
FediverseSoftware = "peertube",
|
|
};
|
|
Assert.Equal(SocialServiceIcons.LogoDataForFediverse("peertube"), entry.LogoData);
|
|
Assert.NotEqual(SocialServiceIcons.LogoDataFor(SocialService.Link), entry.LogoData);
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialServiceIcons_DetectService_ByDomain()
|
|
{
|
|
Assert.Equal(SocialService.YouTube, SocialServiceIcons.DetectService("youtube.com/@handle").service);
|
|
Assert.Equal(SocialService.X, SocialServiceIcons.DetectService("https://x.com/user").service);
|
|
Assert.Equal(SocialService.Instagram, SocialServiceIcons.DetectService("instagram.com/user").service);
|
|
var fediverse = SocialServiceIcons.DetectService("@user@instance.tube");
|
|
Assert.Equal(SocialService.Fediverse, fediverse.service);
|
|
Assert.Equal("@user@instance.tube", fediverse.handle);
|
|
Assert.Equal("https://instance.tube/@user", fediverse.url);
|
|
Assert.Equal(SocialService.GitHub, SocialServiceIcons.DetectService("https://github.com/dev").service);
|
|
Assert.Equal(SocialService.Website, SocialServiceIcons.DetectService("example.com/path").service);
|
|
}
|
|
|
|
[Fact]
|
|
public void SocialServiceIcons_DetectService_BareHandle_FallsBackToWebsite()
|
|
{
|
|
Assert.Equal(SocialService.Website, SocialServiceIcons.DetectService("justaname").service);
|
|
}
|
|
}
|