Moves carry follows over; first DMs to Lemmy 0.19 and Mbin go as ChatMessage
Two owner decisions of 2026-10-05, both recorded in ROADMAP: - After a verified Move the personas following the old account follow the new one, in the same lists, and a mute or block of the old account carries over, as Mastodon does it. - A direct message to one account on a server whose NodeInfo names Lemmy before 1.0 or Mbin goes as a ChatMessage, the one place PrivaPub decides by a server's software (invariant 17). G-0008 is closed. Mbin addresses its private messages to the recipient's profile page, so a Create addressed to a persona's /@name now reaches the persona. Checked live: moves 8/8, Lemmy 0.19 30/30, Mbin 26/26 with messages both ways. The software theory runs alone, since every test's peer shares 127.0.0.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
f3e8cce2ed
commit
7b0377c85f
19 files changed
+309
-63
No files matched your search
@@ -1,6 +1,7 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Api.Mastodon.Mappers;
|
||||
using PrivaPub.Models.Social;
|
||||
using PrivaPub.Models.User;
|
||||
using PrivaPub.Tests.Support;
|
||||
|
||||
@@ -49,13 +50,23 @@ namespace PrivaPub.Tests.Federation
|
||||
static Task<ForeignAvatar> Stored(RemoteActor actor) =>
|
||||
DB.Default.Find<ForeignAvatar>().Match(a => a.ActorURI == actor.Id).ExecuteFirstAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
// as Mastodon does it (owner decision 2026-10-05): the follow moves to the new account, in the same lists, and a mute
|
||||
// or a block of the old account carries over
|
||||
[Fact]
|
||||
public async Task A_move_the_new_account_confirms_shows_the_old_account_moved_there()
|
||||
public async Task A_move_the_new_account_confirms_moves_the_personas_follows_lists_mutes_and_blocks()
|
||||
{
|
||||
var token = TestContext.Current.CancellationToken;
|
||||
var (_, alice) = await _harness.Persona("alice");
|
||||
var (_, bob) = await _harness.Persona("bob");
|
||||
var old = new RemoteActor(_harness.Peer, "old");
|
||||
await _harness.Remote.GetActor(old.Id, refresh: true, token);
|
||||
await Follows(alice.Id, old);
|
||||
var oldAccount = await Stored(old);
|
||||
var list = new PersonaList { AvatarId = alice.Id, Title = "friends" };
|
||||
await DB.Default.SaveAsync(list, token);
|
||||
await DB.Default.SaveAsync(new PersonaListMember { ListId = list.ID, AvatarId = alice.Id, AccountId = oldAccount.ID }, token);
|
||||
await _harness.Relationships.Mute(alice, old.Id, oldAccount.ID, hideNotifications: true, duration: default, token);
|
||||
await _harness.Relationships.Block(bob, old.Id, oldAccount.ID, token);
|
||||
var target = Target("new", aliasOf: old);
|
||||
|
||||
await Move(old, old, target);
|
||||
@@ -65,6 +76,14 @@ namespace PrivaPub.Tests.Federation
|
||||
var account = await new MastodonMapper(_harness.Db, _harness.Local).Account(stored.ID, token);
|
||||
Assert.Equal(target.Name, account.Moved?.Username);
|
||||
Assert.Contains(_harness.Ledger.Of("in"), e => e.Activity == "Move" && e.Reason == "moved");
|
||||
var newAccount = await Stored(target);
|
||||
Assert.True(await DB.Default.Find<Following>().Match(f => f.AvatarId == alice.Id && f.TargetActorURI == target.Id).ExecuteAnyAsync(token));
|
||||
Assert.False(await DB.Default.Find<Following>().Match(f => f.AvatarId == alice.Id && f.TargetActorURI == old.Id).ExecuteAnyAsync(token));
|
||||
Assert.Contains(await _harness.Outgoing(target.Id + "/inbox"), a => a["type"]!.GetValue<string>() == "Follow");
|
||||
Assert.Contains(await _harness.Outgoing(old.Id + "/inbox"), a => a["type"]!.GetValue<string>() == "Undo");
|
||||
Assert.True(await DB.Default.Find<PersonaListMember>().Match(m => m.ListId == list.ID && m.AccountId == newAccount.ID).ExecuteAnyAsync(token));
|
||||
Assert.True(await DB.Default.Find<Mute>().Match(m => m.AvatarId == alice.Id && m.TargetActorURI == target.Id).ExecuteAnyAsync(token));
|
||||
Assert.True(await DB.Default.Find<Block>().Match(b => b.AvatarId == bob.Id && b.TargetActorURI == target.Id).ExecuteAnyAsync(token));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in new issue
Block a user