├── .gitignore ├── README.md ├── pom.xml ├── wercker.yml ├── zephyr-sync-cli ├── assembly.xml ├── pom.xml └── src │ ├── main │ └── java │ │ └── lv │ │ └── ctco │ │ └── zephyr │ │ └── Runner.java │ └── test │ └── java │ └── lv │ └── ctco │ └── zephyr │ └── CliConfigLoaderTest.java ├── zephyr-sync-core ├── pom.xml └── src │ ├── main │ └── java │ │ └── lv │ │ └── ctco │ │ └── zephyr │ │ ├── AutodetectReportType.java │ │ ├── Config.java │ │ ├── ZephyrSyncService.java │ │ ├── beans │ │ ├── Metafield.java │ │ ├── jira │ │ │ ├── ErrorResponse.java │ │ │ ├── Fields.java │ │ │ ├── Issue.java │ │ │ ├── IssueLink.java │ │ │ ├── IssueLinkDirection.java │ │ │ ├── Login.java │ │ │ ├── Project.java │ │ │ ├── SearchResponse.java │ │ │ └── SessionResponse.java │ │ └── zapi │ │ │ ├── Cycle.java │ │ │ ├── CycleList.java │ │ │ ├── Execution.java │ │ │ ├── ExecutionRequest.java │ │ │ ├── ExecutionResponse.java │ │ │ ├── NewCycle.java │ │ │ └── ZapiTestStep.java │ │ ├── enums │ │ ├── ConfigProperty.java │ │ └── IssueType.java │ │ ├── service │ │ ├── AuthService.java │ │ ├── JiraService.java │ │ ├── MetaInfo.java │ │ ├── MetaInfoRetrievalService.java │ │ ├── TestCaseResolutionService.java │ │ └── ZephyrService.java │ │ ├── transformer │ │ ├── ReportTransformerFactory.java │ │ └── TestCaseToIssueTransformer.java │ │ └── util │ │ ├── ConfigBasedJsonProperty.java │ │ ├── CustomPropertyNamingStrategy.java │ │ └── HttpUtils.java │ └── test │ └── java │ └── lv │ └── ctco │ └── zephyr │ ├── ConfigTest.java │ ├── TestConfigLoader.java │ ├── beans │ └── jira │ │ ├── FieldsTest.java │ │ └── IssueLinkTest.java │ └── transformer │ └── ReportTransformerFactoryTest.java ├── zephyr-sync-maven-plugin ├── pom.xml └── src │ └── main │ └── java │ └── lv │ └── ctco │ └── zephyr │ └── mojo │ └── ZephyrSyncMojo.java ├── zephyr-sync-report-allure ├── pom.xml └── src │ ├── main │ ├── java │ │ └── lv │ │ │ └── ctco │ │ │ └── zephyr │ │ │ └── transformer │ │ │ └── AllureTransformer.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── lv.ctco.zephyr.transformer.ReportTransformer │ └── test │ ├── java │ └── lv │ │ └── ctco │ │ └── zephyr │ │ └── transformer │ │ └── AllureTransformerTest.java │ └── resources │ └── reports │ ├── 0a530bb5-c194-4b2d-b8d1-bf73da9480aa-attachment.html │ ├── 1b3bb409-55c6-40d8-a85f-5faf6f7ef1fa-container.json │ └── 2a83b634-b0a2-4fa4-af22-8316347b5cb6-result.json ├── zephyr-sync-report-api ├── pom.xml └── src │ └── main │ └── java │ └── lv │ └── ctco │ └── zephyr │ ├── beans │ ├── TestCase.java │ └── TestStep.java │ ├── enums │ ├── TestLevel.java │ └── TestStatus.java │ └── transformer │ └── ReportTransformer.java ├── zephyr-sync-report-cucumber ├── pom.xml └── src │ ├── main │ ├── java │ │ └── lv │ │ │ └── ctco │ │ │ └── zephyr │ │ │ ├── beans │ │ │ └── testresult │ │ │ │ └── cucumber │ │ │ │ ├── After.java │ │ │ │ ├── Before.java │ │ │ │ ├── Feature.java │ │ │ │ ├── Match.java │ │ │ │ ├── Result.java │ │ │ │ ├── Scenario.java │ │ │ │ ├── Step.java │ │ │ │ └── Tag.java │ │ │ └── transformer │ │ │ └── CucumberTransformer.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── lv.ctco.zephyr.transformer.ReportTransformer │ └── test │ └── java │ └── lv │ └── ctco │ └── zephyr │ └── transformer │ └── CucumberTransformerTest.java ├── zephyr-sync-report-junit ├── pom.xml └── src │ └── main │ ├── java │ └── lv │ │ └── ctco │ │ └── zephyr │ │ ├── beans │ │ └── testresult │ │ │ └── junit │ │ │ ├── JUnitResult.java │ │ │ ├── JUnitResultTestSuite.java │ │ │ └── JUnitTCProperties.java │ │ └── transformer │ │ └── JUnitTransformer.java │ └── resources │ └── META-INF │ └── services │ └── lv.ctco.zephyr.transformer.ReportTransformer ├── zephyr-sync-report-nunit ├── pom.xml └── src │ └── main │ ├── java │ └── lv │ │ └── ctco │ │ └── zephyr │ │ ├── beans │ │ └── testresult │ │ │ └── nunit │ │ │ ├── NUnitResultTestRun.java │ │ │ ├── NUnitTestCase.java │ │ │ └── NUnitTestSuite.java │ │ └── transformer │ │ └── NUnitTransformer.java │ └── resources │ └── META-INF │ └── services │ └── lv.ctco.zephyr.transformer.ReportTransformer └── zephyr-sync-util ├── pom.xml └── src └── main └── java └── lv └── ctco └── zephyr ├── ZephyrSyncException.java └── util ├── ObjectTransformer.java └── Utils.java /.gitignore: -------------------------------------------------------------------------------- 1 | #IDE specific files 2 | .idea 3 | **/*.iml 4 | .project 5 | .classpath 6 | .settings 7 | 8 | #Java output 9 | **/target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![wercker status](https://app.wercker.com/status/5f663d10cb826a4ebbbfaa7d2f6f1420/s/master "wercker status")](https://app.wercker.com/project/byKey/5f663d10cb826a4ebbbfaa7d2f6f1420) 2 | 3 | # Zephyr Sync 4 | 5 | ## Overview 6 | 7 | Zephyr Sync is a tool, that allows your project to perform synchronization of automated test results to Zephyr - a JIRA addon for Test Management. The advanced configuration of the tool supports multiple report types to work with, as well as some restrictions to be applied during the sync. 8 | 9 | ## Usage example 10 | 11 | ### Maven 12 | 13 | All changes should be done inside `pom.xml`. 14 | 15 | #### Using zephyr-sync-maven-plugin (recommended) 16 | 17 | The configuration is very simple and should be done in `pom.xml` 18 | (note that this example is given for `cucumber`, other reports like `allure` are configured in the similar way): 19 | 20 | ``` 21 | 22 | lv.ctco.zephyr 23 | zephyr-sync-maven-plugin 24 | ${zephyr-sync.version} 25 | 26 | 27 | lv.ctco.zephyr 28 | zephyr-sync-report-cucumber 29 | ${zephyr-sync.version} 30 | 31 | 32 | 33 | TECXYZ01 34 | ${env.TECXYZ01_PWD} 35 | cucumber 36 | XYZ 37 | http://jira.yourcompany.com/rest/ 38 | 1.0 39 | ${project.build.directory}/cucumber-report/report.json 40 | 41 | 42 | ``` 43 | 44 | #### Using maven-exec-plugin (deprecated) 45 | First of all - declare dependency to `zephyr-sync-core`: 46 | 47 | ``` 48 | 49 | ... 50 | 51 | lv.ctco.zephyr 52 | zephyr-sync-core 53 | ${zephyr-sync.version} 54 | 55 | 56 | ``` 57 | 58 | Also configure a Maven plugin that will trigger synchronization to JIRA: 59 | 60 | ``` 61 | 62 | org.codehaus.mojo 63 | exec-maven-plugin 64 | 1.3.1 65 | 66 | 67 | default-cli 68 | 69 | java 70 | 71 | 72 | lv.ctco.zephyr.Runner 73 | 74 | --username=TECXYZ01 75 | --password=${env.TECXYZ01_PWD} 76 | --reportType=cucumber 77 | --projectKey=XYZ 78 | --releaseVersion=1.0 79 | --jiraUrl=http://jira.yourcompany.com/rest/ 80 | --reportPath=${project.build.directory}/cucumber-report/report.json 81 | 82 | 83 | 84 | 85 | 86 | 87 | ``` 88 | 89 | This example shows only minimal set of mandatory attributes. 90 | For complete list of attributes refer to sections below. 91 | 92 | ### Command Line Interface 93 | 94 | ``` 95 | java -jar zephyr-sync-cli-${zephyr-sync.version}-all-in-one.jar --username=SPCABC --password=123456 --reportType=cucumber --projectKey=ABC --releaseVersion="Release 2.1" --jiraUrl=http://jira.yourcompany.com/rest/ --reportPath=build/cucumber-report/report.json 96 | ``` 97 | 98 | ### Using Gradle (using CLI) 99 | ``` 100 | task zephyrSync { 101 | javaexec { 102 | main = "lv.ctco.zephyr.Runner" 103 | classpath = sourceSets.main.output + sourceSets.test.output 104 | args = ["--username=SPCABC", "--password=123456", "--reportType=cucumber", "--projectKey=ABC", 105 | "--releaseVersion=Release 2.1", "--jiraUrl=http://jira.yourcompany.com/rest/", "--reportPath=build/cucumber-report/report.json"] 106 | } 107 | } 108 | ``` 109 | 110 | ## Configuration properties 111 | 112 | This is the list of possible configuration items: 113 | 114 | Property | Meaning | Is mandatory? | Default value | Example 115 | --- | --- | --- | --- | --- 116 | username | User name used to connect to JIRA | yes | | `TECXYZ01` 117 | password | Password for the user to connect to JIRA | yes | | `password` 118 | reportType | Type of report that will be synchronized to Zephyr | yes | | One of `cucumber`, `allure`, `junit` or `nunit` 119 | projectKey | Key of project in JIRA | yes | | `XYZ` 120 | releaseVersion | FixVersion of a project to link Test results to | yes | | `1.0` 121 | testCycle | Zephyr test cycle where the results will be linked to | yes | | 122 | jiraUrl | URL of JIRA (it's RESTful API endpoint) | yes | | `http://jira.yourcompany.com/jira/rest/ 123 | reportPath | Path on the file system where reports are stored | yes | | For cucumber: `${project.build.directory}/cucumber-report/report.json` 124 | orderedSteps | If set to true, numerical prefix for test steps will be put (hierarchical) | no | `false` | 125 | forceStoryLink | If set to true, sync will be failed in case at least one test doesn't have @Stories=ABC-XXX annotation | no | `true` | 126 | generateTestCaseUniqueId | Name of JIRA attribute that is used to store unique ID of test case (will be used for test case tracking, updates and linking) | no | `false` | 127 | severityAttribute | Name of JIRA attribute that stores 'Severity' attribute | no | | 128 | autoCreateTestCycle | Should new test cycle be created automatically | no | `true` | 129 | assignee | Specify an Assignee | no | | 130 | linkType | Link type between Test issue and related story (used in combination with `@Stories` annotation) | no | `Reference` | 131 | linkDirection | Link direction between Test issue and related story | no | `inward` | One of `inward` or `outward` 132 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | lv.ctco.zephyr 6 | zephyr-sync-master 7 | 0.0.18-SNAPSHOT 8 | pom 9 | 10 | 11 | scm:git:git@github.com:ctco/zephyr-sync.git 12 | scm:git:git@github.com:ctco/zephyr-sync.git 13 | git@github.com:ctco/zephyr-sync.git 14 | HEAD 15 | 16 | 17 | zephyr-sync-master 18 | 19 | Zephyr Sync is a tool, that allows your project to perform synchronization of automated test results to Zephyr - a JIRA addon for Test Management. 20 | The advanced configuration of the tool supports multiple report types to work with, as well as some restrictions to be applied during the sync. 21 | 22 | 23 | https://github.com/ctco/zephyr-sync 24 | 25 | 26 | 27 | The Apache License, Version 2.0 28 | http://www.apache.org/licenses/LICENSE-2.0.txt 29 | 30 | 31 | 32 | 33 | 34 | Sergey Trasko 35 | sergey.trasko@gmail.com 36 | C.T.Co 37 | http://ctco.github.io/ 38 | 39 | 40 | 41 | 42 | 43 | zephyr-sync-core 44 | zephyr-sync-report-api 45 | zephyr-sync-report-cucumber 46 | zephyr-sync-report-junit 47 | zephyr-sync-report-nunit 48 | zephyr-sync-util 49 | zephyr-sync-report-allure 50 | zephyr-sync-cli 51 | zephyr-sync-maven-plugin 52 | 53 | 54 | 55 | 56 | 57 | junit 58 | junit 59 | 4.13.1 60 | 61 | 62 | org.hamcrest 63 | hamcrest-all 64 | 1.3 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 2.3.2 76 | 77 | 1.8 78 | 1.8 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | release 88 | 89 | 90 | 91 | org.apache.maven.plugins 92 | maven-javadoc-plugin 93 | 2.10.3 94 | 95 | 96 | attach-javadocs 97 | 98 | jar 99 | 100 | 101 | 102 | 103 | false 104 | -Xdoclint:none 105 | 106 | 107 | 108 | org.apache.maven.plugins 109 | maven-source-plugin 110 | 2.4 111 | 112 | 113 | attach-sources 114 | 115 | jar 116 | 117 | 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-gpg-plugin 123 | 1.6 124 | 125 | 126 | sign-artifacts 127 | verify 128 | 129 | sign 130 | 131 | 132 | ${env.GPG_KEYNAME} 133 | ${env.GPG_KEYNAME} 134 | gpg2 135 | 136 | --pinentry-mode 137 | loopback 138 | 139 | 140 | 141 | 142 | 143 | 144 | org.apache.maven.plugins 145 | maven-release-plugin 146 | 2.5.2 147 | 148 | 149 | org.apache.maven.plugins 150 | maven-deploy-plugin 151 | 2.8.2 152 | 153 | true 154 | 155 | 156 | 157 | org.sonatype.plugins 158 | nexus-staging-maven-plugin 159 | 1.6.3 160 | true 161 | 162 | ossrh 163 | https://oss.sonatype.org/ 164 | true 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | ossrh 175 | https://oss.sonatype.org/content/repositories/snapshots 176 | 177 | 178 | ossrh 179 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 180 | 181 | 182 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: maven:3-jdk-8 2 | 3 | test: 4 | steps: 5 | - script: 6 | name: maven test 7 | code: | 8 | mvn --batch-mode test 9 | 10 | deploy-snapshot: 11 | steps: 12 | - install-packages: 13 | packages: secure-delete gnupg2 gnupg-agent 14 | - script: 15 | name: import gpg keys 16 | code: | 17 | shopt -s xpg_echo 18 | echo $GPGKEY_PUB > ~/gpgkey_pub.gpg 19 | gpg2 --import --no-tty ~/gpgkey_pub.gpg 20 | echo $GPGKEY_SEC > ~/gpgkey_sec.gpg 21 | gpg2 --import --batch --no-tty ~/gpgkey_sec.gpg 22 | echo $GPG_AGENT_CONFIG > ~/.gnupg/gpg-agent.conf 23 | srm -vz ~/gpgkey_sec.gpg ~/gpgkey_pub.gpg 24 | - script: 25 | name: saving settings.xml 26 | code: | 27 | mkdir -p ~/.m2 28 | shopt -s xpg_echo 29 | echo $MAVEN_SETTINGS > ~/.m2/settings.xml 30 | - script: 31 | name: deploy snapshot 32 | code: | 33 | mvn --batch-mode -Dmaven.test.skip=true deploy -P release -s ~/.m2/settings.xml 34 | 35 | release: 36 | steps: 37 | - install-packages: 38 | packages: secure-delete gnupg2 gnupg-agent 39 | - add-ssh-key: 40 | keyname: GITHUB_KEY 41 | - add-to-known_hosts: 42 | hostname: github.com 43 | fingerprint: SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 44 | type: rsa 45 | local: true 46 | - script: 47 | name: import gpg keys 48 | code: | 49 | shopt -s xpg_echo 50 | echo $GPGKEY_PUB > ~/gpgkey_pub.gpg 51 | gpg2 --import --no-tty ~/gpgkey_pub.gpg 52 | echo $GPGKEY_SEC > ~/gpgkey_sec.gpg 53 | gpg2 --import --batch --no-tty ~/gpgkey_sec.gpg 54 | echo $GPG_AGENT_CONFIG > ~/.gnupg/gpg-agent.conf 55 | srm -vz ~/gpgkey_sec.gpg ~/gpgkey_pub.gpg 56 | - script: 57 | name: saving settings.xml 58 | code: | 59 | mkdir -p ~/.m2 60 | shopt -s xpg_echo 61 | echo $MAVEN_SETTINGS > ~/.m2/settings.xml 62 | - script: 63 | name: run release 64 | code: | 65 | git checkout $WERCKER_GIT_BRANCH 66 | git reset --hard $WERCKER_GIT_COMMIT 67 | git remote set-url origin git@github.com:ctco/zephyr-sync.git 68 | git config --global user.name "C.T.Co SCM" 69 | git config --global user.email "cscm@ctco.lv" 70 | mvn --batch-mode -Dmaven.test.skip=true -DuseReleaseProfile=false -DscmCommentPrefix="[maven-release-plugin][skip ci]" --batch-mode release:prepare release:perform -P release -s ~/.m2/settings.xml -------------------------------------------------------------------------------- /zephyr-sync-cli/assembly.xml: -------------------------------------------------------------------------------- 1 | 5 | all-in-one 6 | 7 | jar 8 | 9 | false 10 | 11 | 12 | metaInf-services 13 | 14 | 15 | 16 | 17 | true 18 | true 19 | 20 | 21 | -------------------------------------------------------------------------------- /zephyr-sync-cli/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | zephyr-sync-master 5 | lv.ctco.zephyr 6 | 0.0.18-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | zephyr-sync-cli 11 | 12 | 13 | 14 | 15 | org.apache.maven.plugins 16 | maven-assembly-plugin 17 | 2.5.5 18 | 19 | 20 | jar-assembly 21 | package 22 | 23 | single 24 | 25 | 26 | 27 | assembly.xml 28 | 29 | 30 | 31 | lv.ctco.zephyr.Runner 32 | true 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | lv.ctco.zephyr 45 | zephyr-sync-core 46 | ${project.version} 47 | 48 | 49 | lv.ctco.zephyr 50 | zephyr-sync-report-junit 51 | ${project.version} 52 | 53 | 54 | lv.ctco.zephyr 55 | zephyr-sync-report-allure 56 | ${project.version} 57 | 58 | 59 | lv.ctco.zephyr 60 | zephyr-sync-report-cucumber 61 | ${project.version} 62 | 63 | 64 | lv.ctco.zephyr 65 | zephyr-sync-report-nunit 66 | ${project.version} 67 | 68 | 69 | junit 70 | junit 71 | test 72 | 73 | 74 | -------------------------------------------------------------------------------- /zephyr-sync-cli/src/main/java/lv/ctco/zephyr/Runner.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr; 2 | 3 | import lv.ctco.zephyr.enums.ConfigProperty; 4 | import lv.ctco.zephyr.transformer.ReportTransformerFactory; 5 | import lv.ctco.zephyr.util.Utils; 6 | 7 | public class Runner { 8 | 9 | public static void main(final String[] args) throws Exception { 10 | Utils.log("Supported report types: " + ReportTransformerFactory.getInstance().getSupportedReportTransformers()); 11 | Config config = new Config(new CliConfigLoader(args)); 12 | 13 | ZephyrSyncService service = new ZephyrSyncService(config); 14 | service.execute(); 15 | } 16 | 17 | public static class CliConfigLoader implements Config.Loader { 18 | 19 | private String[] args; 20 | 21 | public CliConfigLoader(String[] args) { 22 | this.args = args; 23 | } 24 | 25 | public void execute(Config config) { 26 | for (String arg : args) { 27 | if (!arg.startsWith("--")) { 28 | throw new ZephyrSyncException("Arguments should start with '--', e.g. --projectType=cucumber. Found: " + arg); 29 | } 30 | String[] keyValue = arg.split("="); 31 | String key = keyValue[0].substring(2); 32 | String value = keyValue[1]; 33 | config.setValue(ConfigProperty.findByName(key), value); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /zephyr-sync-cli/src/test/java/lv/ctco/zephyr/CliConfigLoaderTest.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr; 2 | 3 | import lv.ctco.zephyr.enums.ConfigProperty; 4 | import org.junit.Test; 5 | 6 | import java.util.Map; 7 | 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.junit.Assert.assertThat; 10 | 11 | public class CliConfigLoaderTest { 12 | 13 | @Test 14 | public void testExecute() throws Exception { 15 | Config config = new Config(); 16 | Runner.CliConfigLoader configLoader = new Runner.CliConfigLoader(new String[] {"--projectKey=ABC"}); 17 | configLoader.execute(config); 18 | Map result = config.properties; 19 | assertThat(result.size(), is(1)); 20 | assertThat(result.get(ConfigProperty.PROJECT_KEY), is("ABC")); 21 | } 22 | 23 | @Test(expected = ZephyrSyncException.class) 24 | public void testExecute_InvalidFormat() throws Exception { 25 | Config config = new Config(); 26 | Runner.CliConfigLoader configLoader = new Runner.CliConfigLoader(new String[] {"projectKey=ABC"}); 27 | configLoader.execute(config); 28 | } 29 | } -------------------------------------------------------------------------------- /zephyr-sync-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | lv.ctco.zephyr 7 | zephyr-sync-master 8 | 0.0.18-SNAPSHOT 9 | 10 | 11 | zephyr-sync-core 12 | 13 | 14 | 15 | 16 | maven-assembly-plugin 17 | 2.6 18 | 19 | 20 | jar-with-dependencies 21 | 22 | 23 | 24 | lv.ctco.zephyr.Runner 25 | 26 | 27 | 28 | 29 | 30 | make-assembly 31 | package 32 | 33 | single 34 | 35 | 36 | 37 | 38 | 39 | org.apache.maven.plugins 40 | maven-source-plugin 41 | 2.4 42 | 43 | 44 | attach-sources 45 | 46 | jar 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | lv.ctco.zephyr 57 | zephyr-sync-util 58 | ${project.version} 59 | 60 | 61 | lv.ctco.zephyr 62 | zephyr-sync-report-api 63 | ${project.version} 64 | 65 | 66 | lv.ctco.zephyr 67 | zephyr-sync-report-junit 68 | ${project.version} 69 | test 70 | 71 | 72 | junit 73 | junit 74 | test 75 | 76 | 77 | 78 | 79 | 80 | SCM 81 | 82 | true 83 | 84 | 85 | 86 | commons-io 87 | commons-io 88 | 2.7 89 | 90 | 91 | org.apache.httpcomponents 92 | httpclient 93 | 4.5.13 94 | 95 | 96 | commons-logging 97 | commons-logging 98 | 99 | 100 | commons-codec 101 | commons-codec 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/AutodetectReportType.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr; 2 | 3 | import lv.ctco.zephyr.enums.ConfigProperty; 4 | import lv.ctco.zephyr.transformer.ReportTransformerFactory; 5 | 6 | import java.util.List; 7 | 8 | public class AutodetectReportType implements Config.Loader { 9 | public void execute(Config config) { 10 | List supportedReportTransformers = ReportTransformerFactory.getInstance().getSupportedReportTransformers(); 11 | if (supportedReportTransformers.size() == 1) { 12 | config.applyDefault(ConfigProperty.REPORT_TYPE, supportedReportTransformers.get(0)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/Config.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr; 2 | 3 | import lv.ctco.zephyr.enums.ConfigProperty; 4 | 5 | import java.util.*; 6 | 7 | public class Config { 8 | 9 | public interface Loader { 10 | void execute(Config config); 11 | } 12 | 13 | public static final Loader EMPTY_LOADER = new Loader() { 14 | public void execute(Config config) { 15 | //Do nothing 16 | } 17 | }; 18 | 19 | final Map properties; 20 | 21 | public Config(Loader loader) { 22 | this(); 23 | loader.execute(this); 24 | new AutodetectReportType().execute(this); 25 | applyDefaults(); 26 | validateMandatoryAttributes(); 27 | } 28 | 29 | public Config() { 30 | properties = new HashMap(); 31 | } 32 | 33 | public String getValue(ConfigProperty property) { 34 | return properties.get(property); 35 | } 36 | 37 | public void setValue(ConfigProperty property, String value) { 38 | properties.put(property, value); 39 | } 40 | 41 | public void setValue(ConfigProperty property, Boolean value) { 42 | properties.put(property, value == null ? null : value.toString()); 43 | } 44 | 45 | public void applyDefault(ConfigProperty property, String value) { 46 | if (properties.get(property) == null) { 47 | setValue(property, value); 48 | } 49 | } 50 | 51 | void applyDefaults() { 52 | for (ConfigProperty property : ConfigProperty.values()) { 53 | if (property.getDefaultValue() != null) { 54 | applyDefault(property, property.getDefaultValue()); 55 | } 56 | } 57 | } 58 | 59 | void validateMandatoryAttributes() { 60 | List mandatoryProperties = new ArrayList(); 61 | for (ConfigProperty property : ConfigProperty.values()) { 62 | if (property.isMandatory() && properties.get(property) == null) { 63 | mandatoryProperties.add(property.getPropertyName()); 64 | } 65 | } 66 | if (!mandatoryProperties.isEmpty()) { 67 | throw new ZephyrSyncException("The following properties are not passed: " + mandatoryProperties); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/ZephyrSyncService.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr; 2 | 3 | import lv.ctco.zephyr.beans.TestCase; 4 | import lv.ctco.zephyr.beans.jira.Issue; 5 | import lv.ctco.zephyr.util.CustomPropertyNamingStrategy; 6 | import lv.ctco.zephyr.util.ObjectTransformer; 7 | import lv.ctco.zephyr.service.*; 8 | 9 | import java.io.IOException; 10 | import java.util.List; 11 | 12 | public class ZephyrSyncService { 13 | 14 | private AuthService authService; 15 | private MetaInfoRetrievalService metaInfoRetrievalService; 16 | private TestCaseResolutionService testCaseResolutionService; 17 | private JiraService jiraService; 18 | private ZephyrService zephyrService; 19 | 20 | public ZephyrSyncService(Config config) { 21 | ObjectTransformer.setPropertyNamingStrategy(new CustomPropertyNamingStrategy(config)); 22 | 23 | authService = new AuthService(config); 24 | metaInfoRetrievalService = new MetaInfoRetrievalService(config); 25 | testCaseResolutionService = new TestCaseResolutionService(config); 26 | jiraService = new JiraService(config); 27 | zephyrService = new ZephyrService(config); 28 | } 29 | 30 | public void execute() throws IOException, InterruptedException { 31 | authService.authenticateInJira(); 32 | 33 | MetaInfo metaInfo = metaInfoRetrievalService.retrieve(); 34 | 35 | List testCases = testCaseResolutionService.resolveTestCases(); 36 | List issues = jiraService.getTestIssues(); 37 | 38 | zephyrService.mapTestCasesToIssues(testCases, issues); 39 | 40 | for (TestCase testCase : testCases) { 41 | if (testCase.getId() == null) { 42 | jiraService.createTestIssue(testCase); 43 | zephyrService.addStepsToTestIssue(testCase); 44 | jiraService.linkToStory(testCase); 45 | } 46 | } 47 | 48 | zephyrService.linkExecutionsToTestCycle(metaInfo, testCases); 49 | zephyrService.updateExecutionStatuses(testCases); 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/Metafield.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans; 2 | 3 | public class Metafield { 4 | private String id; 5 | private String name; 6 | private String key; 7 | 8 | public String getId() { 9 | return id; 10 | } 11 | 12 | public void setId(String id) { 13 | this.id = id; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | public String getKey() { 25 | return key; 26 | } 27 | 28 | public void setKey(String key) { 29 | this.key = key; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/jira/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | 7 | public class ErrorResponse { 8 | private List errorMessages; 9 | private Map errors; 10 | 11 | public List getErrorMessages() { 12 | return errorMessages; 13 | } 14 | 15 | public void setErrorMessages(List errorMessages) { 16 | this.errorMessages = errorMessages; 17 | } 18 | 19 | public Map getErrors() { 20 | return errors; 21 | } 22 | 23 | public void setErrors(Map errors) { 24 | this.errors = errors; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/jira/Fields.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | import lv.ctco.zephyr.beans.Metafield; 4 | import lv.ctco.zephyr.enums.ConfigProperty; 5 | import lv.ctco.zephyr.util.ConfigBasedJsonProperty; 6 | 7 | import java.util.List; 8 | 9 | public class Fields { 10 | private String summary; 11 | private String description; 12 | private String testCaseUniqueId; 13 | private Metafield project; 14 | private Metafield assignee; 15 | private Metafield issuetype; 16 | private Metafield priority; 17 | private Metafield severity; 18 | private List versions; 19 | private String[] labels; 20 | 21 | public String getSummary() { 22 | return summary; 23 | } 24 | 25 | public void setSummary(String summary) { 26 | this.summary = summary; 27 | } 28 | 29 | public String getDescription() { 30 | return description; 31 | } 32 | 33 | public void setDescription(String description) { 34 | this.description = description; 35 | } 36 | 37 | @ConfigBasedJsonProperty(ConfigProperty.TEST_CASE_UNIQUE_ID) 38 | public String getTestCaseUniqueId() { 39 | return testCaseUniqueId; 40 | } 41 | 42 | @ConfigBasedJsonProperty(ConfigProperty.TEST_CASE_UNIQUE_ID) 43 | public void setTestCaseUniqueId(String testCaseUniqueId) { 44 | this.testCaseUniqueId = testCaseUniqueId; 45 | } 46 | 47 | public Metafield getProject() { 48 | return project; 49 | } 50 | 51 | public void setProject(Metafield project) { 52 | this.project = project; 53 | } 54 | 55 | public Metafield getAssignee() { 56 | return assignee; 57 | } 58 | 59 | public void setAssignee(Metafield assignee) { 60 | this.assignee = assignee; 61 | } 62 | 63 | public Metafield getIssuetype() { 64 | return issuetype; 65 | } 66 | 67 | public void setIssuetype(Metafield issuetype) { 68 | this.issuetype = issuetype; 69 | } 70 | 71 | public List getVersions() { 72 | return versions; 73 | } 74 | 75 | public void setVersions(List versions) { 76 | this.versions = versions; 77 | } 78 | 79 | public Metafield getPriority() { 80 | return priority; 81 | } 82 | 83 | public void setPriority(Metafield priority) { 84 | this.priority = priority; 85 | } 86 | 87 | @ConfigBasedJsonProperty(ConfigProperty.SEVERITY) 88 | public Metafield getSeverity() { 89 | return severity; 90 | } 91 | 92 | @ConfigBasedJsonProperty(ConfigProperty.SEVERITY) 93 | public void setSeverity(Metafield severity) { 94 | this.severity = severity; 95 | } 96 | 97 | public String[] getLabels() { 98 | return labels; 99 | } 100 | 101 | public void setLabels(String[] labels) { 102 | this.labels = labels; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/jira/Issue.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | public class Issue { 4 | private Integer id; 5 | private String key; 6 | private Fields fields; 7 | 8 | public Issue() { 9 | fields = new Fields(); 10 | } 11 | 12 | public Integer getId() { 13 | return id; 14 | } 15 | 16 | public void setId(Integer id) { 17 | this.id = id; 18 | } 19 | 20 | public String getKey() { 21 | return key; 22 | } 23 | 24 | public void setKey(String key) { 25 | this.key = key; 26 | } 27 | 28 | public Fields getFields() { 29 | return fields; 30 | } 31 | 32 | public void setFields(Fields fields) { 33 | this.fields = fields; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/jira/IssueLink.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | import lv.ctco.zephyr.beans.Metafield; 4 | 5 | public class IssueLink { 6 | 7 | private Metafield type; 8 | private Metafield inwardIssue; 9 | private Metafield outwardIssue; 10 | 11 | public IssueLink(String source, String target, String type) { 12 | this.type = new Metafield(); 13 | this.type.setName(type); 14 | this.inwardIssue = new Metafield(); 15 | this.inwardIssue.setKey(source); 16 | this.outwardIssue = new Metafield(); 17 | this.outwardIssue.setKey(target); 18 | } 19 | 20 | public Metafield getType() { 21 | return type; 22 | } 23 | 24 | public void setType(Metafield type) { 25 | this.type = type; 26 | } 27 | 28 | public Metafield getInwardIssue() { 29 | return inwardIssue; 30 | } 31 | 32 | public void setInwardIssue(Metafield inwardIssue) { 33 | this.inwardIssue = inwardIssue; 34 | } 35 | 36 | public Metafield getOutwardIssue() { 37 | return outwardIssue; 38 | } 39 | 40 | public void setOutwardIssue(Metafield outwardIssue) { 41 | this.outwardIssue = outwardIssue; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/jira/IssueLinkDirection.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | import java.util.stream.Stream; 4 | 5 | public enum IssueLinkDirection { 6 | inward, outward; 7 | 8 | public static IssueLinkDirection ofValue(String value) { 9 | return Stream.of(values()) 10 | .filter(dir -> dir.name().equalsIgnoreCase(value)) 11 | .findFirst() 12 | .orElseThrow(() -> new IllegalArgumentException("Issue Link Direction '" + value + "' is not supported.")); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/jira/Login.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | public class Login { 4 | 5 | final String username; 6 | final String password; 7 | 8 | public Login(String username, String password) { 9 | this.username = username; 10 | this.password = password; 11 | } 12 | 13 | public String getUsername() { 14 | return username; 15 | } 16 | 17 | public String getPassword() { 18 | return password; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/jira/Project.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | import lv.ctco.zephyr.beans.Metafield; 4 | 5 | import java.util.List; 6 | 7 | public class Project { 8 | 9 | private String id; 10 | private String key; 11 | private List versions; 12 | 13 | public String getId() { 14 | return id; 15 | } 16 | 17 | public void setId(String id) { 18 | this.id = id; 19 | } 20 | 21 | public String getKey() { 22 | return key; 23 | } 24 | 25 | public void setKey(String key) { 26 | this.key = key; 27 | } 28 | 29 | public List getVersions() { 30 | return versions; 31 | } 32 | 33 | public void setVersions(List versions) { 34 | this.versions = versions; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/jira/SearchResponse.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | import java.util.List; 4 | 5 | public class SearchResponse { 6 | 7 | private int total; 8 | private List issues; 9 | 10 | public int getTotal() { 11 | return total; 12 | } 13 | 14 | public void setTotal(int total) { 15 | this.total = total; 16 | } 17 | 18 | public List getIssues() { 19 | return issues; 20 | } 21 | 22 | public void setIssues(List issues) { 23 | this.issues = issues; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/jira/SessionResponse.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | import java.util.Map; 4 | 5 | public class SessionResponse { 6 | 7 | Map session; 8 | Map loginInfo; 9 | 10 | public Map getSession() { 11 | return session; 12 | } 13 | 14 | public void setSession(Map session) { 15 | this.session = session; 16 | } 17 | 18 | public Map getLoginInfo() { 19 | return loginInfo; 20 | } 21 | 22 | public void setLoginInfo(Map loginInfo) { 23 | this.loginInfo = loginInfo; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/zapi/Cycle.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.zapi; 2 | 3 | public class Cycle { 4 | 5 | private String id; 6 | private String projectKey; 7 | private Integer versionId; 8 | private String name; 9 | 10 | public String getId() { 11 | return id; 12 | } 13 | 14 | public void setId(String id) { 15 | this.id = id; 16 | } 17 | 18 | public String getProjectKey() { 19 | return projectKey; 20 | } 21 | 22 | public void setProjectKey(String projectKey) { 23 | this.projectKey = projectKey; 24 | } 25 | 26 | public Integer getVersionId() { 27 | return versionId; 28 | } 29 | 30 | public void setVersionId(Integer versionId) { 31 | this.versionId = versionId; 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public void setName(String name) { 39 | this.name = name; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/zapi/CycleList.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.zapi; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAnyGetter; 4 | import com.fasterxml.jackson.annotation.JsonAnySetter; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class CycleList { 10 | 11 | private HashMap cycleMap = new HashMap(); 12 | 13 | private Integer recordsCount; 14 | 15 | @JsonAnySetter 16 | public void add(String key, Cycle user) { 17 | cycleMap.put(key, user); 18 | } 19 | 20 | @JsonAnyGetter 21 | public Map getCycleMap() { 22 | return cycleMap; 23 | } 24 | 25 | public Integer getRecordsCount() { 26 | return recordsCount; 27 | } 28 | 29 | public void setRecordsCount(Integer recordsCount) { 30 | this.recordsCount = recordsCount; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/zapi/Execution.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.zapi; 2 | 3 | import java.util.List; 4 | 5 | public class Execution { 6 | 7 | private Integer id; 8 | private String issueKey; 9 | private String projectId; 10 | private String versionId; 11 | private List issues; 12 | private String cycleId; 13 | private String method; 14 | 15 | public Integer getId() { 16 | return id; 17 | } 18 | 19 | public void setId(Integer id) { 20 | this.id = id; 21 | } 22 | 23 | public String getIssueKey() { 24 | return issueKey; 25 | } 26 | 27 | public void setIssueKey(String issueKey) { 28 | this.issueKey = issueKey; 29 | } 30 | 31 | public String getProjectId() { 32 | return projectId; 33 | } 34 | 35 | public void setProjectId(String projectId) { 36 | this.projectId = projectId; 37 | } 38 | 39 | public String getVersionId() { 40 | return versionId; 41 | } 42 | 43 | public void setVersionId(String versionId) { 44 | this.versionId = versionId; 45 | } 46 | 47 | public List getIssues() { 48 | return issues; 49 | } 50 | 51 | public void setIssues(List issues) { 52 | this.issues = issues; 53 | } 54 | 55 | public String getCycleId() { 56 | return cycleId; 57 | } 58 | 59 | public void setCycleId(String cycleId) { 60 | this.cycleId = cycleId; 61 | } 62 | 63 | public String getMethod() { 64 | return method; 65 | } 66 | 67 | public void setMethod(String method) { 68 | this.method = method; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/zapi/ExecutionRequest.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.zapi; 2 | 3 | public class ExecutionRequest { 4 | 5 | private int status; 6 | 7 | public int getStatus() { 8 | return status; 9 | } 10 | 11 | public void setStatus(int status) { 12 | this.status = status; 13 | } 14 | } -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/zapi/ExecutionResponse.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.zapi; 2 | 3 | import java.util.List; 4 | 5 | public class ExecutionResponse { 6 | 7 | private int totalCount; 8 | private List executions; 9 | 10 | public int getTotalCount() { 11 | return totalCount; 12 | } 13 | 14 | public void setTotalCount(int totalCount) { 15 | this.totalCount = totalCount; 16 | } 17 | 18 | public List getExecutions() { 19 | return executions; 20 | } 21 | 22 | public void setExecutions(List executions) { 23 | this.executions = executions; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/zapi/NewCycle.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.zapi; 2 | 3 | public class NewCycle { 4 | 5 | private String projectId; 6 | private String versionId; 7 | private String name; 8 | 9 | public String getProjectId() { 10 | return projectId; 11 | } 12 | 13 | public void setProjectId(String projectId) { 14 | this.projectId = projectId; 15 | } 16 | 17 | public String getVersionId() { 18 | return versionId; 19 | } 20 | 21 | public void setVersionId(String versionId) { 22 | this.versionId = versionId; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public void setName(String name) { 30 | this.name = name; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/beans/zapi/ZapiTestStep.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.zapi; 2 | 3 | public class ZapiTestStep { 4 | 5 | private Integer id; 6 | private Integer orderId; 7 | private String step; 8 | private String data; 9 | private String result; 10 | 11 | public ZapiTestStep() { 12 | } 13 | 14 | public ZapiTestStep(String step) { 15 | this.step = step; 16 | } 17 | 18 | public Integer getId() { 19 | return id; 20 | } 21 | 22 | public void setId(Integer id) { 23 | this.id = id; 24 | } 25 | 26 | public Integer getOrderId() { 27 | return orderId; 28 | } 29 | 30 | public void setOrderId(Integer orderId) { 31 | this.orderId = orderId; 32 | } 33 | 34 | public String getStep() { 35 | return step; 36 | } 37 | 38 | public void setStep(String step) { 39 | this.step = step; 40 | } 41 | 42 | public String getData() { 43 | return data; 44 | } 45 | 46 | public void setData(String data) { 47 | this.data = data; 48 | } 49 | 50 | public String getResult() { 51 | return result; 52 | } 53 | 54 | public void setResult(String result) { 55 | this.result = result; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/enums/ConfigProperty.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.enums; 2 | 3 | import lv.ctco.zephyr.ZephyrSyncException; 4 | 5 | public enum ConfigProperty { 6 | USERNAME("username", true), 7 | PASSWORD("password", true), 8 | REPORT_TYPE("reportType", true), 9 | PROJECT_KEY("projectKey", true), 10 | RELEASE_VERSION("releaseVersion", true), 11 | TEST_CYCLE("testCycle", true), 12 | JIRA_URL("jiraUrl", true), 13 | REPORT_PATH("reportPath", true), 14 | ORDERED_STEPS("orderedSteps", false, "false"), 15 | FORCE_STORY_LINK("forceStoryLink", false, "true"), 16 | TEST_CASE_UNIQUE_ID("testCaseUniqueId", false), 17 | GENERATE_TEST_CASE_UNIQUE_ID("generateTestCaseUniqueId", false, "false"), 18 | SEVERITY("severityAttribute", false), 19 | AUTO_CREATE_TEST_CYCLE("autoCreateTestCycle", false, "true"), 20 | ASSIGNEE("assignee", false), 21 | LINK_TYPE("linkType", false, "Reference"), 22 | LINK_DIRECTION("linkDirection", false, "inward") 23 | ; 24 | 25 | private String propertyName; 26 | private boolean mandatory; 27 | private String defaultValue; 28 | 29 | ConfigProperty(String propertyName, boolean mandatory) { 30 | this.propertyName = propertyName; 31 | this.mandatory = mandatory; 32 | } 33 | 34 | ConfigProperty(String propertyName, boolean mandatory, String defaultValue) { 35 | this(propertyName, mandatory); 36 | this.defaultValue = defaultValue; 37 | } 38 | 39 | public String getPropertyName() { 40 | return propertyName; 41 | } 42 | 43 | public boolean isMandatory() { 44 | return mandatory; 45 | } 46 | 47 | public String getDefaultValue() { 48 | return defaultValue; 49 | } 50 | 51 | public static ConfigProperty findByName(String name) { 52 | for (ConfigProperty property : values()) { 53 | if (property.getPropertyName().equalsIgnoreCase(name)) { 54 | return property; 55 | } 56 | } 57 | throw new ZephyrSyncException("Unsupported parameter is passed " + name); 58 | } 59 | } -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/enums/IssueType.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.enums; 2 | 3 | public enum IssueType { 4 | DEFECT("Defect"), 5 | TEST("Test"); 6 | 7 | private String name; 8 | 9 | IssueType(String name) { 10 | this.name = name; 11 | } 12 | 13 | public String getName() { 14 | return name; 15 | } 16 | } -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/service/AuthService.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.service; 2 | 3 | import lv.ctco.zephyr.Config; 4 | import lv.ctco.zephyr.beans.jira.ErrorResponse; 5 | import lv.ctco.zephyr.beans.jira.Login; 6 | import lv.ctco.zephyr.beans.jira.SessionResponse; 7 | import lv.ctco.zephyr.enums.ConfigProperty; 8 | import lv.ctco.zephyr.util.HttpUtils; 9 | import lv.ctco.zephyr.util.ObjectTransformer; 10 | import lv.ctco.zephyr.util.Utils; 11 | import org.apache.http.HttpResponse; 12 | import org.apache.http.impl.client.BasicCookieStore; 13 | 14 | import java.io.IOException; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | import static lv.ctco.zephyr.util.Utils.log; 19 | 20 | public class AuthService { 21 | public static final BasicCookieStore COOKIE = new BasicCookieStore(); 22 | private static String jSessionId; 23 | 24 | private Config config; 25 | 26 | public AuthService(Config config) { 27 | this.config = config; 28 | } 29 | 30 | public void authenticateInJira() throws IOException { 31 | if (jSessionId == null) { 32 | Login login = new Login(config.getValue(ConfigProperty.USERNAME), config.getValue(ConfigProperty.PASSWORD)); 33 | 34 | HttpResponse response = HttpUtils.post(config, "auth/1/session", login); 35 | if (response.getStatusLine().getStatusCode() == 403) { 36 | if (response.containsHeader("X-Authentication-Denied-Reason")) { 37 | log("ERROR: JIRA authentication denied reason: " + response.getFirstHeader("X-Authentication-Denied-Reason").getValue()); 38 | } 39 | 40 | ErrorResponse errorResponse = ObjectTransformer.deserialize(Utils.readInputStream(response.getEntity().getContent()), ErrorResponse.class); 41 | List errorMessages = errorResponse.getErrorMessages(); 42 | log("ERROR: JIRA authentication failed, error messages: " + errorMessages.stream().collect(Collectors.joining(", "))); 43 | return; 44 | } 45 | if (response.getStatusLine().getStatusCode() != 200) { 46 | log("ERROR: JIRA authentication failed: " + response.getStatusLine().getProtocolVersion() + " " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()); 47 | return; 48 | } 49 | SessionResponse loginResponse = ObjectTransformer.deserialize(Utils.readInputStream(response.getEntity().getContent()), SessionResponse.class); 50 | if (loginResponse != null) { 51 | jSessionId = loginResponse.getSession().get("value"); 52 | } 53 | } 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/service/JiraService.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.service; 2 | 3 | import lv.ctco.zephyr.Config; 4 | import lv.ctco.zephyr.ZephyrSyncException; 5 | import lv.ctco.zephyr.beans.Metafield; 6 | import lv.ctco.zephyr.beans.TestCase; 7 | import lv.ctco.zephyr.beans.jira.Issue; 8 | import lv.ctco.zephyr.beans.jira.IssueLink; 9 | import lv.ctco.zephyr.beans.jira.IssueLinkDirection; 10 | import lv.ctco.zephyr.beans.jira.SearchResponse; 11 | import lv.ctco.zephyr.transformer.TestCaseToIssueTransformer; 12 | import lv.ctco.zephyr.util.ObjectTransformer; 13 | import org.apache.http.HttpResponse; 14 | 15 | import java.io.IOException; 16 | import java.util.List; 17 | 18 | import static java.lang.String.format; 19 | import static lv.ctco.zephyr.enums.ConfigProperty.*; 20 | import static lv.ctco.zephyr.util.HttpUtils.*; 21 | import static lv.ctco.zephyr.util.Utils.log; 22 | import static lv.ctco.zephyr.util.Utils.readInputStream; 23 | 24 | public class JiraService { 25 | 26 | private static final int TOP = 500; 27 | 28 | private Config config; 29 | 30 | public JiraService(Config config) { 31 | this.config = config; 32 | } 33 | 34 | public List getTestIssues() throws IOException { 35 | int skip = 0; 36 | log("Fetching JIRA Test issues for the project"); 37 | String search = "project='" + config.getValue(PROJECT_KEY) + "'%20and%20issueType=Test"; 38 | SearchResponse searchResults = searchInJQL(search, skip); 39 | if (searchResults == null || searchResults.getIssues() == null) { 40 | throw new ZephyrSyncException("Unable to fetch JIRA test issues"); 41 | } 42 | 43 | List issues = searchResults.getIssues(); 44 | 45 | int totalCount = searchResults.getTotal(); 46 | if (totalCount > TOP) { 47 | while (issues.size() >= totalCount) { 48 | skip += TOP; 49 | SearchResponse newSearchResponse = searchInJQL(search, skip); 50 | totalCount = newSearchResponse.getTotal(); 51 | if (issues.size() > totalCount) { 52 | return getTestIssues(); 53 | } 54 | issues.addAll(newSearchResponse.getIssues()); 55 | } 56 | } 57 | log(format("Retrieved %s Test issues\n", issues.size())); 58 | return issues; 59 | } 60 | 61 | SearchResponse searchInJQL(String search, int skip) throws IOException { 62 | String response = getAndReturnBody(config, "api/2/search?jql=" + search + "&maxResults=" + TOP + "&startAt=" + skip); 63 | return ObjectTransformer.deserialize(response, SearchResponse.class); 64 | } 65 | 66 | public void createTestIssue(TestCase testCase) throws IOException { 67 | log("INFO: Creating JIRA Test item with Name: \"" + testCase.getName() + "\"."); 68 | Issue issue = TestCaseToIssueTransformer.transform(config, testCase); 69 | 70 | HttpResponse response = post(config, "api/2/issue", issue); 71 | ensureResponse(response, 201, "ERROR: Could not create JIRA Test item"); 72 | 73 | String responseBody = readInputStream(response.getEntity().getContent()); 74 | Metafield result = ObjectTransformer.deserialize(responseBody, Metafield.class); 75 | if (result != null) { 76 | testCase.setId(Integer.valueOf(result.getId())); 77 | testCase.setKey(result.getKey()); 78 | } 79 | log("INFO: Created. JIRA Test item Id is: [" + testCase.getKey() + "]."); 80 | } 81 | 82 | public void linkToStory(TestCase testCase) throws IOException { 83 | List storyKeys = testCase.getStoryKeys(); 84 | if (Boolean.valueOf(config.getValue(FORCE_STORY_LINK))) { 85 | if (storyKeys == null || storyKeys.isEmpty()) { 86 | throw new ZephyrSyncException("Linking Test issues to Story is mandatory, please check if Story marker exists in " + testCase.getKey()); 87 | } 88 | } 89 | if (storyKeys == null) return; 90 | 91 | log("Linking Test issue " + testCase.getKey() + " to Stories " + testCase.getStoryKeys()); 92 | for (String storyKey : storyKeys) { 93 | HttpResponse response = post(config, "api/2/issueLink", createIssueLink(testCase, storyKey)); 94 | ensureResponse(response, 201, "Could not link Test issue: " + testCase.getId() + " to Story " + storyKey + ". " + 95 | "Please check if Story issue exists and is valid"); 96 | } 97 | } 98 | 99 | private IssueLink createIssueLink(TestCase testCase, String storyKey) { 100 | IssueLinkDirection direction = IssueLinkDirection.ofValue(config.getValue(LINK_DIRECTION)); 101 | if (direction == IssueLinkDirection.inward) { 102 | return new IssueLink(testCase.getKey(), storyKey.toUpperCase(), config.getValue(LINK_TYPE)); 103 | } 104 | return new IssueLink(storyKey.toUpperCase(), testCase.getKey(), config.getValue(LINK_TYPE)); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/service/MetaInfo.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.service; 2 | 3 | public class MetaInfo { 4 | 5 | private String projectId; 6 | private String versionId; 7 | private String cycleId; 8 | 9 | public String getProjectId() { 10 | return projectId; 11 | } 12 | 13 | public void setProjectId(String projectId) { 14 | this.projectId = projectId; 15 | } 16 | 17 | public String getVersionId() { 18 | return versionId; 19 | } 20 | 21 | public void setVersionId(String versionId) { 22 | this.versionId = versionId; 23 | } 24 | 25 | public String getCycleId() { 26 | return cycleId; 27 | } 28 | 29 | public void setCycleId(String cycleId) { 30 | this.cycleId = cycleId; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/service/MetaInfoRetrievalService.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.service; 2 | 3 | import lv.ctco.zephyr.Config; 4 | import lv.ctco.zephyr.ZephyrSyncException; 5 | import lv.ctco.zephyr.beans.Metafield; 6 | import lv.ctco.zephyr.beans.jira.Project; 7 | import lv.ctco.zephyr.beans.zapi.Cycle; 8 | import lv.ctco.zephyr.beans.zapi.CycleList; 9 | import lv.ctco.zephyr.beans.zapi.NewCycle; 10 | import lv.ctco.zephyr.util.Utils; 11 | import lv.ctco.zephyr.enums.ConfigProperty; 12 | import lv.ctco.zephyr.util.ObjectTransformer; 13 | import org.apache.http.HttpResponse; 14 | 15 | import java.io.IOException; 16 | import java.util.Map; 17 | 18 | import static lv.ctco.zephyr.util.HttpUtils.getAndReturnBody; 19 | import static lv.ctco.zephyr.util.HttpUtils.post; 20 | import static java.lang.String.format; 21 | 22 | public class MetaInfoRetrievalService { 23 | 24 | private Config config; 25 | 26 | public MetaInfoRetrievalService(Config config) { 27 | this.config = config; 28 | } 29 | 30 | public MetaInfo retrieve() throws IOException { 31 | MetaInfo metaInfo = new MetaInfo(); 32 | retrieveProjectMetaInfo(metaInfo); 33 | retrieveTestCycleId(metaInfo); 34 | return metaInfo; 35 | } 36 | 37 | private void retrieveProjectMetaInfo(MetaInfo metaInfo) throws IOException { 38 | String projectKey = config.getValue(ConfigProperty.PROJECT_KEY); 39 | String response = getAndReturnBody(config, format("api/2/project/%s", projectKey)); 40 | Project project = ObjectTransformer.deserialize(response, Project.class); 41 | 42 | if (project == null || project.getKey() == null || !project.getKey().equals(projectKey)) { 43 | throw new ZephyrSyncException("Improper JIRA project retrieved"); 44 | } 45 | 46 | String projectId = project.getId(); 47 | Utils.log("Retrieved project ID - " + projectId); 48 | metaInfo.setProjectId(projectId); 49 | 50 | for (Metafield version : project.getVersions()) { 51 | if (version.getName().equals(config.getValue(ConfigProperty.RELEASE_VERSION))) { 52 | String versionId = version.getId(); 53 | Utils.log("Retrieved version ID - " + versionId); 54 | metaInfo.setVersionId(versionId); 55 | } 56 | } 57 | } 58 | 59 | private void retrieveTestCycleId(MetaInfo metaInfo) throws IOException { 60 | String projectId = metaInfo.getProjectId(); 61 | String versionId = metaInfo.getVersionId(); 62 | if (projectId == null || versionId == null) 63 | throw new ZephyrSyncException("JIRA projectID or versionID are missing"); 64 | 65 | String response = getAndReturnBody(config, format("zapi/latest/cycle?projectId=%s&versionId=%s", projectId, versionId)); 66 | CycleList cycleList = ObjectTransformer.deserialize(response, CycleList.class); 67 | if (cycleList == null || cycleList.getCycleMap().isEmpty()) { 68 | throw new ZephyrSyncException("Unable to retrieve JIRA test cycle"); 69 | } 70 | 71 | for (Map.Entry entry : cycleList.getCycleMap().entrySet()) { 72 | Cycle value = entry.getValue(); 73 | if (value != null 74 | && value.getProjectKey().equals(config.getValue(ConfigProperty.PROJECT_KEY)) 75 | && value.getVersionId().toString().equals(versionId) 76 | && value.getName().equals(config.getValue(ConfigProperty.TEST_CYCLE))) { 77 | String cycleId = entry.getKey(); 78 | Utils.log("Retrieved target Test Cycle ID - " + cycleId + "\n"); 79 | metaInfo.setCycleId(cycleId); 80 | return; 81 | } 82 | } 83 | if (Boolean.parseBoolean(config.getValue(ConfigProperty.AUTO_CREATE_TEST_CYCLE))) { 84 | Utils.log("Creating new test cycle"); 85 | String cycleId = createNewTestCycle(projectId, versionId); 86 | Utils.log("New test cycle created - " + cycleId); 87 | metaInfo.setCycleId(cycleId); 88 | } else { 89 | throw new ZephyrSyncException("Unable to retrieve JIRA test cycle"); 90 | } 91 | } 92 | 93 | private String createNewTestCycle(String projectId, String versionId) throws IOException { 94 | NewCycle newCycle = new NewCycle(); 95 | newCycle.setProjectId(projectId); 96 | newCycle.setVersionId(versionId); 97 | newCycle.setName(config.getValue(ConfigProperty.TEST_CYCLE)); 98 | HttpResponse response = post(config, "zapi/latest/cycle", newCycle); 99 | Cycle cycle = ObjectTransformer.deserialize(Utils.readInputStream(response.getEntity().getContent()), Cycle.class); 100 | return cycle.getId(); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/service/TestCaseResolutionService.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.service; 2 | 3 | import lv.ctco.zephyr.Config; 4 | import lv.ctco.zephyr.ZephyrSyncException; 5 | import lv.ctco.zephyr.beans.TestCase; 6 | import lv.ctco.zephyr.transformer.ReportTransformer; 7 | import lv.ctco.zephyr.transformer.ReportTransformerFactory; 8 | 9 | import java.util.Iterator; 10 | import java.util.List; 11 | 12 | import static lv.ctco.zephyr.enums.ConfigProperty.REPORT_PATH; 13 | import static lv.ctco.zephyr.enums.ConfigProperty.REPORT_TYPE; 14 | 15 | public class TestCaseResolutionService { 16 | 17 | private Config config; 18 | 19 | public TestCaseResolutionService(Config config) { 20 | this.config = config; 21 | } 22 | 23 | public List resolveTestCases() { 24 | String reportType = config.getValue(REPORT_TYPE); 25 | String path = config.getValue(REPORT_PATH); 26 | ReportTransformer transformer = ReportTransformerFactory.getInstance().getTransformer(reportType); 27 | List testCases = transformer.transformToTestCases(path); 28 | if (testCases == null) { 29 | throw new ZephyrSyncException("No Test Cases extracted from the Test Report"); 30 | } 31 | for (Iterator it = testCases.iterator(); it.hasNext(); ) { 32 | TestCase testCase = it.next(); 33 | if (testCase.getName() == null || testCase.getName().length() == 0) { 34 | it.remove(); 35 | } 36 | } 37 | if (testCases.isEmpty()) { 38 | throw new ZephyrSyncException("No Test Cases extracted from the Test Report"); 39 | } 40 | return testCases; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/service/ZephyrService.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.service; 2 | 3 | import lv.ctco.zephyr.Config; 4 | import lv.ctco.zephyr.beans.TestCase; 5 | import lv.ctco.zephyr.beans.TestStep; 6 | import lv.ctco.zephyr.beans.jira.Issue; 7 | import lv.ctco.zephyr.beans.zapi.Execution; 8 | import lv.ctco.zephyr.beans.zapi.ExecutionRequest; 9 | import lv.ctco.zephyr.beans.zapi.ExecutionResponse; 10 | import lv.ctco.zephyr.beans.zapi.ZapiTestStep; 11 | import lv.ctco.zephyr.util.ObjectTransformer; 12 | import org.apache.http.HttpResponse; 13 | import org.apache.http.util.EntityUtils; 14 | 15 | import java.io.IOException; 16 | import java.net.URLEncoder; 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | import static java.lang.String.format; 23 | import static lv.ctco.zephyr.enums.ConfigProperty.*; 24 | import static lv.ctco.zephyr.util.HttpUtils.*; 25 | import static lv.ctco.zephyr.util.Utils.log; 26 | 27 | public class ZephyrService { 28 | 29 | private static final int TOP = 20; 30 | 31 | private Config config; 32 | 33 | public ZephyrService(Config config) { 34 | this.config = config; 35 | } 36 | 37 | private Map getAllExecutions(Config config) throws IOException { 38 | log("Fetching JIRA Test Executions for the project"); 39 | int skip = 0; 40 | String search = "project='" + config.getValue(PROJECT_KEY) + "'%20and%20fixVersion='" 41 | + URLEncoder.encode(config.getValue(RELEASE_VERSION), "UTF-8") + "'%20and%20cycleName='" + config.getValue(TEST_CYCLE) + "'"; 42 | 43 | ExecutionResponse executionResponse = searchInZQL(search, skip); 44 | if (executionResponse == null || executionResponse.getExecutions().isEmpty()) { 45 | return new HashMap<>(); 46 | } 47 | 48 | List executions = executionResponse.getExecutions(); 49 | 50 | int totalCount = executionResponse.getTotalCount(); 51 | if (totalCount > TOP) { 52 | while (executions.size() != totalCount) { 53 | skip += TOP; 54 | List nextPageExecutions = searchInZQL(search, skip).getExecutions(); 55 | if (nextPageExecutions.isEmpty()) { 56 | break; 57 | } 58 | executions.addAll(nextPageExecutions); 59 | } 60 | } 61 | Map result = new HashMap<>(executions.size()); 62 | for (Execution execution : executions) { 63 | result.put(execution.getIssueKey(), execution); 64 | } 65 | log(format("Retrieved %s Test executions\n", executions.size())); 66 | return result; 67 | } 68 | 69 | private ExecutionResponse searchInZQL(String search, int skip) throws IOException { 70 | String response = getAndReturnBody(config, "zapi/latest/zql/executeSearch?zqlQuery=" + search + "&offset=" + skip); 71 | return ObjectTransformer.deserialize(response, ExecutionResponse.class); 72 | } 73 | 74 | public void linkExecutionsToTestCycle(MetaInfo metaInfo, List testCases) throws IOException, InterruptedException { 75 | Map executions = getAllExecutions(config); 76 | 77 | List keys = new ArrayList<>(); 78 | 79 | for (TestCase testCase : testCases) { 80 | if (!executions.containsKey(testCase.getKey())) { 81 | keys.add(testCase.getKey()); 82 | } 83 | } 84 | if (keys.size() > 0) { 85 | linkTestToCycle(metaInfo, keys); 86 | } else { 87 | log("All Test cases are already linked to the Test cycle.\n"); 88 | } 89 | 90 | } 91 | 92 | private void linkTestToCycle(MetaInfo metaInfo, List keys) throws IOException, InterruptedException { 93 | log("INFO: Linking Test Cases to Test Cycle:" + keys.toString() + ""); 94 | 95 | Execution execution = new Execution(); 96 | execution.setCycleId(metaInfo.getCycleId()); 97 | execution.setIssues(keys); 98 | execution.setMethod("1"); 99 | execution.setProjectId(metaInfo.getProjectId()); 100 | execution.setVersionId(metaInfo.getVersionId()); 101 | 102 | /* 103 | On first execution adding items to test cycle may take long time, 104 | since that job is ran async, it may cause the situation when limited amount of tests will be returned by getAllExecutions() 105 | in ZAPI version 2.3.0 Atlassian made addTestsToCycle to return jobProgressToken to check job status 106 | prooflink1: https://marketplace.atlassian.com/plugins/com.thed.zephyr.zapi/versions 107 | prooflink2: http://docs.getzephyr.apiary.io/#reference/executionresource/add-tests-to-cycle/add-test%27s-to-cycle 108 | */ 109 | 110 | // TODO: for ZAPI version 2.3.0 or higher implement handling of jobProgressToken 111 | HttpResponse response = post(config, "zapi/latest/execution/addTestsToCycle/", execution); 112 | ensureResponse(response, 200, "Could not link Test cases"); 113 | 114 | // waiting for addTestsToCycle() to finish it's job, workaround/hack/waiter for ZAPI version lover than 2.3.0 115 | int iteration = 0; 116 | while (!checkTestCycleIsInSync(keys) && iteration < 5) { 117 | log("INFO: Test Cycle is not in sync. Giving Zephyr another chance."); 118 | Thread.sleep(5000); 119 | iteration++; 120 | } 121 | } 122 | 123 | private boolean checkTestCycleIsInSync(List keys) throws IOException { 124 | Map executions = getAllExecutions(config); 125 | for (String testId : keys) { 126 | if (!executions.containsKey(testId)) { 127 | log("INFO: Test Case " + testId + " was not found in Test Cycle"); 128 | return false; 129 | } 130 | } 131 | return true; 132 | } 133 | 134 | public void updateExecutionStatuses(List testCases) throws IOException { 135 | Map executions = getAllExecutions(config); 136 | 137 | for (TestCase testCase : testCases) { 138 | log("INFO: Setting status " + testCase.getStatus() + " for Test: " + testCase.getKey() + ""); 139 | 140 | Execution execution = executions.get(testCase.getKey()); 141 | if (execution == null) { 142 | log("WARN: Test " + testCase.getKey() + " not found in Test Cycle " + config.getValue(TEST_CYCLE) + ""); 143 | continue; 144 | } 145 | 146 | ExecutionRequest request = new ExecutionRequest(); 147 | request.setStatus(testCase.getStatus().getId()); 148 | HttpResponse response = put(config, "zapi/latest/execution/" + execution.getId() + "/execute", request); 149 | ensureResponse(response, 200, "Could not successfully update execution status"); 150 | } 151 | } 152 | 153 | public void addStepsToTestIssue(TestCase testCase) throws IOException { 154 | log("INFO: Getting Test Steps for Test: " + testCase.getKey()); 155 | List testSteps = testCase.getSteps(); 156 | if (testSteps == null) { 157 | log("INFO: No Test Steps found for Test: " + testCase.getKey()); 158 | return; 159 | } 160 | 161 | Map map = prepareTestSteps(testSteps, 0, "", Boolean.valueOf(config.getValue(ORDERED_STEPS))); 162 | log("INFO: Setting Test Steps for Test: " + testCase.getKey()); 163 | for (TestStep step : map.values()) { 164 | HttpResponse response = post(config, "zapi/latest/teststep/" + testCase.getId(), new ZapiTestStep(step.getDescription())); 165 | ensureResponse(response, 200, "Could not add Test Steps for Test Case: " + testCase.getId()); 166 | } 167 | } 168 | 169 | private Map prepareTestSteps(List testSteps, int level, String prefix, Boolean isOrdered) { 170 | Map map = new HashMap<>(); 171 | for (int i = 1; i <= testSteps.size(); i++) { 172 | TestStep testStep = testSteps.get(i - 1); 173 | String description = testStep.getDescription(); 174 | testStep.setDescription(isOrdered ? format("%s %s", prefix + i + ".", description) : description); 175 | map.put(map.size() + 1, testStep); 176 | 177 | if (testStep.getSteps() != null && testStep.getSteps().size() > 0) { 178 | map.putAll(prepareTestSteps(testStep.getSteps(), level + 1, prefix + i + ".", isOrdered)); 179 | 180 | } 181 | } 182 | 183 | return map; 184 | } 185 | 186 | public void mapTestCasesToIssues(List resultTestCases, List issues) { 187 | Map uniqueKeyMap = new HashMap<>(issues.size()); 188 | for (Issue issue : issues) { 189 | uniqueKeyMap.put(issue.getKey(), issue); 190 | String testCaseUniqueId = issue.getFields().getTestCaseUniqueId(); 191 | if (testCaseUniqueId != null) { 192 | uniqueKeyMap.put(testCaseUniqueId, issue); 193 | } 194 | } 195 | 196 | for (TestCase testCase : resultTestCases) { 197 | String testCaseKey = testCase.getKey(); 198 | String testCaseName = testCase.getName(); 199 | // case when test case can be matched by id 200 | if (testCaseKey != null && uniqueKeyMap.containsKey(testCaseKey)) { 201 | testCase.setId(uniqueKeyMap.get(testCaseKey).getId()); 202 | continue; 203 | } 204 | // if no exact match by id was found, trying to match by exact name 205 | if (testCaseKey == null && testCaseName != null) { 206 | for (Issue issue : issues) { 207 | if (issue.getFields().getSummary().equalsIgnoreCase(testCaseName)) { 208 | testCase.setId(issue.getId()); 209 | testCase.setKey(issue.getKey()); 210 | continue; 211 | } 212 | } 213 | } 214 | // if no exact match by id or by name, creating new one 215 | if (testCaseKey != null) { 216 | log(format("INFO: Key %s not found, new Test Case will be created", testCaseKey)); 217 | testCase.setId(null); 218 | testCase.setKey(null); 219 | continue; 220 | } 221 | } 222 | } 223 | } 224 | 225 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/transformer/ReportTransformerFactory.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.transformer; 2 | 3 | import lv.ctco.zephyr.ZephyrSyncException; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.ServiceLoader; 8 | 9 | public class ReportTransformerFactory { 10 | 11 | private static final ReportTransformerFactory instance = new ReportTransformerFactory(); 12 | public static ReportTransformerFactory getInstance() { 13 | return instance; 14 | } 15 | 16 | private final ServiceLoader transformers; 17 | 18 | private ReportTransformerFactory() { 19 | transformers = ServiceLoader.load(ReportTransformer.class); 20 | } 21 | 22 | public List getSupportedReportTransformers() { 23 | List result = new ArrayList(); 24 | for (ReportTransformer transformer : transformers) { 25 | result.add(transformer.getType()); 26 | } 27 | return result; 28 | } 29 | 30 | public ReportTransformer getTransformer(String reportType) { 31 | for (ReportTransformer transformer : transformers) { 32 | if (transformer.getType().equalsIgnoreCase(reportType)) { 33 | return transformer; 34 | } 35 | } 36 | throw new ZephyrSyncException("Report type " + reportType + " is not recognized!"); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/transformer/TestCaseToIssueTransformer.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.transformer; 2 | 3 | import lv.ctco.zephyr.Config; 4 | import lv.ctco.zephyr.beans.Metafield; 5 | import lv.ctco.zephyr.beans.TestCase; 6 | import lv.ctco.zephyr.beans.jira.Issue; 7 | import lv.ctco.zephyr.enums.ConfigProperty; 8 | import lv.ctco.zephyr.enums.IssueType; 9 | import org.apache.http.util.TextUtils; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class TestCaseToIssueTransformer { 15 | 16 | 17 | public static Issue transform(Config config, TestCase testCase) { 18 | Issue issue = new Issue(); 19 | if (config.getValue(ConfigProperty.GENERATE_TEST_CASE_UNIQUE_ID).equalsIgnoreCase("true")) { 20 | issue.getFields().setTestCaseUniqueId(testCase.getUniqueId()); 21 | } 22 | 23 | setIssueFieldsFromTestCaseAttributes(issue, testCase); 24 | setIssueFieldsFromConfig(issue, config); 25 | return issue; 26 | } 27 | 28 | public static void setIssueFieldsFromTestCaseAttributes(Issue issue, TestCase testCase) { 29 | issue.getFields().setSummary(testCase.getName()); 30 | issue.getFields().setDescription( 31 | TextUtils.isBlank(testCase.getDescription()) ? 32 | testCase.getSuiteName() : 33 | testCase.getDescription()); 34 | 35 | Metafield issueType = new Metafield(); 36 | issueType.setName(IssueType.TEST.getName()); 37 | issue.getFields().setIssuetype(issueType); 38 | 39 | if (testCase.getSeverity() != null) { 40 | Metafield severity = new Metafield(); 41 | severity.setId(testCase.getSeverity().getIndex().toString()); 42 | issue.getFields().setSeverity(severity); 43 | } 44 | 45 | if (testCase.getPriority() != null) { 46 | Metafield priority = new Metafield(); 47 | priority.setName(testCase.getPriority().getName()); 48 | issue.getFields().setPriority(priority); 49 | } 50 | 51 | List labels = new ArrayList<>(); 52 | labels.add("Automation"); 53 | List testLabels = testCase.getLabels(); 54 | if (testLabels != null && testLabels.size() > 0) { 55 | labels.addAll(testLabels); 56 | } 57 | issue.getFields().setLabels(labels.toArray(new String[labels.size()])); 58 | } 59 | 60 | public static void setIssueFieldsFromConfig(Issue issue, Config config) { 61 | for (ConfigProperty property : ConfigProperty.values()) { 62 | String value = config.getValue(property); 63 | Metafield metafield = new Metafield(); 64 | if (value != null) { 65 | if (property.equals(ConfigProperty.ASSIGNEE)) { 66 | metafield.setName(value); 67 | issue.getFields().setAssignee(metafield); 68 | } 69 | if (property.equals(ConfigProperty.SEVERITY)) { 70 | metafield.setName(value); 71 | issue.getFields().setSeverity(metafield); 72 | } 73 | if (property.equals(ConfigProperty.RELEASE_VERSION)) { 74 | metafield.setName(value); 75 | List versions = new ArrayList(1); 76 | versions.add(metafield); 77 | issue.getFields().setVersions(versions); 78 | } 79 | if (property.equals(ConfigProperty.PROJECT_KEY)) { 80 | metafield.setKey(value); 81 | issue.getFields().setProject(metafield); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/util/ConfigBasedJsonProperty.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.util; 2 | 3 | import lv.ctco.zephyr.enums.ConfigProperty; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.FIELD, ElementType.METHOD}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface ConfigBasedJsonProperty { 13 | ConfigProperty value(); 14 | } 15 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/util/CustomPropertyNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.util; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import com.fasterxml.jackson.databind.cfg.MapperConfig; 5 | import com.fasterxml.jackson.databind.introspect.AnnotatedField; 6 | import com.fasterxml.jackson.databind.introspect.AnnotatedMember; 7 | import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; 8 | import lv.ctco.zephyr.Config; 9 | 10 | public class CustomPropertyNamingStrategy extends PropertyNamingStrategy { 11 | 12 | private Config config; 13 | 14 | public CustomPropertyNamingStrategy(Config config) { 15 | this.config = config; 16 | } 17 | 18 | @Override 19 | public String nameForField(MapperConfig config, AnnotatedField field, String defaultName) { 20 | return nameForAnnotated(field, defaultName); 21 | } 22 | 23 | @Override 24 | public String nameForGetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName) { 25 | return nameForAnnotated(method, defaultName); 26 | } 27 | 28 | @Override 29 | public String nameForSetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName) { 30 | return nameForAnnotated(method, defaultName); 31 | } 32 | 33 | String nameForAnnotated(AnnotatedMember member, String defaultName) { 34 | ConfigBasedJsonProperty annotation = member.getAnnotation(ConfigBasedJsonProperty.class); 35 | if (annotation == null) { 36 | return defaultName; 37 | } 38 | return config.getValue(annotation.value()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/main/java/lv/ctco/zephyr/util/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.util; 2 | 3 | import lv.ctco.zephyr.Config; 4 | import lv.ctco.zephyr.ZephyrSyncException; 5 | import lv.ctco.zephyr.service.AuthService; 6 | import lv.ctco.zephyr.enums.ConfigProperty; 7 | import org.apache.commons.io.IOUtils; 8 | import org.apache.http.HttpResponse; 9 | import org.apache.http.client.methods.CloseableHttpResponse; 10 | import org.apache.http.client.methods.HttpGet; 11 | import org.apache.http.client.methods.HttpPost; 12 | import org.apache.http.client.methods.HttpPut; 13 | import org.apache.http.client.methods.HttpRequestBase; 14 | import org.apache.http.entity.StringEntity; 15 | import org.apache.http.impl.client.CloseableHttpClient; 16 | import org.apache.http.impl.client.HttpClientBuilder; 17 | 18 | import java.io.IOException; 19 | 20 | import static lv.ctco.zephyr.util.Utils.log; 21 | 22 | public class HttpUtils { 23 | 24 | public static CloseableHttpClient getHttpClient() { 25 | return HttpClientBuilder 26 | .create() 27 | .setDefaultCookieStore(AuthService.COOKIE) 28 | .build(); 29 | } 30 | 31 | private static HttpResponse get(Config config, String url) throws IOException { 32 | CloseableHttpClient httpClient = getHttpClient(); 33 | String uri = config.getValue(ConfigProperty.JIRA_URL) + url; 34 | Utils.log("GET: " + uri); 35 | HttpGet request = new HttpGet(uri); 36 | setCommonHeaders(request); 37 | return httpClient.execute(request); 38 | } 39 | 40 | private static void setCommonHeaders(HttpRequestBase request) throws IOException { 41 | request.setHeader("Accept", "application/json"); 42 | } 43 | 44 | public static String getAndReturnBody(Config config, String url) throws IOException { 45 | HttpResponse response = get(config, url); 46 | return Utils.readInputStream(response.getEntity().getContent()); 47 | } 48 | 49 | public static HttpResponse post(Config config, String url, Object entity) throws IOException { 50 | String json = ObjectTransformer.serialize(entity); 51 | 52 | CloseableHttpClient httpClient = getHttpClient(); 53 | String uri = config.getValue(ConfigProperty.JIRA_URL) + url; 54 | Utils.log("POST: " + uri); 55 | HttpPost request = new HttpPost(uri); 56 | setCommonHeaders(request); 57 | request.setHeader("Content-Type", "application/json"); 58 | request.setEntity(new StringEntity(json)); 59 | return httpClient.execute(request); 60 | } 61 | 62 | public static HttpResponse put(Config config, String url, Object entity) throws IOException { 63 | String json = ObjectTransformer.serialize(entity); 64 | 65 | CloseableHttpClient httpClient = getHttpClient(); 66 | String uri = config.getValue(ConfigProperty.JIRA_URL) + url; 67 | Utils.log("PUT: " + uri); 68 | HttpPut request = new HttpPut(uri); 69 | setCommonHeaders(request); 70 | request.setHeader("Content-Type", "application/json"); 71 | request.setEntity(new StringEntity(json)); 72 | CloseableHttpResponse response = httpClient.execute(request); 73 | httpClient.close(); 74 | return response; 75 | } 76 | 77 | public static void ensureResponse(HttpResponse response, int expectedStatusCode, String failureMessage) { 78 | if (response.getStatusLine().getStatusCode() != expectedStatusCode) { 79 | String responseBody; 80 | try { 81 | responseBody = IOUtils.toString(response.getEntity().getContent()); 82 | } catch (IOException e) { 83 | Utils.log("Failed to parse response", e); 84 | responseBody = ""; 85 | } 86 | throw new ZephyrSyncException(failureMessage + ": " + responseBody); 87 | } 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /zephyr-sync-core/src/test/java/lv/ctco/zephyr/ConfigTest.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr; 2 | 3 | import lv.ctco.zephyr.enums.ConfigProperty; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.util.Map; 8 | 9 | import static org.hamcrest.CoreMatchers.is; 10 | import static org.hamcrest.CoreMatchers.not; 11 | import static org.hamcrest.CoreMatchers.nullValue; 12 | import static org.junit.Assert.*; 13 | 14 | public class ConfigTest { 15 | 16 | private Config config; 17 | private Map properties; 18 | 19 | @Before 20 | public void setUp() throws Exception { 21 | config = new Config(); 22 | properties = config.properties; 23 | } 24 | 25 | @Test 26 | public void testApplyDefaults() throws Exception { 27 | config.applyDefaults(); 28 | assertThat(properties.size(), is(not(0))); 29 | for (Map.Entry entry : properties.entrySet()) { 30 | assertThat(entry.getValue(), is(not(nullValue()))); 31 | } 32 | } 33 | 34 | @Test 35 | public void testValidateMandatoryAttributes() throws Exception { 36 | for (ConfigProperty property : ConfigProperty.values()) { 37 | properties.put(property, "aaa"); 38 | } 39 | config.validateMandatoryAttributes(); 40 | } 41 | 42 | @Test(expected = ZephyrSyncException.class) 43 | public void testValidateMandatoryAttributes_SomethingMissing() throws Exception { 44 | config.validateMandatoryAttributes(); 45 | } 46 | } -------------------------------------------------------------------------------- /zephyr-sync-core/src/test/java/lv/ctco/zephyr/TestConfigLoader.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr; 2 | 3 | import lv.ctco.zephyr.enums.ConfigProperty; 4 | 5 | public class TestConfigLoader implements Config.Loader { 6 | 7 | public void execute(Config config) { 8 | for (ConfigProperty configProperty : ConfigProperty.values()) { 9 | config.setValue(configProperty, configProperty.getDefaultValue() == null ? "test" : configProperty.getDefaultValue()); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /zephyr-sync-core/src/test/java/lv/ctco/zephyr/beans/jira/FieldsTest.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | import lv.ctco.zephyr.Config; 4 | import lv.ctco.zephyr.beans.TestCase; 5 | import lv.ctco.zephyr.enums.ConfigProperty; 6 | import lv.ctco.zephyr.enums.IssueType; 7 | import lv.ctco.zephyr.enums.TestLevel; 8 | import lv.ctco.zephyr.util.ObjectTransformer; 9 | import org.junit.Test; 10 | import static junit.framework.Assert.assertEquals; 11 | import static lv.ctco.zephyr.transformer.TestCaseToIssueTransformer.setIssueFieldsFromConfig; 12 | import static lv.ctco.zephyr.transformer.TestCaseToIssueTransformer.setIssueFieldsFromTestCaseAttributes; 13 | import static org.junit.Assert.assertArrayEquals; 14 | 15 | 16 | public class FieldsTest { 17 | 18 | @Test 19 | public void shouldPopulateTestCaseFieldsToJiraObject() throws Exception{ 20 | TestCase testCase = createTestCase("testCaseName", "testCaseDescription", TestLevel.CRITICAL, TestLevel.LOW); 21 | Issue issue = createIssue(); 22 | setIssueFieldsFromTestCaseAttributes(issue, testCase); 23 | assertEquals(testCase.getName(), issue.getFields().getSummary()); 24 | assertEquals(testCase.getDescription(), issue.getFields().getDescription()); 25 | assertArrayEquals(new String[]{"Automation"}, issue.getFields().getLabels()); 26 | assertEquals(testCase.getPriority().getName(), issue.getFields().getPriority().getName()); 27 | assertEquals(testCase.getSeverity().getIndex().toString(), issue.getFields().getSeverity().getId()); 28 | assertEquals(IssueType.TEST.getName(), issue.getFields().getIssuetype().getName()); 29 | } 30 | 31 | @Test 32 | public void shouldPopulateConfigFieldsToJiraObject()throws Exception{ 33 | Issue issue = createIssue(); 34 | Config config = createConfig("PRJ","Version 1.2.3", "employee", "Major"); 35 | setIssueFieldsFromConfig(issue, config); 36 | assertEquals(config.getValue(ConfigProperty.PROJECT_KEY), issue.getFields().getProject().getKey()); 37 | assertEquals(config.getValue(ConfigProperty.ASSIGNEE), issue.getFields().getAssignee().getName()); 38 | assertEquals(config.getValue(ConfigProperty.SEVERITY), issue.getFields().getSeverity().getName()); 39 | } 40 | 41 | @Test 42 | public void shouldSerializeJiraObjectToJson() throws Exception{ 43 | TestCase testCase = createTestCase("testCaseName", "testCaseDescription", TestLevel.CRITICAL, TestLevel.LOW); 44 | Config config = createConfig("PRJ","Version 1.2.3", "employee", "Major"); 45 | Issue issue = createIssue(); 46 | setIssueFieldsFromConfig(issue, config); 47 | setIssueFieldsFromTestCaseAttributes(issue, testCase); 48 | String json = ObjectTransformer.serialize(issue.getFields()); 49 | String expectedJson = "{\"summary\":\"testCaseName\",\"description\":\"testCaseDescription\",\"project\":{\"key\":\"PRJ\"},\"assignee\":{\"name\":\"employee\"},\"issuetype\":{\"name\":\"Test\"},\"priority\":{\"name\":\"Low\"},\"severity\":{\"id\":\"10121\"},\"versions\":[{\"name\":\"Version 1.2.3\"}],\"labels\":[\"Automation\"]}"; 50 | assertEquals(expectedJson, json); 51 | } 52 | 53 | private Config createConfig (String cfgProjectKey, String cfgReleaseVersion, String cfgAssignee, String cfgSeverity){ 54 | Config config = new Config(); 55 | config.setValue(ConfigProperty.PROJECT_KEY, cfgProjectKey); 56 | config.setValue(ConfigProperty.RELEASE_VERSION, cfgReleaseVersion); 57 | config.setValue(ConfigProperty.ASSIGNEE, cfgAssignee); 58 | config.setValue(ConfigProperty.SEVERITY, cfgSeverity); 59 | return config; 60 | } 61 | 62 | private TestCase createTestCase (String tcName, String tcDescr, TestLevel tcSeverity, TestLevel tcPriority){ 63 | TestCase testCase = new TestCase(); 64 | testCase.setName(tcName); 65 | testCase.setDescription(tcDescr); 66 | testCase.setSeverity(tcSeverity); 67 | testCase.setPriority(tcPriority); 68 | return testCase; 69 | } 70 | 71 | private Issue createIssue (){ 72 | Issue issue = new Issue(); 73 | return issue; 74 | } 75 | } -------------------------------------------------------------------------------- /zephyr-sync-core/src/test/java/lv/ctco/zephyr/beans/jira/IssueLinkTest.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.beans.jira; 2 | 3 | import lv.ctco.zephyr.util.ObjectTransformer; 4 | import org.junit.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.*; 7 | import static org.junit.Assert.assertThat; 8 | 9 | public class IssueLinkTest { 10 | 11 | @Test 12 | public void testSerialize() throws Exception { 13 | String json = ObjectTransformer.serialize(new IssueLink("aaa", "bbb", "Reference")); 14 | System.out.println(json); 15 | assertThat(json, is(not(nullValue()))); 16 | } 17 | } -------------------------------------------------------------------------------- /zephyr-sync-core/src/test/java/lv/ctco/zephyr/transformer/ReportTransformerFactoryTest.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.transformer; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.hamcrest.CoreMatchers.not; 7 | import static org.hamcrest.CoreMatchers.nullValue; 8 | import static org.junit.Assert.*; 9 | 10 | public class ReportTransformerFactoryTest { 11 | 12 | @Test 13 | public void testGetTransformer_FromSPI() throws Exception { 14 | ReportTransformer transformer = ReportTransformerFactory.getInstance().getTransformer("junit"); 15 | assertThat(transformer, is(not(nullValue()))); 16 | } 17 | } -------------------------------------------------------------------------------- /zephyr-sync-maven-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | zephyr-sync-master 5 | lv.ctco.zephyr 6 | 0.0.18-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | zephyr-sync-maven-plugin 11 | maven-plugin 12 | 13 | 14 | install 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-plugin-plugin 19 | 3.5.1 20 | 21 | true 22 | 23 | 24 | 25 | default-descriptor 26 | 27 | descriptor 28 | 29 | process-classes 30 | 31 | 32 | help-descriptor 33 | 34 | helpmojo 35 | 36 | process-classes 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.apache.maven 46 | maven-plugin-api 47 | 2.0 48 | 49 | 50 | org.apache.maven.plugin-tools 51 | maven-plugin-annotations 52 | 3.3 53 | 54 | 55 | lv.ctco.zephyr 56 | zephyr-sync-core 57 | ${project.version} 58 | 59 | 60 | 61 | 62 | 63 | SCM 64 | 65 | true 66 | 67 | 68 | 69 | commons-logging 70 | commons-logging 71 | 1.1 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /zephyr-sync-maven-plugin/src/main/java/lv/ctco/zephyr/mojo/ZephyrSyncMojo.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.mojo; 2 | 3 | import lv.ctco.zephyr.Config; 4 | import lv.ctco.zephyr.ZephyrSyncService; 5 | import lv.ctco.zephyr.enums.ConfigProperty; 6 | import org.apache.maven.plugin.AbstractMojo; 7 | import org.apache.maven.plugin.MojoExecutionException; 8 | import org.apache.maven.plugin.MojoFailureException; 9 | import org.apache.maven.plugins.annotations.Mojo; 10 | import org.apache.maven.plugins.annotations.Parameter; 11 | 12 | import java.io.IOException; 13 | 14 | @Mojo( name = "sync" ) 15 | public class ZephyrSyncMojo 16 | extends AbstractMojo 17 | implements Config.Loader 18 | { 19 | 20 | /** 21 | * User name used to connect to JIRA. 22 | */ 23 | @Parameter( required = true ) 24 | private String username; 25 | 26 | /** 27 | * Password for the user to connect to JIRA. 28 | */ 29 | @Parameter( required = true ) 30 | private String password; 31 | 32 | /** 33 | * Type of report that will be synchronized to Zephyr. One of `cucumber`, `allure`, `junit` or `nunit`. 34 | */ 35 | @Parameter( required = true ) 36 | private String reportType; 37 | 38 | /** 39 | * Key of project in JIRA. 40 | */ 41 | @Parameter( required = true ) 42 | private String projectKey; 43 | 44 | /** 45 | * FixVersion of a project to link Test results to. 46 | */ 47 | @Parameter( required = true ) 48 | private String releaseVersion; 49 | 50 | /** 51 | * Zephyr test cycle where the results will be linked to. 52 | */ 53 | @Parameter( required = true ) 54 | private String testCycle; 55 | 56 | /** 57 | * URL of JIRA (it's RESTful API endpoint), eg "http://your.jira.server/jira/rest/". 58 | */ 59 | @Parameter( required = true ) 60 | private String jiraUrl; 61 | 62 | /** 63 | * Path on the file system where reports are stored, eg "${project.build.directory}/cucumber-report/report.json". 64 | */ 65 | @Parameter( required = true ) 66 | private String reportPath; 67 | 68 | /** 69 | * If set to true, numerical prefix for test steps will be put (hierarchical). 70 | */ 71 | @Parameter( defaultValue = "false" ) 72 | private Boolean orderedSteps; 73 | 74 | /** 75 | * If set to true, sync will be failed in case at least one test doesn't have @Stories=ABC-XXX annotation. 76 | */ 77 | @Parameter( defaultValue = "true" ) 78 | private Boolean forceStoryLink; 79 | 80 | /** 81 | * 82 | */ 83 | @Parameter( defaultValue = "false" ) 84 | private Boolean generateTestCaseUniqueId; 85 | 86 | /** 87 | * Name of JIRA attribute that stores 'Severity' attribute. 88 | */ 89 | @Parameter 90 | private String severityAttribute; 91 | 92 | /** 93 | * Should new test cycle be created automatically? 94 | */ 95 | @Parameter( defaultValue = "true" ) 96 | private Boolean autoCreateTestCycle; 97 | 98 | /** 99 | * Specify an Assignee. 100 | */ 101 | @Parameter 102 | private String assignee; 103 | 104 | /** 105 | * Link type between Test issue and related story (used in combination with `@Stories` annotation). 106 | */ 107 | @Parameter 108 | private String linkType; 109 | 110 | /** 111 | * Link direction between Test issue and related story (one of: inward, outward) 112 | */ 113 | @Parameter 114 | private String linkDirection; 115 | 116 | public void execute() 117 | throws MojoExecutionException, MojoFailureException 118 | { 119 | Config config = new Config( this ); 120 | 121 | ZephyrSyncService syncService = new ZephyrSyncService( config ); 122 | try 123 | { 124 | syncService.execute(); 125 | } 126 | catch ( IOException e ) 127 | { 128 | throw new MojoExecutionException( "Cannot sync test results into zephyr", e ); 129 | } 130 | catch ( InterruptedException e ) 131 | { 132 | e.printStackTrace(); 133 | } 134 | } 135 | 136 | public void execute( Config config ) 137 | { 138 | config.setValue( ConfigProperty.USERNAME, username ); 139 | config.setValue( ConfigProperty.PASSWORD, password ); 140 | config.setValue( ConfigProperty.REPORT_TYPE, reportType ); 141 | config.setValue( ConfigProperty.PROJECT_KEY, projectKey ); 142 | config.setValue( ConfigProperty.RELEASE_VERSION, releaseVersion ); 143 | config.setValue( ConfigProperty.TEST_CYCLE, testCycle ); 144 | config.setValue( ConfigProperty.JIRA_URL, jiraUrl ); 145 | config.setValue( ConfigProperty.REPORT_PATH, reportPath ); 146 | config.setValue( ConfigProperty.ORDERED_STEPS, orderedSteps ); 147 | config.setValue( ConfigProperty.FORCE_STORY_LINK, forceStoryLink ); 148 | config.setValue( ConfigProperty.GENERATE_TEST_CASE_UNIQUE_ID, generateTestCaseUniqueId ); 149 | config.setValue( ConfigProperty.SEVERITY, severityAttribute ); 150 | config.setValue( ConfigProperty.AUTO_CREATE_TEST_CYCLE, autoCreateTestCycle ); 151 | config.setValue( ConfigProperty.ASSIGNEE, assignee ); 152 | config.setValue( ConfigProperty.LINK_TYPE, linkType ); 153 | config.setValue( ConfigProperty.LINK_DIRECTION, linkDirection); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /zephyr-sync-report-allure/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | zephyr-sync-master 5 | lv.ctco.zephyr 6 | 0.0.18-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | zephyr-sync-report-allure 11 | 12 | 13 | 2.20.1 14 | 15 | 16 | 17 | lv.ctco.zephyr 18 | zephyr-sync-report-api 19 | ${project.version} 20 | 21 | 22 | lv.ctco.zephyr 23 | zephyr-sync-util 24 | ${project.version} 25 | 26 | 27 | junit 28 | junit 29 | test 30 | 31 | 32 | org.hamcrest 33 | hamcrest-all 34 | test 35 | 36 | 37 | 38 | 39 | 40 | SCM 41 | 42 | true 43 | 44 | 45 | 46 | io.qameta.allure 47 | allure-java-commons 48 | ${qameta.allure.version} 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /zephyr-sync-report-allure/src/main/java/lv/ctco/zephyr/transformer/AllureTransformer.java: -------------------------------------------------------------------------------- 1 | package lv.ctco.zephyr.transformer; 2 | 3 | import io.qameta.allure.SeverityLevel; 4 | import io.qameta.allure.internal.shadowed.jackson.databind.ObjectMapper; 5 | import io.qameta.allure.model.Label; 6 | import io.qameta.allure.model.StepResult; 7 | import io.qameta.allure.model.TestResult; 8 | import lv.ctco.zephyr.beans.TestCase; 9 | import lv.ctco.zephyr.beans.TestStep; 10 | import lv.ctco.zephyr.enums.TestLevel; 11 | import lv.ctco.zephyr.enums.TestStatus; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.nio.file.Paths; 16 | import java.util.*; 17 | import java.util.function.Predicate; 18 | import java.util.stream.Collectors; 19 | import java.util.stream.Stream; 20 | 21 | import static io.qameta.allure.internal.shadowed.jackson.databind.DeserializationFeature.*; 22 | import static io.qameta.allure.internal.shadowed.jackson.databind.MapperFeature.*; 23 | import static io.qameta.allure.util.ResultsUtils.*; 24 | 25 | public class AllureTransformer implements ReportTransformer { 26 | 27 | private static final List STORY_LABELS = Arrays.asList(EPIC_LABEL_NAME, FEATURE_LABEL_NAME, STORY_LABEL_NAME); 28 | private static final List LABEL_LABELS = Arrays.asList("label", TAG_LABEL_NAME); 29 | 30 | @Override 31 | public String getType() { 32 | return "allure"; 33 | } 34 | 35 | @Override 36 | public List transformToTestCases(String reportPath) { 37 | return transform(readAllureReport(reportPath)); 38 | } 39 | 40 | private Stream readAllureReport(String path) { 41 | ObjectMapper mapper = getAllureMapper(); 42 | return Arrays.stream(Objects.requireNonNull(Paths.get(path).toFile().listFiles())) 43 | .filter(file -> file.getName().endsWith("-result.json")) 44 | .map(file -> readTestResultFile(mapper, file)); 45 | } 46 | 47 | private TestResult readTestResultFile(ObjectMapper allureMapper, File testResultFile) { 48 | try { 49 | return allureMapper.readValue(testResultFile, TestResult.class); 50 | } catch (IOException e) { 51 | throw new RuntimeException(e); 52 | } 53 | } 54 | 55 | private ObjectMapper getAllureMapper() { 56 | ObjectMapper mapper = new io.qameta.allure.internal.shadowed.jackson.databind.ObjectMapper(); 57 | mapper.configure(USE_WRAPPER_NAME_AS_PROPERTY_NAME, true); 58 | mapper.configure(ACCEPT_CASE_INSENSITIVE_ENUMS, true); 59 | mapper.configure(ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); 60 | mapper.configure(READ_UNKNOWN_ENUM_VALUES_AS_NULL, true); 61 | mapper.configure(FAIL_ON_IGNORED_PROPERTIES, false); 62 | mapper.configure(FAIL_ON_NUMBERS_FOR_ENUMS, false); 63 | mapper.configure(FAIL_ON_NULL_FOR_PRIMITIVES, false); 64 | return mapper; 65 | } 66 | 67 | private List transform(Stream results) { 68 | return results.map(this::transform).collect(Collectors.toList()); 69 | } 70 | 71 | private TestCase transform(TestResult testResult) { 72 | TestCase currentTestCase = new TestCase(); 73 | //trimming name to fit into MAX length size supported by JIRA 74 | currentTestCase.setName(testResult.getName().substring(0,250)); 75 | currentTestCase.setUniqueId(generateUniqueId(testResult)); 76 | currentTestCase.setDescription(testResult.getDescription()); 77 | currentTestCase.setStoryKeys(getStoryKeys(testResult)); 78 | currentTestCase.setStatus(getStatus(testResult)); 79 | currentTestCase.setSeverity(getSeverity(testResult)); 80 | currentTestCase.setLabels(getLabels(testResult)); 81 | currentTestCase.setSteps(addTestSteps(testResult.getSteps(), 1)); 82 | return currentTestCase; 83 | } 84 | 85 | private String generateUniqueId(TestResult testResult) { 86 | return testResult.getName(); 87 | } 88 | 89 | private TestStatus getStatus(TestResult testResult) { 90 | switch (testResult.getStatus()) { 91 | case FAILED: 92 | case BROKEN: 93 | return TestStatus.FAILED; 94 | case PASSED: 95 | return TestStatus.PASSED; 96 | default: 97 | return TestStatus.NOT_EXECUTED; 98 | } 99 | } 100 | 101 | private TestLevel getSeverity(TestResult testResult) { 102 | String severity = ""; 103 | 104 | for (Label currentLabel : testResult.getLabels()) { 105 | if (currentLabel.getName().equalsIgnoreCase(SEVERITY_LABEL_NAME) && !currentLabel.getValue().isEmpty()) { 106 | severity = currentLabel.getValue(); 107 | } 108 | } 109 | if (!(severity.isEmpty())) { 110 | switch (fromValue(severity)) { 111 | case TRIVIAL: 112 | return TestLevel.TRIVIAL; 113 | case MINOR: 114 | return TestLevel.MINOR; 115 | case CRITICAL: 116 | return TestLevel.CRITICAL; 117 | case BLOCKER: 118 | return TestLevel.BLOCKER; 119 | default: 120 | return TestLevel.MAJOR; 121 | } 122 | } 123 | return null; 124 | } 125 | 126 | private SeverityLevel fromValue(String severity) { 127 | return Stream.of(SeverityLevel.values()) 128 | .filter(level -> level.value().equals(severity)) 129 | .findFirst() 130 | .orElseThrow(() -> new IllegalArgumentException("Unsupported severity level: " + severity)); 131 | } 132 | 133 | private List getStoryKeys(TestResult testResult) { 134 | return getAllureLabels(testResult, label -> STORY_LABELS.contains(label.getName())); 135 | } 136 | 137 | private List getLabels(TestResult testResult) { 138 | return getAllureLabels(testResult, label -> LABEL_LABELS.contains(label.getName())); 139 | } 140 | 141 | private List getAllureLabels(TestResult testResult, Predicate