diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index a6243d5ab4..b645ddab35 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -132,9 +132,13 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_REGION }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} shell: bash run: | - if [[ -n $BROWSERSTACK_USER && -n $BROWSERSTACK_KEY && -n $APPLITOOLS_READ_KEY && -n $APPLITOOLS_READ_KEY && -n $MONGODB_USERNAME && -n $MONGODB_PASSWORD && -n $KAFKA_PASSWORD && -n $AWS_ACCESS_KEY_ID && -n $AWS_SECRET_ACCESS_KEY && -n $AWS_REGION ]]; then + if [[ -n $BROWSERSTACK_USER && -n $BROWSERSTACK_KEY && -n $APPLITOOLS_READ_KEY && -n $APPLITOOLS_READ_KEY && -n $MONGODB_USERNAME && -n $MONGODB_PASSWORD && -n $KAFKA_PASSWORD && -n $AWS_ACCESS_KEY_ID && -n $AWS_SECRET_ACCESS_KEY && -n $AWS_REGION && -n $AZURE_CLIENT_ID && -n $AZURE_CLIENT_SECRET && -n $AZURE_TENANT_ID && -n $AZURE_SUBSCRIPTION_ID ]]; then ./gradlew :vividus-tests:runStories -x testVividusInitialization \ -Pvividus.configuration.environments=system/web,system/api \ -Pvividus.configuration.suites=system \ diff --git a/docs/modules/plugins/nav.adoc b/docs/modules/plugins/nav.adoc index 4ec5128740..4373ac84be 100644 --- a/docs/modules/plugins/nav.adoc +++ b/docs/modules/plugins/nav.adoc @@ -5,6 +5,8 @@ ** xref:plugin-aws-kinesis.adoc[Kinesis] ** xref:plugin-aws-lambda.adoc[Lambda] ** xref:plugin-aws-s3.adoc[S3] +* xref:azure.adoc[Azure] +** xref:plugin-azure-functions.adoc[Functions] * xref:plugin-csv.adoc[CSV] * xref:plugin-datetime.adoc[Date/Time] * xref:plugin-db.adoc[Relational DB*] diff --git a/docs/modules/plugins/pages/azure.adoc b/docs/modules/plugins/pages/azure.adoc new file mode 100644 index 0000000000..af9a9119d3 --- /dev/null +++ b/docs/modules/plugins/pages/azure.adoc @@ -0,0 +1,6 @@ += What is Azure + +Microsoft https://azure.microsoft.com/[Azure] is a cloud computing service created by Microsoft for building, testing, deploying, +and managing applications and services through Microsoft-managed data centers. + +Vividus provides set of plugins to interact with the services. diff --git a/docs/modules/plugins/pages/plugin-azure-functions.adoc b/docs/modules/plugins/pages/plugin-azure-functions.adoc new file mode 100644 index 0000000000..9a9a9606c9 --- /dev/null +++ b/docs/modules/plugins/pages/plugin-azure-functions.adoc @@ -0,0 +1,60 @@ += Azure functions Plugin + +The plugin provides functionality to interact with https://azure.microsoft.com/en-us/services/functions/[Azure Functions] + +== Installation + +.build.gradle +[source,gradle,subs="attributes+"] +---- +implementation(group: 'org.vividus', name: 'vividus-plugin-azure-functions', version: '{current-version}') +---- + +== Configuration + +=== Authentication + +Authentication process relies on the configuration of environment variables. + +See the official https://github.com/Azure/azure-sdk-for-java/tree/master/sdk/identity/azure-identity#environment-variables["Azure identity"] guide to get more details on what types of authentication could be used. + +=== Azure Environment selection + +The Azure environment could be selected via a property: `azure.functions.environment` +The default value is AZURE + +Possible values are: AZURE, AZURE_CHINA, AZURE_GERMANY, AZURE_US_GOVERNMENT + +=== Azure Subscription selection + +The azure subscription should be configured via `AZURE_SUBSCRIPTION_ID` environment variable + +== Steps + +=== Function triggering + +Triggers a function + +[source,gherkin] +---- +When I trigger funtion `$functionName` from function app `$functionAppName` in resource group `$resourceGroup` with payload:$payload and save response into $scopes variable `$variableNames` +---- + +* `$functionName` - The name of the function to trigger +* `$functionAppName` - The name of the function app +* `$resourceGroup` - The resource group function relates to +* `$payload` - The JSON payload to send to a function (could be empty) +* `$scopes` - xref:parameters:variable-scope.adoc[The comma-separated set of the variables scopes]. +* `$variableName` - The variable name to store results in JSON format. If the variable name is `my-var`, the following variables will be created: +** `${my-var.body}` - The response body +** `${my-var.status-code}` - The HTTP status code +** `${my-var.headers}` - The response headers +** `${my-var.url}` - The request URL + +.Trigger function +[source,gherkin] +---- +When I trigger function `HttpTrigger1` from function app `vivdus-http-function` in resource group `vividus` with payload: +and save response into scenario variable `functionTrigger` +Then `${functionTrigger.status-code}` is equal to `202` +---- diff --git a/settings.gradle b/settings.gradle index 34c9b664ac..7dcf125d8c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -18,6 +18,7 @@ include 'vividus-plugin-aws-dynamodb' include 'vividus-plugin-aws-kinesis' include 'vividus-plugin-aws-lambda' include 'vividus-plugin-aws-s3' +include 'vividus-plugin-azure-functions' include 'vividus-plugin-browserstack' include 'vividus-plugin-csv' include 'vividus-plugin-datetime' diff --git a/vividus-plugin-azure-functions/build.gradle b/vividus-plugin-azure-functions/build.gradle new file mode 100644 index 0000000000..598a39f9f0 --- /dev/null +++ b/vividus-plugin-azure-functions/build.gradle @@ -0,0 +1,19 @@ +project.description = 'Vividus plugin for Azure Functions' + +dependencies { + api project(':vividus-bdd-engine') + implementation project(':vividus-soft-assert') + implementation project(':vividus-util') + implementation(group: 'com.azure.resourcemanager', name: 'azure-resourcemanager-appservice', version: '2.2.0') + implementation(group: 'com.azure', name: 'azure-identity', version: '1.3.0-beta.2') + + implementation(group: 'org.slf4j', name: 'slf4j-api', version: versions.slf4j) + + testImplementation platform(group: 'org.junit', name: 'junit-bom', version: versions.junit) + testImplementation(group: 'org.junit.jupiter', name: 'junit-jupiter') + testImplementation(group: 'org.mockito', name: 'mockito-junit-jupiter', version: versions.mockito) + testImplementation(group: 'com.github.valfirst', name: 'slf4j-test', version: versions.slf4jTest) +} + +tasks.artifactoryPublish.enabled = false +tasks.publish.enabled = false diff --git a/vividus-plugin-azure-functions/src/main/java/org/vividus/azure/functions/service/FunctionService.java b/vividus-plugin-azure-functions/src/main/java/org/vividus/azure/functions/service/FunctionService.java new file mode 100644 index 0000000000..8f824188fc --- /dev/null +++ b/vividus-plugin-azure-functions/src/main/java/org/vividus/azure/functions/service/FunctionService.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.vividus.azure.functions.service; + +import java.util.Map; + +import com.azure.core.credential.TokenCredential; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.management.profile.AzureProfile; +import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.resourcemanager.appservice.AppServiceManager; + +public class FunctionService +{ + private final AzureProfile azureProfile; + + public FunctionService(AzureProfile azureProfile) + { + this.azureProfile = azureProfile; + } + + private AppServiceManager createManager(ResponseCapturingHttpPipelinePolicy responseCapturingHttpPipelinePolicy) + { + TokenCredential credential = new DefaultAzureCredentialBuilder() + .authorityHost(azureProfile.getEnvironment().getActiveDirectoryEndpoint()).build(); + return AppServiceManager.configure() + .withLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS) + .withPolicy(responseCapturingHttpPipelinePolicy) + .authenticate(credential, azureProfile); + } + + public Map triggerFunction(String resourceGroup, String appName, String functionName, + Object payload) + { + ResponseCapturingHttpPipelinePolicy responseCapturingHttpPipelinePolicy = + new ResponseCapturingHttpPipelinePolicy(functionName); + createManager(responseCapturingHttpPipelinePolicy) + .functionApps() + .getByResourceGroup(resourceGroup, appName) + .triggerFunction(functionName, payload); + return responseCapturingHttpPipelinePolicy.getResponses(); + } +} diff --git a/vividus-plugin-azure-functions/src/main/java/org/vividus/azure/functions/service/ResponseCapturingHttpPipelinePolicy.java b/vividus-plugin-azure-functions/src/main/java/org/vividus/azure/functions/service/ResponseCapturingHttpPipelinePolicy.java new file mode 100644 index 0000000000..ccb8d7a407 --- /dev/null +++ b/vividus-plugin-azure-functions/src/main/java/org/vividus/azure/functions/service/ResponseCapturingHttpPipelinePolicy.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.vividus.azure.functions.service; + +import java.util.HashMap; +import java.util.Map; + +import com.azure.core.http.HttpPipelineCallContext; +import com.azure.core.http.HttpPipelineNextPolicy; +import com.azure.core.http.HttpResponse; +import com.azure.core.http.policy.HttpPipelinePolicy; + +import reactor.core.publisher.Mono; + +public class ResponseCapturingHttpPipelinePolicy implements HttpPipelinePolicy +{ + private final Map recorded = new HashMap<>(); + private final String funcitonName; + + public ResponseCapturingHttpPipelinePolicy(String funcitonName) + { + this.funcitonName = funcitonName; + } + + public Map getResponses() + { + return new HashMap<>(recorded); + } + + @Override + public Mono process(HttpPipelineCallContext context, HttpPipelineNextPolicy next) + { + return next.process().doOnSuccess(this::saveResponse); + } + + private void saveResponse(HttpResponse response) + { + String url = response.getRequest().getUrl().toString(); + if (url.endsWith(funcitonName)) + { + recorded.put("url", url); + recorded.put("status-code", response.getStatusCode()); + recorded.put("body", response.getBodyAsString()); + recorded.put("headers", response.getHeaders().toMap()); + } + } +} diff --git a/vividus-plugin-azure-functions/src/main/java/org/vividus/azure/functions/steps/FunctionSteps.java b/vividus-plugin-azure-functions/src/main/java/org/vividus/azure/functions/steps/FunctionSteps.java new file mode 100644 index 0000000000..0610c9fca4 --- /dev/null +++ b/vividus-plugin-azure-functions/src/main/java/org/vividus/azure/functions/steps/FunctionSteps.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.vividus.azure.functions.steps; + +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.jbehave.core.annotations.When; +import org.vividus.azure.functions.service.FunctionService; +import org.vividus.bdd.context.IBddVariableContext; +import org.vividus.bdd.variable.VariableScope; +import org.vividus.util.json.JsonUtils; + +import reactor.core.publisher.Mono; + +public class FunctionSteps +{ + private final JsonUtils jsonUtils; + private final FunctionService functionsService; + private final IBddVariableContext bddVariableContext; + + public FunctionSteps(JsonUtils jsonUtils, FunctionService functionsService, IBddVariableContext bddVariableContext) + { + this.jsonUtils = jsonUtils; + this.functionsService = functionsService; + this.bddVariableContext = bddVariableContext; + } + + /** + * Triggers Azure function by its name. And saves response from the service into variable with + * a provided scope and name + * @param functionApp The name of Azure Function App. The value can be retrieved by looking + * at the function in the Azure Portal. + * @param functionName The name of the function to execute. The value can be retrieved by looking + * at the function in the Azure Portal. + * @param resourceGroup Resource group name. Resource group - container that holds related resources + * for an Azure solution. The value can be retrieved by looking + * at the function in the Azure Portal. + * @param payload the JSON that to provide to Function App function as input. + * @param scopes The set (comma separated list of scopes e.g.: STORY, NEXT_BATCHES) of variables scopes
+ * Available scopes: + *
    + *
  • STEP - the variable will be available only within the step, + *
  • SCENARIO - the variable will be available only within the scenario, + *
  • STORY - the variable will be available within the whole story, + *
  • NEXT_BATCHES - the variable will be available starting from next batch + *
scopes + * @param variableName The variable name to store result. If the variable name is my-var, the following + * variables will be created: + *
    + *
  • ${my-var.body} - the response body
  • + *
  • ${my-var.status-code} - the HTTP status code is in the 200 range for a successful + * request
  • + *
  • ${my-var.headers} - the response headers
  • + *
  • ${my-var.url} - the request URL
  • + *
+ */ + @SuppressWarnings("unchecked") + @When("I trigger function `$functionName` from function app `$functionAppName` in resource group `$resourceGroup`" + + " with payload:$payload and save response into $scopes variable `$variableNames`") + public void triggerFunction(String functionName, String functionApp, String resourceGroup, String payload, + Set scopes, String variableName) + { + Map responses = functionsService.triggerFunction(resourceGroup, functionApp, functionName, + convertPayload(payload)); + responses.compute("body", (k, v) -> ((Mono) v).block()); + bddVariableContext.putVariable(scopes, variableName, responses); + } + + @SuppressWarnings("unchecked") + private Map convertPayload(String payload) + { + if (StringUtils.isBlank(payload)) + { + return Map.of(); + } + return jsonUtils.toObject(payload, Map.class); + } +} diff --git a/vividus-plugin-azure-functions/src/main/resources/log4j2-vividus-azure-functions.xml b/vividus-plugin-azure-functions/src/main/resources/log4j2-vividus-azure-functions.xml new file mode 100644 index 0000000000..8ff71622e0 --- /dev/null +++ b/vividus-plugin-azure-functions/src/main/resources/log4j2-vividus-azure-functions.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/vividus-plugin-azure-functions/src/main/resources/properties/defaults/default.properties b/vividus-plugin-azure-functions/src/main/resources/properties/defaults/default.properties new file mode 100644 index 0000000000..9618701d80 --- /dev/null +++ b/vividus-plugin-azure-functions/src/main/resources/properties/defaults/default.properties @@ -0,0 +1 @@ +azure.functions.environment=AZURE diff --git a/vividus-plugin-azure-functions/src/main/resources/spring.xml b/vividus-plugin-azure-functions/src/main/resources/spring.xml new file mode 100644 index 0000000000..497a99e317 --- /dev/null +++ b/vividus-plugin-azure-functions/src/main/resources/spring.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/vividus-plugin-azure-functions/src/test/java/org/vividus/azure/functions/service/FunctionServiceTests.java b/vividus-plugin-azure-functions/src/test/java/org/vividus/azure/functions/service/FunctionServiceTests.java new file mode 100644 index 0000000000..c44ca7f184 --- /dev/null +++ b/vividus-plugin-azure-functions/src/test/java/org/vividus/azure/functions/service/FunctionServiceTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.vividus.azure.functions.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import com.azure.core.credential.TokenCredential; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.management.AzureEnvironment; +import com.azure.core.management.profile.AzureProfile; +import com.azure.resourcemanager.appservice.AppServiceManager; +import com.azure.resourcemanager.appservice.AppServiceManager.Configurable; +import com.azure.resourcemanager.appservice.models.FunctionApp; +import com.azure.resourcemanager.appservice.models.FunctionApps; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class FunctionServiceTests +{ + private static final Map PAYLOAD = Map.of(); + private static final String FUNCTION = "test"; + private static final String FUNCTION_APP = "vividus-tests"; + private static final String RESOURCE_GROUP = "vividus"; + private static final String ENDPOINT = "https://azure.dev"; + @Mock private AzureEnvironment azureEnvironment; + @Mock private AzureProfile azureProfile; + + @InjectMocks private FunctionService functionService; + + @Test + void shouldExecuteAFunction() + { + try (MockedStatic appServiceMock = mockStatic(AppServiceManager.class); + MockedConstruction policy = + mockConstruction(ResponseCapturingHttpPipelinePolicy.class)) + { + when(azureProfile.getEnvironment()).thenReturn(azureEnvironment); + when(azureEnvironment.getActiveDirectoryEndpoint()).thenReturn(ENDPOINT); + Configurable configurable = mock(Configurable.class); + appServiceMock.when(() -> AppServiceManager.configure()).thenReturn(configurable); + when(configurable.withLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS)).thenReturn(configurable); + when(configurable.withPolicy(any(ResponseCapturingHttpPipelinePolicy.class))).thenReturn(configurable); + AppServiceManager serviceManager = mock(AppServiceManager.class); + when(configurable.authenticate(any(TokenCredential.class), eq(azureProfile))).thenReturn(serviceManager); + FunctionApps functionApps = mock(FunctionApps.class); + when(serviceManager.functionApps()).thenReturn(functionApps); + FunctionApp functionApp = mock(FunctionApp.class); + when(functionApps.getByResourceGroup(RESOURCE_GROUP, FUNCTION_APP)).thenReturn(functionApp); + + functionService.triggerFunction(RESOURCE_GROUP, FUNCTION_APP, FUNCTION, PAYLOAD); + + verify(azureProfile).getEnvironment(); + verify(azureEnvironment).getActiveDirectoryEndpoint(); + verify(functionApp).triggerFunction(FUNCTION, PAYLOAD); + verify(policy.constructed().get(0)).getResponses(); + } + } +} diff --git a/vividus-plugin-azure-functions/src/test/java/org/vividus/azure/functions/service/ResponseCapturingHttpPipelinePolicyTests.java b/vividus-plugin-azure-functions/src/test/java/org/vividus/azure/functions/service/ResponseCapturingHttpPipelinePolicyTests.java new file mode 100644 index 0000000000..e9a7d0edcd --- /dev/null +++ b/vividus-plugin-azure-functions/src/test/java/org/vividus/azure/functions/service/ResponseCapturingHttpPipelinePolicyTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.vividus.azure.functions.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.Map; + +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpPipelineCallContext; +import com.azure.core.http.HttpPipelineNextPolicy; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import reactor.core.publisher.Mono; + +@ExtendWith(MockitoExtension.class) +class ResponseCapturingHttpPipelinePolicyTests +{ + private static final String BODY = "body"; + private static final int SC_OK = 200; + private static final String URL = "https://azure.com/function"; + @Mock private HttpPipelineCallContext context; + @Mock private HttpPipelineNextPolicy next; + + private ResponseCapturingHttpPipelinePolicy underTest = new ResponseCapturingHttpPipelinePolicy("function"); + + @SuppressWarnings("unchecked") + @Test + void shouldCaptureResponseData() throws MalformedURLException + { + HttpResponse response = mock(HttpResponse.class); + Mono mono = mock(Mono.class); + Mono body = Mono.just(BODY); + when(response.getBodyAsString()).thenReturn(body); + HttpHeaders headers = mock(HttpHeaders.class); + when(response.getHeaders()).thenReturn(headers); + Map headersMap = Map.of("header", "value"); + when(headers.toMap()).thenReturn(headersMap); + HttpRequest request = mock(HttpRequest.class); + URL url = URI.create(URL).toURL(); + when(request.getUrl()).thenReturn(url).thenReturn(URI.create("https://google.com").toURL()); + when(response.getRequest()).thenReturn(request); + when(response.getStatusCode()).thenReturn(SC_OK); + when(mono.doOnSuccess(argThat(c -> { + c.accept(response); + return true; + }))).thenReturn(mono); + when(next.process()).thenReturn(mono); + + underTest.process(context, next); + underTest.process(context, next); + + Map captured = underTest.getResponses(); + Assertions.assertAll( + () -> assertEquals(headersMap, captured.get("headers")), + () -> assertEquals(body, captured.get(BODY)), + () -> assertEquals(SC_OK, captured.get("status-code")), + () -> assertEquals(URL, captured.get("url"))); + } +} diff --git a/vividus-plugin-azure-functions/src/test/java/org/vividus/azure/functions/steps/FunctionStepsTests.java b/vividus-plugin-azure-functions/src/test/java/org/vividus/azure/functions/steps/FunctionStepsTests.java new file mode 100644 index 0000000000..2b29f1a0b1 --- /dev/null +++ b/vividus-plugin-azure-functions/src/test/java/org/vividus/azure/functions/steps/FunctionStepsTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.vividus.azure.functions.steps; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.vividus.azure.functions.service.FunctionService; +import org.vividus.bdd.context.IBddVariableContext; +import org.vividus.bdd.variable.VariableScope; +import org.vividus.util.json.JsonUtils; + +import reactor.core.publisher.Mono; + +@ExtendWith(MockitoExtension.class) +class FunctionStepsTests +{ + private static final String DATA = "data"; + private static final String BODY = "body"; + private static final Set SCOPES = Set.of(VariableScope.STORY); + private static final String VARIABLE_NAME = "variableName"; + private static final String FUNCTION_NAME = "functionName"; + private static final String APP_NAME = "appName"; + private static final String RESOURCE_GROUP = "resourceGroup"; + private static final String PAYLOAD = "{\"key\":\"value\"}"; + + @Mock private JsonUtils jsonUtils; + @Mock private FunctionService functionService; + @Mock private IBddVariableContext bddVariableContext; + + @InjectMocks private FunctionSteps functionSteps; + + @Test + void shouldTriggerFunctionAndSaveResponsesToAScopedVariale() + { + Map payloadMap = Map.of("key", "value"); + when(jsonUtils.toObject(PAYLOAD, Map.class)).thenReturn(payloadMap); + mockTrigger(payloadMap); + functionSteps.triggerFunction(FUNCTION_NAME, APP_NAME, RESOURCE_GROUP, PAYLOAD, + SCOPES, VARIABLE_NAME); + verify(bddVariableContext).putVariable(SCOPES, VARIABLE_NAME, Map.of(BODY, DATA)); + } + + private void mockTrigger(Map payloadMap) + { + Map result = new HashMap<>(); + result.put(BODY, Mono.just(DATA)); + when(functionService.triggerFunction(RESOURCE_GROUP, APP_NAME, FUNCTION_NAME, payloadMap)).thenReturn(result); + } + + @Test + void shouldTriggerFunctionWithEmptyPayload() + { + Map payloadMap = Map.of(); + mockTrigger(payloadMap); + functionSteps.triggerFunction(FUNCTION_NAME, APP_NAME, RESOURCE_GROUP, "", SCOPES, VARIABLE_NAME); + verify(bddVariableContext).putVariable(SCOPES, VARIABLE_NAME, Map.of(BODY, DATA)); + verifyNoInteractions(jsonUtils); + } +} diff --git a/vividus-plugin-azure-functions/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/vividus-plugin-azure-functions/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..1f0955d450 --- /dev/null +++ b/vividus-plugin-azure-functions/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/vividus-tests/build.gradle b/vividus-tests/build.gradle index 0821b2302e..89305e8dcd 100644 --- a/vividus-tests/build.gradle +++ b/vividus-tests/build.gradle @@ -12,6 +12,7 @@ dependencies { implementation project(':vividus-plugin-aws-kinesis') implementation project(':vividus-plugin-aws-lambda') implementation project(':vividus-plugin-aws-s3') + implementation project(':vividus-plugin-azure-functions') implementation project(':vividus-plugin-browserstack') implementation project(':vividus-plugin-db') implementation project(':vividus-plugin-mobile-app') diff --git a/vividus-tests/src/main/resources/story/system/Azure - Functions.story b/vividus-tests/src/main/resources/story/system/Azure - Functions.story new file mode 100644 index 0000000000..2fb337da8b --- /dev/null +++ b/vividus-tests/src/main/resources/story/system/Azure - Functions.story @@ -0,0 +1,7 @@ +Meta: + @epic vividus-plugin-azure-functions + +Scenario: Trigger a function +When I trigger function `HttpTrigger1` from function app `vivdus-http-function` in resource group `vividus` with payload: +and save response into scenario variable `result` +Then `202` is equal to `${result.status-code}`