Files
ytLlive/ytLive.Tests/SocialValidatorTests.cs
T

209 lines
7.5 KiB
C#

using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using ytLive.Models;
using ytLive.Services;
namespace ytLive.Tests;
public class SocialValidatorTests
{
private sealed class StubHandler : HttpMessageHandler
{
private readonly Func<HttpRequestMessage, HttpResponseMessage> _respond;
public StubHandler(HttpResponseMessage response) : this(_ => response) { }
public StubHandler(Func<HttpRequestMessage, HttpResponseMessage> respond) => _respond = respond;
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
{
if (ct.IsCancellationRequested)
return Task.FromCanceled<HttpResponseMessage>(ct);
return Task.FromResult(_respond(request));
}
}
private static HttpSocialValidator Validator(HttpMessageHandler handler)
=> new(new HttpClient(handler));
[Fact]
public async Task FediverseHandle_BuildsHostUrl()
{
var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.OK)));
var result = await validator.LookupAsync(SocialService.Fediverse, "@user@site.tld", CancellationToken.None);
Assert.True(result.Success);
Assert.Equal("https://site.tld/@user", result.ProfileUrl);
Assert.Equal("@user@site.tld", result.Handle);
}
[Fact]
public async Task FediverseHandle_FetchesInstanceSoftware()
{
var validator = Validator(new StubHandler(RespondFediverse("peertube")));
var result = await validator.LookupAsync(SocialService.Fediverse, "@gramps@llamachile.tube", CancellationToken.None);
Assert.True(result.Success);
Assert.Equal("peertube", result.FediverseSoftware);
}
[Fact]
public async Task FediverseHandle_RootRedirectToSubdomain_ResolvesSoftware()
{
var validator = Validator(new StubHandler(request =>
{
var uri = request.RequestUri!;
if (uri.Host == "site.tld")
{
if (uri.AbsolutePath == "/")
{
// Identity domain's default app redirects to the real instance.
return new HttpResponseMessage(HttpStatusCode.OK)
{
RequestMessage = new HttpRequestMessage(HttpMethod.Get, "https://social.site.tld/"),
};
}
if (uri.AbsolutePath.StartsWith("/@"))
return new HttpResponseMessage(HttpStatusCode.OK); // profile validates
return new HttpResponseMessage(HttpStatusCode.NotFound); // nodeinfo SSO-blocked
}
if (uri.Host == "social.site.tld")
{
if (uri.AbsolutePath.StartsWith("/.well-known"))
return Json(new { links = new[] { new { rel = "http://nodeinfo.diaspora.software/ns/schema/2.0", href = "https://social.site.tld/nodeinfo/2.0" } } });
if (uri.AbsolutePath.StartsWith("/nodeinfo"))
return Json(new { software = new { name = "mastodon", version = "4.6.3" } });
}
return new HttpResponseMessage(HttpStatusCode.OK);
}));
var result = await validator.LookupAsync(SocialService.Fediverse, "@user@site.tld", CancellationToken.None);
Assert.True(result.Success);
Assert.Equal("mastodon", result.FediverseSoftware);
}
[Fact]
public async Task FediverseHandle_NodeInfoUnavailable_StillSucceeds()
{
var validator = Validator(new StubHandler(request =>
request.RequestUri!.AbsolutePath.StartsWith("/.well-known")
? new HttpResponseMessage(HttpStatusCode.NotFound)
: new HttpResponseMessage(HttpStatusCode.OK)));
var result = await validator.LookupAsync(SocialService.Fediverse, "@user@site.tld", CancellationToken.None);
Assert.True(result.Success);
Assert.Null(result.FediverseSoftware);
}
[Fact]
public async Task FediverseHandle_CanceledDuringNodeInfo_IsCanceled()
{
using var cts = new CancellationTokenSource();
var request = 0;
var validator = Validator(new StubHandler(_ =>
{
if (++request >= 2) cts.Cancel();
return new HttpResponseMessage(HttpStatusCode.OK);
}));
var result = await validator.LookupAsync(SocialService.Fediverse, "@user@site.tld", cts.Token);
Assert.True(result.Canceled);
Assert.False(result.Success);
}
private static Func<HttpRequestMessage, HttpResponseMessage> RespondFediverse(string software)
{
return request =>
{
if (request.RequestUri!.AbsolutePath.StartsWith("/.well-known"))
{
return Json(new
{
links = new[]
{
new
{
rel = "http://nodeinfo.diaspora.software/ns/schema/2.0",
href = $"https://{request.RequestUri.Host}/nodeinfo/2.0",
},
},
});
}
if (request.RequestUri!.AbsolutePath.StartsWith("/nodeinfo"))
{
return Json(new { software = new { name = software, version = "1.0.0" } });
}
return new HttpResponseMessage(HttpStatusCode.OK);
};
}
private static HttpResponseMessage Json(object payload)
=> new(HttpStatusCode.OK) { Content = new StringContent(System.Text.Json.JsonSerializer.Serialize(payload)) };
[Fact]
public async Task KnownServiceHandle_CanonicalUrl()
{
var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.OK)));
var result = await validator.LookupAsync(SocialService.X, "creator", CancellationToken.None);
Assert.True(result.Success);
Assert.Equal("https://x.com/creator", result.ProfileUrl);
Assert.Equal("creator", result.Handle);
}
[Fact]
public async Task NotFound_IsRejected()
{
var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.NotFound)));
var result = await validator.LookupAsync(SocialService.X, "doesnotexist", CancellationToken.None);
Assert.False(result.Success);
Assert.Contains("NotFound", result.Error);
}
[Fact]
public async Task Redirect_CountsAsExists()
{
var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.Found)));
var result = await validator.LookupAsync(SocialService.GitHub, "dev", CancellationToken.None);
Assert.True(result.Success);
}
[Fact]
public async Task ConnectionFailure_IsFriendlyError()
{
var validator = Validator(new StubHandler(_ => throw new HttpRequestException("boom")));
var result = await validator.LookupAsync(SocialService.X, "user", CancellationToken.None);
Assert.False(result.Success);
Assert.Contains("Couldn't reach", result.Error);
}
[Fact]
public async Task CanceledToken_AbortsRequest()
{
using var cts = new CancellationTokenSource();
cts.Cancel();
var validator = Validator(new StubHandler(new HttpResponseMessage(HttpStatusCode.OK)));
var result = await validator.LookupAsync(SocialService.X, "creator", cts.Token);
Assert.True(result.Canceled);
Assert.False(result.Success);
}
}