Files
tweetdistributor/store/store_test.go
sirrow 067c9252c9 mirror Discord deletions to X and bluesky
Deleting a message on the watched channel now deletes the posts it
produced. Making that possible reshapes how a message reaches an output:

  - OutputInterface takes an output.Post and returns an output.Ref, the
    identifier the platform gave the new post (tweet ID for X, URI and
    CID for bluesky). It also gained Delete(Ref).
  - The discord package no longer forwards raw MessageCreate values but
    a discord.Event carrying the kind of change, so the MessageDelete
    handler can use the same channel. Deleted messages arrive without an
    author, so only the channel ID can be filtered on.
  - The new store package remembers Discord message ID -> per output Ref.
    Set POST_STORE to a path to keep that mapping across restarts;
    without it the mapping is memory only and a restart makes older
    messages undeletable. It holds the most recent 1000 messages.

main.go grows a distributor type to hold the outputs and the store
rather than threading them through the event loop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 12:00:22 +09:00

78 lines
1.8 KiB
Go

package store
import (
"path/filepath"
"testing"
"tweetdistributor/output"
)
func TestStoreRoundTripThroughFile(t *testing.T) {
path := filepath.Join(t.TempDir(), "posts.json")
s, err := New(path)
if err != nil {
t.Fatal(err)
}
refs := Refs{
"twitter": {ID: "123"},
"bluesky": {URI: "at://did:plc:xyz/app.bsky.feed.post/abc", CID: "bafy"},
}
if err := s.Put("discord-1", refs); err != nil {
t.Fatal(err)
}
reopened, err := New(path)
if err != nil {
t.Fatal(err)
}
got, ok := reopened.Get("discord-1")
if !ok {
t.Fatal("stored message was not found after reopening")
}
if got["twitter"] != (output.Ref{ID: "123"}) {
t.Errorf("twitter ref = %+v, want ID 123", got["twitter"])
}
if got["bluesky"].URI != refs["bluesky"].URI {
t.Errorf("bluesky URI = %q, want %q", got["bluesky"].URI, refs["bluesky"].URI)
}
if err := reopened.Delete("discord-1"); err != nil {
t.Fatal(err)
}
if _, ok := reopened.Get("discord-1"); ok {
t.Error("message is still present after Delete")
}
}
func TestStoreMissingMessage(t *testing.T) {
s, err := New("")
if err != nil {
t.Fatal(err)
}
if _, ok := s.Get("nope"); ok {
t.Error("Get returned a ref for a message that was never stored")
}
if err := s.Delete("nope"); err != nil {
t.Errorf("deleting an unknown message returned %s", err)
}
}
func TestStoreEvictsOldest(t *testing.T) {
s, err := New("")
if err != nil {
t.Fatal(err)
}
for i := 0; i < maxEntries+10; i++ {
if err := s.Put(string(rune('a'+i%26))+string(rune(i)), Refs{"stdout": {ID: "x"}}); err != nil {
t.Fatal(err)
}
}
if len(s.refs) > maxEntries {
t.Errorf("store holds %d entries, want at most %d", len(s.refs), maxEntries)
}
if len(s.order) != len(s.refs) {
t.Errorf("order has %d entries but refs has %d", len(s.order), len(s.refs))
}
}