diff --git a/.github/workflows/maven-release.yml b/.github/workflows/maven-release.yml index 8e6ab4f..ebdcef6 100644 --- a/.github/workflows/maven-release.yml +++ b/.github/workflows/maven-release.yml @@ -75,7 +75,7 @@ jobs: run: echo "version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_OUTPUT - name: Publish to Maven Central if: ${{ !endsWith(steps.artefact_version.outputs.version, '-SNAPSHOT') && github.ref == 'refs/heads/main' }} - run: mvn --batch-mode deploy -P gpg-sign -P nexus-staging + run: mvn --batch-mode deploy -P gpg-sign -P nexus-staging -Dnexus-staging-maven-plugin.autoReleaseAfterClose=false - name: Publish to GitHub Packages if: ${{ !endsWith(steps.artefact_version.outputs.version, '-SNAPSHOT') && github.ref == 'refs/heads/main' }} run: mvn --batch-mode deploy -P gpg-sign -P deploy-github-packages diff --git a/.gitignore b/.gitignore index 523d664..29e1845 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ replay_pid* target/ *.iml dependency-reduced-pom.xml + +src/main/resources/webapp/*-admin/html/about.html +README.html diff --git a/.scripts/convert-readme.sh b/.scripts/convert-readme.sh new file mode 100755 index 0000000..413bcb1 --- /dev/null +++ b/.scripts/convert-readme.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -e + +# Check if a command is installed +function check_command() { + if ! command -v "$1" &> /dev/null; then + echo -e "\033[0;31m $1 is not installed! \033[0m" >&2 + echo -e "\033[0;31m Help for About page will not be generated! \033[0m" >&2 + + if [ -n "$FAIL_ON_CHECK_COMMANDS" ]; then + exit 1 + else + exit 0 + fi + fi +} + +# Check if required commands are installed +check_command jq +check_command curl +check_command awk + +INPUT_FILE="${1:-README.md}" +OUTPUT_FILE="${2:-README.html}" + +# Convert the markdown file to a JSON payload +JSON_PAYLOAD=$(jq -R -s '{"mode": "gfm", "text": .}' < "$INPUT_FILE") + +# Send the JSON payload to the GitHub API +CURL_OUTPUT=$(curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/markdown \ + -d "$JSON_PAYLOAD") + +# Process the curl output with awk to remove the Build, Installation and Changelog sections +MODIFIED_CONTENT=$(echo "$CURL_OUTPUT" | awk ' +/

Build<\/h2>/ {skip=1; next} +/

Polarion configuration<\/h2>/ {skip=0} +/

Changelog<\/h2>/ {skip=1; next} +!skip') + +# Write the modified content to the output file +echo "$MODIFIED_CONTENT" > "$OUTPUT_FILE" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..999e1c5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +## Changelog before migration to conventional commits + +| Version | Changes | +|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| v1.4.0 | About page help now is generating based on README.md | +| v1.3.1 | Converted JAX-RS controllers, filters and exception mappers to singletons | +| v1.3.0 | About page extended with help and icon | +| v1.2.0 | * Refactoring using field-related features from generic
* Update maven dependencies
* Delimited parameters fix
* Test case result error messages support | +| v1.1.0 | Added mandatory testRunTemplateId parameter | +| v1.0.0 | Initial release | diff --git a/README.md b/README.md index 069a77b..187a240 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -# Polarion ALM extension to <...> +# Cucumber Integration Extension for Polarion ALM + +This Polarion extension is designed to integrate BDD process into Polarion. +It provides the almost same functionality as Xray plugin for Jira: +- Cucumber feature tracked as WorkItem attachment and can be edited using Gherkin editor +- Cucumber features can be downloaded as .zip file according to provided criteria +- Execution results can be imported as Test Run into Polarion -This Polarion extension provides possibility to <...> ## Build This extension can be produced using maven: @@ -10,8 +15,8 @@ mvn clean package ## Installation to Polarion -To install the extension to Polarion `ch.sbb.polarion.extension.-.jar` -should be copied to `/polarion/extensions/ch.sbb.polarion.extension./eclipse/plugins` +To install the extension to Polarion `ch.sbb.polarion.extension.cucumber-.jar` +should be copied to `/polarion/extensions/ch.sbb.polarion.extension.cucumber/eclipse/plugins` It can be done manually or automated using maven build: ```bash mvn clean install -P install-to-local-polarion @@ -22,15 +27,127 @@ Changes only take effect after restart of Polarion. ## Polarion configuration -<...> +### Cucumber feature editor to appear on Test Case page + +1. Open a project which Test Case pages should display the editor +2. On the top of the project's navigation pane click ⚙ (Actions) ➙ 🔧 Administration. Project's administration page will be opened. +3. On the administration's navigation pane select Work Items ➙ Form Configuration. +4. On the form configuration page you will see 2 sections: Form Filters and Form Layouts. +5. In the table of Form Layouts section find line Test Case and click 📝 Edit +6. In opened Form Layout Configuration editor find a line with code: + ```xml + … + + … + ``` +7. Insert following new line after it: + ```xml + … + + … + ``` +8. Save changes by clicking 💾 Save + +### Test Run's description to appear on UI + +By default, Test Run's UI doesn't display its description (as a custom field). To enable displaying description following needs to be done: +1. Open a project which Test Run Templates needs to be modified. +2. In Navigation, click Test Runs. The Test Runs page opens. The top section of the page displays a table of existing Test Runs. +3. On the right hand side of the toolbar of the Test Runs page, click Manage Templates. The table of Test Runs is replaced by a table of Test Run Templates. +4. Select a template you want to modify. +5. On the toolbar of the detail pane, click ⚙ (Actions) ➙ Customize Test Run Page. +6. You will see a warning block containing text: + > This is a Test Run template, you cannot execute this test. Please create a Test Run first using this template +7. Click ✎ (Pencil) to edit code of this block, on the right hand side of the page you will see following lines of code: + ```velocity + #if($testRun.fields().isTemplate().get()) + $widgetContext.renderWarning("This is a Test Run template, you cannot execute this test. Please create a Test Run first using this template") + #end + #if($testRun.fields().records().is().empty() && $testRun.fields().selectTestCasesBy().optionId().equals("manualSelection")) + $widgetContext.renderInfo("Select the test cases you want to plan for execution by clicking Operations > Select Test Cases.") + #end + ``` +8. Add new lines: + ```velocity + … + #if(!$testRun.fields().description().is().empty()) + $testRun.fields.description.render().withText() + #end + ``` +9. Save template by clicking 💾 +### Custom fields: `Components` -## Extension Configuration +1. Create an enum containing all acceptable `components` names for selected project: + - go to `Project Administration -> Work Items -> Enumerations` + - use Work Item Type: `--Unspecific--`, Enumeration: `Custom` + provide own name e.g. `components` + - fill all possible values of this enum, e.g. `TA-Adapter`, `DailyTrainDataCalculator`, `ItisGateway`, etc + - save enum configuration +2. Create a custom field for selected project: + - go to `Project Administration -> Testing -> Test Run Custom Fields` + - use ID: `components`, Name: `Components`, Type: Enum (+ select enum name created on step 1) and enable `'Multi'` checkbox + - save configuration +3. Customize Test Run Report to show Components field: + - Test Runs -> on the right hand side of the toolbar of the Test Runs page, click Manage Templates and select a template you want to modify + - Place where it is needed widget "Script - Block" and put following code inside: + ```velocity + #if(!$testRun.fields().get("components").is().empty()) +

Components

+ $testRun.fields().get("components").render() + #end + ``` + - click 'Save' + - Now 'Components' field would appear on detail page if the field was sent during importing test results -<...> +### Custom fields: `Test Plan` +1. Create a custom field for selected project : + - go to `Project Administration -> Testing -> Test Run Custom Fields` + - use ID: `plans`, Name: `Test Plan`, Type: `Rich Text (multi-line)` + - save configuration +2. Customize Test Run Report to show `Test Plan` field: + - Test Runs -> on the right hand side of the toolbar of the Test Runs page, click Manage Templates and select a template you want to modify + - Place where it is needed widget "Script - Block" and put following code inside: + ```velocity + #if(!$testRun.fields().get("plans").is().empty()) +

Test plan

+ $testRun.fields().get("plans").render() + #end + ``` + - click 'Save' + - Now 'Test Plan' field would appear on Test Run detail page if the field was sent by maven-plugin. Also if current project contains some plan(s) with the id equal to given value - the value will be transformaed into a link to certain plan. +3. Customize Plan to show links to Test Runs: + - go to `Administration -> Plans -> Plan Custom Fields` and create custom field using ID: `testRunLinks`, Name: `Test Run Links`, Type: `Rich Text (multi-line)` + - go to `Plans` -> select any plan and click ⚙ -> `Customize Plan Report -> Customize Shared Report` + - Place where it is needed widget "Script - Block" and put following code inside: + ```velocity + #if(!$plan.fields().get("testRunLinks").is().empty()) +

Test runs

+ $plan.fields().get("testRunLinks").render() + #end + ``` + - click 'Save' + - Now 'Test Runs' field should contain links to the Test Runs which had created references to current plan on step 2. -## Usage -<...> +### Custom field: `Team-SBB` +1. Create an enum containing all acceptable team names for selected project: + - go to `Administration -> Work Items -> Enumerations` + - use Work Item Type: `--Unspecific--`, Enumeration: `Custom` + provide own name e.g. `team_sbb` + - fill all possible values of this enum, e.g. `Adler`, `Otter`, `Orca`, etc + - save enum configuration +2. Create a custom field which will keep the value (this step can be made in global configuration, but global fields will be visible only if the local configuration for test runs is deleted https://community.sw.siemens.com/s/question/0D54O00007Fh7iLSAR/do-global-test-run-custom-fields-work-locally): + - go to `Administration -> Testing -> Test Run Custom Fields` + - use ID: `team-sbb`, Name: `Team-SBB`, Type: `Enum` (+ select enum name created on step 1) +3. Customize Test Run Report to show Team-SBB field: + - Test Runs -> on the right hand side of the toolbar of the Test Runs page, click Manage Templates and select a template you want to modify + - Place where it is needed widget "Script - Block" and put following code inside: + ```velocity + #if(!$testRun.fields().get("teamSBB").is().empty()) +

Team-SBB

+ $testRun.fields().get("teamSBB").render() + #end + ``` + - click 'Save' + - Now 'Team-SBB' field would appear on detail page if the field was sent by maven-plugin diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..f202d30 --- /dev/null +++ b/lombok.config @@ -0,0 +1,4 @@ +# tells Lombok that this is the root directory and that it shouldn’t search parent directories for more configuration files +config.stopBubbling = true +# tells Lombok to add @lombok.Generated annotation to all generated methods +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7b346c9..1fc0c8f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,21 +5,21 @@ ch.sbb.polarion.extensions ch.sbb.polarion.extension.generic - 6.0.2 + 6.0.3 - ch.sbb.polarion.extension.extension-name - 0.0.0-SNAPSHOT + ch.sbb.polarion.extension.cucumber + 1.4.1-SNAPSHOT jar - ... extension of Polarion ALM - This is a Polarion extension which provides ... - https://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.extension-name + This Polarion extension is designed to integrate BDD process into Polarion + It provides the almost same functionality as Xray plugin for Jira. + https://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.cucumber The SBB License, Version 1.0 - https://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.extension-name/blob/main/LICENSES/SBB.txt + https://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.cucumber/blob/main/LICENSES/SBB.txt @@ -33,21 +33,22 @@ - scm:git:git://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.extension-name.git - scm:git:ssh://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.extension-name.git - http://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.extension-name/tree/main + scm:git:git://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.cucumber.git + scm:git:ssh://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.cucumber.git + http://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.cucumber/tree/main GitHub - https://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.extension-name/issues + https://github.com/SchweizerischeBundesbahnen/ch.sbb.polarion.extension.cucumber/issues - extension-name - ch.sbb.polarion.extension.extension_name + cucumber ${maven-jar-plugin.Extension-Context} + + 28.0.0 @@ -56,6 +57,12 @@ ch.sbb.polarion.extension.generic.app ${project.parent.version} + + + io.cucumber + gherkin + ${cucumber-gherkin.version} + diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationAdminUiServlet.java b/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationAdminUiServlet.java new file mode 100644 index 0000000..1f2616d --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationAdminUiServlet.java @@ -0,0 +1,15 @@ +package ch.sbb.polarion.extension.cucumber; + +import ch.sbb.polarion.extension.generic.GenericUiServlet; + +import java.io.Serial; + +public class CucumberIntegrationAdminUiServlet extends GenericUiServlet { + + @Serial + private static final long serialVersionUID = 6687845260037475974L; + + public CucumberIntegrationAdminUiServlet() { + super("cucumber-admin"); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationFormExtension.java b/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationFormExtension.java new file mode 100644 index 0000000..fcb3741 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationFormExtension.java @@ -0,0 +1,142 @@ +package ch.sbb.polarion.extension.cucumber; + +import ch.sbb.polarion.extension.generic.util.ExtensionInfo; +import com.polarion.alm.shared.api.SharedContext; +import com.polarion.alm.shared.api.transaction.TransactionalExecutor; +import com.polarion.alm.shared.api.utils.html.HtmlFragmentBuilder; +import com.polarion.alm.shared.api.utils.links.HtmlLinkFactory; +import com.polarion.alm.tracker.model.IAttachmentBase; +import com.polarion.alm.tracker.model.IWorkItem; +import com.polarion.alm.ui.server.forms.extensions.IFormExtension; +import com.polarion.alm.ui.server.forms.extensions.IFormExtensionContext; +import com.polarion.core.util.logging.Logger; +import com.polarion.platform.persistence.model.IPObject; +import com.polarion.platform.persistence.model.IPObjectList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.springframework.web.util.HtmlUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@SuppressWarnings({"java:S1192", "unchecked"}) +public class CucumberIntegrationFormExtension implements IFormExtension { + + public static final String ID = "cucumber"; + private static final Logger logger = Logger.getLogger(CucumberIntegrationFormExtension.class); + private final String bundleTimestamp = ExtensionInfo.getInstance().getVersion().getBundleBuildTimestampDigitsOnly(); + + @NotNull + public String getContent(IWorkItem workItem, IPObjectList attachments) throws IOException { + String content = ""; + List list = attachments.stream() + .filter(IAttachmentBase.class::isInstance) + .filter(x -> ((IAttachmentBase) x).getFileName().equals(workItem.getId() + ".feature")) + .toList(); + + for (IPObject attachment : list) { + byte[] attachmentContent = ((IAttachmentBase) attachment).getDataStream().readAllBytes(); + content = new String(attachmentContent, StandardCharsets.UTF_8); + } + + return content; + } + + @Override + @Nullable + public String render(@NotNull IFormExtensionContext context) { + boolean validateOnSave = Boolean.parseBoolean(context.attributes().getOrDefault("validateOnSave", "false")); + return TransactionalExecutor.executeSafelyInReadOnlyTransaction( + transaction -> renderIntegrationTest(transaction.context(), context.object().getOldApi(), validateOnSave)); + } + + @Override + @Nullable + public String getIcon(@NotNull IPObject object, @Nullable Map attributes) { + return null; + } + + @Override + @Nullable + public String getLabel(@NotNull IPObject object, @Nullable Map attributes) { + return "Cucumber Test"; + } + + public String renderIntegrationTest(@NotNull SharedContext context, @NotNull IPObject object, boolean validateOnSave) { + HtmlFragmentBuilder builder = context.createHtmlFragmentBuilderFor().gwt(); + + try { + if (object instanceof IWorkItem workItem) { + if (object.isPersisted()) { + IPObjectList attachments = workItem.getAttachments(); + + String content = getContent(workItem, attachments); + + displayContentInEditor(builder, workItem, content, validateOnSave); + } else { + builder.tag().div().append().text("Cucumber editor will be available after Work Item created."); + } + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + + builder.tag().div().append().tag().b().append().text("Unknown error - see server log for more information."); + } + + builder.finished(); + return builder.toString(); + } + + public void addSource(HtmlFragmentBuilder builder, String type, String url) { + builder.tag().script().attributes().type(type) + .src(HtmlLinkFactory.fromEncodedRelativeUrl(url)); + } + + private void addCss(HtmlFragmentBuilder builder) { + builder.html(""); + builder.html(""); + builder.html(""); + } + + private void displayContentInEditor(@NotNull HtmlFragmentBuilder builder, @NotNull IWorkItem workItem, + @NotNull String content, boolean validateOnSave) { + addCss(builder); + + String filename = workItem.getId() + ".feature"; + + builder.html("" + + "
" + + " " + + " " + + " " + + " " + + " " + + "
" + + "
" + + " " + + "
" + ); + + builder.html("" + + "
" + + "
" + + "
" + + "" + + ""); + + addSource(builder, "module", "/polarion/cucumber/ui/js/gherkin-editor.js?bundle=" + UUID.randomUUID()); + addSource(builder, "text/javascript", "/polarion/cucumber/ui/js/cucumber.js?bundle=" + bundleTimestamp); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationModule.java b/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationModule.java new file mode 100644 index 0000000..814527a --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationModule.java @@ -0,0 +1,12 @@ +package ch.sbb.polarion.extension.cucumber; + +import ch.sbb.polarion.extension.generic.GenericModule; +import com.polarion.alm.ui.server.forms.extensions.FormExtensionContribution; + +public class CucumberIntegrationModule extends GenericModule { + + @Override + protected FormExtensionContribution getFormExtensionContribution() { + return new FormExtensionContribution(CucumberIntegrationFormExtension.class, CucumberIntegrationFormExtension.ID); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationUiServlet.java b/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationUiServlet.java new file mode 100644 index 0000000..f79a11a --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationUiServlet.java @@ -0,0 +1,15 @@ +package ch.sbb.polarion.extension.cucumber; + +import ch.sbb.polarion.extension.generic.GenericUiServlet; + +import java.io.Serial; + +public class CucumberIntegrationUiServlet extends GenericUiServlet { + + @Serial + private static final long serialVersionUID = 7394283682969105251L; + + public CucumberIntegrationUiServlet() { + super("cucumber"); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/exception/TestRunCreationException.java b/src/main/java/ch/sbb/polarion/extension/cucumber/exception/TestRunCreationException.java new file mode 100644 index 0000000..79205e3 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/exception/TestRunCreationException.java @@ -0,0 +1,10 @@ +package ch.sbb.polarion.extension.cucumber.exception; + +public class TestRunCreationException extends RuntimeException { + + private static final long serialVersionUID = 2654551444203246155L; + + public TestRunCreationException(String message) { + super(message); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/helper/PolarionTestRun.java b/src/main/java/ch/sbb/polarion/extension/cucumber/helper/PolarionTestRun.java new file mode 100644 index 0000000..2d2eece --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/helper/PolarionTestRun.java @@ -0,0 +1,172 @@ +package ch.sbb.polarion.extension.cucumber.helper; + +import ch.sbb.polarion.extension.cucumber.exception.TestRunCreationException; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.ExecutionInfo; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.ExecutionRecord; +import ch.sbb.polarion.extension.cucumber.rest.model.fields.FieldType; +import ch.sbb.polarion.extension.generic.service.PolarionService; +import com.polarion.alm.projects.model.IProject; +import com.polarion.alm.tracker.ITestManagementService; +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.internal.model.StatusOpt; +import com.polarion.alm.tracker.model.IStatusOpt; +import com.polarion.alm.tracker.model.ITestRecord; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.alm.tracker.model.ITypeOpt; +import com.polarion.alm.tracker.model.IWorkItem; +import com.polarion.core.util.StringUtils; +import com.polarion.core.util.types.Text; +import com.polarion.platform.persistence.model.IPObjectList; +import com.polarion.platform.persistence.spi.EnumOption; +import com.polarion.subterra.base.data.model.ICustomField; +import lombok.experimental.UtilityClass; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.ws.rs.BadRequestException; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +@UtilityClass +public class PolarionTestRun { + + public static final String PASSED_STATUS = "passed"; + public static final int PASSED_STATUS_NUMBER = 3; + public static final String FAILED_STATUS = "failed"; + public static final int FAILED_STATUS_NUMBER = 2; + public static final String TYPE_REQUIRES_SIGNATURE = "requiresSignatureForTestCaseExecution"; + private static final String TIMESTAMP_PATTERN = "yyyyMMdd-HHmmssSSS"; + + public static List createTestRuns( + @NotNull PolarionService polarionService, + @NotNull ITestManagementService testManagementService, + @NotNull ExecutionInfo executionInfo, + @NotNull List executionResults) { + + String projectKey = getProjectKey(executionInfo); + if (projectKey == null) { + throw new TestRunCreationException("Project key not specified, skipping test run creation"); + } + + ITrackerService trackerService = polarionService.getTrackerService(); + IProject project = trackerService.getProjectsService().getProject(projectKey); + if (project == null) { + throw new TestRunCreationException(String.format("Project '%s' not found, skipping test run creation", projectKey)); + } + + ITestRun testRun = createTestRun(testManagementService, polarionService, project, executionInfo); + for (ExecutionRecord executionRecord : executionResults) { + addTestRecord(trackerService, testRun, executionRecord); + } + + testRun.setStatus(getTestRunStatus(testRun)); + testRun.save(); + + return Collections.singletonList(testRun); + } + + private static String getProjectKey(ExecutionInfo info) { + return info.getProject().getId() != null ? info.getProject().getId() : info.getProject().getKey(); + } + + private static @NotNull ITestRun createTestRun( + @NotNull ITestManagementService testManagementService, + @NotNull PolarionService polarionService, + @NotNull IProject project, + @NotNull ExecutionInfo executionInfo) { + + String testRunId = String.format("%s_%s", project.getId(), new SimpleDateFormat(TIMESTAMP_PATTERN).format(new Date())); + String testRunTemplateId = StringUtils.isEmpty(executionInfo.getTestRunTemplateId()) ? "xUnit Build Test" : executionInfo.getTestRunTemplateId(); + + ITestRun template = testManagementService.searchTestRunTemplates("project.id:" + project.getId(), null, -1) + .stream().filter(t -> t.getId().equals(testRunTemplateId)).findFirst().orElse(null); + if (template == null) { + throw new TestRunCreationException(String.format("Test run template '%s' not found in project '%s', skipping test run creation", testRunTemplateId, project.getId())); + } else { + ITypeOpt type = template.getType(); + if (type != null && Boolean.TRUE.equals(Boolean.parseBoolean(type.getProperty(TYPE_REQUIRES_SIGNATURE)))) { + throw new TestRunCreationException(String.format("Test run template '%s' of type '%s' requires signature for Test Case execution, skipping test run creation", testRunTemplateId, type.getName())); + } + } + + ITestRun testRun = testManagementService.createTestRun(project.getId(), testRunId, testRunTemplateId); + testRun.setTitle(executionInfo.getTitle()); + if (executionInfo.getDescription() != null) { + testRun.setCustomField("description", Text.html( + executionInfo.getDescription().replaceAll(System.lineSeparator(), "
"))); + } + fillCustomFields(polarionService, testRun, executionInfo); + + return testRun; + } + + private static void addTestRecord(@NotNull ITrackerService trackerService, + @NotNull ITestRun testRun, @NotNull ExecutionRecord executionRecord) { + + IWorkItem testCase = findTestCase(trackerService, testRun, executionRecord); + if (testCase != null) { + ITestRecord testRecord = testRun.addRecord(); + testRecord.setTestCase(testCase); + testRecord.setDuration(executionRecord.getDuration()); + testRecord.setExecuted(executionRecord.getDate()); + testRecord.setResult(executionRecord.getStatus()); + testRecord.setComment(executionRecord.getComment()); + } + } + + private static @Nullable IWorkItem findTestCase(@NotNull ITrackerService trackerService, + @NotNull ITestRun testRun, @NotNull ExecutionRecord executionRecord) { + + if (!executionRecord.getTestCaseIds().isEmpty()) { + for (String testCaseId : executionRecord.getTestCaseIds()) { + IWorkItem testCase = trackerService.findWorkItem(testRun.getProjectId(), testCaseId); + if (testCase != null) { + return testCase; + } + } + } + if (!StringUtils.isEmpty(executionRecord.getTestCaseTitle())) { + + @SuppressWarnings("unchecked") + IPObjectList list = trackerService.queryWorkItems( + String.format("project.id:%s AND type:testcase AND title:\"%s\"", + testRun.getProjectId(), executionRecord.getTestCaseTitle()), "id"); + if (!list.isEmpty()) { + return list.get(0); + } + } + return null; + } + + private static @Nullable IStatusOpt getTestRunStatus(@NotNull ITestRun testRun) { + if (!testRun.getAllRecords().isEmpty()) { + String status = testRun.getAllRecords().stream() + .map(testRecord -> testRecord.getResult().getId()) + .reduce(PASSED_STATUS, (a, b) -> a.equals(PASSED_STATUS) && b.equals(PASSED_STATUS) ? PASSED_STATUS : FAILED_STATUS); + if (status.equals(PASSED_STATUS)) { + return new StatusOpt(new EnumOption("testing/testrun-status", PASSED_STATUS, "Passed", PASSED_STATUS_NUMBER, false)); + } else { + return new StatusOpt(new EnumOption("testing/testrun-status", FAILED_STATUS, "Failed", FAILED_STATUS_NUMBER, false)); + } + } + return null; + } + + private static void fillCustomFields(PolarionService polarionService, ITestRun testRun, ExecutionInfo executionInfo) { + for (String customFieldId : executionInfo.getCustomFieldIds()) { + if (!testRun.getCustomFieldsList().contains(customFieldId)) { + throw new BadRequestException(String.format("Unknown custom field %s", customFieldId)); + } + ICustomField fieldPrototype = testRun.getCustomFieldPrototype(customFieldId); + FieldType fieldType = FieldType.fromIType(fieldPrototype.getType()); + if (fieldType == null) { + throw new BadRequestException(String.format("Unsupported type for custom field %s", customFieldId)); + } + Object valueToSet = fieldType.getConverter().convert( + polarionService.getTrackerService(), testRun, fieldPrototype, executionInfo.getFields().get(customFieldId)); + polarionService.setFieldValue(testRun, customFieldId, valueToSet); + } + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/helper/PolarionWorkItem.java b/src/main/java/ch/sbb/polarion/extension/cucumber/helper/PolarionWorkItem.java new file mode 100644 index 0000000..9f3a6ca --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/helper/PolarionWorkItem.java @@ -0,0 +1,86 @@ +package ch.sbb.polarion.extension.cucumber.helper; + +import ch.sbb.polarion.extension.cucumber.rest.model.Feature; +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.model.IAttachment; +import com.polarion.alm.tracker.model.IWorkItem; +import com.polarion.core.util.logging.Logger; +import lombok.experimental.UtilityClass; +import org.jetbrains.annotations.NotNull; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.NotFoundException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +@UtilityClass +public class PolarionWorkItem { + + private static final Logger logger = Logger.getLogger(PolarionWorkItem.class); + + @NotNull + public static IAttachment createOrUpdateFeature(@NotNull ITrackerService trackerService, @NotNull Feature feature) { + IWorkItem workItem = trackerService.findWorkItem(feature.getProjectId(), feature.getWorkItemId()); + + if (workItem.isUnresolvable()) { + throw new NotFoundException(String.format("WorkItem '%s/%s' not found!", feature.getProjectId(), feature.getWorkItemId())); + } + + IAttachment attachment = workItem.getAttachmentByFileName(feature.getFilename()); + + if (attachment == null) { + attachment = workItem.createAttachment(feature.getFilename(), feature.getTitle(), null); + } + + attachment.setValue("title", feature.getTitle()); + + try (InputStream inputStream = new ByteArrayInputStream(feature.getContent().getBytes(StandardCharsets.UTF_8))) { + attachment.setDataStream(inputStream); + attachment.save(); + return attachment; + } catch (IOException e) { + logger.error(e); + throw new InternalServerErrorException(e); + } + } + + @NotNull + public static Feature getFeature(@NotNull ITrackerService trackerService, @NotNull String projectId, @NotNull String workItemId) { + IWorkItem workItem = trackerService.findWorkItem(projectId, workItemId); + + if (workItem.isUnresolvable()) { + throw new NotFoundException(String.format("WorkItem '%s/%s' not found!", projectId, workItemId)); + } + + String filename = workItemId + ".feature"; + IAttachment attachment = workItem.getAttachmentByFileName(filename); + + if (attachment == null) { + throw new NotFoundException(String.format("WorkItem '%s/%s' doesn't have attachment with filename '%s'!", projectId, workItemId, filename)); + } + String title = attachment.getTitle(); + + try (InputStream inputStream = attachment.getDataStream()) { + String content = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + return new Feature(projectId, workItemId, title, filename, content); + } catch (IOException e) { + logger.error(e); + throw new InternalServerErrorException(e); + } + } + + @NotNull + public static Feature getFeature(@NotNull ITrackerService trackerService, @NotNull String fullWorkItemId) { + String[] tokens = fullWorkItemId.split("/"); + if (tokens.length != 2) { + throw new BadRequestException("Provided workitem id is not in 'projectId/workItemId' format: " + fullWorkItemId); + } + String projectId = tokens[0]; + String workItemId = tokens[1]; + + return getFeature(trackerService, projectId, workItemId); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/CucumberIntegrationRestApplication.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/CucumberIntegrationRestApplication.java new file mode 100644 index 0000000..7507f9f --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/CucumberIntegrationRestApplication.java @@ -0,0 +1,33 @@ +package ch.sbb.polarion.extension.cucumber.rest; + +import ch.sbb.polarion.extension.cucumber.rest.controller.ApiController; +import ch.sbb.polarion.extension.cucumber.rest.controller.InternalController; +import ch.sbb.polarion.extension.cucumber.rest.controller.JiraRestApiController; +import ch.sbb.polarion.extension.cucumber.rest.controller.XrayExportCucumberTestsController; +import ch.sbb.polarion.extension.cucumber.rest.controller.XrayImportExecutionResultsController; +import ch.sbb.polarion.extension.cucumber.rest.exception.TestRunCreationExceptionMapper; +import ch.sbb.polarion.extension.generic.rest.GenericRestApplication; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +public class CucumberIntegrationRestApplication extends GenericRestApplication { + + @Override + @NotNull + protected Set getExtensionExceptionMapperSingletons() { + return Set.of(new TestRunCreationExceptionMapper()); + } + + @Override + @NotNull + protected Set getExtensionControllerSingletons() { + return Set.of( + new ApiController(), + new InternalController(), + new JiraRestApiController(), + new XrayExportCucumberTestsController(), + new XrayImportExecutionResultsController() + ); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/ApiController.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/ApiController.java new file mode 100644 index 0000000..3c3b16b --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/ApiController.java @@ -0,0 +1,22 @@ +package ch.sbb.polarion.extension.cucumber.rest.controller; + +import ch.sbb.polarion.extension.cucumber.rest.model.Feature; +import ch.sbb.polarion.extension.generic.rest.filter.Secured; + +import javax.ws.rs.Path; + +@Secured +@Path("/api") +public class ApiController extends InternalController { + + @Override + public Feature getFeature(String projectId, String workItemId) { + return polarionService.callPrivileged(() -> super.getFeature(projectId, workItemId)); + } + + @Override + public void createOrUpdateFeature(Feature feature) { + polarionService.callPrivileged(() -> super.createOrUpdateFeature(feature)); + } + +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/InternalController.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/InternalController.java new file mode 100644 index 0000000..9030edb --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/InternalController.java @@ -0,0 +1,69 @@ +package ch.sbb.polarion.extension.cucumber.rest.controller; + +import ch.sbb.polarion.extension.cucumber.helper.PolarionWorkItem; +import ch.sbb.polarion.extension.cucumber.rest.model.Feature; +import ch.sbb.polarion.extension.cucumber.rest.model.ValidationError; +import ch.sbb.polarion.extension.cucumber.rest.model.ValidationResult; +import ch.sbb.polarion.extension.generic.service.PolarionService; +import com.polarion.alm.shared.api.transaction.TransactionalExecutor; +import io.cucumber.gherkin.GherkinParser; +import io.cucumber.messages.types.Envelope; +import io.cucumber.messages.types.Source; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import java.util.List; + +import static io.cucumber.messages.types.SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN; + +@Tag(name = "Features") +@Hidden +@Path("/internal") +public class InternalController { + + protected final PolarionService polarionService = new PolarionService(); + + @Operation(summary = "Get feature") + @GET + @Path("/feature/{projectId}/{workItemId}") + @Produces(MediaType.APPLICATION_JSON) + public Feature getFeature(@PathParam("projectId") String projectId, @PathParam("workItemId") String workItemId) { + + return TransactionalExecutor.executeSafelyInReadOnlyTransaction( + transaction -> PolarionWorkItem.getFeature(polarionService.getTrackerService(), projectId, workItemId)); + } + + @Operation(summary = "Create/update feature") + @POST + @Path("/feature") + @Consumes(MediaType.APPLICATION_JSON) + public void createOrUpdateFeature(Feature feature) { + TransactionalExecutor.executeInWriteTransaction( + transaction -> PolarionWorkItem.createOrUpdateFeature(polarionService.getTrackerService(), feature)); + } + + + @Hidden + @POST + @Path("/cucumber/validate") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.APPLICATION_JSON) + public ValidationResult validateCucumber(String cucumberFeature) { + GherkinParser parser = GherkinParser.builder().build(); + final Envelope envelope = Envelope.of(new Source("some.feature", cucumberFeature, TEXT_X_CUCUMBER_GHERKIN_PLAIN)); + List errors = parser.parse(envelope) + .filter(e -> e.getParseError().isPresent()) + .map(e -> ValidationError.fromParseError(e.getParseError().get())) + .toList(); + + return ValidationResult.builder().result(errors.isEmpty() ? "valid" : "not-valid").errors(errors).build(); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/JiraRestApiController.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/JiraRestApiController.java new file mode 100644 index 0000000..420ffb3 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/JiraRestApiController.java @@ -0,0 +1,75 @@ +package ch.sbb.polarion.extension.cucumber.rest.controller; + +import ch.sbb.polarion.extension.cucumber.rest.model.fields.FieldDefinition; +import ch.sbb.polarion.extension.cucumber.rest.model.fields.FieldType; +import ch.sbb.polarion.extension.generic.fields.model.FieldMetadata; +import ch.sbb.polarion.extension.generic.rest.filter.Secured; +import ch.sbb.polarion.extension.generic.service.PolarionService; +import com.polarion.alm.projects.model.IProject; +import com.polarion.alm.shared.api.transaction.TransactionalExecutor; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.platform.guice.internal.GuicePlatform; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +@Tag(name = "Fields") +@Secured +@Path("/api/2") +public class JiraRestApiController { + + public static final String PROJECT_ID_HEADER = "projectId"; + + private final PolarionService polarionService; + + @SuppressWarnings("unused") + public JiraRestApiController() { + this(new PolarionService()); + } + + public JiraRestApiController(PolarionService polarionService) { + this.polarionService = polarionService; + GuicePlatform.getGlobalInjector().injectMembers(this); + } + + @Operation(summary = "Get declared fields list for project") + @GET + @Path("/field") + @Produces(MediaType.APPLICATION_JSON) + public List field(@HeaderParam(value = PROJECT_ID_HEADER) String projectId) { + if (projectId == null || projectId.isBlank()) { + throw new BadRequestException(String.format("No proper %s header found!", PROJECT_ID_HEADER)); + } + + return polarionService.callPrivileged(() -> + TransactionalExecutor.executeInReadOnlyTransaction(transaction -> { + IProject project = polarionService.getProject(projectId); // validate provided projectId + + List resultList = new ArrayList<>(); + Collection customFields = polarionService.getCustomFields(ITestRun.PROTO, project.getContextId(), null); + for (FieldMetadata customField : customFields) { + //filter out unsupported types + if (FieldType.fromIType(customField.getType()) != null) { + resultList.add(FieldDefinition.fromFieldMetadata(customField)); + } + } + return addStaticFields(resultList); + }) + ); + } + + private List addStaticFields(List resultList) { + return Stream.concat(resultList.stream(), Stream.of(FieldDefinition.TITLE, FieldDefinition.DESCRIPTION)) + .toList(); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayExportCucumberTestsController.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayExportCucumberTestsController.java new file mode 100644 index 0000000..abbe053 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayExportCucumberTestsController.java @@ -0,0 +1,160 @@ +package ch.sbb.polarion.extension.cucumber.rest.controller; + +import ch.sbb.polarion.extension.cucumber.helper.PolarionWorkItem; +import ch.sbb.polarion.extension.cucumber.rest.model.Feature; +import ch.sbb.polarion.extension.generic.rest.filter.Secured; +import ch.sbb.polarion.extension.generic.service.PolarionService; +import com.polarion.alm.shared.api.transaction.TransactionalExecutor; +import com.polarion.alm.tracker.model.IWorkItem; +import com.polarion.core.util.logging.Logger; +import com.polarion.platform.persistence.model.IPObjectList; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipOutputStream; + +@Tag(name = "Export") +@Secured +@Path("/raven/1.0/export") + +public class XrayExportCucumberTestsController { + + private static final Logger logger = Logger.getLogger(XrayExportCucumberTestsController.class); + + private final PolarionService polarionService; + + @SuppressWarnings("unused") + public XrayExportCucumberTestsController() { + this(new PolarionService()); + } + + XrayExportCucumberTestsController(PolarionService polarionService) { + this.polarionService = polarionService; + } + + @Operation(summary = "Get features list") + @GET + @Path("/test") + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response exportTest( + @Parameter(description = "String which contain list of work items information (e.g. 'elibrary/EL-103;drivepilot/DP-47')") @QueryParam("keys") String keys, + @Parameter(description = "Query which will be passed to the trackerService#queryWorkItems method") @QueryParam("filter") String query, + @Parameter(description = "True - the response will contain zip-archive with all workitems found, false - only first workitem with no archiving") @QueryParam("fz") boolean fz) { + + List features = polarionService.callPrivileged(() -> TransactionalExecutor.executeSafelyInReadOnlyTransaction(transaction -> findFeatures(keys, query))); + + return createResponse(features, fz); + } + + @NotNull + private List findFeatures(@Nullable String keys, @Nullable String query) { + List result = new ArrayList<>(); + + if (keys != null) { + result.addAll(findFeaturesByWorkItemIds(keys)); + } + if (query != null) { + result.addAll(findFeaturesByQuery(query)); + } + + return result; + } + + @NotNull + private List findFeaturesByWorkItemIds(@NotNull String keys) { + List result = new ArrayList<>(); + + String[] workItemIds = keys.split(";"); + for (String workItemId : workItemIds) { + Feature feature = PolarionWorkItem.getFeature(polarionService.getTrackerService(), workItemId); + result.add(feature); + } + + return result; + } + + @NotNull + @SuppressWarnings("squid:S1166") // no need to log or rethrow exception by design + private List findFeaturesByQuery(@NotNull String query) { + List result = new ArrayList<>(); + + @SuppressWarnings("unchecked") + IPObjectList workItemList = polarionService.getTrackerService().queryWorkItems(query, "id"); + workItemList.forEach(wi -> { + try { + Feature feature = PolarionWorkItem.getFeature(polarionService.getTrackerService(), wi.getProjectId(), wi.getId()); + result.add(feature); + } catch (NotFoundException e) { + // nothing to do: test case has no cucumber feature + } + }); + + return result; + } + + private Response createResponse(List features, boolean fz) { + if (features.isEmpty()) { + throw new BadRequestException("No cucumber features found!"); + } + + if (fz) { + return createZipFileResponse(features); + } else { + return createSingleFeatureResponse(features.get(0)); + } + } + + private Response createSingleFeatureResponse(@NotNull Feature feature) { + StreamingOutput streamingOutput = new StreamingOutput() { + @Override + public void write(OutputStream outputStream) throws IOException, WebApplicationException { + outputStream.write(feature.getContent().getBytes(StandardCharsets.UTF_8)); + } + }; + + return Response.ok(streamingOutput).build(); + } + + private Response createZipFileResponse(@NotNull List features) { + StreamingOutput streamingOutput = new StreamingOutput() { + @Override + public void write(OutputStream outputStream) throws IOException, WebApplicationException { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { + for (Feature feature : features) { + try { + String filename = feature.getTitle() != null ? feature.getTitle() : feature.getFilename(); + ZipEntry zipEntry = new ZipEntry(filename); + zipOutputStream.putNextEntry(zipEntry); + zipOutputStream.write(feature.getContent().getBytes(StandardCharsets.UTF_8)); + zipOutputStream.closeEntry(); + } catch (ZipException e) { + logger.error(e); + } + } + } + } + }; + + return Response.ok(streamingOutput).build(); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayImportExecutionResultsController.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayImportExecutionResultsController.java new file mode 100644 index 0000000..445ea28 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayImportExecutionResultsController.java @@ -0,0 +1,181 @@ +package ch.sbb.polarion.extension.cucumber.rest.controller; + +import ch.sbb.polarion.extension.cucumber.helper.PolarionTestRun; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.ExecutionInfo; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.ExecutionRecord; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.ImportExecutionResponse; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.TestExecIssue; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.CucumberExecutionResult; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.junit.TestSuite; +import ch.sbb.polarion.extension.generic.rest.filter.Secured; +import ch.sbb.polarion.extension.generic.service.PolarionService; +import com.google.inject.Inject; +import com.polarion.alm.shared.api.transaction.TransactionalExecutor; +import com.polarion.alm.tracker.ITestManagementService; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.core.util.StringUtils; +import com.polarion.core.util.logging.Logger; +import com.polarion.platform.guice.internal.GuicePlatform; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.FormDataParam; +import org.jetbrains.annotations.VisibleForTesting; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Tag(name = "Import") +@Secured +@Path("/raven/1.0/import/execution") +@SuppressWarnings("unused") +public class XrayImportExecutionResultsController { + + private static final Logger logger = Logger.getLogger(XrayImportExecutionResultsController.class); + + private final PolarionService polarionService; + + private ITestManagementService testManagementService; + + @Context + private UriInfo uriInfo; + + public XrayImportExecutionResultsController() { + this(new PolarionService()); + } + + public XrayImportExecutionResultsController(PolarionService polarionService) { + this.polarionService = polarionService; + GuicePlatform.getGlobalInjector().injectMembers(this); + } + + @Operation(summary = "Import cucumber test result (multipart)") + @POST + @Path("/cucumber/multipart") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public ImportExecutionResponse importExecutionCucumberMultipart( + @FormDataParam("info") FormDataBodyPart info, + @FormDataParam("result") FormDataBodyPart result) { + info.setMediaType(MediaType.APPLICATION_JSON_TYPE); + result.setMediaType(MediaType.APPLICATION_JSON_TYPE); + + logger.info("cucumberExecutionInfo = " + info.getValueAs(String.class)); + logger.info("cucumberExecutionResult = " + result.getValueAs(String.class)); + + ExecutionInfo executionInfo = info.getValueAs(ExecutionInfo.class); + CucumberExecutionResult[] cucumberExecutionResults = result.getValueAs(CucumberExecutionResult[].class); + + List testRuns = polarionService.callPrivileged( + () -> TransactionalExecutor.executeInWriteTransaction(transaction -> PolarionTestRun.createTestRuns( + polarionService, + testManagementService, + executionInfo, + ExecutionRecord.fromCucumberExecutions(Arrays.asList(cucumberExecutionResults)))) + ); + + return createImportExecutionResponse(testRuns); + } + + @Operation(summary = "Import JUnit test result") + @POST + @Path("/junit") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public ImportExecutionResponse importExecutionJunit( + @QueryParam("projectKey") String projectKey, + @QueryParam("testExecKey") String testExecKey, + @QueryParam("testPlanKey") String testPlanKey, + @QueryParam("testEnvironments") String testEnvironments, + @QueryParam("revision") String revision, + @QueryParam("fixVersion") String fixVersion, + @FormDataParam("file") FormDataBodyPart file) { + file.setMediaType(MediaType.APPLICATION_XML_TYPE); + + if (StringUtils.isEmpty(projectKey) && StringUtils.isEmpty(testExecKey)) { + throw new BadRequestException("projectKey or testExecKey must be provided"); + } + + ExecutionInfo info = new ExecutionInfo(); + Map projectMap = new HashMap<>(); + projectMap.put("id", projectKey); + projectMap.put("key", testExecKey); + info.getFields().put(ExecutionInfo.PROJECT_KEY, projectMap); + + return importJUnitTests(info, file); + } + + private ImportExecutionResponse importJUnitTests(ExecutionInfo info, FormDataBodyPart file) { + TestSuite testSuite = file.getValueAs(TestSuite.class); + List testRuns = polarionService.callPrivileged( + () -> TransactionalExecutor.executeInWriteTransaction(transaction -> PolarionTestRun.createTestRuns( + polarionService, + testManagementService, + info, + ExecutionRecord.fromJUnitReport(testSuite))) + ); + return createImportExecutionResponse(testRuns); + } + + @Operation(summary = "Import JUnit test result (multipart)") + @POST + @Path("/junit/multipart") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public ImportExecutionResponse importExecutionJunitMultipart( + @FormDataParam("info") FormDataBodyPart info, + @FormDataParam("file") FormDataBodyPart file) { + info.setMediaType(MediaType.APPLICATION_JSON_TYPE); + file.setMediaType(MediaType.APPLICATION_XML_TYPE); + + ExecutionInfo executionInfo = info.getValueAs(ExecutionInfo.class); + return importJUnitTests(executionInfo, file); + } + + private ImportExecutionResponse createImportExecutionResponse(List testRuns) { + ITestRun testRun = testRuns.get(0); + String testRunURL = getBaseUri() + "/polarion/#/project/" + testRun.getProjectId() + "/testrun?id=" + testRun.getId(); + TestExecIssue testExecIssue = new TestExecIssue(testRun.getProjectId() + "/" + testRun.getId(), testRun.getId(), testRunURL); + return new ImportExecutionResponse(testExecIssue); + } + + private URI getBaseUri() { + try { + return new URI( + uriInfo.getBaseUri().getScheme(), + null, + uriInfo.getBaseUri().getHost(), + uriInfo.getBaseUri().getPort(), + null, + null, + null); + } catch (URISyntaxException e) { + logger.error(e); + return null; + } + } + + @VisibleForTesting + public XrayImportExecutionResultsController withUriInfo(UriInfo uriInfo) { + this.uriInfo = uriInfo; + return this; + } + + @Inject + public void setTestManagementService(ITestManagementService testManagementService) { + this.testManagementService = testManagementService; + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/exception/TestRunCreationExceptionMapper.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/exception/TestRunCreationExceptionMapper.java new file mode 100644 index 0000000..4d55772 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/exception/TestRunCreationExceptionMapper.java @@ -0,0 +1,17 @@ +package ch.sbb.polarion.extension.cucumber.rest.exception; + +import ch.sbb.polarion.extension.cucumber.exception.TestRunCreationException; + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class TestRunCreationExceptionMapper implements ExceptionMapper { + + public Response toResponse(TestRunCreationException e) { + return Response.status(Response.Status.BAD_REQUEST.getStatusCode()) + .entity(e.getMessage()) + .build(); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/Feature.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/Feature.java new file mode 100644 index 0000000..08af424 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/Feature.java @@ -0,0 +1,16 @@ +package ch.sbb.polarion.extension.cucumber.rest.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Feature { + private String projectId; + private String workItemId; + private String title; + private String filename; + private String content; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/ValidationError.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/ValidationError.java new file mode 100644 index 0000000..d456a82 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/ValidationError.java @@ -0,0 +1,27 @@ +package ch.sbb.polarion.extension.cucumber.rest.model; + +import io.cucumber.messages.types.ParseError; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ValidationError { + private String message; + private Long line; + private Long column; + + public static ValidationError fromParseError(ParseError parseError) { + ValidationError validationError = new ValidationError(); + validationError.setMessage(parseError.getMessage()); + if (parseError.getSource() != null) { + parseError.getSource().getLocation().ifPresent(location -> { + validationError.setLine(location.getLine()); + location.getColumn().ifPresent(validationError::setColumn); + }); + } + return validationError; + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/ValidationResult.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/ValidationResult.java new file mode 100644 index 0000000..71c6f72 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/ValidationResult.java @@ -0,0 +1,17 @@ +package ch.sbb.polarion.extension.cucumber.rest.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ValidationResult { + private String result; + private List errors; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ExecutionInfo.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ExecutionInfo.java new file mode 100644 index 0000000..b92d6b8 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ExecutionInfo.java @@ -0,0 +1,62 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution; + +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.Project; +import ch.sbb.polarion.extension.cucumber.rest.model.fields.FieldDefinition; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ExecutionInfo { + + public static final String PROJECT_KEY = "project"; + + private final Map fields = new HashMap<>(); + private Project project; + + @SuppressWarnings("unused") + public Map getFields() { + return fields; + } + + public String getTitle() { + return getString(FieldDefinition.TITLE.getId()); + } + + public String getDescription() { + return getString(FieldDefinition.DESCRIPTION.getId()); + } + + public String getTestRunTemplateId() { + return getString(FieldDefinition.TEST_RUN_TEMPLATE_ID.getId()); + } + + public String getString(String key) { + return (String) fields.get(key); + } + + public Set getCustomFieldIds() { + Set nonCustomFieldIds = new HashSet<>(Arrays.asList(PROJECT_KEY, + FieldDefinition.TITLE.getId(), FieldDefinition.DESCRIPTION.getId(), FieldDefinition.TEST_RUN_TEMPLATE_ID.getId())); + return fields.keySet().stream().filter(id -> !nonCustomFieldIds.contains(id)).collect(Collectors.toSet()); + } + + public Project getProject() { + if (project == null) { + project = Project.fromMap(fields); + } + return project; + } + + @Override + public String toString() { + return "CucumberExecutionInfo{" + + "fields=" + fields + + '}'; + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ExecutionRecord.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ExecutionRecord.java new file mode 100644 index 0000000..51d5272 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ExecutionRecord.java @@ -0,0 +1,178 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution; + +import ch.sbb.polarion.extension.cucumber.helper.PolarionTestRun; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.CucumberExecutionResult; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.Element; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.Tag; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.junit.TestCase; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.junit.TestSuite; +import com.polarion.core.util.StringUtils; +import com.polarion.core.util.types.Text; +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Getter +public class ExecutionRecord { + + public static final int NANOSECONDS_IN_SECOND = 1000000000; + private final List testCaseIds = new ArrayList<>(); + private Date date; + private Float duration; + private String status; + private Text comment; + private String testCaseTitle; + + public static List fromJUnitReport(TestSuite testSuite) { + List resultList = new ArrayList<>(); + + for (TestCase testCase : testSuite.getTestCases()) { + ExecutionRecord executionRecord = new ExecutionRecord(); + executionRecord.duration = testCase.getTime() != null ? testCase.getTime().floatValue() : null; + executionRecord.date = new Date(); // using current date (probably must be filled from the source testCase/testSuite?) + executionRecord.status = getJUnitRecordStatus(testCase); + executionRecord.testCaseTitle = testCase.getName(); + resultList.add(executionRecord); + } + + return resultList; + } + + public static List fromCucumberExecutions(List results) { + List resultList = new ArrayList<>(); + for (CucumberExecutionResult result : results) { + Iterator iter = result.getElements().iterator(); + + Element elem; + Element backgroundElement = null; + Element scenarioElement; + while (iter.hasNext()) { + elem = iter.next(); + backgroundElement = + backgroundElement == null ? getElementByType("background", elem) : backgroundElement; + + if ("background".equals(elem.getType()) && backgroundElement != null) { + continue; + } + + scenarioElement = getElementByType("scenario", elem); + + if (scenarioElement != null) { + resultList.add(getExecutionRecord(backgroundElement, scenarioElement)); + } + + backgroundElement = null; + } + } + return resultList; + } + + @NotNull + private static ExecutionRecord getExecutionRecord(Element backgroundElement, Element scenarioElement) { + ExecutionRecord executionRecord = new ExecutionRecord(); + executionRecord.duration = getTestRecordDuration(backgroundElement, scenarioElement); + executionRecord.date = scenarioElement.getStartTimestamp() != null ? scenarioElement.getStartTimestamp() : new Date(); + executionRecord.status = getTestRecordStatus(backgroundElement, scenarioElement); + executionRecord.comment = getTestRecordComment(backgroundElement, scenarioElement); + + if (scenarioElement.getTags() != null) { + executionRecord.testCaseIds.addAll( + scenarioElement.getTags().stream() + .map(ExecutionRecord::getTagName) + .filter(Objects::nonNull) + .filter(tag -> tag.matches("^[a-zA-Z]*-\\d*$")) + .toList() + ); + } + return executionRecord; + } + + private static Element getElementByType(String expectedElementType, Element element) { + return expectedElementType.equals(element.getType()) ? element : null; + } + + private static @Nullable Float getTestRecordDuration(@Nullable Element backgroundElement, @NotNull Element scenarioElement) { + Float duration = null; + if (backgroundElement != null && backgroundElement.getSteps() != null && !backgroundElement.getSteps().isEmpty()) { + duration = getDuration(backgroundElement); + } + if (scenarioElement.getSteps() != null && !scenarioElement.getSteps().isEmpty()) { + float scenarioDuration = getDuration(scenarioElement); + duration = duration == null ? scenarioDuration : duration + scenarioDuration; + } + return duration; + } + + private static float getDuration(@NotNull Element scenarioElement) { + return scenarioElement.getSteps().stream() + .map(step -> step.getResult().getDuration()) + .reduce((long) 0, Long::sum).floatValue() / NANOSECONDS_IN_SECOND; + } + + private static @Nullable String getTestRecordStatus(@Nullable Element backgroundElement, @NotNull Element scenarioElement) { + String backgroundStatus = null; + if (backgroundElement != null && backgroundElement.getSteps() != null && !backgroundElement.getSteps().isEmpty()) { + backgroundStatus = getStatus(backgroundElement); + } + + String scenarioStatus = null; + if (scenarioElement.getSteps() != null && !scenarioElement.getSteps().isEmpty()) { + scenarioStatus = getStatus(scenarioElement); + } + + if (backgroundStatus == null) { + return scenarioStatus; + } else { + return scenarioStatus == null ? backgroundStatus : getCombinedStatus(backgroundStatus, scenarioStatus); + } + } + + @NotNull + private static String getCombinedStatus(String backgroundStatus, String scenarioStatus) { + return PolarionTestRun.PASSED_STATUS.equals(backgroundStatus) && PolarionTestRun.PASSED_STATUS.equals(scenarioStatus) ? PolarionTestRun.PASSED_STATUS : PolarionTestRun.FAILED_STATUS; + } + + @NotNull + private static String getStatus(@NotNull Element backgroundElement) { + return backgroundElement.getSteps().stream() + .map(step -> step.getResult().getStatus()) + .reduce(PolarionTestRun.PASSED_STATUS, (a, b) -> a.equals(PolarionTestRun.PASSED_STATUS) && b.equals(PolarionTestRun.PASSED_STATUS) ? PolarionTestRun.PASSED_STATUS : PolarionTestRun.FAILED_STATUS); + } + + private static @Nullable Text getTestRecordComment(@Nullable Element backgroundElement, @NotNull Element scenarioElement) { + return Stream.concat(getErrorMessages(backgroundElement).stream(), getErrorMessages(scenarioElement).stream()) + .filter(errorMessage -> !StringUtils.isEmptyTrimmed(errorMessage)) + .collect(Collectors.collectingAndThen( + Collectors.joining(System.lineSeparator()), + result -> StringUtils.isEmpty(result) ? null : Text.plain(result) + )); + } + + private static List getErrorMessages(@Nullable Element element) { + return element == null || element.getSteps() == null ? new ArrayList<>() : element.getSteps().stream() + .map(step -> step.getResult().getErrorMessage()) + .filter(errorMessage -> !StringUtils.isEmptyTrimmed(errorMessage)) + .toList(); + } + + private static @Nullable String getTagName(@NotNull Tag tag) { + if (tag.getName() != null && tag.getName().startsWith("@")) { + return tag.getName().length() > 1 ? tag.getName().substring(1) : null; + } + return tag.getName(); + } + + private static String getJUnitRecordStatus(TestCase testCase) { + return testCase.getSkipped() == null && + (testCase.getFailures() == null || testCase.getFailures().isEmpty()) && + (testCase.getErrors() == null || testCase.getErrors().isEmpty()) ? "passed" : "failed"; + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ImportExecutionResponse.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ImportExecutionResponse.java new file mode 100644 index 0000000..b0d240e --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ImportExecutionResponse.java @@ -0,0 +1,12 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ImportExecutionResponse { + private TestExecIssue testExecIssue; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/TestExecIssue.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/TestExecIssue.java new file mode 100644 index 0000000..54bce58 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/TestExecIssue.java @@ -0,0 +1,14 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TestExecIssue { + private String id; + private String key; + private String self; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/After.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/After.java new file mode 100644 index 0000000..1a47640 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/After.java @@ -0,0 +1,15 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class After { + private Result result; + private Match match; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Argument.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Argument.java new file mode 100644 index 0000000..3cf5431 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Argument.java @@ -0,0 +1,15 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Argument { + private int offset; + private String val; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Comment.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Comment.java new file mode 100644 index 0000000..7dbc3bc --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Comment.java @@ -0,0 +1,15 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Comment { + private String value; + private int line; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/CucumberExecutionResult.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/CucumberExecutionResult.java new file mode 100644 index 0000000..82db70f --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/CucumberExecutionResult.java @@ -0,0 +1,24 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@SuppressWarnings("java:S107") +@JsonIgnoreProperties(ignoreUnknown = true) +public class CucumberExecutionResult { + private String keyword; + private String name; + private int line; + private String description; + private List tags; + private String id; + private String uri; + private List elements; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Element.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Element.java new file mode 100644 index 0000000..6743701 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Element.java @@ -0,0 +1,30 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Date; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@SuppressWarnings({"java:S107"}) +@JsonIgnoreProperties(ignoreUnknown = true) +public class Element { + @JsonProperty("start_timestamp") + private Date startTimestamp; + private int line; + private String id; + private String name; + private String description; + private String type; + private String keyword; + private List after; + private List steps; + private List tags; + private List comments; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Embedding.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Embedding.java new file mode 100644 index 0000000..b894651 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Embedding.java @@ -0,0 +1,17 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Embedding { + @JsonProperty("mime_type") + private String mimeType; + private String data; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Location.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Location.java new file mode 100644 index 0000000..016372f --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Location.java @@ -0,0 +1,15 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Location { + private int line; + private int column; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Match.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Match.java new file mode 100644 index 0000000..2bb1870 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Match.java @@ -0,0 +1,17 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Match { + private List arguments; + private String location; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Project.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Project.java new file mode 100644 index 0000000..093c087 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Project.java @@ -0,0 +1,26 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +import static ch.sbb.polarion.extension.cucumber.rest.model.execution.ExecutionInfo.PROJECT_KEY; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Project { + private String id; + private String key; + + public static Project fromMap(Map map) { + + @SuppressWarnings("unchecked") + Map projectInfoMap = (Map) map.get(PROJECT_KEY); + return new Project(projectInfoMap.get("id"), projectInfoMap.get("key")); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Result.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Result.java new file mode 100644 index 0000000..d19aebb --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Result.java @@ -0,0 +1,18 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Result { + private String status; + private long duration; + @JsonProperty("error_message") + private String errorMessage; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Row.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Row.java new file mode 100644 index 0000000..4904434 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Row.java @@ -0,0 +1,16 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Row { + private List cells; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Step.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Step.java new file mode 100644 index 0000000..03772dc --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Step.java @@ -0,0 +1,23 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@SuppressWarnings("java:S107") +@JsonIgnoreProperties(ignoreUnknown = true) +public class Step { + private List embeddings; + private String keyword; + private String name; + private int line; + private Match match; + private Result result; + private List rows; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Tag.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Tag.java new file mode 100644 index 0000000..a5a3b59 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/cucumber/Tag.java @@ -0,0 +1,17 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Tag { + private String name; + private int line; + private String type; + private Location location; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Error.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Error.java new file mode 100644 index 0000000..602ab43 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Error.java @@ -0,0 +1,22 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.junit; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +public class Error { + + @XmlAttribute + private String type; + + @XmlAttribute + private String message; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Failure.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Failure.java new file mode 100644 index 0000000..f908251 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Failure.java @@ -0,0 +1,22 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.junit; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +public class Failure { + + @XmlAttribute + private String type; + + @XmlAttribute + private String message; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Property.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Property.java new file mode 100644 index 0000000..e20fa3a --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Property.java @@ -0,0 +1,22 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.junit; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +public class Property { + + @XmlAttribute(required = true) + private String name; + + @XmlAttribute(required = true) + private String value; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Skipped.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Skipped.java new file mode 100644 index 0000000..c2c45f1 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/Skipped.java @@ -0,0 +1,7 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.junit; + +import lombok.Data; + +@Data +public class Skipped { +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/TestCase.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/TestCase.java new file mode 100644 index 0000000..1cf1786 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/TestCase.java @@ -0,0 +1,48 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.junit; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +public class TestCase { + + @XmlElement + private Skipped skipped; + + @XmlElement(name = "error") + private List errors; + + @XmlElement(name = "failure") + private List failures; + + @XmlElement(name = "system-out") + private String systemOut; + + @XmlElement(name = "system-err") + private String systemErr; + + @XmlAttribute(required = true) + private String name; + + @XmlAttribute + private int assertions; + + @XmlAttribute + private Double time; + + @XmlAttribute(name = "classname", required = true) + private String className; + + @XmlAttribute + private String status; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/TestSuite.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/TestSuite.java new file mode 100644 index 0000000..fbd5eb6 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/TestSuite.java @@ -0,0 +1,67 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.junit; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@XmlRootElement(name = "testsuite") +@XmlAccessorType(XmlAccessType.FIELD) +public class TestSuite { + + @XmlElementWrapper(name = "properties") + @XmlElement(name = "property") + private List properties; + + @XmlElement(name = "testcase") + private List testCases; + + @XmlElement(name = "system-out") + private String systemOut; + + @XmlElement(name = "system-err") + private String systemErr; + + @XmlAttribute(required = true) + private String name; + + @XmlAttribute(required = true) + private int tests; + + @XmlAttribute + private int failures; + + @XmlAttribute + private int errors; + + @XmlAttribute + private double time; + + @XmlAttribute + private boolean disabled; + + @XmlAttribute + private int skipped; + + @XmlAttribute + private String timestamp; + + @XmlAttribute + private String hostname; + + @XmlAttribute + private String id; + + @XmlAttribute(name = "package") + private String pkg; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/TestSuites.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/TestSuites.java new file mode 100644 index 0000000..cbdb785 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/junit/TestSuites.java @@ -0,0 +1,42 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution.junit; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.List; + +@SuppressWarnings("java:S1700") +@Data +@NoArgsConstructor +@AllArgsConstructor +@XmlRootElement(name = "testsuites") +@XmlAccessorType(XmlAccessType.FIELD) +public class TestSuites { + + @XmlElement(name = "testsuite") + private List testSuites; + + @XmlAttribute + private String name; + + @XmlAttribute + private double time; + + @XmlAttribute + private int tests; + + @XmlAttribute + private int failures; + + @XmlAttribute + private boolean disabled; + + @XmlAttribute + private int errors; +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/EnumConverter.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/EnumConverter.java new file mode 100644 index 0000000..1fe79b0 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/EnumConverter.java @@ -0,0 +1,26 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.fields; + +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.subterra.base.data.model.ICustomField; +import com.polarion.subterra.base.data.model.IListType; +import com.polarion.subterra.base.data.model.IType; + +import java.util.List; +import java.util.Map; + +public class EnumConverter implements FieldValueConverter { + + @Override + @SuppressWarnings("unchecked") + public Object convert(ITrackerService trackerService, ITestRun testRun, ICustomField fieldPrototype, Object source) { + IType fieldType = fieldPrototype.getType(); + boolean array = fieldType instanceof IListType; + + //This particular converter just extracts string names or value from initial json. + //These values will be converted to enum options on generic side. + return array ? ((List>) source) + .stream().map(m -> m.get("name")).toList() : + ((Map) source).get("value"); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/FieldDefinition.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/FieldDefinition.java new file mode 100644 index 0000000..934f771 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/FieldDefinition.java @@ -0,0 +1,93 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.fields; + +import ch.sbb.polarion.extension.generic.fields.model.FieldMetadata; + +import java.util.Collections; +import java.util.List; + +@SuppressWarnings("unused") +public final class FieldDefinition { + + public static final FieldDefinition TITLE = fromStaticFieldMapping("title", "summary"); + public static final FieldDefinition DESCRIPTION = fromStaticFieldMapping("description", "description"); + public static final FieldDefinition TEST_RUN_TEMPLATE_ID = fromStaticFieldMapping("testRunTemplateId", "testRunTemplateId"); + + private final String id; + private final String name; + private final boolean custom; + private final List clauseNames; + private final Schema schema; + + private FieldDefinition(String id, String name, boolean custom, List clauseNames, Schema schema) { + this.id = id; + this.name = name; + this.custom = custom; + this.clauseNames = clauseNames; + this.schema = schema; + } + + public static FieldDefinition fromFieldMetadata(FieldMetadata fieldMetadata) { + FieldType fieldType = FieldType.fromIType(fieldMetadata.getType()); + return new FieldDefinition( + fieldMetadata.getId(), + fieldMetadata.getLabel(), + fieldMetadata.isCustom(), + Collections.singletonList(fieldMetadata.getLabel()), + new Schema(fieldType.getMavenPluginTypeName(), fieldType.getMavenPluginItemsName()) + ); + } + + private static FieldDefinition fromStaticFieldMapping(String staticFieldId, String staticFieldClauseName) { + return new FieldDefinition( + staticFieldId, + staticFieldClauseName, + false, + Collections.singletonList(staticFieldClauseName), + new Schema(FieldType.STRING.getMavenPluginTypeName()) + ); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public boolean isCustom() { + return custom; + } + + public List getClauseNames() { + return clauseNames; + } + + public Schema getSchema() { + return schema; + } + + public static class Schema { + + private final String type; + private final String items; + + private Schema(String type) { + this.type = type; + this.items = null; + } + + public Schema(String type, String items) { + this.type = type; + this.items = items; + } + + public String getType() { + return type; + } + + public String getItems() { + return items; + } + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/FieldType.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/FieldType.java new file mode 100644 index 0000000..aa05199 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/FieldType.java @@ -0,0 +1,67 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.fields; + +import com.polarion.core.util.types.Text; +import com.polarion.subterra.base.data.model.IType; +import com.polarion.subterra.base.data.model.internal.EnumType; +import com.polarion.subterra.base.data.model.internal.ListType; +import com.polarion.subterra.base.data.model.internal.PrimitiveType; + +import java.util.Arrays; +import java.util.function.Function; + +public enum FieldType { + + STRING(FieldType.TYPE_STRING, null, + type -> new PrimitiveType(String.class.getName(), null).equals(type), + new TextConverter(false)), + + STRING_LIST(FieldType.TYPE_ARRAY, FieldType.TYPE_STRING, + type -> type.equals(new PrimitiveType(Text.class.getName(), "html")), + new StringListConverter()), + + TEXT(FieldType.TYPE_STRING, null, + type -> new PrimitiveType(Text.class.getName(), null).equals(type), + new TextConverter(true)), + + ENUM(FieldType.TYPE_OPTION, null, + EnumType.class::isInstance, + new EnumConverter()), + + COMPONENTS_LIST(FieldType.TYPE_ARRAY, FieldType.TYPE_COMPONENT, + type -> type instanceof ListType listType && listType.getItemType() instanceof EnumType, + new EnumConverter()); + + private static final String TYPE_STRING = "string"; + private static final String TYPE_ARRAY = "array"; + private static final String TYPE_OPTION = "option"; + private static final String TYPE_COMPONENT = "component"; + + private final Function typeMatcher; + private final String mavenPluginItemsName; + private final String mavenPluginTypeName; + private final FieldValueConverter converter; + + FieldType(String mavenPluginTypeName, String mavenPluginItemsName, + Function typeMatcher, FieldValueConverter converter) { + this.mavenPluginItemsName = mavenPluginItemsName; + this.typeMatcher = typeMatcher; + this.mavenPluginTypeName = mavenPluginTypeName; + this.converter = converter; + } + + public static FieldType fromIType(IType type) { + return Arrays.stream(values()).filter(t -> t.typeMatcher.apply(type)).findFirst().orElse(null); + } + + public String getMavenPluginTypeName() { + return mavenPluginTypeName; + } + + public String getMavenPluginItemsName() { + return mavenPluginItemsName; + } + + public FieldValueConverter getConverter() { + return converter; + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/FieldValueConverter.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/FieldValueConverter.java new file mode 100644 index 0000000..e6a6e3e --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/FieldValueConverter.java @@ -0,0 +1,11 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.fields; + +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.subterra.base.data.model.ICustomField; + +public interface FieldValueConverter { + + Object convert(ITrackerService trackerService, ITestRun testRun, ICustomField fieldPrototype, Object source); + +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/StringListConverter.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/StringListConverter.java new file mode 100644 index 0000000..0f81ccb --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/StringListConverter.java @@ -0,0 +1,52 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.fields; + +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.model.IPlan; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.core.util.StringUtils; +import com.polarion.core.util.types.Text; +import com.polarion.subterra.base.data.model.ICustomField; + +import java.util.ArrayList; +import java.util.List; + +public class StringListConverter implements FieldValueConverter { + + private static final String INTERNAL_HREF = "%s%s"; + private static final String PATH_PLAN = "plan"; + private static final String PATH_TEST_RUN = "testrun"; + private static final String TEST_RUN_LINKS = "testRunLinks"; + private static final String SPACE = " "; + + @Override + @SuppressWarnings("unchecked") + public Object convert(ITrackerService trackerService, ITestRun testRun, ICustomField fieldPrototype, Object source) { + List resultList = new ArrayList<>(); + List sourceList = (List) source; + + String projectId = testRun.getProjectId(); + for (String entry : sourceList) { + IPlan plan = trackerService.getPlanningManager().searchPlans(String.format("project.id:%s AND id:\"%s\"", projectId, entry), "id", 1).stream() + .findFirst().orElse(null); + + //try to create cross-links between Plan and Test Run + if (plan != null) { + resultList.add(String.format(INTERNAL_HREF, projectId, PATH_PLAN, plan.getId(), plan.getName(), SPACE)); + String testRunLink = String.format(INTERNAL_HREF, projectId, PATH_TEST_RUN, testRun.getId(), testRun.getId(), SPACE); + + Text text = !(plan.getCustomField(TEST_RUN_LINKS) instanceof Text textObject) ? null : textObject; + if (text == null || !text.getContent().contains(testRunLink)) { + plan.setCustomField(TEST_RUN_LINKS, + Text.html((text == null ? "" : StringUtils.getEmptyIfNull(text.getContent())) + testRunLink)); + plan.save(); + } + } else { + + //otherwise place it just as a regular text + resultList.add(String.format("%s%s", entry, SPACE)); + } + } + + return Text.html(String.join("", resultList)); + } +} diff --git a/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/TextConverter.java b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/TextConverter.java new file mode 100644 index 0000000..e94c341 --- /dev/null +++ b/src/main/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/TextConverter.java @@ -0,0 +1,21 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.fields; + +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.core.util.types.Text; +import com.polarion.subterra.base.data.model.ICustomField; + +public class TextConverter implements FieldValueConverter { + + private final boolean wrapText; + + public TextConverter(boolean wrapText) { + this.wrapText = wrapText; + } + + @Override + public Object convert(ITrackerService trackerService, ITestRun testRun, ICustomField fieldPrototype, Object source) { + String content = source == null ? "" : String.valueOf(source); + return wrapText ? Text.plain(content) : content; + } +} diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF index e38ad38..4921a8b 100644 --- a/src/main/resources/META-INF/MANIFEST.MF +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -1,5 +1,5 @@ Support-Email: polarion-opensource@sbb.ch -Bundle-Name: Extension for Polarion ALM +Bundle-Name: Cucumber Integration Extension for Polarion ALM Require-Bundle: com.polarion.portal.tomcat, com.polarion.alm.ui, com.polarion.platform.guice, @@ -8,7 +8,7 @@ Require-Bundle: com.polarion.portal.tomcat, com.fasterxml.jackson, com.fasterxml.jackson.jaxrs, io.swagger, - org.apache.commons.logging, - slf4j.api, org.springframework.spring-core, org.springframework.spring-web +Guice-Modules: ch.sbb.polarion.extension.cucumber.CucumberIntegrationModule +Export-Package: ch.sbb.polarion.extension.cucumber diff --git a/src/main/resources/META-INF/hivemodule.xml b/src/main/resources/META-INF/hivemodule.xml new file mode 100644 index 0000000..15d89cc --- /dev/null +++ b/src/main/resources/META-INF/hivemodule.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/plugin.xml b/src/main/resources/plugin.xml new file mode 100644 index 0000000..d3d734f --- /dev/null +++ b/src/main/resources/plugin.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber-admin/WEB-INF/web.xml b/src/main/resources/webapp/cucumber-admin/WEB-INF/web.xml new file mode 100644 index 0000000..5f66b64 --- /dev/null +++ b/src/main/resources/webapp/cucumber-admin/WEB-INF/web.xml @@ -0,0 +1,59 @@ + + + + cucumber-admin + + + DoAsFilter + com.polarion.portal.tomcat.servlets.DoAsFilter + + + + DoAsFilter + /* + + + + cucumber-admin-ui + ch.sbb.polarion.extension.cucumber.CucumberIntegrationAdminUiServlet + + + debug + 0 + + 1 + + + + cucumber-admin-ui + /ui/* + + + + 30 + + + + + All + /* + + + user + + + + + + FORM + PolarionRealm + + /login/login + /login/error + + + \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber-admin/html/.gitkeep b/src/main/resources/webapp/cucumber-admin/html/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/webapp/cucumber-admin/images/app-icon.svg b/src/main/resources/webapp/cucumber-admin/images/app-icon.svg new file mode 100644 index 0000000..a954fff --- /dev/null +++ b/src/main/resources/webapp/cucumber-admin/images/app-icon.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/webapp/cucumber-admin/pages/about.jsp b/src/main/resources/webapp/cucumber-admin/pages/about.jsp new file mode 100644 index 0000000..a378633 --- /dev/null +++ b/src/main/resources/webapp/cucumber-admin/pages/about.jsp @@ -0,0 +1,103 @@ +<%@ page import="ch.sbb.polarion.extension.generic.properties.CurrentExtensionConfiguration" %> +<%@ page import="ch.sbb.polarion.extension.generic.rest.model.Version" %> +<%@ page import="ch.sbb.polarion.extension.generic.util.ExtensionInfo" %> +<%@ page import="ch.sbb.polarion.extension.generic.util.VersionUtils" %> +<%@ page import="java.util.Collections" %> +<%@ page import="java.util.Properties" %> +<%@ page import="java.io.InputStream" %> +<%@ page import="java.nio.charset.StandardCharsets" %> +<%@ page import="java.util.List" %> +<%@ page import="java.util.Set" %> +<%@ page import="java.util.ArrayList" %> +<%@ page contentType="text/html; charset=UTF-8" %> + + + +<%! + private static final String ABOUT_TABLE_ROW = "%s%s"; + private static final String CONFIGURATION_PROPERTIES_TABLE_ROW = "%s%s"; + + Version version = ExtensionInfo.getInstance().getVersion(); + Properties properties = CurrentExtensionConfiguration.getInstance().getExtensionConfiguration().getProperties(); +%> + + + + + + + + + +
+

About

+ +
+ + +

Extension info

+ + + + + + + + + + <% + out.println(ABOUT_TABLE_ROW.formatted(VersionUtils.BUNDLE_NAME, version.getBundleName())); + out.println(ABOUT_TABLE_ROW.formatted(VersionUtils.BUNDLE_VENDOR, version.getBundleVendor())); + if (version.getSupportEmail() != null) { + String mailToLink = "%s".formatted(version.getSupportEmail(), version.getSupportEmail()); + out.println(ABOUT_TABLE_ROW.formatted(VersionUtils.SUPPORT_EMAIL, mailToLink)); + } + out.println(ABOUT_TABLE_ROW.formatted(VersionUtils.AUTOMATIC_MODULE_NAME, version.getAutomaticModuleName())); + out.println(ABOUT_TABLE_ROW.formatted(VersionUtils.BUNDLE_VERSION, version.getBundleVersion())); + out.println(ABOUT_TABLE_ROW.formatted(VersionUtils.BUNDLE_BUILD_TIMESTAMP, version.getBundleBuildTimestamp())); + %> + +
Manifest entryValue
+ +

Extension configuration properties

+ + + + + + + + + + <% + Set keySet = properties.keySet(); + List propertyNames = new ArrayList<>(); + for (Object key : keySet) { + propertyNames.add((String) key); + } + Collections.sort(propertyNames); + + for (String key : propertyNames) { + String value = properties.getProperty(key); + String row = CONFIGURATION_PROPERTIES_TABLE_ROW.formatted(key, value); + out.println(row); + } + %> + +
Configuration propertyValue
+ + "/> + +
+ <% + try (InputStream inputStream = ExtensionInfo.class.getResourceAsStream("/webapp/cucumber-admin/html/about.html")) { + assert inputStream != null; + String configurationHelp = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + out.println(configurationHelp); + } + %> +
+
+
+ + diff --git a/src/main/resources/webapp/cucumber/WEB-INF/web.xml b/src/main/resources/webapp/cucumber/WEB-INF/web.xml new file mode 100644 index 0000000..1a18f28 --- /dev/null +++ b/src/main/resources/webapp/cucumber/WEB-INF/web.xml @@ -0,0 +1,110 @@ + + + + cucumber + + + org.springframework.web.context.request.RequestContextListener + + + + DoAsFilter + com.polarion.portal.tomcat.servlets.DoAsFilter + + + + DoAsFilter + /* + + + + cucumber-ui + ch.sbb.polarion.extension.cucumber.CucumberIntegrationUiServlet + + + debug + 0 + + 1 + + + + cucumber-ui + /ui/* + + + + cucumber-rest + org.glassfish.jersey.servlet.ServletContainer + + + javax.ws.rs.Application + ch.sbb.polarion.extension.cucumber.rest.CucumberIntegrationRestApplication + + + + jersey.config.server.provider.classnames + org.glassfish.jersey.media.multipart.MultiPartFeature + + + + debug + 0 + + 1 + + + + cucumber-rest + /rest/* + + + + 30 + + + + log + text/plain + + + + + All + /* + + + user + + + + + + All + /rest/api/* + + + + + + + All + /rest/raven/* + + + + + + + FORM + PolarionRealm + + /login/login + /login/error + + + \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber/css/cucumber.css b/src/main/resources/webapp/cucumber/css/cucumber.css new file mode 100644 index 0000000..d705271 --- /dev/null +++ b/src/main/resources/webapp/cucumber/css/cucumber.css @@ -0,0 +1,51 @@ +.editor-buttons { + margin-bottom: 10px; +} + +.editor-buttons button { + background-color: transparent; + border: 1px solid #DEDEDE; + color: #555; + padding: 4px 8px; + font-size: 12px; + font-weight: bold; + cursor: pointer; +} + +.editor-buttons button:not(:first-child) { + margin-left: 5px; +} + +.editor-buttons button:disabled { + text-shadow: white 1px 1px 0.01em; + color: #C9D1D7; + cursor: default; +} + +.editor-buttons .divider { + padding: 4px 0; + width: 1px; + border-right-color: #fff; + border-left-color: #ccc; +} + +.editor-buttons button img { + margin-right: 6px; + vertical-align: bottom; +} + +.editor-buttons button:disabled img { + opacity: 0.3; +} + +#cucumberFeatureCodeEditor { + border: 1px solid rgba(0, 0, 0, 0.2); +} + +.validation-pass { + color: green; +} + +.validation-fail, .line-with-error { + color: red; +} diff --git a/src/main/resources/webapp/cucumber/css/highlightjs.css b/src/main/resources/webapp/cucumber/css/highlightjs.css new file mode 100644 index 0000000..a75ea91 --- /dev/null +++ b/src/main/resources/webapp/cucumber/css/highlightjs.css @@ -0,0 +1,9 @@ +/*! + Theme: Default + Description: Original highlight.js style + Author: (c) Ivan Sagalaev + Maintainer: @highlightjs/core-team + Website: https://highlightjs.org/ + License: see project LICENSE + Touched: 2021 +*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber/css/petrel.css b/src/main/resources/webapp/cucumber/css/petrel.css new file mode 100644 index 0000000..abcd921 --- /dev/null +++ b/src/main/resources/webapp/cucumber/css/petrel.css @@ -0,0 +1,122 @@ +.petrel-code-editor { + overflow-wrap: normal; + overflow-x: hidden; + position: relative; + font-family: monospace; +} + +.petrel-code-editor * { + font-family: inherit; + line-height: inherit; +} + +.petrel-code-editor textarea { + padding: 0; + margin: 0; + line-height: inherit; + min-height: 100%; + overflow: hidden; + border: none; + outline: none; + width: auto; + resize: none; + font-family: inherit; + color: transparent; + caret-color: #989898; + overflow-wrap: normal; + white-space: pre; + min-width: calc(100% - 40px); + position: absolute; + top: 0; + font-size: inherit; + left: 0; + cursor: text; + background: transparent; +} + +.petrel-code-editor textarea:read-only { + cursor: default; + background-color: rgba(0, 0, 0, 0.05); +} + +.petrel-code-editor textarea:read-only + pre { + opacity: 0.85; +} + +.petrel-code-editor textarea::-moz-selection, .petrel-code-editor textarea::selection { + color: #000FFF00; + background: #FFFFFF21; + border-radius: 10px; +} + +.petrel-code-editor pre { + padding: 0; + margin: 0; + user-select: none; + line-height: inherit; + position: absolute; + top: 0; + pointer-events: none; + font-family: inherit; + font-size: inherit; +} + +.petrel-code-editor .petrel-code-editor-line-numbering { + position: sticky; + min-height: 100%; + left: 0; + + line-height: inherit; + float: left; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + min-width: 30px; + padding-right: 10px; + text-align: right; + z-index: 10; + background: #ccc; + color: rgba(67, 111, 178, 0.82); +} + +.petrel-code-editor .petrel-code-editor-line-numbering span { + display: block; +} + + +.petrel-code-editor .petrel-code-editor-line-numbering span div { + /*height: 22px;*/ + width: 100%; + left: 0; + position: absolute; + background: #FFFFFF11; + margin-top: -1px; +} + +.petrel-code-editor .petrel-code-editor-autocompletion { + display: block; + min-height: 35px; + min-width: 240px; + max-width: 400px; + top: 22px; + position: sticky; + left: 92px; + background: #FFF; +} + + +.petrel-code-editor .petrel-code-editor-autocompletion a { + cursor: pointer; + display: block; +} + +.petrel-code-editor .petrel-code-editor-autocompletion a.selected { + background: #00000022; +} + +.petrel-code-editor .petrel-code-editor-autocompletion a span { + right: 10px; + opacity: 0.6; + text-transform: lowercase; + margin-left: 15px; +} \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber/js/cucumber.js b/src/main/resources/webapp/cucumber/js/cucumber.js new file mode 100644 index 0000000..95dfb25 --- /dev/null +++ b/src/main/resources/webapp/cucumber/js/cucumber.js @@ -0,0 +1,122 @@ +function handleSaveFeature(projectId, workItemId, filename, validate) { + if (validate === "false") { + saveCucumberFeature(projectId, workItemId, filename); + } else { + validateFeature().then(() => saveCucumberFeature(projectId, workItemId, filename)); + } +} + +function saveCucumberFeature(projectId, workItemId, filename) { + document.getElementById("cancel-edit-feature-button").disabled = 'true'; + document.getElementById("save-feature-button").disabled = 'true'; + globalThis.cucumberFeatureCodeEditor.readonly = true; + + var xhr = new XMLHttpRequest(); + xhr.open('POST', '/polarion/cucumber/rest/internal/feature', true); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.onload = (_event) => { + document.location.reload(true); + }; + + xhr.send(JSON.stringify( + { + 'projectId': projectId, + 'workItemId': workItemId, + 'filename': filename, + 'content': globalThis.cucumberFeatureCodeEditor.getValue() + } + )); +} + +const handleEditFeature = () => { + document.getElementById("edit-feature-button").disabled = true; + document.getElementById("cancel-edit-feature-button").disabled = false; + document.getElementById("save-feature-button").disabled = false; + document.getElementById("validate-feature-button").disabled = false; + globalThis.cucumberFeatureCodeEditor.readonly = false; +} + +const handleValidateFeature = () => { + return validateFeature(); +} + +const validateFeature = () => { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('POST', '/polarion/cucumber/rest/internal/cucumber/validate', false); + xhr.setRequestHeader('Content-Type', 'text/plain'); + xhr.onload = () => { + let result = false; + const validationResultSpan = document.getElementById("feature-validation-result"); + globalThis.cucumberFeatureCodeEditor.setLinesWithError([]); + if (xhr.status === 200) { + const response = JSON.parse(xhr.responseText); + if (response && response.result === "valid") { + result = true; + validationResultSpan.innerText = "Cucumber feature is valid!"; + validationResultSpan.className = "validation-pass"; + } else { + let errorMessage = ""; + const linesWithError = []; + response.errors.forEach(error => { + if (errorMessage.length > 0) { + errorMessage += "
"; + } + errorMessage += error.message; + + const match = error.message.match(/\((?\d+):\d+\):/); + if (match && match.groups) { + linesWithError.push(Number.parseInt(match.groups.linenumber) - 1); + } + }); + globalThis.cucumberFeatureCodeEditor.setLinesWithError(linesWithError); + validationResultSpan.innerHTML = errorMessage; + validationResultSpan.className = "validation-fail"; + } + } else { + validationResultSpan.innerText = "Error during validation, please contact system administrator for details"; + validationResultSpan.className = "validation-fail"; + } + validationResultSpan.style.display = "block"; + globalThis.cucumberFeatureCodeEditor.update(); + if (result) { + resolve(); + } else { + reject(); + } + }; + xhr.send(globalThis.cucumberFeatureCodeEditor.getValue()); + }); +} + +const handleCancelEditFeature = () => { + if (confirm("Are you sure you want to cancel editing and revert changes?")) { + document.getElementById("cancel-edit-feature-button").disabled = true; + document.getElementById("save-feature-button").disabled = true; + document.getElementById("edit-feature-button").disabled = false; + document.getElementById("validate-feature-button").disabled = true; + document.getElementById("feature-validation-result").style.display = "none"; + globalThis.cucumberFeatureCodeEditor.readonly = true; + globalThis.cucumberFeatureCodeEditor.setValue(document.getElementById('cucumberFeatureCodeEditorOriginalContent').innerText); + } +} + +(function () { + const imagesToUpdate = document.getElementsByClassName("append-build-number"); + if (imagesToUpdate && imagesToUpdate.length > 0) { + const imagesWithBuildNumber = document.getElementsByClassName("polarion-ToolbarButton-Icon"); + if (imagesWithBuildNumber && imagesWithBuildNumber.length > 0) { + const srcWithBuildNumber = imagesWithBuildNumber[0].getAttribute("src"); + const buildParamIndex = srcWithBuildNumber.indexOf("?buildId="); + if (buildParamIndex >= 0) { + const buildParam = srcWithBuildNumber.slice(buildParamIndex); + if (buildParam) { + for (let i = 0; i < imagesToUpdate.length; i++) { + imagesToUpdate[i].setAttribute("src", imagesToUpdate[i].getAttribute("src").concat(buildParam)); + imagesToUpdate[i].classList.remove("append-build-number"); + } + } + } + } + } +})(); \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber/js/gherkin-editor.js b/src/main/resources/webapp/cucumber/js/gherkin-editor.js new file mode 100644 index 0000000..c18d8c2 --- /dev/null +++ b/src/main/resources/webapp/cucumber/js/gherkin-editor.js @@ -0,0 +1,23 @@ +import CodeEditor from './petrel/CodeEditor.js' +import GherkinAutoComplete from './petrel/GherkinAutoComplete.js' + +import hljs from './highlight/core.min.js' +import gherkin from './highlight/gherkin.js' + +hljs.registerLanguage('gherkin', gherkin); + +const content = document.getElementById('cucumberFeatureCodeEditorOriginalContent').innerText; + +const element = document.getElementById('cucumberFeatureCodeEditor'); +const codeEditor = new CodeEditor(element, { + readonly: true, + tabSize: 4 +}); + +codeEditor.setHighlighter(code => hljs.highlight(code, {language: 'gherkin', ignoreIllegals: true}).value); +codeEditor.setAutoCompleteHandler(new GherkinAutoComplete()); +codeEditor.setValue(content); +codeEditor.create(); + +// make it global +globalThis.cucumberFeatureCodeEditor = codeEditor; diff --git a/src/main/resources/webapp/cucumber/js/highlight/core.min.js b/src/main/resources/webapp/cucumber/js/highlight/core.min.js new file mode 100644 index 0000000..5e5da43 --- /dev/null +++ b/src/main/resources/webapp/cucumber/js/highlight/core.min.js @@ -0,0 +1,306 @@ +/*! + Highlight.js v11.7.0 (git: 82688fad18) + (c) 2006-2022 undefined and other contributors + License: BSD-3-Clause + */ +var e={exports:{}};function t(e){ +return e instanceof Map?e.clear=e.delete=e.set=()=>{ +throw Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var i=e[n] +;"object"!=typeof i||Object.isFrozen(i)||t(i)})),e} +e.exports=t,e.exports.default=t;class n{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function i(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function r(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t] +;return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n} +const s=e=>!!e.scope||e.sublanguage&&e.language;class o{constructor(e,t){ +this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){ +this.buffer+=i(e)}openNode(e){if(!s(e))return;let t="" +;t=e.sublanguage?"language-"+e.language:((e,{prefix:t})=>{if(e.includes(".")){ +const n=e.split(".") +;return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ") +}return`${t}${e}`})(e.scope,{prefix:this.classPrefix}),this.span(t)} +closeNode(e){s(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const a=(e={})=>{const t={children:[]} +;return Object.assign(t,e),t};class c{constructor(){ +this.rootNode=a(),this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const t=a({scope:e}) +;this.add(t),this.stack.push(t)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){ +return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t), +t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +c._collapse(e)})))}}class l extends c{constructor(e){super(),this.options=e} +addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())} +addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root +;n.sublanguage=!0,n.language=t,this.add(n)}toHTML(){ +return new o(this,this.options).value()}finalize(){return!0}}function g(e){ +return e?"string"==typeof e?e:e.source:null}function d(e){return p("(?=",e,")")} +function u(e){return p("(?:",e,")*")}function h(e){return p("(?:",e,")?")} +function p(...e){return e.map((e=>g(e))).join("")}function f(...e){const t=(e=>{ +const t=e[e.length-1] +;return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{} +})(e);return"("+(t.capture?"":"?:")+e.map((e=>g(e))).join("|")+")"} +function b(e){return RegExp(e.toString()+"|").exec("").length-1} +const m=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function E(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n +;let i=g(e),r="";for(;i.length>0;){const e=m.exec(i);if(!e){r+=i;break} +r+=i.substring(0,e.index), +i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?r+="\\"+(Number(e[1])+t):(r+=e[0], +"("===e[0]&&n++)}return r})).map((e=>`(${e})`)).join(t)} +const x="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",w={ +begin:"\\\\[\\s\\S]",relevance:0},y={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[w]},_={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[w]},O=(e,t,n={})=>{const i=r({scope:"comment",begin:e,end:t, +contains:[]},n);i.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const s=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return i.contains.push({begin:p(/[ ]+/,"(",s,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i +},v=O("//","$"),N=O("/\\*","\\*/"),k=O("#","$");var M=Object.freeze({ +__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:"[a-zA-Z]\\w*", +UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:x, +BINARY_NUMBER_RE:"\\b(0b[01]+)", +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const t=/^#![ ]*\// +;return e.binary&&(e.begin=p(t,/.*\b/,e.binary,/\b.*/)),r({scope:"meta",begin:t, +end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)}, +BACKSLASH_ESCAPE:w,APOS_STRING_MODE:y,QUOTE_STRING_MODE:_,PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},COMMENT:O,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:N,HASH_COMMENT_MODE:k, +NUMBER_MODE:{scope:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0}, +C_NUMBER_MODE:{scope:"number",begin:x,relevance:0},BINARY_NUMBER_MODE:{ +scope:"number",begin:"\\b(0b[01]+)",relevance:0},REGEXP_MODE:{ +begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//,end:/\/[gimuy]*/, +illegal:/\n/,contains:[w,{begin:/\[/,end:/\]/,relevance:0,contains:[w]}]}]}, +TITLE_MODE:{scope:"title",begin:"[a-zA-Z]\\w*",relevance:0}, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:"[a-zA-Z_]\\w*",relevance:0}, +METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0}, +END_SAME_AS_BEGIN:e=>Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1] +},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})}) +;function S(e,t){"."===e.input[e.index-1]&&t.ignoreMatch()}function R(e,t){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function A(e,t){ +t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=S,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function j(e,t){ +Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function I(e,t){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function T(e,t){ +void 0===e.relevance&&(e.relevance=1)}const L=(e,t)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t] +})),e.keywords=n.keywords,e.begin=p(n.beforeMatch,d(n.begin)),e.starts={ +relevance:0,contains:[Object.assign(n,{endsParent:!0})] +},e.relevance=0,delete n.beforeMatch +},B=["of","and","for","in","not","or","if","then","parent","list","value"] +;function D(e,t,n="keyword"){const i=Object.create(null) +;return"string"==typeof e?r(n,e.split(" ")):Array.isArray(e)?r(n,e):Object.keys(e).forEach((n=>{ +Object.assign(i,D(e[n],t,n))})),i;function r(e,n){ +t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|") +;i[n[0]]=[e,H(n[0],n[1])]}))}}function H(e,t){ +return t?Number(t):(e=>B.includes(e.toLowerCase()))(e)?0:1}const P={},C=e=>{ +console.error(e)},$=(e,...t)=>{console.log("WARN: "+e,...t)},U=(e,t)=>{ +P[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),P[`${e}/${t}`]=!0) +},z=Error();function K(e,t,{key:n}){let i=0;const r=e[n],s={},o={} +;for(let e=1;e<=t.length;e++)o[e+i]=r[e],s[e+i]=!0,i+=b(t[e-1]) +;e[n]=o,e[n]._emit=s,e[n]._multi=!0}function W(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw C("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +z +;if("object"!=typeof e.beginScope||null===e.beginScope)throw C("beginScope must be object"), +z;K(e,e.begin,{key:"beginScope"}),e.begin=E(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw C("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +z +;if("object"!=typeof e.endScope||null===e.endScope)throw C("endScope must be object"), +z;K(e,e.end,{key:"endScope"}),e.end=E(e.end,{joinWith:""})}})(e)}function X(e){ +function t(t,n){ +return RegExp(g(t),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(n?"g":"")) +}class n{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,t){ +t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]), +this.matchAt+=b(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=t(E(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const t=this.matcherRe.exec(e);if(!t)return null +;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n] +;return t.splice(0,n),Object.assign(t,i)}}class i{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n +;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))), +t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){ +this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){ +const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex +;let n=t.exec(e) +;if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{ +const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)} +return n&&(this.regexIndex+=n.position+1, +this.regexIndex===this.count&&this.considerAll()),n}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=r(e.classNameAliases||{}),function n(s,o){const a=s +;if(s.isCompiled)return a +;[R,I,W,L].forEach((e=>e(s,o))),e.compilerExtensions.forEach((e=>e(s,o))), +s.__beforeBegin=null,[A,j,T].forEach((e=>e(s,o))),s.isCompiled=!0;let c=null +;return"object"==typeof s.keywords&&s.keywords.$pattern&&(s.keywords=Object.assign({},s.keywords), +c=s.keywords.$pattern, +delete s.keywords.$pattern),c=c||/\w+/,s.keywords&&(s.keywords=D(s.keywords,e.case_insensitive)), +a.keywordPatternRe=t(c,!0), +o&&(s.begin||(s.begin=/\B|\b/),a.beginRe=t(a.begin),s.end||s.endsWithParent||(s.end=/\B|\b/), +s.end&&(a.endRe=t(a.end)), +a.terminatorEnd=g(a.end)||"",s.endsWithParent&&o.terminatorEnd&&(a.terminatorEnd+=(s.end?"|":"")+o.terminatorEnd)), +s.illegal&&(a.illegalRe=t(s.illegal)), +s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>r(e,{ +variants:null},t)))),e.cachedVariants?e.cachedVariants:Z(e)?r(e,{ +starts:e.starts?r(e.starts):null +}):Object.isFrozen(e)?r(e):e))("self"===e?s:e)))),s.contains.forEach((e=>{n(e,a) +})),s.starts&&n(s.starts,o),a.matcher=(e=>{const t=new i +;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(a),a}(e)}function Z(e){ +return!!e&&(e.endsWithParent||Z(e.starts))}class G extends Error{ +constructor(e,t){super(e),this.name="HTMLInjectionError",this.html=t}} +const F=i,V=r,q=Symbol("nomatch");var J=(t=>{ +const i=Object.create(null),r=Object.create(null),s=[];let o=!0 +;const a="Could not find the language '{}', did you forget to load/include a language module?",c={ +disableAutodetect:!0,name:"Plain text",contains:[]};let g={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:l};function b(e){ +return g.noHighlightRe.test(e)}function m(e,t,n){let i="",r="" +;"object"==typeof t?(i=e, +n=t.ignoreIllegals,r=t.language):(U("10.7.0","highlight(lang, code, ...args) has been deprecated."), +U("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +r=e,i=t),void 0===n&&(n=!0);const s={code:i,language:r};k("before:highlight",s) +;const o=s.result?s.result:E(s.language,s.code,n) +;return o.code=s.code,k("after:highlight",o),o}function E(e,t,r,s){ +const c=Object.create(null);function l(){if(!N.keywords)return void M.addText(S) +;let e=0;N.keywordPatternRe.lastIndex=0;let t=N.keywordPatternRe.exec(S),n="" +;for(;t;){n+=S.substring(e,t.index) +;const r=y.case_insensitive?t[0].toLowerCase():t[0],s=(i=r,N.keywords[i]);if(s){ +const[e,i]=s +;if(M.addText(n),n="",c[r]=(c[r]||0)+1,c[r]<=7&&(R+=i),e.startsWith("_"))n+=t[0];else{ +const n=y.classNameAliases[e]||e;M.addKeyword(t[0],n)}}else n+=t[0] +;e=N.keywordPatternRe.lastIndex,t=N.keywordPatternRe.exec(S)}var i +;n+=S.substring(e),M.addText(n)}function d(){null!=N.subLanguage?(()=>{ +if(""===S)return;let e=null;if("string"==typeof N.subLanguage){ +if(!i[N.subLanguage])return void M.addText(S) +;e=E(N.subLanguage,S,!0,k[N.subLanguage]),k[N.subLanguage]=e._top +}else e=x(S,N.subLanguage.length?N.subLanguage:null) +;N.relevance>0&&(R+=e.relevance),M.addSublanguage(e._emitter,e.language) +})():l(),S=""}function u(e,t){let n=1;const i=t.length-1;for(;n<=i;){ +if(!e._emit[n]){n++;continue}const i=y.classNameAliases[e[n]]||e[n],r=t[n] +;i?M.addKeyword(r,i):(S=r,l(),S=""),n++}}function h(e,t){ +return e.scope&&"string"==typeof e.scope&&M.openNode(y.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(M.addKeyword(S,y.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +S=""):e.beginScope._multi&&(u(e.beginScope,t),S="")),N=Object.create(e,{parent:{ +value:N}}),N}function p(e,t,i){let r=((e,t)=>{const n=e&&e.exec(t) +;return n&&0===n.index})(e.endRe,i);if(r){if(e["on:end"]){const i=new n(e) +;e["on:end"](t,i),i.isMatchIgnored&&(r=!1)}if(r){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return p(e.parent,t,i)}function f(e){ +return 0===N.matcher.regexIndex?(S+=e[0],1):(I=!0,0)}function b(e){ +const n=e[0],i=t.substring(e.index),r=p(N,e,i);if(!r)return q;const s=N +;N.endScope&&N.endScope._wrap?(d(), +M.addKeyword(n,N.endScope._wrap)):N.endScope&&N.endScope._multi?(d(), +u(N.endScope,e)):s.skip?S+=n:(s.returnEnd||s.excludeEnd||(S+=n), +d(),s.excludeEnd&&(S=n));do{ +N.scope&&M.closeNode(),N.skip||N.subLanguage||(R+=N.relevance),N=N.parent +}while(N!==r.parent);return r.starts&&h(r.starts,e),s.returnEnd?0:n.length} +let m={};function w(i,s){const a=s&&s[0];if(S+=i,null==a)return d(),0 +;if("begin"===m.type&&"end"===s.type&&m.index===s.index&&""===a){ +if(S+=t.slice(s.index,s.index+1),!o){const t=Error(`0 width match regex (${e})`) +;throw t.languageName=e,t.badRule=m.rule,t}return 1} +if(m=s,"begin"===s.type)return(e=>{ +const t=e[0],i=e.rule,r=new n(i),s=[i.__beforeBegin,i["on:begin"]] +;for(const n of s)if(n&&(n(e,r),r.isMatchIgnored))return f(t) +;return i.skip?S+=t:(i.excludeBegin&&(S+=t), +d(),i.returnBegin||i.excludeBegin||(S=t)),h(i,e),i.returnBegin?0:t.length})(s) +;if("illegal"===s.type&&!r){ +const e=Error('Illegal lexeme "'+a+'" for mode "'+(N.scope||"")+'"') +;throw e.mode=N,e}if("end"===s.type){const e=b(s);if(e!==q)return e} +if("illegal"===s.type&&""===a)return 1 +;if(j>1e5&&j>3*s.index)throw Error("potential infinite loop, way more iterations than matches") +;return S+=a,a.length}const y=O(e) +;if(!y)throw C(a.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const _=X(y);let v="",N=s||_;const k={},M=new g.__emitter(g);(()=>{const e=[] +;for(let t=N;t!==y;t=t.parent)t.scope&&e.unshift(t.scope) +;e.forEach((e=>M.openNode(e)))})();let S="",R=0,A=0,j=0,I=!1;try{ +for(N.matcher.considerAll();;){ +j++,I?I=!1:N.matcher.considerAll(),N.matcher.lastIndex=A +;const e=N.matcher.exec(t);if(!e)break;const n=w(t.substring(A,e.index),e) +;A=e.index+n} +return w(t.substring(A)),M.closeAllNodes(),M.finalize(),v=M.toHTML(),{ +language:e,value:v,relevance:R,illegal:!1,_emitter:M,_top:N}}catch(n){ +if(n.message&&n.message.includes("Illegal"))return{language:e,value:F(t), +illegal:!0,relevance:0,_illegalBy:{message:n.message,index:A, +context:t.slice(A-100,A+100),mode:n.mode,resultSoFar:v},_emitter:M};if(o)return{ +language:e,value:F(t),illegal:!1,relevance:0,errorRaised:n,_emitter:M,_top:N} +;throw n}}function x(e,t){t=t||g.languages||Object.keys(i);const n=(e=>{ +const t={value:F(e),illegal:!1,relevance:0,_top:c,_emitter:new g.__emitter(g)} +;return t._emitter.addText(e),t})(e),r=t.filter(O).filter(N).map((t=>E(t,e,!1))) +;r.unshift(n);const s=r.sort(((e,t)=>{ +if(e.relevance!==t.relevance)return t.relevance-e.relevance +;if(e.language&&t.language){if(O(e.language).supersetOf===t.language)return 1 +;if(O(t.language).supersetOf===e.language)return-1}return 0})),[o,a]=s,l=o +;return l.secondBest=a,l}function w(e){let t=null;const n=(e=>{ +let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"" +;const n=g.languageDetectRe.exec(t);if(n){const t=O(n[1]) +;return t||($(a.replace("{}",n[1])), +$("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"} +return t.split(/\s+/).find((e=>b(e)||O(e)))})(e);if(b(n))return +;if(k("before:highlightElement",{el:e,language:n +}),e.children.length>0&&(g.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),g.throwUnescapedHTML))throw new G("One of your code blocks includes unescaped HTML.",e.innerHTML) +;t=e;const i=t.textContent,s=n?m(i,{language:n,ignoreIllegals:!0}):x(i) +;e.innerHTML=s.value,((e,t,n)=>{const i=t&&r[t]||n +;e.classList.add("hljs"),e.classList.add("language-"+i) +})(e,n,s.language),e.result={language:s.language,re:s.relevance, +relevance:s.relevance},s.secondBest&&(e.secondBest={ +language:s.secondBest.language,relevance:s.secondBest.relevance +}),k("after:highlightElement",{el:e,result:s,text:i})}let y=!1;function _(){ +"loading"!==document.readyState?document.querySelectorAll(g.cssSelector).forEach(w):y=!0 +}function O(e){return e=(e||"").toLowerCase(),i[e]||i[r[e]]} +function v(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +r[e.toLowerCase()]=t}))}function N(e){const t=O(e) +;return t&&!t.disableAutodetect}function k(e,t){const n=e;s.forEach((e=>{ +e[n]&&e[n](t)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +y&&_()}),!1),Object.assign(t,{highlight:m,highlightAuto:x,highlightAll:_, +highlightElement:w, +highlightBlock:e=>(U("10.7.0","highlightBlock will be removed entirely in v12.0"), +U("10.7.0","Please use highlightElement now."),w(e)),configure:e=>{g=V(g,e)}, +initHighlighting:()=>{ +_(),U("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +_(),U("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(e,n)=>{let r=null;try{r=n(t)}catch(t){ +if(C("Language definition for '{}' could not be registered.".replace("{}",e)), +!o)throw t;C(t),r=c} +r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&v(r.aliases,{ +languageName:e})},unregisterLanguage:e=>{delete i[e] +;for(const t of Object.keys(r))r[t]===e&&delete r[t]}, +listLanguages:()=>Object.keys(i),getLanguage:O,registerAliases:v, +autoDetection:N,inherit:V,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{ +e["before:highlightBlock"](Object.assign({block:t.el},t)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{ +e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),s.push(e)} +}),t.debugMode=()=>{o=!1},t.safeMode=()=>{o=!0 +},t.versionString="11.7.0",t.regex={concat:p,lookahead:d,either:f,optional:h, +anyNumberOfTimes:u};for(const t in M)"object"==typeof M[t]&&e.exports(M[t]) +;return Object.assign(t,M),t})({});export{J as default}; \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber/js/highlight/gherkin.js b/src/main/resources/webapp/cucumber/js/highlight/gherkin.js new file mode 100644 index 0000000..c9ebde7 --- /dev/null +++ b/src/main/resources/webapp/cucumber/js/highlight/gherkin.js @@ -0,0 +1,47 @@ +/* + Language: Gherkin + Author: Sam Pikesley (@pikesley) + Description: Gherkin is the format for cucumber specifications. It is a domain specific language which helps you to describe business behavior without the need to go into detail of implementation. + Website: https://cucumber.io/docs/gherkin/ + */ + +export default function(hljs) { + return { + name: 'Gherkin', + aliases: [ 'feature' ], + keywords: 'Feature Background Ability Business\ Need Scenario Scenarios Scenario\ Outline Scenario\ Template Examples Given And Then But When', + contains: [ + { + className: 'symbol', + begin: '\\*', + relevance: 0 + }, + { + className: 'meta', + begin: '@[^@\\s]+' + }, + { + begin: '\\|', + end: '\\|\\w*$', + contains: [ + { + className: 'string', + begin: '[^|]+' + } + ] + }, + { + className: 'variable', + begin: '<', + end: '>' + }, + hljs.HASH_COMMENT_MODE, + { + className: 'string', + begin: '"""', + end: '"""' + }, + hljs.QUOTE_STRING_MODE + ] + }; +} \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber/js/petrel/AutoCompletion.js b/src/main/resources/webapp/cucumber/js/petrel/AutoCompletion.js new file mode 100644 index 0000000..947be15 --- /dev/null +++ b/src/main/resources/webapp/cucumber/js/petrel/AutoCompletion.js @@ -0,0 +1,5 @@ +export default class AutoCompletion { + autoComplete(){ + throw "Not implemented!" + } +} \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber/js/petrel/CodeEditor.js b/src/main/resources/webapp/cucumber/js/petrel/CodeEditor.js new file mode 100644 index 0000000..33e5a91 --- /dev/null +++ b/src/main/resources/webapp/cucumber/js/petrel/CodeEditor.js @@ -0,0 +1,463 @@ +import AutoCompletion from "./AutoCompletion.js" + +export default class CodeEditor { + + constructor(parentElement, options = {}){ + this.parentElement = parentElement + this.initElements() + this.tab = options.tabSize ? new Array(options.tabSize).join(" ") : " " + this.tabShortcutsEnabled = options.tabShortcutsEnabled ? options.tabShortcutsEnabled : true + this.placeholder = options.placeholder ? options.placeholder : "" + this.codeRightPadding = options.codeRightPadding ? options.codeRightPadding : 0 + this.readonly = options.readonly ? options.readonly : false + this.leftLineNumPadding = 0 + this.autoCompletionIndex = 0 + this.autoCompletionOpened = false + this.linesWithError = []; + + this.stringKeys = ['"', "'", '`', ...(options.stringKeys ? options.stringKeys : [])] + + this.closeKeys = { + '{': '}', + '(': ')', + '[': ']', + '"': '"', + "'": "'", + '`': '`', + ...(options.closeKeys ? options.closeKeys : {}) + } + + this.newLineTabs = [':', ': ', ...(options.newLineTabs ? options.newLineTabs : [])] + + this.value = options.value ? options.value : "" + + this.autoCompleteHandler = options.autoCompleteHandler ? options.autoCompleteHandler : null + + this.highlighter = v=>v.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'") + } + + initElements(){ + this.textAreaElement = document.createElement("textarea") + this.lineNumerationsElement = document.createElement("div") + this.codePreElement = document.createElement("pre") + this.autoCompletionElement = document.createElement("div") + + this.autoCompletionElement.classList.add("petrel-code-editor-autocompletion") + + this.lineNumerationsElement.classList.add("petrel-code-editor-line-numbering") + + this.emit("elementsReady", this.textAreaElement) + + this.textAreaElement.setAttribute("contenteditable", "on") + this.textAreaElement.setAttribute("spellcheck", "false") + this.textAreaElement.setAttribute("wrap", "off") + this.textAreaElement.setAttribute("placeholder", this.placeholder ? this.placeholder :'') + this.textAreaElement.addEventListener("input", ()=>{ + this.update() + }) + this.textAreaElement.addEventListener("keydown", event => { + if (this.readonly) { + event.preventDefault() + return; + } + const caretPos = this.getCaretPosition() + const oldValue = this.value + + if (this.tabShortcutsEnabled){ + switch (event.key) { + case "Tab": + if (!this.autoCompletionOpened){ + if (event.shiftKey) { + if (!this.hasSelection() && this.value.substring(this.getCaretPosition() - this.tab.length, this.getCaretPosition()) == this.tab) { + const newCaretPosition = this.getCaretPosition() - 4; + this.value = this.value.substring(0, this.getCaretPosition() - 4) + this.value.substring(this.getCaretPosition(), this.value.length); + this.setCaretPosition(newCaretPosition); + } + } else { + const newCaretPos = caretPos+this.tab.length + this.value = this.value.substring(0, this.getCaretPosition()) + this.tab + this.value.substring(this.getCaretPosition(), this.value.length) + this.setCaretPosition(newCaretPos) + this.emit("tab") + } + event.preventDefault() + break; + } + case "Enter": + if (this.autoCompletionOpened) { + this.autoCompletionElement.childNodes[this.autoCompletionIndex].click() + this.checkAutoCompletion() + event.preventDefault() + return; + } + break + case "Backspace": + if (!this.hasSelection() && this.value.substring(this.getCaretPosition() - this.tab.length, this.getCaretPosition()) == this.tab) { + const newCaretPosition = this.getCaretPosition() - 3; + this.value = this.value.substring(0, this.getCaretPosition() - 3) + this.value.substring(this.getCaretPosition(), this.value.length); + this.setCaretPosition(newCaretPosition); + } + this.emit("removedTab") + + break; + case "ArrowLeft": + if (!this.hasSelection() && !event.shiftKey && this.value.substring(this.getCaretPosition()-this.tab.length, this.getCaretPosition()) == this.tab) { + this.setCaretPosition(this.getCaretPosition()-this.tab.length+1); + } + break; + case "ArrowRight": + if (!this.hasSelection() && !event.shiftKey && this.value.substring(this.getCaretPosition()+this.tab.length, this.getCaretPosition()) == this.tab) { + this.setCaretPosition(this.getCaretPosition()+this.tab.length-1); + } + break; + case "ArrowUp": + if (this.autoCompletionOpened && !event.shiftKey) { + this.autoCompletionIndex-- + this.checkAutoCompletion() + event.preventDefault() + } + break + case "ArrowDown": + if (this.autoCompletionOpened && !event.shiftKey) { + this.autoCompletionIndex++ + this.checkAutoCompletion() + event.preventDefault() + } + break + } + } + + if (event.key == 'Enter'){ + if (caretPos == this.value.length) + this.textAreaElement.scrollBy(0, this.textAreaElement.scrollHeight) + } + + if (this.closeKeys !== false){ + for (const key in this.closeKeys) { + if (event.key == this.closeKeys[key] && this.value[caretPos-1] == key && this.value[caretPos] == this.closeKeys[key] + ) { + event.preventDefault() + this.setCaretPosition(caretPos+1) + break + } + if (event.key == 'Backspace' + && this.value.substring(caretPos-1, caretPos) == key + && this.value.substring(caretPos, caretPos+1) == this.closeKeys[key] + ) { + this.value = this.value.substring(0, caretPos-1)+this.value.substring(caretPos+1, this.value.length) + event.preventDefault() + this.setCaretPosition(caretPos-1) + break + } + if (event.key == key) { + const allowedKeys = ["\n", " ", ...Object.values(this.closeKeys)] + if (this.hasSelection()) { + const newCaret = caretPos+this.value.substring(this.textAreaElement.selectionStart, this.textAreaElement.selectionEnd).length + const oldStart = this.textAreaElement.selectionStart + const oldEnd = this.textAreaElement.selectionEnd + this.value = this.value.substring(0, this.textAreaElement.selectionStart)+key+this.value.substring(this.textAreaElement.selectionStart, this.textAreaElement.selectionEnd)+this.closeKeys[key]+this.value.substring(this.textAreaElement.selectionEnd, this.value.length) + + + this.setCaretPosition(newCaret+1); + this.textAreaElement.select() + this.textAreaElement.selectionStart = oldStart+1 + this.textAreaElement.selectionEnd = oldEnd+1 + event.preventDefault() + this.update() + return; + } else { + if (!(this.value.length > caretPos && !allowedKeys.includes(this.value[caretPos]))) { + this.value = this.value.substring(0, caretPos)+key+this.closeKeys[key]+this.value.substring(caretPos, this.value.length) + this.setCaretPosition(caretPos+1); + event.preventDefault() + this.update() + return; + } + } + } + } + + if (event.key == "Enter" && !this.hasSelection()) { + let startingSpaces = "" + let i = 0 + let lines = this.value.split("\n") + for (let line in lines) { + for (let a in lines[line].split("")) { + i++ + if (caretPos == i) { + const l = lines[line].split("") + for (let b in l) { + if (l[b] == ' ') + startingSpaces += " " + else + break + } + } + } + i++ + } + let caretInc = 0 + const char = this.value.substring(caretPos-1, caretPos) + if (['{','(','['].includes(char) || ('>' == char && this.value.substring(caretPos, caretPos+1) == '<')){ + console.log("YYAAA"); + this.value = this.value.substring(0, caretPos)+"\n"+this.tab+startingSpaces + // Check if the closing bracket is right after the caret. If yes, add newline + + (this.value.substring(caretPos, caretPos+1) == this.closeKeys[this.value.substring(caretPos-1, caretPos)] ? "\n" : '') + + startingSpaces + // Adding new line for < + + (char == '>' ? "\n" : '') + + this.value.substring(caretPos, this.value.length) + caretInc = this.tab.length+1 + } else if (this.newLineTabs.includes(char)){ + this.value = this.value.substring(0, caretPos)+"\n"+this.tab+startingSpaces+this.value.substring(caretPos, this.value.length) + caretInc = 5 + } else { + if (startingSpaces == "") + return; + this.value = this.value.substring(0, caretPos)+"\n"+startingSpaces+this.value.substring(caretPos, this.value.length) + caretInc = 1 + } + event.preventDefault() + this.setCaretPosition(caretPos+startingSpaces.length+caretInc); + } + } + + if (this.value != oldValue){ + document.execCommand('insertText',false,this.text); + this.update() + } + }) + + this.textAreaElement.addEventListener("keyup", ev=>{ + if (ev.key == "Escape") + this.closeAutocompletion() + + this.updateLineNumbering() + if (!ev.shiftKey && !["ArrowDown", "ArrowUp", "ArrowLeft", "ArrowRight", "Escape"].includes(ev.key)){ + this.checkAutoCompletion() + } + }) + this.textAreaElement.addEventListener("click", ()=>{ + this.updateLineNumbering() + this.closeAutocompletion() + }) + } + + async checkAutoCompletion(){ + if (this.autoCompleteHandler === null) + return; + const val = this.value + let lastWord = "" + for (let i = this.getCaretPosition()-1; i > -1; i--) { + const key = val[i] + if (key == "\n" || key == " ") { + break; + } + + lastWord = key+lastWord + } + + if (this.lastWord != lastWord){ + this.autoCompletionIndex = 0 + this.lastWord = lastWord + } + + let completionsWord; + + if (typeof completionsWord == 'function') { + completionsWord = this.autoCompleteHandler(lastWord, this) + } else /*if (completionsWord instanceof AutoCompletion)*/ { + completionsWord = this.autoCompleteHandler.autoComplete(lastWord, this) + } + + this.autoCompletionElement.innerHTML = "" + + if (completionsWord.length > 0) { //petrel-code-editor-autocompletion + // Get caret position + const { offsetLeft: inputX, offsetTop: inputY } = this.textAreaElement + const div = document.createElement('div') + const copyStyle = getComputedStyle(this.textAreaElement) + for (const prop of copyStyle) + div.style[prop] = copyStyle[prop] + const inputValue = this.textAreaElement.value + const textContent = inputValue.substr(0, this.textAreaElement.selectionEnd) + div.textContent = textContent + const span = document.createElement('span') + span.textContent = inputValue.substr(this.textAreaElement.selectionEnd) || '.' + div.appendChild(span) + document.body.appendChild(div) + + this.autoCompletionOpened = true + this.autoCompletionElement.style.display = "block" + + this.autoCompletionElement.style.top = inputX + span.offsetTop-20+"px" + this.autoCompletionElement.style.left = inputY + span.offsetLeft+this.leftLineNumPadding+5+"px" + + document.body.removeChild(div) + + let i = 0 + for (const completion of completionsWord) { + const el = document.createElement("a") + el.innerText = completion.text + + if (completion.type){ + const spanEl = document.createElement("span") + spanEl.innerText = completion.type + spanEl.classList.add("type-"+completion.type.toLowerCase()) + el.appendChild(spanEl) + } + + el.addEventListener("click", () => { + const caretPos = this.getCaretPosition() + const val = completion.replace() + this.value = this.value.substring(0, caretPos-lastWord.length)+val+this.value.substring(caretPos, this.value.length) + this.setCaretPosition(caretPos+val.length-lastWord.length+(completion.cursorMove||0)) + this.update() + this.checkAutoCompletion() + }) + this.autoCompletionElement.appendChild(el) + if (this.autoCompletionIndex == i) + el.classList.add("selected") + i++ + } + } else { + this.closeAutocompletion() + } + } + + closeAutocompletion(){ + this.autoCompletionElement.style.display = "none" + this.autoCompletionOpened = false + } + + emit(event, ...values){} + on(event, callable){} + + updateLineNumbering(){ + this.lineNumerationsElement.innerHTML = "" + const line = this.getCaretLine() + const lines = this.value.split("\n"); + for (let i = 0; i < lines.length; i++) { + i = Number(i) + const el = document.createElement("span") + if (this.linesWithError.includes(i)) { + el.className = "line-with-error"; + } + + if (line == i){ + const el2 = document.createElement("div") + el.appendChild(el2) + } + el.appendChild(document.createTextNode(i+1)) + this.lineNumerationsElement.appendChild(el) + } + } + + getCaretLine(){ + const caretPos = this.getCaretPosition() + const val = this.value + let line = 0 + let i = 0 + for (const c of val.split("")) { + if (caretPos == i) + break; + if (c == '\n') + line++; + i++; + } + return line + } + + update(){ + if (!this.lineNumberingDisabled) + this.updateLineNumbering() + this.codePreElement.innerHTML = this.highlighter(this.value) + + const leftPadding = (this.lineNumberingDisabled ? 0 : this.lineNumerationsElement.offsetWidth)+this.codeRightPadding + this.leftLineNumPadding = leftPadding + this.textAreaElement.style.marginLeft = leftPadding+'px' + this.codePreElement.style.marginLeft = leftPadding+'px' + + this.textAreaElement.style.height = '0px' + this.textAreaElement.style.height = this.textAreaElement.scrollHeight+'px' + this.textAreaElement.style.width = '0px' + this.textAreaElement.style.width = this.textAreaElement.scrollWidth+'px' + } + + create(){ + this.parentElement.classList.add("petrel-code-editor") + this.parentElement.appendChild(this.lineNumerationsElement) + + this.parentElement.appendChild(this.textAreaElement) + this.parentElement.appendChild(this.codePreElement) + this.parentElement.appendChild(this.autoCompletionElement) + this.autoCompletionElement.style.display = "none" + this.update() + } + + + setHighlighter(highlighter){ + this.highlighter = highlighter + return this + } + + setAutoCompleteHandler(autoCompleteHandler){ + this.autoCompleteHandler = autoCompleteHandler + return this + } + + setLinesWithError(linesWithError){ + this.linesWithError = linesWithError; + return this; + } + + setValue(val){ + this.value = val; + this.update() + return this + } + + getValue(){ + return this.value; + } + + set value(to){ + this.textAreaElement.value = to + } + + get value(){ + return this.textAreaElement.value + } + + set readonly(to){ + this.textAreaElement.readOnly = to + } + get readonly(){ + return this.textAreaElement.readOnly + } + + focus(){ + this.textAreaElement.focus() + } + + getCaretPosition(){ + return this.textAreaElement.selectionStart + } + + setCaretPosition(position) { //change the caret position of the textarea + this.textAreaElement.selectionStart = position; + this.textAreaElement.selectionEnd = position; + this.focus(); + }; + hasSelection() { + return this.textAreaElement.selectionStart != this.textAreaElement.selectionEnd + }; + getSelectedText() { + return this.value.substring(this.textAreaElement.selectionStart, this.textAreaElement.selectionEnd); + }; + setSelection(start, end) { + this.textAreaElement.selectionStart = start; + this.textAreaElement.selectionEnd = end; + this.textAreaElement.focus(); + }; + +} \ No newline at end of file diff --git a/src/main/resources/webapp/cucumber/js/petrel/GherkinAutoComplete.js b/src/main/resources/webapp/cucumber/js/petrel/GherkinAutoComplete.js new file mode 100644 index 0000000..2b92429 --- /dev/null +++ b/src/main/resources/webapp/cucumber/js/petrel/GherkinAutoComplete.js @@ -0,0 +1,27 @@ +import AutoCompletion from './AutoCompletion.js' + +export default class GherkinAutoComplete extends AutoCompletion { + autoComplete(word, editor){ + const searchWord = word.replaceAll(/\(|{|;/g, "") + + const ret = [] + if (searchWord == "") + return [] + + for (const key of GherkinAutoComplete.KEYWORDS) { + if (key.toLowerCase().startsWith(searchWord.toLowerCase()) && searchWord !== key){ + + ret.push({ + text: key, + type: 'KEYWORD', + replace: () => key+" " + }) + } + } + + return ret + } +} + +GherkinAutoComplete.FILE_EXTENSIONS = ["feature"] +GherkinAutoComplete.KEYWORDS = ["Feature", "Rule", "Example", "Scenario", "Given", "When", "Then", "And", "But", "*", "Scenario Outline", "Scenario Template", "Examples"] diff --git a/src/test/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationFormExtensionTest.java b/src/test/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationFormExtensionTest.java new file mode 100644 index 0000000..e9c5a54 --- /dev/null +++ b/src/test/java/ch/sbb/polarion/extension/cucumber/CucumberIntegrationFormExtensionTest.java @@ -0,0 +1,162 @@ +package ch.sbb.polarion.extension.cucumber; + +import com.polarion.alm.projects.model.IProject; +import com.polarion.alm.shared.api.SharedContext; +import com.polarion.alm.shared.api.utils.html.HtmlBuilderTargetSelector; +import com.polarion.alm.shared.api.utils.html.HtmlFragmentBuilder; +import com.polarion.alm.tracker.model.IAttachmentBase; +import com.polarion.alm.tracker.model.ICategory; +import com.polarion.alm.tracker.model.IWorkItem; +import com.polarion.platform.persistence.model.IPObject; +import com.polarion.platform.persistence.spi.PObjectList; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.stream.Stream; + +import static com.polarion.platform.persistence.model.IPObjectList.EMPTY_POBJECTLIST; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings("unchecked") +class CucumberIntegrationFormExtensionTest { + @Mock + CucumberIntegrationFormExtension extension; + + @Mock + private IWorkItem iWorkItem; + + @Mock + private SharedContext context; + @Mock + private HtmlBuilderTargetSelector builder; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private HtmlFragmentBuilder htmlFragmentBuilder; + + private static Stream testValuesForRenderIntegrationTestIsPersistedIssue() { + + var workItem = mock(IWorkItem.class); + var iProject = mock(IProject.class); + + return Stream.of( + Arguments.of(false, workItem), + Arguments.of(true, iProject), + Arguments.of(false, iProject) + ); + } + + private static Stream testValuesForgetContentWithAttachment() { + + var object = mock(IWorkItem.class); + when(object.getId()).thenReturn("id"); + + var attachment = mock(IAttachmentBase.class); + when(attachment.getFileName()).thenReturn("id.feature"); + when(attachment.getDataStream()).thenAnswer((arg) -> new ByteArrayInputStream("test".getBytes())); + + var attachment2 = mock(ICategory.class); + + var attachment3 = mock(IAttachmentBase.class); + when(attachment3.getFileName()).thenReturn("id3.feature"); + when(attachment3.getDataStream()).thenReturn(new ByteArrayInputStream("test3".getBytes())); + + var attachment4 = mock(IAttachmentBase.class); + when(attachment4.getFileName()).thenReturn("id.feature"); + when(attachment4.getDataStream()).thenReturn(new ByteArrayInputStream("test4".getBytes())); + + return Stream.of( + Arguments.of(object, new PObjectList(null, Arrays.asList(attachment)), "test"), + Arguments.of(object, new PObjectList(null, Arrays.asList(attachment, attachment2)), "test"), + Arguments.of(object, new PObjectList(null, Arrays.asList(attachment, attachment2, attachment3)), "test"), + Arguments.of(object, new PObjectList(null, Arrays.asList(attachment, attachment4)), "test4") + ); + } + + @Test + void getLabel() { + when(extension.getLabel(iWorkItem, new HashMap<>())).thenCallRealMethod(); + assertThat(extension.getLabel(iWorkItem, new HashMap<>())) + .isEqualTo("Cucumber Test"); + } + + @Test + void getIcon() { + when(extension.getIcon(iWorkItem, new HashMap<>())).thenCallRealMethod(); + + assertThat(extension.getIcon(iWorkItem, new HashMap<>())).isNull(); + } + + @ParameterizedTest + @MethodSource("testValuesForRenderIntegrationTestIsPersistedIssue") + void renderIntegrationTestIsPersistedIssue(Boolean isPersisted, IPObject object) { + when(extension.renderIntegrationTest(context, object, true)).thenCallRealMethod(); + + when(context.createHtmlFragmentBuilderFor()).thenReturn(builder); + when(builder.gwt()).thenReturn(htmlFragmentBuilder); + + String builderText = "text"; + when(htmlFragmentBuilder.toString()).thenReturn(builderText); + when(htmlFragmentBuilder.tag().div().append().tag().b().append().text(anyString())).thenReturn(htmlFragmentBuilder); + + when(object.isPersisted()).thenReturn(isPersisted); + + assertThat(extension.renderIntegrationTest(context, object, true)).isEqualTo(builderText); + + verify(extension, times(0)).addSource(eq(htmlFragmentBuilder), eq("text/javascript"), anyString()); + verify(htmlFragmentBuilder, times(0)).html(anyString()); + verify(htmlFragmentBuilder, times(1)).finished(); + } + + @Test + void renderIntegrationTestWithProjectAsWorkItem() throws IOException { + var object = mock(IWorkItem.class); + + when(extension.renderIntegrationTest(context, object, true)).thenCallRealMethod(); + + when(extension.getContent(object, EMPTY_POBJECTLIST)).thenCallRealMethod(); + + when(context.createHtmlFragmentBuilderFor()).thenReturn(builder); + when(builder.gwt()).thenReturn(htmlFragmentBuilder); + + String builderText = "text"; + when(htmlFragmentBuilder.toString()).thenReturn(builderText); + + when(object.isPersisted()).thenReturn(true); + when(object.getAttachments()).thenReturn(EMPTY_POBJECTLIST); + + assertThat(extension.renderIntegrationTest(context, object, true)).isEqualTo(builderText); + + verify(extension, times(1)).addSource(eq(htmlFragmentBuilder), eq("text/javascript"), anyString()); + verify(extension, times(1)).addSource(eq(htmlFragmentBuilder), eq("module"), anyString()); + verify(htmlFragmentBuilder, times(5)).html(anyString()); + verify(htmlFragmentBuilder, times(1)).finished(); + } + + @Test + void getContentWithoutAttachment() throws IOException { + var object = mock(IWorkItem.class); + when(extension.getContent(object, EMPTY_POBJECTLIST)).thenCallRealMethod(); + + var b = extension.getContent(object, EMPTY_POBJECTLIST); + assertThat(b).isEmpty(); + } + + @ParameterizedTest + @MethodSource("testValuesForgetContentWithAttachment") + void getContentWithAttachment(IWorkItem object, PObjectList list, String expected) throws IOException { + when(extension.getContent(object, list)).thenCallRealMethod(); + var result = extension.getContent(object, list); + assertThat(result).isEqualTo(expected); + } +} diff --git a/src/test/java/ch/sbb/polarion/extension/cucumber/helper/PolarionTestRunTest.java b/src/test/java/ch/sbb/polarion/extension/cucumber/helper/PolarionTestRunTest.java new file mode 100644 index 0000000..cceb21a --- /dev/null +++ b/src/test/java/ch/sbb/polarion/extension/cucumber/helper/PolarionTestRunTest.java @@ -0,0 +1,302 @@ +package ch.sbb.polarion.extension.cucumber.helper; + +import ch.sbb.polarion.extension.cucumber.exception.TestRunCreationException; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.ExecutionInfo; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.ExecutionRecord; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.Comment; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.CucumberExecutionResult; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.Element; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.Result; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.Step; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.Tag; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.junit.TestCase; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.junit.TestSuite; +import ch.sbb.polarion.extension.cucumber.rest.model.fields.FieldDefinition; +import ch.sbb.polarion.extension.generic.service.PolarionService; +import com.polarion.alm.projects.IProjectService; +import com.polarion.alm.projects.model.IProject; +import com.polarion.alm.tracker.ITestManagementService; +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.internal.model.StatusOpt; +import com.polarion.alm.tracker.model.ITestRecord; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.alm.tracker.model.ITypeOpt; +import com.polarion.alm.tracker.model.IWorkItem; +import com.polarion.core.util.types.Text; +import com.polarion.platform.persistence.IDataService; +import com.polarion.platform.persistence.spi.EnumOption; +import com.polarion.platform.persistence.spi.PObjectList; +import com.polarion.subterra.base.data.model.ICustomField; +import com.polarion.subterra.base.data.model.internal.PrimitiveType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.stream.Stream; + +import static ch.sbb.polarion.extension.cucumber.helper.PolarionTestRun.TYPE_REQUIRES_SIGNATURE; +import static ch.sbb.polarion.extension.cucumber.rest.model.execution.ExecutionRecord.NANOSECONDS_IN_SECOND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PolarionTestRunTest { + public static final String TEST_EXECUTION = "TEST_EXECUTION"; + public static final String TEST_PROJECT_ID = "PRJ"; + public static final String TEST_ISSUE_1 = "PRJ-1"; + public static final String TEST_ISSUE_2 = "PRJ-2"; + public static final String TEST_CASE_1 = "TestCase1"; + public static final String TEST_CASE_2 = "TestCase2"; + public static final String TEST_CASE_3 = "TestCase3"; + public static final String TEST_PROJECT_KEY = "TEST_PROJECT_KEY"; + public static final String TEST_TITLE = "TEST_TITLE"; + public static final String TEST_TEMPLATE = "TEST_TEMPLATE"; + private static final String TEST_CUSTOM_FIELD_ID = "testCustomFieldId"; + + @Mock + private ITrackerService trackerService; + + @Mock + private IProjectService projectService; + + @Mock + private ITestManagementService testManagementService; + + @Mock + private IProject project; + + @Mock + private ITestRun testRun; + + @Mock + private IWorkItem workItem1; + @Mock + private IWorkItem workItem2; + + @Mock + private ITestRecord testRecord1; + @Mock + private ITestRecord testRecord2; + + private PolarionService polarionService; + + @BeforeEach + public void setup() { + polarionService = mock(PolarionService.class); + lenient().when(polarionService.getTrackerService()).thenReturn(trackerService); + } + + @Test + void shouldCreatePassedTestRun() { + // Arrange + prepareMockedData(TEST_TEMPLATE, true); + List testRuns = PolarionTestRun.createTestRuns(polarionService, testManagementService, prepareCucumberExecutionInfo(), buildResults(null)); + + // Assert + assertThat(testRuns) + .hasSize(1) + .contains(testRun); + verify(testRecord1).setTestCase(workItem1); + verify(testRecord2).setTestCase(workItem2); + Stream.of(testRecord1, testRecord2) + .forEach(tr -> { + verify(tr).setDuration((float) 6 / NANOSECONDS_IN_SECOND); + verify(tr).setExecuted(any(Date.class)); + verify(tr).setResult("passed"); + }); + verify(testRun).setTitle(TEST_TITLE); + EnumOption enumOption = new EnumOption("testing/testrun-status", "passed", "Passed", 3, false); + verify(testRun).setStatus(new StatusOpt(enumOption)); + } + + @Test + void shouldGetTestRunUsingDifferentMethods() { + // Arrange + prepareMockedData(TEST_TEMPLATE, true); + //lenient().when(trackerService.findWorkItem(anyString(), anyString())).thenReturn(null); + when(trackerService.queryWorkItems(anyString(), anyString())).thenReturn(new PObjectList(mock(IDataService.class), List.of())); + List executionResults = buildJUnitResults(); + List testRuns = PolarionTestRun.createTestRuns(polarionService, testManagementService, prepareCucumberExecutionInfo(), executionResults); + assertThat(testRuns) + .hasSize(1) + .contains(testRun); + verify(testRun, times(0)).addRecord(); // no test run found using queryWorkItems() -> 0 test records added + + when(trackerService.queryWorkItems(anyString(), anyString())).thenReturn(new PObjectList(mock(IDataService.class), List.of())); + + when(trackerService.queryWorkItems(String.format("project.id:%s AND type:testcase AND title:\"%s\"", + TEST_PROJECT_ID, TEST_CASE_1), "id")).thenReturn(new PObjectList(mock(IDataService.class), List.of(workItem1))); + when(trackerService.queryWorkItems(String.format("project.id:%s AND type:testcase AND title:\"%s\"", + TEST_PROJECT_ID, TEST_CASE_2), "id")).thenReturn(new PObjectList(mock(IDataService.class), List.of(workItem2))); + + testRuns = PolarionTestRun.createTestRuns(polarionService, testManagementService, prepareCucumberExecutionInfo(), executionResults); + assertThat(testRuns) + .hasSize(1) + .contains(testRun); + + verify(testRun, times(2)).addRecord(); // no test run found using queryWorkItems() -> 0 test records added + } + + @Test + void shouldCreateFailedTestRun() { + // Arrange + prepareMockedData(TEST_TEMPLATE, false); + + // Act + List testRuns = PolarionTestRun.createTestRuns(polarionService, testManagementService, prepareCucumberExecutionInfo(), buildResults("errMsg1")); + + // Assert + assertThat(testRuns) + .hasSize(1) + .contains(testRun); + verify(testRecord1).setTestCase(workItem1); + verify(testRecord2).setTestCase(workItem2); + Stream.of(testRecord1, testRecord2) + .forEach(tr -> { + verify(tr).setDuration((float) 6 / NANOSECONDS_IN_SECOND); + verify(tr).setExecuted(any(Date.class)); + verify(tr).setResult("failed"); + verify(tr).setComment(Text.plain("errMsg1")); + }); + verify(testRun).setTitle(TEST_TITLE); + EnumOption enumOption = new EnumOption("testing/testrun-status", "failed", "Failed", 3, false); + verify(testRun).setStatus(new StatusOpt(enumOption)); + verify(testRun).setStatus(new StatusOpt(enumOption)); + } + + @Test + void shouldFailCreateTestRunBadTemplateId() { + // Arrange + prepareMockedData("UNKNOWN_TEST_TEMPLATE", true); + + // Act + ExecutionInfo executionInfo = prepareCucumberExecutionInfo(); + List executionResults = buildResults(null); + assertThatThrownBy(() -> PolarionTestRun.createTestRuns(polarionService, testManagementService, executionInfo, executionResults)) + .isInstanceOf(TestRunCreationException.class); + } + + @Test + void shouldFailCreateTestRunBadTemplateType() { + // Arrange + prepareMockedData(TEST_TEMPLATE, true); + + //emulate type with 'Requires Signature for Test Case execution' turned on + ITestRun template = mock(ITestRun.class); + when(template.getId()).thenReturn(TEST_TEMPLATE); + ITypeOpt type = mock(ITypeOpt.class); + when(type.getProperty(TYPE_REQUIRES_SIGNATURE)).thenReturn("true"); + when(template.getType()).thenReturn(type); + when(testManagementService.searchTestRunTemplates(eq("project.id:" + TEST_PROJECT_ID), any(), anyInt())) + .thenReturn(List.of(template)); + + // Act + ExecutionInfo executionInfo = prepareCucumberExecutionInfo(); + List executionResults = buildResults("errMsg1"); + assertThatThrownBy(() -> PolarionTestRun.createTestRuns(polarionService, testManagementService, executionInfo, executionResults)) + .isInstanceOf(TestRunCreationException.class); + } + + private List buildResults(String secondStepErrorMessage) { + return ExecutionRecord.fromCucumberExecutions(List.of(prepareCucumberExecutionResult(secondStepErrorMessage))); + } + + private List buildJUnitResults() { + TestSuite testSuite = new TestSuite(); + testSuite.setTestCases(Arrays.asList( + new TestCase(null, null, null, null, null, TEST_CASE_1, 1, 1d, null, null), + new TestCase(null, null, null, null, null, TEST_CASE_2, 1, 2d, null, null), + new TestCase(null, null, null, null, null, TEST_CASE_3, 1, 3d, null, null) + )); + return ExecutionRecord.fromJUnitReport(testSuite); + } + + private void prepareMockedData(String testRunTemplateId, boolean passed) { + when(trackerService.getProjectsService()).thenReturn(projectService); + when(projectService.getProject(TEST_PROJECT_KEY)).thenReturn(project); + when(project.getId()).thenReturn(TEST_PROJECT_ID); + lenient().when(testManagementService.createTestRun(eq(TEST_PROJECT_ID), startsWith(TEST_PROJECT_ID), anyString())) + .thenReturn(testRun); + ITestRun template = mock(ITestRun.class); + lenient().when(template.getId()).thenReturn(testRunTemplateId); + lenient().when(testManagementService.searchTestRunTemplates(eq("project.id:" + TEST_PROJECT_ID), any(), anyInt())) + .thenReturn(List.of(template)); + + lenient().when(testRun.getProjectId()).thenReturn(TEST_PROJECT_ID); + lenient().when(trackerService.findWorkItem(TEST_PROJECT_ID, TEST_ISSUE_1)).thenReturn(workItem1); + lenient().when(trackerService.findWorkItem(TEST_PROJECT_ID, TEST_ISSUE_2)).thenReturn(workItem2); + lenient().when(testRun.addRecord()).thenReturn(testRecord1, testRecord2); + lenient().when(testRun.getAllRecords()).thenReturn(List.of(testRecord1, testRecord2)); + + lenient().when(testRun.getCustomFieldsList()).thenReturn(List.of(TEST_CUSTOM_FIELD_ID)); + ICustomField customField = mock(ICustomField.class); + lenient().when(customField.getType()).thenReturn(new PrimitiveType(String.class.getName(), null)); + lenient().when(testRun.getCustomFieldPrototype(any())).thenReturn(customField); + + lenient().when(testRecord1.getResult()).thenReturn(new EnumOption("testing/testrun-status", passed ? "passed" : "failed", passed ? "Passed" : "Failed", 3, false)); + lenient().when(testRecord2.getResult()).thenReturn(new EnumOption("testing/testrun-status", passed ? "passed" : "failed", passed ? "Passed" : "Failed", 3, false)); + } + + private ExecutionInfo prepareCucumberExecutionInfo() { + ExecutionInfo executionInfo = new ExecutionInfo(); + executionInfo.getFields().put(ExecutionInfo.PROJECT_KEY, Collections.singletonMap("key", TEST_PROJECT_KEY)); + executionInfo.getFields().put(FieldDefinition.TITLE.getId(), TEST_TITLE); + executionInfo.getFields().put(FieldDefinition.TEST_RUN_TEMPLATE_ID.getId(), TEST_TEMPLATE); + executionInfo.getFields().put(TEST_CUSTOM_FIELD_ID, "customFieldValue"); + return executionInfo; + } + + private CucumberExecutionResult prepareCucumberExecutionResult(String secondStepErrorMessage) { + String resultName = TEST_EXECUTION; + String issueId1 = TEST_ISSUE_1; + String issueId2 = TEST_ISSUE_2; + CucumberExecutionResult result = new CucumberExecutionResult(); + result.setName(resultName); + Tag tag = new Tag(); + tag.setName(resultName); + result.setTags(List.of(tag)); + + Element executionElement1 = prepareExecutionElement(issueId1, "background", null); + Element executionElement2 = prepareExecutionElement(issueId1, "scenario", secondStepErrorMessage); + Element executionElement3 = prepareExecutionElement(issueId2, "background", null); + Element executionElement4 = prepareExecutionElement(issueId2, "scenario", secondStepErrorMessage); + + result.setElements(List.of( + executionElement1, + executionElement2, + executionElement3, + executionElement4)); + return result; + } + + private Element prepareExecutionElement(String issueId, String type, String secondStepErrorMessage) { + Element executionElement = new Element(); + Tag elementTag = new Tag(); + elementTag.setName("@" + issueId); + executionElement.setTags(List.of(elementTag)); + Comment comment = new Comment(); + comment.setValue("TEST_COMMENT"); + executionElement.setComments(List.of(comment)); + Step step1 = prepareCucumberStep("testStep1", 1L, "passed", null); + Step step2 = prepareCucumberStep("testStep2", 2L, secondStepErrorMessage != null ? "failed" : "passed", secondStepErrorMessage); + executionElement.setSteps(List.of(step1, step2)); + executionElement.setType(type); + return executionElement; + } + + private Step prepareCucumberStep(String stepName, long duration, String status, String errorMessage) { + Step step = new Step(); + step.setName(stepName); + step.setResult(new Result(status, duration, errorMessage)); + return step; + } + +} diff --git a/src/test/java/ch/sbb/polarion/extension/cucumber/helper/PolarionWorkItemTest.java b/src/test/java/ch/sbb/polarion/extension/cucumber/helper/PolarionWorkItemTest.java new file mode 100644 index 0000000..f2643e7 --- /dev/null +++ b/src/test/java/ch/sbb/polarion/extension/cucumber/helper/PolarionWorkItemTest.java @@ -0,0 +1,135 @@ +package ch.sbb.polarion.extension.cucumber.helper; + +import ch.sbb.polarion.extension.cucumber.rest.model.Feature; +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.model.IAttachment; +import com.polarion.alm.tracker.model.IWorkItem; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.NotFoundException; +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PolarionWorkItemTest { + + String testProjectId = "TEST_PROJECT_ID"; + String testWi = "TEST_WI"; + String title = "path/to/the/feature/AAA.feature"; + String filename = "TEST_WI.feature"; + String fileContent = "content"; + Feature created = new Feature(testProjectId, testWi, title, filename, fileContent); + @Mock + private ITrackerService trackerService; + @Mock + private IWorkItem workItem; + @Mock + private IAttachment attachment; + + @Test + void getFeatureWithUnresolvableWorkItem() { + when(trackerService.findWorkItem(testProjectId, testWi)).thenReturn(workItem); + when(workItem.isUnresolvable()).thenReturn(true); + + assertThatThrownBy(() -> + PolarionWorkItem.getFeature(trackerService, testProjectId, testWi)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining(String.format("WorkItem '%s/%s' not found!", testProjectId, testWi)); + } + + @Test + void getFeatureWithoutAttachment() { + when(trackerService.findWorkItem(testProjectId, testWi)).thenReturn(workItem); + when(workItem.isUnresolvable()).thenReturn(false); + when(workItem.getAttachmentByFileName(filename)).thenReturn(null); + + Exception thrown = assertThrows(Exception.class, () -> + PolarionWorkItem.getFeature(trackerService, testProjectId, testWi) + ); + + assertEquals( + String.format("WorkItem '%s/%s' doesn't have attachment with filename '%s'!", testProjectId, testWi, filename), + thrown.getMessage() + ); + } + + @Test + void getFeatureWithoutErrors() { + when(trackerService.findWorkItem(testProjectId, testWi)).thenReturn(workItem); + when(workItem.isUnresolvable()).thenReturn(false); + when(workItem.getAttachmentByFileName(filename)).thenReturn(attachment); + when(attachment.getTitle()).thenReturn(title); + when(attachment.getDataStream()).thenReturn(new ByteArrayInputStream(fileContent.getBytes(StandardCharsets.UTF_8))); + + Feature feature = PolarionWorkItem.getFeature(trackerService, testProjectId, testWi); + Feature expected = new Feature(testProjectId, testWi, title, filename, fileContent); + + assertThat(feature.getWorkItemId()).isEqualTo(expected.getWorkItemId()); + assertThat(feature.getContent()).isEqualTo(expected.getContent()); + assertThat(feature.getProjectId()).isEqualTo(expected.getProjectId()); + assertThat(feature.getFilename()).isEqualTo(expected.getFilename()); + assertThat(feature.getTitle()).isEqualTo(expected.getTitle()); + } + + @Test + void getFeatureByFullWorkItemId() { + BadRequestException thrown = assertThrows(BadRequestException.class, () -> + PolarionWorkItem.getFeature(trackerService, "noSlashId") + ); + assertEquals("Provided workitem id is not in 'projectId/workItemId' format: noSlashId", thrown.getMessage()); + + try (MockedStatic mockedPolarionWorkItem = mockStatic(PolarionWorkItem.class)) { + Feature feature = new Feature(); + mockedPolarionWorkItem.when(() -> PolarionWorkItem.getFeature(any(ITrackerService.class), anyString(), anyString())) + .thenAnswer(invocation -> feature); + mockedPolarionWorkItem.when(() -> PolarionWorkItem.getFeature(any(ITrackerService.class), anyString())).thenCallRealMethod(); + Feature resultFeature = PolarionWorkItem.getFeature(trackerService, "proj/wiId"); + assertEquals(feature, resultFeature); + } + } + + @Test + void createOrUpdateFeatureWihUnresolvableWorkItem() { + Feature created = new Feature(testProjectId, testWi, title, filename, fileContent); + + when(trackerService.findWorkItem(testProjectId, testWi)).thenReturn(workItem); + when(workItem.isUnresolvable()).thenReturn(true); + + assertThatThrownBy(() -> + PolarionWorkItem.createOrUpdateFeature(trackerService, created)) + .isInstanceOf(NotFoundException.class) + .hasMessageContaining(String.format("WorkItem '%s/%s' not found!", testProjectId, testWi)); + } + + @Test + void createOrUpdateFeatureWithoutAttachment() { + when(trackerService.findWorkItem(testProjectId, testWi)).thenReturn(workItem); + when(workItem.isUnresolvable()).thenReturn(false); + when(workItem.getAttachmentByFileName(filename)).thenReturn(null); + when(workItem.createAttachment(created.getFilename(), title, null)).thenReturn(attachment); + + PolarionWorkItem.createOrUpdateFeature(trackerService, created); + verify(attachment, times(1)).save(); + } + + @Test + void createOrUpdateFeature() { + when(trackerService.findWorkItem(testProjectId, testWi)).thenReturn(workItem); + when(workItem.isUnresolvable()).thenReturn(false); + when(workItem.getAttachmentByFileName("TEST_WI.feature")).thenReturn(attachment); + + PolarionWorkItem.createOrUpdateFeature(trackerService, created); + verify(attachment, times(1)).save(); + } +} diff --git a/src/test/java/ch/sbb/polarion/extension/cucumber/rest/controller/JiraRestApiControllerTest.java b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/controller/JiraRestApiControllerTest.java new file mode 100644 index 0000000..e7de4b2 --- /dev/null +++ b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/controller/JiraRestApiControllerTest.java @@ -0,0 +1,90 @@ +package ch.sbb.polarion.extension.cucumber.rest.controller; + +import ch.sbb.polarion.extension.cucumber.rest.model.fields.FieldDefinition; +import ch.sbb.polarion.extension.generic.fields.model.FieldMetadata; +import ch.sbb.polarion.extension.generic.service.PolarionService; +import com.google.inject.Injector; +import com.polarion.alm.projects.model.IProject; +import com.polarion.alm.shared.api.transaction.ReadOnlyTransaction; +import com.polarion.alm.shared.api.transaction.RunnableInReadOnlyTransaction; +import com.polarion.alm.shared.api.transaction.TransactionalExecutor; +import com.polarion.core.util.types.Text; +import com.polarion.platform.guice.internal.GuicePlatform; +import com.polarion.subterra.base.data.identification.IContextId; +import com.polarion.subterra.base.data.model.internal.PrimitiveType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.ws.rs.BadRequestException; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings({"unchecked", "rawtypes", "ResultOfMethodCallIgnored"}) +class JiraRestApiControllerTest { + + @Test + void testExceptionOnEmptyProject() { + try (MockedStatic mockedExecutor = mockStatic(TransactionalExecutor.class); + MockedStatic guicePlatform = mockStatic(GuicePlatform.class)) { + + PolarionService polarionService = mockCommonThings(mockedExecutor, guicePlatform); + JiraRestApiController controller = new JiraRestApiController(polarionService); + BadRequestException thrown = assertThrows(BadRequestException.class, () -> + controller.field(null) + ); + assertEquals("No proper projectId header found!", thrown.getMessage()); + thrown = assertThrows(BadRequestException.class, () -> + controller.field(" ") + ); + assertEquals("No proper projectId header found!", thrown.getMessage()); + } + } + + @Test + void testField() { + try (MockedStatic mockedExecutor = mockStatic(TransactionalExecutor.class); + MockedStatic guicePlatform = mockStatic(GuicePlatform.class)) { + + PolarionService polarionService = mockCommonThings(mockedExecutor, guicePlatform); + IProject project = mock(IProject.class); + when(project.getContextId()).thenReturn(mock(IContextId.class)); + when(polarionService.getProject(anyString())).thenReturn(project); + when(polarionService.getCustomFields(anyString(), any(IContextId.class), isNull())).thenReturn(Set.of( + new FieldMetadata("id1", "label1", new PrimitiveType(Text.class.getName(), null), true, true, false, Set.of()), + new FieldMetadata("id2", "label2", new PrimitiveType(Text.class.getName(), null), true, true, false, Set.of()) + )); + + List fields = new JiraRestApiController(polarionService).field("projId"); + assertEquals(4, fields.size()); + assertTrue(fields.stream().map(FieldDefinition::getId).toList().containsAll(List.of("id1", "id2", "title", "description"))); + assertTrue(fields.stream().map(FieldDefinition::getName).toList().containsAll(List.of("label1", "label2", "summary", "description"))); + } + } + + private PolarionService mockCommonThings(MockedStatic mockedExecutor, MockedStatic guicePlatform) { + mockedExecutor.when(() -> TransactionalExecutor.executeInReadOnlyTransaction(any(RunnableInReadOnlyTransaction.class))) + .thenAnswer(invocation -> { + RunnableInReadOnlyTransaction transaction = invocation.getArgument(0); + return transaction.run(mock(ReadOnlyTransaction.class)); + }); + + guicePlatform.when(GuicePlatform::getGlobalInjector).thenReturn(mock(Injector.class)); + + PolarionService polarionService = mock(PolarionService.class); + lenient().when(polarionService.callPrivileged(any(Callable.class))).thenAnswer(invocation -> { + Callable callable = invocation.getArgument(0); + return callable.call(); + }); + + return polarionService; + } + +} diff --git a/src/test/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayExportCucumberTestsControllerTest.java b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayExportCucumberTestsControllerTest.java new file mode 100644 index 0000000..da0c59a --- /dev/null +++ b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayExportCucumberTestsControllerTest.java @@ -0,0 +1,97 @@ +package ch.sbb.polarion.extension.cucumber.rest.controller; + +import ch.sbb.polarion.extension.cucumber.helper.PolarionWorkItem; +import ch.sbb.polarion.extension.cucumber.rest.model.Feature; +import ch.sbb.polarion.extension.generic.service.PolarionService; +import com.polarion.alm.shared.api.transaction.ReadOnlyTransaction; +import com.polarion.alm.shared.api.transaction.RunnableInReadOnlyTransaction; +import com.polarion.alm.shared.api.transaction.TransactionalExecutor; +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.model.IWorkItem; +import com.polarion.platform.persistence.model.IPObjectList; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.StreamingOutput; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.concurrent.Callable; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings({"unchecked", "rawtypes"}) +class XrayExportCucumberTestsControllerTest { + + @Test + @SneakyThrows + void testExportTest() { + + try (MockedStatic mockedExecutor = mockStatic(TransactionalExecutor.class); + MockedStatic mockedPolarionWorkItem = mockStatic(PolarionWorkItem.class)) { + + PolarionService polarionService = mock(PolarionService.class); + + mockedExecutor.when(() -> TransactionalExecutor.executeSafelyInReadOnlyTransaction(any(RunnableInReadOnlyTransaction.class))) + .thenAnswer(invocation -> { + RunnableInReadOnlyTransaction transaction = invocation.getArgument(0); + return transaction.run(mock(ReadOnlyTransaction.class)); + }); + + Feature testFeatureOne = new Feature("projId1", "wiId1", "title1", "filename1", "content1"); + mockedPolarionWorkItem.when(() -> PolarionWorkItem.getFeature(any(), any())).thenReturn(testFeatureOne); + Feature testFeatureTwo = new Feature("projId2", "wiId2", "title2", "filename2", "content2"); + mockedPolarionWorkItem.when(() -> PolarionWorkItem.getFeature(any(), any(), any())).thenReturn(testFeatureTwo); + + when(polarionService.callPrivileged(any(Callable.class))).thenAnswer(invocation -> { + Callable callable = invocation.getArgument(0); + return callable.call(); + }); + + ITrackerService trackerService = mock(ITrackerService.class); + IPObjectList wiList = mock(IPObjectList.class); + IWorkItem workItem = mock(IWorkItem.class); + when(wiList.iterator()).thenReturn(Collections.singletonList(workItem).iterator()); + doCallRealMethod().when(wiList).forEach(any()); + when(trackerService.queryWorkItems(any(), any())).thenReturn(wiList); + when(polarionService.getTrackerService()).thenReturn(trackerService); + + try (Response response = new XrayExportCucumberTestsController(polarionService).exportTest("keys", "query", true)) { + assertThat(response).isNotNull(); + assertTrue(response.getEntity() instanceof StreamingOutput); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ((StreamingOutput) response.getEntity()).write(outputStream); + ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(outputStream.toByteArray())); + ZipEntry firstEntry = zipInputStream.getNextEntry(); + ZipEntry secondEntry = zipInputStream.getNextEntry(); + assertThat(firstEntry).isNotNull(); + assertEquals("title1", firstEntry.getName()); + assertThat(secondEntry).isNotNull(); + assertEquals("title2", secondEntry.getName()); + } + + try (Response response = new XrayExportCucumberTestsController(polarionService).exportTest("keys", "query", false)) { + assertThat(response).isNotNull(); + assertTrue(response.getEntity() instanceof StreamingOutput); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ((StreamingOutput) response.getEntity()).write(outputStream); + assertEquals("content1", outputStream.toString(StandardCharsets.UTF_8)); + } + } + } + +} diff --git a/src/test/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayImportExecutionResultsControllerTest.java b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayImportExecutionResultsControllerTest.java new file mode 100644 index 0000000..775e262 --- /dev/null +++ b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/controller/XrayImportExecutionResultsControllerTest.java @@ -0,0 +1,147 @@ +package ch.sbb.polarion.extension.cucumber.rest.controller; + +import ch.sbb.polarion.extension.cucumber.helper.PolarionTestRun; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.ExecutionInfo; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.ImportExecutionResponse; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.TestExecIssue; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.junit.TestCase; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.junit.TestSuite; +import ch.sbb.polarion.extension.generic.service.PolarionService; +import com.google.inject.Injector; +import com.polarion.alm.shared.api.transaction.RunnableInWriteTransaction; +import com.polarion.alm.shared.api.transaction.TransactionalExecutor; +import com.polarion.alm.shared.api.transaction.WriteTransaction; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.platform.guice.internal.GuicePlatform; +import lombok.SneakyThrows; +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.ws.rs.core.UriInfo; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.Callable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings({"unchecked", "rawtypes", "ResultOfMethodCallIgnored"}) +class XrayImportExecutionResultsControllerTest { + + @Test + @SneakyThrows + void testImportExecutionCucumberMultipart() { + try (MockedStatic mockedExecutor = mockStatic(TransactionalExecutor.class); + MockedStatic guicePlatform = mockStatic(GuicePlatform.class); + MockedStatic polarionTestRun = mockStatic(PolarionTestRun.class)) { + + PolarionService polarionService = mockCommonThings(mockedExecutor, guicePlatform, polarionTestRun); + + FormDataBodyPart infoBodyPart = mock(FormDataBodyPart.class); + when(infoBodyPart.getValueAs(any())).thenReturn(new ExecutionInfo()); + + FormDataBodyPart fileBodyPart = mock(FormDataBodyPart.class); + + TestSuite testSuite = new TestSuite(); + testSuite.setTestCases(Arrays.asList( + new TestCase(null, new ArrayList<>(), new ArrayList<>(), null, null, "tc1", 0, 7d, null, null), + new TestCase(null, new ArrayList<>(), new ArrayList<>(), null, null, "tc2", 0, 11d, null, null))); + when(fileBodyPart.getValueAs(any())).thenReturn(testSuite); + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getBaseUri()).thenReturn(URI.create("https://somedomain.com:4242")); + ImportExecutionResponse response = new XrayImportExecutionResultsController(polarionService).withUriInfo(uriInfo).importExecutionJunitMultipart(infoBodyPart, fileBodyPart); + assertThat(response).isNotNull(); + TestExecIssue issue = response.getTestExecIssue(); + assertThat(issue).isNotNull(); + assertEquals("testRunProjId/testRunId", issue.getId()); + assertEquals("testRunId", issue.getKey()); + assertEquals("https://somedomain.com:4242/polarion/#/project/testRunProjId/testrun?id=testRunId", issue.getSelf()); + } + } + + @Test + @SneakyThrows + void testImportExecutionJunit() { + try (MockedStatic mockedExecutor = mockStatic(TransactionalExecutor.class); + MockedStatic guicePlatform = mockStatic(GuicePlatform.class); + MockedStatic polarionTestRun = mockStatic(PolarionTestRun.class)) { + + PolarionService polarionService = mockCommonThings(mockedExecutor, guicePlatform, polarionTestRun); + + FormDataBodyPart fileBodyPart = mock(FormDataBodyPart.class); + + TestSuite testSuite = new TestSuite(); + testSuite.setTestCases(Arrays.asList( + new TestCase(null, new ArrayList<>(), new ArrayList<>(), null, null, "tc1", 0, 7d, null, null), + new TestCase(null, new ArrayList<>(), new ArrayList<>(), null, null, "tc2", 0, 11d, null, null))); + when(fileBodyPart.getValueAs(any())).thenReturn(testSuite); + + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getBaseUri()).thenReturn(URI.create("https://somedomain.com:4242")); + ImportExecutionResponse response = new XrayImportExecutionResultsController(polarionService).withUriInfo(uriInfo).importExecutionJunit( + "projKey", "testExecLKey", "testPlanKey", "envs", "rev", "fixVersion", fileBodyPart); + assertThat(response).isNotNull(); + + } + } + + @Test + @SneakyThrows + void testImportExecutionJunitMultipart() { + try (MockedStatic mockedExecutor = mockStatic(TransactionalExecutor.class); + MockedStatic guicePlatform = mockStatic(GuicePlatform.class); + MockedStatic polarionTestRun = mockStatic(PolarionTestRun.class)) { + + PolarionService polarionService = mockCommonThings(mockedExecutor, guicePlatform, polarionTestRun); + + FormDataBodyPart infoBodyPart = mock(FormDataBodyPart.class); + when(infoBodyPart.getValueAs(any())).thenReturn(new ExecutionInfo()); + + FormDataBodyPart fileBodyPart = mock(FormDataBodyPart.class); + + TestSuite testSuite = new TestSuite(); + testSuite.setTestCases(Arrays.asList( + new TestCase(null, new ArrayList<>(), new ArrayList<>(), null, null, "tc1", 0, 7d, null, null), + new TestCase(null, new ArrayList<>(), new ArrayList<>(), null, null, "tc2", 0, 11d, null, null))); + when(fileBodyPart.getValueAs(any())).thenReturn(testSuite); + + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getBaseUri()).thenReturn(URI.create("https://somedomain.com:4242")); + ImportExecutionResponse response = new XrayImportExecutionResultsController(polarionService).withUriInfo(uriInfo).importExecutionJunitMultipart( + infoBodyPart, fileBodyPart); + assertThat(response).isNotNull(); + + } + } + + private PolarionService mockCommonThings(MockedStatic mockedExecutor, MockedStatic guicePlatform, MockedStatic polarionTestRun) { + mockedExecutor.when(() -> TransactionalExecutor.executeInWriteTransaction(any(RunnableInWriteTransaction.class))) + .thenAnswer(invocation -> { + RunnableInWriteTransaction transaction = invocation.getArgument(0); + return transaction.run(mock(WriteTransaction.class)); + }); + + guicePlatform.when(GuicePlatform::getGlobalInjector).thenReturn(mock(Injector.class)); + + PolarionService polarionService = mock(PolarionService.class); + when(polarionService.callPrivileged(any(Callable.class))).thenAnswer(invocation -> { + Callable callable = invocation.getArgument(0); + return callable.call(); + }); + + ITestRun testRun = mock(ITestRun.class); + when(testRun.getProjectId()).thenReturn("testRunProjId"); + when(testRun.getId()).thenReturn("testRunId"); + polarionTestRun.when(() -> PolarionTestRun.createTestRuns(any(), any(), any(), any())).thenReturn(Collections.singletonList(testRun)); + + return polarionService; + } + +} diff --git a/src/test/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ExecutionRecordTest.java b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ExecutionRecordTest.java new file mode 100644 index 0000000..a2dd796 --- /dev/null +++ b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/model/execution/ExecutionRecordTest.java @@ -0,0 +1,99 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.execution; + +import ch.sbb.polarion.extension.cucumber.rest.model.execution.cucumber.CucumberExecutionResult; +import ch.sbb.polarion.extension.cucumber.rest.model.execution.junit.TestSuite; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.polarion.core.util.types.Text; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import java.io.InputStream; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static ch.sbb.polarion.extension.cucumber.helper.PolarionTestRun.FAILED_STATUS; +import static ch.sbb.polarion.extension.cucumber.helper.PolarionTestRun.PASSED_STATUS; +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class ExecutionRecordTest { + + @Test + void checkJunitRecordsCreation() { + List goodRecords = ExecutionRecord.fromJUnitReport(loadJunitSuite("good.xml")); + Assertions.assertThat(goodRecords).hasSize(3); + assertThat(goodRecords.get(2).getDuration()).isEqualTo(3.333f); + assertThat(goodRecords.stream().map(ExecutionRecord::getStatus)) + .containsOnly(PASSED_STATUS); + assertThat(goodRecords.stream().map(ExecutionRecord::getTestCaseTitle)) + .containsExactly("testCase1", "testCase2", "testCase3"); + + List someFailedRecords = ExecutionRecord.fromJUnitReport(loadJunitSuite("someFailed.xml")); + assertThat(someFailedRecords.stream() + .map(ExecutionRecord::getStatus) + .collect(Collectors.toList())) + .isEqualTo(List.of(PASSED_STATUS, FAILED_STATUS, FAILED_STATUS, FAILED_STATUS)); + } + + @Test + void checkCucumberRecordsCreation() { + List goodRecords = ExecutionRecord.fromCucumberExecutions(loadCucumberExecutions("good.json")); + Assertions.assertThat(goodRecords).hasSize(6); + assertThat(goodRecords.get(0).getDuration()).isEqualTo(0.012877f); + assertThat(goodRecords.stream().map(ExecutionRecord::getStatus)) + .containsOnly(PASSED_STATUS); + assertThat(goodRecords.stream().flatMap(r -> r.getTestCaseIds().stream())) + .containsExactly("EL-1", "EL-2", "EL-3", "EL-4", "EL-21", "EL-22"); + + List someFailedRecords = + ExecutionRecord.fromCucumberExecutions(loadCucumberExecutions("someFailed.json")); + assertThat(someFailedRecords.get(0).getTestCaseIds()).isEmpty(); + assertThat(someFailedRecords.get(1).getTestCaseIds()) + .containsExactly("EL-222", "EL-333"); + assertThat(Stream.of(someFailedRecords.get(0), someFailedRecords.get(1)) + .map(ExecutionRecord::getStatus)) + .containsOnly(PASSED_STATUS); + assertThat(Stream.of(someFailedRecords.get(2), someFailedRecords.get(3)) + .map(ExecutionRecord::getStatus)) + .containsOnly(FAILED_STATUS); + assertThat(Stream.of(someFailedRecords.get(2), someFailedRecords.get(3)) + .map(ExecutionRecord::getComment)) + .containsOnly(Text.plain("single failed message"), Text.plain("failed message" + System.lineSeparator() + "passed message")); + } + + @Test + void checkFilteringTagsDuringCucumberRecordsCreation() { + List taaRecords = ExecutionRecord.fromCucumberExecutions(loadCucumberExecutions("taa-small.json")); + Assertions.assertThat(taaRecords).hasSize(11); + for (ExecutionRecord taaRecord : taaRecords) { + List testCaseIds = taaRecord.getTestCaseIds(); + Assertions.assertThat(testCaseIds).hasSize(1); + Assertions.assertThat(testCaseIds.get(0)).startsWith("TIADREQ-"); + } + } + + private TestSuite loadJunitSuite(String fileName) { + try (InputStream is = this.getClass().getResourceAsStream("/import/junit/" + fileName)) { + JAXBContext jaxbContext = JAXBContext.newInstance(TestSuite.class); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + return (TestSuite) unmarshaller.unmarshal(is); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private List loadCucumberExecutions(String fileName) { + try (InputStream is = this.getClass().getResourceAsStream("/import/cucumber/" + fileName)) { + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readValue(is, + objectMapper.getTypeFactory().constructCollectionType(List.class, CucumberExecutionResult.class)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/StringListConverterTest.java b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/StringListConverterTest.java new file mode 100644 index 0000000..a6a1d59 --- /dev/null +++ b/src/test/java/ch/sbb/polarion/extension/cucumber/rest/model/fields/StringListConverterTest.java @@ -0,0 +1,44 @@ +package ch.sbb.polarion.extension.cucumber.rest.model.fields; + +import com.polarion.alm.tracker.IPlanningManager; +import com.polarion.alm.tracker.ITrackerService; +import com.polarion.alm.tracker.model.IPlan; +import com.polarion.alm.tracker.model.ITestRun; +import com.polarion.core.util.types.Text; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class StringListConverterTest { + + @Test + void testConvert() { + ITrackerService trackerService = mock(ITrackerService.class); + ITestRun testRun = mock(ITestRun.class); + Object result = new StringListConverter().convert(trackerService, testRun, null, List.of()); + assertEquals(Text.html(""), result); + + when(testRun.getProjectId()).thenReturn("projId"); + IPlanningManager planningManager = mock(IPlanningManager.class); + when(trackerService.getPlanningManager()).thenReturn(planningManager); + when(planningManager.searchPlans(anyString(), anyString(), anyInt())).thenReturn(List.of()); + result = new StringListConverter().convert(trackerService, testRun, null, List.of("some")); + assertEquals(Text.html("some "), result); + + IPlan plan = mock(IPlan.class); + when(plan.getId()).thenReturn("planId"); + when(plan.getName()).thenReturn("planName"); + when(planningManager.searchPlans(anyString(), anyString(), anyInt())).thenReturn(List.of(plan)); + result = new StringListConverter().convert(trackerService, testRun, null, List.of("some")); + assertEquals(Text.html("planName "), result); + } +} diff --git a/src/test/resources/import/cucumber/good.json b/src/test/resources/import/cucumber/good.json new file mode 100644 index 0000000..e250899 --- /dev/null +++ b/src/test/resources/import/cucumber/good.json @@ -0,0 +1,602 @@ +[ + { + "keyword": "Feature", + "name": "Arithmetic Operations", + "line": 3, + "description": "", + "tags": [ + { + "name": "@DEMO-48", + "line": 1 + }, + { + "name": "@REQ_DEMO-45", + "line": 2 + } + ], + "id": "arithmetic-operations", + "uri": "features/1_DEMO-45.feature", + "elements": [ + { + "comments": [ + { + "value": "#In order to avoid silly mistakes", + "line": 4 + }, + { + "value": "#As a math idiot ", + "line": 5 + }, + { + "value": "#I want to be told the result of basic arithmetic operations between two numbers", + "line": 6 + } + ], + "keyword": "Scenario Outline", + "name": "Add two Numbers", + "line": 18, + "description": "", + "tags": [ + { + "name": "@EL-1", + "line": 9 + } + ], + "id": "arithmetic-operations;add-two-numbers;;2", + "type": "scenario", + "steps": [ + { + "embeddings": [ + { + "mime_type": "text/plain", + "data": "{data base64}" + }, + { + "mime_type": "text/plain", + "data": "{data base64}" + } + ], + "keyword": "Given ", + "name": "I have entered 20 into the calculator", + "line": 11, + "match": { + "arguments": [ + { + "offset": 15, + "val": "20" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 487000 + } + }, + { + "keyword": "And ", + "name": "I have entered 30 into the calculator", + "line": 12, + "match": { + "arguments": [ + { + "offset": 15, + "val": "30" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 340000 + } + }, + { + "keyword": "When ", + "name": "I press add", + "line": 13, + "match": { + "arguments": [ + { + "offset": 8, + "val": "add" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 327000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 50 on the screen", + "line": 14, + "match": { + "arguments": [ + { + "offset": 21, + "val": "50" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 11723000 + } + } + ] + }, + { + "comments": [ + { + "value": "#In order to avoid silly mistakes", + "line": 4 + }, + { + "value": "#As a math idiot ", + "line": 5 + }, + { + "value": "#I want to be told the result of basic arithmetic operations between two numbers", + "line": 6 + } + ], + "keyword": "Scenario Outline", + "name": "Add two Numbers", + "line": 19, + "description": "", + "tags": [ + { + "name": "@EL-2", + "line": 9 + } + ], + "id": "arithmetic-operations;add-two-numbers;;3", + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "I have entered 2 into the calculator", + "line": 11, + "match": { + "arguments": [ + { + "offset": 15, + "val": "2" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 992000 + } + }, + { + "keyword": "And ", + "name": "I have entered 5 into the calculator", + "line": 12, + "match": { + "arguments": [ + { + "offset": 15, + "val": "5" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 775000 + } + }, + { + "keyword": "When ", + "name": "I press add", + "line": 13, + "match": { + "arguments": [ + { + "offset": 8, + "val": "add" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 322000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 7 on the screen", + "line": 14, + "match": { + "arguments": [ + { + "offset": 21, + "val": "7" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 423000 + } + } + ] + }, + { + "comments": [ + { + "value": "#In order to avoid silly mistakes", + "line": 4 + }, + { + "value": "#As a math idiot ", + "line": 5 + }, + { + "value": "#I want to be told the result of basic arithmetic operations between two numbers", + "line": 6 + } + ], + "keyword": "Scenario Outline", + "name": "Add two Numbers", + "line": 20, + "description": "", + "tags": [ + { + "name": "@EL-3", + "line": 9 + } + ], + "id": "arithmetic-operations;add-two-numbers;;4", + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "I have entered 0 into the calculator", + "line": 11, + "match": { + "arguments": [ + { + "offset": 15, + "val": "0" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 384000 + } + }, + { + "keyword": "And ", + "name": "I have entered 40 into the calculator", + "line": 12, + "match": { + "arguments": [ + { + "offset": 15, + "val": "40" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 313000 + } + }, + { + "keyword": "When ", + "name": "I press add", + "line": 13, + "match": { + "arguments": [ + { + "offset": 8, + "val": "add" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 280000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 40 on the screen", + "line": 14, + "match": { + "arguments": [ + { + "offset": 21, + "val": "40" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 350000 + } + } + ] + }, + { + "keyword": "Scenario Outline", + "name": "Divide Two Numbers", + "line": 32, + "description": "", + "tags": [ + { + "name": "@EL-4", + "line": 23 + } + ], + "id": "arithmetic-operations;divide-two-numbers;;2", + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "I have entered 8 into the calculator", + "line": 25, + "match": { + "arguments": [ + { + "offset": 15, + "val": "8" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 344000 + } + }, + { + "keyword": "And ", + "name": "I have entered 4 into the calculator", + "line": 26, + "match": { + "arguments": [ + { + "offset": 15, + "val": "4" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 292000 + } + }, + { + "keyword": "When ", + "name": "I press divide", + "line": 27, + "match": { + "arguments": [ + { + "offset": 8, + "val": "divide" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 291000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 2 on the screen", + "line": 28, + "match": { + "arguments": [ + { + "offset": 21, + "val": "2" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 320000 + } + } + ] + }, + { + "keyword": "Scenario Outline", + "name": "Divide Two Numbers", + "line": 33, + "description": "", + "tags": [ + { + "name": "@EL-21", + "line": 23 + } + ], + "id": "arithmetic-operations;divide-two-numbers;;3", + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "I have entered 12 into the calculator", + "line": 25, + "match": { + "arguments": [ + { + "offset": 15, + "val": "12" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 1102000 + } + }, + { + "keyword": "And ", + "name": "I have entered 3 into the calculator", + "line": 26, + "match": { + "arguments": [ + { + "offset": 15, + "val": "3" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 891000 + } + }, + { + "keyword": "When ", + "name": "I press divide", + "line": 27, + "match": { + "arguments": [ + { + "offset": 8, + "val": "divide" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 291000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 4 on the screen", + "line": 28, + "match": { + "arguments": [ + { + "offset": 21, + "val": "4" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 339000 + } + } + ] + }, + { + "keyword": "Scenario Outline", + "name": "Divide Two Numbers", + "line": 34, + "description": "", + "tags": [ + { + "name": "@EL-22", + "line": 23 + } + ], + "id": "arithmetic-operations;divide-two-numbers;;4", + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "I have entered 3 into the calculator", + "line": 25, + "match": { + "arguments": [ + { + "offset": 15, + "val": "3" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 304000 + } + }, + { + "keyword": "And ", + "name": "I have entered 1 into the calculator", + "line": 26, + "match": { + "arguments": [ + { + "offset": 15, + "val": "1" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 309000 + } + }, + { + "keyword": "When ", + "name": "I press divide", + "line": 27, + "match": { + "arguments": [ + { + "offset": 8, + "val": "divide" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 257000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 5 on the screen", + "line": 28, + "match": { + "arguments": [ + { + "offset": 21, + "val": "5" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 840000 + } + } + ] + } + ] + } +] diff --git a/src/test/resources/import/cucumber/someFailed.json b/src/test/resources/import/cucumber/someFailed.json new file mode 100644 index 0000000..bfd76e6 --- /dev/null +++ b/src/test/resources/import/cucumber/someFailed.json @@ -0,0 +1,682 @@ +[ + { + "keyword": "Feature", + "name": "Arithmetic Operations", + "line": 3, + "description": "", + "tags": [ + { + "name": "@DEMO-48", + "line": 1 + }, + { + "name": "@REQ_DEMO-45", + "line": 2 + } + ], + "id": "arithmetic-operations", + "uri": "features/1_DEMO-45.feature", + "elements": [ + { + "comments": [ + { + "value": "#In order to avoid silly mistakes", + "line": 4 + }, + { + "value": "#As a math idiot ", + "line": 5 + }, + { + "value": "#I want to be told the result of basic arithmetic operations between two numbers", + "line": 6 + } + ], + "keyword": "Scenario Outline", + "name": "Empty tag scenario", + "line": 18, + "description": "", + "tags": [ + ], + "id": "arithmetic-operations;add-two-numbers;;2", + "type": "scenario", + "steps": [ + { + "embeddings": [ + { + "mime_type": "text/plain", + "data": "{data base64}" + }, + { + "mime_type": "text/plain", + "data": "{data base64}" + } + ], + "keyword": "Given ", + "name": "I have entered 20 into the calculator", + "line": 11, + "match": { + "arguments": [ + { + "offset": 15, + "val": "20" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 487000 + } + }, + { + "keyword": "And ", + "name": "I have entered 30 into the calculator", + "line": 12, + "match": { + "arguments": [ + { + "offset": 15, + "val": "30" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 340000 + } + }, + { + "keyword": "When ", + "name": "I press add", + "line": 13, + "match": { + "arguments": [ + { + "offset": 8, + "val": "add" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 327000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 50 on the screen", + "line": 14, + "match": { + "arguments": [ + { + "offset": 21, + "val": "50" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 11723000 + } + } + ] + }, + { + "comments": [ + { + "value": "#In order to avoid silly mistakes", + "line": 4 + }, + { + "value": "#As a math idiot ", + "line": 5 + }, + { + "value": "#I want to be told the result of basic arithmetic operations between two numbers", + "line": 6 + } + ], + "keyword": "Scenario Outline", + "name": "Two tags scenario", + "line": 19, + "description": "", + "tags": [ + { + "name": "EL-222", + "line": 9 + }, + { + "name": "EL-333", + "line": 9 + } + ], + "id": "arithmetic-operations;add-two-numbers;;3", + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "I have entered 2 into the calculator", + "line": 11, + "match": { + "arguments": [ + { + "offset": 15, + "val": "2" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 992000 + } + }, + { + "keyword": "And ", + "name": "I have entered 5 into the calculator", + "line": 12, + "match": { + "arguments": [ + { + "offset": 15, + "val": "5" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 775000 + } + }, + { + "keyword": "When ", + "name": "I press add", + "line": 13, + "match": { + "arguments": [ + { + "offset": 8, + "val": "add" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 322000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 7 on the screen", + "line": 14, + "match": { + "arguments": [ + { + "offset": 21, + "val": "7" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 423000 + } + } + ] + }, + { + "line": 4, + "name": "Failed background", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 1022587000, + "status": "failed", + "error_message": "single failed message" + }, + "line": 6, + "name": "TAA Operating State Publisher is ready with Advanced topology", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": "", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 838000, + "status": "passed" + }, + "line": 7, + "name": "Configuration parameter application.topology.update-time-cron-expression is set to \"0 30 0 * * *\"", + "match": { + "arguments": [ + { + "val": "application.topology.update-time-cron-expression", + "offset": 24 + }, + { + "val": "\"0 30 0 * * *\"", + "offset": 83 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 386000, + "status": "passed" + }, + "line": 8, + "name": "Configuration parameter application.topology.retry.delay-in-ms is set to 1000", + "match": { + "arguments": [ + { + "val": "application.topology.retry.delay-in-ms", + "offset": 24 + }, + { + "val": "1000", + "offset": 73 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 85000, + "status": "passed" + }, + "line": 9, + "name": "Configuration parameter application.topology.timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.topology.timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 69 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 85000, + "status": "passed" + }, + "line": 10, + "name": "Configuration parameter application.topology.retry.max-retries is set to 3", + "match": { + "arguments": [ + { + "val": "application.topology.retry.max-retries", + "offset": 24 + }, + { + "val": "3", + "offset": 73 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + } + ] + }, + { + "comments": [ + { + "value": "#In order to avoid silly mistakes", + "line": 4 + }, + { + "value": "#As a math idiot ", + "line": 5 + }, + { + "value": "#I want to be told the result of basic arithmetic operations between two numbers", + "line": 6 + } + ], + "keyword": "Scenario Outline", + "name": "Scenario empty steps", + "line": 20, + "description": "", + "tags": [ + { + "name": "@EL-3", + "line": 9 + } + ], + "id": "arithmetic-operations;add-two-numbers;;4", + "type": "scenario", + "steps": [] + }, + { + "line": 4, + "name": "On more Failed background", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 1022587000, + "status": "failed", + "error_message": "failed message" + }, + "line": 6, + "name": "TAA Operating State Publisher is ready with Advanced topology", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": "", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 838000, + "status": "passed", + "error_message": "passed message" + }, + "line": 7, + "name": "Configuration parameter application.topology.update-time-cron-expression is set to \"0 30 0 * * *\"", + "match": { + "arguments": [ + { + "val": "application.topology.update-time-cron-expression", + "offset": 24 + }, + { + "val": "\"0 30 0 * * *\"", + "offset": 83 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 386000, + "status": "passed" + }, + "line": 8, + "name": "Configuration parameter application.topology.retry.delay-in-ms is set to 1000", + "match": { + "arguments": [ + { + "val": "application.topology.retry.delay-in-ms", + "offset": 24 + }, + { + "val": "1000", + "offset": 73 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 85000, + "status": "passed" + }, + "line": 9, + "name": "Configuration parameter application.topology.timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.topology.timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 69 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 85000, + "status": "passed" + }, + "line": 10, + "name": "Configuration parameter application.topology.retry.max-retries is set to 3", + "match": { + "arguments": [ + { + "val": "application.topology.retry.max-retries", + "offset": 24 + }, + { + "val": "3", + "offset": 73 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + } + ] + }, + { + "keyword": "Scenario Outline", + "name": "But passed scenario", + "line": 32, + "description": "", + "tags": [ + { + "name": "@EL-4", + "line": 23 + } + ], + "id": "arithmetic-operations;divide-two-numbers;;2", + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "I have entered 8 into the calculator", + "line": 25, + "match": { + "arguments": [ + { + "offset": 15, + "val": "8" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 344000 + } + }, + { + "keyword": "And ", + "name": "I have entered 4 into the calculator", + "line": 26, + "match": { + "arguments": [ + { + "offset": 15, + "val": "4" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 292000 + } + }, + { + "keyword": "When ", + "name": "I press divide", + "line": 27, + "match": { + "arguments": [ + { + "offset": 8, + "val": "divide" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 291000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 2 on the screen", + "line": 28, + "match": { + "arguments": [ + { + "offset": 21, + "val": "2" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 320000 + } + } + ] + }, + { + "keyword": "Scenario Outline", + "name": "Divide Two Numbers", + "line": 33, + "description": "", + "tags": [ + { + "name": "@EL-21", + "line": 23 + } + ], + "id": "arithmetic-operations;divide-two-numbers;;3", + "type": "scenario", + "steps": [ + { + "keyword": "Given ", + "name": "I have entered 12 into the calculator", + "line": 25, + "match": { + "arguments": [ + { + "offset": 15, + "val": "12" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 1102000 + } + }, + { + "keyword": "And ", + "name": "I have entered 3 into the calculator", + "line": 26, + "match": { + "arguments": [ + { + "offset": 15, + "val": "3" + } + ], + "location": "features/step_definitions/calculator_steps.rb:14" + }, + "result": { + "status": "passed", + "duration": 891000 + } + }, + { + "keyword": "When ", + "name": "I press divide", + "line": 27, + "match": { + "arguments": [ + { + "offset": 8, + "val": "divide" + } + ], + "location": "features/step_definitions/calculator_steps.rb:18" + }, + "result": { + "status": "passed", + "duration": 291000 + } + }, + { + "keyword": "Then ", + "name": "the result should be 4 on the screen", + "line": 28, + "match": { + "arguments": [ + { + "offset": 21, + "val": "4" + } + ], + "location": "features/step_definitions/calculator_steps.rb:22" + }, + "result": { + "status": "passed", + "duration": 339000 + } + } + ] + } + ] + } +] diff --git a/src/test/resources/import/cucumber/taa-small.json b/src/test/resources/import/cucumber/taa-small.json new file mode 100644 index 0000000..ecc9780 --- /dev/null +++ b/src/test/resources/import/cucumber/taa-small.json @@ -0,0 +1,5044 @@ +[ + { + "line": 2, + "elements": [ + { + "line": 4, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 206137000, + "status": "passed" + }, + "line": 6, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 230000, + "status": "passed" + }, + "line": 7, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 1905000, + "status": "passed" + }, + "line": 9, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 106839000, + "status": "passed" + }, + "line": 13, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 2245000, + "status": "passed" + }, + "line": 15, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 1175000, + "status": "passed" + }, + "line": 19, + "name": "a RestrictionDataUpdate message with the following attributes in GeneralTransmission", + "match": { + "arguments": [ + { + "val": " in GeneralTransmission", + "offset": 61 + }, + { + "val": "", + "offset": 84 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.RestrictionUpdateSimulationSteps.createPayloadWithAttributesAndSend(boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "activationState", + "ACTIVE" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 5021000, + "status": "passed" + }, + "line": 22, + "name": "a CapacityConflictUpdate message with the following attributes in GeneralTransmission is received", + "match": { + "arguments": [ + { + "val": "CapacityConflictUpdate", + "offset": 2 + }, + { + "val": " in GeneralTransmission", + "offset": 62 + }, + { + "val": " is received", + "offset": 85 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericTaOperatingStateUpdateSimulationSteps.simulateMessage(java.lang.String,boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 208280000, + "status": "passed" + }, + "line": 24, + "name": "a SectionEntryPrevention has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SectionEntryPrevention", + "offset": 2 + }, + { + "val": "", + "offset": 43 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "uraId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "resolved", + "false" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 208387000, + "status": "passed" + }, + "line": 27, + "name": "a ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 2 + }, + { + "val": "", + "offset": 50 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:04:22.077Z", + "line": 31, + "name": "Connection to a new PSS Gateway which has become active", + "description": "", + "id": "connection---subscription-to-a-new-pss-gateway;connection-to-a-new-pss-gateway-which-has-become-active", + "after": [ + { + "result": { + "duration": 50000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 703000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 428000, + "status": "passed" + }, + "line": 33, + "name": "no PssGatewayHeartbeat message is received periodically", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.noMessageReceivedPeriodically(java.lang.String)" + }, + "keyword": "When " + }, + { + "result": { + "duration": 154000, + "status": "passed" + }, + "line": 34, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-2222-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 106430000, + "status": "passed" + }, + "line": 38, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 970000, + "status": "passed" + }, + "line": 40, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 2440000, + "status": "passed" + }, + "line": 44, + "name": "an empty GeneralTransmission is received", + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GeneralTransmissionSimulationSteps.receiveEmptyTaGeneralTransmission()" + }, + "keyword": "And " + }, + { + "result": { + "duration": 203364000, + "status": "passed" + }, + "line": 45, + "name": "a SectionEntryPrevention has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SectionEntryPrevention", + "offset": 2 + }, + { + "val": "", + "offset": 43 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "uraId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "resolved", + "true" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 206989000, + "status": "passed" + }, + "line": 48, + "name": "a ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 2 + }, + { + "val": "", + "offset": 50 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "activationState", + "INACTIVE" + ] + } + ], + "keyword": "And " + } + ], + "tags": [ + { + "name": "@TIADREQ-529" + } + ] + } + ], + "name": "Connection - subscription to a new PSS Gateway", + "description": "", + "id": "connection---subscription-to-a-new-pss-gateway", + "keyword": "Feature", + "uri": "file:src/test/cucumber/connectionfailure/Connection/Conection-subscription-to-new-pss-gateway.feature", + "tags": [] + }, + { + "line": 3, + "elements": [ + { + "line": 5, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 407400000, + "status": "passed" + }, + "line": 7, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 421000, + "status": "passed" + }, + "line": 8, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 856000, + "status": "passed" + }, + "line": 10, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 105554000, + "status": "passed" + }, + "line": 14, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 697000, + "status": "passed" + }, + "line": 16, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 222000, + "status": "passed" + }, + "line": 20, + "name": "a RestrictionDataUpdate message with the following attributes in GeneralTransmission", + "match": { + "arguments": [ + { + "val": " in GeneralTransmission", + "offset": 61 + }, + { + "val": "", + "offset": 84 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.RestrictionUpdateSimulationSteps.createPayloadWithAttributesAndSend(boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "activationState", + "ACTIVE" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 1571000, + "status": "passed" + }, + "line": 23, + "name": "a CapacityConflictUpdate message with the following attributes in GeneralTransmission is received", + "match": { + "arguments": [ + { + "val": "CapacityConflictUpdate", + "offset": 2 + }, + { + "val": " in GeneralTransmission", + "offset": 62 + }, + { + "val": " is received", + "offset": 85 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericTaOperatingStateUpdateSimulationSteps.simulateMessage(java.lang.String,boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 203759000, + "status": "passed" + }, + "line": 25, + "name": "a SectionEntryPrevention has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SectionEntryPrevention", + "offset": 2 + }, + { + "val": "", + "offset": 43 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "uraId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "resolved", + "false" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 206345000, + "status": "passed" + }, + "line": 28, + "name": "a ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 2 + }, + { + "val": "", + "offset": 50 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:04:23.353Z", + "line": 32, + "name": "Publication disabled, if heartbeat timeout detected", + "description": "", + "id": "connection---heartbeat-timeout;publication-disabled--if-heartbeat-timeout-detected", + "after": [ + { + "result": { + "duration": 37000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 264000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 81000, + "status": "passed" + }, + "line": 34, + "name": "no PssGatewayHeartbeat message is received periodically", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.noMessageReceivedPeriodically(java.lang.String)" + }, + "keyword": "When " + }, + { + "result": { + "duration": 3005127000, + "status": "passed" + }, + "line": 35, + "name": "the timeout of 3000 ms has elapsed", + "match": { + "arguments": [ + { + "val": "3000", + "offset": 15 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.setTimeout(int)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 1408000, + "status": "passed" + }, + "line": 36, + "name": "a RestrictionDataUpdate message with the following attributes in GeneralTransmission is received", + "match": { + "arguments": [ + { + "val": " in GeneralTransmission", + "offset": 61 + }, + { + "val": " is received", + "offset": 84 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.RestrictionUpdateSimulationSteps.createPayloadWithAttributesAndSend(boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000021" + ] + }, + { + "cells": [ + "activationState", + "ACTIVE" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 205151000, + "status": "passed" + }, + "line": 39, + "name": "no ExecutionUsageRestrictionArea has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 3 + }, + { + "val": "", + "offset": 51 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 236000, + "status": "passed" + }, + "line": 41, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 107771000, + "status": "passed" + }, + "line": 45, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 1804000, + "status": "passed" + }, + "line": 47, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 203783000, + "status": "passed" + }, + "line": 52, + "name": "no ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 3 + }, + { + "val": "", + "offset": 51 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 1572000, + "status": "passed" + }, + "line": 55, + "name": "a RestrictionDataUpdate message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "", + "offset": 61 + }, + { + "val": " is received", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.RestrictionUpdateSimulationSteps.createPayloadWithAttributesAndSend(boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000021" + ] + }, + { + "cells": [ + "activationState", + "INACTIVE" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 205489000, + "status": "passed" + }, + "line": 58, + "name": "a ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 2 + }, + { + "val": "", + "offset": 50 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000021" + ] + }, + { + "cells": [ + "activationState", + "INACTIVE" + ] + } + ], + "keyword": "Then " + } + ], + "tags": [ + { + "name": "@TIADREQ-516" + } + ] + } + ], + "name": "Connection - heartbeat timeout", + "description": "", + "id": "connection---heartbeat-timeout", + "keyword": "Feature", + "uri": "file:src/test/cucumber/connectionfailure/Connection/Connection-heartbeat-timeout.feature", + "tags": [] + }, + { + "line": 5, + "elements": [ + { + "line": 7, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 409066000, + "status": "passed" + }, + "line": 9, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 639000, + "status": "passed" + }, + "line": 10, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 681000, + "status": "passed" + }, + "line": 12, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 103254000, + "status": "passed" + }, + "line": 16, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 1411000, + "status": "passed" + }, + "line": 18, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 461000, + "status": "passed" + }, + "line": 22, + "name": "a RestrictionDataUpdate message with the following attributes in GeneralTransmission", + "match": { + "arguments": [ + { + "val": " in GeneralTransmission", + "offset": 61 + }, + { + "val": "", + "offset": 84 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.RestrictionUpdateSimulationSteps.createPayloadWithAttributesAndSend(boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "activationState", + "ACTIVE" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 2439000, + "status": "passed" + }, + "line": 25, + "name": "a CapacityConflictUpdate message with the following attributes in GeneralTransmission is received", + "match": { + "arguments": [ + { + "val": "CapacityConflictUpdate", + "offset": 2 + }, + { + "val": " in GeneralTransmission", + "offset": 62 + }, + { + "val": " is received", + "offset": 85 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericTaOperatingStateUpdateSimulationSteps.simulateMessage(java.lang.String,boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 205767000, + "status": "passed" + }, + "line": 27, + "name": "a SectionEntryPrevention has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SectionEntryPrevention", + "offset": 2 + }, + { + "val": "", + "offset": 43 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "uraId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "resolved", + "false" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 200926000, + "status": "passed" + }, + "line": 30, + "name": "a ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 2 + }, + { + "val": "", + "offset": 50 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:04:28.024Z", + "line": 35, + "name": "Publication of operation mode disabled, if the TA connection is not established due a", + "description": " pss-gateway heartbeats timeout.", + "id": "connection---no-publication-operation-mode-heartbeat-timeout;publication-of-operation-mode-disabled--if-the-ta-connection-is-not-established-due-a", + "after": [ + { + "result": { + "duration": 37000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 199000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 60000, + "status": "passed" + }, + "line": 38, + "name": "no PssGatewayHeartbeat message is received periodically", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.noMessageReceivedPeriodically(java.lang.String)" + }, + "keyword": "When " + }, + { + "result": { + "duration": 3006509000, + "status": "passed" + }, + "line": 39, + "name": "the timeout of 3000 ms has elapsed", + "match": { + "arguments": [ + { + "val": "3000", + "offset": 15 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.setTimeout(int)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 2460000, + "status": "passed" + }, + "line": 40, + "name": "TaOperationMode INCIDENT is received", + "match": { + "arguments": [ + { + "val": "INCIDENT", + "offset": 16 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.OperationModeSimulationSteps.taOperationModeReceived(ch.sbb.tms.iad.taa.operatingstatepublisher.common.model.TaOperationMode)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 202507000, + "status": "passed" + }, + "line": 41, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "Then " + } + ], + "tags": [ + { + "name": "@ConnectionFailure" + }, + { + "name": "@TIADREQ-514" + } + ] + }, + { + "line": 7, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 409226000, + "status": "passed" + }, + "line": 9, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 258000, + "status": "passed" + }, + "line": 10, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 717000, + "status": "passed" + }, + "line": 12, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 104834000, + "status": "passed" + }, + "line": 16, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 887000, + "status": "passed" + }, + "line": 18, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 291000, + "status": "passed" + }, + "line": 22, + "name": "a RestrictionDataUpdate message with the following attributes in GeneralTransmission", + "match": { + "arguments": [ + { + "val": " in GeneralTransmission", + "offset": 61 + }, + { + "val": "", + "offset": 84 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.RestrictionUpdateSimulationSteps.createPayloadWithAttributesAndSend(boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "activationState", + "ACTIVE" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 1639000, + "status": "passed" + }, + "line": 25, + "name": "a CapacityConflictUpdate message with the following attributes in GeneralTransmission is received", + "match": { + "arguments": [ + { + "val": "CapacityConflictUpdate", + "offset": 2 + }, + { + "val": " in GeneralTransmission", + "offset": 62 + }, + { + "val": " is received", + "offset": 85 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericTaOperatingStateUpdateSimulationSteps.simulateMessage(java.lang.String,boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 202746000, + "status": "passed" + }, + "line": 27, + "name": "a SectionEntryPrevention has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SectionEntryPrevention", + "offset": 2 + }, + { + "val": "", + "offset": 43 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "uraId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "resolved", + "false" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 201147000, + "status": "passed" + }, + "line": 30, + "name": "a ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 2 + }, + { + "val": "", + "offset": 50 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:04:32.171Z", + "line": 45, + "name": "Publication of operation mode disabled, if the TA connect. is re-established after", + "description": " a pss-gw heartbeats timeout and a new TaOperationMode was received\n while SubscribeToTa was published", + "id": "connection---no-publication-operation-mode-heartbeat-timeout;publication-of-operation-mode-disabled--if-the-ta-connect.-is-re-established-after", + "after": [ + { + "result": { + "duration": 39000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 171000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 81000, + "status": "passed" + }, + "line": 49, + "name": "no PssGatewayHeartbeat message is received periodically", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.noMessageReceivedPeriodically(java.lang.String)" + }, + "keyword": "When " + }, + { + "result": { + "duration": 3003770000, + "status": "passed" + }, + "line": 50, + "name": "the timeout of 3000 ms has elapsed", + "match": { + "arguments": [ + { + "val": "3000", + "offset": 15 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.setTimeout(int)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 205099000, + "status": "passed" + }, + "line": 51, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 243000, + "status": "passed" + }, + "line": 53, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 106017000, + "status": "passed" + }, + "line": 57, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 738000, + "status": "passed" + }, + "line": 58, + "name": "TaOperationMode MAINTENANCE is received", + "match": { + "arguments": [ + { + "val": "MAINTENANCE", + "offset": 16 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.OperationModeSimulationSteps.taOperationModeReceived(ch.sbb.tms.iad.taa.operatingstatepublisher.common.model.TaOperationMode)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 201390000, + "status": "passed" + }, + "line": 59, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "Then " + } + ], + "tags": [ + { + "name": "@ConnectionFailure" + }, + { + "name": "@TIADREQ-512" + } + ] + }, + { + "line": 7, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 406837000, + "status": "passed" + }, + "line": 9, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 178000, + "status": "passed" + }, + "line": 10, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 313000, + "status": "passed" + }, + "line": 12, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 102057000, + "status": "passed" + }, + "line": 16, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 1230000, + "status": "passed" + }, + "line": 18, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 223000, + "status": "passed" + }, + "line": 22, + "name": "a RestrictionDataUpdate message with the following attributes in GeneralTransmission", + "match": { + "arguments": [ + { + "val": " in GeneralTransmission", + "offset": 61 + }, + { + "val": "", + "offset": 84 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.RestrictionUpdateSimulationSteps.createPayloadWithAttributesAndSend(boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "activationState", + "ACTIVE" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 1179000, + "status": "passed" + }, + "line": 25, + "name": "a CapacityConflictUpdate message with the following attributes in GeneralTransmission is received", + "match": { + "arguments": [ + { + "val": "CapacityConflictUpdate", + "offset": 2 + }, + { + "val": " in GeneralTransmission", + "offset": 62 + }, + { + "val": " is received", + "offset": 85 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericTaOperatingStateUpdateSimulationSteps.simulateMessage(java.lang.String,boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 206468000, + "status": "passed" + }, + "line": 27, + "name": "a SectionEntryPrevention has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SectionEntryPrevention", + "offset": 2 + }, + { + "val": "", + "offset": 43 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "uraId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "resolved", + "false" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 205952000, + "status": "passed" + }, + "line": 30, + "name": "a ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 2 + }, + { + "val": "", + "offset": 50 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:04:36.619Z", + "line": 63, + "name": "Publication of operation mode disabled, if TA connect. is re-established after", + "description": " a pss-gw heartbeats timeout and no new TaOperationMode was received.", + "id": "connection---no-publication-operation-mode-heartbeat-timeout;publication-of-operation-mode-disabled--if-ta-connect.-is-re-established-after", + "after": [ + { + "result": { + "duration": 45000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 213000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 106000, + "status": "passed" + }, + "line": 66, + "name": "no PssGatewayHeartbeat message is received periodically", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.noMessageReceivedPeriodically(java.lang.String)" + }, + "keyword": "When " + }, + { + "result": { + "duration": 3005778000, + "status": "passed" + }, + "line": 67, + "name": "the timeout of 3000 ms has elapsed", + "match": { + "arguments": [ + { + "val": "3000", + "offset": 15 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.setTimeout(int)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 202399000, + "status": "passed" + }, + "line": 68, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 646000, + "status": "passed" + }, + "line": 70, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 106237000, + "status": "passed" + }, + "line": 74, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 205194000, + "status": "passed" + }, + "line": 75, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 1140000, + "status": "passed" + }, + "line": 77, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 200241000, + "status": "passed" + }, + "line": 81, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "Then " + } + ], + "tags": [ + { + "name": "@ConnectionFailure" + }, + { + "name": "@TIADREQ-511" + } + ] + }, + { + "line": 7, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 413355000, + "status": "passed" + }, + "line": 9, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 276000, + "status": "passed" + }, + "line": 10, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 544000, + "status": "passed" + }, + "line": 12, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 105997000, + "status": "passed" + }, + "line": 16, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 1672000, + "status": "passed" + }, + "line": 18, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 435000, + "status": "passed" + }, + "line": 22, + "name": "a RestrictionDataUpdate message with the following attributes in GeneralTransmission", + "match": { + "arguments": [ + { + "val": " in GeneralTransmission", + "offset": 61 + }, + { + "val": "", + "offset": 84 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.RestrictionUpdateSimulationSteps.createPayloadWithAttributesAndSend(boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "activationState", + "ACTIVE" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 2686000, + "status": "passed" + }, + "line": 25, + "name": "a CapacityConflictUpdate message with the following attributes in GeneralTransmission is received", + "match": { + "arguments": [ + { + "val": "CapacityConflictUpdate", + "offset": 2 + }, + { + "val": " in GeneralTransmission", + "offset": 62 + }, + { + "val": " is received", + "offset": 85 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericTaOperatingStateUpdateSimulationSteps.simulateMessage(java.lang.String,boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 202248000, + "status": "passed" + }, + "line": 27, + "name": "a SectionEntryPrevention has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SectionEntryPrevention", + "offset": 2 + }, + { + "val": "", + "offset": 43 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "uraId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "resolved", + "false" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 204687000, + "status": "passed" + }, + "line": 30, + "name": "a ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 2 + }, + { + "val": "", + "offset": 50 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:04:41.278Z", + "line": 85, + "name": "Publication of operation mode enabled, if the TA connect. is re-established after", + "description": " a pss-gw heartbeats timeout and a new TaOperationMode was received\n while TAConnectionUp was published", + "id": "connection---no-publication-operation-mode-heartbeat-timeout;publication-of-operation-mode-enabled--if-the-ta-connect.-is-re-established-after", + "after": [ + { + "result": { + "duration": 32000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 176000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 105000, + "status": "passed" + }, + "line": 89, + "name": "no PssGatewayHeartbeat message is received periodically", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.noMessageReceivedPeriodically(java.lang.String)" + }, + "keyword": "When " + }, + { + "result": { + "duration": 3003182000, + "status": "passed" + }, + "line": 90, + "name": "the timeout of 3000 ms has elapsed", + "match": { + "arguments": [ + { + "val": "3000", + "offset": 15 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.setTimeout(int)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 1058000, + "status": "passed" + }, + "line": 91, + "name": "TaOperationMode INCIDENT is received", + "match": { + "arguments": [ + { + "val": "INCIDENT", + "offset": 16 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.OperationModeSimulationSteps.taOperationModeReceived(ch.sbb.tms.iad.taa.operatingstatepublisher.common.model.TaOperationMode)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 203964000, + "status": "passed" + }, + "line": 92, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 198000, + "status": "passed" + }, + "line": 94, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 106035000, + "status": "passed" + }, + "line": 98, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 947000, + "status": "passed" + }, + "line": 99, + "name": "TaOperationMode MAINTENANCE is received", + "match": { + "arguments": [ + { + "val": "MAINTENANCE", + "offset": 16 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.OperationModeSimulationSteps.taOperationModeReceived(ch.sbb.tms.iad.taa.operatingstatepublisher.common.model.TaOperationMode)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 201014000, + "status": "passed" + }, + "line": 100, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 2199000, + "status": "passed" + }, + "line": 102, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 325000, + "status": "passed" + }, + "line": 106, + "name": "TaOperationMode NORMAL is received", + "match": { + "arguments": [ + { + "val": "NORMAL", + "offset": 16 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.OperationModeSimulationSteps.taOperationModeReceived(ch.sbb.tms.iad.taa.operatingstatepublisher.common.model.TaOperationMode)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 205115000, + "status": "passed" + }, + "line": 107, + "name": "a OperationMode has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 2 + }, + { + "val": "", + "offset": 34 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "operationMode", + "NORMAL" + ] + } + ], + "keyword": "Then " + } + ], + "tags": [ + { + "name": "@ConnectionFailure" + }, + { + "name": "@TIADREQ-510" + } + ] + }, + { + "line": 7, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 410831000, + "status": "passed" + }, + "line": 9, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 409000, + "status": "passed" + }, + "line": 10, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 716000, + "status": "passed" + }, + "line": 12, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 106219000, + "status": "passed" + }, + "line": 16, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 1655000, + "status": "passed" + }, + "line": 18, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 1165000, + "status": "passed" + }, + "line": 22, + "name": "a RestrictionDataUpdate message with the following attributes in GeneralTransmission", + "match": { + "arguments": [ + { + "val": " in GeneralTransmission", + "offset": 61 + }, + { + "val": "", + "offset": 84 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.RestrictionUpdateSimulationSteps.createPayloadWithAttributesAndSend(boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "activationState", + "ACTIVE" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 2160000, + "status": "passed" + }, + "line": 25, + "name": "a CapacityConflictUpdate message with the following attributes in GeneralTransmission is received", + "match": { + "arguments": [ + { + "val": "CapacityConflictUpdate", + "offset": 2 + }, + { + "val": " in GeneralTransmission", + "offset": 62 + }, + { + "val": " is received", + "offset": 85 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericTaOperatingStateUpdateSimulationSteps.simulateMessage(java.lang.String,boolean,boolean,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "restrictionId", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 205640000, + "status": "passed" + }, + "line": 27, + "name": "a SectionEntryPrevention has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SectionEntryPrevention", + "offset": 2 + }, + { + "val": "", + "offset": 43 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "uraId", + "10210000-0000-0000-0000-000000000001" + ] + }, + { + "cells": [ + "resolved", + "false" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 205327000, + "status": "passed" + }, + "line": 30, + "name": "a ExecutionUsageRestrictionArea has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "ExecutionUsageRestrictionArea", + "offset": 2 + }, + { + "val": "", + "offset": 50 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "id", + "10210000-0000-0000-0000-000000000001" + ] + } + ], + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:04:45.943Z", + "line": 112, + "name": "Publication of operation mode enabled, if the TA connect. is re-established after", + "description": " a pss-gw heartbeats timeout and a new TaOperationMode was received\n while SubscribeToTa was published", + "id": "connection---no-publication-operation-mode-heartbeat-timeout;publication-of-operation-mode-enabled--if-the-ta-connect.-is-re-established-after", + "after": [ + { + "result": { + "duration": 35000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 162000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 65000, + "status": "passed" + }, + "line": 116, + "name": "no PssGatewayHeartbeat message is received periodically", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.noMessageReceivedPeriodically(java.lang.String)" + }, + "keyword": "When " + }, + { + "result": { + "duration": 3000908000, + "status": "passed" + }, + "line": 117, + "name": "the timeout of 3000 ms has elapsed", + "match": { + "arguments": [ + { + "val": "3000", + "offset": 15 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.setTimeout(int)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 1289000, + "status": "passed" + }, + "line": 118, + "name": "TaOperationMode INCIDENT is received", + "match": { + "arguments": [ + { + "val": "INCIDENT", + "offset": 16 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.OperationModeSimulationSteps.taOperationModeReceived(ch.sbb.tms.iad.taa.operatingstatepublisher.common.model.TaOperationMode)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 205127000, + "status": "passed" + }, + "line": 119, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 301000, + "status": "passed" + }, + "line": 121, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 2000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "2000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 105884000, + "status": "passed" + }, + "line": 125, + "name": "a SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 469000, + "status": "passed" + }, + "line": 126, + "name": "TaOperationMode MAINTENANCE is received", + "match": { + "arguments": [ + { + "val": "MAINTENANCE", + "offset": 16 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.OperationModeSimulationSteps.taOperationModeReceived(ch.sbb.tms.iad.taa.operatingstatepublisher.common.model.TaOperationMode)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 204970000, + "status": "passed" + }, + "line": 127, + "name": "no OperationMode has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 3 + }, + { + "val": "", + "offset": 35 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String,int)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 1997000, + "status": "passed" + }, + "line": 129, + "name": "a TaConnectionUp message with the following attributes is received", + "match": { + "arguments": [ + { + "val": "TaConnectionUp", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdateSimulationSteps.simulateMessage(java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "rctProtocolVersion", + "V200" + ] + }, + { + "cells": [ + "systemProtocolVersion", + "V200" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 201705000, + "status": "passed" + }, + "line": 133, + "name": "a OperationMode has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "OperationMode", + "offset": 2 + }, + { + "val": "", + "offset": 34 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericOperatingStateAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "operationMode", + "MAINTENANCE" + ] + } + ], + "keyword": "Then " + } + ], + "tags": [ + { + "name": "@ConnectionFailure" + }, + { + "name": "@TIADREQ-509" + } + ] + } + ], + "name": "Connection - no publication operation mode heartbeat timeout", + "description": "", + "id": "connection---no-publication-operation-mode-heartbeat-timeout", + "keyword": "Feature", + "uri": "file:src/test/cucumber/connectionfailure/Connection/Connection-no-publication-operating-mode-heartbeat-timeout.feature", + "tags": [ + { + "name": "@ConnectionFailure", + "type": "Tag", + "location": { + "line": 4, + "column": 1 + } + } + ] + }, + { + "line": 3, + "elements": [ + { + "line": 5, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 411233000, + "status": "passed" + }, + "line": 7, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 224000, + "status": "passed" + }, + "line": 8, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 44000, + "status": "passed" + }, + "line": 9, + "name": "Configuration parameter application.tainterfaceprocessor.tasubscription.heartbeat-interval-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.tasubscription.heartbeat-interval-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 107 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:04:50.609Z", + "line": 12, + "name": "Re-subscription after the PSS Gateway was down", + "description": "", + "id": "connection---re-subscription-to-pss-gateway;re-subscription-after-the-pss-gateway-was-down", + "after": [ + { + "result": { + "duration": 173000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 726000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 1050000, + "status": "passed" + }, + "line": 14, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 1000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "1000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 111019000, + "status": "passed" + }, + "line": 19, + "name": "a SubscribeToTa has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "subscriptionType", + "CONSUMER" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 106531000, + "status": "passed" + }, + "line": 21, + "name": "a TaSubscriptionHeartbeat has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "TaSubscriptionHeartbeat", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 1795153000, + "status": "passed" + }, + "line": 22, + "name": "a TaSubscriptionHeartbeat has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "TaSubscriptionHeartbeat", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 3008615000, + "status": "passed" + }, + "line": 23, + "name": "no SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 107000, + "status": "passed" + }, + "line": 25, + "name": "no PssGatewayHeartbeat message is received periodically", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.noMessageReceivedPeriodically(java.lang.String)" + }, + "keyword": "When " + }, + { + "result": { + "duration": 3004458000, + "status": "passed" + }, + "line": 26, + "name": "the timeout of 3000 ms has elapsed", + "match": { + "arguments": [ + { + "val": "3000", + "offset": 15 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.setTimeout(int)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 461000, + "status": "passed" + }, + "line": 27, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 1000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "1000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "And " + }, + { + "result": { + "duration": 106209000, + "status": "passed" + }, + "line": 32, + "name": "a SubscribeToTa has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "subscriptionType", + "CONSUMER" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 105625000, + "status": "passed" + }, + "line": 34, + "name": "a TaSubscriptionHeartbeat has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "TaSubscriptionHeartbeat", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 1798012000, + "status": "passed" + }, + "line": 35, + "name": "a TaSubscriptionHeartbeat has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "TaSubscriptionHeartbeat", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 3005683000, + "status": "passed" + }, + "line": 36, + "name": "no SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + } + ], + "tags": [ + { + "name": "@ConnectionFailure" + }, + { + "name": "@TIADREQ-606" + } + ] + } + ], + "name": "Connection - re-subscription to PSS Gateway", + "description": "", + "id": "connection---re-subscription-to-pss-gateway", + "keyword": "Feature", + "uri": "file:src/test/cucumber/connectionfailure/Connection/Connection-re-subscription-to-pss-gateway.feature", + "tags": [ + { + "name": "@ConnectionFailure", + "type": "Tag", + "location": { + "line": 2, + "column": 1 + } + } + ] + }, + { + "line": 3, + "elements": [ + { + "line": 5, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 410193000, + "status": "passed" + }, + "line": 7, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 1203000, + "status": "passed" + }, + "line": 8, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 152000, + "status": "passed" + }, + "line": 9, + "name": "Configuration parameter application.tainterfaceprocessor.tasubscription.heartbeat-interval-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.tasubscription.heartbeat-interval-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 107 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:05:04.093Z", + "line": 12, + "name": "No subscription, if no heartbeat has been received", + "description": "", + "id": "connection---subscription-to-pss-gateway;no-subscription--if-no-heartbeat-has-been-received", + "after": [ + { + "result": { + "duration": 41000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 227000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 880000, + "status": "passed" + }, + "line": 14, + "name": "no PssGatewayHeartbeat message is received periodically", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.noMessageReceivedPeriodically(java.lang.String)" + }, + "keyword": "When " + }, + { + "result": { + "duration": 3006475000, + "status": "passed" + }, + "line": 15, + "name": "no SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "Then " + }, + { + "result": { + "duration": 3004397000, + "status": "passed" + }, + "line": 16, + "name": "no TaSubscriptionHeartbeat has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "TaSubscriptionHeartbeat", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + } + ], + "tags": [ + { + "name": "@ConnectionFailure" + }, + { + "name": "@TIADREQ-608" + } + ] + }, + { + "line": 5, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 403577000, + "status": "passed" + }, + "line": 7, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 162000, + "status": "passed" + }, + "line": 8, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 28000, + "status": "passed" + }, + "line": 9, + "name": "Configuration parameter application.tainterfaceprocessor.tasubscription.heartbeat-interval-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.tasubscription.heartbeat-interval-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 107 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:05:10.528Z", + "line": 19, + "name": "Subscription, when first heartbeat is received", + "description": "", + "id": "connection---subscription-to-pss-gateway;subscription--when-first-heartbeat-is-received", + "after": [ + { + "result": { + "duration": 102000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 313000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 492000, + "status": "passed" + }, + "line": 21, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 1000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "1000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 109217000, + "status": "passed" + }, + "line": 26, + "name": "a SubscribeToTa has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "subscriptionType", + "CONSUMER" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 105484000, + "status": "passed" + }, + "line": 28, + "name": "a TaSubscriptionHeartbeat has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "TaSubscriptionHeartbeat", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 1788328000, + "status": "passed" + }, + "line": 29, + "name": "a TaSubscriptionHeartbeat has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "TaSubscriptionHeartbeat", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 3006061000, + "status": "passed" + }, + "line": 30, + "name": "no SubscribeToTa has been published", + "match": { + "arguments": [ + { + "val": "no ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 3 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + } + ], + "tags": [ + { + "name": "@ConnectionFailure" + }, + { + "name": "@TIADREQ-607" + } + ] + }, + { + "line": 5, + "name": "", + "description": "", + "type": "background", + "keyword": "Background", + "steps": [ + { + "result": { + "duration": 410710000, + "status": "passed" + }, + "line": 7, + "name": "TAA Operating State Publisher is ready with Advanced topology and not connected", + "match": { + "arguments": [ + { + "val": "Advanced", + "offset": 44 + }, + { + "val": " and not connected", + "offset": 61 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.taaOperatingStatePublisherIsReady(ch.sbb.tms.iad.taa.operatingstatepublisher.testutils.topology.TestTopology,boolean)" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 179000, + "status": "passed" + }, + "line": 8, + "name": "Configuration parameter application.tainterfaceprocessor.heartbeat-timeout-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.heartbeat-timeout-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 91 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + }, + { + "result": { + "duration": 41000, + "status": "passed" + }, + "line": 9, + "name": "Configuration parameter application.tainterfaceprocessor.tasubscription.heartbeat-interval-in-ms is set to 2000", + "match": { + "arguments": [ + { + "val": "application.tainterfaceprocessor.tasubscription.heartbeat-interval-in-ms", + "offset": 24 + }, + { + "val": "2000", + "offset": 107 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.configurationParameterIsSetTo(java.lang.String,java.lang.String)" + }, + "keyword": "And " + } + ] + }, + { + "start_timestamp": "2023-03-08T13:05:15.954Z", + "line": 33, + "name": "Subscription, when first heartbeat is received, even if it is passive", + "description": "", + "id": "connection---subscription-to-pss-gateway;subscription--when-first-heartbeat-is-received--even-if-it-is-passive", + "after": [ + { + "result": { + "duration": 35000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.stopSendingOfPeriodicMessages()" + } + }, + { + "result": { + "duration": 198000, + "status": "passed" + }, + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.SetupSteps.resetApplicationAfterEachTest()" + } + } + ], + "type": "scenario", + "keyword": "Scenario", + "steps": [ + { + "result": { + "duration": 5609000, + "status": "passed" + }, + "line": 35, + "name": "leadership is lost", + "match": { + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.LeadershipSimulationSteps.leadershipIsLost()" + }, + "keyword": "Given " + }, + { + "result": { + "duration": 295000, + "status": "passed" + }, + "line": 37, + "name": "a PssGatewayHeartbeat message with the following attributes is received periodically in every 1000 ms", + "match": { + "arguments": [ + { + "val": "PssGatewayHeartbeat", + "offset": 2 + }, + { + "val": "1000", + "offset": 94 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.simulationsteps.GenericUpdatePeriodicallySimulationSteps.messageReceivedPeriodically(java.lang.String,int,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "taInstance", + "GBT" + ] + }, + { + "cells": [ + "gatewayInstanceId", + "00000000-0000-0000-0000-000000000005" + ] + }, + { + "cells": [ + "time", + "DATE_TIME_NOW" + ] + } + ], + "keyword": "When " + }, + { + "result": { + "duration": 105622000, + "status": "passed" + }, + "line": 42, + "name": "a SubscribeToTa has been published with", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "SubscribeToTa", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatAMessageHasBeenPublishedWith(boolean,java.lang.String,ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.common.DataTable)" + }, + "rows": [ + { + "cells": [ + "subscriptionType", + "CONSUMER" + ] + } + ], + "keyword": "Then " + }, + { + "result": { + "duration": 105855000, + "status": "passed" + }, + "line": 44, + "name": "a TaSubscriptionHeartbeat has been published", + "match": { + "arguments": [ + { + "val": "a ", + "offset": 0 + }, + { + "val": "TaSubscriptionHeartbeat", + "offset": 2 + } + ], + "location": "ch.sbb.tms.iad.taa.operatingstatepublisher.cucumber.gluecode.assertionsteps.GenericMessageAssertionSteps.assertThatNoMessageHasBeenPublished(boolean,java.lang.String)" + }, + "keyword": "And " + } + ], + "tags": [ + { + "name": "@ConnectionFailure" + }, + { + "name": "@TIADREQ-534" + } + ] + } + ], + "name": "Connection - Subscription to PSS Gateway", + "description": "", + "id": "connection---subscription-to-pss-gateway", + "keyword": "Feature", + "uri": "file:src/test/cucumber/connectionfailure/Connection/Connection-subscription-to-pss-gateway.feature", + "tags": [ + { + "name": "@ConnectionFailure", + "type": "Tag", + "location": { + "line": 2, + "column": 1 + } + } + ] + } +] \ No newline at end of file diff --git a/src/test/resources/import/junit/good.xml b/src/test/resources/import/junit/good.xml new file mode 100644 index 0000000..e46a60f --- /dev/null +++ b/src/test/resources/import/junit/good.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/import/junit/someFailed.xml b/src/test/resources/import/junit/someFailed.xml new file mode 100644 index 0000000..4af30e5 --- /dev/null +++ b/src/test/resources/import/junit/someFailed.xml @@ -0,0 +1,18 @@ + + + + + + + + failure details + + + error details + + + + + \ No newline at end of file