Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
7.1 kB · 210 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211package model
import ( "context" "testing" "time"
"github.com/stretchr/testify/require" "stream.place/streamplace/pkg/appbsky")
// indexedTestDB returns an empty in-memory index. The concrete type is handed// back so assertions can count rows directly, without going through the query// helpers (several of which filter deleted rows or need joined repos).func indexedTestDB(t *testing.T) *DBModel { t.Helper() mod, err := MakeDB(":memory:") require.NoError(t, err) db, ok := mod.(*DBModel) require.True(t, ok) return db}
func countRows(t *testing.T, db *DBModel, model any) int64 { t.Helper() var n int64 require.NoError(t, db.DB.Model(model).Count(&n).Error) return n}
// TestCreateOrVerifyBlock covers both arms of the conflict path on a row keyed// by rkey with a separate CID column: the same record twice is a silent no-op,// and a new CID at the same key overwrites.func TestCreateOrVerifyBlock(t *testing.T) { ctx := context.Background() mod := indexedTestDB(t)
block := &Block{ RKey: "3lblock1", CID: "bafyfirst", RepoDID: "did:plc:blocker", SubjectDID: "did:plc:blocked", Record: []byte("one"), CreatedAt: time.Now().UTC(), } require.NoError(t, mod.CreateBlock(ctx, block)) require.Equal(t, int64(1), countRows(t, mod, &Block{}))
// Redelivery: same key, same CID. again := *block require.ErrorIs(t, mod.CreateBlock(ctx, &again), ErrAlreadyIndexed) require.Equal(t, int64(1), countRows(t, mod, &Block{})) stored, err := mod.GetBlock(ctx, block.RKey) require.NoError(t, err) require.Equal(t, "bafyfirst", stored.CID) require.Equal(t, []byte("one"), stored.Record)
// An update we missed: same key, different CID. updated := *block updated.CID = "bafysecond" updated.SubjectDID = "did:plc:blocked-someone-else" updated.Record = []byte("two") require.NoError(t, mod.CreateBlock(ctx, &updated)) require.Equal(t, int64(1), countRows(t, mod, &Block{})) stored, err = mod.GetBlock(ctx, block.RKey) require.NoError(t, err) require.Equal(t, "bafysecond", stored.CID) require.Equal(t, "did:plc:blocked-someone-else", stored.SubjectDID) require.Equal(t, []byte("two"), stored.Record)}
// TestCreateOrVerifyFeedPost is the same shape on a URI-keyed table.func TestCreateOrVerifyFeedPost(t *testing.T) { ctx := context.Background() mod := indexedTestDB(t)
now := time.Now().UTC() body := []byte("first") post := &FeedPost{ URI: "at://did:plc:poster/app.bsky.feed.post/3lpost1", CID: "bafyfirst", CreatedAt: now, FeedPost: &body, RepoDID: "did:plc:poster", Type: "reply", IndexedAt: &now, } require.NoError(t, mod.CreateFeedPost(ctx, post))
again := *post require.ErrorIs(t, mod.CreateFeedPost(ctx, &again), ErrAlreadyIndexed) require.Equal(t, int64(1), countRows(t, mod, &FeedPost{})) stored, err := mod.GetFeedPost(post.URI) require.NoError(t, err) require.Equal(t, "bafyfirst", stored.CID) require.Equal(t, "first", string(*stored.FeedPost))
newBody := []byte("edited") updated := *post updated.CID = "bafysecond" updated.FeedPost = &newBody require.NoError(t, mod.CreateFeedPost(ctx, &updated)) require.Equal(t, int64(1), countRows(t, mod, &FeedPost{})) stored, err = mod.GetFeedPost(post.URI) require.NoError(t, err) require.Equal(t, "bafysecond", stored.CID) require.Equal(t, "edited", string(*stored.FeedPost))}
// TestCreateOrVerifyChatMessage covers the CID-keyed tables. There a conflict// can only ever be a redelivery, and an edited message -- a different CID -- is// a row of its own, which is how the table has always worked.func TestCreateOrVerifyChatMessage(t *testing.T) { ctx := context.Background() mod := indexedTestDB(t)
now := time.Now().UTC() body := []byte("hello") msg := &ChatMessage{ CID: "bafymsg1", URI: "at://did:plc:chatter/place.stream.chat.message/3lmsg1", CreatedAt: now, ChatMessage: &body, RepoDID: "did:plc:chatter", StreamerRepoDID: "did:plc:streamer", IndexedAt: &now, } require.NoError(t, mod.CreateChatMessage(ctx, msg))
again := *msg require.ErrorIs(t, mod.CreateChatMessage(ctx, &again), ErrAlreadyIndexed) require.Equal(t, int64(1), countRows(t, mod, &ChatMessage{}))
edited := *msg edited.CID = "bafymsg2" require.NoError(t, mod.CreateChatMessage(ctx, &edited)) require.Equal(t, int64(2), countRows(t, mod, &ChatMessage{}), "a message keyed by CID gets a new row when its content changes")}
// TestCreateFollowIsIdempotent guards the Save-based writers, which never had a// duplicate problem to fix but must not grow one.func TestCreateFollowIsIdempotent(t *testing.T) { ctx := context.Background() mod := indexedTestDB(t)
follow := appbsky.GraphFollow{ LexiconTypeID: "app.bsky.graph.follow", Subject: "did:plc:followed", CreatedAt: time.Now().UTC().Format(time.RFC3339), } require.NoError(t, mod.CreateFollow(ctx, "did:plc:follower", "3lfollow1", follow)) require.NoError(t, mod.CreateFollow(ctx, "did:plc:follower", "3lfollow1", follow)) require.Equal(t, int64(1), countRows(t, mod, &Follow{}))
got, err := mod.GetUserFollowingUser(ctx, "did:plc:follower", "did:plc:followed") require.NoError(t, err) require.NotNil(t, got) require.Equal(t, "3lfollow1", got.RKey)}
// TestCreateChatProfileIsIdempotent: one row per repo, no CID to compare, so// re-storing it is a plain overwrite rather than ErrAlreadyIndexed.func TestCreateChatProfileIsIdempotent(t *testing.T) { ctx := context.Background() mod := indexedTestDB(t)
rec := []byte("profile") profile := &ChatProfile{RepoDID: "did:plc:chatter", Record: &rec} require.NoError(t, mod.CreateChatProfile(ctx, profile)) require.NoError(t, mod.CreateChatProfile(ctx, profile)) require.Equal(t, int64(1), countRows(t, mod, &ChatProfile{}))}
// TestRepoStatus covers the account lifecycle column: setting a status leaves// the sync state alone, and the terminal list is what the boot sweep filters on.func TestRepoStatus(t *testing.T) { ctx := context.Background() mod := indexedTestDB(t)
require.NoError(t, mod.UpdateRepo(&Repo{ DID: "did:plc:gone", Handle: "gone.test", PDS: "https://pds.test", Version: "3lrev0000", RootCID: "bafyroot", })) require.NoError(t, mod.UpdateRepo(&Repo{DID: "did:plc:fine", Version: "3lrev0001"}))
dids, err := mod.TerminalRepoDIDs(ctx) require.NoError(t, err) require.Empty(t, dids)
require.NoError(t, mod.SetRepoStatus(ctx, "did:plc:gone", RepoStatusDeactivated)) stored, err := mod.GetRepo("did:plc:gone") require.NoError(t, err) require.Equal(t, RepoStatusDeactivated, stored.Status) require.True(t, stored.TerminalStatus()) require.Equal(t, "3lrev0000", stored.Version, "status must not disturb the sync state") require.Equal(t, "bafyroot", stored.RootCID)
dids, err = mod.TerminalRepoDIDs(ctx) require.NoError(t, err) require.Equal(t, []string{"did:plc:gone"}, dids)
require.NoError(t, mod.SetRepoStatus(ctx, "did:plc:gone", RepoStatusOK)) dids, err = mod.TerminalRepoDIDs(ctx) require.NoError(t, err) require.Empty(t, dids)}