blob: 28ca0a54af11d089c2f0c9db19c5b43d779296ef [file] [log] [blame]
Ray Milkey2b217142014-12-15 09:24:24 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Ray Milkey2b217142014-12-15 09:24:24 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Jian Li8ae91202016-03-24 14:36:16 -070016package org.onosproject.rest.resources;
Ray Milkey2b217142014-12-15 09:24:24 -080017
Author Namee252a002016-09-26 22:42:24 +053018import com.eclipsesource.json.Json;
19import com.eclipsesource.json.JsonArray;
20import com.eclipsesource.json.JsonObject;
21import com.eclipsesource.json.JsonValue;
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +020022import com.fasterxml.jackson.databind.node.ObjectNode;
Ray Milkey2b217142014-12-15 09:24:24 -080023import org.hamcrest.Description;
Ray Milkeyb82c42b2015-06-30 09:42:20 -070024import org.hamcrest.Matchers;
Ray Milkey2b217142014-12-15 09:24:24 -080025import org.hamcrest.TypeSafeMatcher;
26import org.junit.After;
27import org.junit.Before;
28import org.junit.Test;
29import org.onlab.osgi.ServiceDirectory;
30import org.onlab.osgi.TestServiceDirectory;
Author Namee252a002016-09-26 22:42:24 +053031import org.onlab.packet.MacAddress;
Ray Milkey2b217142014-12-15 09:24:24 -080032import org.onlab.rest.BaseResource;
33import org.onosproject.codec.CodecService;
34import org.onosproject.codec.impl.CodecManager;
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +020035import org.onosproject.codec.impl.MockCodecContext;
Ray Milkey2b217142014-12-15 09:24:24 -080036import org.onosproject.core.ApplicationId;
Ayaka Koshibec06c89b2015-02-10 19:25:41 -080037import org.onosproject.core.CoreService;
Ray Milkey2b217142014-12-15 09:24:24 -080038import org.onosproject.core.DefaultApplicationId;
Author Namee252a002016-09-26 22:42:24 +053039import org.onosproject.core.GroupId;
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +020040import org.onosproject.net.ConnectPoint;
41import org.onosproject.net.DefaultLink;
Author Namee252a002016-09-26 22:42:24 +053042import org.onosproject.net.DeviceId;
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +020043import org.onosproject.net.Link;
Ray Milkey2b217142014-12-15 09:24:24 -080044import org.onosproject.net.NetworkResource;
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +020045import org.onosproject.net.PortNumber;
Author Namee252a002016-09-26 22:42:24 +053046import org.onosproject.net.flow.DefaultTrafficSelector;
47import org.onosproject.net.flow.DefaultTrafficTreatment;
48import org.onosproject.net.flow.FlowEntry;
Ray Milkey634eb172017-04-05 14:48:50 -070049import org.onosproject.net.flow.FlowEntryAdapter;
Author Namee252a002016-09-26 22:42:24 +053050import org.onosproject.net.flow.FlowId;
51import org.onosproject.net.flow.FlowRule;
52import org.onosproject.net.flow.FlowRuleExtPayLoad;
53import org.onosproject.net.flow.FlowRuleService;
Carmelo Cascone41605742017-06-19 15:46:44 +090054import org.onosproject.net.flow.TableId;
Author Namee252a002016-09-26 22:42:24 +053055import org.onosproject.net.flow.TrafficSelector;
56import org.onosproject.net.flow.TrafficTreatment;
57import org.onosproject.net.flow.criteria.Criterion;
58import org.onosproject.net.flow.instructions.Instruction;
Ray Milkey7b158512015-07-21 16:32:43 -070059import org.onosproject.net.intent.FakeIntentManager;
Author Namee252a002016-09-26 22:42:24 +053060import org.onosproject.net.intent.FlowRuleIntent;
Ray Milkey2b217142014-12-15 09:24:24 -080061import org.onosproject.net.intent.Intent;
Ray Milkey2b217142014-12-15 09:24:24 -080062import org.onosproject.net.intent.IntentService;
Ray Milkey1534f8d2015-05-13 15:42:50 -070063import org.onosproject.net.intent.IntentState;
Ray Milkeyf9af43c2015-02-09 16:45:48 -080064import org.onosproject.net.intent.Key;
Thomas Vachuska2048c1f2017-05-10 19:32:22 -070065import org.onosproject.net.intent.MockIdGenerator;
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +020066import org.onosproject.net.provider.ProviderId;
Ray Milkey2b217142014-12-15 09:24:24 -080067
dvaddire95c84ed2017-06-14 15:42:24 +053068
Author Namee252a002016-09-26 22:42:24 +053069import javax.ws.rs.NotFoundException;
70import javax.ws.rs.client.Entity;
71import javax.ws.rs.client.WebTarget;
72import javax.ws.rs.core.MediaType;
73import javax.ws.rs.core.Response;
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +020074import java.io.IOException;
Author Namee252a002016-09-26 22:42:24 +053075import java.io.InputStream;
76import java.net.HttpURLConnection;
77import java.util.ArrayList;
78import java.util.Collections;
79import java.util.HashSet;
80import java.util.List;
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +020081import java.util.Set;
Author Namee252a002016-09-26 22:42:24 +053082import java.util.concurrent.TimeUnit;
Ray Milkey2b217142014-12-15 09:24:24 -080083
Author Namee252a002016-09-26 22:42:24 +053084import static java.util.concurrent.TimeUnit.SECONDS;
85import static org.easymock.EasyMock.*;
86import static org.hamcrest.Matchers.*;
Ray Milkey2b217142014-12-15 09:24:24 -080087import static org.junit.Assert.assertThat;
88import static org.junit.Assert.fail;
Ray Milkey43a28222015-02-23 13:57:58 -080089import static org.onosproject.net.intent.IntentTestsMocks.MockIntent;
Ray Milkey2b217142014-12-15 09:24:24 -080090
91/**
92 * Unit tests for Intents REST APIs.
93 */
Ray Milkey9c3d3362015-01-28 10:39:56 -080094public class IntentsResourceTest extends ResourceTest {
Author Namee252a002016-09-26 22:42:24 +053095
96
97 private static final String APPID = "appId";
98 private static final String CRITERIA = "criteria";
99 private static final String DEVICE_ID = "deviceId";
100 private static final String ID = "id";
101 private static final String INSTRUCTIONS = "instructions";
102 private static final String PATHS = "paths";
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +0200103 private static final String INSTALLABLES = "installables";
104 private static final String RESOURCES = "resources";
105 private static final String DEVICE = "device";
106 private static final String PORT = "port";
107 private static final String SRC = "src";
108 private static final String DST = "dst";
Author Namee252a002016-09-26 22:42:24 +0530109 private static final String SELECTOR = "selector";
110 private static final String SPACE = " ";
111 private static final String TREATMENT = "treatment";
112 private static final String TYPE = "type";
113
Ray Milkey2b217142014-12-15 09:24:24 -0800114 final IntentService mockIntentService = createMock(IntentService.class);
Ayaka Koshibec06c89b2015-02-10 19:25:41 -0800115 final CoreService mockCoreService = createMock(CoreService.class);
Author Namee252a002016-09-26 22:42:24 +0530116 final FlowRuleService mockFlowService = createMock(FlowRuleService.class);
Ray Milkey2b217142014-12-15 09:24:24 -0800117 final HashSet<Intent> intents = new HashSet<>();
Author Namee252a002016-09-26 22:42:24 +0530118 final List<org.onosproject.net.intent.Intent> installableIntents = new ArrayList<>();
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800119 private static final ApplicationId APP_ID = new DefaultApplicationId(1, "test");
Ray Milkey2b217142014-12-15 09:24:24 -0800120
Author Namee252a002016-09-26 22:42:24 +0530121 final DeviceId deviceId1 = DeviceId.deviceId("1");
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +0200122 final DeviceId deviceId2 = DeviceId.deviceId("2");
123 final DeviceId deviceId3 = DeviceId.deviceId("3");
124
125 final ConnectPoint connectPoint1 = new ConnectPoint(deviceId1, PortNumber.portNumber(1L));
126 final ConnectPoint connectPoint2 = new ConnectPoint(deviceId2, PortNumber.portNumber(1L));
127 final ConnectPoint connectPoint3 = new ConnectPoint(deviceId2, PortNumber.portNumber(2L));
128 final ConnectPoint connectPoint4 = new ConnectPoint(deviceId3, PortNumber.portNumber(1L));
Author Namee252a002016-09-26 22:42:24 +0530129
130 final TrafficTreatment treatment1 = DefaultTrafficTreatment.builder()
131 .setEthDst(MacAddress.BROADCAST)
132 .build();
133 final TrafficTreatment treatment2 = DefaultTrafficTreatment.builder()
134 .setEthDst(MacAddress.IPV4_MULTICAST)
135 .build();
136
137 final TrafficSelector selector1 = DefaultTrafficSelector.builder()
138 .matchEthType((short) 3)
139 .matchIPProtocol((byte) 9)
140 .build();
141 final TrafficSelector selector2 = DefaultTrafficSelector.builder()
142 .matchEthType((short) 4)
143 .matchIPProtocol((byte) 10)
144 .build();
145
146 final MockFlowEntry flow1 = new MockFlowEntry(deviceId1, 1, treatment1, selector1);
147 final MockFlowEntry flow2 = new MockFlowEntry(deviceId1, 2, treatment2, selector2);
148
149 final MockFlowRule flowRule1 = new MockFlowRule(deviceId1, 1, treatment1, selector1);
150 final MockFlowRule flowRule2 = new MockFlowRule(deviceId1, 2, treatment2, selector2);
151
Ray Milkey2b217142014-12-15 09:24:24 -0800152 private class MockResource implements NetworkResource {
153 int id;
154
155 MockResource(int id) {
156 this.id = id;
157 }
158
Ayaka Koshibec06c89b2015-02-10 19:25:41 -0800159 @Override
Ray Milkey2b217142014-12-15 09:24:24 -0800160 public String toString() {
161 return "Resource " + Integer.toString(id);
162 }
163 }
164
Ray Milkey2b217142014-12-15 09:24:24 -0800165 /**
Author Namee252a002016-09-26 22:42:24 +0530166 * Mock class for a flow entry.
167 */
Ray Milkey634eb172017-04-05 14:48:50 -0700168 private static class MockFlowEntry extends FlowEntryAdapter {
Author Namee252a002016-09-26 22:42:24 +0530169 final DeviceId deviceId;
170 final long baseValue;
171 TrafficTreatment treatment;
172 TrafficSelector selector;
173
174 public MockFlowEntry(DeviceId deviceId, long id,
175 TrafficTreatment treatment,
176 TrafficSelector selector) {
177 this.deviceId = deviceId;
178 this.baseValue = id * 100;
179 this.treatment = treatment;
180 this.selector = selector;
181 }
182
183 @Override
Author Namee252a002016-09-26 22:42:24 +0530184 public long life() {
185 return life(SECONDS);
186 }
187
188 @Override
kalagesa42019542017-03-14 18:00:47 +0530189 public FlowLiveType liveType() {
190 return FlowLiveType.IMMEDIATE;
191 }
192
193 @Override
Author Namee252a002016-09-26 22:42:24 +0530194 public long life(TimeUnit timeUnit) {
195 return SECONDS.convert(baseValue + 11, timeUnit);
196 }
197
198 @Override
199 public long packets() {
200 return baseValue + 22;
201 }
202
203 @Override
204 public long bytes() {
205 return baseValue + 33;
206 }
207
208 @Override
209 public long lastSeen() {
210 return baseValue + 44;
211 }
212
213 @Override
Author Namee252a002016-09-26 22:42:24 +0530214 public FlowId id() {
215 final long id = baseValue + 55;
216 return FlowId.valueOf(id);
217 }
218
219 @Override
220 public GroupId groupId() {
Yi Tsengfa394de2017-02-01 11:26:40 -0800221 return new GroupId(3);
Author Namee252a002016-09-26 22:42:24 +0530222 }
223
224 @Override
225 public short appId() {
226 return 1;
227 }
228
229 @Override
230 public int priority() {
231 return (int) (baseValue + 66);
232 }
233
234 @Override
235 public DeviceId deviceId() {
236 return deviceId;
237 }
238
239 @Override
240 public TrafficSelector selector() {
241 return selector;
242 }
243
244 @Override
245 public TrafficTreatment treatment() {
246 return treatment;
247 }
248
249 @Override
250 public int timeout() {
251 return (int) (baseValue + 77);
252 }
253
Author Namee252a002016-09-26 22:42:24 +0530254
255 @Override
256 public boolean exactMatch(FlowRule rule) {
257 return this.appId() == rule.appId() &&
258 this.deviceId().equals(rule.deviceId()) &&
259 this.id().equals(rule.id()) &&
260 this.treatment.equals(rule.treatment()) &&
261 this.selector().equals(rule.selector());
262 }
263
264 @Override
Author Namee252a002016-09-26 22:42:24 +0530265 public String toString() {
266 return id().id().toString();
267 }
268 }
269
270 /**
271 * Mock class for a flow rule.
272 */
273 private static class MockFlowRule implements FlowRule {
274
275 final DeviceId deviceId;
276 final long baseValue;
277 TrafficTreatment treatment;
278 TrafficSelector selector;
279
280 public MockFlowRule(DeviceId deviceId,
281 long id,
282 TrafficTreatment treatment,
283 TrafficSelector selector) {
284 this.deviceId = deviceId;
285 this.baseValue = id * 100;
286 this.treatment = treatment;
287 this.selector = selector;
288 }
289
290 @Override
291 public FlowId id() {
292 long id = baseValue + 55;
293 return FlowId.valueOf(id);
294 }
295
296 @Override
297 public short appId() {
298 return 1;
299 }
300
301 @Override
302 public GroupId groupId() {
Yi Tsengfa394de2017-02-01 11:26:40 -0800303 return new GroupId(3);
Author Namee252a002016-09-26 22:42:24 +0530304 }
305
306 @Override
307 public int priority() {
308 return 0;
309 }
310
311 @Override
312 public DeviceId deviceId() {
313 return deviceId;
314 }
315
316 @Override
317 public TrafficSelector selector() {
318 return selector;
319 }
320
321 @Override
322 public TrafficTreatment treatment() {
323 return treatment;
324 }
325
326 @Override
327 public int timeout() {
328 return (int) (baseValue + 77);
329 }
330
331 @Override
332 public int hardTimeout() {
333 return 0;
334 }
335
336 @Override
337 public FlowRemoveReason reason() {
338 return FlowRemoveReason.NO_REASON;
339 }
340
341 @Override
342 public boolean isPermanent() {
343 return false;
344 }
345
346 @Override
347 public int tableId() {
348 return 0;
349 }
350
351 @Override
Carmelo Cascone41605742017-06-19 15:46:44 +0900352 public TableId table() {
353 return DEFAULT_TABLE;
354 }
355
356 @Override
Author Namee252a002016-09-26 22:42:24 +0530357 public boolean exactMatch(FlowRule rule) {
358 return false;
359 }
360
361 @Override
362 public FlowRuleExtPayLoad payLoad() {
363 return null;
364 }
365 }
366
367 /**
Ray Milkey2b217142014-12-15 09:24:24 -0800368 * Hamcrest matcher to check that an intent representation in JSON matches
369 * the actual intent.
370 */
371 public static class IntentJsonMatcher extends TypeSafeMatcher<JsonObject> {
372 private final Intent intent;
373 private String reason = "";
374
375 public IntentJsonMatcher(Intent intentValue) {
376 intent = intentValue;
377 }
378
379 @Override
380 public boolean matchesSafely(JsonObject jsonIntent) {
381 // check id
382 final String jsonId = jsonIntent.get("id").asString();
383 if (!jsonId.equals(intent.id().toString())) {
384 reason = "id " + intent.id().toString();
385 return false;
386 }
387
388 // check application id
Ray Milkeyf7cb4012015-07-20 13:01:07 -0700389
Ray Milkey2b217142014-12-15 09:24:24 -0800390 final String jsonAppId = jsonIntent.get("appId").asString();
Ray Milkeyf7cb4012015-07-20 13:01:07 -0700391 final String appId = intent.appId().name();
392 if (!jsonAppId.equals(appId)) {
393 reason = "appId was " + jsonAppId;
Ray Milkey2b217142014-12-15 09:24:24 -0800394 return false;
395 }
396
397 // check intent type
398 final String jsonType = jsonIntent.get("type").asString();
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +0200399 if (!intent.getClass().getSimpleName().equals(jsonType)) {
Ray Milkey2b217142014-12-15 09:24:24 -0800400 reason = "type MockIntent";
401 return false;
402 }
403
Ray Milkey1534f8d2015-05-13 15:42:50 -0700404 // check state field
405 final String jsonState = jsonIntent.get("state").asString();
Jon Halla3fcf672017-03-28 16:53:22 -0700406 if (!"INSTALLED".equals(jsonState)) {
Ray Milkey1534f8d2015-05-13 15:42:50 -0700407 reason = "state INSTALLED";
408 return false;
409 }
410
Ray Milkey2b217142014-12-15 09:24:24 -0800411 // check resources array
412 final JsonArray jsonResources = jsonIntent.get("resources").asArray();
413 if (intent.resources() != null) {
414 if (intent.resources().size() != jsonResources.size()) {
415 reason = "resources array size of " + Integer.toString(intent.resources().size());
416 return false;
417 }
418 for (final NetworkResource resource : intent.resources()) {
419 boolean resourceFound = false;
420 final String resourceString = resource.toString();
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +0200421
422 if (resource instanceof Link) {
423 final Link resourceLink = (Link) resource;
424 MockCodecContext codecContext = new MockCodecContext();
425
426 for (int resourceIndex = 0; resourceIndex < jsonResources.size(); resourceIndex++) {
427 final ObjectNode value;
428 try {
429 value = (ObjectNode) codecContext.mapper()
430 .readTree(jsonResources.get(resourceIndex).toString());
431 } catch (IOException e) {
432 reason = "bad json";
433 return false;
434 }
435 final Link link = codecContext.codec(Link.class).decode(value, codecContext);
436 if (resourceLink.equals(link)) {
437 resourceFound = true;
438 }
439 }
440 } else {
441
442 for (int resourceIndex = 0; resourceIndex < jsonResources.size(); resourceIndex++) {
443 final JsonValue value = jsonResources.get(resourceIndex);
444 if (value.asString().equals(resourceString)) {
445 resourceFound = true;
446 }
Ray Milkey2b217142014-12-15 09:24:24 -0800447 }
448 }
449 if (!resourceFound) {
450 reason = "resource " + resourceString;
451 return false;
452 }
453 }
454 } else if (jsonResources.size() != 0) {
455 reason = "resources array empty";
456 return false;
457 }
458 return true;
459 }
460
461 @Override
462 public void describeTo(Description description) {
463 description.appendText(reason);
464 }
465 }
466
467 /**
468 * Factory to allocate an intent matcher.
469 *
470 * @param intent intent object we are looking for
471 * @return matcher
472 */
473 private static IntentJsonMatcher matchesIntent(Intent intent) {
474 return new IntentJsonMatcher(intent);
475 }
476
477 /**
Author Namee252a002016-09-26 22:42:24 +0530478 * Factory to allocate an IntentRelatedFlows matcher.
479 *
dvaddire95c84ed2017-06-14 15:42:24 +0530480 * @param pathEntries list of path conatining flow entries of a particular intent
Author Namee252a002016-09-26 22:42:24 +0530481 * @param expectedAppId expected app id we are looking for
482 * @return matcher
483 */
484 private static IntentStatsJsonMatcher matchesRelatedFlowEntries(
485 List<List<FlowEntry>> pathEntries,
486 final String expectedAppId) {
487 return new IntentStatsJsonMatcher(pathEntries, expectedAppId);
488 }
489
490 /**
491 * Hamcrest matcher to check that an list of flowEntries in JSON matches
492 * the actual list of flow entries.
493 */
494 public static class IntentStatsJsonMatcher extends
495 TypeSafeMatcher<JsonObject> {
496
497 private final List<List<FlowEntry>> pathEntries;
498 private final String expectedAppId;
499 private String reason = "";
500
501 public IntentStatsJsonMatcher(
502 final List<List<FlowEntry>> pathEntries,
503 final String expectedAppId) {
504 this.pathEntries = pathEntries;
505 this.expectedAppId = expectedAppId;
506 }
507
508 @Override
509 public boolean matchesSafely(JsonObject jsonIntent) {
510 int jsonPathIndex = 0;
511 JsonArray jsonPaths = jsonIntent.get(PATHS).asArray();
512
513 if (pathEntries != null) {
514
rohitsharanfd747602017-01-23 21:57:28 +0530515 if (pathEntries.size() == 0) {
516 reason = "pathEntries array empty";
517 return false;
518 }
519
Author Namee252a002016-09-26 22:42:24 +0530520 if (pathEntries.size() != jsonPaths.size()) {
521 reason = "path entries array size of " +
522 Integer.toString(pathEntries.size());
523 return false;
524 }
525
526 for (List<FlowEntry> flowEntries : pathEntries) {
527 JsonArray jsonFlowEntries = jsonPaths.get(
528 jsonPathIndex++).asArray();
529
530 if (flowEntries.size() != jsonFlowEntries.size()) {
531 reason = "flow entries array size of " +
532 Integer.toString(pathEntries.size());
533
534 return false;
535 }
536
537 int jsonFlowEntryIndex = 0;
538 for (FlowEntry flow : flowEntries) {
539
540 JsonObject jsonFlow = jsonFlowEntries.get(
541 jsonFlowEntryIndex++).asObject();
542
543 String jsonId = jsonFlow.get(ID).asString();
544 String flowId = Long.toString(flow.id().value());
545 if (!jsonId.equals(flowId)) {
546 reason = ID + SPACE + flow.id();
547 return false;
548 }
549
550 // check application id
551 String jsonAppId = jsonFlow.get(APPID).asString();
552 if (!jsonAppId.equals(expectedAppId)) {
553 reason = APPID + SPACE + Short.toString(flow.appId());
554 return false;
555 }
556
557 // check device id
558 String jsonDeviceId =
559 jsonFlow.get(DEVICE_ID).asString();
560
561 if (!jsonDeviceId.equals(flow.deviceId().toString())) {
562 reason = DEVICE_ID + SPACE + flow.deviceId();
563 return false;
564 }
565
566 if (!checkFlowTreatment(flow, jsonFlow)) {
567 return false;
568 }
569
570 if (!checkFlowSelector(flow, jsonFlow)) {
571 return false;
572 }
573
574 }
575
576 }
rohitsharanfd747602017-01-23 21:57:28 +0530577 } else {
Author Namee252a002016-09-26 22:42:24 +0530578 reason = "pathEntries array empty";
579 return false;
580 }
581
582 return true;
583 }
584
585 // check treatment and instructions array.
586 private boolean checkFlowTreatment(FlowEntry flow, JsonObject jsonFlow) {
587
588 if (flow.treatment() != null) {
589 JsonObject jsonTreatment =
590 jsonFlow.get(TREATMENT).asObject();
591 JsonArray jsonInstructions =
592 jsonTreatment.get(INSTRUCTIONS).asArray();
593
594 if (flow.treatment().immediate().size() !=
595 jsonInstructions.size()) {
596 reason = "instructions array size of " +
597 flow.treatment().immediate().size();
598
599 return false;
600 }
601 for (Instruction instruction :
602 flow.treatment().immediate()) {
603 boolean instructionFound = false;
604 for (int instructionIndex = 0;
605 instructionIndex < jsonInstructions.size();
606 instructionIndex++) {
607 String jsonType =
608 jsonInstructions.get(instructionIndex)
609 .asObject().get(TYPE).asString();
610
611 String instructionType =
612 instruction.type().name();
613
614 if (jsonType.equals(instructionType)) {
615 instructionFound = true;
616 }
617 }
618 if (!instructionFound) {
619 reason = INSTRUCTIONS + SPACE + instruction;
620 return false;
621 }
622 }
623 }
624 return true;
625 }
626
627 // check selector and criteria array.
628 private boolean checkFlowSelector(FlowEntry flow, JsonObject jsonFlow) {
629
630 if (flow.selector() != null) {
631 JsonObject jsonTreatment =
632 jsonFlow.get(SELECTOR).asObject();
633
634 JsonArray jsonCriteria =
635 jsonTreatment.get(CRITERIA).asArray();
636
637 if (flow.selector().criteria().size() != jsonCriteria.size()) {
638 reason = CRITERIA + " array size of " +
639 Integer.toString(flow.selector().criteria().size());
640 return false;
641 }
642 for (Criterion criterion : flow.selector().criteria()) {
643 boolean criterionFound = false;
644
645 for (int criterionIndex = 0;
646 criterionIndex < jsonCriteria.size();
647 criterionIndex++) {
648 String jsonType =
649 jsonCriteria.get(criterionIndex)
650 .asObject().get(TYPE).asString();
651 String criterionType = criterion.type().name();
652 if (jsonType.equals(criterionType)) {
653 criterionFound = true;
654 }
655 }
656 if (!criterionFound) {
657 reason = "criterion " + criterion;
658 return false;
659 }
660 }
661 }
662 return true;
663 }
664
665 @Override
666 public void describeTo(Description description) {
667 description.appendText(reason);
668 }
669 }
670
671 /**
Ray Milkey2b217142014-12-15 09:24:24 -0800672 * Hamcrest matcher to check that an intent is represented properly in a JSON
673 * array of intents.
674 */
675 public static class IntentJsonArrayMatcher extends TypeSafeMatcher<JsonArray> {
676 private final Intent intent;
677 private String reason = "";
678
679 public IntentJsonArrayMatcher(Intent intentValue) {
680 intent = intentValue;
681 }
682
683 @Override
684 public boolean matchesSafely(JsonArray json) {
685 boolean intentFound = false;
Antonio Marsico4f68ec92017-03-09 11:16:32 +0100686 final int expectedAttributes = 6;
Ray Milkey2b217142014-12-15 09:24:24 -0800687 for (int jsonIntentIndex = 0; jsonIntentIndex < json.size();
688 jsonIntentIndex++) {
689
690 final JsonObject jsonIntent = json.get(jsonIntentIndex).asObject();
691
692 if (jsonIntent.names().size() != expectedAttributes) {
693 reason = "Found an intent with the wrong number of attributes";
694 return false;
695 }
696
697 final String jsonIntentId = jsonIntent.get("id").asString();
698 if (jsonIntentId.equals(intent.id().toString())) {
699 intentFound = true;
700
701 // We found the correct intent, check attribute values
702 assertThat(jsonIntent, matchesIntent(intent));
703 }
704 }
705 if (!intentFound) {
706 reason = "Intent with id " + intent.id().toString() + " not found";
707 return false;
708 } else {
709 return true;
710 }
711 }
712
713 @Override
714 public void describeTo(Description description) {
715 description.appendText(reason);
716 }
717 }
718
719 /**
720 * Factory to allocate an intent array matcher.
721 *
722 * @param intent intent object we are looking for
723 * @return matcher
724 */
725 private static IntentJsonArrayMatcher hasIntent(Intent intent) {
726 return new IntentJsonArrayMatcher(intent);
727 }
Ray Milkey4f5de002014-12-17 19:26:11 -0800728
Ray Milkeyed0b1662015-02-05 09:34:29 -0800729 /**
730 * Initializes test mocks and environment.
731 */
Ray Milkey2b217142014-12-15 09:24:24 -0800732 @Before
Ray Milkeyed0b1662015-02-05 09:34:29 -0800733 public void setUpTest() {
Ray Milkey2b217142014-12-15 09:24:24 -0800734 expect(mockIntentService.getIntents()).andReturn(intents).anyTimes();
Ray Milkey1534f8d2015-05-13 15:42:50 -0700735 expect(mockIntentService.getIntentState(anyObject()))
736 .andReturn(IntentState.INSTALLED)
737 .anyTimes();
Ray Milkey2b217142014-12-15 09:24:24 -0800738 // Register the services needed for the test
dvaddire95c84ed2017-06-14 15:42:24 +0530739 final CodecManager codecService = new CodecManager();
Ray Milkey2b217142014-12-15 09:24:24 -0800740 codecService.activate();
741 ServiceDirectory testDirectory =
742 new TestServiceDirectory()
743 .add(IntentService.class, mockIntentService)
Author Namee252a002016-09-26 22:42:24 +0530744 .add(FlowRuleService.class, mockFlowService)
Ayaka Koshibec06c89b2015-02-10 19:25:41 -0800745 .add(CodecService.class, codecService)
746 .add(CoreService.class, mockCoreService);
Ray Milkey2b217142014-12-15 09:24:24 -0800747
748 BaseResource.setServiceDirectory(testDirectory);
749
Thomas Vachuska2048c1f2017-05-10 19:32:22 -0700750 MockIdGenerator.cleanBind();
Ray Milkey2b217142014-12-15 09:24:24 -0800751 }
752
Ray Milkeyed0b1662015-02-05 09:34:29 -0800753 /**
754 * Tears down and verifies test mocks and environment.
755 */
Ray Milkey2b217142014-12-15 09:24:24 -0800756 @After
Ray Milkeyed0b1662015-02-05 09:34:29 -0800757 public void tearDownTest() {
Thomas Vachuska2048c1f2017-05-10 19:32:22 -0700758 MockIdGenerator.unbind();
Ray Milkey2b217142014-12-15 09:24:24 -0800759 verify(mockIntentService);
Ray Milkey2b217142014-12-15 09:24:24 -0800760 }
761
762 /**
763 * Tests the result of the rest api GET when there are no intents.
764 */
765 @Test
766 public void testIntentsEmptyArray() {
767 replay(mockIntentService);
Jian Li9d616492016-03-09 10:52:49 -0800768 final WebTarget wt = target();
769 final String response = wt.path("intents").request().get(String.class);
Ray Milkey2b217142014-12-15 09:24:24 -0800770 assertThat(response, is("{\"intents\":[]}"));
771 }
772
773 /**
774 * Tests the result of the rest api GET when intents are defined.
775 */
776 @Test
777 public void testIntentsArray() {
778 replay(mockIntentService);
779
Ray Milkey43a28222015-02-23 13:57:58 -0800780 final Intent intent1 = new MockIntent(1L, Collections.emptyList());
Ray Milkey2b217142014-12-15 09:24:24 -0800781 final HashSet<NetworkResource> resources = new HashSet<>();
782 resources.add(new MockResource(1));
783 resources.add(new MockResource(2));
784 resources.add(new MockResource(3));
Ray Milkey43a28222015-02-23 13:57:58 -0800785 final Intent intent2 = new MockIntent(2L, resources);
Ray Milkey2b217142014-12-15 09:24:24 -0800786
787 intents.add(intent1);
788 intents.add(intent2);
Jian Li9d616492016-03-09 10:52:49 -0800789 final WebTarget wt = target();
790 final String response = wt.path("intents").request().get(String.class);
Ray Milkey2b217142014-12-15 09:24:24 -0800791 assertThat(response, containsString("{\"intents\":["));
792
Jian Li80cfe452016-01-14 16:04:58 -0800793 final JsonObject result = Json.parse(response).asObject();
Ray Milkey2b217142014-12-15 09:24:24 -0800794 assertThat(result, notNullValue());
795
796 assertThat(result.names(), hasSize(1));
797 assertThat(result.names().get(0), is("intents"));
798
799 final JsonArray jsonIntents = result.get("intents").asArray();
800 assertThat(jsonIntents, notNullValue());
801
802 assertThat(jsonIntents, hasIntent(intent1));
803 assertThat(jsonIntents, hasIntent(intent2));
804 }
805
806 /**
807 * Tests the result of a rest api GET for a single intent.
808 */
809 @Test
810 public void testIntentsSingle() {
811 final HashSet<NetworkResource> resources = new HashSet<>();
812 resources.add(new MockResource(1));
813 resources.add(new MockResource(2));
814 resources.add(new MockResource(3));
Ray Milkey43a28222015-02-23 13:57:58 -0800815 final Intent intent = new MockIntent(3L, resources);
Ray Milkey2b217142014-12-15 09:24:24 -0800816
817 intents.add(intent);
818
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800819 expect(mockIntentService.getIntent(Key.of(0, APP_ID)))
Ray Milkey2b217142014-12-15 09:24:24 -0800820 .andReturn(intent)
821 .anyTimes();
Ayaka Koshibec06c89b2015-02-10 19:25:41 -0800822 expect(mockIntentService.getIntent(Key.of("0", APP_ID)))
823 .andReturn(intent)
824 .anyTimes();
Ray Milkey05b169d2015-08-13 14:33:33 -0700825 expect(mockIntentService.getIntent(Key.of(0, APP_ID)))
826 .andReturn(intent)
827 .anyTimes();
828 expect(mockIntentService.getIntent(Key.of("0x0", APP_ID)))
829 .andReturn(null)
830 .anyTimes();
Ray Milkey2b217142014-12-15 09:24:24 -0800831 replay(mockIntentService);
Ray Milkeyf7cb4012015-07-20 13:01:07 -0700832 expect(mockCoreService.getAppId(APP_ID.name()))
Ayaka Koshibec06c89b2015-02-10 19:25:41 -0800833 .andReturn(APP_ID).anyTimes();
834 replay(mockCoreService);
Jian Li9d616492016-03-09 10:52:49 -0800835 final WebTarget wt = target();
Ray Milkey05b169d2015-08-13 14:33:33 -0700836
837 // Test get using key string
Jian Li9d616492016-03-09 10:52:49 -0800838 final String response = wt.path("intents/" + APP_ID.name()
839 + "/0").request().get(String.class);
Jian Li80cfe452016-01-14 16:04:58 -0800840 final JsonObject result = Json.parse(response).asObject();
Ray Milkey2b217142014-12-15 09:24:24 -0800841 assertThat(result, matchesIntent(intent));
Ray Milkey05b169d2015-08-13 14:33:33 -0700842
843 // Test get using numeric value
Jian Li9d616492016-03-09 10:52:49 -0800844 final String responseNumeric = wt.path("intents/" + APP_ID.name()
845 + "/0x0").request().get(String.class);
Jian Li80cfe452016-01-14 16:04:58 -0800846 final JsonObject resultNumeric = Json.parse(responseNumeric).asObject();
Ray Milkey05b169d2015-08-13 14:33:33 -0700847 assertThat(resultNumeric, matchesIntent(intent));
Ray Milkey2b217142014-12-15 09:24:24 -0800848 }
849
850 /**
Author Namee252a002016-09-26 22:42:24 +0530851 * Tests the result of a rest api GET for related flows for single intent.
852 */
853 @Test
854 public void testRelatedFlowsForIntents() {
855 List<FlowEntry> flowEntries = new ArrayList<>();
856 flowEntries.add(flow1);
857 flowEntries.add(flow2);
858 List<List<FlowEntry>> paths = new ArrayList<>();
859 paths.add(flowEntries);
860 List<FlowRule> flowRules = new ArrayList<>();
861 flowRules.add(flowRule1);
862 flowRules.add(flowRule2);
863 FlowRuleIntent flowRuleIntent = new FlowRuleIntent(
864 APP_ID,
865 flowRules,
866 new HashSet<NetworkResource>());
867 Intent intent = new MockIntent(3L);
868 installableIntents.add(flowRuleIntent);
869 intents.add(intent);
870
871 expect(mockIntentService.getIntent(Key.of(0, APP_ID)))
872 .andReturn(intent)
873 .anyTimes();
874 expect(mockIntentService.getIntent(Key.of("0", APP_ID)))
875 .andReturn(intent)
876 .anyTimes();
877 expect(mockIntentService.getIntent(Key.of(0, APP_ID)))
878 .andReturn(intent)
879 .anyTimes();
880 expect(mockIntentService.getIntent(Key.of("0x0", APP_ID)))
881 .andReturn(null)
882 .anyTimes();
883 expect(mockIntentService.getInstallableIntents(intent.key()))
884 .andReturn(installableIntents)
885 .anyTimes();
886 replay(mockIntentService);
887
888 expect(mockFlowService.getFlowEntries(deviceId1))
889 .andReturn(flowEntries).anyTimes();
890 replay(mockFlowService);
891
892 expect(mockCoreService.getAppId(APP_ID.name()))
893 .andReturn(APP_ID).anyTimes();
894 expect(mockCoreService.getAppId(APP_ID.id()))
895 .andReturn(APP_ID).anyTimes();
896 replay(mockCoreService);
897
898 final WebTarget wt = target();
899
900 // Test get using key string
901 final String response = wt.path("intents/relatedflows/" + APP_ID.name()
902 + "/0").request().get(String.class);
903 final JsonObject result = Json.parse(response).asObject();
904 assertThat(result, matchesRelatedFlowEntries(paths, APP_ID.name()));
905
906 // Test get using numeric value
907 final String responseNumeric = wt.path("intents/relatedflows/" + APP_ID.name()
908 + "/0x0").request().get(String.class);
909 final JsonObject resultNumeric = Json.parse(responseNumeric).asObject();
910 assertThat(resultNumeric, matchesRelatedFlowEntries(paths, APP_ID.name()));
911 }
912
913 /**
Rafal Szaleckif97e0ed2017-06-01 13:07:18 +0200914 * Tests the result of a rest api GET for intent installables.
915 */
916 @Test
917 public void testIntentInstallables() {
918
919 Link link1 = DefaultLink.builder()
920 .type(Link.Type.DIRECT)
921 .providerId(ProviderId.NONE)
922 .src(connectPoint1)
923 .dst(connectPoint2)
924 .build();
925
926 Link link2 = DefaultLink.builder()
927 .type(Link.Type.DIRECT)
928 .providerId(ProviderId.NONE)
929 .src(connectPoint3)
930 .dst(connectPoint4)
931 .build();
932
933 Set<NetworkResource> resources = new HashSet<>();
934 resources.add(link1);
935 resources.add(link2);
936
937 FlowRuleIntent flowRuleIntent = new FlowRuleIntent(
938 APP_ID,
939 new ArrayList<>(),
940 resources);
941
942 Intent intent = new MockIntent(MockIntent.nextId());
943 Long intentId = intent.id().id();
944 installableIntents.add(flowRuleIntent);
945 intents.add(intent);
946
947 expect(mockIntentService.getIntent(Key.of(intentId, APP_ID)))
948 .andReturn(intent)
949 .anyTimes();
950 expect(mockIntentService.getIntent(Key.of(intentId.toString(), APP_ID)))
951 .andReturn(intent)
952 .anyTimes();
953 expect(mockIntentService.getIntent(Key.of(intentId, APP_ID)))
954 .andReturn(intent)
955 .anyTimes();
956 expect(mockIntentService.getIntent(Key.of(Long.toHexString(intentId), APP_ID)))
957 .andReturn(null)
958 .anyTimes();
959 expect(mockIntentService.getInstallableIntents(intent.key()))
960 .andReturn(installableIntents)
961 .anyTimes();
962 replay(mockIntentService);
963
964 replay(mockFlowService);
965
966 expect(mockCoreService.getAppId(APP_ID.name()))
967 .andReturn(APP_ID).anyTimes();
968 expect(mockCoreService.getAppId(APP_ID.id()))
969 .andReturn(APP_ID).anyTimes();
970 replay(mockCoreService);
971
972 final WebTarget wt = target();
973
974 // Test get using key string
975 final String response = wt.path("intents/installables/" + APP_ID.name()
976 + "/" + intentId).request().get(String.class);
977 final JsonObject result = Json.parse(response).asObject();
978 assertThat(result.get(INSTALLABLES).asArray(), hasIntent(flowRuleIntent));
979
980 // Test get using numeric value
981 final String responseNumeric = wt.path("intents/installables/" + APP_ID.name()
982 + "/" + Long.toHexString(intentId)).request().get(String.class);
983 final JsonObject resultNumeric = Json.parse(responseNumeric).asObject();
984 assertThat(resultNumeric.get(INSTALLABLES).asArray(), hasIntent(flowRuleIntent));
985 }
986
987 /**
Ray Milkey2b217142014-12-15 09:24:24 -0800988 * Tests that a fetch of a non-existent intent object throws an exception.
989 */
990 @Test
991 public void testBadGet() {
992
Ray Milkeyf9af43c2015-02-09 16:45:48 -0800993 expect(mockIntentService.getIntent(Key.of(0, APP_ID)))
Ray Milkey2b217142014-12-15 09:24:24 -0800994 .andReturn(null)
995 .anyTimes();
996 replay(mockIntentService);
997
Jian Li9d616492016-03-09 10:52:49 -0800998 WebTarget wt = target();
Ray Milkey2b217142014-12-15 09:24:24 -0800999 try {
Jian Li9d616492016-03-09 10:52:49 -08001000 wt.path("intents/0").request().get(String.class);
Ray Milkey2b217142014-12-15 09:24:24 -08001001 fail("Fetch of non-existent intent did not throw an exception");
Jian Li9d616492016-03-09 10:52:49 -08001002 } catch (NotFoundException ex) {
Ray Milkey2b217142014-12-15 09:24:24 -08001003 assertThat(ex.getMessage(),
Jian Li9d616492016-03-09 10:52:49 -08001004 containsString("HTTP 404 Not Found"));
Ray Milkey2b217142014-12-15 09:24:24 -08001005 }
1006 }
Ray Milkeyb82c42b2015-06-30 09:42:20 -07001007
1008 /**
1009 * Tests creating an intent with POST.
1010 */
1011 @Test
1012 public void testPost() {
Ray Milkeyf7cb4012015-07-20 13:01:07 -07001013 ApplicationId testId = new DefaultApplicationId(2, "myApp");
1014 expect(mockCoreService.getAppId("myApp"))
1015 .andReturn(testId);
Ray Milkeyb82c42b2015-06-30 09:42:20 -07001016 replay(mockCoreService);
1017
1018 mockIntentService.submit(anyObject());
1019 expectLastCall();
1020 replay(mockIntentService);
1021
1022 InputStream jsonStream = IntentsResourceTest.class
1023 .getResourceAsStream("post-intent.json");
Jian Li9d616492016-03-09 10:52:49 -08001024 WebTarget wt = target();
Ray Milkeyb82c42b2015-06-30 09:42:20 -07001025
Jian Li9d616492016-03-09 10:52:49 -08001026 Response response = wt.path("intents")
1027 .request(MediaType.APPLICATION_JSON_TYPE)
1028 .post(Entity.json(jsonStream));
Ray Milkeyb82c42b2015-06-30 09:42:20 -07001029 assertThat(response.getStatus(), is(HttpURLConnection.HTTP_CREATED));
1030 String location = response.getLocation().getPath();
Ray Milkey8d076402015-08-31 15:43:18 -07001031 assertThat(location, Matchers.startsWith("/intents/myApp/"));
Ray Milkeyb82c42b2015-06-30 09:42:20 -07001032 }
Ray Milkey7b158512015-07-21 16:32:43 -07001033
1034 /**
Ray Milkey05b169d2015-08-13 14:33:33 -07001035 * Tests creating an intent with POST and illegal JSON.
1036 */
1037 @Test
1038 public void testBadPost() {
1039 replay(mockCoreService);
1040 replay(mockIntentService);
1041
1042 String json = "this is invalid!";
Jian Li9d616492016-03-09 10:52:49 -08001043 WebTarget wt = target();
Ray Milkey05b169d2015-08-13 14:33:33 -07001044
Jian Li9d616492016-03-09 10:52:49 -08001045 Response response = wt.path("intents")
1046 .request(MediaType.APPLICATION_JSON_TYPE)
1047 .post(Entity.json(json));
Ray Milkey05b169d2015-08-13 14:33:33 -07001048 assertThat(response.getStatus(), is(HttpURLConnection.HTTP_BAD_REQUEST));
1049 }
1050
1051 /**
Ray Milkey7b158512015-07-21 16:32:43 -07001052 * Tests removing an intent with DELETE.
1053 */
1054 @Test
1055 public void testRemove() {
1056 final HashSet<NetworkResource> resources = new HashSet<>();
1057 resources.add(new MockResource(1));
1058 resources.add(new MockResource(2));
1059 resources.add(new MockResource(3));
1060 final Intent intent = new MockIntent(3L, resources);
1061 final ApplicationId appId = new DefaultApplicationId(2, "app");
1062 IntentService fakeManager = new FakeIntentManager();
1063
1064 expect(mockCoreService.getAppId("app"))
1065 .andReturn(appId).once();
1066 replay(mockCoreService);
1067
1068 mockIntentService.withdraw(anyObject());
1069 expectLastCall().andDelegateTo(fakeManager).once();
1070 expect(mockIntentService.getIntent(Key.of(2, appId)))
1071 .andReturn(intent)
1072 .once();
1073 expect(mockIntentService.getIntent(Key.of("0x2", appId)))
1074 .andReturn(null)
1075 .once();
1076
1077 mockIntentService.addListener(anyObject());
1078 expectLastCall().andDelegateTo(fakeManager).once();
1079 mockIntentService.removeListener(anyObject());
1080 expectLastCall().andDelegateTo(fakeManager).once();
1081
1082 replay(mockIntentService);
1083
Jian Li9d616492016-03-09 10:52:49 -08001084 WebTarget wt = target();
Ray Milkey7b158512015-07-21 16:32:43 -07001085
Jian Li9d616492016-03-09 10:52:49 -08001086 Response response = wt.path("intents/app/0x2")
Ray Milkey7c251822016-04-06 17:38:25 -07001087 .request(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)
Jian Li9d616492016-03-09 10:52:49 -08001088 .delete();
Ray Milkey7b158512015-07-21 16:32:43 -07001089 assertThat(response.getStatus(), is(HttpURLConnection.HTTP_NO_CONTENT));
1090 }
Ray Milkey05b169d2015-08-13 14:33:33 -07001091
1092 /**
1093 * Tests removal of a non existent intent with DELETE.
1094 */
1095 @Test
1096 public void testBadRemove() {
1097 final ApplicationId appId = new DefaultApplicationId(2, "app");
1098
1099 expect(mockCoreService.getAppId("app"))
1100 .andReturn(appId).once();
1101 replay(mockCoreService);
1102
1103 expect(mockIntentService.getIntent(Key.of(2, appId)))
1104 .andReturn(null)
1105 .once();
1106 expect(mockIntentService.getIntent(Key.of("0x2", appId)))
1107 .andReturn(null)
1108 .once();
1109
1110 replay(mockIntentService);
1111
Jian Li9d616492016-03-09 10:52:49 -08001112 WebTarget wt = target();
Ray Milkey05b169d2015-08-13 14:33:33 -07001113
Jian Li9d616492016-03-09 10:52:49 -08001114 Response response = wt.path("intents/app/0x2")
Ray Milkey7c251822016-04-06 17:38:25 -07001115 .request(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)
Jian Li9d616492016-03-09 10:52:49 -08001116 .delete();
Ray Milkey05b169d2015-08-13 14:33:33 -07001117 assertThat(response.getStatus(), is(HttpURLConnection.HTTP_NO_CONTENT));
1118 }
1119
dvaddire95c84ed2017-06-14 15:42:24 +05301120 @Test
1121 public void testIntentsMiniSummary() {
1122 final Intent intent1 = new MockIntent(1L, Collections.emptyList());
1123 final HashSet<NetworkResource> resources = new HashSet<>();
1124 resources.add(new MockResource(1));
1125 resources.add(new MockResource(2));
1126 resources.add(new MockResource(3));
1127 final Intent intent2 = new MockIntent(2L, resources);
1128 intents.add(intent1);
1129 intents.add(intent2);
1130 final WebTarget wt = target();
1131 replay(mockIntentService);
1132 final String response = wt.path("intents/minisummary").request().get(String.class);
1133 assertThat(response, containsString("{\"All\":{"));
1134 final JsonObject result = Json.parse(response).asObject();
1135 assertThat(result, notNullValue());
1136 assertThat(result.names(), hasSize(2));
1137 assertThat(result.names().get(0), containsString("All"));
1138 JsonObject jsonIntents = (JsonObject) result.get("All");
1139 assertThat(jsonIntents, notNullValue());
1140 assertThat(jsonIntents.get("total").toString(), containsString("2"));
1141 jsonIntents = (JsonObject) result.get("Mock");
1142 assertThat(jsonIntents, notNullValue());
1143 assertThat(jsonIntents.get("installed").toString(), containsString("2"));
1144 }
Ray Milkey2b217142014-12-15 09:24:24 -08001145}
dvaddire95c84ed2017-06-14 15:42:24 +05301146