2026-10-03 11:10:42 +02:00
|
|
|
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", "<html></html>", "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")]);
|
|
|
|
|
}
|
2026-10-03 11:33:30 +02:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task A_connection_remembers_the_address_it_reached_for_locating_the_server()
|
|
|
|
|
{
|
|
|
|
|
var connected = new ConnectedAddresses();
|
|
|
|
|
var http = Peer.Http(connected: connected);
|
|
|
|
|
_peer.Serve("/users/c", "{\"id\":\"{A}/users/c\",\"type\":\"Person\"}");
|
|
|
|
|
|
|
|
|
|
await http.GetJson($"{_peer.A}/users/c", "application/activity+json", default, TestContext.Current.CancellationToken);
|
|
|
|
|
|
|
|
|
|
Assert.Equal(System.Net.IPAddress.Loopback, connected.Of("127.0.0.1"));
|
|
|
|
|
}
|
2026-10-03 11:10:42 +02:00
|
|
|
}
|
|
|
|
|
}
|