Sharkey 2025.4.7 is the Misskey peer under another name, image, database, Redis db and home. Its scenario is Misskey's plus edits both ways and the FEP-e232 quote tag: 40 checks pass. Akkoma 3.20.1 publishes no image, so tools/pasture/images/akkoma installs its OTP release, pinned by checksum, and appends Caddy's CA to the CA bundles the release ships. Its scenario has 44 checks, which pass three runs in a row: - follows, posts, CW, followers-only posts and the published time; - replies, likes, boosts and their undos; - EmojiReact both ways and withdrawn; - DMs, polls, quotes, media, edits with history and deletes, both ways; - unfollow, block, unblock and statistics. The two bugs, both fixed: - Followers-only posts arrived as DMs on Pleroma and Akkoma. They call a post private only if an address in `to` contains "/followers" or its cc is not empty. Ours is /groupies, and a post mentioning nobody had an empty cc. The followers collection is now named in cc as well, which tells nobody anything new. - Akkoma's open polls showed as ended and refused votes. Akkoma carries an open poll's end in `closed` and sends no `endTime`. A `closed` in the future is now read as the end. Neither of these is a PrivaPub bug: - Akkoma's Linkify never takes @user@host.test for a mention, so its DM addresses alice with to[]. - Its API never reports a remote blocker as blocked_by, so the block is read from its database. Also in this commit: - The rate limits are configuration (RateLimits: AccountsPerMinute, InboxBurst, InboxPerTenSeconds), with the old values as defaults. The pasture raises the accounts limit, which back-to-back runs from one address had hit. - A new Lemmy never sends what it queued for a server before it started that server's send worker, so the scenario waits for the worker before its first follow. All six peers in one clean pass: GoToSocial 54 (+1 expected), Mastodon 49 (+2), Misskey 35, Sharkey 40, Akkoma 44, and Lemmy 20 (+3) once the worker wait was added. 642 tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ELjqpznMFMNrJoJUj6K5p2
95 lines
4.2 KiB
C#
95 lines
4.2 KiB
C#
using MongoDB.Entities;
|
|
|
|
using PrivaPub.ClientModels.Post;
|
|
using PrivaPub.ClientModels;
|
|
using PrivaPub.Models.Post;
|
|
using PrivaPub.Models.User;
|
|
using PrivaPub.Tests.Support;
|
|
|
|
namespace PrivaPub.Tests.Domain
|
|
{
|
|
[Trait("Category", "Integration")]
|
|
public sealed class OutboxTests : IAsyncLifetime
|
|
{
|
|
Harness _harness;
|
|
|
|
public async ValueTask InitializeAsync()
|
|
{
|
|
Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip);
|
|
_harness = await Harness.Start();
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_harness != default)
|
|
await _harness.DisposeAsync();
|
|
}
|
|
|
|
async Task<ForeignAvatar> Known(RemoteActor actor) =>
|
|
await _harness.Remote.GetActor(actor.Id, refresh: false, TestContext.Current.CancellationToken);
|
|
|
|
[Fact]
|
|
public async Task The_audience_follows_mastodons_table()
|
|
{
|
|
var token = TestContext.Current.CancellationToken;
|
|
var (_, alice) = await _harness.Persona("alice");
|
|
var follower = new RemoteActor(_harness.Peer, "follower");
|
|
var mentioned = await Known(new RemoteActor(_harness.Peer, "mentioned"));
|
|
var parentAuthor = await Known(new RemoteActor(_harness.Peer, "parent"));
|
|
await _harness.FollowedBy(alice, follower);
|
|
var mention = new PostMention { ActorURI = mentioned.ActorURI };
|
|
|
|
async Task<IReadOnlyList<string>> Audience(PostVisibility visibility, string replyTo = default) =>
|
|
await _harness.Outbox.Audience(alice, new Post { Visibility = visibility, Mentions = new() { mention }, To = new() { mentioned.ActorURI }, InReplyToAccountId = replyTo }, token);
|
|
|
|
Assert.Equal(new[] { follower.SharedInbox, mentioned.InboxURL }, await Audience(PostVisibility.Public));
|
|
Assert.Equal(new[] { follower.SharedInbox, mentioned.InboxURL, parentAuthor.InboxURL }, await Audience(PostVisibility.Unlisted, parentAuthor.ID));
|
|
Assert.Equal(new[] { follower.SharedInbox, mentioned.InboxURL }, await Audience(PostVisibility.FollowersOnly));
|
|
Assert.Equal(new[] { mentioned.InboxURL }, await Audience(PostVisibility.Direct, parentAuthor.ID));
|
|
Assert.Empty(await Audience(PostVisibility.Circle));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_post_is_created_edited_and_deleted_for_its_audience()
|
|
{
|
|
var token = TestContext.Current.CancellationToken;
|
|
var (root, alice) = await _harness.Persona("alice");
|
|
var follower = new RemoteActor(_harness.Peer, "follower");
|
|
await _harness.FollowedBy(alice, follower);
|
|
|
|
var created = await _harness.Posts.InsertPost(root, new InsertPostForm { AvatarId = alice.Id, Text = "first", Visibility = "followersonly" }, token);
|
|
var postId = ((ViewPost)created.Data).Id;
|
|
await _harness.Posts.UpdatePost(root, new UpdatePostForm { AvatarId = alice.Id, PostId = postId, Text = "second", SpoilerText = "cw" }, token);
|
|
await _harness.Posts.DeletePost(root, new DeletePostForm { AvatarId = alice.Id, PostId = postId }, token);
|
|
|
|
var sent = await _harness.Outgoing(follower.SharedInbox);
|
|
Assert.Equal(new[] { "Create", "Update", "Delete" }, sent.Select(a => a["type"]!.GetValue<string>()));
|
|
Assert.DoesNotContain(sent[0]["to"]!.AsArray(), n => n!.GetValue<string>().EndsWith("#Public"));
|
|
// Pleroma reads an empty cc without a "/followers" address as a direct message
|
|
Assert.Contains(sent[0]["object"]!["cc"]!.AsArray(), n => n!.GetValue<string>() == alice.Followers);
|
|
Assert.Equal("cw", sent[1]["object"]!["summary"]!.GetValue<string>());
|
|
Assert.NotNull(sent[1]["object"]!["updated"]);
|
|
Assert.Equal(sent[0]["to"]!.ToJsonString(), sent[2]["to"]!.ToJsonString());
|
|
|
|
var stored = await DB.Default.Find<Post>().OneAsync(postId, token);
|
|
Assert.NotNull(stored.DeletedAt);
|
|
Assert.Null(stored.ContentHtml);
|
|
Assert.Empty(stored.Revisions);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Someone_elses_post_cannot_be_edited()
|
|
{
|
|
var token = TestContext.Current.CancellationToken;
|
|
var (root, alice) = await _harness.Persona("alice");
|
|
var (otherRoot, bob) = await _harness.Persona("bob");
|
|
var created = await _harness.Posts.InsertPost(root, new InsertPostForm { AvatarId = alice.Id, Text = "mine" }, token);
|
|
|
|
var result = await _harness.Posts.UpdatePost(otherRoot, new UpdatePostForm { AvatarId = bob.Id, PostId = ((ViewPost)created.Data).Id, Text = "yours" }, token);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Equal(404, result.StatusCode);
|
|
}
|
|
}
|
|
}
|