feat(membership): room discovery — GET /members/{endpoint}/rooms + ListMyRooms
A peer invited to an encrypted room needs to find it: the control plane is
pull-based (no server push of invitations), so add a discovery endpoint that
lists every room an endpoint belongs to, with the room's metadata and the
endpoint's role.
- store.ListRoomsForEndpoint: JOIN members+rooms, ordered by room id, empty
slice (not error) for an endpoint in no rooms.
- membershipd: GET /members/{endpoint}/rooms returns {room_id, subject, epoch,
policy, role}[].
- client.ListMyRooms + RoomRef: a bot polls this to discover and then Join +
Subscribe rooms it was invited to.
Tests: store-level (owner in N rooms, member in one, unknown endpoint → []) and
client-level e2e through the embedded harness (B discovers a room A invited it
to, without prior knowledge of the room id; owner sees role=owner).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -402,6 +402,59 @@ func TestThreadedReplyAndReaction(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestListMyRoomsDiscovery verifies room discovery: an invited peer finds the
|
||||
// room via ListMyRooms (without being told its id), and a peer in no rooms gets
|
||||
// an empty list. This is what lets a bot discover rooms it was invited to.
|
||||
func TestListMyRoomsDiscovery(t *testing.T) {
|
||||
h := newHarness(t)
|
||||
waitHealth(t, h.ctrlURL)
|
||||
|
||||
a, err := client.New(h.natsURL, h.ctrlURL, mustIdentity(t))
|
||||
if err != nil {
|
||||
t.Fatalf("connect A: %v", err)
|
||||
}
|
||||
defer a.Close()
|
||||
b, err := client.New(h.natsURL, h.ctrlURL, mustIdentity(t))
|
||||
if err != nil {
|
||||
t.Fatalf("connect B: %v", err)
|
||||
}
|
||||
defer b.Close()
|
||||
|
||||
// B is in no rooms yet.
|
||||
if rooms, err := b.ListMyRooms(); err != nil || len(rooms) != 0 {
|
||||
t.Fatalf("B should start in no rooms, got %v err=%v", rooms, err)
|
||||
}
|
||||
|
||||
roomID, err := a.CreateRoom("room.discovery", room.ModeMatrix)
|
||||
if err != nil {
|
||||
t.Fatalf("A create room: %v", err)
|
||||
}
|
||||
if err := a.Invite(roomID, b.Endpoint()); err != nil {
|
||||
t.Fatalf("A invite B: %v", err)
|
||||
}
|
||||
|
||||
// B discovers the room it was invited to, with its policy, without prior knowledge of the id.
|
||||
rooms, err := b.ListMyRooms()
|
||||
if err != nil {
|
||||
t.Fatalf("B ListMyRooms: %v", err)
|
||||
}
|
||||
if len(rooms) != 1 || rooms[0].RoomID != roomID {
|
||||
t.Fatalf("B should discover exactly room %s, got %+v", roomID, rooms)
|
||||
}
|
||||
if rooms[0].Subject != "room.discovery" || !rooms[0].Policy.Encrypt || rooms[0].Role != "member" {
|
||||
t.Fatalf("discovered room metadata wrong: %+v", rooms[0])
|
||||
}
|
||||
|
||||
// A sees the same room as its owner.
|
||||
aRooms, err := a.ListMyRooms()
|
||||
if err != nil {
|
||||
t.Fatalf("A ListMyRooms: %v", err)
|
||||
}
|
||||
if len(aRooms) != 1 || aRooms[0].Role != "owner" {
|
||||
t.Fatalf("A should own exactly one room, got %+v", aRooms)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- test helpers ---------------------------------------------------------
|
||||
|
||||
type collector struct {
|
||||
|
||||
Reference in New Issue
Block a user