-
Notifications
You must be signed in to change notification settings - Fork 61
Add tests for MSC3771 / MSC3773: threaded receipts & notifications #496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
d5fa161
2c42cf9
2c71289
640982a
6f31028
1124741
a5add36
fafa3c4
cc1a391
87fa4e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| package csapi_tests | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "testing" | ||
|
|
||
| "github.com/tidwall/gjson" | ||
|
|
||
| "github.com/matrix-org/complement/internal/b" | ||
| "github.com/matrix-org/complement/internal/client" | ||
| "github.com/matrix-org/complement/runtime" | ||
| ) | ||
|
|
||
| func syncHasUnreadNotifs(roomID string, check func(gjson.Result, gjson.Result) bool) client.SyncCheckOpt { | ||
| return func(clientUserID string, topLevelSyncJSON gjson.Result) error { | ||
| unreadNotifications := topLevelSyncJSON.Get("rooms.join." + client.GjsonEscape(roomID) + ".unread_notifications") | ||
| unreadThreadNotifications := topLevelSyncJSON.Get("rooms.join." + client.GjsonEscape(roomID) + ".unread_thread_notifications") | ||
| if !unreadNotifications.Exists() { | ||
| return fmt.Errorf("syncHasUnreadNotifs(%s): missing notifications", roomID) | ||
clokep marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| if check(unreadNotifications, unreadThreadNotifications) { | ||
| return nil | ||
| } | ||
| return fmt.Errorf("syncHasUnreadNotifs(%s): check function did not pass: %v / %v", roomID, unreadNotifications.Raw, unreadThreadNotifications.Raw) | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could craft much better errors if we changed the (however to do this in Go) syncHasUnreadNotifs(roomID, {
main: {
highlight_count: 2,
notification_count: 4
},
[firstEventID]: {
label: "firstEventIDThreadNotifications",
highlight_count: 1,
notification_count: 4
}
})
For comparison as a different, separate example, I made the errors for the MSC3030 tests a bit fancy to show the diff of what was expected vs actual in context of the room timeline so it's much easier to figure out what's going wrong, see #178 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I left this somewhat generic because:
|
||
|
|
||
| // Test behavior of threaded receipts and notifications. | ||
| // | ||
| // 1. Send a series of messages, some of which are in threads. | ||
| // 2. Send combinations of threaded and unthreaded receipts. | ||
| // 3. Ensure the notification counts are updated appropriately. | ||
| // | ||
| // This sends four messages as alice creating a DAG somewhat like: | ||
| // | ||
| // A<--B<--C | ||
| // ^ | ||
| // +---D | ||
clokep marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // | ||
| // Where C and D generate highlight notifications. | ||
| // | ||
| // Notification counts and receipts are handled by bob. | ||
| func TestThreadedReceipts(t *testing.T) { | ||
| runtime.SkipIf(t, runtime.Dendrite) // not supported | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Feels a bit icky to couple the test to the implementation, but I suppose we do this everywhere. Might be possible to do something cleaner with build tags, but that might be a faff too (would have to e.g. update Synapse CI and complement.sh...) This is probably fine as it is. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we only use build tags for unstable features? |
||
| deployment := Deploy(t, b.BlueprintOneToOneRoom) | ||
| defer deployment.Destroy(t) | ||
|
|
||
| // Create a room with alice and bob. | ||
| alice := deployment.Client(t, "hs1", "@alice:hs1") | ||
| bob := deployment.Client(t, "hs1", "@bob:hs1") | ||
|
|
||
| roomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) | ||
| bob.JoinRoom(t, roomID, nil) | ||
|
|
||
| token := bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) | ||
clokep marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Send an initial message as alice. | ||
| firstEventID := alice.SendEventSynced(t, roomID, b.Event{ | ||
clokep marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Type: "m.room.message", | ||
| Content: map[string]interface{}{ | ||
| "msgtype": "m.text", | ||
| "body": "Hello world!", | ||
| }, | ||
| }) | ||
|
|
||
| // Create a thread from the above message and send both two messages in it, | ||
| // the second of which is a mention (causing a highlight). | ||
| firstThreadEventID := alice.SendEventSynced(t, roomID, b.Event{ | ||
| Type: "m.room.message", | ||
| Content: map[string]interface{}{ | ||
| "msgtype": "m.text", | ||
| "body": "Start thread!", | ||
| "m.relates_to": map[string]interface{}{ | ||
| "event_id": firstEventID, | ||
| "rel_type": "m.thread", | ||
| }, | ||
| }, | ||
| }) | ||
| alice.SendEventSynced(t, roomID, b.Event{ | ||
| Type: "m.room.message", | ||
| Content: map[string]interface{}{ | ||
| "msgtype": "m.text", | ||
| "body": fmt.Sprintf("Thread response %s!", bob.UserID), | ||
| "m.relates_to": map[string]interface{}{ | ||
| "event_id": firstEventID, | ||
| "rel_type": "m.thread", | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| // Send an additional unthreaded message, which is a mention (causing a highlight). | ||
| secondEventID := alice.SendEventSynced(t, roomID, b.Event{ | ||
| Type: "m.room.message", | ||
| Content: map[string]interface{}{ | ||
| "msgtype": "m.text", | ||
| "body": fmt.Sprintf("Hello %s!", bob.UserID), | ||
| }, | ||
| }) | ||
|
|
||
| // A filter to get thread notifications. | ||
| threadFilter := `{"room":{"timeline":{"unread_thread_notifications":true}}}` | ||
|
|
||
| // Check the unthreaded and threaded counts, which should include all previously | ||
| // sent messages. | ||
| bob.MustSyncUntil( | ||
| t, client.SyncReq{Since: token}, | ||
| client.SyncTimelineHas(roomID, func(r gjson.Result) bool { | ||
| return r.Get("event_id").Str == secondEventID | ||
| }), | ||
| syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool { | ||
| return r.Get("highlight_count").Num == 2 && r.Get("notification_count").Num == 4 && !t.Exists() | ||
clokep marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }), | ||
| ) | ||
| bob.MustSyncUntil( | ||
| t, | ||
| client.SyncReq{Since: token, Filter: threadFilter}, client.SyncTimelineHas(roomID, func(r gjson.Result) bool { | ||
clokep marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return r.Get("event_id").Str == secondEventID | ||
| }), | ||
| syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool { | ||
| threadNotifications := t.Get(client.GjsonEscape(firstEventID)) | ||
| return r.Get("highlight_count").Num == 1 && r.Get("notification_count").Num == 2 && | ||
DMRobertson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| threadNotifications.Get("highlight_count").Num == 1 && threadNotifications.Get("notification_count").Num == 2 | ||
| }), | ||
| ) | ||
|
|
||
| // Mark the first event as read with a threaded receipt. This causes only the | ||
| // notification from that event to be marked as read and only impacts the main | ||
| // timeline. | ||
| bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "receipt", "m.read", firstEventID}, client.WithJSONBody(t, map[string]interface{}{"thread_id": "main"})) | ||
| bob.MustSyncUntil( | ||
| t, client.SyncReq{Since: token}, | ||
| client.SyncTimelineHas(roomID, func(r gjson.Result) bool { | ||
| return r.Get("event_id").Str == secondEventID | ||
| }), | ||
| syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool { | ||
| return r.Get("highlight_count").Num == 2 && r.Get("notification_count").Num == 3 && !t.Exists() | ||
| }), | ||
clokep marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ) | ||
| bob.MustSyncUntil( | ||
| t, | ||
| client.SyncReq{Since: token, Filter: threadFilter}, client.SyncTimelineHas(roomID, func(r gjson.Result) bool { | ||
| return r.Get("event_id").Str == secondEventID | ||
| }), | ||
| syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool { | ||
| threadNotifications := t.Get(client.GjsonEscape(firstEventID)) | ||
| return r.Get("highlight_count").Num == 1 && r.Get("notification_count").Num == 1 && | ||
| threadNotifications.Get("highlight_count").Num == 1 && threadNotifications.Get("notification_count").Num == 2 | ||
| }), | ||
| ) | ||
|
|
||
| // Mark the first thread event as read. This causes only the notification from | ||
| // that event to be marked as read and only impacts the thread timeline. | ||
| bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "receipt", "m.read", firstThreadEventID}, client.WithJSONBody(t, map[string]interface{}{"thread_id": firstEventID})) | ||
| bob.MustSyncUntil( | ||
| t, client.SyncReq{Since: token}, | ||
| client.SyncTimelineHas(roomID, func(r gjson.Result) bool { | ||
| return r.Get("event_id").Str == secondEventID | ||
| }), | ||
| syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool { | ||
| return r.Get("highlight_count").Num == 2 && r.Get("notification_count").Num == 2 && !t.Exists() | ||
| }), | ||
| ) | ||
| bob.MustSyncUntil( | ||
| t, | ||
| client.SyncReq{Since: token, Filter: threadFilter}, client.SyncTimelineHas(roomID, func(r gjson.Result) bool { | ||
| return r.Get("event_id").Str == secondEventID | ||
| }), | ||
| syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool { | ||
| threadNotifications := t.Get(client.GjsonEscape(firstEventID)) | ||
| return r.Get("highlight_count").Num == 1 && r.Get("notification_count").Num == 1 && | ||
| threadNotifications.Get("highlight_count").Num == 1 && threadNotifications.Get("notification_count").Num == 1 | ||
| }), | ||
| ) | ||
|
|
||
| // Mark the entire room as read by sending an unthreaded read receipt on the last | ||
| // event. This clears all notification counts. | ||
| bob.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "receipt", "m.read", secondEventID}, client.WithJSONBody(t, struct{}{})) | ||
| bob.MustSyncUntil( | ||
| t, client.SyncReq{Since: token}, | ||
| client.SyncTimelineHas(roomID, func(r gjson.Result) bool { | ||
| return r.Get("event_id").Str == secondEventID | ||
| }), | ||
| syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool { | ||
| return r.Get("highlight_count").Num == 0 && r.Get("notification_count").Num == 0 && !t.Exists() | ||
| }), | ||
| ) | ||
| bob.MustSyncUntil( | ||
| t, | ||
| client.SyncReq{Since: token, Filter: threadFilter}, client.SyncTimelineHas(roomID, func(r gjson.Result) bool { | ||
| return r.Get("event_id").Str == secondEventID | ||
| }), | ||
| syncHasUnreadNotifs(roomID, func(r gjson.Result, t gjson.Result) bool { | ||
| return r.Get("highlight_count").Num == 0 && r.Get("notification_count").Num == 0 && !t.Exists() | ||
| }), | ||
| ) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.