2023-03-27 09:26:52 +00:00
|
|
|
package routing
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2023-06-06 20:55:18 +00:00
|
|
|
"context"
|
2023-03-27 09:26:52 +00:00
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/matrix-org/dendrite/internal/fulltext"
|
|
|
|
"github.com/matrix-org/dendrite/internal/sqlutil"
|
2023-06-06 20:55:18 +00:00
|
|
|
rsapi "github.com/matrix-org/dendrite/roomserver/api"
|
2023-04-27 11:54:20 +00:00
|
|
|
rstypes "github.com/matrix-org/dendrite/roomserver/types"
|
2023-03-27 09:26:52 +00:00
|
|
|
"github.com/matrix-org/dendrite/syncapi/storage"
|
2023-04-04 17:16:53 +00:00
|
|
|
"github.com/matrix-org/dendrite/syncapi/synctypes"
|
2023-03-27 09:26:52 +00:00
|
|
|
"github.com/matrix-org/dendrite/syncapi/types"
|
|
|
|
"github.com/matrix-org/dendrite/test"
|
|
|
|
"github.com/matrix-org/dendrite/test/testrig"
|
|
|
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
|
|
|
"github.com/matrix-org/gomatrixserverlib"
|
2023-04-19 14:50:33 +00:00
|
|
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
2023-03-27 09:26:52 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
)
|
|
|
|
|
2023-06-06 20:55:18 +00:00
|
|
|
type FakeSyncRoomserverAPI struct{ rsapi.SyncRoomserverAPI }
|
|
|
|
|
2023-06-14 14:23:46 +00:00
|
|
|
func (f *FakeSyncRoomserverAPI) QueryUserIDForSender(ctx context.Context, roomID spec.RoomID, senderID spec.SenderID) (*spec.UserID, error) {
|
2023-06-07 17:14:35 +00:00
|
|
|
return spec.NewUserID(string(senderID), true)
|
2023-06-06 20:55:18 +00:00
|
|
|
}
|
|
|
|
|
2023-03-27 09:26:52 +00:00
|
|
|
func TestSearch(t *testing.T) {
|
|
|
|
alice := test.NewUser(t)
|
|
|
|
aliceDevice := userapi.Device{UserID: alice.ID}
|
|
|
|
room := test.NewRoom(t, alice)
|
|
|
|
room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": "context before"})
|
|
|
|
room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": "hello world3!"})
|
|
|
|
room.CreateAndInsert(t, alice, "m.room.message", map[string]interface{}{"body": "context after"})
|
|
|
|
|
|
|
|
roomsFilter := []string{room.ID}
|
|
|
|
roomsFilterUnknown := []string{"!unknown"}
|
|
|
|
|
|
|
|
emptyFromString := ""
|
|
|
|
fromStringValid := "1"
|
|
|
|
fromStringInvalid := "iCantBeParsed"
|
|
|
|
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
wantOK bool
|
|
|
|
searchReq SearchRequest
|
|
|
|
device *userapi.Device
|
|
|
|
wantResponseCount int
|
|
|
|
from *string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "no user ID",
|
|
|
|
searchReq: SearchRequest{},
|
|
|
|
device: &userapi.Device{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "with alice ID",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{},
|
|
|
|
device: &aliceDevice,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "searchTerm specified, found at the beginning",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{RoomEvents: RoomEvents{SearchTerm: "hello"}},
|
|
|
|
},
|
|
|
|
device: &aliceDevice,
|
|
|
|
wantResponseCount: 1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "searchTerm specified, found at the end",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{RoomEvents: RoomEvents{SearchTerm: "world3"}},
|
|
|
|
},
|
|
|
|
device: &aliceDevice,
|
|
|
|
wantResponseCount: 1,
|
|
|
|
},
|
|
|
|
/* the following would need matchQuery.SetFuzziness(1) in bleve.go
|
|
|
|
{
|
|
|
|
name: "searchTerm fuzzy search",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{RoomEvents: RoomEvents{SearchTerm: "hell"}}, // this still should find hello world
|
|
|
|
},
|
|
|
|
device: &aliceDevice,
|
|
|
|
wantResponseCount: 1,
|
|
|
|
},
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
name: "searchTerm specified but no result",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{RoomEvents: RoomEvents{SearchTerm: "i don't match"}},
|
|
|
|
},
|
|
|
|
device: &aliceDevice,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "filter on room",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{
|
|
|
|
RoomEvents: RoomEvents{
|
|
|
|
SearchTerm: "hello",
|
2023-04-04 17:16:53 +00:00
|
|
|
Filter: synctypes.RoomEventFilter{
|
2023-03-27 09:26:52 +00:00
|
|
|
Rooms: &roomsFilter,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
device: &aliceDevice,
|
|
|
|
wantResponseCount: 1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "filter on unknown room",
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{
|
|
|
|
RoomEvents: RoomEvents{
|
|
|
|
SearchTerm: "hello",
|
2023-04-04 17:16:53 +00:00
|
|
|
Filter: synctypes.RoomEventFilter{
|
2023-03-27 09:26:52 +00:00
|
|
|
Rooms: &roomsFilterUnknown,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
device: &aliceDevice,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "include state",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{
|
|
|
|
RoomEvents: RoomEvents{
|
|
|
|
SearchTerm: "hello",
|
2023-04-04 17:16:53 +00:00
|
|
|
Filter: synctypes.RoomEventFilter{
|
2023-03-27 09:26:52 +00:00
|
|
|
Rooms: &roomsFilter,
|
|
|
|
},
|
|
|
|
IncludeState: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
device: &aliceDevice,
|
|
|
|
wantResponseCount: 1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "empty from does not error",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{
|
|
|
|
RoomEvents: RoomEvents{
|
|
|
|
SearchTerm: "hello",
|
2023-04-04 17:16:53 +00:00
|
|
|
Filter: synctypes.RoomEventFilter{
|
2023-03-27 09:26:52 +00:00
|
|
|
Rooms: &roomsFilter,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantResponseCount: 1,
|
|
|
|
device: &aliceDevice,
|
|
|
|
from: &emptyFromString,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "valid from does not error",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{
|
|
|
|
RoomEvents: RoomEvents{
|
|
|
|
SearchTerm: "hello",
|
2023-04-04 17:16:53 +00:00
|
|
|
Filter: synctypes.RoomEventFilter{
|
2023-03-27 09:26:52 +00:00
|
|
|
Rooms: &roomsFilter,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantResponseCount: 1,
|
|
|
|
device: &aliceDevice,
|
|
|
|
from: &fromStringValid,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "invalid from does error",
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{
|
|
|
|
RoomEvents: RoomEvents{
|
|
|
|
SearchTerm: "hello",
|
2023-04-04 17:16:53 +00:00
|
|
|
Filter: synctypes.RoomEventFilter{
|
2023-03-27 09:26:52 +00:00
|
|
|
Rooms: &roomsFilter,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
device: &aliceDevice,
|
|
|
|
from: &fromStringInvalid,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "order by stream position",
|
|
|
|
wantOK: true,
|
|
|
|
searchReq: SearchRequest{
|
|
|
|
SearchCategories: SearchCategories{RoomEvents: RoomEvents{SearchTerm: "hello", OrderBy: "recent"}},
|
|
|
|
},
|
|
|
|
device: &aliceDevice,
|
|
|
|
wantResponseCount: 1,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
|
|
|
|
cfg, processCtx, closeDB := testrig.CreateConfig(t, dbType)
|
|
|
|
defer closeDB()
|
|
|
|
|
|
|
|
// create requisites
|
|
|
|
fts, err := fulltext.New(processCtx, cfg.SyncAPI.Fulltext)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.NotNil(t, fts)
|
|
|
|
|
|
|
|
cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
|
|
|
|
db, err := storage.NewSyncServerDatasource(processCtx.Context(), cm, &cfg.SyncAPI.Database)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
elements := []fulltext.IndexElement{}
|
|
|
|
// store the events in the database
|
|
|
|
var sp types.StreamPosition
|
|
|
|
for _, x := range room.Events() {
|
2023-04-27 11:54:20 +00:00
|
|
|
var stateEvents []*rstypes.HeaderedEvent
|
2023-03-27 09:26:52 +00:00
|
|
|
var stateEventIDs []string
|
2023-04-19 14:50:33 +00:00
|
|
|
if x.Type() == spec.MRoomMember {
|
2023-03-27 09:26:52 +00:00
|
|
|
stateEvents = append(stateEvents, x)
|
|
|
|
stateEventIDs = append(stateEventIDs, x.EventID())
|
|
|
|
}
|
2023-06-28 18:29:49 +00:00
|
|
|
x.StateKeyResolved = x.StateKey()
|
2023-03-27 09:26:52 +00:00
|
|
|
sp, err = db.WriteEvent(processCtx.Context(), x, stateEvents, stateEventIDs, nil, nil, false, gomatrixserverlib.HistoryVisibilityShared)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
if x.Type() != "m.room.message" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
elements = append(elements, fulltext.IndexElement{
|
|
|
|
EventID: x.EventID(),
|
|
|
|
RoomID: x.RoomID(),
|
|
|
|
Content: string(x.Content()),
|
|
|
|
ContentType: x.Type(),
|
|
|
|
StreamPosition: int64(sp),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
// Index the events
|
|
|
|
err = fts.Index(elements...)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// run the tests
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
reqBody := &bytes.Buffer{}
|
|
|
|
err = json.NewEncoder(reqBody).Encode(tc.searchReq)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/", reqBody)
|
|
|
|
|
2023-06-06 20:55:18 +00:00
|
|
|
res := Search(req, tc.device, db, fts, tc.from, &FakeSyncRoomserverAPI{})
|
2023-03-27 09:26:52 +00:00
|
|
|
if !tc.wantOK && !res.Is2xx() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
resp, ok := res.JSON.(SearchResponse)
|
|
|
|
if !ok && !tc.wantOK {
|
|
|
|
t.Fatalf("not a SearchResponse: %T: %s", res.JSON, res.JSON)
|
|
|
|
}
|
|
|
|
assert.Equal(t, tc.wantResponseCount, resp.SearchCategories.RoomEvents.Count)
|
|
|
|
|
|
|
|
// if we requested state, it should not be empty
|
|
|
|
if tc.searchReq.SearchCategories.RoomEvents.IncludeState {
|
|
|
|
assert.NotEmpty(t, resp.SearchCategories.RoomEvents.State)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|