using MongoDB.Bson; using MongoDB.Driver; using MongoDB.Entities; using PrivaPub.Infrastructure.Statistics; using PrivaPub.Models.Statistics; using PrivaPub.Tests.Support; using PrivaPub.Tests.Support.Host; using System.Net; using System.Text.Json.Nodes; namespace PrivaPub.Tests.Statistics { [Trait("Category", "Integration")] public sealed class LedgerOverHttpTests : IAsyncLifetime { PrivaPubHost _host; Peer _peer; public async ValueTask InitializeAsync() { Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip); _host = await PrivaPubHost.Shared(); _peer = await Peer.Start(); } public async ValueTask DisposeAsync() { if (_peer != default) await _peer.DisposeAsync(); } JsonObject DirectNote(RemoteActor sender, Persona persona, string text) { var noteId = $"{_peer.A}/notes/{Guid.NewGuid():N}"; var to = new JsonArray($"{PrivaPubHost.Base}/peasants/{persona.UserName}"); return new JsonObject { ["id"] = noteId + "/activity", ["type"] = "Create", ["actor"] = sender.Id, ["to"] = to.DeepClone(), ["object"] = new JsonObject { ["id"] = noteId, ["type"] = "Note", ["attributedTo"] = sender.Id, ["to"] = to.DeepClone(), ["content"] = text } }; } async Task> StoredSince(DateTime since, CancellationToken token) { await _host.Get().Flush(token); return await DB.Default.Database().GetCollection(nameof(InteractionEvent)) .Find(Builders.Filter.Gte(nameof(InteractionEvent.At), since) & Builders.Filter.In(nameof(InteractionEvent.Host), new[] { "127.0.0.1", Interactions.Unknown })) .ToListAsync(token); } [Fact] public async Task A_delivery_and_its_refusals_are_recorded_naming_only_the_server() { var token = TestContext.Current.CancellationToken; var since = DateTime.UtcNow.AddSeconds(-1); var persona = await _host.Persona(await _host.SignUp(), "ledger"); var bob = new RemoteActor(_peer, "bob"); using var client = _host.Client(); Assert.Equal(HttpStatusCode.Accepted, (await client.SendAsync(bob.SignedPost($"/peasants/{persona.UserName}/mouth", DirectNote(bob, persona, "psst")), token)).StatusCode); var forged = bob.SignedPost("/human-centipede", DirectNote(bob, persona, "forged")); forged.Headers.Remove("Signature"); forged.Headers.TryAddWithoutValidation("Signature", bob.SignedPost("/elsewhere", new JsonObject()).Headers.GetValues("Signature").Single()); Assert.Equal(HttpStatusCode.Unauthorized, (await client.SendAsync(forged, token)).StatusCode); Assert.Equal(HttpStatusCode.NotFound, (await client.SendAsync(bob.SignedPost($"/peasants/nobody{Guid.NewGuid():N}"[..20] + "/mouth", DirectNote(bob, persona, "lost")), token)).StatusCode); var stored = await StoredSince(since, token); var reasons = stored.Select(e => e.GetValue(nameof(InteractionEvent.Reason), BsonNull.Value).ToString()).ToList(); Assert.Contains("queued", reasons); Assert.Contains("signature-invalid", reasons); Assert.Contains("unknown-recipient", reasons); var everything = string.Join("\n", stored.Select(e => e.ToJson())); Assert.DoesNotContain(persona.UserName, everything); Assert.DoesNotContain(persona.Id, everything); Assert.DoesNotContain(persona.Root.Id, everything); Assert.DoesNotContain(bob.Name, everything); Assert.DoesNotContain("/notes/", everything); } } }