Add unit test for TrafficTreatment REST API

Change-Id: I19834b9edbb87e66ca6fb4ce4f6601f685ba116e
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/TrafficTreatmentCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/TrafficTreatmentCodecTest.java
new file mode 100644
index 0000000..f23e846
--- /dev/null
+++ b/core/common/src/test/java/org/onosproject/codec/impl/TrafficTreatmentCodecTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed 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.onosproject.codec.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.apache.commons.lang3.StringUtils;
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeDiagnosingMatcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.meter.MeterId;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Unit tests for traffic treatment codec.
+ */
+public class TrafficTreatmentCodecTest {
+
+    private MockCodecContext context;
+    private JsonCodec<TrafficTreatment> trafficTreatmentCodec;
+
+    @Before
+    public void setUp() {
+        context = new MockCodecContext();
+        trafficTreatmentCodec = context.codec(TrafficTreatment.class);
+        assertThat(trafficTreatmentCodec, notNullValue());
+    }
+
+    /**
+     * Tests encoding of a traffic treatment object.
+     */
+    @Test
+    public void testTrafficTreatmentEncode() {
+
+        Instruction output = Instructions.createOutput(PortNumber.portNumber(0));
+        Instruction modL2Src = Instructions.modL2Src(MacAddress.valueOf("11:22:33:44:55:66"));
+        Instruction modL2Dst = Instructions.modL2Dst(MacAddress.valueOf("44:55:66:77:88:99"));
+        MeterId meterId = MeterId.meterId(0);
+        Instruction meter = Instructions.meterTraffic(meterId);
+        Instruction transition = Instructions.transition(1);
+        TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
+        TrafficTreatment treatment = tBuilder
+                .add(output)
+                .add(modL2Src)
+                .add(modL2Dst)
+                .add(meter)
+                .add(transition)
+                .build();
+
+        ObjectNode treatmentJson = trafficTreatmentCodec.encode(treatment, context);
+        assertThat(treatmentJson, TrafficTreatmentJsonMatcher.matchesTrafficTreatment(treatment));
+    }
+
+    /**
+     * Tests decoding of a traffic treatment JSON object.
+     */
+    @Test
+    public void testTrafficTreatmentDecode() throws IOException {
+        TrafficTreatment treatment = getTreatment("TrafficTreatment.json");
+
+        List<Instruction> insts = treatment.immediate();
+        assertThat(insts.size(), is(2));
+
+        ImmutableSet<String> types = ImmutableSet.of("OUTPUT", "L2MODIFICATION");
+        assertThat(types.contains(insts.get(0).type().name()), is(true));
+        assertThat(types.contains(insts.get(1).type().name()), is(true));
+    }
+
+    private static final class TrafficTreatmentJsonMatcher
+            extends TypeSafeDiagnosingMatcher<JsonNode> {
+
+        private final TrafficTreatment trafficTreatment;
+
+        private TrafficTreatmentJsonMatcher(TrafficTreatment trafficTreatment) {
+            this.trafficTreatment = trafficTreatment;
+        }
+
+        /**
+         * Filtered out the meter and table transition instructions.
+         *
+         * @param node JSON node
+         * @return filtered JSON node
+         */
+        private int filteredSize(JsonNode node) {
+            int counter = 0;
+            for (int idx = 0; idx < node.size(); idx++) {
+                String type = node.get(idx).get("type").asText();
+                if (!type.equals("METER") && !type.equals("TABLE")) {
+                    counter++;
+                }
+            }
+            return counter;
+        }
+
+        private JsonNode getInstNode(JsonNode node, String name) {
+            for (int idx = 0; idx < node.size(); idx++) {
+                String type = node.get(idx).get("type").asText();
+                if (type.equals(name)) {
+                    return node.get(idx);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        protected boolean matchesSafely(JsonNode jsonNode, Description description) {
+
+            // check instructions
+            final JsonNode jsonInstructions = jsonNode.get("instructions");
+
+            if (trafficTreatment.immediate().size() != filteredSize(jsonInstructions)) {
+                description.appendText("instructions array size of " +
+                        Integer.toString(trafficTreatment.immediate().size()));
+                return false;
+            }
+
+            for (final Instruction instruction : trafficTreatment.immediate()) {
+                boolean instructionFound = false;
+                for (int instructionIndex = 0; instructionIndex < jsonInstructions.size();
+                     instructionIndex++) {
+                    final String jsonType =
+                            jsonInstructions.get(instructionIndex).get("type").asText();
+                    final String instructionType = instruction.type().name();
+                    if (jsonType.equals(instructionType)) {
+                        instructionFound = true;
+                    }
+                }
+                if (!instructionFound) {
+                    description.appendText("instruction " + instruction.toString());
+                    return false;
+                }
+            }
+
+            // check metered
+            JsonNode meterNode = getInstNode(jsonInstructions, "METER");
+            String jsonMeterId = meterNode != null ? meterNode.get("meterId").asText() : null;
+            String meterId = trafficTreatment.metered().meterId().toString();
+            if (!StringUtils.equals(jsonMeterId, meterId)) {
+                description.appendText("meter id was " + jsonMeterId);
+                return false;
+            }
+
+            // check table transition
+            JsonNode tableNode = getInstNode(jsonInstructions, "TABLE");
+            String jsonTableId = tableNode != null ? tableNode.get("tableId").asText() : null;
+            String tableId = trafficTreatment.tableTransition().tableId().toString();
+            if (!StringUtils.equals(jsonTableId, tableId)) {
+                description.appendText("table id was " + jsonMeterId);
+                return false;
+            }
+
+            // TODO: check deferred
+
+            return true;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText(trafficTreatment.toString());
+        }
+
+        /**
+         * Factory to allocate a traffic treatment.
+         *
+         * @param trafficTreatment traffic treatment object we are looking for
+         * @return matcher
+         */
+        static TrafficTreatmentJsonMatcher matchesTrafficTreatment(TrafficTreatment trafficTreatment) {
+            return new TrafficTreatmentJsonMatcher(trafficTreatment);
+        }
+    }
+
+    /**
+     * Reads in a traffic treatment from the given resource and decodes it.
+     *
+     * @param resourceName resource to use to read the JSON for the rule
+     * @return decoded trafficTreatment
+     * @throws IOException if processing the resource fails
+     */
+    private TrafficTreatment getTreatment(String resourceName) throws IOException {
+        InputStream jsonStream = TrafficTreatmentCodecTest.class.getResourceAsStream(resourceName);
+        JsonNode json = context.mapper().readTree(jsonStream);
+        assertThat(json, notNullValue());
+        TrafficTreatment treatment = trafficTreatmentCodec.decode((ObjectNode) json, context);
+        assertThat(treatment, notNullValue());
+        return treatment;
+    }
+}
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/TrafficTreatment.json b/core/common/src/test/resources/org/onosproject/codec/impl/TrafficTreatment.json
new file mode 100644
index 0000000..c185905
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/TrafficTreatment.json
@@ -0,0 +1,21 @@
+{
+  "instructions": [
+    {
+      "type": "OUTPUT",
+      "port": -3
+    },
+    {
+      "type": "L2MODIFICATION",
+      "subtype": "ETH_SRC",
+      "mac": "00:00:00:00:00:00"
+    },
+    {
+      "type": "METER",
+      "meterId": "0"
+    },
+    {
+      "type": "TABLE",
+      "tableId": "1"
+    }
+  ]
+}
\ No newline at end of file