diff --git a/FEDERATION.md b/FEDERATION.md index 47085aa..303202a 100644 --- a/FEDERATION.md +++ b/FEDERATION.md @@ -184,7 +184,7 @@ Received: | `EmojiReact`, `Like` with an emoji `content` | an emoji reaction (FEP-c0e0; Pleroma, Akkoma, Iceshrimp.NET, Misskey, Sharkey), Unicode or a custom emoji from its `tag`; a `Like` whose content is ❤ stays a favourite; `Undo` takes it back | | `Join` | answered with `Ignore`: PrivaPub hosts no events of its own yet (FEP-8a8e) | | `Announce` | counted and notified for local posts; shown to followers of the announcer, with the original refetched from its origin | -| `Delete` | deletes the object, or the actor and its follows; a deleted object id is remembered for 90 days, so a late `Create` cannot bring it back | +| `Delete` | deletes the object, or the actor and its follows; a deleted object id is remembered for 90 days, so a late `Create` cannot bring it back, though a new post its server publishes under that id after the deletion (Gancio numbers events from the last one kept) is kept | | `Flag` | becomes a report for this server's moderators | | `Move` | an account moving: believed as Mastodon believes it, when the account sends it about itself and the new account, read again from its server, names it in `alsoKnownAs`. The old account then shows where it went (`moved`), and, as Mastodon does it (owner decision 2026-10-05), the personas following it follow the new one instead (a Follow to its server, an Undo to the old), in the same lists; a mute or a block of the old account carries over | | `Add`/`Remove` on the actor's `featured` | the account pins or unpins one of its own posts (fetched from its server when not held); inside a community's announce, the community features a post made in it. Its profile shows them first (`pinned=true`). The collection itself is read with the account's counts, at most once a day. On the account's wall (`sm:wall`), see "Walls". Any other target (a community's moderators) is dropped; one on the account's own server that PrivaPub does not know has its document read again first, at most hourly | diff --git a/PrivaPub.Tests/Federation/ReusedIdTests.cs b/PrivaPub.Tests/Federation/ReusedIdTests.cs new file mode 100644 index 0000000..0880680 --- /dev/null +++ b/PrivaPub.Tests/Federation/ReusedIdTests.cs @@ -0,0 +1,64 @@ +using MongoDB.Entities; + +using PrivaPub.Models.Federation; +using PrivaPub.Models.Post; +using PrivaPub.Models.Social; +using PrivaPub.Tests.Support; + +using System.Text.Json.Nodes; + +using static PrivaPub.Tests.Support.FederatedSeeds; + +namespace PrivaPub.Tests.Federation +{ + // A deleted remote post is never brought back by its own Create, however late it comes; an id its server gives a new + // post afterwards (Gancio numbers an event after the last one it keeps) is that new post + [Trait("Category", "Integration")] + public sealed class ReusedIdTests : 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(); + } + + [Fact] + public async Task A_deleted_posts_create_stays_dropped_and_a_new_post_under_its_id_is_kept() + { + var token = TestContext.Current.CancellationToken; + var (_, alice) = await _harness.Persona("alice"); + var carol = new RemoteActor(_harness.Peer, "carol"); + await Follows(alice.Id, carol); + var first = PublicNote(carol, "
the first event
"); + first["published"] = DateTime.UtcNow.AddMinutes(-10).ToString("O"); + await _harness.Deliver(carol, "/human-centipede", Create(carol, first)); + await _harness.Deliver(carol, "/human-centipede", new JsonObject + { + ["id"] = NewId(carol, "deletes"), ["type"] = "Delete", ["actor"] = carol.Id, + ["object"] = new JsonObject { ["id"] = IdOf(first), ["type"] = "Tombstone" } + }); + Assert.True(await DB.Default.Finda new event, numbered as the first
"); + second["id"] = IdOf(first); + second["published"] = DateTime.UtcNow.ToString("O"); + await _harness.Deliver(carol, "/human-centipede", Create(carol, second)); + + var kept = await DB.Default.Find