using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging.Abstractions; using MongoDB.Entities; using PrivaPub.Domain.Content; using PrivaPub.Domain.Social; using PrivaPub.Federation.Actors; using PrivaPub.Federation.Inbox; using PrivaPub.Federation.Inbox.Handlers; using PrivaPub.Federation.Outbox; using PrivaPub.Infrastructure.Jobs; using PrivaPub.Models; using PrivaPub.Models.Federation; using PrivaPub.Models.Jobs; using PrivaPub.Models.User; using PrivaPub.Resources; using PrivaPub.Services; using PrivaPub.StaticServices; using System.Text.Json; using System.Text.Json.Nodes; namespace PrivaPub.Tests.Support { public sealed class Harness : IAsyncDisposable { public const string Host = "privapub.test"; public const string Base = "https://" + Host; Harness(Peer peer) { Peer = peer; var cache = new MemoryCache(new MemoryCacheOptions()); Local = new LocalActorService(Db, new StaticOptions(new AppConfiguration { BackendBaseAddress = Base })); Remote = new RemoteActorService(Peer.Http(cache), Local, cache, Db); Delivery = new DeliveryService(Db, Queue); Receiver = new InboxReceiver(Local, Remote, Queue, new NoBlocks(), NullLogger.Instance); Processor = new InboxProcessor(Remote, new IActivityHandler[] { new FollowHandler(Db, Local, Remote, Delivery), new AcceptHandler(Db, Local), new RejectHandler(Db, Local), new UndoHandler(Db, Local, Remote, Delivery), new CreateHandler(Db, Local, Remote, Delivery, new NoBlocks()), new DeleteHandler(Db, Local, Remote, Delivery), new UpdateHandler(Db, Local, Remote) }, NullLogger.Instance); Follows = new FollowService(Db, Local, Remote, Delivery, new KeyLocalizer(), NullLogger.Instance); Content = new ContentRenderer(Local, Remote); Outbox = new OutboxPublisher(Db, Local, Delivery); Posts = new PostsService(Db, Local, Remote, Delivery, Content, Outbox, new KeyLocalizer(), NullLogger.Instance); } public Peer Peer { get; } public DbEntities Db { get; } = new(); public JobQueue Queue { get; } = new(); public LocalActorService Local { get; } public RemoteActorService Remote { get; } public DeliveryService Delivery { get; } public InboxReceiver Receiver { get; } public InboxProcessor Processor { get; } public FollowService Follows { get; } public ContentRenderer Content { get; } public OutboxPublisher Outbox { get; } public PostsService Posts { get; } public async Task FollowedBy(LocalActor local, RemoteActor follower) => await DB.Default.SaveAsync(new Follower { LocalActorId = local.Id, LocalActorKind = local.Kind, ActorURI = follower.Id, InboxURL = follower.Id + "/inbox", SharedInboxURL = follower.SharedInbox }); public static async Task Start() => new(await Peer.Start()); public async Task<(string RootId, LocalActor Avatar)> Persona(string name, string rootId = default) { rootId ??= Guid.NewGuid().ToString("N")[..24]; var (privateKey, publicKey) = Keys.NewKeyPair(); var avatar = new Avatar { UserName = $"{name}{Guid.NewGuid():N}"[..20], Name = name, PrivateKey = privateKey, PublicKey = publicKey }; await DB.Default.SaveAsync(avatar); await DB.Default.SaveAsync(new RootToAvatar { RootId = rootId, AvatarId = avatar.ID }); return (rootId, Local.FromAvatar(avatar)); } public async Task Deliver(RemoteActor sender, string path, JsonNode activity) { var result = await Receiver.Receive(sender.Post(Host, path, activity), default, CancellationToken.None); var dedupe = "inbox|" + (activity is JsonObject ? activity["id"]?.GetValue() : default); var job = await DB.Default.Find().Match(j => j.DedupeKey == dedupe).ExecuteFirstAsync(); if (job != default) Assert.Equal(JobResult.Done, (await Processor.Handle(job, CancellationToken.None)).Result); return result; } public async Task> Outgoing(string inbox) => (await DB.Default.Find().Match(j => j.Kind == JobKind.Deliver).ExecuteAsync()) .Select(j => JsonSerializer.Deserialize(j.Payload)) .Where(p => p.Inbox == inbox) .Select(p => JsonNode.Parse(p.Body)!.AsObject()) .ToList(); public async ValueTask DisposeAsync() => await Peer.DisposeAsync(); } public sealed class KeyLocalizer : IStringLocalizer { public LocalizedString this[string name] => new(name, name); public LocalizedString this[string name, params object[] arguments] => new(name, string.Format(name, arguments)); public IEnumerable GetAllStrings(bool includeParentCultures) => Enumerable.Empty(); } }