From 0cdde557a3ed7624de31aa844929037b65e1fe11 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 15 Apr 2018 15:36:01 +0300 Subject: [PATCH] Add tests for pushrule conditions and fix bugs found when making tests --- .gitignore | 1 + coverage.html | 682 ++++++++++++++++++ matrix/pushrules/condition.go | 55 +- .../pushrules/condition_displayname_test.go | 59 ++ matrix/pushrules/condition_eventmatch_test.go | 85 +++ .../pushrules/condition_membercount_test.go | 71 ++ matrix/pushrules/condition_test.go | 134 ++++ matrix/pushrules/pushrules_test.go | 15 +- matrix/pushrules/rule.go | 13 +- matrix/pushrules/ruleset.go | 3 +- 10 files changed, 1072 insertions(+), 46 deletions(-) create mode 100644 coverage.html create mode 100644 matrix/pushrules/condition_displayname_test.go create mode 100644 matrix/pushrules/condition_eventmatch_test.go create mode 100644 matrix/pushrules/condition_membercount_test.go create mode 100644 matrix/pushrules/condition_test.go diff --git a/.gitignore b/.gitignore index 7b6682e..a9c68ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea/ gomuks gomuks.exe +coverage.out diff --git a/coverage.html b/coverage.html new file mode 100644 index 0000000..c3d77dd --- /dev/null +++ b/coverage.html @@ -0,0 +1,682 @@ + + + + + + + + +
+ +
+ not tracked + + not covered + covered + +
+
+
+ + + + + + + + + + + +
+ + + diff --git a/matrix/pushrules/condition.go b/matrix/pushrules/condition.go index e9b11af..4d17695 100644 --- a/matrix/pushrules/condition.go +++ b/matrix/pushrules/condition.go @@ -26,6 +26,13 @@ import ( "maunium.net/go/gomuks/matrix/rooms" ) +// Room is an interface with the functions that are needed for processing room-specific push conditions +type Room interface { + GetMember(mxid string) *rooms.Member + GetMembers() map[string]*rooms.Member + GetSessionOwner() *rooms.Member +} + // PushCondKind is the type of a push condition. type PushCondKind string @@ -53,7 +60,7 @@ type PushCondition struct { var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$") // Match checks if this condition is fulfilled for the given event in the given room. -func (cond *PushCondition) Match(room *rooms.Room, event *gomatrix.Event) bool { +func (cond *PushCondition) Match(room Room, event *gomatrix.Event) bool { switch cond.Kind { case KindEventMatch: return cond.matchValue(room, event) @@ -66,7 +73,7 @@ func (cond *PushCondition) Match(room *rooms.Room, event *gomatrix.Event) bool { } } -func (cond *PushCondition) matchValue(room *rooms.Room, event *gomatrix.Event) bool { +func (cond *PushCondition) matchValue(room Room, event *gomatrix.Event) bool { index := strings.IndexRune(cond.Key, '.') key := cond.Key subkey := "" @@ -75,10 +82,7 @@ func (cond *PushCondition) matchValue(room *rooms.Room, event *gomatrix.Event) b key = key[0:index] } - pattern, err := glob.Compile(cond.Pattern) - if err != nil { - return false - } + pattern, _ := glob.Compile(cond.Pattern) switch key { case "type": @@ -100,48 +104,39 @@ func (cond *PushCondition) matchValue(room *rooms.Room, event *gomatrix.Event) b } } -func (cond *PushCondition) matchDisplayName(room *rooms.Room, event *gomatrix.Event) bool { - member := room.GetMember(room.SessionUserID) - if member == nil { +func (cond *PushCondition) matchDisplayName(room Room, event *gomatrix.Event) bool { + member := room.GetSessionOwner() + if member == nil || member.UserID == event.Sender { return false } text, _ := event.Content["body"].(string) return strings.Contains(text, member.DisplayName) } -func (cond *PushCondition) matchMemberCount(room *rooms.Room, event *gomatrix.Event) bool { - groupGroups := MemberCountFilterRegex.FindAllStringSubmatch(cond.MemberCountCondition, -1) - if len(groupGroups) != 1 { +func (cond *PushCondition) matchMemberCount(room Room, event *gomatrix.Event) bool { + group := MemberCountFilterRegex.FindStringSubmatch(cond.MemberCountCondition) + if len(group) != 3 { return false } - operator := "==" - wantedMemberCount := 0 - - group := groupGroups[0] - if len(group) == 0 { - return false - } else if len(group) == 1 { - wantedMemberCount, _ = strconv.Atoi(group[0]) - } else { - operator = group[0] - wantedMemberCount, _ = strconv.Atoi(group[1]) - } + operator := group[1] + wantedMemberCount, _ := strconv.Atoi(group[2]) memberCount := len(room.GetMembers()) switch operator { - case "==": - return wantedMemberCount == memberCount + case "==", "": + return memberCount == wantedMemberCount case ">": - return wantedMemberCount > memberCount + return memberCount > wantedMemberCount case ">=": - return wantedMemberCount >= memberCount + return memberCount >= wantedMemberCount case "<": - return wantedMemberCount < memberCount + return memberCount < wantedMemberCount case "<=": - return wantedMemberCount <= memberCount + return memberCount <= wantedMemberCount default: + // Should be impossible due to regex. return false } } diff --git a/matrix/pushrules/condition_displayname_test.go b/matrix/pushrules/condition_displayname_test.go new file mode 100644 index 0000000..e859ff8 --- /dev/null +++ b/matrix/pushrules/condition_displayname_test.go @@ -0,0 +1,59 @@ +// gomuks - A terminal Matrix client written in Go. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package pushrules_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPushCondition_Match_DisplayName(t *testing.T) { + event := newFakeEvent("m.room.message", map[string]interface{}{ + "msgtype": "m.text", + "body": "tulir: test mention", + }) + event.Sender = "@someone_else:matrix.org" + assert.True(t, displaynamePushCondition.Match(displaynameTestRoom, event)) +} + +func TestPushCondition_Match_DisplayName_Fail(t *testing.T) { + event := newFakeEvent("m.room.message", map[string]interface{}{ + "msgtype": "m.text", + "body": "not a mention", + }) + event.Sender = "@someone_else:matrix.org" + assert.False(t, displaynamePushCondition.Match(displaynameTestRoom, event)) +} + +func TestPushCondition_Match_DisplayName_CantHighlightSelf(t *testing.T) { + event := newFakeEvent("m.room.message", map[string]interface{}{ + "msgtype": "m.text", + "body": "tulir: I can't highlight myself", + }) + assert.False(t, displaynamePushCondition.Match(displaynameTestRoom, event)) +} + +func TestPushCondition_Match_DisplayName_FailsOnEmptyRoom(t *testing.T) { + emptyRoom := newFakeRoom(0) + event := newFakeEvent("m.room.message", map[string]interface{}{ + "msgtype": "m.text", + "body": "tulir: this room doesn't have the owner Member available, so it fails.", + }) + event.Sender = "@someone_else:matrix.org" + assert.False(t, displaynamePushCondition.Match(emptyRoom, event)) +} diff --git a/matrix/pushrules/condition_eventmatch_test.go b/matrix/pushrules/condition_eventmatch_test.go new file mode 100644 index 0000000..2fcd054 --- /dev/null +++ b/matrix/pushrules/condition_eventmatch_test.go @@ -0,0 +1,85 @@ +// gomuks - A terminal Matrix client written in Go. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package pushrules_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPushCondition_Match_KindEvent_MsgType(t *testing.T) { + condition := newMatchPushCondition("content.msgtype", "m.emote") + event := newFakeEvent("m.room.message", map[string]interface{}{ + "msgtype": "m.emote", + "body": "tests gomuks pushconditions", + }) + assert.True(t, condition.Match(blankTestRoom, event)) +} + +func TestPushCondition_Match_KindEvent_MsgType_Fail(t *testing.T) { + condition := newMatchPushCondition("content.msgtype", "m.emote") + + event := newFakeEvent("m.room.message", map[string]interface{}{ + "msgtype": "m.text", + "body": "I'm testing gomuks pushconditions", + }) + assert.False(t, condition.Match(blankTestRoom, event)) +} + +func TestPushCondition_Match_KindEvent_EventType(t *testing.T) { + condition := newMatchPushCondition("type", "m.room.foo") + event := newFakeEvent("m.room.foo", map[string]interface{}{}) + assert.True(t, condition.Match(blankTestRoom, event)) +} + +func TestPushCondition_Match_KindEvent_Sender_Fail(t *testing.T) { + condition := newMatchPushCondition("sender", "@foo:maunium.net") + event := newFakeEvent("m.room.foo", map[string]interface{}{}) + assert.False(t, condition.Match(blankTestRoom, event)) +} + +func TestPushCondition_Match_KindEvent_RoomID(t *testing.T) { + condition := newMatchPushCondition("room_id", "!fakeroom:maunium.net") + event := newFakeEvent("", map[string]interface{}{}) + assert.True(t, condition.Match(blankTestRoom, event)) +} + +func TestPushCondition_Match_KindEvent_BlankStateKey(t *testing.T) { + condition := newMatchPushCondition("state_key", "") + event := newFakeEvent("m.room.foo", map[string]interface{}{}) + assert.True(t, condition.Match(blankTestRoom, event)) +} + +func TestPushCondition_Match_KindEvent_BlankStateKey_Fail(t *testing.T) { + condition := newMatchPushCondition("state_key", "not blank") + event := newFakeEvent("m.room.foo", map[string]interface{}{}) + assert.False(t, condition.Match(blankTestRoom, event)) +} + +func TestPushCondition_Match_KindEvent_NonBlankStateKey(t *testing.T) { + condition := newMatchPushCondition("state_key", "*:maunium.net") + event := newFakeEvent("m.room.foo", map[string]interface{}{}) + event.StateKey = &event.Sender + assert.True(t, condition.Match(blankTestRoom, event)) +} + +func TestPushCondition_Match_KindEvent_UnknownKey(t *testing.T) { + condition := newMatchPushCondition("non-existent key", "doesn't affect anything") + event := newFakeEvent("m.room.foo", map[string]interface{}{}) + assert.False(t, condition.Match(blankTestRoom, event)) +} diff --git a/matrix/pushrules/condition_membercount_test.go b/matrix/pushrules/condition_membercount_test.go new file mode 100644 index 0000000..32a776b --- /dev/null +++ b/matrix/pushrules/condition_membercount_test.go @@ -0,0 +1,71 @@ +// gomuks - A terminal Matrix client written in Go. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package pushrules_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPushCondition_Match_KindMemberCount_OneToOne_ImplicitPrefix(t *testing.T) { + condition := newCountPushCondition("2") + room := newFakeRoom(2) + assert.True(t, condition.Match(room, countConditionTestEvent)) +} + +func TestPushCondition_Match_KindMemberCount_OneToOne_ExplicitPrefix(t *testing.T) { + condition := newCountPushCondition("==2") + room := newFakeRoom(2) + assert.True(t, condition.Match(room, countConditionTestEvent)) +} + +func TestPushCondition_Match_KindMemberCount_BigRoom(t *testing.T) { + condition := newCountPushCondition(">200") + room := newFakeRoom(201) + assert.True(t, condition.Match(room, countConditionTestEvent)) +} + +func TestPushCondition_Match_KindMemberCount_BigRoom_Fail(t *testing.T) { + condition := newCountPushCondition(">=200") + room := newFakeRoom(199) + assert.False(t, condition.Match(room, countConditionTestEvent)) +} + +func TestPushCondition_Match_KindMemberCount_SmallRoom(t *testing.T) { + condition := newCountPushCondition("<10") + room := newFakeRoom(9) + assert.True(t, condition.Match(room, countConditionTestEvent)) +} + +func TestPushCondition_Match_KindMemberCount_SmallRoom_Fail(t *testing.T) { + condition := newCountPushCondition("<=10") + room := newFakeRoom(11) + assert.False(t, condition.Match(room, countConditionTestEvent)) +} + +func TestPushCondition_Match_KindMemberCount_InvalidPrefix(t *testing.T) { + condition := newCountPushCondition("??10") + room := newFakeRoom(11) + assert.False(t, condition.Match(room, countConditionTestEvent)) +} + +func TestPushCondition_Match_KindMemberCount_InvalidCondition(t *testing.T) { + condition := newCountPushCondition("foobar") + room := newFakeRoom(1) + assert.False(t, condition.Match(room, countConditionTestEvent)) +} diff --git a/matrix/pushrules/condition_test.go b/matrix/pushrules/condition_test.go new file mode 100644 index 0000000..7fd06ee --- /dev/null +++ b/matrix/pushrules/condition_test.go @@ -0,0 +1,134 @@ +// gomuks - A terminal Matrix client written in Go. +// Copyright (C) 2018 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package pushrules_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "maunium.net/go/gomatrix" + "maunium.net/go/gomuks/matrix/pushrules" + "maunium.net/go/gomuks/matrix/rooms" +) + +var ( + blankTestRoom *rooms.Room + displaynameTestRoom pushrules.Room + + countConditionTestEvent *gomatrix.Event + + displaynamePushCondition *pushrules.PushCondition +) + +func init() { + blankTestRoom = rooms.NewRoom("!fakeroom:maunium.net", "@tulir:maunium.net") + + countConditionTestEvent = &gomatrix.Event{ + Sender: "@tulir:maunium.net", + Type: "m.room.message", + Timestamp: 1523791120, + ID: "$123:maunium.net", + RoomID: "!fakeroom:maunium.net", + Content: map[string]interface{}{ + "msgtype": "m.text", + "body": "test", + }, + } + + displaynameTestRoom = newFakeRoom(4) + displaynamePushCondition = &pushrules.PushCondition{ + Kind: pushrules.KindContainsDisplayName, + } +} + +func newFakeEvent(evtType string, content map[string]interface{}) *gomatrix.Event { + return &gomatrix.Event{ + Sender: "@tulir:maunium.net", + Type: evtType, + Timestamp: 1523791120, + ID: "$123:maunium.net", + RoomID: "!fakeroom:maunium.net", + Content: content, + } +} + +func newCountPushCondition(condition string) *pushrules.PushCondition { + return &pushrules.PushCondition{ + Kind: pushrules.KindRoomMemberCount, + MemberCountCondition: condition, + } +} + +func newMatchPushCondition(key, pattern string) *pushrules.PushCondition { + return &pushrules.PushCondition{ + Kind: pushrules.KindEventMatch, + Key: key, + Pattern: pattern, + } +} + +func TestPushCondition_Match_InvalidKind(t *testing.T) { + condition := &pushrules.PushCondition{ + Kind: pushrules.PushCondKind("invalid"), + } + event := newFakeEvent("m.room.foobar", map[string]interface{}{}) + assert.False(t, condition.Match(blankTestRoom, event)) +} + +type FakeRoom struct { + members map[string]*rooms.Member + owner string +} + +func newFakeRoom(memberCount int) *FakeRoom { + room := &FakeRoom{ + owner: "@tulir:maunium.net", + members: make(map[string]*rooms.Member), + } + + if memberCount >= 1 { + room.members["@tulir:maunium.net"] = &rooms.Member{ + UserID: "@tulir:maunium.net", + Membership: rooms.MembershipJoin, + DisplayName: "tulir", + } + } + + for i := 0; i < memberCount-1; i++ { + mxid := fmt.Sprintf("@extrauser_%d:matrix.org", i) + room.members[mxid] = &rooms.Member{ + UserID: mxid, + Membership: rooms.MembershipJoin, + DisplayName: fmt.Sprintf("Extra User %d", i), + } + } + + return room +} + +func (fr *FakeRoom) GetMember(mxid string) *rooms.Member { + return fr.members[mxid] +} + +func (fr *FakeRoom) GetSessionOwner() *rooms.Member { + return fr.members[fr.owner] +} + +func (fr *FakeRoom) GetMembers() map[string]*rooms.Member { + return fr.members +} diff --git a/matrix/pushrules/pushrules_test.go b/matrix/pushrules/pushrules_test.go index 7e0e72e..09698ac 100644 --- a/matrix/pushrules/pushrules_test.go +++ b/matrix/pushrules/pushrules_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -package pushrules +package pushrules_test import ( "encoding/json" @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/assert" "maunium.net/go/gomatrix" + "maunium.net/go/gomuks/matrix/pushrules" ) var mapExamplePushRules map[string]interface{} @@ -37,15 +38,15 @@ func TestEventToPushRules(t *testing.T) { Timestamp: 1523380910, Content: mapExamplePushRules, } - pushRuleset, err := EventToPushRules(event) + pushRuleset, err := pushrules.EventToPushRules(event) assert.Nil(t, err) assert.NotNil(t, pushRuleset) - assert.IsType(t, pushRuleset.Override, PushRuleArray{}) - assert.IsType(t, pushRuleset.Content, PushRuleArray{}) - assert.IsType(t, pushRuleset.Room, PushRuleMap{}) - assert.IsType(t, pushRuleset.Sender, PushRuleMap{}) - assert.IsType(t, pushRuleset.Underride, PushRuleArray{}) + assert.IsType(t, pushRuleset.Override, pushrules.PushRuleArray{}) + assert.IsType(t, pushRuleset.Content, pushrules.PushRuleArray{}) + assert.IsType(t, pushRuleset.Room, pushrules.PushRuleMap{}) + assert.IsType(t, pushRuleset.Sender, pushrules.PushRuleMap{}) + assert.IsType(t, pushRuleset.Underride, pushrules.PushRuleArray{}) assert.Len(t, pushRuleset.Override, 2) assert.Len(t, pushRuleset.Content, 1) assert.Empty(t, pushRuleset.Room.Map) diff --git a/matrix/pushrules/rule.go b/matrix/pushrules/rule.go index dd8a4d3..0caa13d 100644 --- a/matrix/pushrules/rule.go +++ b/matrix/pushrules/rule.go @@ -19,11 +19,10 @@ package pushrules import ( "github.com/zyedidia/glob" "maunium.net/go/gomatrix" - "maunium.net/go/gomuks/matrix/rooms" ) type PushRuleCollection interface { - GetActions(room *rooms.Room, event *gomatrix.Event) PushActionArray + GetActions(room Room, event *gomatrix.Event) PushActionArray } type PushRuleArray []*PushRule @@ -35,7 +34,7 @@ func (rules PushRuleArray) setType(typ PushRuleType) PushRuleArray { return rules } -func (rules PushRuleArray) GetActions(room *rooms.Room, event *gomatrix.Event) PushActionArray { +func (rules PushRuleArray) GetActions(room Room, event *gomatrix.Event) PushActionArray { for _, rule := range rules { if !rule.Match(room, event) { continue @@ -62,7 +61,7 @@ func (rules PushRuleArray) setTypeAndMap(typ PushRuleType) PushRuleMap { return data } -func (ruleMap PushRuleMap) GetActions(room *rooms.Room, event *gomatrix.Event) PushActionArray { +func (ruleMap PushRuleMap) GetActions(room Room, event *gomatrix.Event) PushActionArray { var rule *PushRule var found bool switch ruleMap.Type { @@ -117,7 +116,7 @@ type PushRule struct { Pattern string `json:"pattern,omitempty"` } -func (rule *PushRule) Match(room *rooms.Room, event *gomatrix.Event) bool { +func (rule *PushRule) Match(room Room, event *gomatrix.Event) bool { if !rule.Enabled { return false } @@ -135,7 +134,7 @@ func (rule *PushRule) Match(room *rooms.Room, event *gomatrix.Event) bool { } } -func (rule *PushRule) matchConditions(room *rooms.Room, event *gomatrix.Event) bool { +func (rule *PushRule) matchConditions(room Room, event *gomatrix.Event) bool { for _, cond := range rule.Conditions { if !cond.Match(room, event) { return false @@ -144,7 +143,7 @@ func (rule *PushRule) matchConditions(room *rooms.Room, event *gomatrix.Event) b return true } -func (rule *PushRule) matchPattern(room *rooms.Room, event *gomatrix.Event) bool { +func (rule *PushRule) matchPattern(room Room, event *gomatrix.Event) bool { pattern, err := glob.Compile(rule.Pattern) if err != nil { return false diff --git a/matrix/pushrules/ruleset.go b/matrix/pushrules/ruleset.go index f3026c8..8402dc9 100644 --- a/matrix/pushrules/ruleset.go +++ b/matrix/pushrules/ruleset.go @@ -20,7 +20,6 @@ import ( "encoding/json" "maunium.net/go/gomatrix" - "maunium.net/go/gomuks/matrix/rooms" ) type PushRuleset struct { @@ -81,7 +80,7 @@ var DefaultPushActions = make(PushActionArray, 0) // GetActions matches the given event against all of the push rule // collections in this push ruleset in the order of priority as // specified in spec section 11.12.1.4. -func (rs *PushRuleset) GetActions(room *rooms.Room, event *gomatrix.Event) (match PushActionArray) { +func (rs *PushRuleset) GetActions(room Room, event *gomatrix.Event) (match PushActionArray) { // Add push rule collections to array in priority order arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride} // Loop until one of the push rule collections matches the room/event combo.