using MongoDB.Entities; using PrivaPub.Domain.Social; using PrivaPub.Models.Post; using PrivaPub.Tests.Support; using PrivaPub.Tests.Support.Host; namespace PrivaPub.Tests.Http { // Trends from what PrivaPub holds (hashtags and links by how many authors use them, posts by their interactions, // from discoverable authors only) and the directory of accounts that let themselves be discovered [Trait("Category", "Integration")] public sealed class MastodonTrendsTests : IAsyncLifetime { PrivaPubHost _host; static CancellationToken Token => TestContext.Current.CancellationToken; public async ValueTask InitializeAsync() { Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip); _host = await PrivaPubHost.Shared(); } public ValueTask DisposeAsync() => ValueTask.CompletedTask; void Fresh() => _host.Get().Forget(); static string NewTag() => $"tr{Guid.NewGuid():N}"[..12]; [Fact] public async Task A_hashtag_trends_once_two_authors_use_it_today_and_never_from_one_or_behind_a_warning() { var alice = await _host.Mastodon("alice"); var bob = await _host.Mastodon("bob"); var carol = await _host.Mastodon("carol"); var shared = NewTag(); var lonely = NewTag(); var warned = NewTag(); await alice.Status($"#{shared} is good"); await bob.Status($"#{shared} indeed"); for (var i = 0; i < 4; i++) await carol.Status($"#{lonely} again {i}"); await alice.Status($"#{warned}", ("spoiler_text", "careful")); await bob.Status($"#{warned}", ("spoiler_text", "careful")); Fresh(); var trends = (await _host.Client().Get("/api/v1/trends/tags?limit=20")).Ok().Array; var trend = Assert.Single(trends, t => t.Text("name") == shared)!; Assert.Equal(("2", "2"), (trend["history"]![0].Text("uses"), trend["history"]![0].Text("accounts"))); Assert.DoesNotContain(trends, t => t.Text("name") == lonely); Assert.DoesNotContain(trends, t => t.Text("name") == warned); Assert.Contains((await alice.Client.Get("/api/v1/trends?limit=20")).Ok().Array, t => t.Text("name") == shared); } [Fact] public async Task A_post_trends_by_its_interactions_when_its_author_is_discoverable() { var alice = await _host.Mastodon("alice"); var hidden = await _host.Mastodon("hidden"); var bob = await _host.Mastodon("bob"); (await hidden.Client.Patch("/api/v1/accounts/update_credentials", ("discoverable", "false"))).Ok(); var liked = (await alice.Status("worth a like")).Text("id"); var quiet = (await hidden.Status("worth a like too")).Text("id"); var answer = (await alice.Status("a reply", ("in_reply_to_id", liked))).Text("id"); foreach (var id in new[] { liked, quiet, answer }) { (await bob.Client.Post($"/api/v1/statuses/{id}/favourite")).Ok(); (await bob.Client.Post($"/api/v1/statuses/{id}/reblog")).Ok(); } Fresh(); var trending = (await _host.Client().Get("/api/v1/trends/statuses?limit=40")).Ok().Ids.ToList(); Assert.Contains(liked, trending); Assert.DoesNotContain(quiet, trending); Assert.DoesNotContain(answer, trending); } [Fact] public async Task A_link_trends_once_two_authors_share_it() { var alice = await _host.Mastodon("alice"); var bob = await _host.Mastodon("bob"); var url = $"https://news.example/{Guid.NewGuid():N}"; foreach (var account in new[] { alice, bob }) { var id = (await account.Status($"read {url}")).Text("id"); await DB.Default.Update().MatchID(id).Modify(p => p.Link, new PostLink { Href = url, Title = "The news" }).ExecuteAsync(Token); } Fresh(); var link = Assert.Single((await _host.Client().Get("/api/v1/trends/links?limit=20")).Ok().Array, l => l.Text("url") == url)!; Assert.Equal("The news", link.Text("title")); Assert.Equal("2", link["history"]![0].Text("accounts")); } [Fact] public async Task The_directory_lists_discoverable_accounts_by_activity_or_by_joining_and_never_by_creation() { var alice = await _host.Mastodon("alice"); var hidden = await _host.Mastodon("hidden"); (await hidden.Client.Patch("/api/v1/accounts/update_credentials", ("discoverable", "false"))).Ok(); await hidden.Status("not listed"); await alice.Status("listed"); var active = (await _host.Client().Get("/api/v1/directory?order=active&local=true&limit=80")).Ok().Ids.ToList(); Assert.Contains(alice.Id, active); Assert.DoesNotContain(hidden.Id, active); var joined = (await _host.Client().Get("/api/v1/directory?order=new&local=true&limit=80")).Ok().Ids.ToList(); Assert.Contains(alice.Id, joined); Assert.DoesNotContain(hidden.Id, joined); } } }