2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.drools.apps.controller.usecases.step;
23 import static org.assertj.core.api.Assertions.assertThat;
24 import static org.assertj.core.api.Assertions.assertThatCode;
25 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertFalse;
28 import static org.junit.Assert.assertSame;
29 import static org.junit.Assert.assertTrue;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyString;
32 import static org.mockito.Mockito.mock;
33 import static org.mockito.Mockito.never;
34 import static org.mockito.Mockito.times;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when;
38 import java.util.HashMap;
39 import java.util.List;
41 import java.util.TreeMap;
42 import java.util.UUID;
43 import java.util.concurrent.BlockingQueue;
44 import java.util.concurrent.CompletableFuture;
45 import java.util.concurrent.ForkJoinPool;
46 import java.util.concurrent.LinkedBlockingQueue;
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.mockito.Mock;
50 import org.mockito.MockitoAnnotations;
51 import org.onap.aai.domain.yang.CloudRegion;
52 import org.onap.aai.domain.yang.GenericVnf;
53 import org.onap.aai.domain.yang.ModelVer;
54 import org.onap.aai.domain.yang.ServiceInstance;
55 import org.onap.aai.domain.yang.Tenant;
56 import org.onap.aai.domain.yang.Vserver;
57 import org.onap.policy.aai.AaiCqResponse;
58 import org.onap.policy.common.utils.coder.StandardCoderObject;
59 import org.onap.policy.controlloop.VirtualControlLoopEvent;
60 import org.onap.policy.controlloop.actor.aai.AaiGetPnfOperation;
61 import org.onap.policy.controlloop.actor.aai.AaiGetTenantOperation;
62 import org.onap.policy.controlloop.actorserviceprovider.ActorService;
63 import org.onap.policy.controlloop.actorserviceprovider.Operation;
64 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
65 import org.onap.policy.controlloop.actorserviceprovider.OperationProperties;
66 import org.onap.policy.controlloop.actorserviceprovider.Operator;
67 import org.onap.policy.controlloop.actorserviceprovider.TargetType;
68 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
69 import org.onap.policy.controlloop.actorserviceprovider.spi.Actor;
70 import org.onap.policy.controlloop.eventmanager.StepContext;
71 import org.onap.policy.drools.apps.controller.usecases.UsecasesConstants;
73 public class Step2Test {
74 private static final UUID REQ_ID = UUID.randomUUID();
75 private static final String POLICY_ACTOR = "my-actor";
76 private static final String POLICY_OPERATION = "my-operation";
77 private static final String MY_TARGET = "my-target";
78 private static final String PAYLOAD_KEY = "payload-key";
79 private static final String PAYLOAD_VALUE = "payload-value";
80 private static final String NO_SLASH = "noslash";
81 private static final String ONE_SLASH = "/one";
84 private Operator policyOperator;
86 private Operation policyOperation;
88 private Actor policyActor;
90 private ActorService actors;
92 private StepContext stepContext;
94 private AaiCqResponse aaicq;
96 private CompletableFuture<OperationOutcome> future;
97 private Map<String, String> payload;
98 private VirtualControlLoopEvent event;
99 private BlockingQueue<OperationOutcome> starts;
100 private BlockingQueue<OperationOutcome> completions;
101 private ControlLoopOperationParams params;
108 public void setUp() {
109 MockitoAnnotations.initMocks(this);
111 future = new CompletableFuture<>();
113 // configure policy operation
114 when(actors.getActor(POLICY_ACTOR)).thenReturn(policyActor);
115 when(policyActor.getOperator(POLICY_OPERATION)).thenReturn(policyOperator);
116 when(policyOperator.buildOperation(any())).thenReturn(policyOperation);
117 when(policyOperation.start()).thenReturn(future);
119 when(policyOperation.getPropertyNames()).thenReturn(List.of());
121 when(stepContext.getProperty(AaiCqResponse.CONTEXT_KEY)).thenReturn(aaicq);
123 payload = Map.of(PAYLOAD_KEY, PAYLOAD_VALUE);
125 event = new VirtualControlLoopEvent();
126 event.setRequestId(REQ_ID);
128 starts = new LinkedBlockingQueue<>();
129 completions = new LinkedBlockingQueue<>();
131 Map<String, String> entityIds = new HashMap<>();
133 params = ControlLoopOperationParams.builder().actor(POLICY_ACTOR).actorService(actors)
134 .completeCallback(completions::add).executor(ForkJoinPool.commonPool())
135 .operation(POLICY_OPERATION).payload(new TreeMap<>(payload)).startCallback(starts::add)
136 .targetType(TargetType.VM).targetEntityIds(entityIds)
137 .requestId(REQ_ID).build();
139 step = new Step2(stepContext, params, event);
144 public void testConstructor() {
145 assertSame(stepContext, step.stepContext);
146 assertSame(event, step.event);
147 assertSame(actors, step.getParams().getActorService());
151 public void testConstructorStep2() {
152 step = new Step2(step, "actorB", "operationB");
153 assertSame(stepContext, step.stepContext);
154 assertSame(event, step.event);
156 assertEquals("actorB", step.getActorName());
157 assertEquals("operationB", step.getOperationName());
158 assertSame(actors, step.getParams().getActorService());
162 public void testAcceptsEvent() {
163 // it's a policy step, thus it accepts events
164 assertTrue(step.acceptsEvent());
166 step = new Step2(step, "actorB", "operationB");
168 // it's not a policy step, thus it does not accept events
169 assertFalse(step.acceptsEvent());
173 public void testSuccess() {
174 assertThatCode(() -> step.success(null)).doesNotThrowAnyException();
178 public void testGetPropertyNames() {
179 // empty property list
180 assertThat(step.getPropertyNames()).isEmpty();
182 // try with non-empty list
183 when(policyOperation.getPropertyNames()).thenReturn(List.of("propA", "propB"));
184 assertThat(step.getPropertyNames()).isEqualTo(List.of("propA", "propB"));
188 public void testSetProperties() {
189 CloudRegion cloudRegion = new CloudRegion();
190 when(aaicq.getDefaultCloudRegion()).thenReturn(cloudRegion);
192 Tenant tenant = new Tenant();
193 when(aaicq.getDefaultTenant()).thenReturn(tenant);
195 when(policyOperation.getPropertyNames()).thenReturn(
196 List.of(OperationProperties.AAI_DEFAULT_CLOUD_REGION, OperationProperties.AAI_DEFAULT_TENANT));
198 step.setProperties();
200 // should have been exactly two properties set
201 verify(policyOperation, times(2)).setProperty(any(), any());
202 verify(policyOperation).setProperty(OperationProperties.AAI_DEFAULT_CLOUD_REGION, cloudRegion);
203 verify(policyOperation).setProperty(OperationProperties.AAI_DEFAULT_TENANT, tenant);
207 * Tests setProperties() when the property is unknown.
210 public void testSetPropertiesUnknown() {
211 when(policyOperation.getPropertyNames()).thenReturn(List.of("unknown-property"));
213 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
214 .withMessage("unknown property unknown-property needed by my-actor.my-operation");
218 public void testLoadCloudRegion_testGetCloudRegion() {
219 CloudRegion data = new CloudRegion();
220 when(aaicq.getDefaultCloudRegion()).thenReturn(data);
221 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.AAI_DEFAULT_CLOUD_REGION));
223 step.setProperties();
224 verify(policyOperation).setProperty(OperationProperties.AAI_DEFAULT_CLOUD_REGION, data);
226 when(aaicq.getDefaultCloudRegion()).thenReturn(null);
227 assertThatIllegalArgumentException().isThrownBy(() -> step.getCloudRegion())
228 .withMessageContaining("missing default cloud region in A&AI response");
232 public void testLoadTenant_testGetTenant() {
233 Tenant data = new Tenant();
234 when(aaicq.getDefaultTenant()).thenReturn(data);
235 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.AAI_DEFAULT_TENANT));
237 step.setProperties();
238 verify(policyOperation).setProperty(OperationProperties.AAI_DEFAULT_TENANT, data);
240 when(aaicq.getDefaultTenant()).thenReturn(null);
241 assertThatIllegalArgumentException().isThrownBy(() -> step.getTenant())
242 .withMessageContaining("missing default tenant in A&AI response");
246 public void testLoadPnf_testGetPnf() {
247 StandardCoderObject data = new StandardCoderObject();
248 when(stepContext.getProperty(OperationProperties.AAI_TARGET_ENTITY)).thenReturn(MY_TARGET);
249 when(stepContext.getProperty(AaiGetPnfOperation.getKey(MY_TARGET))).thenReturn(data);
250 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.AAI_PNF));
252 step.setProperties();
253 verify(policyOperation).setProperty(OperationProperties.AAI_PNF, data);
255 when(stepContext.getProperty(AaiGetPnfOperation.getKey(MY_TARGET))).thenReturn(null);
256 assertThatIllegalArgumentException().isThrownBy(() -> step.getPnf())
257 .withMessageContaining("missing PNF for my-target");
261 public void testLoadResourceVnf_testGetResourceVnf() {
262 params.getTargetEntityIds().put(Step2.TARGET_RESOURCE_ID, "my-resource");
263 GenericVnf data = new GenericVnf();
264 when(aaicq.getGenericVnfByModelInvariantId("my-resource")).thenReturn(data);
265 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.AAI_RESOURCE_VNF));
267 step.setProperties();
268 verify(policyOperation).setProperty(OperationProperties.AAI_RESOURCE_VNF, data);
270 when(aaicq.getGenericVnfByModelInvariantId("my-resource")).thenReturn(null);
271 assertThatIllegalArgumentException().isThrownBy(() -> step.getResourceVnf())
272 .withMessageContaining("missing VNF for my-resource");
274 // missing resource ID
275 params.getTargetEntityIds().put(Step2.TARGET_RESOURCE_ID, null);
276 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
277 .withMessageContaining("missing Target resource ID");
279 // missing target entity IDs
280 params = params.toBuilder().targetEntityIds(null).build();
281 step = new Step2(stepContext, params, event);
283 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
284 .withMessageContaining(Step2.TARGET_INFO_MSG);
288 public void testLoadService_testGetService() {
289 ServiceInstance data = new ServiceInstance();
290 when(aaicq.getServiceInstance()).thenReturn(data);
291 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.AAI_SERVICE));
293 step.setProperties();
294 verify(policyOperation).setProperty(OperationProperties.AAI_SERVICE, data);
296 when(aaicq.getServiceInstance()).thenReturn(null);
297 assertThatIllegalArgumentException().isThrownBy(() -> step.getService())
298 .withMessageContaining("missing service instance in A&AI response");
302 public void testLoadServiceModel_testGetServiceModel() {
303 ServiceInstance service = new ServiceInstance();
304 service.setModelVersionId("my-service-version");
305 when(aaicq.getServiceInstance()).thenReturn(service);
307 ModelVer data = new ModelVer();
308 when(aaicq.getModelVerByVersionId("my-service-version")).thenReturn(data);
309 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.AAI_SERVICE_MODEL));
311 step.setProperties();
312 verify(policyOperation).setProperty(OperationProperties.AAI_SERVICE_MODEL, data);
314 when(aaicq.getModelVerByVersionId("my-service-version")).thenReturn(null);
315 assertThatIllegalArgumentException().isThrownBy(() -> step.getServiceModel())
316 .withMessageContaining("missing model version for service in A&AI response");
318 service.setModelVersionId(null);
319 assertThatIllegalArgumentException().isThrownBy(() -> step.getServiceModel())
320 .withMessageContaining("missing service model version ID in A&AI response");
322 when(aaicq.getServiceInstance()).thenReturn(null);
323 assertThatIllegalArgumentException().isThrownBy(() -> step.getServiceModel())
324 .withMessageContaining("missing service instance in A&AI response");
328 public void testGetVserver() {
329 Vserver vserver = new Vserver();
330 when(aaicq.getVserver()).thenReturn(vserver);
332 assertSame(vserver, step.getVServer());
334 when(aaicq.getVserver()).thenReturn(null);
335 assertThatIllegalArgumentException().isThrownBy(() -> step.getVServer())
336 .withMessageContaining("missing vserver in A&AI response");
340 public void testGetTargetEntity() {
341 when(stepContext.getProperty(OperationProperties.AAI_TARGET_ENTITY)).thenReturn(MY_TARGET);
343 assertEquals(MY_TARGET, step.getTargetEntity());
345 when(stepContext.getProperty(OperationProperties.AAI_TARGET_ENTITY)).thenReturn(null);
346 assertThatIllegalArgumentException().isThrownBy(() -> step.getTargetEntity())
347 .withMessageContaining("missing A&AI target entity");
351 public void testLoadVnf_testGetVnf() {
352 params.getTargetEntityIds().put(Step2.TARGET_MODEL_INVARIANT_ID, "my-model-invariant");
353 GenericVnf data = new GenericVnf();
354 when(aaicq.getGenericVnfByVfModuleModelInvariantId("my-model-invariant")).thenReturn(data);
355 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.AAI_VNF));
357 step.setProperties();
358 verify(policyOperation).setProperty(OperationProperties.AAI_VNF, data);
360 when(aaicq.getGenericVnfByVfModuleModelInvariantId("my-model-invariant")).thenReturn(null);
361 assertThatIllegalArgumentException().isThrownBy(() -> step.getVnf())
362 .withMessageContaining("missing generic VNF in A&AI response for my-model-invariant");
364 // missing model invariant ID
365 params.getTargetEntityIds().put(Step2.TARGET_MODEL_INVARIANT_ID, null);
366 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
367 .withMessageContaining("missing modelInvariantId");
370 params = params.toBuilder().targetEntityIds(null).build();
371 step = new Step2(stepContext, params, event);
373 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
374 .withMessageContaining(Step2.TARGET_INFO_MSG);
378 public void testLoadVnfModel_testGetVnfModel() {
379 params.getTargetEntityIds().put(Step2.TARGET_MODEL_INVARIANT_ID, "my-model-invariant");
380 GenericVnf vnf = new GenericVnf();
381 when(aaicq.getGenericVnfByVfModuleModelInvariantId("my-model-invariant")).thenReturn(vnf);
383 vnf.setModelVersionId("my-vnf-model-version-id");
384 ModelVer data = new ModelVer();
385 when(aaicq.getModelVerByVersionId("my-vnf-model-version-id")).thenReturn(data);
386 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.AAI_VNF_MODEL));
388 step.setProperties();
389 verify(policyOperation).setProperty(OperationProperties.AAI_VNF_MODEL, data);
391 when(aaicq.getModelVerByVersionId("my-vnf-model-version-id")).thenReturn(null);
392 assertThatIllegalArgumentException().isThrownBy(() -> step.getVnfModel())
393 .withMessageContaining("missing model version for generic VNF in A&AI response");
395 vnf.setModelVersionId(null);
396 assertThatIllegalArgumentException().isThrownBy(() -> step.getVnfModel())
397 .withMessageContaining("missing model version ID for generic VNF in A&AI response");
401 public void testLoadVserverLink_testGetVserverLink() {
402 event.setAai(Map.of(Step2.VSERVER_VSERVER_NAME, "vserverB"));
404 StandardCoderObject tenant = mock(StandardCoderObject.class);
405 when(stepContext.getProperty(AaiGetTenantOperation.getKey("vserverB"))).thenReturn(tenant);
407 when(tenant.getString("result-data", 0, "resource-link")).thenReturn("/aai/v7/some/link/bbb");
409 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.AAI_VSERVER_LINK));
411 step.setProperties();
412 verify(policyOperation).setProperty(OperationProperties.AAI_VSERVER_LINK, "/some/link/bbb");
414 // missing resource link
415 when(tenant.getString("result-data", 0, "resource-link")).thenReturn(null);
416 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
417 .withMessageContaining("missing tenant data resource-link");
419 // missing tenant data
420 when(stepContext.getProperty(AaiGetTenantOperation.getKey("vserverB"))).thenReturn(null);
421 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
422 .withMessageContaining("missing tenant data for");
424 // empty vserver name
425 event.setAai(Map.of(Step2.VSERVER_VSERVER_NAME, ""));
426 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
427 .withMessageContaining("missing vserver.vserver-name");
429 // missing vserver name
430 event.setAai(Map.of());
431 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
432 .withMessageContaining("missing vserver.vserver-name");
436 public void testLoadVfCount_testGetVfCount() {
437 params.getTargetEntityIds().put(Step2.TARGET_MODEL_CUSTOMIZATION_ID, "vf-count-customization");
438 params.getTargetEntityIds().put(Step2.TARGET_MODEL_INVARIANT_ID, "vf-count-invariant");
439 params.getTargetEntityIds().put(Step2.TARGET_MODEL_VERSION_ID, "vf-count-version");
440 when(aaicq.getVfModuleCount("vf-count-customization", "vf-count-invariant", "vf-count-version")).thenReturn(11);
441 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.DATA_VF_COUNT));
443 step.setProperties();
444 verify(policyOperation).setProperty(OperationProperties.DATA_VF_COUNT, 11);
446 // missing model version id
447 params.getTargetEntityIds().put(Step2.TARGET_MODEL_VERSION_ID, null);
448 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
449 .withMessageContaining("missing target modelVersionId");
451 // missing model invariant id
452 params.getTargetEntityIds().put(Step2.TARGET_MODEL_INVARIANT_ID, null);
453 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
454 .withMessageContaining("missing target modelInvariantId");
456 // missing model customization id
457 params.getTargetEntityIds().put(Step2.TARGET_MODEL_CUSTOMIZATION_ID, null);
458 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
459 .withMessageContaining("missing target modelCustomizationId");
462 params = params.toBuilder().targetEntityIds(null).build();
463 step = new Step2(stepContext, params, event);
465 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties())
466 .withMessageContaining(Step2.TARGET_INFO_MSG);
468 // get it from the step context
469 when(stepContext.contains(OperationProperties.DATA_VF_COUNT)).thenReturn(true);
470 when(stepContext.getProperty(OperationProperties.DATA_VF_COUNT)).thenReturn(22);
471 step.setProperties();
472 verify(policyOperation).setProperty(OperationProperties.DATA_VF_COUNT, 22);
476 public void testLoadEnrichment_testGetEnrichment() {
477 event.setAai(Map.of("bandwidth", "bandwidth-value"));
478 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.ENRICHMENT_BANDWIDTH));
480 step.setProperties();
481 verify(policyOperation).setProperty(OperationProperties.ENRICHMENT_BANDWIDTH, "bandwidth-value");
483 // missing enrichment data
484 event.setAai(Map.of());
485 assertThatIllegalArgumentException().isThrownBy(() -> step.setProperties());
489 public void testLoadAdditionalEventParams_testGetAdditionalEventParams() {
490 Map<String, String> data = Map.of("addA", "add-valueA", "addB", "add-valueB");
491 event.setAdditionalEventParams(data);
492 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.EVENT_ADDITIONAL_PARAMS));
494 step.setProperties();
495 verify(policyOperation).setProperty(OperationProperties.EVENT_ADDITIONAL_PARAMS, data);
499 public void testLoadEventPayload_testGetEventPayload() {
500 event.setPayload("some-event-payload");
501 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.EVENT_PAYLOAD));
503 step.setProperties();
504 verify(policyOperation).setProperty(OperationProperties.EVENT_PAYLOAD, "some-event-payload");
508 public void testLoadOptCdsGrpcAaiProperties() {
509 when(policyOperation.getPropertyNames()).thenReturn(List.of(OperationProperties.OPT_CDS_GRPC_AAI_PROPERTIES));
511 step.setProperties();
512 verify(policyOperation, never()).setProperty(any(), anyString());
516 public void testLoadDefaultGenericVnf_testGetDefaultGenericVnf() {
517 GenericVnf data = new GenericVnf();
518 when(aaicq.getDefaultGenericVnf()).thenReturn(data);
519 when(policyOperation.getPropertyNames()).thenReturn(List.of(UsecasesConstants.AAI_DEFAULT_GENERIC_VNF));
521 step.setProperties();
522 verify(policyOperation).setProperty(UsecasesConstants.AAI_DEFAULT_GENERIC_VNF, data);
524 when(aaicq.getDefaultGenericVnf()).thenReturn(null);
525 assertThatIllegalArgumentException().isThrownBy(() -> step.getDefaultGenericVnf())
526 .withMessageContaining("missing generic VNF in A&AI response");
530 public void testGetCustomQueryData() {
531 assertSame(aaicq, step.getCustomQueryData());
533 when(stepContext.getProperty(AaiCqResponse.CONTEXT_KEY)).thenReturn(null);
535 assertThatIllegalArgumentException().isThrownBy(() -> step.getCustomQueryData())
536 .withMessage("missing custom query data for my-actor.my-operation");
540 public void testVerifyNotNull() {
541 assertThatCode(() -> step.verifyNotNull("verifyA", "verify-value-A")).doesNotThrowAnyException();
543 assertThatIllegalArgumentException().isThrownBy(() -> step.verifyNotNull("verifyB", null))
544 .withMessage("missing verifyB for my-actor.my-operation");
548 public void testStripPrefix() {
549 assertEquals(NO_SLASH, Step2.stripPrefix(NO_SLASH, 0));
550 assertEquals(NO_SLASH, Step2.stripPrefix(NO_SLASH, 1));
551 assertEquals(NO_SLASH, Step2.stripPrefix(NO_SLASH, 2));
553 assertEquals(ONE_SLASH, Step2.stripPrefix(ONE_SLASH, 1));
554 assertEquals(ONE_SLASH, Step2.stripPrefix(ONE_SLASH, 2));
556 assertEquals("/slashes", Step2.stripPrefix("/two/slashes", 2));
557 assertEquals("/slashes", Step2.stripPrefix("/two/slashes", 3));
559 assertEquals("/and/more", Step2.stripPrefix("/three/slashes/and/more", 3));
561 assertEquals("/and/more", Step2.stripPrefix("prefix/three/slashes/and/more", 3));