Skip to content

Commit 22bf344

Browse files
authored
Add /search tests (#464)
* Add tests/43search.pl * Fix duplicate transaction ID * Add missing msgtype, skip on Dendrite * Remove next_batch where it's not returned * Force count to be 1, remove unneeded fmt.Sprintf * Add next_batch again * Re-add next_batch check
1 parent d404c9f commit 22bf344

File tree

1 file changed

+355
-0
lines changed

1 file changed

+355
-0
lines changed

tests/csapi/apidoc_search_test.go

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
package csapi_tests
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/url"
7+
"testing"
8+
9+
"github.com/matrix-org/util"
10+
"github.com/tidwall/gjson"
11+
12+
"github.com/matrix-org/complement/internal/b"
13+
"github.com/matrix-org/complement/internal/client"
14+
"github.com/matrix-org/complement/internal/match"
15+
"github.com/matrix-org/complement/internal/must"
16+
"github.com/matrix-org/complement/runtime"
17+
)
18+
19+
// Note: In contrast to Sytest, we define a filter.rooms on each search request, this allows us to run in parallel.
20+
func TestSearch(t *testing.T) {
21+
runtime.SkipIf(t, runtime.Dendrite) // https://github.com/matrix-org/dendrite/pull/2675
22+
deployment := Deploy(t, b.BlueprintAlice)
23+
defer deployment.Destroy(t)
24+
25+
alice := deployment.Client(t, "hs1", "@alice:hs1")
26+
27+
t.Run("parallel", func(t *testing.T) {
28+
// sytest: Can search for an event by body
29+
t.Run("Can search for an event by body", func(t *testing.T) {
30+
t.Parallel()
31+
roomID := alice.CreateRoom(t, map[string]interface{}{
32+
"preset": "private_chat",
33+
})
34+
eventID := alice.SendEventSynced(t, roomID, b.Event{
35+
Type: "m.room.message",
36+
Content: map[string]interface{}{
37+
"msgtype": "m.text",
38+
"body": "hello, world",
39+
},
40+
})
41+
42+
searchRequest := client.WithJSONBody(t, map[string]interface{}{
43+
"search_categories": map[string]interface{}{
44+
"room_events": map[string]interface{}{
45+
"keys": []string{"content.body"},
46+
"search_term": "hello",
47+
"filter": map[string]interface{}{
48+
"rooms": []string{roomID},
49+
},
50+
},
51+
},
52+
})
53+
54+
resp := alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "search"}, searchRequest)
55+
sce := "search_categories.room_events"
56+
result0 := sce + ".results.0.result"
57+
must.MatchResponse(t, resp, match.HTTPResponse{
58+
StatusCode: http.StatusOK,
59+
JSON: []match.JSON{
60+
match.JSONKeyPresent(sce + ".count"),
61+
match.JSONKeyPresent(sce + ".results"),
62+
match.JSONKeyEqual(sce+".count", float64(1)),
63+
match.JSONKeyEqual(result0+".event_id", eventID),
64+
match.JSONKeyEqual(result0+".room_id", roomID),
65+
match.JSONKeyPresent(result0 + ".content"),
66+
match.JSONKeyPresent(result0 + ".type"),
67+
match.JSONKeyEqual(result0+".content.body", "hello, world"),
68+
},
69+
})
70+
})
71+
72+
// sytest: Can get context around search results
73+
t.Run("Can get context around search results", func(t *testing.T) {
74+
t.Parallel()
75+
roomID := alice.CreateRoom(t, map[string]interface{}{
76+
"preset": "private_chat",
77+
})
78+
79+
for i := 1; i <= 7; i++ {
80+
alice.SendEventSynced(t, roomID, b.Event{
81+
Type: "m.room.message",
82+
Content: map[string]interface{}{
83+
"body": fmt.Sprintf("Message number %d", i),
84+
"msgtype": "m.text",
85+
},
86+
})
87+
}
88+
89+
searchRequest := client.WithJSONBody(t, map[string]interface{}{
90+
"search_categories": map[string]interface{}{
91+
"room_events": map[string]interface{}{
92+
"keys": []string{"content.body"},
93+
"search_term": "Message 4",
94+
"order_by": "recent",
95+
"filter": map[string]interface{}{
96+
"limit": 1,
97+
"rooms": []string{roomID},
98+
},
99+
"event_context": map[string]interface{}{
100+
"before_limit": 2,
101+
"after_limit": 2,
102+
},
103+
},
104+
},
105+
})
106+
107+
resp := alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "search"}, searchRequest)
108+
sce := "search_categories.room_events"
109+
result0 := sce + ".results.0.result"
110+
resBefore := sce + ".results.0.context.events_before"
111+
resAfter := sce + ".results.0.context.events_after"
112+
must.MatchResponse(t, resp, match.HTTPResponse{
113+
StatusCode: http.StatusOK,
114+
JSON: []match.JSON{
115+
match.JSONKeyPresent(sce + ".count"),
116+
match.JSONKeyPresent(sce + ".results"),
117+
match.JSONKeyPresent(sce + ".next_batch"),
118+
match.JSONKeyEqual(sce+".count", float64(1)),
119+
match.JSONKeyEqual(result0+".room_id", roomID),
120+
match.JSONKeyPresent(result0 + ".content"),
121+
match.JSONKeyPresent(result0 + ".type"),
122+
match.JSONKeyEqual(result0+".content.body", "Message number 4"),
123+
match.JSONKeyEqual(resBefore+".0.content.body", "Message number 3"),
124+
match.JSONKeyEqual(resBefore+".1.content.body", "Message number 2"),
125+
match.JSONKeyEqual(resAfter+".0.content.body", "Message number 5"),
126+
match.JSONKeyEqual(resAfter+".1.content.body", "Message number 6"),
127+
},
128+
})
129+
})
130+
131+
// sytest: Can back-paginate search results
132+
t.Run("Can back-paginate search results", func(t *testing.T) {
133+
t.Parallel()
134+
roomID := alice.CreateRoom(t, map[string]interface{}{
135+
"preset": "private_chat",
136+
})
137+
138+
eventIDs := make([]string, 20)
139+
for i := 0; i <= 19; i++ {
140+
eventID := alice.SendEventSynced(t, roomID, b.Event{
141+
Type: "m.room.message",
142+
Content: map[string]interface{}{
143+
"body": fmt.Sprintf("Message number %d", i),
144+
"msgtype": "m.text",
145+
},
146+
})
147+
eventIDs[i] = eventID
148+
}
149+
150+
searchRequest := client.WithJSONBody(t, map[string]interface{}{
151+
"search_categories": map[string]interface{}{
152+
"room_events": map[string]interface{}{
153+
"keys": []string{"content.body"},
154+
"search_term": "Message",
155+
"order_by": "recent",
156+
"filter": map[string]interface{}{
157+
"limit": 10,
158+
"rooms": []string{roomID},
159+
},
160+
},
161+
},
162+
})
163+
164+
resp := alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "search"}, searchRequest)
165+
166+
// First search result
167+
nextBatch := checkBackpaginateResult(t, resp, 20, eventIDs[19], eventIDs[10])
168+
169+
if nextBatch == "" {
170+
t.Fatalf("no next batch set!")
171+
}
172+
173+
values := url.Values{}
174+
values.Set("next_batch", nextBatch)
175+
params := client.WithQueries(values)
176+
resp = alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "search"}, searchRequest, params)
177+
// Second search result
178+
nextBatch = checkBackpaginateResult(t, resp, 20, eventIDs[9], eventIDs[0])
179+
180+
// At this point we expect next_batch to be empty
181+
values.Set("next_batch", nextBatch)
182+
params = client.WithQueries(values)
183+
resp = alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "search"}, searchRequest, params)
184+
// third search result
185+
sce := "search_categories.room_events"
186+
result0 := sce + ".results.0.result"
187+
must.MatchResponse(t, resp, match.HTTPResponse{
188+
StatusCode: http.StatusOK,
189+
JSON: []match.JSON{
190+
match.JSONKeyPresent(sce + ".count"),
191+
match.JSONKeyPresent(sce + ".results"),
192+
match.JSONKeyEqual(sce+".count", float64(20)),
193+
match.JSONKeyMissing(result0),
194+
match.JSONKeyMissing(sce + ".next_batch"),
195+
},
196+
})
197+
})
198+
199+
// sytest: Search works across an upgraded room and its predecessor
200+
t.Run("Search works across an upgraded room and its predecessor", func(t *testing.T) {
201+
t.Parallel()
202+
roomID := alice.CreateRoom(t, map[string]interface{}{
203+
"preset": "private_chat",
204+
"version": "8",
205+
})
206+
207+
eventBeforeUpgrade := alice.SendEventSynced(t, roomID, b.Event{
208+
Type: "m.room.message",
209+
Content: map[string]interface{}{
210+
"body": "Message before upgrade",
211+
"msgtype": "m.text",
212+
},
213+
})
214+
215+
upgradeBody := client.WithJSONBody(t, map[string]string{
216+
"new_version": "9",
217+
})
218+
upgradeResp := alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "upgrade"}, upgradeBody)
219+
body := must.ParseJSON(t, upgradeResp.Body)
220+
newRoomID := must.GetJSONFieldStr(t, body, "replacement_room")
221+
222+
eventAfterUpgrade := alice.SendEventSynced(t, newRoomID, b.Event{
223+
Type: "m.room.message",
224+
Content: map[string]interface{}{
225+
"body": "Message after upgrade",
226+
"msgtype": "m.text",
227+
},
228+
})
229+
230+
searchRequest := client.WithJSONBody(t, map[string]interface{}{
231+
"search_categories": map[string]interface{}{
232+
"room_events": map[string]interface{}{
233+
"keys": []string{"content.body"},
234+
"search_term": "upgrade",
235+
"filter": map[string]interface{}{
236+
"rooms": []string{roomID, newRoomID},
237+
},
238+
},
239+
},
240+
})
241+
242+
resp := alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "search"}, searchRequest)
243+
sce := "search_categories.room_events"
244+
result0 := sce + ".results.0.result"
245+
result1 := sce + ".results.1.result"
246+
must.MatchResponse(t, resp, match.HTTPResponse{
247+
StatusCode: http.StatusOK,
248+
JSON: []match.JSON{
249+
match.JSONKeyPresent(sce + ".count"),
250+
match.JSONKeyPresent(sce + ".results"),
251+
match.JSONKeyEqual(sce+".count", float64(2)),
252+
match.JSONKeyPresent(result0 + ".content"),
253+
match.JSONKeyPresent(result0 + ".type"),
254+
match.JSONKeyEqual(result0+".event_id", eventBeforeUpgrade),
255+
match.JSONKeyEqual(result1+".event_id", eventAfterUpgrade),
256+
match.JSONKeyEqual(result0+".content.body", "Message before upgrade"),
257+
match.JSONKeyEqual(result1+".content.body", "Message after upgrade"),
258+
},
259+
})
260+
})
261+
262+
for _, ordering := range []string{"rank", "recent"} {
263+
// sytest: Search results with $ordering_type ordering do not include redacted events
264+
t.Run(fmt.Sprintf("Search results with %s ordering do not include redacted events", ordering), func(t *testing.T) {
265+
t.Parallel()
266+
roomID := alice.CreateRoom(t, map[string]interface{}{
267+
"preset": "private_chat",
268+
})
269+
270+
redactedEventID := alice.SendEventSynced(t, roomID, b.Event{
271+
Type: "m.room.message",
272+
Content: map[string]interface{}{
273+
"body": "This message is going to be redacted",
274+
"msgtype": "m.text",
275+
},
276+
})
277+
278+
wantContentBody := "This message is not going to be redacted"
279+
visibleEventID := alice.SendEventSynced(t, roomID, b.Event{
280+
Type: "m.room.message",
281+
Content: map[string]interface{}{
282+
"body": wantContentBody,
283+
"msgtype": "m.text",
284+
},
285+
})
286+
287+
// redact the event
288+
redactBody := client.WithJSONBody(t, map[string]interface{}{"reason": "testing"})
289+
txnID := util.RandomString(8) // random string, as time.Now().Unix() might create the same txnID
290+
resp := alice.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "redact", redactedEventID, txnID}, redactBody)
291+
j := must.ParseJSON(t, resp.Body)
292+
redactionEventID := must.GetJSONFieldStr(t, j, "event_id")
293+
// wait for the redaction to come down sync
294+
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncTimelineHasEventID(roomID, redactionEventID))
295+
296+
searchRequest := client.WithJSONBody(t, map[string]interface{}{
297+
"search_categories": map[string]interface{}{
298+
"room_events": map[string]interface{}{
299+
"keys": []string{"content.body"},
300+
"order_by": ordering,
301+
"search_term": "redacted",
302+
"filter": map[string]interface{}{
303+
"rooms": []string{roomID},
304+
},
305+
},
306+
},
307+
})
308+
309+
resp = alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "search"}, searchRequest)
310+
sce := "search_categories.room_events"
311+
result0 := sce + ".results.0.result"
312+
must.MatchResponse(t, resp, match.HTTPResponse{
313+
StatusCode: http.StatusOK,
314+
JSON: []match.JSON{
315+
match.JSONKeyPresent(sce + ".count"),
316+
match.JSONKeyPresent(sce + ".results"),
317+
match.JSONKeyArrayOfSize(sce+".results", 1),
318+
match.JSONKeyPresent(result0 + ".content"),
319+
match.JSONKeyPresent(result0 + ".type"),
320+
match.JSONKeyEqual(result0+".event_id", visibleEventID),
321+
match.JSONKeyEqual(result0+".content.body", wantContentBody),
322+
},
323+
})
324+
})
325+
}
326+
327+
})
328+
}
329+
330+
func checkBackpaginateResult(t *testing.T, resp *http.Response, wantCount float64, lastEventID, firstEventID string) (nextBatch string) {
331+
sce := "search_categories.room_events"
332+
result0 := sce + ".results.0.result"
333+
result9 := sce + ".results.9.result"
334+
must.MatchResponse(t, resp, match.HTTPResponse{
335+
StatusCode: http.StatusOK,
336+
JSON: []match.JSON{
337+
match.JSONKeyPresent(sce + ".count"),
338+
match.JSONKeyPresent(sce + ".results"),
339+
match.JSONMapEach(sce, func(k, v gjson.Result) error {
340+
if k.Str == "next_batch" {
341+
nextBatch = v.Str
342+
return nil
343+
}
344+
return nil
345+
}),
346+
match.JSONKeyPresent(sce + ".next_batch"),
347+
match.JSONKeyEqual(sce+".count", wantCount),
348+
match.JSONKeyPresent(result0 + ".content"),
349+
match.JSONKeyPresent(result0 + ".type"),
350+
match.JSONKeyEqual(result0+".event_id", lastEventID),
351+
match.JSONKeyEqual(result9+".event_id", firstEventID),
352+
},
353+
})
354+
return
355+
}

0 commit comments

Comments
 (0)