Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
M
mtxclient
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Nheko Reborn
mtxclient
Commits
453a7f37
Verified
Commit
453a7f37
authored
1 year ago
by
Nicolas Werner
Browse files
Options
Downloads
Patches
Plain Diff
Support event_property_is and event_property_contains rules
parent
d558041f
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Pipeline
#5623
failed
1 year ago
Stage: build
Stage: deploy
Changes
3
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
include/mtx/pushrules.hpp
+3
-0
3 additions, 0 deletions
include/mtx/pushrules.hpp
lib/structs/pushrules.cpp
+165
-23
165 additions, 23 deletions
lib/structs/pushrules.cpp
tests/pushrules.cpp
+126
-0
126 additions, 0 deletions
tests/pushrules.cpp
with
294 additions
and
23 deletions
include/mtx/pushrules.hpp
+
3
−
0
View file @
453a7f37
...
...
@@ -42,6 +42,9 @@ struct PushCondition
//! with no special glob characters should be treated as having asterisks prepended and
//! appended when testing the condition.
std
::
string
pattern
;
//! Required for event_property_is and event_property_contains conditions. A non compound json
//! value.
std
::
optional
<
std
::
variant
<
std
::
string
,
std
::
int64_t
,
bool
,
std
::
nullptr_t
>>
value
;
//! Required for room_member_count conditions. A decimal integer optionally prefixed by one
//! of, ==, <, >, >= or <=. A prefix of < matches rooms where the member count is strictly
//! less than the given number and so forth. If no prefix is present, this parameter
...
...
This diff is collapsed.
Click to expand it.
lib/structs/pushrules.cpp
+
165
−
23
View file @
453a7f37
...
...
@@ -8,15 +8,41 @@
#include
"mtx/events/collections.hpp"
#include
"mtx/log.hpp"
using
non_compound_json_value
=
std
::
variant
<
std
::
string
,
std
::
int64_t
,
bool
,
std
::
nullptr_t
>
;
using
push_json_value
=
std
::
variant
<
std
::
string
,
std
::
int64_t
,
bool
,
std
::
nullptr_t
,
std
::
vector
<
non_compound_json_value
>>
;
namespace
{
struct
RelatedEvents
{
std
::
vector
<
std
::
unordered_map
<
std
::
string
,
std
::
string
>>
fallbacks
;
//!< fallback related events
std
::
vector
<
std
::
unordered_map
<
std
::
string
,
std
::
string
>>
events
;
//!< related events
std
::
vector
<
std
::
unordered_map
<
std
::
string
,
push_json_value
>>
fallbacks
;
//!< fallback related events
std
::
vector
<
std
::
unordered_map
<
std
::
string
,
push_json_value
>>
events
;
//!< related events
};
}
static
std
::
optional
<
non_compound_json_value
>
to_non_compound_json_value
(
const
nlohmann
::
json
&
val
)
{
switch
(
val
.
type
())
{
case
nlohmann
::
json
::
value_t
::
string
:
{
return
val
.
get
<
std
::
string
>
();
}
case
nlohmann
::
json
::
value_t
::
null
:
{
return
nullptr
;
}
case
nlohmann
::
json
::
value_t
::
boolean
:
{
return
val
.
get
<
bool
>
();
}
case
nlohmann
::
json
::
value_t
::
number_integer
:
case
nlohmann
::
json
::
value_t
::
number_unsigned
:
{
return
val
.
get
<
std
::
int64_t
>
();
}
default
:
return
std
::
nullopt
;
}
}
namespace
mtx
{
namespace
pushrules
{
...
...
@@ -32,6 +58,10 @@ to_json(nlohmann::json &obj, const PushCondition &condition)
obj
[
"is"
]
=
condition
.
is
;
if
(
condition
.
rel_type
!=
mtx
::
common
::
RelationType
::
Unsupported
)
obj
[
"rel_type"
]
=
condition
.
rel_type
;
if
(
condition
.
value
)
{
std
::
visit
([
&
obj
](
const
auto
&
e
)
{
obj
[
"value"
]
=
e
;
},
*
condition
.
value
);
}
}
void
...
...
@@ -43,6 +73,10 @@ from_json(const nlohmann::json &obj, PushCondition &condition)
condition
.
is
=
obj
.
value
(
"is"
,
""
);
condition
.
rel_type
=
obj
.
value
(
"rel_type"
,
mtx
::
common
::
RelationType
::
Unsupported
);
condition
.
include_fallback
=
obj
.
value
(
"include_fallback"
,
false
);
if
(
obj
.
contains
(
"value"
))
if
(
auto
val
=
to_non_compound_json_value
(
obj
.
at
(
"pattern"
)))
condition
.
value
=
*
val
;
}
namespace
actions
{
...
...
@@ -196,15 +230,17 @@ struct PushRuleEvaluator::OptimizedRules
std
::
unique_ptr
<
re2
::
RE2
>
pattern
;
//!< the pattern
std
::
string
field
;
//!< the field to match with pattern
bool
matches
(
const
std
::
unordered_map
<
std
::
string
,
std
::
string
>
&
ev
)
const
[[
nodiscard
]]
bool
matches
(
const
std
::
unordered_map
<
std
::
string
,
push_json_value
>
&
ev
)
const
{
if
(
auto
it
=
ev
.
find
(
field
);
it
!=
ev
.
end
())
{
if
(
pattern
)
{
if
(
pattern
&&
std
::
holds_alternative
<
std
::
string
>
(
it
->
second
)
)
{
if
(
field
==
"content.body"
)
{
if
(
!
re2
::
RE2
::
PartialMatch
(
it
->
second
,
*
pattern
))
if
(
!
re2
::
RE2
::
PartialMatch
(
std
::
get
<
std
::
string
>
(
it
->
second
),
*
pattern
))
return
false
;
}
else
{
if
(
!
re2
::
RE2
::
FullMatch
(
it
->
second
,
*
pattern
))
if
(
!
re2
::
RE2
::
FullMatch
(
std
::
get
<
std
::
string
>
(
it
->
second
)
,
*
pattern
))
return
false
;
}
}
...
...
@@ -218,6 +254,45 @@ struct PushRuleEvaluator::OptimizedRules
// TODO(Nico): Sort by field for faster matching?
std
::
vector
<
PatternCondition
>
patterns
;
//!< conditions that match on a field
//! a event_property_is condition to match
struct
IsCondition
{
non_compound_json_value
value
;
//!< the pattern
std
::
string
field
;
//!< the field to match with pattern
[[
nodiscard
]]
bool
matches
(
const
std
::
unordered_map
<
std
::
string
,
push_json_value
>
&
ev
)
const
{
if
(
auto
it
=
ev
.
find
(
field
);
it
!=
ev
.
end
())
{
return
std
::
visit
(
[
it
](
const
auto
&
e
)
{
return
it
->
second
==
push_json_value
(
e
);
},
value
);
}
return
false
;
}
};
std
::
vector
<
IsCondition
>
is
;
//!< conditions that match on a field being an exact value
//!
//! a event_property_contains condition to match
struct
ContainsCondition
{
non_compound_json_value
value
;
//!< the pattern
std
::
string
field
;
//!< the field to match with pattern
[[
nodiscard
]]
bool
matches
(
const
std
::
unordered_map
<
std
::
string
,
push_json_value
>
&
ev
)
const
{
if
(
auto
it
=
ev
.
find
(
field
);
it
!=
ev
.
end
())
{
if
(
auto
arr
=
std
::
get_if
<
std
::
vector
<
non_compound_json_value
>>
(
&
it
->
second
))
{
return
std
::
find
(
arr
->
begin
(),
arr
->
end
(),
value
)
!=
arr
->
end
();
}
}
return
false
;
}
};
std
::
vector
<
ContainsCondition
>
contains
;
//!< conditions that match on arrays
//! a pattern condition to match on a related event
struct
RelatedEventCondition
{
...
...
@@ -255,7 +330,7 @@ struct PushRuleEvaluator::OptimizedRules
std
::
vector
<
actions
::
Action
>
actions
;
//< the actions to apply on match
[[
nodiscard
]]
bool
matches
(
const
std
::
unordered_map
<
std
::
string
,
std
::
string
>
&
ev
,
const
std
::
unordered_map
<
std
::
string
,
push_json_value
>
&
ev
,
const
PushRuleEvaluator
::
RoomContext
&
ctx
,
const
std
::
map
<
mtx
::
common
::
RelationType
,
RelatedEvents
>
&
relatedEventsFlat
)
const
{
...
...
@@ -284,7 +359,8 @@ struct PushRuleEvaluator::OptimizedRules
if
(
sender_
==
ev
.
end
())
return
false
;
auto
sender_level
=
ctx
.
power_levels
.
user_level
(
sender_
->
second
);
auto
sender_level
=
ctx
.
power_levels
.
user_level
(
std
::
get
<
std
::
string
>
(
sender_
->
second
));
for
(
const
auto
&
n
:
notification_levels
)
{
if
(
sender_level
<
ctx
.
power_levels
.
notification_level
(
n
))
...
...
@@ -296,6 +372,14 @@ struct PushRuleEvaluator::OptimizedRules
if
(
!
cond
.
matches
(
ev
))
return
false
;
}
for
(
const
auto
&
cond
:
is
)
{
if
(
!
cond
.
matches
(
ev
))
return
false
;
}
for
(
const
auto
&
cond
:
contains
)
{
if
(
!
cond
.
matches
(
ev
))
return
false
;
}
for
(
const
auto
&
cond
:
related_event_patterns
)
{
bool
matched
=
false
;
...
...
@@ -327,12 +411,13 @@ struct PushRuleEvaluator::OptimizedRules
if
(
ctx
.
user_display_name
.
empty
())
return
false
;
if
(
auto
it
=
ev
.
find
(
"content.body"
);
it
!=
ev
.
end
())
{
if
(
auto
it
=
ev
.
find
(
"content.body"
);
it
!=
ev
.
end
()
&&
std
::
holds_alternative
<
std
::
string
>
(
it
->
second
))
{
re2
::
RE2
::
Options
opts
;
opts
.
set_case_sensitive
(
false
);
if
(
!
re2
::
RE2
::
PartialMatch
(
it
->
second
,
std
::
get
<
std
::
string
>
(
it
->
second
)
,
re2
::
RE2
(
"(
\\
W|^)"
+
re2
::
RE2
::
QuoteMeta
(
ctx
.
user_display_name
)
+
"(
\\
W|$)"
,
opts
)))
...
...
@@ -387,6 +472,16 @@ PushRuleEvaluator::PushRuleEvaluator(const Ruleset &rules_)
c
.
pattern
=
construct_re_from_pattern
(
cond
.
pattern
,
cond
.
key
);
if
(
c
.
pattern
)
rule
.
patterns
.
push_back
(
std
::
move
(
c
));
}
else
if
(
cond
.
kind
==
"event_property_is"
&&
cond
.
value
)
{
OptimizedRules
::
OptimizedRule
::
IsCondition
c
;
c
.
field
=
cond
.
key
;
c
.
value
=
cond
.
value
.
value
();
rule
.
is
.
push_back
(
std
::
move
(
c
));
}
else
if
(
cond
.
kind
==
"event_property_contains"
&&
cond
.
value
)
{
OptimizedRules
::
OptimizedRule
::
ContainsCondition
c
;
c
.
field
=
cond
.
key
;
c
.
value
=
cond
.
value
.
value
();
rule
.
contains
.
push_back
(
std
::
move
(
c
));
}
else
if
(
cond
.
kind
==
"im.nheko.msc3664.related_event_match"
)
{
OptimizedRules
::
OptimizedRule
::
RelatedEventCondition
c
;
...
...
@@ -516,13 +611,23 @@ PushRuleEvaluator::PushRuleEvaluator(const Ruleset &rules_)
static
void
flatten_impl
(
const
nlohmann
::
json
&
value
,
std
::
unordered_map
<
std
::
string
,
std
::
string
>
&
result
,
std
::
unordered_map
<
std
::
string
,
push_json_value
>
&
result
,
const
std
::
string
&
current_path
,
int
current_depth
)
{
if
(
current_depth
>
100
)
return
;
auto
escape_key
=
[](
std
::
string
input
)
{
for
(
size_t
i
=
0
;
i
<
input
.
size
();
i
++
)
{
if
(
input
[
i
]
==
'.'
)
{
input
.
insert
(
i
,
1
,
'\\'
);
i
++
;
}
}
return
input
;
};
switch
(
value
.
type
())
{
case
nlohmann
::
json
::
value_t
::
object
:
{
// iterate object and use keys as reference string
...
...
@@ -530,24 +635,61 @@ flatten_impl(const nlohmann::json &value,
if
(
!
current_path
.
empty
())
prefix
=
current_path
+
"."
;
for
(
const
auto
&
element
:
value
.
items
())
{
flatten_impl
(
element
.
value
(),
result
,
prefix
+
element
.
key
(),
current_depth
+
1
);
flatten_impl
(
element
.
value
(),
result
,
prefix
+
escape_key
(
element
.
key
()),
current_depth
+
1
);
}
break
;
}
case
nlohmann
::
json
::
value_t
::
array
:
{
std
::
vector
<
non_compound_json_value
>
arr
;
for
(
const
auto
&
val
:
value
)
{
switch
(
val
.
type
())
{
case
nlohmann
::
json
::
value_t
::
string
:
{
arr
.
emplace_back
(
val
.
get
<
std
::
string
>
());
break
;
}
case
nlohmann
::
json
::
value_t
::
null
:
{
arr
.
emplace_back
(
nullptr
);
break
;
}
case
nlohmann
::
json
::
value_t
::
boolean
:
{
arr
.
emplace_back
(
val
.
get
<
bool
>
());
break
;
}
case
nlohmann
::
json
::
value_t
::
number_integer
:
case
nlohmann
::
json
::
value_t
::
number_unsigned
:
{
arr
.
emplace_back
(
val
.
get
<
std
::
int64_t
>
());
break
;
}
default
:
break
;
}
}
result
[
current_path
]
=
arr
;
break
;
}
case
nlohmann
::
json
::
value_t
::
string
:
{
// add primitive value with its reference string
result
[
current_path
]
=
value
.
get
<
std
::
string
>
();
break
;
}
// currently we only match strings
case
nlohmann
::
json
::
value_t
::
array
:
case
nlohmann
::
json
::
value_t
::
null
:
case
nlohmann
::
json
::
value_t
::
boolean
:
case
nlohmann
::
json
::
value_t
::
null
:
{
result
[
current_path
]
=
nullptr
;
break
;
}
case
nlohmann
::
json
::
value_t
::
boolean
:
{
result
[
current_path
]
=
value
.
get
<
bool
>
();
break
;
}
case
nlohmann
::
json
::
value_t
::
number_integer
:
case
nlohmann
::
json
::
value_t
::
number_unsigned
:
case
nlohmann
::
json
::
value_t
::
number_float
:
case
nlohmann
::
json
::
value_t
::
number_unsigned
:
{
result
[
current_path
]
=
value
.
get
<
std
::
int64_t
>
();
break
;
}
case
nlohmann
::
json
::
value_t
::
number_float
:
// matrix events can't have floats
case
nlohmann
::
json
::
value_t
::
binary
:
case
nlohmann
::
json
::
value_t
::
discarded
:
default
:
...
...
@@ -555,10 +697,10 @@ flatten_impl(const nlohmann::json &value,
}
}
static
std
::
unordered_map
<
std
::
string
,
std
::
string
>
static
std
::
unordered_map
<
std
::
string
,
push_json_value
>
flatten_event
(
const
nlohmann
::
json
&
j
)
{
std
::
unordered_map
<
std
::
string
,
std
::
string
>
flat
;
std
::
unordered_map
<
std
::
string
,
push_json_value
>
flat
;
flatten_impl
(
j
,
flat
,
""
,
0
);
return
flat
;
}
...
...
This diff is collapsed.
Click to expand it.
tests/pushrules.cpp
+
126
−
0
View file @
453a7f37
...
...
@@ -424,6 +424,7 @@ TEST(Pushrules, EventMatches)
.
kind
=
"event_match"
,
.
key
=
"content.body"
,
.
pattern
=
"honk"
,
.
value
=
std
::
nullopt
,
.
is
=
""
,
});
...
...
@@ -513,6 +514,7 @@ TEST(Pushrules, DisplaynameMatches)
.
kind
=
"contains_display_name"
,
.
key
=
""
,
.
pattern
=
""
,
.
value
=
std
::
nullopt
,
.
is
=
""
,
});
...
...
@@ -586,6 +588,7 @@ TEST(Pushrules, PowerLevelMatches)
.
kind
=
"sender_notification_permission"
,
.
key
=
"room"
,
.
pattern
=
""
,
.
value
=
std
::
nullopt
,
.
is
=
""
,
});
...
...
@@ -642,6 +645,7 @@ TEST(Pushrules, MemberCountMatches)
.
kind
=
"room_member_count"
,
.
key
=
""
,
.
pattern
=
""
,
.
value
=
std
::
nullopt
,
.
is
=
is
,
},
};
...
...
@@ -667,6 +671,128 @@ TEST(Pushrules, MemberCountMatches)
testEval
(
"<100"
,
true
,
false
,
false
);
}
TEST
(
Pushrules
,
EventPropertyIsMatches
)
{
mtx
::
pushrules
::
PushRule
event_match_rule
;
event_match_rule
.
actions
=
{
mtx
::
pushrules
::
actions
::
notify
{},
mtx
::
pushrules
::
actions
::
set_tweak_highlight
{},
};
event_match_rule
.
conditions
.
push_back
(
mtx
::
pushrules
::
PushCondition
{
.
kind
=
"event_property_is"
,
.
key
=
"content.body"
,
.
pattern
=
""
,
.
value
=
"honk"
,
.
is
=
""
,
});
mtx
::
events
::
RoomEvent
<
mtx
::
events
::
msg
::
Text
>
textEv
{};
textEv
.
content
.
body
=
"honk"
;
textEv
.
room_id
=
"!abc:def.ghi"
;
textEv
.
event_id
=
"$abc1234567890:def.ghi"
;
textEv
.
sender
=
"@me:def.ghi"
;
auto
testEval
=
[
actions
=
event_match_rule
.
actions
,
&
textEv
](
const
mtx
::
pushrules
::
PushRuleEvaluator
&
evaluator
)
{
mtx
::
pushrules
::
PushRuleEvaluator
::
RoomContext
ctx
{};
EXPECT_EQ
(
evaluator
.
evaluate
({
textEv
},
ctx
,
{}),
actions
);
auto
textEvNo
=
textEv
;
textEvNo
.
content
.
body
=
"hon"
;
EXPECT_TRUE
(
evaluator
.
evaluate
({
textEvNo
},
ctx
,
{}).
empty
());
auto
textEvNo2
=
textEv
;
textEvNo2
.
content
.
body
=
"honkb"
;
EXPECT_TRUE
(
evaluator
.
evaluate
({
textEvNo2
},
ctx
,
{}).
empty
());
auto
textEvWordBoundaries
=
textEv
;
textEvWordBoundaries
.
content
.
body
=
"@honk:"
;
EXPECT_TRUE
(
evaluator
.
evaluate
({
textEvWordBoundaries
},
ctx
,
{}).
empty
());
};
mtx
::
pushrules
::
Ruleset
override_ruleset
;
override_ruleset
.
override_
.
push_back
(
event_match_rule
);
mtx
::
pushrules
::
PushRuleEvaluator
over_evaluator
{
override_ruleset
};
testEval
(
over_evaluator
);
mtx
::
pushrules
::
Ruleset
underride_ruleset
;
underride_ruleset
.
underride
.
push_back
(
event_match_rule
);
mtx
::
pushrules
::
PushRuleEvaluator
under_evaluator
{
underride_ruleset
};
testEval
(
under_evaluator
);
mtx
::
pushrules
::
Ruleset
room_ruleset
;
auto
room_rule
=
event_match_rule
;
room_rule
.
rule_id
=
"!abc:def.ghi"
;
room_ruleset
.
room
.
push_back
(
room_rule
);
mtx
::
pushrules
::
PushRuleEvaluator
room_evaluator
{
room_ruleset
};
EXPECT_EQ
(
room_evaluator
.
evaluate
({
textEv
},
{},
{}),
room_rule
.
actions
);
mtx
::
pushrules
::
Ruleset
sender_ruleset
;
auto
sender_rule
=
event_match_rule
;
sender_rule
.
rule_id
=
"@me:def.ghi"
;
sender_ruleset
.
sender
.
push_back
(
sender_rule
);
mtx
::
pushrules
::
PushRuleEvaluator
sender_evaluator
{
sender_ruleset
};
EXPECT_EQ
(
sender_evaluator
.
evaluate
({
textEv
},
{},
{}),
sender_rule
.
actions
);
mtx
::
pushrules
::
PushRule
event_match_5_rule
;
event_match_rule
.
actions
=
{
mtx
::
pushrules
::
actions
::
notify
{},
mtx
::
pushrules
::
actions
::
set_tweak_highlight
{},
};
event_match_rule
.
conditions
.
push_back
(
mtx
::
pushrules
::
PushCondition
{
.
kind
=
"event_property_is"
,
.
key
=
"content.info.size"
,
.
pattern
=
""
,
.
value
=
5
,
.
is
=
""
,
});
mtx
::
pushrules
::
Ruleset
match5_ruleset
;
match5_ruleset
.
override_
.
push_back
(
event_match_rule
);
mtx
::
pushrules
::
PushRuleEvaluator
over_evaluator5
{
match5_ruleset
};
mtx
::
events
::
RoomEvent
<
mtx
::
events
::
msg
::
Image
>
imageEv
{};
imageEv
.
content
.
info
.
size
=
5
;
mtx
::
pushrules
::
PushRuleEvaluator
::
RoomContext
ctx
{};
EXPECT_EQ
(
over_evaluator5
.
evaluate
({
imageEv
},
ctx
,
{}),
event_match_5_rule
.
actions
);
imageEv
.
content
.
info
.
size
=
6
;
EXPECT_TRUE
(
over_evaluator5
.
evaluate
({
imageEv
},
ctx
,
{}).
empty
());
}
TEST
(
Pushrules
,
EventPropertyContainsMatches
)
{
mtx
::
pushrules
::
PushRule
event_match_rule
;
event_match_rule
.
actions
=
{
mtx
::
pushrules
::
actions
::
notify
{},
mtx
::
pushrules
::
actions
::
set_tweak_highlight
{},
};
event_match_rule
.
conditions
.
push_back
(
mtx
::
pushrules
::
PushCondition
{
.
kind
=
"event_property_contains"
,
.
key
=
"content.via"
,
.
pattern
=
""
,
.
value
=
"honk"
,
.
is
=
""
,
});
mtx
::
events
::
StateEvent
<
mtx
::
events
::
state
::
space
::
Child
>
childEv
{};
childEv
.
content
.
via
=
{
"honk"
};
childEv
.
room_id
=
"!abc:def.ghi"
;
childEv
.
event_id
=
"$abc1234567890:def.ghi"
;
childEv
.
sender
=
"@me:def.ghi"
;
mtx
::
pushrules
::
Ruleset
override_ruleset
;
override_ruleset
.
override_
.
push_back
(
event_match_rule
);
mtx
::
pushrules
::
PushRuleEvaluator
evaluator
{
override_ruleset
};
mtx
::
pushrules
::
PushRuleEvaluator
::
RoomContext
ctx
{};
EXPECT_EQ
(
evaluator
.
evaluate
({
childEv
},
ctx
,
{}),
event_match_rule
.
actions
);
childEv
.
content
.
via
=
{
"abc"
,
"honk"
,
""
};
EXPECT_EQ
(
evaluator
.
evaluate
({
childEv
},
ctx
,
{}),
event_match_rule
.
actions
);
childEv
.
content
.
via
=
{};
EXPECT_TRUE
(
evaluator
.
evaluate
({
childEv
},
ctx
,
{}).
empty
());
childEv
.
content
.
via
=
{
"not honk"
};
EXPECT_TRUE
(
evaluator
.
evaluate
({
childEv
},
ctx
,
{}).
empty
());
}
TEST
(
Pushrules
,
ContentOverRoomRulesMatches
)
{
json
raw_rule
=
R"(
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment