Files
SocialPub/PrivaPub.Tests/Support/Host/FederationHelpers.cs
T

142 lines
5.8 KiB
C#

using MongoDB.Entities;
using PrivaPub.Domain.Statuses;
using PrivaPub.Federation.Actors;
using PrivaPub.Models.Federation;
using PrivaPub.Models.Group;
using PrivaPub.Models.Post;
using System.Net;
using System.Net.Http.Json;
using System.Text.Json.Nodes;
using GroupEntity = PrivaPub.Models.Group.Group;
namespace PrivaPub.Tests.Support.Host
{
public sealed record Fetched(HttpStatusCode Status, string MediaType, string Text, HttpResponseMessage Response)
{
public JsonObject Json => JsonNode.Parse(Text)!.AsObject();
public string Location => Response.Headers.Location?.OriginalString;
}
public sealed record LocalGroup(string Id, string UserName)
{
public string Uri => $"{PrivaPubHost.Base}/peasants/{UserName}";
}
public static class FederationHelpers
{
public const string ActivityJson = "application/activity+json";
public const string LdJson = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
public const string Browser = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
public static string ActorUri(this Persona persona) => $"{PrivaPubHost.Base}/peasants/{persona.UserName}";
public static async Task<Fetched> Fetch(this HttpClient client, string path, string accept = ActivityJson)
{
using var request = new HttpRequestMessage(HttpMethod.Get, path);
if (accept != default)
request.Headers.TryAddWithoutValidation("Accept", accept);
return await client.Fetch(request);
}
public static async Task<Fetched> Fetch(this HttpClient client, HttpRequestMessage request)
{
var response = await client.SendAsync(request, TestContext.Current.CancellationToken);
var text = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
return new Fetched(response.StatusCode, response.Content.Headers.ContentType?.MediaType, text, response);
}
public static async Task<LocalActor> Actor(this PrivaPubHost host, Persona persona) =>
await host.Get<ILocalActorService>().FindById(LocalActorKind.Person, persona.Id, TestContext.Current.CancellationToken);
public static async Task<Post> Publish(this PrivaPubHost host, Persona persona, StatusDraft draft)
{
var outcome = await host.Get<IStatusService>().Publish(await host.Actor(persona), draft, TestContext.Current.CancellationToken);
Assert.True(outcome.Ok, outcome.Error);
return outcome.Post;
}
public static Task<Post> Publish(this PrivaPubHost host, Persona persona, string text, PostVisibility visibility = PostVisibility.Public) =>
host.Publish(persona, new StatusDraft { Text = text, PlainText = true, Visibility = visibility });
public static Task<Post> PublishLocated(this PrivaPubHost host, Persona persona, string text) =>
host.Publish(persona, new StatusDraft { Text = text, PlainText = true, Latitude = 45.46, Longitude = 9.19, RangeKm = 5 });
public static async Task Remove(this PrivaPubHost host, Persona persona, Post post)
{
var outcome = await host.Get<IStatusService>().Remove(await host.Actor(persona), post.ID, TestContext.Current.CancellationToken);
Assert.True(outcome.Ok, outcome.Error);
}
public static async Task<Post> Reblog(this PrivaPubHost host, Persona persona, Post original)
{
var outcome = await host.Get<IStatusService>().Reblog(await host.Actor(persona), original.ID, true, PostVisibility.Public, TestContext.Current.CancellationToken);
Assert.True(outcome.Ok, outcome.Error);
return outcome.Post;
}
public static async Task<Post> FederatedCopy(this PrivaPubHost host, Persona persona)
{
var post = new Post
{
GroupUserId = persona.Id,
AuthorAccountId = persona.Id,
IsFederatedCopy = true,
Visibility = PostVisibility.Public,
ObjectURI = $"https://remote.example/notes/{Guid.NewGuid():N}",
ActorURI = "https://remote.example/users/someone",
ContentHtml = "<p>a copy that is not ours to serve</p>"
};
await DB.Default.SaveAsync(post, TestContext.Current.CancellationToken);
return post;
}
public static async Task<LocalGroup> FederatedGroup(this PrivaPubHost host, Persona owner, bool community, string name = default)
{
var userName = $"{name ?? (community ? "community" : "circle")}{Guid.NewGuid():N}"[..20];
using var client = host.As(owner.Root.Jwt);
var response = await client.PostAsJsonAsync("/clientapi/group/insert",
new { avatarId = owner.Id, userName, name = userName, description = "testing", isCommunity = community, postingPolicy = community ? "anyone" : default },
TestContext.Current.CancellationToken);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var group = (await response.Content.ReadFromJsonAsync<JsonObject>(TestContext.Current.CancellationToken))!;
return new LocalGroup(group["id"]!.GetValue<string>(), userName);
}
public static async Task AddForeignMember(this LocalGroup group, string actorUri) =>
await DB.Default.Update<GroupEntity>().MatchID(group.Id)
.Modify(b => b.Push(g => g.Members, new GroupMember { AvatarId = actorUri, IsForeign = true }))
.ExecuteAsync(TestContext.Current.CancellationToken);
public static IEnumerable<JsonNode> Nodes(JsonNode node)
{
if (node == default)
yield break;
yield return node;
switch (node)
{
case JsonObject obj:
foreach (var (_, value) in obj)
foreach (var inner in Nodes(value))
yield return inner;
break;
case JsonArray array:
foreach (var item in array)
foreach (var inner in Nodes(item))
yield return inner;
break;
}
}
public static IEnumerable<string> Keys(JsonNode node) =>
Nodes(node).OfType<JsonObject>().SelectMany(o => o.Select(p => p.Key));
public static IEnumerable<string> Strings(JsonNode node) =>
Nodes(node).OfType<JsonValue>().Where(v => v.TryGetValue<string>(out _)).Select(v => v.GetValue<string>());
public static string PathOf(string url) => new Uri(url).PathAndQuery;
}
}