using MongoDB.Entities; using PrivaPub.Domain.Statistics; using PrivaPub.Infrastructure.Statistics; using PrivaPub.Models.Jobs; using PrivaPub.Tests.Support; using PrivaPub.Tests.Support.Host; using System.Net; using System.Text.Json.Nodes; namespace PrivaPub.Tests.Statistics { public class StatisticsQueriesTests { [Fact] public void Percentiles_come_from_the_latency_buckets() { var latency = new Dictionary { ["out:le50ms"] = 50, ["out:le250ms"] = 45, ["out:le5000ms"] = 4, ["out:inf"] = 1, ["in:le50ms"] = 9 }; Assert.Equal(50, StatisticsQueries.Percentile(latency, "out", 0.5)); Assert.Equal(250, StatisticsQueries.Percentile(latency, "out", 0.95)); Assert.Equal(5000, StatisticsQueries.Percentile(latency, "out", 0.99)); Assert.Equal(30001, StatisticsQueries.Percentile(latency, "out", 1)); Assert.Null(StatisticsQueries.Percentile(latency, "http:actor", 0.5)); } [Fact] public void Outcomes_add_up_per_channel() { var counters = new Dictionary { ["out:Create:Note:ok"] = 3, ["out:Create:Note:retry:503"] = 2, ["out:Like:-:ok"] = 1, ["in:Create:Note:accepted:stored"] = 4, ["recv:Create:202:queued"] = 9 }; Assert.Equal(new Dictionary { ["ok"] = 4, ["retry"] = 2 }, StatisticsQueries.Outcomes(counters, "out")); Assert.Equal(new Dictionary { ["accepted"] = 4 }, StatisticsQueries.Outcomes(counters, "in")); } } [Trait("Category", "Integration")] public sealed class AdminStatisticsTests : 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(); } [Fact] public async Task Only_the_admin_reads_the_statistics() { var token = TestContext.Current.CancellationToken; var plain = await _host.SignUp(); using var anonymous = _host.Client(); using var user = _host.As(plain.Jwt); Assert.Equal(HttpStatusCode.Unauthorized, (await anonymous.GetAsync("/clientapi/admin/statistics/overview", token)).StatusCode); Assert.Equal(HttpStatusCode.Forbidden, (await user.GetAsync("/clientapi/admin/statistics/overview", token)).StatusCode); Assert.Equal(HttpStatusCode.Forbidden, (await user.PostAsync("/clientapi/admin/statistics/rollups/2001-01-01", default, token)).StatusCode); } [Fact] public async Task The_admin_sees_a_server_its_events_and_the_overview() { var token = TestContext.Current.CancellationToken; var admin = await _host.Admin(); var persona = await _host.Persona(await _host.SignUp(), "stats"); var bob = new RemoteActor(_peer, "bob"); var noteId = $"{_peer.A}/notes/{Guid.NewGuid():N}"; var to = new JsonArray($"{PrivaPubHost.Base}/peasants/{persona.UserName}"); using (var client = _host.Client()) Assert.Equal(HttpStatusCode.Accepted, (await client.SendAsync(bob.SignedPost($"/peasants/{persona.UserName}/mouth", new JsonObject { ["id"] = noteId + "/activity", ["type"] = "Create", ["actor"] = bob.Id, ["to"] = to.DeepClone(), ["object"] = new JsonObject { ["id"] = noteId, ["type"] = "Note", ["attributedTo"] = bob.Id, ["to"] = to.DeepClone(), ["content"] = "hi" } }), token)).StatusCode); await _host.RunInbox(noteId + "/activity", token); await _host.Get().Flush(token); using var adminClient = _host.As(admin.Jwt); var host = JsonNode.Parse(await adminClient.GetStringAsync("/clientapi/admin/statistics/hosts/127.0.0.1?days=1", token))!; var overview = JsonNode.Parse(await adminClient.GetStringAsync("/clientapi/admin/statistics/overview?days=1", token))!; var hosts = JsonNode.Parse(await adminClient.GetStringAsync("/clientapi/admin/statistics/hosts?days=1&sort=host", token))!; var events = JsonNode.Parse(await adminClient.GetStringAsync("/clientapi/admin/statistics/events?host=127.0.0.1&channel=in&limit=5", token))!; var today = host["days"]!.AsArray().Last()!; Assert.True(today["live"]!.GetValue()); Assert.True(today["counters"]!["in:Create:Note:accepted:stored"]!.GetValue() >= 1); Assert.Contains("DB-IP", host["attribution"]!.GetValue()); Assert.True(overview["channels"]!["in"]!.GetValue() >= 1); Assert.Contains(hosts["hosts"]!.AsArray(), h => h!["host"]!.GetValue() == "127.0.0.1"); Assert.NotEmpty(events.AsArray()); Assert.DoesNotContain(persona.UserName, host.ToJsonString()); Assert.DoesNotContain(bob.Id, host.ToJsonString()); Assert.Equal(HttpStatusCode.NotFound, (await adminClient.GetAsync($"/clientapi/admin/statistics/hosts/never{Guid.NewGuid():N}.example", token)).StatusCode); } [Fact] public async Task The_admin_can_refold_a_past_day_and_describe_a_server_again() { var token = TestContext.Current.CancellationToken; var admin = await _host.Admin(); using var client = _host.As(admin.Jwt); var describe = $"described{Guid.NewGuid():N}.example"; Assert.Equal(HttpStatusCode.BadRequest, (await client.PostAsync($"/clientapi/admin/statistics/rollups/{DateTime.UtcNow:yyyy-MM-dd}", default, token)).StatusCode); Assert.Equal(HttpStatusCode.BadRequest, (await client.PostAsync("/clientapi/admin/statistics/rollups/yesterday", default, token)).StatusCode); Assert.Equal(HttpStatusCode.Accepted, (await client.PostAsync("/clientapi/admin/statistics/rollups/2001-01-05", default, token)).StatusCode); Assert.Equal(HttpStatusCode.Accepted, (await client.PostAsync($"/clientapi/admin/statistics/hosts/{describe}/describe", default, token)).StatusCode); Assert.True(await DB.Default.Find().Match(j => j.Kind == JobKind.RollupDay && j.Payload == "2001-01-05" && j.DedupeKey.StartsWith("rollup|2001-01-05|rerun|")).ExecuteAnyAsync(token)); Assert.True(await DB.Default.Find().Match(j => j.Kind == JobKind.DescribeInstance && j.Payload == describe).ExecuteAnyAsync(token)); } } }