package storage_test import ( "slices" "testing" "github.com/dariuszjaniak/warp/server/internal/storage" ) // Backfilled history: events numbered downwards from -1, below everything // the room already holds, and every stream read that has to find them // there. // timelineIDs reads a window and returns its event IDs in the order the // store gave them. func timelineIDs(t *testing.T, rooms storage.RoomStore, req storage.TimelineRequest) []string { t.Helper() req.RoomID = testRoomID if req.Limit == 0 { req.Limit = 100 } events, err := rooms.Timeline(t.Context(), req) if err != nil { t.Fatalf("Timeline(%+v): %v", req, err) } return eventIDsOf(events) } func TestBackfillSortsBelowEverythingAlreadyHeld(t *testing.T) { for _, driver := range testDrivers { t.Run(driver, func(t *testing.T) { ctx := t.Context() rooms := migratedRoomDB(t, driver).Rooms() if err := rooms.CreateRoom(ctx, newTestRoom(testRoomID)); err != nil { t.Fatalf("CreateRoom: %v", err) } // The room as a server that joined it part-way through holds // it: the state it was handed as outliers, then its own join // and what followed in the timeline. create := newTestEvent(testRoomID, "$create", "m.room.create", stateKey("")) topic := newTestEvent(testRoomID, "$topic", "m.room.topic", stateKey("")) if err := rooms.WriteOutliers(ctx, testRoomID, []storage.StoredEvent{create, topic}); err != nil { t.Fatalf("WriteOutliers: %v", err) } state := []storage.StateEntry{ {Type: "m.room.create", StateKey: "", EventID: "$create"}, {Type: "m.room.topic", StateKey: "", EventID: "$topic"}, } if _, err := rooms.Write(ctx, storage.RoomWrite{ Event: newTestEvent(testRoomID, "$join", "m.room.member", stateKey("@bob:warp.example")), StateBefore: state, StateDelta: append(slices.Clone(state), storage.StateEntry{ Type: "m.room.member", StateKey: "@bob:warp.example", EventID: "$join", Membership: "join", }), StateDeltaIsWholeState: true, Extremities: []string{"$join"}, }); err != nil { t.Fatalf("Write the join: %v", err) } writeMessages(t, rooms, testRoomID, "$live") topBefore, err := rooms.MaxStreamOrdering(ctx) if err != nil { t.Fatalf("MaxStreamOrdering: %v", err) } // The history before the join, oldest first. The create event // and the topic are outliers already and are promoted; the // message is new. if err := rooms.WriteBackfill(ctx, testRoomID, []storage.BackfillEvent{ {Event: create}, {Event: topic, StateBefore: state[:1]}, {Event: newTestEvent(testRoomID, "$old", "m.room.message", nil), StateBefore: state}, }); err != nil { t.Fatalf("WriteBackfill: %v", err) } want := map[string]int64{"$create": -3, "$topic": -2, "$old": -1} for id, pos := range want { stored, err := rooms.Event(ctx, testRoomID, id) if err != nil { t.Fatalf("Event(%s): %v", id, err) } if stored.StreamOrdering != pos || stored.Outlier { t.Errorf("%s: position %d, outlier %v; want %d in the timeline", id, stored.StreamOrdering, stored.Outlier, pos) } } // The whole room, forwards and unbounded, is history first. wantAll := []string{"$create", "$topic", "$old", "$join", "$live"} if got := timelineIDs(t, rooms, storage.TimelineRequest{Direction: storage.DirectionForward}); !slices.Equal(got, wantAll) { t.Errorf("forwards from the start = %v, want %v", got, wantAll) } // Negative bounds, both ends, both directions. for _, tc := range []struct { name string req storage.TimelineRequest want []string }{ { name: "backwards from below the first live event", req: storage.TimelineRequest{High: storage.PositionBefore(1), Direction: storage.DirectionBackward}, want: []string{"$old", "$topic", "$create"}, }, { name: "backwards from a negative position", req: storage.TimelineRequest{High: -2, Direction: storage.DirectionBackward}, want: []string{"$topic", "$create"}, }, { name: "forwards past a negative position into the live stream", req: storage.TimelineRequest{Low: -2, Direction: storage.DirectionForward, Limit: 2}, want: []string{"$old", "$join"}, }, { name: "a window entirely below zero", req: storage.TimelineRequest{Low: -3, High: -1, Direction: storage.DirectionForward}, want: []string{"$topic", "$old"}, }, } { if got := timelineIDs(t, rooms, tc.req); !slices.Equal(got, tc.want) { t.Errorf("%s: %v, want %v", tc.name, got, tc.want) } } // History changes neither the top of the stream nor anything // an incremental sync looks at. topAfter, err := rooms.MaxStreamOrdering(ctx) if err != nil { t.Fatalf("MaxStreamOrdering: %v", err) } if topAfter > topBefore { t.Errorf("the stream top moved from %d to %d", topBefore, topAfter) } if changed, err := rooms.RoomsWithEvents(ctx, []string{testRoomID}, topAfter, 0); err != nil { t.Fatalf("RoomsWithEvents: %v", err) } else if len(changed) != 0 { t.Errorf("RoomsWithEvents since the top = %v, want nothing", changed) } if changed, err := rooms.RoomsWithEvents(ctx, []string{testRoomID}, 0, -1); err != nil { t.Fatalf("RoomsWithEvents: %v", err) } else if len(changed) != 1 { t.Errorf("RoomsWithEvents over the history = %v, want the room", changed) } extremities, err := rooms.Extremities(ctx, testRoomID) if err != nil { t.Fatalf("Extremities: %v", err) } if len(extremities) != 1 || extremities[0].EventID != "$live" { t.Errorf("extremities = %v, want just $live", eventIDsOf(extremities)) } current, err := rooms.CurrentState(ctx, testRoomID) if err != nil { t.Fatalf("CurrentState: %v", err) } if len(current) != 3 { t.Errorf("current state = %+v, want the three tuples from before", current) } // The state at a position inside the history is the snapshot // recorded against the event there, plus the event. for _, tc := range []struct { pos int64 want int }{{-4, 0}, {-3, 1}, {-2, 2}, {-1, 2}} { entries, err := rooms.StateAtStream(ctx, testRoomID, tc.pos) if err != nil { t.Fatalf("StateAtStream(%d): %v", tc.pos, err) } if len(entries) != tc.want { t.Errorf("StateAtStream(%d) = %+v, want %d entries", tc.pos, entries, tc.want) } } // A second batch goes below the first. An event already in the // timeline is skipped and takes no position. if err := rooms.WriteBackfill(ctx, testRoomID, []storage.BackfillEvent{ {Event: newTestEvent(testRoomID, "$older", "m.room.message", nil), StateBefore: state}, {Event: newTestEvent(testRoomID, "$old", "m.room.message", nil), StateBefore: state}, }); err != nil { t.Fatalf("WriteBackfill again: %v", err) } older, err := rooms.Event(ctx, testRoomID, "$older") if err != nil { t.Fatalf("Event($older): %v", err) } if older.StreamOrdering != -4 { t.Errorf("$older is at %d, want -4", older.StreamOrdering) } if old, _ := rooms.Event(ctx, testRoomID, "$old"); old.StreamOrdering != -1 { t.Errorf("$old moved to %d", old.StreamOrdering) } }) } } func TestPositionBeforeSkipsZero(t *testing.T) { for pos, want := range map[int64]int64{5: 4, 2: 1, 1: -1, -1: -2, -7: -8} { if got := storage.PositionBefore(pos); got != want { t.Errorf("PositionBefore(%d) = %d, want %d", pos, got, want) } } }