/*
* Famedly Matrix SDK
* Copyright (C) 2019, 2020 Famedly GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
import 'dart:convert';
import 'package:test/test.dart';
import 'package:matrix/matrix.dart';
import 'fake_client.dart';
void _testMatch(PushRuleSet ruleset, Event event) {
final evaluator = PushruleEvaluator.fromRuleset(ruleset);
final actions = evaluator.match(event);
expect(actions.notify, true);
expect(actions.highlight, true);
expect(actions.sound, 'goose.wav');
}
void _testNotMatch(PushRuleSet ruleset, Event event) {
final evaluator = PushruleEvaluator.fromRuleset(ruleset);
final actions = evaluator.match(event);
expect(actions.notify, false);
expect(actions.highlight, false);
expect(actions.sound, null);
}
void main() {
/// All Tests related to the Event
group('Event', () {
Logs().level = Level.error;
final timestamp = DateTime.now().millisecondsSinceEpoch;
final id = '!4fsdfjisjf:server.abc';
final senderID = '@alice:server.abc';
final type = 'm.room.message';
final msgtype = 'm.text';
final body = 'Hello fox';
final formatted_body = 'Hello fox';
final contentJson =
'{"msgtype":"$msgtype","body":"$body","formatted_body":"$formatted_body","m.relates_to":{"m.in_reply_to":{"event_id":"\$1234:example.com"}}}';
final jsonObj = {
'event_id': id,
'sender': senderID,
'origin_server_ts': timestamp,
'type': type,
'room_id': '!testroom:example.abc',
'status': EventStatus.synced.intValue,
'content': json.decode(contentJson),
};
late Client client;
late Room room;
setUpAll(() async {
client = await getClient();
room = Room(id: '!testroom:example.abc', client: client);
});
test('event_match rule', () async {
final event = Event.fromJson(jsonObj, room);
final override_ruleset = PushRuleSet(
override: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
conditions: [
PushCondition(
kind: 'event_match',
pattern: 'fox',
key: 'content.body',
),
],
),
],
);
final underride_ruleset = PushRuleSet(
underride: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
conditions: [
PushCondition(
kind: 'event_match',
pattern: 'fox',
key: 'content.body',
),
],
),
],
);
final content_ruleset = PushRuleSet(
content: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
pattern: 'fox',
),
],
);
final room_ruleset = PushRuleSet(
room: [
PushRule(
ruleId: room.id,
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
),
],
);
final sender_ruleset = PushRuleSet(
sender: [
PushRule(
ruleId: senderID,
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
),
],
);
_testMatch(override_ruleset, event);
_testMatch(underride_ruleset, event);
_testMatch(content_ruleset, event);
_testMatch(room_ruleset, event);
_testMatch(sender_ruleset, event);
event.content['body'] = 'FoX';
_testMatch(override_ruleset, event);
_testMatch(underride_ruleset, event);
_testMatch(content_ruleset, event);
_testMatch(room_ruleset, event);
_testMatch(sender_ruleset, event);
event.content['body'] = '@FoX:';
_testMatch(override_ruleset, event);
_testMatch(underride_ruleset, event);
_testMatch(content_ruleset, event);
_testMatch(room_ruleset, event);
_testMatch(sender_ruleset, event);
event.content['body'] = 'äFoXü';
_testMatch(override_ruleset, event);
_testMatch(underride_ruleset, event);
_testMatch(content_ruleset, event);
_testMatch(room_ruleset, event);
_testMatch(sender_ruleset, event);
event.content['body'] = 'äFoXu';
_testNotMatch(override_ruleset, event);
_testNotMatch(underride_ruleset, event);
_testNotMatch(content_ruleset, event);
_testMatch(room_ruleset, event);
_testMatch(sender_ruleset, event);
event.content['body'] = 'aFoXü';
_testNotMatch(override_ruleset, event);
_testNotMatch(underride_ruleset, event);
_testNotMatch(content_ruleset, event);
_testMatch(room_ruleset, event);
_testMatch(sender_ruleset, event);
final override_ruleset2 = PushRuleSet(
override: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
conditions: [
PushCondition(
kind: 'event_match',
pattern: senderID,
key: 'sender',
),
],
),
],
);
_testMatch(override_ruleset2, event);
event.senderId = '@nope:server.tld';
_testNotMatch(override_ruleset2, event);
event.senderId = '${senderID}a';
_testNotMatch(override_ruleset2, event);
event.senderId = 'a$senderID';
_testNotMatch(override_ruleset2, event);
event.senderId = senderID;
_testMatch(override_ruleset2, event);
override_ruleset2.override?[0].enabled = false;
_testNotMatch(override_ruleset2, event);
});
test('event_property_is rule', () async {
final event = Event.fromJson(jsonObj, room);
event.content['body'] = 'Hello fox';
final ruleset = PushRuleSet(
override: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
conditions: [
PushCondition(
kind: 'event_property_is',
key: 'content.body',
value: 'Hello fox',
),
],
),
],
);
_testMatch(ruleset, event);
event.content['body'] = 'Hello Fox';
_testNotMatch(ruleset, event);
event.content['body'] = null;
ruleset.override?[0].conditions?[0].value = null;
_testMatch(ruleset, event);
event.content['body'] = true;
_testNotMatch(ruleset, event);
ruleset.override?[0].conditions?[0].value = true;
_testMatch(ruleset, event);
event.content['body'] = 12345;
_testNotMatch(ruleset, event);
ruleset.override?[0].conditions?[0].value = 12345;
_testMatch(ruleset, event);
});
test('event_property_contains rule', () async {
final event = Event.fromJson(jsonObj, room);
final ruleset = PushRuleSet(
override: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
conditions: [
PushCondition(
kind: 'event_property_contains',
key: 'content.body',
value: 'Fox',
),
],
),
],
);
_testNotMatch(ruleset, event);
event.content['body'] = [];
_testNotMatch(ruleset, event);
event.content['body'] = null;
_testNotMatch(ruleset, event);
event.content['body'] = ['Fox'];
_testMatch(ruleset, event);
ruleset.override?[0].conditions?[0].value = true;
_testNotMatch(ruleset, event);
event.content['body'] = [12345, true];
_testMatch(ruleset, event);
ruleset.override?[0].conditions?[0].value = 12345;
_testMatch(ruleset, event);
});
test('match_display_name rule', () async {
final event = Event.fromJson(jsonObj, room);
(event.room.states[EventTypes.RoomMember] ??= {})[client.userID!] =
Event.fromJson(
{
'type': EventTypes.RoomMember,
'sender': senderID,
'state_key': 'client.senderID',
'content': {'displayname': 'Nico', 'membership': 'join'},
'room_id': room.id,
'origin_server_ts': 5,
},
room,
);
final ruleset = PushRuleSet(
override: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
conditions: [
PushCondition(kind: 'contains_display_name'),
],
),
],
);
event.content['body'] = 'äNicoü';
_testMatch(ruleset, event);
event.content['body'] = 'äNicou';
_testNotMatch(ruleset, event);
});
test('member_count rule', () async {
final event = Event.fromJson(jsonObj, room);
(event.room.states[EventTypes.RoomMember] ??= {})[client.userID!] =
Event.fromJson(
{
'type': EventTypes.RoomMember,
'sender': senderID,
'state_key': 'client.senderID',
'content': {'displayname': 'Nico', 'membership': 'join'},
'room_id': room.id,
'origin_server_ts': 5,
},
room,
);
final ruleset = PushRuleSet(
override: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
conditions: [
PushCondition(kind: 'room_member_count', is$: '<5'),
],
),
],
);
event.content['body'] = 'äNicoü';
_testMatch(ruleset, event);
ruleset.override?[0].conditions?[0].is$ = '<=0';
_testNotMatch(ruleset, event);
ruleset.override?[0].conditions?[0].is$ = '<=1';
_testMatch(ruleset, event);
ruleset.override?[0].conditions?[0].is$ = '>=1';
_testMatch(ruleset, event);
ruleset.override?[0].conditions?[0].is$ = '>1';
_testNotMatch(ruleset, event);
ruleset.override?[0].conditions?[0].is$ = '==1';
_testMatch(ruleset, event);
ruleset.override?[0].conditions?[0].is$ = '1';
_testMatch(ruleset, event);
});
test('notification permissions rule', () async {
final event = Event.fromJson(jsonObj, room);
(event.room.states[EventTypes.RoomPowerLevels] ??= {})[''] =
Event.fromJson(
{
'type': EventTypes.RoomMember,
'sender': senderID,
'state_key': 'client.senderID',
'content': {
'notifications': {'broom': 20},
'users': {senderID: 20},
},
'room_id': room.id,
'origin_server_ts': 5,
},
room,
);
final ruleset = PushRuleSet(
override: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
conditions: [
PushCondition(
kind: 'sender_notification_permission',
key: 'broom',
),
],
),
],
);
_testMatch(ruleset, event);
event.senderId = '@a:b.c';
_testNotMatch(ruleset, event);
});
test('invalid push condition', () async {
final invalid_ruleset = PushRuleSet(
override: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
conditions: [
PushCondition(
kind: 'invalidcondition',
pattern: 'fox',
key: 'content.body',
),
],
),
],
);
expect(
() => PushruleEvaluator.fromRuleset(invalid_ruleset),
returnsNormally,
);
final event = Event.fromJson(jsonObj, room);
_testNotMatch(invalid_ruleset, event);
});
test('invalid content rule', () async {
final invalid_content_ruleset = PushRuleSet(
content: [
PushRule(
ruleId: 'my.rule',
default$: false,
enabled: true,
actions: [
'notify',
{'set_tweak': 'highlight', 'value': true},
{'set_tweak': 'sound', 'value': 'goose.wav'},
],
// pattern: 'fox', <- no pattern!
),
],
);
expect(
() => PushruleEvaluator.fromRuleset(invalid_content_ruleset),
returnsNormally,
);
final dendriteRuleset = PushRuleSet.fromJson(
json.decode('''{
"global": {
"override": [
{
"rule_id": ".m.rule.master",
"default": true,
"enabled": false,
"actions": [
"dont_notify"
],
"conditions": [],
"pattern": ""
},
{
"rule_id": ".m.rule.suppress_notices",
"default": true,
"enabled": true,
"actions": [
"dont_notify"
],
"conditions": [
{
"kind": "event_match",
"key": "content.msgtype",
"pattern": "m.notice"
}
],
"pattern": ""
},
{
"rule_id": ".m.rule.invite_for_me",
"default": true,
"enabled": true,
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak": "highlight",
"value": false
}
],
"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": "@deepbluev7:dendrite.matrix.org"
}
],
"pattern": ""
},
{
"rule_id": ".m.rule.member_event",
"default": true,
"enabled": true,
"actions": [
"dont_notify"
],
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.member"
}
],
"pattern": ""
},
{
"rule_id": ".m.rule.contains_display_name",
"default": true,
"enabled": true,
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak": "highlight",
"value": true
}
],
"conditions": [
{
"kind": "contains_display_name"
}
],
"pattern": ""
},
{
"rule_id": ".m.rule.tombstone",
"default": true,
"enabled": true,
"actions": [
"notify",
{
"set_tweak": "highlight",
"value": false
}
],
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.tombstone"
},
{
"kind": "event_match",
"key": "state_key"
}
],
"pattern": ""
},
{
"rule_id": ".m.rule.roomnotif",
"default": true,
"enabled": true,
"actions": [
"notify",
{
"set_tweak": "highlight",
"value": false
}
],
"conditions": [
{
"kind": "event_match",
"key": "content.body",
"pattern": "@room"
},
{
"kind": "sender_notification_permission",
"key": "room"
}
],
"pattern": ""
}
],
"content": [
{
"rule_id": ".m.rule.contains_user_name",
"default": true,
"enabled": true,
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "default"
},
{
"set_tweak": "highlight",
"value": true
}
],
"conditions": null,
"pattern": "deepbluev7"
}
],
"underride": [
{
"rule_id": ".m.rule.call",
"default": true,
"enabled": true,
"actions": [
"notify",
{
"set_tweak": "sound",
"value": "ring"
},
{
"set_tweak": "highlight",
"value": false
}
],
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.call.invite"
}
],
"pattern": ""
},
{
"rule_id": ".m.rule.encrypted_room_one_to_one",
"default": true,
"enabled": true,
"actions": [
"notify",
{
"set_tweak": "highlight",
"value": false
}
],
"conditions": [
{
"kind": "room_member_count",
"is": "2"
},
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.encrypted"
}
],
"pattern": ""
},
{
"rule_id": ".m.rule.room_one_to_one",
"default": true,
"enabled": true,
"actions": [
"notify",
{
"set_tweak": "highlight",
"value": false
}
],
"conditions": [
{
"kind": "room_member_count",
"is": "2"
},
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.message"
}
],
"pattern": ""
},
{
"rule_id": ".m.rule.message",
"default": true,
"enabled": true,
"actions": [
"notify"
],
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.message"
}
],
"pattern": ""
},
{
"rule_id": ".m.rule.encrypted",
"default": true,
"enabled": true,
"actions": [
"notify"
],
"conditions": [
{
"kind": "event_match",
"key": "type",
"pattern": "m.room.encrypted"
}
],
"pattern": ""
}
]
}
}
'''),
);
expect(
() => PushruleEvaluator.fromRuleset(dendriteRuleset),
returnsNormally,
);
});
});
}