using Microsoft.Extensions.Logging.Abstractions; using PrivaPub.Infrastructure.Statistics; using PrivaPub.Models.Statistics; using PrivaPub.Tests.Support; namespace PrivaPub.Tests.Statistics { public class InteractionsTests { [Fact] public void The_local_kind_is_kept_only_on_public_and_unlisted_traffic() { Assert.Equal("person", Interactions.Sanitize(new InteractionEvent { Audience = "public", LocalKind = "person" }).LocalKind); Assert.Equal("group", Interactions.Sanitize(new InteractionEvent { Audience = "unlisted", LocalKind = "group" }).LocalKind); Assert.Null(Interactions.Sanitize(new InteractionEvent { Audience = "private", LocalKind = "group" }).LocalKind); Assert.Null(Interactions.Sanitize(new InteractionEvent { Audience = "none", LocalKind = "person" }).LocalKind); Assert.Null(Interactions.Sanitize(new InteractionEvent { LocalKind = "person" }).LocalKind); Assert.Null(Interactions.Sanitize(new InteractionEvent { Audience = "public", LocalKind = "circle" }).LocalKind); } [Fact] public void Junk_becomes_other_and_hosts_are_normalised() { var e = Interactions.Sanitize(new InteractionEvent { Host = "Mastodon.Social.", Channel = "sideways", Activity = "https://www.w3.org/ns/activitystreams#Create", Object = "Note", Outcome = "Dropped!", Reason = "not-addressed", Audience = "everyone", Inbox = "front-door", Signature = "cavage:rsa-sha256", Features = new() { "fep-044f", "Bad Feature", "fep-044f", "source:text/markdown" } }); Assert.Equal("mastodon.social", e.Host); Assert.Equal("other", e.Channel); Assert.Equal("other", e.Activity); Assert.Equal("Note", e.Object); Assert.Equal("other", e.Outcome); Assert.Equal("not-addressed", e.Reason); Assert.Null(e.Audience); Assert.Null(e.Inbox); Assert.Equal("cavage:rsa-sha256", e.Signature); Assert.Equal(new[] { "fep-044f", "other", "source:text/markdown" }, e.Features); Assert.Equal("-", Interactions.Host("evil host/with path")); Assert.Equal("-", Interactions.Host(default)); Assert.Equal("social.example", Interactions.HostOf("https://Social.Example:8443/users/x")); } [Fact] public void Latencies_and_waits_fall_into_buckets() { Assert.Equal("le50ms", Interactions.Latency(0)); Assert.Equal("le250ms", Interactions.Latency(101)); Assert.Equal("inf", Interactions.Latency(31_000)); Assert.Equal("le1s", Interactions.Wait(900)); Assert.Equal("le60s", Interactions.Wait(59_000)); Assert.Equal("inf", Interactions.Wait(100_000_000)); } [Fact] public void An_actor_hash_is_stable_for_a_salt_and_unlinkable_across_salts() { var monday = new byte[32]; var tuesday = Enumerable.Repeat((byte)1, 32).ToArray(); const string actor = "https://social.example/users/alice"; var first = InteractionSalts.Hash(monday, actor); Assert.Equal(first, InteractionSalts.Hash(monday, actor)); Assert.NotEqual(first, InteractionSalts.Hash(tuesday, actor)); Assert.NotEqual(first, InteractionSalts.Hash(monday, actor + "2")); Assert.Equal(16, first.Length); Assert.DoesNotContain("alice", first); Assert.Null(InteractionSalts.Hash(default, actor)); } [Fact] public void A_full_ledger_drops_and_counts_but_never_blocks_or_throws() { var ledger = new InteractionLedger(new InteractionSalts(), new StaticOptions(new StatisticsOptions()), NullLogger.Instance); for (var i = 0; i < 10_005; i++) ledger.Record(new InteractionEvent { Channel = Interactions.In }); ledger.Record(default); Assert.Equal(5, ledger.Dropped); } [Fact] public void A_disabled_ledger_records_nothing() { var ledger = new InteractionLedger(new InteractionSalts(), new StaticOptions(new StatisticsOptions { Enabled = false }), NullLogger.Instance); for (var i = 0; i < 10_005; i++) ledger.Record(new InteractionEvent { Channel = Interactions.In }); Assert.Equal(0, ledger.Dropped); } } }