package software.amazon.event.ruler; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** * Unit testing a state GenericMachine is hard. Tried hand-computing a few GenericMachines * but kept getting them wrong, the software was right. So this is really * more of a smoke/integration test. But the coverage is quite good. */ public class GenericMachineTest { @Test public void anythingButPrefixTest() throws Exception { String event = " {\n" + " \"a\": \"lorem\", " + " \"b\": \"ipsum\"" + " }"; String ruleTemplate = "{\n" + " \"a\": [ { \"anything-but\": { \"prefix\": \"FOO\" } } ]" + "}"; String[] prefixes = { "l", "lo", "lor", "lorem" }; List<String> ruleNames = new ArrayList<>(); Machine m = new Machine(); for (int i = 0; i < prefixes.length; i++) { String ruleName = "r" + i; ruleNames.add(ruleName); String rule = ruleTemplate.replace("FOO", prefixes[i]); m.addRule(ruleName, rule); } List<String> matches = m.rulesForJSONEvent(event); assertEquals(0, matches.size()); String[] shouldMatch = { "now", "is", "the", "time", "for", "all", "good", "aliens" }; for (String s : shouldMatch) { String e = event.replace("lorem", s); matches = m.rulesForJSONEvent(e); assertEquals(ruleNames.size(), matches.size()); for (String rn : ruleNames) { assertTrue(matches.contains(rn)); } } for (int i = 0; i < prefixes.length; i++) { String ruleName = "r" + i; String rule = ruleTemplate.replace("FOO", prefixes[i]); m.deleteRule(ruleName, rule); } for (String s : shouldMatch) { String e = event.replace("lorem", s); matches = m.rulesForJSONEvent(e); assertEquals(0, matches.size()); } } @Test public void anythingButSuffixTest() throws Exception { String event = " {\n" + " \"a\": \"lorem\", " + " \"b\": \"ipsum\"" + " }"; String ruleTemplate = "{\n" + " \"a\": [ { \"anything-but\": { \"suffix\": \"FOO\" } } ]" + "}"; String[] suffixes = { "m", "em", "rem", "orem", "lorem" }; List<String> ruleNames = new ArrayList<>(); Machine m = new Machine(); for (int i = 0; i < suffixes.length; i++) { String ruleName = "r" + i; ruleNames.add(ruleName); String rule = ruleTemplate.replace("FOO", suffixes[i]); m.addRule(ruleName, rule); } List<String> matches = m.rulesForJSONEvent(event); assertEquals(0, matches.size()); String[] shouldMatch = { "run", "walk", "skip", "hop" }; for (String s : shouldMatch) { String e = event.replace("lorem", s); matches = m.rulesForJSONEvent(e); assertEquals(ruleNames.size(), matches.size()); for (String rn : ruleNames) { assertTrue(matches.contains(rn)); } } for (int i = 0; i < suffixes.length; i++) { String ruleName = "r" + i; String rule = ruleTemplate.replace("FOO", suffixes[i]); m.deleteRule(ruleName, rule); } for (String s : shouldMatch) { String e = event.replace("lorem", s); matches = m.rulesForJSONEvent(e); assertEquals(0, matches.size()); } } public static String readData(String jsonName) throws Exception { String wd = System.getProperty("user.dir"); Path path = FileSystems.getDefault().getPath(wd, "src", "test", "data", jsonName); return new String(Files.readAllBytes(path)); } public static JsonNode readAsTree(String jsonName) throws Exception, IOException { return new ObjectMapper().readTree(readData(jsonName)); } @Test public void arraysBugTest() throws Exception { String event = "{\n" + " \"requestContext\": { \"obfuscatedCustomerId\": \"AIDACKCEVSQ6C2EXAMPLE\" },\n" + " \"hypotheses\": [\n" + " { \"isBluePrint\": true, \"creator\": \"A123\" },\n" + " { \"isBluePrint\": false, \"creator\": \"A234\" }\n" + " ]\n" + "}"; String r1 = "{\n" + " \"hypotheses\": {\n" + " \"isBluePrint\": [ false ],\n" + " \"creator\": [ \"A123\" ]\n" + " }\n" + "}"; String r2 = "{\n" + " \"hypotheses\": {\n" + " \"isBluePrint\": [ true ],\n" + " \"creator\": [ \"A234\" ]\n" + " }\n" + "}"; Machine m = new Machine(); m.addRule("r1", r1); m.addRule("r2", r2); JsonNode json = new ObjectMapper().readTree(event); List<String> matches = m.rulesForJSONEvent(json); assertEquals(0, matches.size()); } @Test public void nestedArraysTest() throws Exception { String event1 = readData("arrayEvent1.json"); String event2 = readData("arrayEvent2.json"); String event3 = readData("arrayEvent3.json"); String event4 = readData("arrayEvent4.json"); String rule1 = readData("arrayRule1.json"); String rule2 = readData("arrayRule2.json"); String rule3 = readData("arrayRule3.json"); Machine m = new Machine(); m.addRule("rule1", rule1); m.addRule("rule2", rule2); m.addRule("rule3", rule3); List<String> r1 = m.rulesForEvent(event1); List<String> r1AC = m.rulesForJSONEvent(event1); assertEquals(2, r1.size()); assertTrue(r1.contains("rule1")); assertTrue(r1.contains("rule2")); assertEquals(r1, r1AC); // event2 shouldn't match any rules List<String> r2 = m.rulesForEvent(event2); List<String> r2AC = m.rulesForJSONEvent(event2); assertEquals(0, r2.size()); assertEquals(r2, r2AC); // event3 shouldn't match any rules with AC on List<String> r3 = m.rulesForEvent(event3); List<String> r3AC = m.rulesForJSONEvent(event3); assertEquals(1, r3.size()); assertTrue(r3.contains("rule3")); assertEquals(0, r3AC.size()); // event4 should match rule3 List<String> r4 = m.rulesForEvent(event4); List<String> r4AC = m.rulesForJSONEvent(event4); assertEquals(1, r4.size()); assertTrue(r4.contains("rule3")); assertEquals(r4, r4AC); } // create a customized class as T // TODO: Figure out what these unused fields are for and either finish what was started here, or discard it public static final class SimpleFilter { private String filterId; private String filterExpression; private List<String> downChannels; private long lastUpdatedMs; SimpleFilter(String clientId, String filterId, String filterExpression, List<String> downChannels, long lastUpdatedMs) { this.filterId = filterId; this.filterExpression = filterExpression; this.downChannels = downChannels; this.lastUpdatedMs = lastUpdatedMs; } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof SimpleFilter)) { return false; } SimpleFilter other = (SimpleFilter) obj; return filterId.equals(other.filterId); } @Override public int hashCode() { return filterId.hashCode(); } } private final static SimpleFilter r1 = new SimpleFilter("clientId1111","filterId1111","{ \"a\" : [ 1 ] }", Arrays.asList("dc11111", "dc21111", "dc31111"),1525821702L); private final static SimpleFilter r2 = new SimpleFilter("clientId2222","filterId2222","{ \"a\" : [ 2 ] }", Arrays.asList("dc12222", "dc22222", "dc32222"),1525821702L); private final static SimpleFilter r3 = new SimpleFilter("clientId1111","filterId3333","{ \"a\" : [ 3 ] }", Arrays.asList("dc13333", "dc23333", "dc33333"),1525821702L); @Test public void testSimplestPossibleGenericMachine() throws Exception { String rule1 = "{ \"a\" : [ 1 ] }"; String rule2 = "{ \"b\" : [ 2 ] }"; String rule3 = "{ \"c\" : [ 3 ] }"; GenericMachine<SimpleFilter> genericMachine = new GenericMachine<>(); genericMachine.addRule(r1, rule1); genericMachine.addRule(r2, rule2); genericMachine.addRule(r3, rule3); String[] event1 = { "a", "1" }; String jsonEvent1 = "{ \"a\" : 1 }" ; String[] event2 = { "b", "2" }; String jsonEvent2 = "{ \"b\" : 2 }" ; String[] event4 = { "x", "true" }; String jsonEvent4 = "{ \"x\" : true }" ; String[] event5 = { "a", "1", "b", "2","c", "3"}; String jsonEvent5 = "{ \"a\" : 1, \"b\": 2, \"c\" : 3 }"; List<SimpleFilter> val; val = genericMachine.rulesForEvent(event1); assertEquals(1, val.size()); assertEquals(r1, val.get(0)); assertNotNull(val.get(0)); val = genericMachine.rulesForJSONEvent(jsonEvent1); assertEquals(1, val.size()); assertEquals(r1, val.get(0)); assertNotNull(val.get(0)); val = genericMachine.rulesForEvent(event2); assertEquals(1, val.size()); assertEquals(r2, val.get(0)); assertNotNull(val.get(0)); val = genericMachine.rulesForJSONEvent(jsonEvent2); assertEquals(1, val.size()); assertEquals(r2, val.get(0)); assertNotNull(val.get(0)); val = genericMachine.rulesForEvent(event4); assertEquals(0, val.size()); val = genericMachine.rulesForJSONEvent(jsonEvent4); assertEquals(0, val.size()); val = genericMachine.rulesForEvent(event5); assertEquals(3, val.size()); val.forEach(r -> assertTrue(Stream.of(r1, r2, r3).anyMatch(i -> (i == r)))); val = genericMachine.rulesForJSONEvent(jsonEvent5); assertEquals(3, val.size()); val.forEach(r -> assertTrue(Stream.of(r1, r2, r3).anyMatch(i -> (i == r)))); } @Test public void testEvaluateComplexity() throws Exception { String rule1 = "{ \"a\" : [ { \"wildcard\": \"a*bc\" } ] }"; String rule2 = "{ \"b\" : [ { \"wildcard\": \"a*aa\" } ] }"; String rule3 = "{ \"c\" : [ { \"wildcard\": \"xyz*\" } ] }"; GenericMachine<SimpleFilter> genericMachine = new GenericMachine<>(); genericMachine.addRule(r1, rule1); genericMachine.addRule(r2, rule2); genericMachine.addRule(r3, rule3); MachineComplexityEvaluator evaluator = new MachineComplexityEvaluator(100); assertEquals(3, genericMachine.evaluateComplexity(evaluator)); } @Test public void testEvaluateComplexityHitMax() throws Exception { String rule1 = "{ \"a\" : [ { \"wildcard\": \"a*bc\" } ] }"; String rule2 = "{ \"b\" : [ { \"wildcard\": \"a*aa\" } ] }"; String rule3 = "{ \"c\" : [ { \"wildcard\": \"xyz*\" } ] }"; GenericMachine<SimpleFilter> genericMachine = new GenericMachine<>(); genericMachine.addRule(r1, rule1); genericMachine.addRule(r2, rule2); genericMachine.addRule(r3, rule3); MachineComplexityEvaluator evaluator = new MachineComplexityEvaluator(2); assertEquals(2, genericMachine.evaluateComplexity(evaluator)); } @Test public void testEmptyInput() throws Exception { String rule1 = "{\n" + "\"detail\": {\n" + " \"c-count\": [ { \"exists\": false } ]\n" + "},\n" + "\"d-count\": [ { \"exists\": false } ],\n" + "\"e-count\": [ { \"exists\": false } ]\n" + "}"; String event = "{}"; GenericMachine<String> genericMachine = new GenericMachine<>(); genericMachine.addRule("rule1", rule1); List<String> rules = genericMachine.rulesForJSONEvent(event); assertTrue(rules.contains("rule1")); rules = genericMachine.rulesForEvent(new ArrayList<>()); assertTrue(rules.contains("rule1")); } }