From c5281dd38d7f1989bf8750c6072192cdbc46031d Mon Sep 17 00:00:00 2001 From: Michal Kabaj Date: Wed, 14 Feb 2018 12:36:17 +0100 Subject: ChefApiClient JUnits Add(fix) JUnits for ChefApiClient + refactor. Junits work on mocked HttpClient instead of attempting to make real calls. All ChefApiClient methods are now tested. Improve readability and refactored the ChefApiClient flow (simplified). Heavily improved SRP in both ChefApiClient and Api request creation. Change-Id: I997490bf269e7d78b741baf7f8d5d7bf64f6b99a Issue-ID: APPC-437 Signed-off-by: Michal Kabaj --- .../adapter/chef/chefclient/ChefApiClientTest.java | 215 +++++++++++++++++++++ .../chef/chefclient/ChefApiHeaderFactoryTest.java | 83 ++++++++ .../chef/chefclient/FormattedTimestampTest.java | 40 ++++ .../adapter/chef/chefclient/TestChefApiClient.java | 119 ------------ 4 files changed, 338 insertions(+), 119 deletions(-) create mode 100644 appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientTest.java create mode 100644 appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactoryTest.java create mode 100644 appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestampTest.java delete mode 100644 appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/TestChefApiClient.java (limited to 'appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java') diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientTest.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientTest.java new file mode 100644 index 000000000..58b55854a --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiClientTest.java @@ -0,0 +1,215 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import static junit.framework.TestCase.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; + +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.function.Supplier; +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.entity.StringEntity; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ChefApiClientTest { + + private static final String END_POINT = "https://chefServer"; + private static final String ORGANIZATIONS_PATH = "onap"; + private static final String USER_ID = "testUser"; + private static final String REQUEST_PATH = "/test/path"; + private static final String BODY = "SOME BODY STRING"; + private static final ImmutableMap HEADERS = ImmutableMap.builder() + .put("Content-type", "application/json") + .put("Accept", "application/json") + .put("X-Ops-Timestamp", "1970-01-15T06:56:07Z") + .put("X-Ops-UserId", USER_ID) + .put("X-Chef-Version", "12.4.1") + .put("X-Ops-Content-Hash", BODY) + .put("X-Ops-Sign", "version=1.0").build(); + + @Mock + private HttpClient httpClient; + @Mock + private ChefApiHeaderFactory chefHttpHeaderFactory; + + @InjectMocks + private ChefApiClientFactory chefApiClientFactory; + private static final String PEM_FILEPATH = "path/to/pemFile"; + private ChefApiClient chefApiClient; + + @Before + public void setUp() { + chefApiClient = chefApiClientFactory.create( + END_POINT, + ORGANIZATIONS_PATH, + USER_ID, + PEM_FILEPATH); + } + + @Test + public void execute_HttpGet_shouldReturnResponseObject_whenRequestIsSuccessful() throws IOException { + // GIVEN + String methodName = "GET"; + String body = ""; + Supplier chefClientApiCall = () -> chefApiClient.get(REQUEST_PATH); + + // WHEN //THEN + assertChefApiClientCall(methodName, body, chefClientApiCall); + } + + @Test + public void execute_HttpDelete_shouldReturnResponseObject_whenRequestIsSuccessful() throws IOException { + // GIVEN + String methodName = "DELETE"; + String body = ""; + Supplier chefClientApiCall = () -> chefApiClient.delete(REQUEST_PATH); + + // WHEN //THEN + assertChefApiClientCall(methodName, body, chefClientApiCall); + } + + @Test + public void execute_HttpPost_shouldReturnResponseObject_whenRequestIsSuccessful() throws IOException { + // GIVEN + String methodName = "POST"; + Supplier chefClientApiCall = () -> chefApiClient.post(REQUEST_PATH, BODY); + + // WHEN //THEN + assertChefApiClientCall(methodName, BODY, chefClientApiCall); + } + + @Test + public void execute_HttpPut_shouldReturnResponseObject_whenRequestIsSuccessful() throws IOException { + // GIVEN + String methodName = "PUT"; + Supplier chefClientApiCall = () -> chefApiClient.put(REQUEST_PATH, BODY); + + // WHEN //THEN + assertChefApiClientCall(methodName, BODY, chefClientApiCall); + } + + private void assertChefApiClientCall(String methodName, String body, Supplier httpMethod) + throws IOException { + // GIVEN + given(chefHttpHeaderFactory.create(methodName, REQUEST_PATH, body, USER_ID, ORGANIZATIONS_PATH, PEM_FILEPATH)) + .willReturn(HEADERS); + + StatusLine statusLine = mock(StatusLine.class); + given(statusLine.getStatusCode()).willReturn(HttpStatus.SC_OK); + HttpResponse httpResponse = mock(HttpResponse.class); + given(httpResponse.getStatusLine()).willReturn(statusLine); + given(httpResponse.getEntity()).willReturn(new StringEntity("Successful Response String")); + given(httpClient.execute(argThat(new HttpRequestBaseMatcher(methodName)))) + .willReturn(httpResponse); + + // WHEN + ChefResponse chefResponse = httpMethod.get(); + + // THEN + assertEquals("Successful Response String", chefResponse.getBody()); + assertEquals(HttpStatus.SC_OK, chefResponse.getStatusCode()); + } + + @Test + public void execute_shouldHandleException_whenHttpClientExecutionFails() throws IOException { + + // GIVEN + given(chefHttpHeaderFactory.create("GET", REQUEST_PATH, "", USER_ID, ORGANIZATIONS_PATH, PEM_FILEPATH)) + .willReturn(HEADERS); + + String expectedErrorMsg = "HttpClient call failed"; + given(httpClient.execute(argThat(new HttpRequestBaseMatcher("GET")))) + .willThrow(new IOException(expectedErrorMsg)); + + // WHEN + ChefResponse chefResponse = chefApiClient.get(REQUEST_PATH); + + // THEN + assertEquals(expectedErrorMsg, chefResponse.getBody()); + assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, chefResponse.getStatusCode()); + } + + @Test + public void execute_shouldHandleException_whenEndpointURIisMalformed() { + // GIVEN + String expectedErrorMsg = "Malformed escape pair at index 1: /%#@/"; + + // WHEN + ChefApiClient chefApiClient = chefApiClientFactory.create( + "/%#@/", + ORGANIZATIONS_PATH, + USER_ID, + PEM_FILEPATH); + ChefResponse chefResponse = chefApiClient.get(REQUEST_PATH); + + // THEN + assertEquals(expectedErrorMsg, chefResponse.getBody()); + assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, chefResponse.getStatusCode()); + } + + private class HttpRequestBaseMatcher extends ArgumentMatcher { + + private final String methodName; + + public HttpRequestBaseMatcher(String methodName) { + this.methodName = methodName; + } + + @Override + public boolean matches(Object argument) { + HttpRequestBase httpRequestBase = (HttpRequestBase) argument; + + boolean headersMatch = checkIfHeadersMatch(httpRequestBase); + try { + return methodName.equals(httpRequestBase.getMethod()) + && new URI(END_POINT + REQUEST_PATH).equals(httpRequestBase.getURI()) + && headersMatch; + } catch (URISyntaxException e) { + e.printStackTrace(); + return false; + } + } + + private boolean checkIfHeadersMatch(HttpRequestBase httpRequestBase) { + Header[] generatedHeaders = httpRequestBase.getAllHeaders(); + return generatedHeaders.length > 0 + && generatedHeaders.length == HEADERS.size() + && HEADERS.entrySet().stream() + .allMatch(p -> httpRequestBase.getFirstHeader(p.getKey()).getValue().equals(p.getValue())); + } + } +} \ No newline at end of file diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactoryTest.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactoryTest.java new file mode 100644 index 000000000..cc309811d --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/ChefApiHeaderFactoryTest.java @@ -0,0 +1,83 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import static junit.framework.TestCase.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; + +import com.google.common.collect.ImmutableMap; +import java.util.Date; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ChefApiHeaderFactoryTest { + + private static final String ORGANIZATIONS_PATH = "onap"; + private static final String USER_ID = "testUser"; + private static final String REQUEST_PATH = "/test/path"; + private static final String EXPECTED_TIMESTAMP = "1970-01-15T06:56:07Z"; + private static final String EMPTY_BODY = ""; + + @Mock + private FormattedTimestamp formattedTimestamp; + + @InjectMocks + private ChefApiHeaderFactory chefApiHeaderFactory; + + @Test + public void create_shouldCreateProperChefHeaders_withHashedAuthorizationString() { + // GIVEN + given(formattedTimestamp.format(any(Date.class))).willReturn(EXPECTED_TIMESTAMP); + String pemFilePath = getClass().getResource("/testclient.pem").getPath(); + + // WHEN + ImmutableMap headers = chefApiHeaderFactory + .create("GET", REQUEST_PATH, "", USER_ID, ORGANIZATIONS_PATH, pemFilePath); + + // THEN + assertEquals(headers, createExpectedHeaders()); + } + + private ImmutableMap createExpectedHeaders() { + String hashedBody = Utils.sha1AndBase64(EMPTY_BODY); + ImmutableMap.Builder builder = ImmutableMap.builder(); + builder + .put("Content-type", "application/json") + .put("Accept", "application/json") + .put("X-Ops-Timestamp", EXPECTED_TIMESTAMP) + .put("X-Ops-UserId", USER_ID) + .put("X-Chef-Version", "12.4.1") + .put("X-Ops-Content-Hash", hashedBody) + .put("X-Ops-Sign", "version=1.0") + .put("X-Ops-Authorization-1", "i+HGCso703727yd2ZQWMZIIpGKgTzm41fA31LIExNxEf9mOUMcpesIHjH/Wr") + .put("X-Ops-Authorization-2", "QEvsX/Gy1ay9KsUtqhy9GA6PB8UfDeMNoVUisqR4HQW+S6IOfvqBjW+2afzE") + .put("X-Ops-Authorization-3", "RdRReB/TJIF3s6ZC8vNpbEdY9kHmwiDglhxmS8X2FS+ArSh/DK/i7MqBbjux") + .put("X-Ops-Authorization-4", "49iiOlRVG7aTr/FA115hlBYP9CYCIQWKIBUOK3JyV9fXNdVqc9R0r1XdjxUl") + .put("X-Ops-Authorization-5", "EDGw6tuE8YW8mH5wkgHCjKpXG3WjmWt2X6kUrdIu44qCBK2N3sZziSub2fJA") + .put("X-Ops-Authorization-6", "hPBuOhjiYDZuFUqC99lCryM0Hf5RMw1uTlkYsBEZmA=="); + + return builder.build(); + } +} \ No newline at end of file diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestampTest.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestampTest.java new file mode 100644 index 000000000..47d2f6c2c --- /dev/null +++ b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/FormattedTimestampTest.java @@ -0,0 +1,40 @@ +/* + * ============LICENSE_START======================================================= + * ONAP : APPC + * ================================================================================ + * Copyright (C) 2018 Nokia. All rights reserved. + * ============================================================================= + * 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. + * ============LICENSE_END========================================================= + */ +package org.onap.appc.adapter.chef.chefclient; + +import static org.junit.Assert.assertEquals; + +import java.util.Date; +import org.junit.Test; + +public class FormattedTimestampTest { + + @Test + public void format_shouldFormatGivenDate_withCorrectTimezoneSet() { + // GIVEN + String expectedFormattedDate = "1970-01-15T06:56:07Z"; + + // WHEN + String formattedDateWithTimezone = new FormattedTimestamp().format(new Date(1234567890)); + + // THEN + assertEquals(expectedFormattedDate, formattedDateWithTimezone); + } +} \ No newline at end of file diff --git a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/TestChefApiClient.java b/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/TestChefApiClient.java deleted file mode 100644 index 9e787b64d..000000000 --- a/appc-adapters/appc-chef-adapter/appc-chef-adapter-bundle/src/test/java/org/onap/appc/adapter/chef/chefclient/TestChefApiClient.java +++ /dev/null @@ -1,119 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * ONAP : APPC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. - * ================================================================================ - * Copyright (C) 2017 Amdocs - * ============================================================================= - * 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. - * - * ECOMP is a trademark and service mark of AT&T Intellectual Property. - * ============LICENSE_END========================================================= - */ - -package org.onap.appc.adapter.chef.chefclient; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import java.io.InputStream; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.Properties; -import org.junit.Before; -import org.junit.Test; -import org.onap.appc.adapter.chef.chefapi.ApiMethod; -import org.onap.appc.adapter.chef.chefapi.Delete; -import org.onap.appc.adapter.chef.chefapi.Get; -import org.onap.appc.adapter.chef.chefapi.Post; -import org.onap.appc.adapter.chef.chefapi.Put; - -public class TestChefApiClient { - - private ChefApiClient client; - private Properties props; - private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"); - - @Before - public void setup() throws IllegalArgumentException, IllegalAccessException { - props = new Properties(); - InputStream propStr = getClass().getResourceAsStream("/test.properties"); - if (propStr == null) { - fail("src/test/resources/test.properties missing"); - } - - try { - props.load(propStr); - propStr.close(); - } catch (Exception e) { - e.printStackTrace(); - fail("Could not initialize properties"); - } - client = new ChefApiClient(props.getProperty("org.onap.appc.adapter.chef.chefclient.userId"), - System.getProperty("user.dir") + props.getProperty("org.onap.appc.adapter.chef.chefclient.pemPath"), - props.getProperty("org.onap.appc.adapter.chef.chefclient.endPoint"), - props.getProperty("org.onap.appc.adapter.chef.chefclient.organizations")); - } - - @Test - public void testGet() { - Get get = client.get(props.getProperty("org.onap.appc.adapter.chef.chefclient.path")); - ApiMethod method = get.execute(); - String[] response = method.test.split("\n"); - thenStringShouldMatch("GET", response); - } - - @Test - public void testPut() { - Put put = client.put(props.getProperty("org.onap.appc.adapter.chef.chefclient.path")); - ApiMethod method = put.execute(); - String[] response = method.test.split("\n"); - - thenStringShouldMatch("PUT", response); - } - - @Test - public void testPost() { - Post post = client.post(props.getProperty("org.onap.appc.adapter.chef.chefclient.path")); - ApiMethod method = post.execute(); - String[] response = method.test.split("\n"); - - thenStringShouldMatch("POST", response); - } - - @Test - public void testDelete() { - Delete delete = client.delete(props.getProperty("org.onap.appc.adapter.chef.chefclient.path")); - ApiMethod method = delete.execute(); - String[] response = method.test.split("\n"); - - thenStringShouldMatch("DELETE", response); - } - - private void thenStringShouldMatch(String method, String[] response) { - assertEquals("sb Method:" + method, response[0]); - assertEquals("Hashed Path:+JEk1y2gXwqZRweNjXYtx4ojxW8=", response[1]); - assertEquals("X-Ops-Content-Hash:2jmj7l5rSw0yVb/vlWAYkK/YBwk=", response[2]); - checkTimestamp(response[3], 30000); - assertEquals("X-Ops-UserId:test", response[4]); - } - - private void checkTimestamp(String timeStampHeader, long maxDeltaMs) { - assertTrue(timeStampHeader.startsWith("X-Ops-Timestamp:")); - LocalDateTime ld1 = LocalDateTime.parse(timeStampHeader.replace("X-Ops-Timestamp:", ""), dtf); - assertTrue(ChronoUnit.MILLIS.between(ld1, LocalDateTime.now(ZoneId.of("UTC"))) <= maxDeltaMs); - } -} -- cgit 1.2.3-korg