using MongoDB.Entities; using PrivaPub.Federation.Actors; using PrivaPub.Models.Jobs; using PrivaPub.Models.Post; using PrivaPub.Models.Social; using PrivaPub.Tests.Support; using System.Text.Json.Nodes; namespace PrivaPub.Tests.Federation { // a follow that holds reads the account's latest public posts from its outbox, so its profile is not empty [Trait("Category", "Integration")] public sealed class BackfillTests : IAsyncLifetime { Harness _harness; public async ValueTask InitializeAsync() { Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip); _harness = await Harness.Start(); } public async ValueTask DisposeAsync() { if (_harness != default) await _harness.DisposeAsync(); } [Fact] public async Task An_accepted_follow_reads_the_accounts_latest_public_posts_from_its_outbox() { var token = TestContext.Current.CancellationToken; var (_, alice) = await _harness.Persona("alice"); var bob = new RemoteActor(_harness.Peer, "bob"); var mallory = new RemoteActor(_harness.Peer, "mallory", _harness.Peer.B); var origin = new Uri(bob.Id).GetLeftPart(UriPartial.Authority); JsonObject Note(string id, string text, RemoteActor author) => new() { ["id"] = id, ["type"] = "Note", ["attributedTo"] = author.Id, ["content"] = $"
{text}
", ["to"] = new JsonArray("https://www.w3.org/ns/activitystreams#Public"), ["published"] = DateTime.UtcNow.ToString("O") }; var older = $"{origin}/notes/{Guid.NewGuid():N}"; var newer = $"{origin}/notes/{Guid.NewGuid():N}"; var notBobs = $"{new Uri(mallory.Id).GetLeftPart(UriPartial.Authority)}/notes/{Guid.NewGuid():N}"; _harness.Peer.Serve(new Uri(older).AbsolutePath, Note(older, "an older post", bob).ToJsonString()); _harness.Peer.Serve(new Uri(newer).AbsolutePath, Note(newer, "a newer post", bob).ToJsonString()); _harness.Peer.Serve(new Uri(bob.Id).AbsolutePath + "/outbox", new JsonObject { ["id"] = bob.Id + "/outbox", ["type"] = "OrderedCollection", ["totalItems"] = 4, ["orderedItems"] = new JsonArray( new JsonObject { ["type"] = "Create", ["object"] = Note(newer, "a newer post", bob) }, new JsonObject { ["type"] = "Announce", ["object"] = notBobs }, new JsonObject { ["type"] = "Create", ["object"] = notBobs }, new JsonObject { ["type"] = "Create", ["object"] = older }) }.ToJsonString()); var account = await _harness.Remote.GetActor(bob.Id, refresh: false, token); var followId = alice.ActivityUri($"follow-{Guid.NewGuid():N}"); await DB.Default.SaveAsync(new Following { AvatarId = alice.Id, TargetActorURI = bob.Id, TargetAccountId = account.ID, State = FollowState.Requested, FollowActivityURI = followId }, token); await _harness.Deliver(bob, "/human-centipede", new JsonObject { ["id"] = $"{bob.Id}#accept-{Guid.NewGuid():N}", ["type"] = "Accept", ["actor"] = bob.Id, ["object"] = new JsonObject { ["id"] = followId, ["type"] = "Follow", ["actor"] = alice.Uri, ["object"] = bob.Id } }); var job = await DB.Default.Find