Pushrule tweaks, make pattern
non-optional on EventMatchCondition
(#2918)
This should fix https://github.com/matrix-org/dendrite/issues/2882 (Tested with FluffyChat 1.7.1) Also adds tests that the predefined push rules (as per the spec) is what we have in Dendrite.
This commit is contained in:
parent
5eed31fea3
commit
f47515e38b
|
@ -14,7 +14,7 @@ type Condition struct {
|
||||||
|
|
||||||
// Pattern indicates the value pattern that must match. Required
|
// Pattern indicates the value pattern that must match. Required
|
||||||
// for EventMatchCondition.
|
// for EventMatchCondition.
|
||||||
Pattern string `json:"pattern,omitempty"`
|
Pattern *string `json:"pattern,omitempty"`
|
||||||
|
|
||||||
// Is indicates the condition that must be fulfilled. Required for
|
// Is indicates the condition that must be fulfilled. Required for
|
||||||
// RoomMemberCountCondition.
|
// RoomMemberCountCondition.
|
||||||
|
|
|
@ -15,13 +15,7 @@ func mRuleContainsUserNameDefinition(localpart string) *Rule {
|
||||||
RuleID: MRuleContainsUserName,
|
RuleID: MRuleContainsUserName,
|
||||||
Default: true,
|
Default: true,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Pattern: localpart,
|
Pattern: &localpart,
|
||||||
Conditions: []*Condition{
|
|
||||||
{
|
|
||||||
Kind: EventMatchCondition,
|
|
||||||
Key: "content.body",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Actions: []*Action{
|
Actions: []*Action{
|
||||||
{Kind: NotifyAction},
|
{Kind: NotifyAction},
|
||||||
{
|
{
|
||||||
|
@ -32,7 +26,6 @@ func mRuleContainsUserNameDefinition(localpart string) *Rule {
|
||||||
{
|
{
|
||||||
Kind: SetTweakAction,
|
Kind: SetTweakAction,
|
||||||
Tweak: HighlightTweak,
|
Tweak: HighlightTweak,
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,15 +22,15 @@ const (
|
||||||
MRuleTombstone = ".m.rule.tombstone"
|
MRuleTombstone = ".m.rule.tombstone"
|
||||||
MRuleRoomNotif = ".m.rule.roomnotif"
|
MRuleRoomNotif = ".m.rule.roomnotif"
|
||||||
MRuleReaction = ".m.rule.reaction"
|
MRuleReaction = ".m.rule.reaction"
|
||||||
|
MRuleRoomACLs = ".m.rule.room.server_acl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mRuleMasterDefinition = Rule{
|
mRuleMasterDefinition = Rule{
|
||||||
RuleID: MRuleMaster,
|
RuleID: MRuleMaster,
|
||||||
Default: true,
|
Default: true,
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Conditions: []*Condition{},
|
Actions: []*Action{{Kind: DontNotifyAction}},
|
||||||
Actions: []*Action{{Kind: DontNotifyAction}},
|
|
||||||
}
|
}
|
||||||
mRuleSuppressNoticesDefinition = Rule{
|
mRuleSuppressNoticesDefinition = Rule{
|
||||||
RuleID: MRuleSuppressNotices,
|
RuleID: MRuleSuppressNotices,
|
||||||
|
@ -40,7 +40,7 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "content.msgtype",
|
Key: "content.msgtype",
|
||||||
Pattern: "m.notice",
|
Pattern: pointer("m.notice"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{{Kind: DontNotifyAction}},
|
Actions: []*Action{{Kind: DontNotifyAction}},
|
||||||
|
@ -53,7 +53,7 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "type",
|
Key: "type",
|
||||||
Pattern: "m.room.member",
|
Pattern: pointer("m.room.member"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{{Kind: DontNotifyAction}},
|
Actions: []*Action{{Kind: DontNotifyAction}},
|
||||||
|
@ -73,7 +73,6 @@ var (
|
||||||
{
|
{
|
||||||
Kind: SetTweakAction,
|
Kind: SetTweakAction,
|
||||||
Tweak: HighlightTweak,
|
Tweak: HighlightTweak,
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -85,12 +84,12 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "type",
|
Key: "type",
|
||||||
Pattern: "m.room.tombstone",
|
Pattern: pointer("m.room.tombstone"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "state_key",
|
Key: "state_key",
|
||||||
Pattern: "",
|
Pattern: pointer(""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{
|
Actions: []*Action{
|
||||||
|
@ -98,10 +97,27 @@ var (
|
||||||
{
|
{
|
||||||
Kind: SetTweakAction,
|
Kind: SetTweakAction,
|
||||||
Tweak: HighlightTweak,
|
Tweak: HighlightTweak,
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
mRuleACLsDefinition = Rule{
|
||||||
|
RuleID: MRuleRoomACLs,
|
||||||
|
Default: true,
|
||||||
|
Enabled: true,
|
||||||
|
Conditions: []*Condition{
|
||||||
|
{
|
||||||
|
Kind: EventMatchCondition,
|
||||||
|
Key: "type",
|
||||||
|
Pattern: pointer("m.room.server_acl"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Kind: EventMatchCondition,
|
||||||
|
Key: "state_key",
|
||||||
|
Pattern: pointer(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Actions: []*Action{},
|
||||||
|
}
|
||||||
mRuleRoomNotifDefinition = Rule{
|
mRuleRoomNotifDefinition = Rule{
|
||||||
RuleID: MRuleRoomNotif,
|
RuleID: MRuleRoomNotif,
|
||||||
Default: true,
|
Default: true,
|
||||||
|
@ -110,7 +126,7 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "content.body",
|
Key: "content.body",
|
||||||
Pattern: "@room",
|
Pattern: pointer("@room"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Kind: SenderNotificationPermissionCondition,
|
Kind: SenderNotificationPermissionCondition,
|
||||||
|
@ -122,7 +138,6 @@ var (
|
||||||
{
|
{
|
||||||
Kind: SetTweakAction,
|
Kind: SetTweakAction,
|
||||||
Tweak: HighlightTweak,
|
Tweak: HighlightTweak,
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -134,7 +149,7 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "type",
|
Key: "type",
|
||||||
Pattern: "m.reaction",
|
Pattern: pointer("m.reaction"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{
|
Actions: []*Action{
|
||||||
|
@ -152,17 +167,17 @@ func mRuleInviteForMeDefinition(userID string) *Rule {
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "type",
|
Key: "type",
|
||||||
Pattern: "m.room.member",
|
Pattern: pointer("m.room.member"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "content.membership",
|
Key: "content.membership",
|
||||||
Pattern: "invite",
|
Pattern: pointer("invite"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "state_key",
|
Key: "state_key",
|
||||||
Pattern: userID,
|
Pattern: pointer(userID),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{
|
Actions: []*Action{
|
||||||
|
@ -172,11 +187,6 @@ func mRuleInviteForMeDefinition(userID string) *Rule {
|
||||||
Tweak: SoundTweak,
|
Tweak: SoundTweak,
|
||||||
Value: "default",
|
Value: "default",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Kind: SetTweakAction,
|
|
||||||
Tweak: HighlightTweak,
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
111
internal/pushrules/default_pushrules_test.go
Normal file
111
internal/pushrules/default_pushrules_test.go
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
package pushrules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that the pre-defined rules as of
|
||||||
|
// https://spec.matrix.org/v1.4/client-server-api/#predefined-rules
|
||||||
|
// are correct
|
||||||
|
func TestDefaultRules(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
inputBytes []byte
|
||||||
|
want Rule
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []testCase{
|
||||||
|
// Default override rules
|
||||||
|
{
|
||||||
|
name: ".m.rule.master",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.master","default":true,"enabled":false,"actions":["dont_notify"]}`),
|
||||||
|
want: mRuleMasterDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.suppress_notices",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.suppress_notices","default":true,"enabled":true,"conditions":[{"kind":"event_match","key":"content.msgtype","pattern":"m.notice"}],"actions":["dont_notify"]}`),
|
||||||
|
want: mRuleSuppressNoticesDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.invite_for_me",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.invite_for_me","default":true,"enabled":true,"conditions":[{"kind":"event_match","key":"type","pattern":"m.room.member"},{"kind":"event_match","key":"content.membership","pattern":"invite"},{"kind":"event_match","key":"state_key","pattern":"@test:localhost"}],"actions":["notify",{"set_tweak":"sound","value":"default"}]}`),
|
||||||
|
want: *mRuleInviteForMeDefinition("@test:localhost"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.member_event",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.member_event","default":true,"enabled":true,"conditions":[{"kind":"event_match","key":"type","pattern":"m.room.member"}],"actions":["dont_notify"]}`),
|
||||||
|
want: mRuleMemberEventDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.contains_display_name",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.contains_display_name","default":true,"enabled":true,"conditions":[{"kind":"contains_display_name"}],"actions":["notify",{"set_tweak":"sound","value":"default"},{"set_tweak":"highlight"}]}`),
|
||||||
|
want: mRuleContainsDisplayNameDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.tombstone",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.tombstone","default":true,"enabled":true,"conditions":[{"kind":"event_match","key":"type","pattern":"m.room.tombstone"},{"kind":"event_match","key":"state_key","pattern":""}],"actions":["notify",{"set_tweak":"highlight"}]}`),
|
||||||
|
want: mRuleTombstoneDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.room.server_acl",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.room.server_acl","default":true,"enabled":true,"conditions":[{"kind":"event_match","key":"type","pattern":"m.room.server_acl"},{"kind":"event_match","key":"state_key","pattern":""}],"actions":[]}`),
|
||||||
|
want: mRuleACLsDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.roomnotif",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.roomnotif","default":true,"enabled":true,"conditions":[{"kind":"event_match","key":"content.body","pattern":"@room"},{"kind":"sender_notification_permission","key":"room"}],"actions":["notify",{"set_tweak":"highlight"}]}`),
|
||||||
|
want: mRuleRoomNotifDefinition,
|
||||||
|
},
|
||||||
|
// Default content rules
|
||||||
|
{
|
||||||
|
name: ".m.rule.contains_user_name",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.contains_user_name","default":true,"enabled":true,"actions":["notify",{"set_tweak":"sound","value":"default"},{"set_tweak":"highlight"}],"pattern":"myLocalUser"}`),
|
||||||
|
want: *mRuleContainsUserNameDefinition("myLocalUser"),
|
||||||
|
},
|
||||||
|
// default underride rules
|
||||||
|
{
|
||||||
|
name: ".m.rule.call",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.call","default":true,"enabled":true,"conditions":[{"kind":"event_match","key":"type","pattern":"m.call.invite"}],"actions":["notify",{"set_tweak":"sound","value":"ring"}]}`),
|
||||||
|
want: mRuleCallDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.encrypted_room_one_to_one",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.encrypted_room_one_to_one","default":true,"enabled":true,"conditions":[{"kind":"room_member_count","is":"2"},{"kind":"event_match","key":"type","pattern":"m.room.encrypted"}],"actions":["notify",{"set_tweak":"sound","value":"default"}]}`),
|
||||||
|
want: mRuleEncryptedRoomOneToOneDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.room_one_to_one",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.room_one_to_one","default":true,"enabled":true,"conditions":[{"kind":"room_member_count","is":"2"},{"kind":"event_match","key":"type","pattern":"m.room.message"}],"actions":["notify",{"set_tweak":"sound","value":"default"}]}`),
|
||||||
|
want: mRuleRoomOneToOneDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.message",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.message","default":true,"enabled":true,"conditions":[{"kind":"event_match","key":"type","pattern":"m.room.message"}],"actions":["notify"]}`),
|
||||||
|
want: mRuleMessageDefinition,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: ".m.rule.encrypted",
|
||||||
|
inputBytes: []byte(`{"rule_id":".m.rule.encrypted","default":true,"enabled":true,"conditions":[{"kind":"event_match","key":"type","pattern":"m.room.encrypted"}],"actions":["notify"]}`),
|
||||||
|
want: mRuleEncryptedDefinition,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
r := Rule{}
|
||||||
|
// unmarshal predefined push rules
|
||||||
|
err := json.Unmarshal(tc.inputBytes, &r)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.want, r)
|
||||||
|
|
||||||
|
// and reverse it to check we get the expected result
|
||||||
|
got, err := json.Marshal(r)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, string(got), string(tc.inputBytes))
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "type",
|
Key: "type",
|
||||||
Pattern: "m.call.invite",
|
Pattern: pointer("m.call.invite"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{
|
Actions: []*Action{
|
||||||
|
@ -35,11 +35,6 @@ var (
|
||||||
Tweak: SoundTweak,
|
Tweak: SoundTweak,
|
||||||
Value: "ring",
|
Value: "ring",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Kind: SetTweakAction,
|
|
||||||
Tweak: HighlightTweak,
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
mRuleEncryptedRoomOneToOneDefinition = Rule{
|
mRuleEncryptedRoomOneToOneDefinition = Rule{
|
||||||
|
@ -54,7 +49,7 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "type",
|
Key: "type",
|
||||||
Pattern: "m.room.encrypted",
|
Pattern: pointer("m.room.encrypted"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{
|
Actions: []*Action{
|
||||||
|
@ -64,11 +59,6 @@ var (
|
||||||
Tweak: SoundTweak,
|
Tweak: SoundTweak,
|
||||||
Value: "default",
|
Value: "default",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Kind: SetTweakAction,
|
|
||||||
Tweak: HighlightTweak,
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
mRuleRoomOneToOneDefinition = Rule{
|
mRuleRoomOneToOneDefinition = Rule{
|
||||||
|
@ -83,20 +73,15 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "type",
|
Key: "type",
|
||||||
Pattern: "m.room.message",
|
Pattern: pointer("m.room.message"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{
|
Actions: []*Action{
|
||||||
{Kind: NotifyAction},
|
{Kind: NotifyAction},
|
||||||
{
|
{
|
||||||
Kind: SetTweakAction,
|
Kind: SetTweakAction,
|
||||||
Tweak: HighlightTweak,
|
Tweak: SoundTweak,
|
||||||
Value: false,
|
Value: "default",
|
||||||
},
|
|
||||||
{
|
|
||||||
Kind: SetTweakAction,
|
|
||||||
Tweak: HighlightTweak,
|
|
||||||
Value: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -108,16 +93,11 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "type",
|
Key: "type",
|
||||||
Pattern: "m.room.message",
|
Pattern: pointer("m.room.message"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{
|
Actions: []*Action{
|
||||||
{Kind: NotifyAction},
|
{Kind: NotifyAction},
|
||||||
{
|
|
||||||
Kind: SetTweakAction,
|
|
||||||
Tweak: HighlightTweak,
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
mRuleEncryptedDefinition = Rule{
|
mRuleEncryptedDefinition = Rule{
|
||||||
|
@ -128,16 +108,11 @@ var (
|
||||||
{
|
{
|
||||||
Kind: EventMatchCondition,
|
Kind: EventMatchCondition,
|
||||||
Key: "type",
|
Key: "type",
|
||||||
Pattern: "m.room.encrypted",
|
Pattern: pointer("m.room.encrypted"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Actions: []*Action{
|
Actions: []*Action{
|
||||||
{Kind: NotifyAction},
|
{Kind: NotifyAction},
|
||||||
{
|
|
||||||
Kind: SetTweakAction,
|
|
||||||
Tweak: HighlightTweak,
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -104,7 +104,10 @@ func ruleMatches(rule *Rule, kind Kind, event *gomatrixserverlib.Event, ec Evalu
|
||||||
case ContentKind:
|
case ContentKind:
|
||||||
// TODO: "These configure behaviour for (unencrypted) messages
|
// TODO: "These configure behaviour for (unencrypted) messages
|
||||||
// that match certain patterns." - Does that mean "content.body"?
|
// that match certain patterns." - Does that mean "content.body"?
|
||||||
return patternMatches("content.body", rule.Pattern, event)
|
if rule.Pattern == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return patternMatches("content.body", *rule.Pattern, event)
|
||||||
|
|
||||||
case RoomKind:
|
case RoomKind:
|
||||||
return rule.RuleID == event.RoomID(), nil
|
return rule.RuleID == event.RoomID(), nil
|
||||||
|
@ -120,7 +123,10 @@ func ruleMatches(rule *Rule, kind Kind, event *gomatrixserverlib.Event, ec Evalu
|
||||||
func conditionMatches(cond *Condition, event *gomatrixserverlib.Event, ec EvaluationContext) (bool, error) {
|
func conditionMatches(cond *Condition, event *gomatrixserverlib.Event, ec EvaluationContext) (bool, error) {
|
||||||
switch cond.Kind {
|
switch cond.Kind {
|
||||||
case EventMatchCondition:
|
case EventMatchCondition:
|
||||||
return patternMatches(cond.Key, cond.Pattern, event)
|
if cond.Pattern == nil {
|
||||||
|
return false, fmt.Errorf("missing condition pattern")
|
||||||
|
}
|
||||||
|
return patternMatches(cond.Key, *cond.Pattern, event)
|
||||||
|
|
||||||
case ContainsDisplayNameCondition:
|
case ContainsDisplayNameCondition:
|
||||||
return patternMatches("content.body", ec.UserDisplayName(), event)
|
return patternMatches("content.body", ec.UserDisplayName(), event)
|
||||||
|
|
|
@ -79,8 +79,8 @@ func TestRuleMatches(t *testing.T) {
|
||||||
{"underrideConditionMatch", UnderrideKind, Rule{Enabled: true}, `{}`, true},
|
{"underrideConditionMatch", UnderrideKind, Rule{Enabled: true}, `{}`, true},
|
||||||
{"underrideConditionNoMatch", UnderrideKind, Rule{Enabled: true, Conditions: []*Condition{{}}}, `{}`, false},
|
{"underrideConditionNoMatch", UnderrideKind, Rule{Enabled: true, Conditions: []*Condition{{}}}, `{}`, false},
|
||||||
|
|
||||||
{"contentMatch", ContentKind, Rule{Enabled: true, Pattern: "b"}, `{"content":{"body":"abc"}}`, true},
|
{"contentMatch", ContentKind, Rule{Enabled: true, Pattern: pointer("b")}, `{"content":{"body":"abc"}}`, true},
|
||||||
{"contentNoMatch", ContentKind, Rule{Enabled: true, Pattern: "d"}, `{"content":{"body":"abc"}}`, false},
|
{"contentNoMatch", ContentKind, Rule{Enabled: true, Pattern: pointer("d")}, `{"content":{"body":"abc"}}`, false},
|
||||||
|
|
||||||
{"roomMatch", RoomKind, Rule{Enabled: true, RuleID: "!room@example.com"}, `{"room_id":"!room@example.com"}`, true},
|
{"roomMatch", RoomKind, Rule{Enabled: true, RuleID: "!room@example.com"}, `{"room_id":"!room@example.com"}`, true},
|
||||||
{"roomNoMatch", RoomKind, Rule{Enabled: true, RuleID: "!room@example.com"}, `{"room_id":"!otherroom@example.com"}`, false},
|
{"roomNoMatch", RoomKind, Rule{Enabled: true, RuleID: "!room@example.com"}, `{"room_id":"!otherroom@example.com"}`, false},
|
||||||
|
@ -106,41 +106,44 @@ func TestConditionMatches(t *testing.T) {
|
||||||
Name string
|
Name string
|
||||||
Cond Condition
|
Cond Condition
|
||||||
EventJSON string
|
EventJSON string
|
||||||
Want bool
|
WantMatch bool
|
||||||
|
WantErr bool
|
||||||
}{
|
}{
|
||||||
{"empty", Condition{}, `{}`, false},
|
{Name: "empty", Cond: Condition{}, EventJSON: `{}`, WantMatch: false, WantErr: false},
|
||||||
{"empty", Condition{Kind: "unknownstring"}, `{}`, false},
|
{Name: "empty", Cond: Condition{Kind: "unknownstring"}, EventJSON: `{}`, WantMatch: false, WantErr: false},
|
||||||
|
|
||||||
// Neither of these should match because `content` is not a full string match,
|
// Neither of these should match because `content` is not a full string match,
|
||||||
// and `content.body` is not a string value.
|
// and `content.body` is not a string value.
|
||||||
{"eventMatch", Condition{Kind: EventMatchCondition, Key: "content"}, `{"content":{}}`, false},
|
{Name: "eventMatch", Cond: Condition{Kind: EventMatchCondition, Key: "content", Pattern: pointer("")}, EventJSON: `{"content":{}}`, WantMatch: false, WantErr: false},
|
||||||
{"eventBodyMatch", Condition{Kind: EventMatchCondition, Key: "content.body", Is: "3"}, `{"content":{"body": 3}}`, false},
|
{Name: "eventBodyMatch", Cond: Condition{Kind: EventMatchCondition, Key: "content.body", Is: "3", Pattern: pointer("")}, EventJSON: `{"content":{"body": "3"}}`, WantMatch: false, WantErr: false},
|
||||||
|
{Name: "eventBodyMatch matches", Cond: Condition{Kind: EventMatchCondition, Key: "content.body", Pattern: pointer("world")}, EventJSON: `{"content":{"body": "hello world!"}}`, WantMatch: true, WantErr: false},
|
||||||
|
{Name: "EventMatch missing pattern", Cond: Condition{Kind: EventMatchCondition, Key: "content.body"}, EventJSON: `{"content":{"body": "hello world!"}}`, WantMatch: false, WantErr: true},
|
||||||
|
|
||||||
{"displayNameNoMatch", Condition{Kind: ContainsDisplayNameCondition}, `{"content":{"body":"something without displayname"}}`, false},
|
{Name: "displayNameNoMatch", Cond: Condition{Kind: ContainsDisplayNameCondition}, EventJSON: `{"content":{"body":"something without displayname"}}`, WantMatch: false, WantErr: false},
|
||||||
{"displayNameMatch", Condition{Kind: ContainsDisplayNameCondition}, `{"content":{"body":"hello Dear User, how are you?"}}`, true},
|
{Name: "displayNameMatch", Cond: Condition{Kind: ContainsDisplayNameCondition}, EventJSON: `{"content":{"body":"hello Dear User, how are you?"}}`, WantMatch: true, WantErr: false},
|
||||||
|
|
||||||
{"roomMemberCountLessNoMatch", Condition{Kind: RoomMemberCountCondition, Is: "<2"}, `{}`, false},
|
{Name: "roomMemberCountLessNoMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: "<2"}, EventJSON: `{}`, WantMatch: false, WantErr: false},
|
||||||
{"roomMemberCountLessMatch", Condition{Kind: RoomMemberCountCondition, Is: "<3"}, `{}`, true},
|
{Name: "roomMemberCountLessMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: "<3"}, EventJSON: `{}`, WantMatch: true, WantErr: false},
|
||||||
{"roomMemberCountLessEqualNoMatch", Condition{Kind: RoomMemberCountCondition, Is: "<=1"}, `{}`, false},
|
{Name: "roomMemberCountLessEqualNoMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: "<=1"}, EventJSON: `{}`, WantMatch: false, WantErr: false},
|
||||||
{"roomMemberCountLessEqualMatch", Condition{Kind: RoomMemberCountCondition, Is: "<=2"}, `{}`, true},
|
{Name: "roomMemberCountLessEqualMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: "<=2"}, EventJSON: `{}`, WantMatch: true, WantErr: false},
|
||||||
{"roomMemberCountEqualNoMatch", Condition{Kind: RoomMemberCountCondition, Is: "==1"}, `{}`, false},
|
{Name: "roomMemberCountEqualNoMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: "==1"}, EventJSON: `{}`, WantMatch: false, WantErr: false},
|
||||||
{"roomMemberCountEqualMatch", Condition{Kind: RoomMemberCountCondition, Is: "==2"}, `{}`, true},
|
{Name: "roomMemberCountEqualMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: "==2"}, EventJSON: `{}`, WantMatch: true, WantErr: false},
|
||||||
{"roomMemberCountGreaterEqualNoMatch", Condition{Kind: RoomMemberCountCondition, Is: ">=3"}, `{}`, false},
|
{Name: "roomMemberCountGreaterEqualNoMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: ">=3"}, EventJSON: `{}`, WantMatch: false, WantErr: false},
|
||||||
{"roomMemberCountGreaterEqualMatch", Condition{Kind: RoomMemberCountCondition, Is: ">=2"}, `{}`, true},
|
{Name: "roomMemberCountGreaterEqualMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: ">=2"}, EventJSON: `{}`, WantMatch: true, WantErr: false},
|
||||||
{"roomMemberCountGreaterNoMatch", Condition{Kind: RoomMemberCountCondition, Is: ">2"}, `{}`, false},
|
{Name: "roomMemberCountGreaterNoMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: ">2"}, EventJSON: `{}`, WantMatch: false, WantErr: false},
|
||||||
{"roomMemberCountGreaterMatch", Condition{Kind: RoomMemberCountCondition, Is: ">1"}, `{}`, true},
|
{Name: "roomMemberCountGreaterMatch", Cond: Condition{Kind: RoomMemberCountCondition, Is: ">1"}, EventJSON: `{}`, WantMatch: true, WantErr: false},
|
||||||
|
|
||||||
{"senderNotificationPermissionMatch", Condition{Kind: SenderNotificationPermissionCondition, Key: "powerlevel"}, `{"sender":"@poweruser:example.com"}`, true},
|
{Name: "senderNotificationPermissionMatch", Cond: Condition{Kind: SenderNotificationPermissionCondition, Key: "powerlevel"}, EventJSON: `{"sender":"@poweruser:example.com"}`, WantMatch: true, WantErr: false},
|
||||||
{"senderNotificationPermissionNoMatch", Condition{Kind: SenderNotificationPermissionCondition, Key: "powerlevel"}, `{"sender":"@nobody:example.com"}`, false},
|
{Name: "senderNotificationPermissionNoMatch", Cond: Condition{Kind: SenderNotificationPermissionCondition, Key: "powerlevel"}, EventJSON: `{"sender":"@nobody:example.com"}`, WantMatch: false, WantErr: false},
|
||||||
}
|
}
|
||||||
for _, tst := range tsts {
|
for _, tst := range tsts {
|
||||||
t.Run(tst.Name, func(t *testing.T) {
|
t.Run(tst.Name, func(t *testing.T) {
|
||||||
got, err := conditionMatches(&tst.Cond, mustEventFromJSON(t, tst.EventJSON), &fakeEvaluationContext{2})
|
got, err := conditionMatches(&tst.Cond, mustEventFromJSON(t, tst.EventJSON), &fakeEvaluationContext{2})
|
||||||
if err != nil {
|
if err != nil && !tst.WantErr {
|
||||||
t.Fatalf("conditionMatches failed: %v", err)
|
t.Fatalf("conditionMatches failed: %v", err)
|
||||||
}
|
}
|
||||||
if got != tst.Want {
|
if got != tst.WantMatch {
|
||||||
t.Errorf("conditionMatches: got %v, want %v on %s", got, tst.Want, tst.Name)
|
t.Errorf("conditionMatches: got %v, want %v on %s", got, tst.WantMatch, tst.Name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,18 +36,18 @@ type Rule struct {
|
||||||
// around. Required.
|
// around. Required.
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
|
|
||||||
|
// Conditions provide the rule's conditions for OverrideKind and
|
||||||
|
// UnderrideKind. Not allowed for other kinds.
|
||||||
|
Conditions []*Condition `json:"conditions,omitempty"`
|
||||||
|
|
||||||
// Actions describe the desired outcome, should the rule
|
// Actions describe the desired outcome, should the rule
|
||||||
// match. Required.
|
// match. Required.
|
||||||
Actions []*Action `json:"actions"`
|
Actions []*Action `json:"actions"`
|
||||||
|
|
||||||
// Conditions provide the rule's conditions for OverrideKind and
|
|
||||||
// UnderrideKind. Not allowed for other kinds.
|
|
||||||
Conditions []*Condition `json:"conditions"`
|
|
||||||
|
|
||||||
// Pattern is the body pattern to match for ContentKind. Required
|
// Pattern is the body pattern to match for ContentKind. Required
|
||||||
// for that kind. The interpretation is the same as that of
|
// for that kind. The interpretation is the same as that of
|
||||||
// Condition.Pattern.
|
// Condition.Pattern.
|
||||||
Pattern string `json:"pattern"`
|
Pattern *string `json:"pattern,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scope only has one valid value. See also AccountRuleSets.
|
// Scope only has one valid value. See also AccountRuleSets.
|
||||||
|
|
|
@ -128,3 +128,7 @@ func parseRoomMemberCountCondition(s string) (func(int) bool, error) {
|
||||||
b = int(v)
|
b = int(v)
|
||||||
return cmp, nil
|
return cmp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pointer[t any](s t) *t {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,10 @@ func ValidateRule(kind Kind, rule *Rule) []error {
|
||||||
}
|
}
|
||||||
|
|
||||||
case ContentKind:
|
case ContentKind:
|
||||||
if rule.Pattern == "" {
|
if rule.Pattern == nil {
|
||||||
|
errs = append(errs, fmt.Errorf("missing content rule pattern"))
|
||||||
|
}
|
||||||
|
if rule.Pattern != nil && *rule.Pattern == "" {
|
||||||
errs = append(errs, fmt.Errorf("missing content rule pattern"))
|
errs = append(errs, fmt.Errorf("missing content rule pattern"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,15 +12,16 @@ func TestValidateRuleNegatives(t *testing.T) {
|
||||||
Rule Rule
|
Rule Rule
|
||||||
WantErrString string
|
WantErrString string
|
||||||
}{
|
}{
|
||||||
{"emptyRuleID", OverrideKind, Rule{}, "invalid rule ID"},
|
{Name: "emptyRuleID", Kind: OverrideKind, Rule: Rule{}, WantErrString: "invalid rule ID"},
|
||||||
{"invalidKind", Kind("something else"), Rule{}, "invalid rule kind"},
|
{Name: "invalidKind", Kind: Kind("something else"), Rule: Rule{}, WantErrString: "invalid rule kind"},
|
||||||
{"ruleIDBackslash", OverrideKind, Rule{RuleID: "#foo\\:example.com"}, "invalid rule ID"},
|
{Name: "ruleIDBackslash", Kind: OverrideKind, Rule: Rule{RuleID: "#foo\\:example.com"}, WantErrString: "invalid rule ID"},
|
||||||
{"noActions", OverrideKind, Rule{}, "missing actions"},
|
{Name: "noActions", Kind: OverrideKind, Rule: Rule{}, WantErrString: "missing actions"},
|
||||||
{"invalidAction", OverrideKind, Rule{Actions: []*Action{{}}}, "invalid rule action kind"},
|
{Name: "invalidAction", Kind: OverrideKind, Rule: Rule{Actions: []*Action{{}}}, WantErrString: "invalid rule action kind"},
|
||||||
{"invalidCondition", OverrideKind, Rule{Conditions: []*Condition{{}}}, "invalid rule condition kind"},
|
{Name: "invalidCondition", Kind: OverrideKind, Rule: Rule{Conditions: []*Condition{{}}}, WantErrString: "invalid rule condition kind"},
|
||||||
{"overrideNoCondition", OverrideKind, Rule{}, "missing rule conditions"},
|
{Name: "overrideNoCondition", Kind: OverrideKind, Rule: Rule{}, WantErrString: "missing rule conditions"},
|
||||||
{"underrideNoCondition", UnderrideKind, Rule{}, "missing rule conditions"},
|
{Name: "underrideNoCondition", Kind: UnderrideKind, Rule: Rule{}, WantErrString: "missing rule conditions"},
|
||||||
{"contentNoPattern", ContentKind, Rule{}, "missing content rule pattern"},
|
{Name: "contentNoPattern", Kind: ContentKind, Rule: Rule{}, WantErrString: "missing content rule pattern"},
|
||||||
|
{Name: "contentEmptyPattern", Kind: ContentKind, Rule: Rule{Pattern: pointer("")}, WantErrString: "missing content rule pattern"},
|
||||||
}
|
}
|
||||||
for _, tst := range tsts {
|
for _, tst := range tsts {
|
||||||
t.Run(tst.Name, func(t *testing.T) {
|
t.Run(tst.Name, func(t *testing.T) {
|
||||||
|
|
|
@ -81,11 +81,6 @@ func Test_evaluatePushRules(t *testing.T) {
|
||||||
wantAction: pushrules.NotifyAction,
|
wantAction: pushrules.NotifyAction,
|
||||||
wantActions: []*pushrules.Action{
|
wantActions: []*pushrules.Action{
|
||||||
{Kind: pushrules.NotifyAction},
|
{Kind: pushrules.NotifyAction},
|
||||||
{
|
|
||||||
Kind: pushrules.SetTweakAction,
|
|
||||||
Tweak: pushrules.HighlightTweak,
|
|
||||||
Value: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -103,7 +98,6 @@ func Test_evaluatePushRules(t *testing.T) {
|
||||||
{
|
{
|
||||||
Kind: pushrules.SetTweakAction,
|
Kind: pushrules.SetTweakAction,
|
||||||
Tweak: pushrules.HighlightTweak,
|
Tweak: pushrules.HighlightTweak,
|
||||||
Value: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue