using Microsoft.AspNetCore.Http; using PrivaPub.Infrastructure.Http; using PrivaPub.Infrastructure.Statistics; using PrivaPub.Tests.Support; namespace PrivaPub.Tests.Statistics { public class HttpScopeTests { [Fact] public async Task Scopes_nest_and_restore_across_awaits() { Assert.Null(HttpScope.Purpose); Assert.Equal("request", HttpScope.Trigger); using (HttpScope.Triggered("deliver")) { using (HttpScope.Default("object")) { Assert.Equal("object", HttpScope.Purpose); using (HttpScope.For("actor")) { await Task.Yield(); Assert.Equal("actor", HttpScope.Purpose); using (HttpScope.Default("object")) Assert.Equal("actor", HttpScope.Purpose); } Assert.Equal("object", HttpScope.Purpose); } Assert.Null(HttpScope.Purpose); Assert.Equal("deliver", HttpScope.Trigger); Assert.False(HttpScope.Crawling); using (HttpScope.Crawl()) Assert.True(HttpScope.Crawling); } Assert.Equal("request", HttpScope.Trigger); } } public class TrafficMeterTests { [Theory] [InlineData("api/v1/accounts/{id}/follow", "api/v1/accounts")] [InlineData("/api/v1/statuses", "api/v1/statuses")] [InlineData("/oauth/token", "oauth/token")] [InlineData("/clientapi/avatar/private/insert", "clientapi/avatar/private")] [InlineData("api/{x}", "api")] [InlineData(null, "unmatched")] public void Client_routes_group_by_their_first_literal_segments(string template, string group) => Assert.Equal(group, TrafficMeter.Group(template)); [Theory] [InlineData("peasants/{actor}", "/peasants/alice", "actor")] [InlineData("peasants/{actor}/anus", "/peasants/alice/anus", "outbox")] [InlineData("peasants/{actor}/flock", "/peasants/c/flock", "collection")] [InlineData("peasants/{actor}/groupies", "/peasants/alice/groupies", "collection")] [InlineData("peasants/{actor}/scribbles/{postId}", "/peasants/alice/scribbles/1", "object")] [InlineData("peasants/{actor}/grunts/{activityId}", "/peasants/alice/grunts/1", "activity")] [InlineData("peasants/{actor}/parrot-licences/{licenceId}", "/peasants/alice/parrot-licences/1", "licence")] [InlineData("users/{actor}", "/users/alice", "actor")] [InlineData(".well-known/webfinger", "/.well-known/webfinger", "webfinger")] [InlineData("nodeinfo/2.1", "/nodeinfo/2.1", "nodeinfo")] [InlineData("@{user}", "/@alice", null)] public void Served_documents_are_counted_by_kind_never_by_name(string template, string path, string kind) => Assert.Equal(kind, TrafficMeter.Served(template, new PathString(path))); } public sealed class OutboundRequestTests : IAsyncLifetime { Peer _peer; public async ValueTask InitializeAsync() => _peer = await Peer.Start(); public async ValueTask DisposeAsync() => await _peer.DisposeAsync(); [Fact] public async Task A_federation_fetch_is_recorded_with_its_purpose_trigger_and_size() { var ledger = new MemoryLedger(); var http = Peer.Http(ledger: ledger); _peer.Serve("/users/a", "{\"id\":\"{A}/users/a\",\"type\":\"Person\"}"); using (HttpScope.Triggered("processinbox")) using (HttpScope.For("actor")) Assert.NotNull(await http.GetJson($"{_peer.A}/users/a", "application/activity+json", default, TestContext.Current.CancellationToken)); var e = Assert.Single(ledger.Of("http")); Assert.Equal(("127.0.0.1", "actor", "processinbox", "ok", 200), (e.Host, e.Purpose, e.Trigger, e.Outcome, e.Status!.Value)); Assert.True(e.Bytes > 10); Assert.Equal(0, e.Redirects); } [Fact] public async Task Refusals_are_recorded_and_a_second_try_is_remembered() { var ledger = new MemoryLedger(); var http = Peer.Http(ledger: ledger); _peer.Answer("/gone", 410); _peer.ServeText("/page", "", "text/html"); using (HttpScope.Triggered("fetchancestors")) { await http.GetJson($"{_peer.A}/gone", "application/activity+json", default, TestContext.Current.CancellationToken); await http.GetJson($"{_peer.A}/gone", "application/activity+json", default, TestContext.Current.CancellationToken); await http.GetJson($"{_peer.A}/page", "application/activity+json", default, TestContext.Current.CancellationToken); await http.GetJson("ftp://example.org/x", "application/activity+json", default, TestContext.Current.CancellationToken); } var events = ledger.Of("http"); Assert.Equal(new[] { ("refused", "410"), ("refused", "remembered"), ("refused", "content-type"), ("refused", "disallowed") }, events.Select(e => (e.Outcome, e.Reason))); Assert.Equal("object", events[0].Purpose); } [Fact] public async Task A_fetch_a_reader_caused_is_only_counted() { var ledger = new MemoryLedger(); var http = Peer.Http(ledger: ledger); _peer.Serve("/users/b", "{\"id\":\"{A}/users/b\",\"type\":\"Person\"}"); using (HttpScope.For("webfinger")) await http.GetJson($"{_peer.A}/users/b", "application/activity+json", default, TestContext.Current.CancellationToken); Assert.Empty(ledger.Events); Assert.Equal(1, ledger.Counts[("127.0.0.1", "http:webfinger:ok")]); } } }