blob: da5e3a959e062bc5dc92ddb52b672269ff260f8b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.metron.stellar.dsl.functions;
import com.google.common.collect.ImmutableMap;
import org.apache.metron.stellar.dsl.DefaultVariableResolver;
import org.apache.metron.stellar.dsl.ParseException;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.metron.stellar.common.utils.StellarProcessorUtils.run;
import static org.apache.metron.stellar.common.utils.StellarProcessorUtils.runPredicate;
import static org.junit.jupiter.api.Assertions.*;
public class MatchTest {
// Short Circuit
@Test
@SuppressWarnings("unchecked")
public void testMissingVariableFalsey() {
assertTrue(runPredicate(
"match{NOT(is_alert) => true, foo > 5 => false, foo > 10 => false, default => false}",
new HashMap() {{
put("foo", 100);
}}));
assertFalse(runPredicate(
"match{is_alert => true, foo > 5 => false, foo > 10 => false, default => false}",
new HashMap() {{
put("foo", 100);
}}));
assertFalse(runPredicate(
"match{foo > 5 => false, is_alert => true, foo > 10 => false, default => false}",
new HashMap() {{
put("foo", 100);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testEmptyListFalsey() {
assertTrue(runPredicate(
"match{NOT([]) => true, foo > 5 => false, foo > 10 => false, default => false}",
new HashMap() {{
put("foo", 100);
}}));
assertFalse(runPredicate(
"match{[] => true, foo > 5 => false, foo > 10 => false, default => false}",
new HashMap() {{
put("foo", 100);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testThreeTrueClausesFirstOnlyFires() {
assertTrue(runPredicate(
"match{foo > 0 => true, foo > 5 => false, foo > 10 => false, default => false}",
new HashMap() {{
put("foo", 100);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testTwoClausesSecondFires() {
assertTrue(runPredicate("match{foo < 0 => false, foo < 500 => true, default => false}",
new HashMap() {{
put("foo", 100);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testThreeClausesFirstFires() {
List<String> list = (List<String>) run(
"match{ foo > 100 => ['oops'], foo > 200 => ['oh no'], foo >= 500 => MAP(['ok', 'haha'], (a) -> TO_UPPER(a)), default => ['a']}",
new HashMap() {{
put("foo", 500);
}});
assertEquals(1, list.size());
assertTrue(list.contains("oops"));
}
@Test
@SuppressWarnings("unchecked")
public void testShortCircuitWithThrows() {
assertEquals("ok",
run("match{ foo > 100 => THROW('oops'), foo > 200 => THROW('oh no'), default => 'ok' }",
new HashMap() {{
put("foo", 50);
}}));
}
// LAMBDAS
@Test
@SuppressWarnings("unchecked")
public void testMatchLambda() {
assertTrue(
runPredicate("match { 1 >= 0 => ()-> true, default => ()->false }", new HashMap() {{
put("foo", 0);
}}));
assertTrue(
runPredicate("match { foo == 0 => ()-> true, default => ()-> false }", new HashMap() {{
put("foo", 0);
}}));
assertFalse(
runPredicate("match { foo == 0 => ()-> true, default => ()-> false }", new HashMap() {{
put("foo", 1);
}}));
assertTrue(runPredicate(
"match { foo == 0 => ()-> false, foo == 1 => ()-> true, default => ()-> false }",
new HashMap() {{
put("foo", 1);
}}));
assertTrue(runPredicate(
"match { foo == 0 => ()-> bFalse, foo == 1 => ()-> bTrue, default => ()-> bFalse }",
new HashMap() {{
put("foo", 1);
put("bFalse", false);
put("bTrue", true);
}}));
assertTrue(runPredicate(
"match { foo == 0 => ()-> bFalse, foo == 1 => ()-> bTrue, default => ()-> bFalse }",
new HashMap() {{
put("foo", 1);
put("bFalse", false);
put("bTrue", true);
}}));
}
// GENERAL WITH MAP EVAL
@Test
@SuppressWarnings("unchecked")
public void testMatchMAPEvaluation() {
String expr = "match{ var1 => MAP(['foo', 'bar'], (x) -> TO_UPPER(x)), default => null }";
Object o = run(expr, ImmutableMap.of("foo", "foo", "bar", "bar", "var1", true));
assertTrue(o instanceof List);
List<String> result = (List<String>) o;
assertEquals(2, result.size());
assertEquals("FOO", result.get(0));
assertEquals("BAR", result.get(1));
}
@Test
@SuppressWarnings("unchecked")
public void workingMatchWithMap() {
assertEquals(Arrays.asList("OK", "HAHA"),
run("match{ foo > 100 => THROW('oops'), foo > 200 => THROW('oh no'), foo >= 50 => MAP(['ok', 'haha'], (a) -> TO_UPPER(a)), default=> 'a' }",
new HashMap() {{
put("foo", 50);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testMapSmall() {
List<String> ret = (List<String>) run(
"match{ foo < 100 => ['oops'], default => MAP(['ok', 'haha'], (a) -> TO_UPPER(a))}",
new HashMap() {{
put("foo", 500);
}});
assertEquals(2, ret.size());
assertTrue(ret.contains("OK"));
assertTrue(ret.contains("HAHA"));
}
@Test
@SuppressWarnings("unchecked")
public void testMultiClauseMap() {
run("match{ foo < 100 => ['oops'], foo < 200 => ['oh no'], foo >= 500 => MAP(['ok', 'haha'], (a) -> TO_UPPER(a)), default => ['a']}",
new HashMap() {{
put("foo", 500);
}});
}
// REGEX
@Test
public void testMatchRegexMatch() {
final Map<String, String> variables = new HashMap<String, String>() {{
put("numbers", "12345");
put("numberPattern", "\\d(\\d)(\\d).*");
put("letters", "abcde");
put("empty", "");
}};
assertTrue(
runPredicate("match{ REGEXP_MATCH(numbers,numberPattern)=> true, default => false}",
new DefaultVariableResolver(variables::get, variables::containsKey)));
assertFalse(
runPredicate("match{ REGEXP_MATCH(letters,numberPattern) => true, default =>false}",
new DefaultVariableResolver(variables::get, variables::containsKey)));
}
// BARE STATEMENTS
@Test
@SuppressWarnings("unchecked")
public void testMatchBareStatements() {
assertTrue(
runPredicate("match { foo == 0 => bFalse, foo == 1 => bTrue, default => false }",
new HashMap() {{
put("foo", 1);
put("bFalse", false);
put("bTrue", true);
}}));
assertEquals("warning",
run("match{ threat.triage.level < 10 => 'info', threat.triage.level < 20 => 'warning', default => 'critical' }",
new HashMap() {{
put("threat.triage.level", 15);
}}));
}
// FUNCTIONS
@Test
@SuppressWarnings("unchecked")
public void testWithFunction() {
assertEquals("WARNING",
run("match{ threat.triage.level < 10 => 'info', threat.triage.level < 20 => TO_UPPER('warning'), default => 'critical' }",
new HashMap() {{
put("threat.triage.level", 15);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testWithFunctionMultiArgs() {
assertEquals("false",
run("match{ threat.triage.level < 10 => 'info', threat.triage.level < 20 => TO_STRING(IS_ENCODING(other,'BASE32')), default => 'critical' }",
new HashMap() {{
put("threat.triage.level", 15);
put("other", "value");
}}));
assertEquals(false,
run("match{ threat.triage.level < 10 => 'info', threat.triage.level < 20 => IS_ENCODING(other,'BASE32'), default => 'critical' }",
new HashMap() {{
put("threat.triage.level", 15);
put("other", "value");
}}));
}
// LOGICAL EXPRESSIONS IN CHECKS
@Test
@SuppressWarnings("unchecked")
public void testLogical() {
assertTrue(
runPredicate("match { foo == 0 OR bar == 'yes' => ()-> true, default => ()-> false }",
new HashMap() {{
put("foo", 1);
put("bar", "yes");
}}));
assertTrue(
runPredicate("match { foo == 0 AND bar == 'yes' => ()-> true, default => ()-> false }",
new HashMap() {{
put("foo", 0);
put("bar", "yes");
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testTernaryFuncWithoutIfCheck() {
assertEquals("a",
run("match{ foo == 5 ? true : false => 'a', default => 'ok' }", new HashMap() {{
put("foo", 5);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testTernaryFuncAsMatchAction() {
assertEquals(false, run("match{ threat.triage.level < 10 => 'info', threat.triage.level < 20 => IS_ENCODING(other,'BASE32')? true : false, default => 'critical' }",
new HashMap() {{
put("threat.triage.level", 15);
put("other", "value");
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testVariableIFCheck() {
assertEquals("a",
run("match{ IF foo == 5 THEN true ELSE false => 'a', default => 'ok' }", new HashMap() {{
put("foo", 5);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testIfThenElseAction() {
assertEquals(2,run("match{ foo == true => IF bar THEN 1 ELSE 2, default => 0}", new HashMap(){{
put("foo",true);
put("bar",false);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testVariableOnly() {
assertEquals("a", run("match{ foo => 'a', default => null}", new HashMap() {{
put("foo", true);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testVariableEqualsCheck() {
assertEquals("a", run("match{ foo == 5 => 'a', default => 'ok' }", new HashMap() {{
put("foo", 5);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testVariableOnlyCheckWithDefault() {
assertEquals("a", run("match{ foo => 'a', default => 'b' }", new HashMap() {{
put("foo", true);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testHandleVariableEqualsCheckWithDefault() {
assertEquals("a", run("match{ foo == true => 'a', default=> 'b' }", new HashMap() {{
put("foo", true);
}}));
}
@Test
@SuppressWarnings("unchecked")
public void testNullInCheckedReturnNull() {
assertNull(
run(
"match{ foo == null => null, foo == true => 'not that null', default => 'really not that null'}",
new HashMap(){{
put("foo",null);
}}));
}
// SYNTAX ERRORS
@Test
@SuppressWarnings("unchecked")
public void testMatchErrorNoDefault() {
assertThrows(
ParseException.class,
() ->
run(
"match{ foo > 100 => 'greater than 100', foo > 200 => 'greater than 200' }",
new HashMap() {
{
put("foo", 50);
}
}));
}
@Test
@SuppressWarnings("unchecked")
public void testNestedMatchNotSupportted() {
// we cannot nest short circuit types in stellar
assertThrows(
ParseException.class,
() ->
run(
"match{ x == 0 => match{ y == 10 => false, default => true}, default => true}",
new HashMap() {
{
put("x", 0);
put("y", 10);
}
}));
}
@Test
@SuppressWarnings("unchecked")
public void testReturnList() {
Object o = run("match{ foo > 100 => ['oops'],default => ['a']}", new HashMap() {{
put("foo", 500);
}});
List l = (List) o;
assertEquals(1, l.size());
}
}