Add tests for pushrule conditions and fix bugs found when making tests

This commit is contained in:
Tulir Asokan
2018-04-15 15:36:01 +03:00
parent bb9ed4558b
commit 0cdde557a3
10 changed files with 1072 additions and 46 deletions

View File

@ -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
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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))
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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))
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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))
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
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)

View File

@ -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

View File

@ -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.