11package csapi_tests
22
33import (
4+ "fmt"
45 "net/http"
56 "testing"
6- "time"
77
88 "github.com/tidwall/gjson"
99
@@ -12,64 +12,230 @@ import (
1212 "github.com/matrix-org/complement/helpers"
1313 "github.com/matrix-org/complement/match"
1414 "github.com/matrix-org/complement/must"
15+ "github.com/matrix-org/complement/should"
1516)
1617
1718func TestPublicRooms (t * testing.T ) {
1819 deployment := complement .Deploy (t , 1 )
1920 defer deployment .Destroy (t )
21+ hs1ServerName := deployment .GetFullyQualifiedHomeserverName (t , "hs1" )
2022
21- t .Run ("parallel" , func (t * testing.T ) {
22- // sytest: Can search public room list
23- t .Run ("Can search public room list" , func (t * testing.T ) {
24- t .Parallel ()
25- authedClient := deployment .Register (t , "hs1" , helpers.RegistrationOpts {})
23+ // sytest: Can search public room list
24+ t .Run ("Can search public room list" , func (t * testing.T ) {
25+ authedClient := deployment .Register (t , "hs1" , helpers.RegistrationOpts {})
2626
27- roomID := authedClient .MustCreateRoom (t , map [string ]any {
28- "visibility" : "public" ,
29- "name" : "Test Name" ,
30- "topic" : "Test Topic Wombles" ,
31- })
27+ roomID := authedClient .MustCreateRoom (t , map [string ]any {
28+ "visibility" : "public" ,
29+ "name" : "Test Name" ,
30+ "topic" : "Test Topic Wombles" ,
31+ })
32+
33+ // Remove the room from the public rooms list to avoid polluting other tests.
34+ defer authedClient .MustDo (
35+ t ,
36+ "PUT" ,
37+ []string {"_matrix" , "client" , "v3" , "directory" , "list" , "room" , roomID },
38+ client .WithJSONBody (t , map [string ]interface {}{
39+ "visibility" : "private" ,
40+ }),
41+ )
42+
43+ authedClient .MustDo (
44+ t ,
45+ "POST" ,
46+ []string {"_matrix" , "client" , "v3" , "publicRooms" },
47+ client .WithJSONBody (t , map [string ]any {
48+ "filter" : map [string ]any {
49+ "generic_search_term" : "wombles" ,
50+ },
51+ }),
52+ client .WithRetryUntil (authedClient .SyncUntilTimeout , func (res * http.Response ) bool {
53+ results := parsePublicRoomsResponse (t , res )
54+
55+ if len (results ) != 1 {
56+ t .Logf ("expected a single search result, got %d" , len (results ))
57+ return false
58+ }
59+
60+ foundRoomID := results [0 ].Get ("room_id" ).Str
61+ if foundRoomID != roomID {
62+ t .Logf ("expected room_id %s in search results, got %s" , roomID , foundRoomID )
63+ return false
64+ }
65+
66+ return true
67+ }),
68+ )
69+ })
70+
71+ // sytest: Name/topic keys are correct
72+ t .Run ("Name/topic keys are correct" , func (t * testing.T ) {
73+ authedClient := deployment .Register (t , "hs1" , helpers.RegistrationOpts {})
74+
75+ // Define room configurations
76+ roomConfigs := []struct {
77+ alias string
78+ name string
79+ topic string
80+ }{
81+ {"publicroomalias_no_name" , "" , "" },
82+ {"publicroomalias_with_name" , "name_1" , "" },
83+ {"publicroomalias_with_topic" , "" , "topic_1" },
84+ {"publicroomalias_with_name_topic" , "name_2" , "topic_2" },
85+ {"publicroom_with_unicode_chars_name" , "un nom français" , "" },
86+ {"publicroom_with_unicode_chars_topic" , "" , "un topic à la française" },
87+ {"publicroom_with_unicode_chars_name_topic" , "un nom français" , "un topic à la française" },
88+ }
89+
90+ for _ , roomConfig := range roomConfigs {
91+ t .Run (fmt .Sprintf ("Creating room with alias %s" , roomConfig .alias ), func (t * testing.T ) {
92+ expectedCanonicalAlias := fmt .Sprintf ("#%s:%s" , roomConfig .alias , hs1ServerName )
93+
94+ // Create the room
95+ roomOptions := map [string ]interface {}{
96+ // Add the room to the public rooms list.
97+ "visibility" : "public" ,
98+ "room_alias_name" : roomConfig .alias ,
99+ }
100+
101+ if roomConfig .name != "" {
102+ roomOptions ["name" ] = roomConfig .name
103+ }
104+ if roomConfig .topic != "" {
105+ roomOptions ["topic" ] = roomConfig .topic
106+ }
107+
108+ roomID := authedClient .MustCreateRoom (t , roomOptions )
109+ t .Logf ("Created room %s with alias %s" , roomID , roomConfig .alias )
110+
111+ // Remove the room from the public rooms list to avoid polluting other tests.
112+ defer authedClient .MustDo (
113+ t ,
114+ "PUT" ,
115+ []string {"_matrix" , "client" , "v3" , "directory" , "list" , "room" , roomID },
116+ client .WithJSONBody (t , map [string ]interface {}{
117+ "visibility" : "private" ,
118+ }),
119+ )
120+
121+ // Poll /publicRooms until the room appears with the correct data
122+
123+ // Keep track of any rooms that we didn't expect to see.
124+ unexpectedRooms := make ([]string , 0 )
125+
126+ var discoveredRoomData gjson.Result
127+ authedClient .MustDo (t , "GET" , []string {"_matrix" , "client" , "v3" , "publicRooms" },
128+ client .WithRetryUntil (authedClient .SyncUntilTimeout , func (res * http.Response ) bool {
129+ results := parsePublicRoomsResponse (t , res )
32130
33- authedClient .MustDo (
34- t ,
35- "POST" ,
36- []string {"_matrix" , "client" , "v3" , "publicRooms" },
37- client .WithJSONBody (t , map [string ]any {
38- "filter" : map [string ]any {
39- "generic_search_term" : "wombles" ,
40- },
41- }),
42- client .WithRetryUntil (15 * time .Second , func (res * http.Response ) bool {
43- body := must .ParseJSON (t , res .Body )
44-
45- must .MatchGJSON (
46- t ,
47- body ,
48- match .JSONKeyPresent ("chunk" ),
49- match .JSONKeyTypeEqual ("chunk" , gjson .JSON ),
50- )
51-
52- chunk := body .Get ("chunk" )
53- if ! chunk .IsArray () {
54- t .Logf ("chunk is not an array" )
55- return false
131+ // Check each room in the public rooms list
132+ for _ , roomData := range results {
133+ discoveredRoomID := roomData .Get ("room_id" ).Str
134+ if discoveredRoomID != roomID {
135+ // Not our room, skip.
136+ unexpectedRooms = append (unexpectedRooms , discoveredRoomID )
137+ continue
138+ }
139+
140+ // We found our room. Stop calling /publicRooms and validate the response.
141+ discoveredRoomData = roomData
142+ }
143+
144+ if ! discoveredRoomData .Exists () {
145+ t .Logf ("Room %s not found in public rooms list, trying again..." , roomID )
146+ return false
147+ }
148+
149+ return true
150+ }),
151+ )
152+
153+ if len (unexpectedRooms ) > 0 {
154+ t .Logf ("Warning: Found unexpected rooms in public rooms list: %v" , unexpectedRooms )
155+ }
156+
157+ // Verify required keys are present in the room data.
158+ err := should .MatchGJSON (
159+ discoveredRoomData ,
160+ match .JSONKeyPresent ("world_readable" ),
161+ match .JSONKeyPresent ("guest_can_join" ),
162+ match .JSONKeyPresent ("num_joined_members" ),
163+ )
164+ if err != nil {
165+ // The room is missing required keys, and
166+ // it's unlikely to get them after
167+ // calling the method again. Let's bail out.
168+ t .Fatalf ("Room %s data missing required keys: %s, data: %v" , roomID , err .Error (), discoveredRoomData )
169+ }
170+
171+ // Keep track of all validation errors, rather than bailing out on the first one.
172+ validationErrors := make ([]error , 0 )
173+
174+ // Verify canonical alias
175+ canonicalAlias := discoveredRoomData .Get ("canonical_alias" ).Str
176+ if canonicalAlias != expectedCanonicalAlias {
177+ err = fmt .Errorf ("Room %s has canonical alias '%s', expected '%s'" , roomID , canonicalAlias , expectedCanonicalAlias )
178+ validationErrors = append (validationErrors , err )
179+ }
180+
181+ // Verify member count
182+ numMembers := discoveredRoomData .Get ("num_joined_members" ).Int ()
183+ if numMembers != 1 {
184+ err = fmt .Errorf ("Room %s has %d members, expected 1" , roomID , numMembers )
185+ validationErrors = append (validationErrors , err )
186+ }
187+
188+ // Verify name field, if there is one to verify
189+ name := discoveredRoomData .Get ("name" ).Str
190+ if roomConfig .name != "" {
191+ if name != roomConfig .name {
192+ err = fmt .Errorf ("Room %s has name '%s', expected '%s'" , roomID , name , roomConfig .name )
193+ validationErrors = append (validationErrors , err )
56194 }
195+ } else {
196+ if name != "" {
197+ err = fmt .Errorf ("Room %s has unexpected name '%s', expected no name" , roomID , name )
198+ validationErrors = append (validationErrors , err )
199+ }
200+ }
57201
58- results := chunk .Array ()
59- if len (results ) != 1 {
60- t .Logf ("expected a single search result, got %d" , len (results ))
61- return false
202+ // Verify topic field, if there is one to verify
203+ topic := discoveredRoomData .Get ("topic" ).Str
204+ if roomConfig .topic != "" {
205+ if topic != roomConfig .topic {
206+ err = fmt .Errorf ("Room %s has topic '%s', expected '%s'" , roomID , topic , roomConfig .topic )
207+ validationErrors = append (validationErrors , err )
208+ }
209+ } else {
210+ if topic != "" {
211+ err = fmt .Errorf ("Room %s has unexpected topic '%s', expected no topic" , roomID , topic )
212+ validationErrors = append (validationErrors , err )
62213 }
214+ }
63215
64- foundRoomID := results [0 ].Get ("room_id" ).Str
65- if foundRoomID != roomID {
66- t .Logf ("expected room_id %s in search results, got %s" , roomID , foundRoomID )
67- return false
216+ if len (validationErrors ) > 0 {
217+ for _ , e := range validationErrors {
218+ t .Errorf ("Validation error for room %s: %s" , roomID , e .Error ())
68219 }
69220
70- return true
71- }),
72- )
73- })
221+ t . Fail ()
222+ }
223+ } )
224+ }
74225 })
75226}
227+
228+ func parsePublicRoomsResponse (t * testing.T , res * http.Response ) []gjson.Result {
229+ t .Helper ()
230+ body := must .ParseJSON (t , res .Body )
231+
232+ chunk := body .Get ("chunk" )
233+ if ! chunk .Exists () {
234+ t .Fatalf ("`chunk` field on public rooms response does not exist, got body: %v" , body )
235+ }
236+ if ! chunk .IsArray () {
237+ t .Fatalf ("`chunk` field on public rooms response is not an array, got: %v" , chunk )
238+ }
239+
240+ return chunk .Array ()
241+ }
0 commit comments