Commit 88f4149c authored by Maria C's avatar Maria C
Browse files

Functionizer Testing Tool: output conditions, xmi validation and test config deletion

parent d640d5ba
package eu.functionizer.functionizertestingtool;
import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult;
import eu.passage.upperware.commons.model.testing.FunctionizerTestResult;
import eu.functionizer.functionizertestingtool.service.TestRunner;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......@@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.*;
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class FunctionizerTestingToolController {
private TestRunner testRunner;
private final TestRunner testRunner;
@RequestMapping("/")
@ResponseStatus(HttpStatus.OK)
......
......@@ -2,7 +2,9 @@ package eu.functionizer.functionizertestingtool.model;
public class ReportEntryKey {
public final static String EVENT = "event";
public final static String EXPECTED_OUTPUT = "expectedOutput";
public final static String EXPECTED_VALUE = "expectedValue";
public final static String CONDITION = "condition";
public final static String ACTUAL_OUTPUT = "actualOutput";
public final static String IGNORED = "ignored";
public final static String ROOT = "root";
public final static String STAGE = "stage";
......
package eu.functionizer.functionizertestingtool.service;
import eu.functionizer.functionizertestingtool.model.FunctionizerTestResult;
import eu.passage.upperware.commons.model.testing.FunctionizerTestResult;
import eu.functionizer.functionizertestingtool.service.test.FunctionizerReportData;
import eu.functionizer.functionizertestingtool.service.test.FunctionizerTestListener;
import eu.functionizer.functionizertestingtool.service.test.ServerlessFunctionTestFactory;
......
......@@ -9,6 +9,7 @@ import com.amazonaws.services.lambda.model.InvokeRequest;
import com.amazonaws.services.lambda.model.InvokeResult;
import eu.functionizer.functionizertestingtool.model.ReportEntryKey;
import eu.functionizer.functionizertestingtool.service.test.Stage;
import eu.passage.upperware.commons.model.testing.Condition;
import eu.passage.upperware.commons.model.testing.FunctionTestConfiguration;
import eu.passage.upperware.commons.model.testing.TestCase;
import lombok.extern.slf4j.Slf4j;
......@@ -21,7 +22,7 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
......@@ -87,39 +88,45 @@ public class AWSLambdaService extends TestPreparationService {
TestCase testCase
) {
log.debug(
"Creating test case: event={}, expected output={}",
"Creating test case: event={}, condition={}, expected value={}",
testCase.getEvent(),
testCase.getExpectedOutput()
testCase.getCondition().name(),
testCase.getExpectedValue()
);
String displayName = createTestCaseDisplayName(
functionName,
testCase.getEvent(),
testCase.getExpectedOutput()
testCase.getCondition().name(),
testCase.getExpectedValue()
);
Map<String, String> reportEntry = createReportEntry(displayName);
reportEntry.put(ReportEntryKey.EVENT, testCase.getEvent());
reportEntry.put(ReportEntryKey.EXPECTED_OUTPUT, testCase.getExpectedOutput());
testReporter.publishEntry(reportEntry);
return dynamicTest(
displayName,
() -> executeAWSLambdaTestCase(
displayName,
awsLambdaClient,
awsLambdaFunctionName,
testCase.getEvent(),
testCase.getExpectedOutput()
testCase.getCondition(),
testCase.getExpectedValue()
)
);
}
public static void executeAWSLambdaTestCase(
String displayName,
AWSLambda awsLambda,
String functionName,
String event,
String expectedOutput
Condition condition,
String expectedValue
) {
Map<String, String> reportEntry = createReportEntry(displayName);
reportEntry.put(ReportEntryKey.EVENT, event);
reportEntry.put(ReportEntryKey.CONDITION, condition.name());
reportEntry.put(ReportEntryKey.EXPECTED_VALUE, expectedValue);
InvokeRequest invokeRequest = new InvokeRequest()
.withFunctionName(functionName)
.withPayload(event);
......@@ -128,8 +135,11 @@ public class AWSLambdaService extends TestPreparationService {
InvokeResult result = awsLambda.invoke(invokeRequest);
String resultString = new String(result.getPayload().array(), StandardCharsets.UTF_8);
if (Arrays.asList(200, 202, 204).contains(result.getStatusCode())) {
assertEquals(expectedOutput, resultString);
reportEntry.put(ReportEntryKey.ACTUAL_OUTPUT, resultString);
testReporter.publishEntry(reportEntry);
assertTrue(condition.getMethod().apply(resultString, expectedValue));
} else {
testReporter.publishEntry(reportEntry);
fail(String.format(
"Received status code %s. Reason: %s",
result.getStatusCode(),
......@@ -137,6 +147,7 @@ public class AWSLambdaService extends TestPreparationService {
));
}
} catch (Exception e) {
testReporter.publishEntry(reportEntry);
fail(String.format(
"An exception while fetching response from AWS Lambda Client occurred. Cause: %s",
e.getMessage()
......
......@@ -5,15 +5,13 @@ import com.microsoft.azure.credentials.ApplicationTokenCredentials;
import com.microsoft.azure.management.Azure;
import com.microsoft.azure.management.appservice.FunctionApp;
import eu.functionizer.functionizertestingtool.model.ReportEntryKey;
import eu.functionizer.functionizertestingtool.service.test.ServerlessFunctionTestFactory;
import eu.functionizer.functionizertestingtool.service.test.Stage;
import eu.passage.upperware.commons.model.testing.Condition;
import eu.passage.upperware.commons.model.testing.FunctionTestConfiguration;
import eu.passage.upperware.commons.model.testing.TestCase;
import io.github.cloudiator.rest.model.Function;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestReporter;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
......@@ -27,7 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
......@@ -134,8 +132,7 @@ public class AzureFunctionsService extends TestPreparationService {
configuration.getFunctionName(),
azureFunctionEndpoint,
functionKey,
testCase,
testReporter
testCase
))
.collect(Collectors.toList());
......@@ -146,51 +143,57 @@ public class AzureFunctionsService extends TestPreparationService {
String functionName,
String azureFunctionEndpoint,
String functionKey,
TestCase testCase,
TestReporter testReporter
TestCase testCase
) {
log.debug(
"Creating test case: event={}, expected output={}",
"Creating test case: event={}, condition={} expected value={}",
testCase.getEvent(),
testCase.getExpectedOutput()
testCase.getCondition().name(),
testCase.getExpectedValue()
);
String displayName = createTestCaseDisplayName(
functionName,
testCase.getEvent(),
testCase.getExpectedOutput()
testCase.getCondition().name(),
testCase.getExpectedValue()
);
Map<String, String> reportEntry = createReportEntry(displayName);
reportEntry.put(ReportEntryKey.EVENT, testCase.getEvent());
reportEntry.put(ReportEntryKey.EXPECTED_OUTPUT, testCase.getExpectedOutput());
testReporter.publishEntry(reportEntry);
return dynamicTest(
displayName,
() -> executeAzureTestCase(
displayName,
azureFunctionEndpoint,
functionKey,
testCase.getEvent(),
testCase.getExpectedOutput()
testCase.getCondition(),
testCase.getExpectedValue()
)
);
}
private static void executeAzureTestCase(
String displayName,
String endpoint,
String functionKey,
String event,
String expectedOutput
Condition condition,
String expectedValue
) {
Stage stage = Stage.TEST_EXECUTION;
Map<String, String> reportEntry = createReportEntry(displayName);
reportEntry.put(ReportEntryKey.EVENT, event);
reportEntry.put(ReportEntryKey.CONDITION, condition.name());
reportEntry.put(ReportEntryKey.EXPECTED_VALUE, expectedValue);
try {
String response = AzureFunctionsService.invokeFunction(
endpoint,
functionKey,
event
);
assertEquals(expectedOutput, response);
reportEntry.put(ReportEntryKey.ACTUAL_OUTPUT, response);
testReporter.publishEntry(reportEntry);
assertTrue(condition.getMethod().apply(response, expectedValue));
} catch (Exception e) {
testReporter.publishEntry(reportEntry);
fail(String.format(
"An exception while fetching response from AWS Lambda Client occurred. Cause: %s",
e.getMessage()
......
......@@ -22,13 +22,15 @@ public class TestPreparationService {
static String createTestCaseDisplayName(
String functionName,
String input,
String expectedOutput
String condition,
String expected
) {
return String.format(
"Function %s invoked with %s should return %s",
"Output of %s invoked with %s %s %s",
functionName,
input,
expectedOutput
condition,
expected
);
}
......@@ -56,11 +58,13 @@ public class TestPreparationService {
String testCaseDisplayName = createTestCaseDisplayName(
configuration.getFunctionName(),
testCase.getEvent(),
testCase.getExpectedOutput()
testCase.getCondition().name(),
testCase.getExpectedValue()
);
Map<String, String> reportEntry = createReportEntry(testCaseDisplayName);
reportEntry.put(ReportEntryKey.EVENT, testCase.getEvent());
reportEntry.put(ReportEntryKey.EXPECTED_OUTPUT, testCase.getExpectedOutput());
reportEntry.put(ReportEntryKey.CONDITION, testCase.getCondition().name());
reportEntry.put(ReportEntryKey.EXPECTED_VALUE, testCase.getExpectedValue());
reportEntry.put(ReportEntryKey.IGNORED, "true");
testReporter.publishEntry(reportEntry);
functionNode.add(dynamicTest(testCaseDisplayName, () -> fail("Test case ignored")));
......
package eu.functionizer.functionizertestingtool.service.test;
import eu.functionizer.functionizertestingtool.model.*;
import org.junit.ComparisonFailure;
import eu.passage.upperware.commons.model.testing.FunctionTestResult;
import eu.passage.upperware.commons.model.testing.FunctionizerTestResult;
import eu.passage.upperware.commons.model.testing.TestCaseResult;
import eu.passage.upperware.commons.model.testing.TestResultEnum;
import org.junit.platform.commons.util.ExceptionUtils;
import org.junit.platform.engine.TestExecutionResult;
import org.junit.platform.engine.reporting.ReportEntry;
......@@ -194,18 +197,12 @@ public class FunctionizerReportData {
testCaseResult.setResult(TestResultEnum.IGNORED);
}
testCaseResult.setEvent(reportEntry.get(ReportEntryKey.EVENT));
testCaseResult.setExpectedOutput(reportEntry.get(ReportEntryKey.EXPECTED_OUTPUT));
testCaseResult.setActualOutput(reportEntry.get(ReportEntryKey.EXPECTED_OUTPUT));
testCaseResult.setExpectedValue(reportEntry.get(ReportEntryKey.EXPECTED_VALUE));
testCaseResult.setCondition(reportEntry.get(ReportEntryKey.CONDITION));
testCaseResult.setActualOutput(reportEntry.get(ReportEntryKey.ACTUAL_OUTPUT));
result.getThrowable().ifPresent(error -> {
testCaseResult.setMessage(error.getMessage());
if (error instanceof ComparisonFailure) {
ComparisonFailure comparisonFailure = (ComparisonFailure) error;
testCaseResult.setExpectedOutput(comparisonFailure.getExpected());
testCaseResult.setActualOutput(comparisonFailure.getActual());
} else {
testCaseResult.setActualOutput(null);
}
});
functionTestResult.addTestCaseResult(testCaseResult);
}
......
......@@ -9,8 +9,6 @@ public enum Stage {
BUILD_AZURE_CLIENT("BUILD AZURE CLIENT"),
BUILD_AWS_LAMBDA_CLIENT("BUILD AWS LAMBDA CLIENT"),
GET_AZURE_FUNCTION_KEY("GET AZURE FUNCTION KEY"),
TEST_EXECUTION("TEST CASE EXECUTION"),
AWS_INVOKE("INVOKE AWS LAMBDA FUNCTION"),
END("COLLECTING TESTS FINISH");
private final String name;
......
......@@ -76,6 +76,14 @@ public class ApplicationContext {
return new MonitoringApi(apiClient);
}
@Bean
public QueueInspector queueInspector(
QueueApi queueApi,
CloudiatorProperties cloudiatorProperties
) {
return new QueueInspector(queueApi, cloudiatorProperties);
}
@Bean
public CloudiatorApi cloudiatorApi(
CloudApi cloudApi,
......
package eu.melodic.upperware.guibackend.communication.testingtool;
import eu.passage.upperware.commons.model.testing.FunctionizerTestResult;
public interface FunctionizerTestingToolApi {
Object runTests(String token);
FunctionizerTestResult runTests(String token);
}
......@@ -3,6 +3,7 @@ package eu.melodic.upperware.guibackend.communication.testingtool;
import eu.melodic.upperware.guibackend.communication.commons.RestCommunicationService;
import eu.melodic.upperware.guibackend.communication.commons.ServiceName;
import eu.melodic.upperware.guibackend.properties.GuiBackendProperties;
import eu.passage.upperware.commons.model.testing.FunctionizerTestResult;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
......@@ -22,13 +23,13 @@ public class FunctionizerTestingToolClientApi extends RestCommunicationService i
}
@Override
public Object runTests(String token) {
public FunctionizerTestResult runTests(String token) {
String requestUrl = guiBackendProperties.getFunctionizerTestingTool().getUrl() + "/test";
ParameterizedTypeReference<Object> responseType =
new ParameterizedTypeReference<Object>() {
ParameterizedTypeReference<FunctionizerTestResult> responseType =
new ParameterizedTypeReference<FunctionizerTestResult>() {
};
HttpEntity<Void> request = createEmptyHttpEntityWithAuthorizationHeader(token);
ResponseEntity<Object> response = getResponse(
ResponseEntity<FunctionizerTestResult> response = getResponse(
requestUrl,
responseType,
request,
......
package eu.melodic.upperware.guibackend.controller.testing;
import eu.melodic.upperware.guibackend.communication.testingtool.FunctionizerTestingToolApi;
import eu.melodic.upperware.guibackend.controller.testing.response.UploadTestConfigResponse;
import eu.melodic.upperware.guibackend.controller.testing.response.TestConfigurationResponse;
import eu.melodic.upperware.guibackend.service.testing.TestingService;
import eu.passage.upperware.commons.model.testing.FunctionizerTestResult;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
......@@ -19,22 +20,35 @@ public class TestingController {
TestingService testingService;
FunctionizerTestingToolApi functionizerTestApi;
@PostMapping(value = "/upload", consumes = {"multipart/form-data"})
@PostMapping(value = "/config", consumes = {"multipart/form-data"})
@ResponseStatus(HttpStatus.CREATED)
public UploadTestConfigResponse uploadTestConfigFile(@RequestParam("file") MultipartFile file) {
public TestConfigurationResponse uploadTestConfigFile(@RequestParam("file") MultipartFile file) {
String originalName = file.getResource().getFilename();
log.info(
"POST request for upload test configuration file with name: {}",
originalName
);
String fileName = testingService.uploadTestConfig(file);
log.info("File {} successfully uploaded.", originalName);
return testingService.createUploadTestConfigResponse(fileName);
return testingService.uploadTestConfig(file);
}
@GetMapping(value = "/config")
@ResponseStatus(HttpStatus.OK)
public TestConfigurationResponse getTestConfigResponse() {
log.info("GET request for the test configuration");
return testingService.getTestConfiguration();
}
@DeleteMapping(value = "/config")
@ResponseStatus(HttpStatus.OK)
public void deleteTestConfigFile() {
log.info("DELETE request for deleting the test configuration file");
testingService.removeTestConfigFile();
}
@PostMapping(value = "/run")
@ResponseStatus(HttpStatus.OK)
public Object runTests(@RequestHeader(value = HttpHeaders.AUTHORIZATION) String token) {
public FunctionizerTestResult runTests(@RequestHeader(value = HttpHeaders.AUTHORIZATION) String token) {
log.info("POST request for running the tests");
return functionizerTestApi.runTests(token);
}
}
package eu.melodic.upperware.guibackend.controller.testing.response;
import eu.passage.upperware.commons.model.testing.TestConfiguration;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class TestConfigurationResponse {
private String path;
private TestConfiguration configuration;
}
package eu.melodic.upperware.guibackend.controller.testing.response;
import lombok.*;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UploadTestConfigResponse {
private String testConfigFilePath;
}
......@@ -2,6 +2,7 @@ package eu.melodic.upperware.guibackend.service.cdo;
import camel.core.CamelModel;
import camel.core.NamedElement;
import camel.deployment.impl.DeploymentTypeModelImpl;
import camel.requirement.OptimisationRequirement;
import camel.requirement.RequirementModel;
import camel.requirement.impl.OptimisationRequirementImpl;
......@@ -32,7 +33,9 @@ import org.springframework.web.server.ResponseStatusException;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Service
......@@ -43,7 +46,7 @@ public class CdoService {
private CpModelMapper cpModelMapper;
private GuiBackendProperties guiBackendProperties;
public boolean storeFileInCdo(String cdoName, File file) {
public boolean storeFileInCdo(String cdoName, File file) throws Exception {
log.info("Storing Model {} into CDO with validationEnabled = {}", cdoName, guiBackendProperties.getCdoUploader().isValidationEnabled());
EObject model = null;
......@@ -55,12 +58,47 @@ public class CdoService {
return false;
}
log.info("Checking model's software components name uniqueness");
String duplicateName = validateServerlessComponentNameUniqueness(model);
if (duplicateName != null) {
client.closeSession();
log.error("Software component's name '{}' " +
"is used in more than one component.",
duplicateName
);
throw new Exception(
String.format(
"Software component's name '%s' is used in more than one component." +
" Modify the file to have unique software component names and try again.",
duplicateName
)
);
}
log.info("Validation passed");
boolean successfullyStored = client.storeModel(model, cdoName, guiBackendProperties.getCdoUploader().isValidationEnabled());
log.info("Successfully stored of model {} in CDO = {}", cdoName, successfullyStored);
client.closeSession();
return successfullyStored;
}
private String validateServerlessComponentNameUniqueness(EObject model) {
CamelModel camelModel = (CamelModel) model;
DeploymentTypeModelImpl depModel = (DeploymentTypeModelImpl) camelModel.getDeploymentModels().get(0);
List<String> names = depModel.getSoftwareComponents()
.stream()
.map(NamedElement::getName)
.collect(Collectors.toList());
Set<String> uniqueNames = new HashSet<>();
for (String name : names) {
if (!uniqueNames.add(name)) {
return name;
}
}
return null;
}
public boolean deleteXmi(String cdoName) {
log.info("Deleting model {} from CDO", cdoName);
CDOClient client = getCdoClient();
......
......@@ -91,6 +91,8 @@ public class DeploymentService {
} catch (IllegalStateException e) {
log.error("Error by uploading xmi file:", e);
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.format("Problem by uploading your %s file. CDO repository is in pending state. Please try again.", uploadXmiRequest.getResource().getFilename()));
} catch (Exception e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}
}
......
package eu.melodic.upperware.guibackend.service.testing;
import eu.melodic.upperware.guibackend.controller.testing.response.UploadTestConfigResponse;
import eu.melodic.upperware.guibackend.controller.testing.response.TestConfigurationResponse;
import eu.passage.upperware.commons.MelodicConstants;
import eu.passage.upperware.commons.model.testing.FunctionTestConfiguration;
import eu.passage.upperware.commons.model.testing.TestConfiguration;
import eu.passage.upperware.commons.service.testing.TestConfigurationValidationService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.net4j.connector.ConnectorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
......@@ -17,7 +16,12 @@ import org.yaml.snakeyaml.Yaml;