Skip to content
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

MSC3030 Jump to date API endpoint #178

Merged
merged 23 commits into from
Mar 3, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1889d0a
Add tests to ensure MSC3030 functionality
MadLittleMods Jul 28, 2021
55385ec
Add color coding debug string when the test fails
MadLittleMods Jul 28, 2021
2a360a0
Remove unused GetEvent and GetJSONFieldStringMap functions
MadLittleMods Jul 28, 2021
352b8e9
Merge branch 'master' into madlittlemods/msc3030-jump-to-date
MadLittleMods Nov 10, 2021
5990efe
Fix timestamp insertion in debug string
MadLittleMods Nov 12, 2021
f8b88a9
Show debug message list in scrollback order how you would see it in a…
MadLittleMods Nov 12, 2021
42af51a
Update to use direction paremeter
MadLittleMods Nov 12, 2021
5233383
Add tests for finding nothing before and after the event timeline
MadLittleMods Nov 12, 2021
4348803
Add some comments
MadLittleMods Nov 12, 2021
83f993c
Make tests parallel and add federation tests
MadLittleMods Nov 16, 2021
7d3a691
Add experimental feature flag and use unstable endpoint
MadLittleMods Nov 17, 2021
97a287f
Add potential history visibility test
MadLittleMods Nov 30, 2021
d96622b
Remove superfluous history visibility test
MadLittleMods Nov 30, 2021
e8787c1
Add actual/expected text labels so it's usable with ascii colors
MadLittleMods Dec 3, 2021
f71f88f
Remove commented out code
MadLittleMods Dec 3, 2021
94957f4
Add test to make sure we're not leaking events from private rooms
MadLittleMods Dec 3, 2021
39cb82c
Refactor language to want/got
MadLittleMods Dec 18, 2021
2486df7
Merge remote-tracking branch 'origin/main' into madlittlemods/msc3030…
MadLittleMods Feb 18, 2022
5678986
Merge branch 'main' into madlittlemods/msc3030-jump-to-date
MadLittleMods Mar 1, 2022
148387e
Always set preset since Synapse/Dendrite disagree on what the default is
MadLittleMods Mar 3, 2022
ccb51a9
Fix message A vs B typo
MadLittleMods Mar 3, 2022
3b914c2
Merge branch 'main' into madlittlemods/msc3030-jump-to-date
MadLittleMods Mar 3, 2022
a383a08
Also add test to make sure we don't leak from public room either
MadLittleMods Mar 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,27 @@ func (c *CSAPI) RegisterUser(t *testing.T, localpart, password string) (userID,
return userID, accessToken
}

// GetEvent fetches the given event from the specified room and returns a typed Event
func (c *CSAPI) GetEvent(t *testing.T, roomID, eventId string) b.Event {
t.Helper()
res := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "event", eventId})
body := ParseJSON(t, res)

statKeyRes := gjson.GetBytes(body, "state_key")
var stateKey *string = nil
if statKeyRes.Exists() {
stateKey = b.Ptr(statKeyRes.Str)
}

return b.Event{
Type: GetJSONFieldStr(t, body, "type"),
Sender: GetJSONFieldStr(t, body, "sender"),
StateKey: stateKey,
Content: GetJSONFieldStringMap(t, body, "content"),
Unsigned: GetJSONFieldStringMap(t, body, "unsigned"),
}
}

// MustDo will do the HTTP request and fail the test if the response is not 2xx
func (c *CSAPI) MustDo(t *testing.T, method string, paths []string, jsonBody interface{}) *http.Response {
t.Helper()
Expand Down Expand Up @@ -406,6 +427,26 @@ func GetJSONFieldStringArray(t *testing.T, body []byte, wantKey string) []string
return arr
}

func GetJSONFieldStringMap(t *testing.T, body []byte, wantKey string) map[string]interface{} {
t.Helper()

res := gjson.GetBytes(body, wantKey)

if !res.Exists() {
t.Fatalf("GetJSONFieldStringMap: key '%s' missing from %s", wantKey, string(body))
}

jsonMap := map[string]interface{}{}
res.ForEach(func(key, value gjson.Result) bool {
jsonMap[key.Str] = value.Str

// Keep iterating
return true
})

return jsonMap
}

// ParseJSON parses a JSON-encoded HTTP Response body into a byte slice
func ParseJSON(t *testing.T, res *http.Response) []byte {
t.Helper()
Expand Down
147 changes: 147 additions & 0 deletions tests/msc3030_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// +build msc3030

// This file contains tests for a jump to date API endpoint,
// currently experimental feature defined by MSC3030, which you can read here:
// https://github.com/matrix-org/matrix-doc/pull/3030

package tests

import (
"fmt"
"net/url"
"strconv"
"testing"
"time"

"github.com/matrix-org/complement/internal/b"
"github.com/matrix-org/complement/internal/client"
"github.com/tidwall/gjson"
)

func TestJumpToDateEndpoint(t *testing.T) {
deployment := Deploy(t, b.BlueprintFederationTwoLocalOneRemote)
defer deployment.Destroy(t)

// Create the normal user which will send messages in the room
userID := "@alice:hs1"
alice := deployment.Client(t, "hs1", userID)

roomID := alice.CreateRoom(t, map[string]interface{}{})
alice.JoinRoom(t, roomID, nil)

timeBeforeEventA := time.Now()
eventAID := alice.SendEventSynced(t, roomID, b.Event{
Type: "m.room.message",
Content: map[string]interface{}{
"msgtype": "m.text",
"body": "Message A",
},
})
eventBID := alice.SendEventSynced(t, roomID, b.Event{
Type: "m.room.message",
Content: map[string]interface{}{
"msgtype": "m.text",
"body": "Message A",
MadLittleMods marked this conversation as resolved.
Show resolved Hide resolved
},
})
timeAfterEventB := time.Now()

t.Run("parallel", func(t *testing.T) {
t.Run("should find event after given timestmap", func(t *testing.T) {
checkEventisReturnedForTime(t, alice, roomID, timeBeforeEventA, eventAID)
})

t.Run("should find event before given timestmap", func(t *testing.T) {
checkEventisReturnedForTime(t, alice, roomID, timeAfterEventB, eventBID)
})
})
}

func checkEventisReturnedForTime(t *testing.T, c *client.CSAPI, roomID string, givenTime time.Time, expectedEventId string) {
t.Helper()

givenTimestamp := makeTimestampFromTime(givenTime)
timestampString := strconv.FormatInt(givenTimestamp, 10)
timestampToEventRes := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "timestamp_to_event"}, client.WithContentType("application/json"), client.WithQueries(url.Values{
"ts": []string{timestampString},
}))
timestampToEventResBody := client.ParseJSON(t, timestampToEventRes)

actualEventIdRes := gjson.GetBytes(timestampToEventResBody, "event_id")
actualEventId := actualEventIdRes.String()

if actualEventId != expectedEventId {
debugMessageList := getDebugMessageListFromMessagesResponse(t, c, roomID, expectedEventId, actualEventId, givenTimestamp)
t.Fatalf(
"Expected to see %s given %s but received %s\n%s",
decorateStringWithAnsiColor(expectedEventId, AnsiColorGreen),
decorateStringWithAnsiColor(timestampString, AnsiColorYellow),
decorateStringWithAnsiColor(actualEventId, AnsiColorRed),
debugMessageList,
)
kegsay marked this conversation as resolved.
Show resolved Hide resolved
}
}

func getDebugMessageListFromMessagesResponse(t *testing.T, c *client.CSAPI, roomID string, expectedEventId string, actualEventId string, givenTimestamp int64) string {
t.Helper()

messagesRes := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "r0", "rooms", roomID, "messages"}, client.WithContentType("application/json"), client.WithQueries(url.Values{
"dir": []string{"b"},
"limit": []string{"100"},
}))
messsageResBody := client.ParseJSON(t, messagesRes)

wantKey := "chunk"
res := gjson.GetBytes(messsageResBody, wantKey)
if !res.Exists() {
t.Fatalf("missing key '%s'", wantKey)
}
if !res.IsArray() {
t.Fatalf("key '%s' is not an array (was %s)", wantKey, res.Type)
}

givenTimestampMarker := decorateStringWithAnsiColor(fmt.Sprintf("-- givenTimestamp=%s --\n", strconv.FormatInt(givenTimestamp, 10)), AnsiColorYellow)

resultantString := ""
givenTimestampAlreadyInserted := false
res.ForEach(func(key, r gjson.Result) bool {
// The timestmap could be after-in-time of any of the events
if r.Get("origin_server_ts").Int() < givenTimestamp && !givenTimestampAlreadyInserted {
resultantString += givenTimestampMarker
givenTimestampAlreadyInserted = true
}

event_id := r.Get("event_id").String()
event_id_string := event_id
if event_id == expectedEventId {
event_id_string = decorateStringWithAnsiColor(event_id, AnsiColorGreen)
} else if event_id == actualEventId {
event_id_string = decorateStringWithAnsiColor(event_id, AnsiColorRed)
}

resultantString += fmt.Sprintf("%s (%s) - %s\n", event_id_string, strconv.FormatInt(r.Get("origin_server_ts").Int(), 10), r.Get("type").String())

// The timestmap could be before-in-time of any of the events
if r.Get("origin_server_ts").Int() > givenTimestamp && !givenTimestampAlreadyInserted {
resultantString += givenTimestampMarker
givenTimestampAlreadyInserted = true
}

// keep iterating
return true
})

return resultantString
}

func makeTimestampFromTime(t time.Time) int64 {
return t.UnixNano() / int64(time.Millisecond)
}

const AnsiColorRed string = "31"
const AnsiColorGreen string = "32"
const AnsiColorYellow string = "33"

func decorateStringWithAnsiColor(inputString, decorationColor string) string {
return fmt.Sprintf("\033[%sm%s\033[0m", decorationColor, inputString)
}