Skip to content

Commit

Permalink
test(keel): Test to verify the timestamps in SpringHttpError
Browse files Browse the repository at this point in the history
across different time units when any 4xx http error has occured. These tests ensures the upcoming changes on KeelService with SpinnakerRetrofitErrorHandler, will not modify/break the existing functionality.

Tests covers positive and negative cases with different units of timestamps.
  • Loading branch information
Pranav-b-7 committed Feb 23, 2024
1 parent a587515 commit 21719e0
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 0 deletions.
2 changes: 2 additions & 0 deletions orca-keel/orca-keel.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ dependencies {
testImplementation("org.codehaus.groovy:groovy")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("com.github.tomakehurst:wiremock-jre8-standalone")
testImplementation("org.mockito:mockito-junit-jupiter")
}

test {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* Copyright 2024 OpsMx, Inc.
*
* 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 com.netflix.spinnaker.orca.keel.task;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static com.netflix.spinnaker.orca.api.pipeline.models.ExecutionType.PIPELINE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.http.Fault;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.netflix.spinnaker.okhttp.OkHttpClientConfigurationProperties;
import com.netflix.spinnaker.okhttp.SpinnakerRequestInterceptor;
import com.netflix.spinnaker.orca.KeelService;
import com.netflix.spinnaker.orca.api.pipeline.TaskResult;
import com.netflix.spinnaker.orca.api.pipeline.models.ExecutionStatus;
import com.netflix.spinnaker.orca.api.pipeline.models.ExecutionType;
import com.netflix.spinnaker.orca.api.pipeline.models.StageExecution;
import com.netflix.spinnaker.orca.config.KeelConfiguration;
import com.netflix.spinnaker.orca.igor.ScmService;
import com.netflix.spinnaker.orca.pipeline.model.PipelineExecutionImpl;
import com.netflix.spinnaker.orca.pipeline.model.StageExecutionImpl;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.http.HttpStatus;
import retrofit.RestAdapter;
import retrofit.RetrofitError;
import retrofit.client.OkClient;
import retrofit.converter.JacksonConverter;

/*
* @see com.netflix.spinnaker.orca.keel.ImportDeliveryConfigTaskTests.kt already covers up few tests related to @see ImportDeliveryConfigTask.
* This new java class is Introduced to improvise the API testing with the help of wiremock.
* Test using wiremock would help in smooth migration to retrofit2.x along with the addition of {@link SpinnakerRetrofitErrorHandler}.
* */
public class ImportDeliveryConfigTaskTest {

private static KeelService keelService;
private static ScmService scmService;

private static final ObjectMapper objectMapper = new KeelConfiguration().keelObjectMapper();

private StageExecution stage;

private ImportDeliveryConfigTask importDeliveryConfigTask;

private Map<String, Object> contextMap = new LinkedHashMap<>();

private static final int keelPort = 8087;

@BeforeAll
static void setupOnce(WireMockRuntimeInfo wmRuntimeInfo) {
OkClient okClient = new OkClient();
RestAdapter.LogLevel retrofitLogLevel = RestAdapter.LogLevel.NONE;

keelService =
new RestAdapter.Builder()
.setRequestInterceptor(
new SpinnakerRequestInterceptor(new OkHttpClientConfigurationProperties()))
.setEndpoint(wmRuntimeInfo.getHttpBaseUrl())
.setClient(okClient)
.setLogLevel(retrofitLogLevel)
.setConverter(new JacksonConverter(objectMapper))
.build()
.create(KeelService.class);
}

@BeforeEach
public void setup() {
scmService = mock(ScmService.class);
importDeliveryConfigTask = new ImportDeliveryConfigTask(keelService, scmService, objectMapper);

PipelineExecutionImpl pipeline = new PipelineExecutionImpl(PIPELINE, "keeldemo");
contextMap.put("repoType", "stash");
contextMap.put("projectKey", "SPKR");
contextMap.put("repositorySlug", "keeldemo");
contextMap.put("directory", ".");
contextMap.put("manifest", "spinnaker.yml");
contextMap.put("ref", "refs/heads/master");
contextMap.put("attempt", 1);
contextMap.put("maxRetries", 5);

stage = new StageExecutionImpl(pipeline, ExecutionType.PIPELINE.toString(), contextMap);
}

@RegisterExtension
static WireMockExtension wireMock =
WireMockExtension.newInstance().options(wireMockConfig().port(keelPort)).build();

private static void simulateFault(String url, String body, HttpStatus httpStatus) {
wireMock.givenThat(
WireMock.post(urlPathEqualTo(url))
.willReturn(aResponse().withStatus(httpStatus.value()).withBody(body)));
}

private static void simulateFault(String url, Fault fault) {
wireMock.givenThat(WireMock.post(urlPathEqualTo(url)).willReturn(aResponse().withFault(fault)));
}

/**
* This test is a positive case which verifies if the task returns {@link
* ImportDeliveryConfigTask.SpringHttpError} on 4xx http error. Here the error body is mocked with
* timestamp in {@link ChronoUnit#MILLIS}, which will be parsed to exact same timestamp in the
* method @see {@link ImportDeliveryConfigTask#handleRetryableFailures(RetrofitError,
* ImportDeliveryConfigTask.ImportDeliveryConfigContext)} and results in successful assertions of
* all the fields.
*
* <p>Other positive scenarios when the timestamp results in accurate value, are when the units in
* : {@link ChronoUnit#SECONDS} {@link ChronoUnit#DAYS} {@link ChronoUnit#HOURS} {@link
* ChronoUnit#HALF_DAYS} {@link ChronoUnit#MINUTES}
*/
@Test
public void verifyTaskReturnsSpringHttpError() throws JsonProcessingException {

var httpStatus = HttpStatus.BAD_REQUEST;

// Initialize SpringHttpError with timestamp in milliseconds and HttpStatus 400 bad request.
var result = getTaskResult(httpStatus, ChronoUnit.MILLIS);

verifyGetDeliveryConfigManifestInvocations();

assertThat(result.get("actual")).isEqualTo(result.get("expected"));
}

/**
* This test is a negative case which verifies if the task returns {@link
* ImportDeliveryConfigTask.SpringHttpError} on 4xx http error. Here the error body is mocked with
* timestamp in {@link ChronoUnit#NANOS}, which WILL NOT be parsed to exact timestamp in the
* method @see {@link ImportDeliveryConfigTask#handleRetryableFailures(RetrofitError,
* ImportDeliveryConfigTask.ImportDeliveryConfigContext)} and results in will contain incorrect
* timestamp.
*
* <p>Other cases where the timestamp will result in incorrect value, are when the units in :
* {@link ChronoUnit#MICROS}
*/
@Test
public void verifyTaskResultReturnsSpringHttpErrorWhenTimestampIsInNanos()
throws JsonProcessingException {

var httpStatus = HttpStatus.BAD_REQUEST;

// Initialize SpringHttpError with timestamp in nanos and HttpStatus 400 bad request.
var result = getTaskResult(httpStatus, ChronoUnit.NANOS);

var actualHttpErrorBody =
(ImportDeliveryConfigTask.SpringHttpError) result.get("actual").getContext().get("error");
var expectedHttpErrorBody =
(ImportDeliveryConfigTask.SpringHttpError) result.get("expected").getContext().get("error");

verifyGetDeliveryConfigManifestInvocations();

// assert all the values in the http error body are true except the timestamp in nanos
assertThat(actualHttpErrorBody.getStatus()).isEqualTo(httpStatus.value());
assertThat(actualHttpErrorBody.getError()).isEqualTo(httpStatus.getReasonPhrase());
assertThat(actualHttpErrorBody.getMessage()).isEqualTo(httpStatus.name());
assertThat(actualHttpErrorBody.getDetails())
.isEqualTo(Map.of("exception", "Http Error occured"));
assertThat(actualHttpErrorBody.getTimestamp().getEpochSecond())
.isEqualTo(expectedHttpErrorBody.getTimestamp().getEpochSecond());
assertThat(actualHttpErrorBody.getTimestamp().getNano())
.isNotEqualTo(expectedHttpErrorBody.getTimestamp().getNano());
}

private Map<String, TaskResult> getTaskResult(HttpStatus httpStatus, ChronoUnit unit)
throws JsonProcessingException {

var scmResponseBody =
Map.of(
"name",
"keeldemo-manifest",
"application",
"keeldemo",
"artifacts",
Collections.emptySet(),
"environments",
Collections.emptySet());

var httpError = makeSpringHttpError(httpStatus, Instant.now().truncatedTo(unit));

var context = Map.of("error", httpError);

TaskResult terminal = TaskResult.builder(ExecutionStatus.TERMINAL).context(context).build();

// simulate SpringHttpError with http error status code
simulateFault("/delivery-configs/", objectMapper.writeValueAsString(httpError), httpStatus);

when(scmService.getDeliveryConfigManifest(
(String) contextMap.get("repoType"),
(String) contextMap.get("projectKey"),
(String) contextMap.get("repositorySlug"),
(String) contextMap.get("directory"),
(String) contextMap.get("manifest"),
(String) contextMap.get("ref")))
.thenReturn(scmResponseBody);

var result = importDeliveryConfigTask.execute(stage);

return Map.of("expected", terminal, "actual", result);
}

private ImportDeliveryConfigTask.SpringHttpError makeSpringHttpError(
HttpStatus httpStatus, Instant timestamp) {

return new ImportDeliveryConfigTask.SpringHttpError(
httpStatus.getReasonPhrase(),
httpStatus.value(),
httpStatus.name(),
timestamp,
Map.of("exception", "Http Error occured"));
}

private void verifyGetDeliveryConfigManifestInvocations() {
verify(scmService, times(1))
.getDeliveryConfigManifest(
(String) contextMap.get("repoType"),
(String) contextMap.get("projectKey"),
(String) contextMap.get("repositorySlug"),
(String) contextMap.get("directory"),
(String) contextMap.get("manifest"),
(String) contextMap.get("ref"));
}
}

0 comments on commit 21719e0

Please sign in to comment.