From 101e677c92274594d36d45cf725c9faf87654d39 Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Thu, 8 Jul 2021 13:57:18 +0200 Subject: [PATCH 01/12] Added initial functionality for Jira integration --- .../com/privalia/qa/utils/JiraConnector.java | 64 ++++++++++++++++++ .../com/privalia/qa/ATests/JiraTagIT.java | 66 +++++++++++++++++++ src/test/resources/jira.properties | 3 + 3 files changed, 133 insertions(+) create mode 100644 src/main/java/com/privalia/qa/utils/JiraConnector.java create mode 100644 src/test/java/com/privalia/qa/ATests/JiraTagIT.java create mode 100644 src/test/resources/jira.properties diff --git a/src/main/java/com/privalia/qa/utils/JiraConnector.java b/src/main/java/com/privalia/qa/utils/JiraConnector.java new file mode 100644 index 00000000..b4584bbf --- /dev/null +++ b/src/main/java/com/privalia/qa/utils/JiraConnector.java @@ -0,0 +1,64 @@ +package com.privalia.qa.utils; + +import com.jayway.jsonpath.JsonPath; +import com.ning.http.client.AsyncHttpClient; +import com.ning.http.client.Request; +import com.ning.http.client.RequestBuilder; +import com.ning.http.client.Response; +import org.apache.commons.text.StringSubstitutor; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class JiraConnector { + + public static final String JIRA_PROPERTIES_FILE = "jira.properties"; + + final StringSubstitutor interpolator = StringSubstitutor.createInterpolator(); + + AsyncHttpClient client = new AsyncHttpClient(); + + public String getProperty(String property) { + + interpolator.setEnableUndefinedVariableException(true); + + if (System.getProperty(property) != null) { + return System.getProperty(property); + } + + return interpolator.replace("${properties:src/test/resources/" + JIRA_PROPERTIES_FILE + "::" + property + "}"); + } + + public String getEntityStatus(String entity) throws ExecutionException, InterruptedException, IOException { + + String jiraURL = this.getProperty("jira.server.url"); + String jiraToken = this.getProperty("jira.personal.access.token"); + + Request getRequest = new RequestBuilder() + .setMethod("GET") + .setUrl(jiraURL + "/rest/api/2/issue/" + entity) + .addHeader("Authorization", "Bearer " + jiraToken) + .build(); + + Future f = this.client.executeRequest(getRequest); + Response r = (Response) f.get(); + + return JsonPath.read(r.getResponseBody(),"$.fields.status.name").toString().toUpperCase(); + } + + + public Boolean entityShouldRun(String entity) throws IOException, ExecutionException, InterruptedException { + + String[] valid_statuses = this.getProperty("jira.valid.runnable.statuses").split(","); + String entity_current_status = this.getEntityStatus(entity).toUpperCase(); + + for (String status: valid_statuses) { + if (entity_current_status.matches(status.toUpperCase())) { + return true; + } + } + + return false; + } +} diff --git a/src/test/java/com/privalia/qa/ATests/JiraTagIT.java b/src/test/java/com/privalia/qa/ATests/JiraTagIT.java new file mode 100644 index 00000000..4a7a3eba --- /dev/null +++ b/src/test/java/com/privalia/qa/ATests/JiraTagIT.java @@ -0,0 +1,66 @@ +package com.privalia.qa.ATests; + +import com.privalia.qa.utils.JiraConnector; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class JiraTagIT { + + JiraConnector jc = new JiraConnector(); + + @Test + public void shouldGetDataFromPropertiesFile() { + String jiraServerURL = this.jc.getProperty("jira.server.url"); + assertThat(jiraServerURL).isEqualTo("https://jira.vptech.eu"); + } + + @Test + public void shouldUseSystemVariableIfPresent() { + System.setProperty("jira.server.url", "http://dummy-server.com/"); + String jiraServerURL = this.jc.getProperty("jira.server.url"); + assertThat(jiraServerURL).isEqualTo("http://dummy-server.com/"); + } + + @Test + public void shouldThrowExceptionIfVariableNotFound() { + assertThatThrownBy(() -> { + this.jc.getProperty("not.real.property.key"); + }).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Cannot resolve variable"); + } + + + @Test + public void shouldRetrieveCorrectEntityStatus() throws ExecutionException, InterruptedException, IOException { + String statusName = this.jc.getEntityStatus("QMS-990"); + assertThat(statusName).isEqualToIgnoringCase("Done"); + } + + @Test + public void shouldReturnTrueIfEntitiyStatusMatchesRunnableStatuses() throws ExecutionException, InterruptedException, IOException { + Boolean shouldRun = this.jc.entityShouldRun("QMS-990"); + assertThat(shouldRun).isTrue(); + } + + @Test + public void shouldReturnFalseIfEntitiyStatusDifferentFronRunnableStatuses() throws ExecutionException, InterruptedException, IOException { + System.setProperty("jira.valid.runnable.statuses", "READY,QA READY,DEPLOYED"); + Boolean shouldRun = this.jc.entityShouldRun("QMS-990"); + assertThat(shouldRun).isFalse(); + System.clearProperty("jira.valid.runnable.statuses"); + } + + + @Test + public void shouldTransitionEntityIfTestFails() { + + + + + } +} diff --git a/src/test/resources/jira.properties b/src/test/resources/jira.properties new file mode 100644 index 00000000..e3036dc8 --- /dev/null +++ b/src/test/resources/jira.properties @@ -0,0 +1,3 @@ +jira.server.url=https://jira.vptech.eu +jira.personal.access.token=NzA2NTkyMTgyMDY0OmNVPKWy7BIFOvR+76hB22d+Im4Q +jira.valid.runnable.statuses=READY,QA READY,DONE,DEPLOYED From 35603b528d47ee6e95c73954899a67a2f6b91291 Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Fri, 9 Jul 2021 12:48:38 +0200 Subject: [PATCH 02/12] Added methods to change entity status and post comments --- .../com/privalia/qa/utils/JiraConnector.java | 93 ++++++++++++++++++- .../com/privalia/qa/ATests/JiraTagIT.java | 45 +++++++-- src/test/resources/jira.properties | 3 +- 3 files changed, 126 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/privalia/qa/utils/JiraConnector.java b/src/main/java/com/privalia/qa/utils/JiraConnector.java index b4584bbf..69414816 100644 --- a/src/main/java/com/privalia/qa/utils/JiraConnector.java +++ b/src/main/java/com/privalia/qa/utils/JiraConnector.java @@ -6,9 +6,7 @@ import com.ning.http.client.RequestBuilder; import com.ning.http.client.Response; import org.apache.commons.text.StringSubstitutor; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; +import net.minidev.json.JSONArray; import java.util.concurrent.Future; public class JiraConnector { @@ -30,7 +28,7 @@ public String getProperty(String property) { return interpolator.replace("${properties:src/test/resources/" + JIRA_PROPERTIES_FILE + "::" + property + "}"); } - public String getEntityStatus(String entity) throws ExecutionException, InterruptedException, IOException { + public String getEntityStatus(String entity) throws Exception { String jiraURL = this.getProperty("jira.server.url"); String jiraToken = this.getProperty("jira.personal.access.token"); @@ -44,11 +42,15 @@ public String getEntityStatus(String entity) throws ExecutionException, Interrup Future f = this.client.executeRequest(getRequest); Response r = (Response) f.get(); + if (r.getStatusCode() != 200) { + throw new Exception("Unexpected status code response:" + r.getStatusCode() + ". Body: '" + r.getResponseBody() + "'"); + } + return JsonPath.read(r.getResponseBody(),"$.fields.status.name").toString().toUpperCase(); } - public Boolean entityShouldRun(String entity) throws IOException, ExecutionException, InterruptedException { + public Boolean entityShouldRun(String entity) throws Exception { String[] valid_statuses = this.getProperty("jira.valid.runnable.statuses").split(","); String entity_current_status = this.getEntityStatus(entity).toUpperCase(); @@ -61,4 +63,85 @@ public Boolean entityShouldRun(String entity) throws IOException, ExecutionExcep return false; } + + public void transitionEntityToGivenStatus(String entity, String new_status) throws Exception { + + int targetTransition = this.getTransitionIDForEntityByName(entity, new_status); + + String jiraURL = this.getProperty("jira.server.url"); + String jiraToken = this.getProperty("jira.personal.access.token"); + + Request postRequest = new RequestBuilder() + .setMethod("POST") + .setUrl(jiraURL + "/rest/api/2/issue/" + entity + "/transitions") + .addHeader("Authorization", "Bearer " + jiraToken) + .addHeader("Content-Type", "application/json") + .setBody("{\"transition\": {\"id\": " + targetTransition + " }}") + .build(); + + Future f = this.client.executeRequest(postRequest); + Response r = (Response) f.get(); + + if (r.getStatusCode() != 204) { + throw new Exception("Unexpected status code response:" + r.getStatusCode() + ". Body: '" + r.getResponseBody() + "'"); + } + + } + + public int getTransitionIDForEntityByName(String entity, String transitionName) throws Exception { + + String jiraURL = this.getProperty("jira.server.url"); + String jiraToken = this.getProperty("jira.personal.access.token"); + + Request getRequest = new RequestBuilder() + .setMethod("GET") + .setUrl(jiraURL + "/rest/api/2/issue/" + entity + "/transitions") + .addHeader("Authorization", "Bearer " + jiraToken) + .build(); + + Future f = this.client.executeRequest(getRequest); + Response r = (Response) f.get(); + + if (r.getStatusCode() != 200) { + throw new Exception("Unexpected status code response:" + r.getStatusCode() + ". Body: '" + r.getResponseBody() + "'"); + } + + Object transitionStrings = JsonPath.read(r.getResponseBody(),"$.transitions[?(@.name=='" + transitionName + "')].id"); + JSONArray ja = (JSONArray) transitionStrings; + + if (ja.isEmpty()) { + throw new IndexOutOfBoundsException("Could not find the transition '" + transitionName + "' in the list of valid transitions for entity '" + entity + "'"); + } else { + return Integer.valueOf(ja.get(0).toString()); + } + + } + + public void postCommentToEntity(String entity, String message) throws Exception { + + String jiraURL = this.getProperty("jira.server.url"); + String jiraToken = this.getProperty("jira.personal.access.token"); + + Request postRequest = new RequestBuilder() + .setMethod("POST") + .setUrl(jiraURL + "/rest/api/2/issue/" + entity + "/comment") + .addHeader("Authorization", "Bearer " + jiraToken) + .addHeader("Content-Type", "application/json") + .setBody("{\"body\": \"" + message + "\"}") + .build(); + + Future f = this.client.executeRequest(postRequest); + Response r = (Response) f.get(); + + if (r.getStatusCode() != 201) { + throw new Exception("Unexpected status code response:" + r.getStatusCode() + ". Body: '" + r.getResponseBody() + "'"); + } + } + + public void transitionEntity(String entity) throws Exception { + + String jiraTransitionIfFail = this.getProperty("jira.transition.if.fail"); + this.transitionEntityToGivenStatus(entity, jiraTransitionIfFail); + + } } diff --git a/src/test/java/com/privalia/qa/ATests/JiraTagIT.java b/src/test/java/com/privalia/qa/ATests/JiraTagIT.java index 4a7a3eba..e64f6b3c 100644 --- a/src/test/java/com/privalia/qa/ATests/JiraTagIT.java +++ b/src/test/java/com/privalia/qa/ATests/JiraTagIT.java @@ -1,11 +1,8 @@ package com.privalia.qa.ATests; import com.privalia.qa.utils.JiraConnector; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; - -import java.io.IOException; -import java.util.concurrent.ExecutionException; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -13,6 +10,13 @@ public class JiraTagIT { JiraConnector jc = new JiraConnector(); + @BeforeTest + public void setUp() throws Exception { + this.jc.transitionEntityToGivenStatus("QMS-990", "Done"); + String statusName = this.jc.getEntityStatus("QMS-990"); + assertThat(statusName).isEqualToIgnoringCase("Done"); + } + @Test public void shouldGetDataFromPropertiesFile() { String jiraServerURL = this.jc.getProperty("jira.server.url"); @@ -36,31 +40,54 @@ public void shouldThrowExceptionIfVariableNotFound() { @Test - public void shouldRetrieveCorrectEntityStatus() throws ExecutionException, InterruptedException, IOException { + public void shouldRetrieveCorrectEntityStatus() throws Exception { String statusName = this.jc.getEntityStatus("QMS-990"); assertThat(statusName).isEqualToIgnoringCase("Done"); } @Test - public void shouldReturnTrueIfEntitiyStatusMatchesRunnableStatuses() throws ExecutionException, InterruptedException, IOException { + public void shouldReturnTrueIfEntitiyStatusMatchesRunnableStatuses() throws Exception { Boolean shouldRun = this.jc.entityShouldRun("QMS-990"); assertThat(shouldRun).isTrue(); } @Test - public void shouldReturnFalseIfEntitiyStatusDifferentFronRunnableStatuses() throws ExecutionException, InterruptedException, IOException { + public void shouldReturnFalseIfEntitiyStatusDifferentFronRunnableStatuses() throws Exception { System.setProperty("jira.valid.runnable.statuses", "READY,QA READY,DEPLOYED"); Boolean shouldRun = this.jc.entityShouldRun("QMS-990"); assertThat(shouldRun).isFalse(); System.clearProperty("jira.valid.runnable.statuses"); } - @Test - public void shouldTransitionEntityIfTestFails() { + public void shouldReturnAllPossibleTransitionsForEntity() throws Exception { + int transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "In Progress"); + assertThat(transitionID).isEqualTo(31); + transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "Backlog"); + assertThat(transitionID).isEqualTo(11); + transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "Done"); + assertThat(transitionID).isEqualTo(41); + } + @Test + public void shouldTransitionEntityToGivenStatus() throws Exception { + this.jc.transitionEntityToGivenStatus("QMS-990", "In Progress"); + String statusName = this.jc.getEntityStatus("QMS-990"); + assertThat(statusName).isEqualToIgnoringCase("In Progress"); + } + + @Test + public void shouldTransitionEntity() throws Exception { + this.jc.transitionEntity("QMS-990"); + String statusName = this.jc.getEntityStatus("QMS-990"); + assertThat(statusName).isEqualToIgnoringCase("In Progress"); + } + + @Test + public void shouldAddANewCommentToEntity() throws Exception { + this.jc.postCommentToEntity("QMS-990", "This is a test message"); } } diff --git a/src/test/resources/jira.properties b/src/test/resources/jira.properties index e3036dc8..c42d9ab6 100644 --- a/src/test/resources/jira.properties +++ b/src/test/resources/jira.properties @@ -1,3 +1,4 @@ jira.server.url=https://jira.vptech.eu jira.personal.access.token=NzA2NTkyMTgyMDY0OmNVPKWy7BIFOvR+76hB22d+Im4Q -jira.valid.runnable.statuses=READY,QA READY,DONE,DEPLOYED +jira.valid.runnable.statuses=Ready,QA Ready,Done,Deployed +jira.transition.if.fail=In Progress \ No newline at end of file From e5042f245ee8f960b87d03197f020ffd1416a90b Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Fri, 9 Jul 2021 14:25:08 +0200 Subject: [PATCH 03/12] Added defult values to several keys for jira functionality --- src/main/java/com/privalia/qa/utils/JiraConnector.java | 10 +++++++--- src/test/java/com/privalia/qa/ATests/JiraTagIT.java | 6 ++++++ src/test/resources/jira.properties | 5 +++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/privalia/qa/utils/JiraConnector.java b/src/main/java/com/privalia/qa/utils/JiraConnector.java index 69414816..58c7e1cb 100644 --- a/src/main/java/com/privalia/qa/utils/JiraConnector.java +++ b/src/main/java/com/privalia/qa/utils/JiraConnector.java @@ -52,7 +52,7 @@ public String getEntityStatus(String entity) throws Exception { public Boolean entityShouldRun(String entity) throws Exception { - String[] valid_statuses = this.getProperty("jira.valid.runnable.statuses").split(","); + String[] valid_statuses = this.getProperty("jira.valid.runnable.statuses:-Done,Deployed").split(","); String entity_current_status = this.getEntityStatus(entity).toUpperCase(); for (String status: valid_statuses) { @@ -140,8 +140,12 @@ public void postCommentToEntity(String entity, String message) throws Exception public void transitionEntity(String entity) throws Exception { - String jiraTransitionIfFail = this.getProperty("jira.transition.if.fail"); - this.transitionEntityToGivenStatus(entity, jiraTransitionIfFail); + String jiraTransitionToStatus = this.getProperty("jira.transition.if.fail.status:-In Progress"); + Boolean jiraTransition = Boolean.valueOf(this.getProperty("jira.transition.if.fail:-true")); + + if (jiraTransition) { + this.transitionEntityToGivenStatus(entity, jiraTransitionToStatus); + } } } diff --git a/src/test/java/com/privalia/qa/ATests/JiraTagIT.java b/src/test/java/com/privalia/qa/ATests/JiraTagIT.java index e64f6b3c..0846009b 100644 --- a/src/test/java/com/privalia/qa/ATests/JiraTagIT.java +++ b/src/test/java/com/privalia/qa/ATests/JiraTagIT.java @@ -30,6 +30,12 @@ public void shouldUseSystemVariableIfPresent() { assertThat(jiraServerURL).isEqualTo("http://dummy-server.com/"); } + @Test + public void shouldUseDefaultValueIfProvided() { + String jiraServerURL = this.jc.getProperty("not.real.key:-test"); + assertThat(jiraServerURL).isEqualTo("test"); + } + @Test public void shouldThrowExceptionIfVariableNotFound() { assertThatThrownBy(() -> { diff --git a/src/test/resources/jira.properties b/src/test/resources/jira.properties index c42d9ab6..41691b1d 100644 --- a/src/test/resources/jira.properties +++ b/src/test/resources/jira.properties @@ -1,4 +1,5 @@ jira.server.url=https://jira.vptech.eu jira.personal.access.token=NzA2NTkyMTgyMDY0OmNVPKWy7BIFOvR+76hB22d+Im4Q -jira.valid.runnable.statuses=Ready,QA Ready,Done,Deployed -jira.transition.if.fail=In Progress \ No newline at end of file +jira.valid.runnable.statuses=Done,Deployed +jira.transition.if.fail=true +jira.transition.if.fail.status=In Progress \ No newline at end of file From 3c7d0ef051aef4aa680feec50f57fa8ec0746910 Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Fri, 9 Jul 2021 14:45:40 +0200 Subject: [PATCH 04/12] Added basic javadoc --- .../com/privalia/qa/utils/JiraConnector.java | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/privalia/qa/utils/JiraConnector.java b/src/main/java/com/privalia/qa/utils/JiraConnector.java index 58c7e1cb..2508c059 100644 --- a/src/main/java/com/privalia/qa/utils/JiraConnector.java +++ b/src/main/java/com/privalia/qa/utils/JiraConnector.java @@ -9,6 +9,10 @@ import net.minidev.json.JSONArray; import java.util.concurrent.Future; +/** + * An small utility for interacting with entities in Jira + * @author José Fernández + */ public class JiraConnector { public static final String JIRA_PROPERTIES_FILE = "jira.properties"; @@ -17,6 +21,13 @@ public class JiraConnector { AsyncHttpClient client = new AsyncHttpClient(); + /** + * Reads the given key from the properties file. The system will try to locate the key first in + * the maven variables (System.getProperty) and if not found will look for it in the properties file. + * If the value is still not found it will return the default value (if provided) or an exception + * @param property key + * @return value + */ public String getProperty(String property) { interpolator.setEnableUndefinedVariableException(true); @@ -28,6 +39,12 @@ public String getProperty(String property) { return interpolator.replace("${properties:src/test/resources/" + JIRA_PROPERTIES_FILE + "::" + property + "}"); } + /** + * Retrieves the current entity status + * @param entity Entity identifier (i.e QMS-123) + * @return Status as string (i.e 'In Progress') + * @throws Exception Exception + */ public String getEntityStatus(String entity) throws Exception { String jiraURL = this.getProperty("jira.server.url"); @@ -49,7 +66,12 @@ public String getEntityStatus(String entity) throws Exception { return JsonPath.read(r.getResponseBody(),"$.fields.status.name").toString().toUpperCase(); } - + /** + * Determines if the entity status matches any of the expected statuses + * @param entity Entity identifier (i.e QMS-123) + * @return True if the entity status is within the expected statuses + * @throws Exception Exception + */ public Boolean entityShouldRun(String entity) throws Exception { String[] valid_statuses = this.getProperty("jira.valid.runnable.statuses:-Done,Deployed").split(","); @@ -64,6 +86,13 @@ public Boolean entityShouldRun(String entity) throws Exception { return false; } + /** + * Change the status of an entity to the Given Status by name. The status name should match exactly a valid + * status for that entity + * @param entity Entity identifier (i.e QMS-123) + * @param new_status New status (i.e 'Done') + * @throws Exception Exception + */ public void transitionEntityToGivenStatus(String entity, String new_status) throws Exception { int targetTransition = this.getTransitionIDForEntityByName(entity, new_status); @@ -88,6 +117,13 @@ public void transitionEntityToGivenStatus(String entity, String new_status) thro } + /** + * Gets the id of the transition by the given name + * @param entity Entity identifier (i.e QMS-123) + * @param transitionName Transition name (i.e 'In Progress') + * @return Id of the transition for that name + * @throws Exception Exception + */ public int getTransitionIDForEntityByName(String entity, String transitionName) throws Exception { String jiraURL = this.getProperty("jira.server.url"); @@ -117,6 +153,12 @@ public int getTransitionIDForEntityByName(String entity, String transitionName) } + /** + * Adds a new comment to the entity + * @param entity Entity identifier (i.e QMS-123) + * @param message Message to post + * @throws Exception Exception + */ public void postCommentToEntity(String entity, String message) throws Exception { String jiraURL = this.getProperty("jira.server.url"); @@ -138,6 +180,12 @@ public void postCommentToEntity(String entity, String message) throws Exception } } + /** + * Transition (change status) of the entity to the value provided in the properties file. Will + * default to "In Progress" is the value is not found + * @param entity Entity identifier (i.e QMS-123) + * @throws Exception Exception + */ public void transitionEntity(String entity) throws Exception { String jiraTransitionToStatus = this.getProperty("jira.transition.if.fail.status:-In Progress"); From f322def4c9f2ef7cfe9ff9b91a73d56e4be97705 Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Fri, 9 Jul 2021 14:47:15 +0200 Subject: [PATCH 05/12] Minor checkstyle fixes --- src/main/java/com/privalia/qa/utils/JiraConnector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/privalia/qa/utils/JiraConnector.java b/src/main/java/com/privalia/qa/utils/JiraConnector.java index 2508c059..4961d2bd 100644 --- a/src/main/java/com/privalia/qa/utils/JiraConnector.java +++ b/src/main/java/com/privalia/qa/utils/JiraConnector.java @@ -63,7 +63,7 @@ public String getEntityStatus(String entity) throws Exception { throw new Exception("Unexpected status code response:" + r.getStatusCode() + ". Body: '" + r.getResponseBody() + "'"); } - return JsonPath.read(r.getResponseBody(),"$.fields.status.name").toString().toUpperCase(); + return JsonPath.read(r.getResponseBody(), "$.fields.status.name").toString().toUpperCase(); } /** @@ -142,7 +142,7 @@ public int getTransitionIDForEntityByName(String entity, String transitionName) throw new Exception("Unexpected status code response:" + r.getStatusCode() + ". Body: '" + r.getResponseBody() + "'"); } - Object transitionStrings = JsonPath.read(r.getResponseBody(),"$.transitions[?(@.name=='" + transitionName + "')].id"); + Object transitionStrings = JsonPath.read(r.getResponseBody(), "$.transitions[?(@.name=='" + transitionName + "')].id"); JSONArray ja = (JSONArray) transitionStrings; if (ja.isEmpty()) { From 223b5611a3816c43073a5b3ae45778cc570b76f9 Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Fri, 9 Jul 2021 16:30:29 +0200 Subject: [PATCH 06/12] Added an aspect to control the execution of scenarios with @jira tag --- .../privalia/qa/aspects/JiraTagAspect.java | 69 ++++++++++++ .../com/privalia/qa/ATests/JiraTagIT.java | 105 ++---------------- .../com/privalia/qa/ATests/JiraTagTest.java | 99 +++++++++++++++++ src/test/resources/META-INF/aop.xml | 1 + src/test/resources/features/jiraTag.feature | 10 ++ 5 files changed, 188 insertions(+), 96 deletions(-) create mode 100644 src/main/java/com/privalia/qa/aspects/JiraTagAspect.java create mode 100644 src/test/java/com/privalia/qa/ATests/JiraTagTest.java create mode 100644 src/test/resources/features/jiraTag.feature diff --git a/src/main/java/com/privalia/qa/aspects/JiraTagAspect.java b/src/main/java/com/privalia/qa/aspects/JiraTagAspect.java new file mode 100644 index 00000000..eb07192d --- /dev/null +++ b/src/main/java/com/privalia/qa/aspects/JiraTagAspect.java @@ -0,0 +1,69 @@ +package com.privalia.qa.aspects; + +import com.privalia.qa.utils.JiraConnector; +import io.cucumber.testng.FeatureWrapper; +import io.cucumber.testng.PickleWrapper; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Aspect for managing the @jira() tag on a feature/scenario + */ +@Aspect +public class JiraTagAspect { + + private final Logger logger = LoggerFactory.getLogger(this.getClass().getCanonicalName()); + + /** + * Pointcut is executed for {@link io.cucumber.testng.AbstractTestNGCucumberTests#runScenario(PickleWrapper, FeatureWrapper)} + * @param pickleWrapper the pickleWrapper + * @param featureWrapper the featureWrapper + */ + @Pointcut("execution (void *.runScenario(..)) && args(pickleWrapper, featureWrapper)") + protected void jiraTagPointcutScenario(PickleWrapper pickleWrapper, FeatureWrapper featureWrapper) { + } + + @Around(value = "jiraTagPointcutScenario(pickleWrapper, featureWrapper)") + public void aroundJiraTagPointcut(ProceedingJoinPoint pjp, PickleWrapper pickleWrapper, FeatureWrapper featureWrapper) throws Throwable { + + List tags = pickleWrapper.getPickle().getTags(); + String scenarioName = pickleWrapper.getPickle().getName(); + + String ticket = this.getFirstTicketReference(tags); + + if (ticket != null) { + JiraConnector jc = new JiraConnector(); + if (!jc.entityShouldRun(ticket)) { + logger.warn("Scenario '" + scenarioName + "' was ignored, it is in a non runnable status in Jira."); + return; + } else { + pjp.proceed(); + } + + } else { + pjp.proceed(); + } + } + + private String getFirstTicketReference(List tags) { + String pattern = "@jira\\((.*)\\)"; + Pattern r = Pattern.compile(pattern); + + for (String tag: tags) { + Matcher m = r.matcher(tag); + if (m.find()) { + return m.group(1); + } + } + + return null; + } +} diff --git a/src/test/java/com/privalia/qa/ATests/JiraTagIT.java b/src/test/java/com/privalia/qa/ATests/JiraTagIT.java index 0846009b..1bf216ab 100644 --- a/src/test/java/com/privalia/qa/ATests/JiraTagIT.java +++ b/src/test/java/com/privalia/qa/ATests/JiraTagIT.java @@ -1,99 +1,12 @@ package com.privalia.qa.ATests; -import com.privalia.qa.utils.JiraConnector; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class JiraTagIT { - - JiraConnector jc = new JiraConnector(); - - @BeforeTest - public void setUp() throws Exception { - this.jc.transitionEntityToGivenStatus("QMS-990", "Done"); - String statusName = this.jc.getEntityStatus("QMS-990"); - assertThat(statusName).isEqualToIgnoringCase("Done"); - } - - @Test - public void shouldGetDataFromPropertiesFile() { - String jiraServerURL = this.jc.getProperty("jira.server.url"); - assertThat(jiraServerURL).isEqualTo("https://jira.vptech.eu"); - } - - @Test - public void shouldUseSystemVariableIfPresent() { - System.setProperty("jira.server.url", "http://dummy-server.com/"); - String jiraServerURL = this.jc.getProperty("jira.server.url"); - assertThat(jiraServerURL).isEqualTo("http://dummy-server.com/"); - } - - @Test - public void shouldUseDefaultValueIfProvided() { - String jiraServerURL = this.jc.getProperty("not.real.key:-test"); - assertThat(jiraServerURL).isEqualTo("test"); - } - - @Test - public void shouldThrowExceptionIfVariableNotFound() { - assertThatThrownBy(() -> { - this.jc.getProperty("not.real.property.key"); - }).isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Cannot resolve variable"); - } - - - @Test - public void shouldRetrieveCorrectEntityStatus() throws Exception { - String statusName = this.jc.getEntityStatus("QMS-990"); - assertThat(statusName).isEqualToIgnoringCase("Done"); - } - - @Test - public void shouldReturnTrueIfEntitiyStatusMatchesRunnableStatuses() throws Exception { - Boolean shouldRun = this.jc.entityShouldRun("QMS-990"); - assertThat(shouldRun).isTrue(); - } - - @Test - public void shouldReturnFalseIfEntitiyStatusDifferentFronRunnableStatuses() throws Exception { - System.setProperty("jira.valid.runnable.statuses", "READY,QA READY,DEPLOYED"); - Boolean shouldRun = this.jc.entityShouldRun("QMS-990"); - assertThat(shouldRun).isFalse(); - System.clearProperty("jira.valid.runnable.statuses"); - } - - @Test - public void shouldReturnAllPossibleTransitionsForEntity() throws Exception { - int transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "In Progress"); - assertThat(transitionID).isEqualTo(31); - - transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "Backlog"); - assertThat(transitionID).isEqualTo(11); - - transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "Done"); - assertThat(transitionID).isEqualTo(41); - } - - - @Test - public void shouldTransitionEntityToGivenStatus() throws Exception { - this.jc.transitionEntityToGivenStatus("QMS-990", "In Progress"); - String statusName = this.jc.getEntityStatus("QMS-990"); - assertThat(statusName).isEqualToIgnoringCase("In Progress"); - } - - @Test - public void shouldTransitionEntity() throws Exception { - this.jc.transitionEntity("QMS-990"); - String statusName = this.jc.getEntityStatus("QMS-990"); - assertThat(statusName).isEqualToIgnoringCase("In Progress"); - } - - @Test - public void shouldAddANewCommentToEntity() throws Exception { - this.jc.postCommentToEntity("QMS-990", "This is a test message"); - } +import com.privalia.qa.utils.BaseGTest; +import io.cucumber.testng.CucumberOptions; + +@CucumberOptions( + features = { + "src/test/resources/features/jiraTag.feature", + }, + glue = "com.privalia.qa.specs") +public class JiraTagIT extends BaseGTest { } diff --git a/src/test/java/com/privalia/qa/ATests/JiraTagTest.java b/src/test/java/com/privalia/qa/ATests/JiraTagTest.java new file mode 100644 index 00000000..56e634d4 --- /dev/null +++ b/src/test/java/com/privalia/qa/ATests/JiraTagTest.java @@ -0,0 +1,99 @@ +package com.privalia.qa.ATests; + +import com.privalia.qa.utils.JiraConnector; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class JiraTagTest { + + JiraConnector jc = new JiraConnector(); + + @BeforeTest + public void setUp() throws Exception { + this.jc.transitionEntityToGivenStatus("QMS-990", "Done"); + String statusName = this.jc.getEntityStatus("QMS-990"); + assertThat(statusName).isEqualToIgnoringCase("Done"); + } + + @Test + public void shouldGetDataFromPropertiesFile() { + String jiraServerURL = this.jc.getProperty("jira.server.url"); + assertThat(jiraServerURL).isEqualTo("https://jira.vptech.eu"); + } + + @Test + public void shouldUseSystemVariableIfPresent() { + System.setProperty("jira.server.url", "http://dummy-server.com/"); + String jiraServerURL = this.jc.getProperty("jira.server.url"); + assertThat(jiraServerURL).isEqualTo("http://dummy-server.com/"); + } + + @Test + public void shouldUseDefaultValueIfProvided() { + String jiraServerURL = this.jc.getProperty("not.real.key:-test"); + assertThat(jiraServerURL).isEqualTo("test"); + } + + @Test + public void shouldThrowExceptionIfVariableNotFound() { + assertThatThrownBy(() -> { + this.jc.getProperty("not.real.property.key"); + }).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Cannot resolve variable"); + } + + + @Test(enabled = false) + public void shouldRetrieveCorrectEntityStatus() throws Exception { + String statusName = this.jc.getEntityStatus("QMS-990"); + assertThat(statusName).isEqualToIgnoringCase("Done"); + } + + @Test(enabled = false) + public void shouldReturnTrueIfEntitiyStatusMatchesRunnableStatuses() throws Exception { + Boolean shouldRun = this.jc.entityShouldRun("QMS-990"); + assertThat(shouldRun).isTrue(); + } + + @Test(enabled = false) + public void shouldReturnFalseIfEntitiyStatusDifferentFronRunnableStatuses() throws Exception { + System.setProperty("jira.valid.runnable.statuses", "READY,QA READY,DEPLOYED"); + Boolean shouldRun = this.jc.entityShouldRun("QMS-990"); + assertThat(shouldRun).isFalse(); + System.clearProperty("jira.valid.runnable.statuses"); + } + + @Test(enabled = false) + public void shouldReturnAllPossibleTransitionsForEntity() throws Exception { + int transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "In Progress"); + assertThat(transitionID).isEqualTo(31); + + transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "Backlog"); + assertThat(transitionID).isEqualTo(11); + + transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "Done"); + assertThat(transitionID).isEqualTo(41); + } + + + @Test(enabled = false) + public void shouldTransitionEntityToGivenStatus() throws Exception { + this.jc.transitionEntityToGivenStatus("QMS-990", "In Progress"); + String statusName = this.jc.getEntityStatus("QMS-990"); + assertThat(statusName).isEqualToIgnoringCase("In Progress"); + } + + @Test(enabled = false) + public void shouldTransitionEntity() throws Exception { + this.jc.transitionEntity("QMS-990"); + String statusName = this.jc.getEntityStatus("QMS-990"); + assertThat(statusName).isEqualToIgnoringCase("In Progress"); + } + + @Test(enabled = false) + public void shouldAddANewCommentToEntity() throws Exception { + this.jc.postCommentToEntity("QMS-990", "This is a test message"); + } +} diff --git a/src/test/resources/META-INF/aop.xml b/src/test/resources/META-INF/aop.xml index 8a31b7bc..5f55be6f 100644 --- a/src/test/resources/META-INF/aop.xml +++ b/src/test/resources/META-INF/aop.xml @@ -22,6 +22,7 @@ + diff --git a/src/test/resources/features/jiraTag.feature b/src/test/resources/features/jiraTag.feature new file mode 100644 index 00000000..1d3c2020 --- /dev/null +++ b/src/test/resources/features/jiraTag.feature @@ -0,0 +1,10 @@ +Feature: Testing the Jira Integration + + Using the @jira() you can control the execution of scenarios based on its status + in Jira as well as update an entity status in Jira based on the result of + the scenario execution. You can fully configure the behavior of this tag using the + configuration file located at src/test/resources/jira.properties + + @jira(QMS-990) + Scenario: This scenario should run only if ticket QMS-990 is Done or Deployed status + Given I wait '1' seconds \ No newline at end of file From ae614ccd08d3f658021e71334c79be11a9bfd64b Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Fri, 9 Jul 2021 17:23:17 +0200 Subject: [PATCH 07/12] Added an after hook to change the status of the ticket if the Scenario fails --- .../privalia/qa/aspects/JiraTagAspect.java | 31 +++++++------------ .../java/com/privalia/qa/specs/HookGSpec.java | 28 +++++++++++++++-- .../com/privalia/qa/utils/JiraConnector.java | 23 ++++++++++++++ .../com/privalia/qa/ATests/JiraTagTest.java | 10 ++++++ src/test/resources/features/jiraTag.feature | 4 ++- 5 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/privalia/qa/aspects/JiraTagAspect.java b/src/main/java/com/privalia/qa/aspects/JiraTagAspect.java index eb07192d..a49b57f4 100644 --- a/src/main/java/com/privalia/qa/aspects/JiraTagAspect.java +++ b/src/main/java/com/privalia/qa/aspects/JiraTagAspect.java @@ -22,6 +22,8 @@ public class JiraTagAspect { private final Logger logger = LoggerFactory.getLogger(this.getClass().getCanonicalName()); + JiraConnector jc = new JiraConnector(); + /** * Pointcut is executed for {@link io.cucumber.testng.AbstractTestNGCucumberTests#runScenario(PickleWrapper, FeatureWrapper)} * @param pickleWrapper the pickleWrapper @@ -37,14 +39,18 @@ public void aroundJiraTagPointcut(ProceedingJoinPoint pjp, PickleWrapper pickleW List tags = pickleWrapper.getPickle().getTags(); String scenarioName = pickleWrapper.getPickle().getName(); - String ticket = this.getFirstTicketReference(tags); + String ticket = this.jc.getFirstTicketReference(tags); if (ticket != null) { - JiraConnector jc = new JiraConnector(); - if (!jc.entityShouldRun(ticket)) { - logger.warn("Scenario '" + scenarioName + "' was ignored, it is in a non runnable status in Jira."); - return; - } else { + try { + if (!jc.entityShouldRun(ticket)) { + logger.warn("Scenario '" + scenarioName + "' was ignored, it is in a non runnable status in Jira."); + return; + } else { + pjp.proceed(); + } + } catch (Exception e) { + logger.warn("Could not retrieve info of ticket " + ticket + " from jira: " + e.getMessage() + ". Proceeding with execution..."); pjp.proceed(); } @@ -53,17 +59,4 @@ public void aroundJiraTagPointcut(ProceedingJoinPoint pjp, PickleWrapper pickleW } } - private String getFirstTicketReference(List tags) { - String pattern = "@jira\\((.*)\\)"; - Pattern r = Pattern.compile(pattern); - - for (String tag: tags) { - Matcher m = r.matcher(tag); - if (m.find()) { - return m.group(1); - } - } - - return null; - } } diff --git a/src/main/java/com/privalia/qa/specs/HookGSpec.java b/src/main/java/com/privalia/qa/specs/HookGSpec.java index 7a35acec..f589a603 100644 --- a/src/main/java/com/privalia/qa/specs/HookGSpec.java +++ b/src/main/java/com/privalia/qa/specs/HookGSpec.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.AsyncHttpClientConfig; +import com.privalia.qa.utils.JiraConnector; import com.privalia.qa.utils.ThreadProperty; import io.appium.java_client.MobileDriver; import io.appium.java_client.android.AndroidDriver; @@ -51,6 +52,9 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -77,6 +81,7 @@ public class HookGSpec extends BaseGSpec { private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(HookGSpec.class); + JiraConnector jiraConnector = new JiraConnector(); protected WebDriver driver; @@ -472,10 +477,28 @@ public void restClientTeardown() { } /** - * Disconnect any remaining open SSH connection after each scenario is completed + * Will check if the scenario contains any reference to a Jira ticket and will try to update + * its status based on the result of the scenario execution. It will also try to close any remaining + * SSH connection */ @After(order = 10) - public void remoteSSHConnectionTeardown() { + public void teardown(Scenario scenario) throws Exception { + + if (scenario.isFailed()) { + Collection tags = scenario.getSourceTagNames(); + String ticket = this.jiraConnector.getFirstTicketReference(new ArrayList(tags)); + + if (ticket != null) { + try { + commonspec.getLogger().debug("Updating ticket " + ticket + " in Jira..."); + this.jiraConnector.transitionEntity(ticket); + this.jiraConnector.postCommentToEntity(ticket, "Scenario '" + scenario.getName() + "' failed at: " + scenario.getId()); + } catch (Exception e) { + commonspec.getLogger().warn("Could not change the status of entity " + ticket + " in jira: " + e.getMessage()); + } + } + } + if (commonspec.getRemoteSSHConnection() != null) { commonspec.getLogger().debug("Closing SSH remote connection"); commonspec.getRemoteSSHConnection().getSession().disconnect(); @@ -494,4 +517,5 @@ public void sqlConnectionClose() throws Exception { commonspec.getSqlClient().disconnect(); } } + } diff --git a/src/main/java/com/privalia/qa/utils/JiraConnector.java b/src/main/java/com/privalia/qa/utils/JiraConnector.java index 4961d2bd..0ddcc9a8 100644 --- a/src/main/java/com/privalia/qa/utils/JiraConnector.java +++ b/src/main/java/com/privalia/qa/utils/JiraConnector.java @@ -7,7 +7,11 @@ import com.ning.http.client.Response; import org.apache.commons.text.StringSubstitutor; import net.minidev.json.JSONArray; + +import java.util.List; import java.util.concurrent.Future; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * An small utility for interacting with entities in Jira @@ -196,4 +200,23 @@ public void transitionEntity(String entity) throws Exception { } } + + /** + * Returns the first reference to the jira ticket from the tag reference + * @param tags List of thats (i.e @ignore, @jira(QMS-123)) + * @return The first ticket reference (i.e QMS-123) + */ + public String getFirstTicketReference(List tags) { + String pattern = "@jira\\((.*)\\)"; + Pattern r = Pattern.compile(pattern); + + for (String tag: tags) { + Matcher m = r.matcher(tag); + if (m.find()) { + return m.group(1); + } + } + + return null; + } } diff --git a/src/test/java/com/privalia/qa/ATests/JiraTagTest.java b/src/test/java/com/privalia/qa/ATests/JiraTagTest.java index 56e634d4..64762f28 100644 --- a/src/test/java/com/privalia/qa/ATests/JiraTagTest.java +++ b/src/test/java/com/privalia/qa/ATests/JiraTagTest.java @@ -3,6 +3,10 @@ import com.privalia.qa.utils.JiraConnector; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -96,4 +100,10 @@ public void shouldTransitionEntity() throws Exception { public void shouldAddANewCommentToEntity() throws Exception { this.jc.postCommentToEntity("QMS-990", "This is a test message"); } + + @Test + public void shouldReturnTheTicketFromTheTag() throws Exception { + List tags = Arrays.asList("@jira(QMS-990)", "@ignore", "@jira(QMS-123)"); + assertThat("QMS-990").isEqualToIgnoringCase(this.jc.getFirstTicketReference(tags)); + } } diff --git a/src/test/resources/features/jiraTag.feature b/src/test/resources/features/jiraTag.feature index 1d3c2020..5b3c7b3c 100644 --- a/src/test/resources/features/jiraTag.feature +++ b/src/test/resources/features/jiraTag.feature @@ -1,3 +1,4 @@ +@ignore Feature: Testing the Jira Integration Using the @jira() you can control the execution of scenarios based on its status @@ -7,4 +8,5 @@ Feature: Testing the Jira Integration @jira(QMS-990) Scenario: This scenario should run only if ticket QMS-990 is Done or Deployed status - Given I wait '1' seconds \ No newline at end of file + Given I wait '1' seconds + Then 'a' matches 'b' \ No newline at end of file From e5850af1e24b06ff991e81c11dec56bbb6a53a81 Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Fri, 9 Jul 2021 17:41:56 +0200 Subject: [PATCH 08/12] Minor changes in Javadoc --- src/main/java/com/privalia/qa/specs/HookGSpec.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/privalia/qa/specs/HookGSpec.java b/src/main/java/com/privalia/qa/specs/HookGSpec.java index f589a603..b8b38a96 100644 --- a/src/main/java/com/privalia/qa/specs/HookGSpec.java +++ b/src/main/java/com/privalia/qa/specs/HookGSpec.java @@ -480,9 +480,10 @@ public void restClientTeardown() { * Will check if the scenario contains any reference to a Jira ticket and will try to update * its status based on the result of the scenario execution. It will also try to close any remaining * SSH connection + * @param scenario Scenario */ @After(order = 10) - public void teardown(Scenario scenario) throws Exception { + public void teardown(Scenario scenario) { if (scenario.isFailed()) { Collection tags = scenario.getSourceTagNames(); From 4f339bf9a31871b898e148d1a8878732647aaee9 Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Mon, 12 Jul 2021 09:33:29 +0200 Subject: [PATCH 09/12] Minor changes in default values for jira integration values --- src/main/java/com/privalia/qa/utils/JiraConnector.java | 2 +- src/test/resources/features/jiraTag.feature | 2 +- src/test/resources/jira.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/privalia/qa/utils/JiraConnector.java b/src/main/java/com/privalia/qa/utils/JiraConnector.java index 0ddcc9a8..3a851692 100644 --- a/src/main/java/com/privalia/qa/utils/JiraConnector.java +++ b/src/main/java/com/privalia/qa/utils/JiraConnector.java @@ -192,7 +192,7 @@ public void postCommentToEntity(String entity, String message) throws Exception */ public void transitionEntity(String entity) throws Exception { - String jiraTransitionToStatus = this.getProperty("jira.transition.if.fail.status:-In Progress"); + String jiraTransitionToStatus = this.getProperty("jira.transition.if.fail.status:-TO REVIEW"); Boolean jiraTransition = Boolean.valueOf(this.getProperty("jira.transition.if.fail:-true")); if (jiraTransition) { diff --git a/src/test/resources/features/jiraTag.feature b/src/test/resources/features/jiraTag.feature index 5b3c7b3c..c128a4e0 100644 --- a/src/test/resources/features/jiraTag.feature +++ b/src/test/resources/features/jiraTag.feature @@ -1,4 +1,4 @@ -@ignore + Feature: Testing the Jira Integration Using the @jira() you can control the execution of scenarios based on its status diff --git a/src/test/resources/jira.properties b/src/test/resources/jira.properties index 41691b1d..1980b7b2 100644 --- a/src/test/resources/jira.properties +++ b/src/test/resources/jira.properties @@ -2,4 +2,4 @@ jira.server.url=https://jira.vptech.eu jira.personal.access.token=NzA2NTkyMTgyMDY0OmNVPKWy7BIFOvR+76hB22d+Im4Q jira.valid.runnable.statuses=Done,Deployed jira.transition.if.fail=true -jira.transition.if.fail.status=In Progress \ No newline at end of file +jira.transition.if.fail.status=TO REVIEW \ No newline at end of file From 4a8163c5efdd6cd3c76b65d5c003f0307ffe95ca Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Mon, 12 Jul 2021 10:25:37 +0200 Subject: [PATCH 10/12] Fixed a problem when returning default values in jiraconnector. Removed unit tests for private methods --- .../com/privalia/qa/utils/JiraConnector.java | 40 ++++++----- .../com/privalia/qa/ATests/JiraTagTest.java | 69 ++----------------- 2 files changed, 27 insertions(+), 82 deletions(-) diff --git a/src/main/java/com/privalia/qa/utils/JiraConnector.java b/src/main/java/com/privalia/qa/utils/JiraConnector.java index 3a851692..7534a70b 100644 --- a/src/main/java/com/privalia/qa/utils/JiraConnector.java +++ b/src/main/java/com/privalia/qa/utils/JiraConnector.java @@ -5,8 +5,10 @@ import com.ning.http.client.Request; import com.ning.http.client.RequestBuilder; import com.ning.http.client.Response; +import com.privalia.qa.lookups.DefaultLookUp; import org.apache.commons.text.StringSubstitutor; import net.minidev.json.JSONArray; +import org.apache.commons.text.lookup.StringLookupFactory; import java.util.List; import java.util.concurrent.Future; @@ -32,14 +34,18 @@ public class JiraConnector { * @param property key * @return value */ - public String getProperty(String property) { - - interpolator.setEnableUndefinedVariableException(true); + private String getProperty(String property, String defaultValue) { if (System.getProperty(property) != null) { return System.getProperty(property); } + interpolator.setEnableUndefinedVariableException(true); + + if (defaultValue != null) { + property = property + ":-" + defaultValue; + } + return interpolator.replace("${properties:src/test/resources/" + JIRA_PROPERTIES_FILE + "::" + property + "}"); } @@ -49,10 +55,10 @@ public String getProperty(String property) { * @return Status as string (i.e 'In Progress') * @throws Exception Exception */ - public String getEntityStatus(String entity) throws Exception { + private String getEntityStatus(String entity) throws Exception { - String jiraURL = this.getProperty("jira.server.url"); - String jiraToken = this.getProperty("jira.personal.access.token"); + String jiraURL = this.getProperty("jira.server.url", null); + String jiraToken = this.getProperty("jira.personal.access.token", null); Request getRequest = new RequestBuilder() .setMethod("GET") @@ -78,7 +84,7 @@ public String getEntityStatus(String entity) throws Exception { */ public Boolean entityShouldRun(String entity) throws Exception { - String[] valid_statuses = this.getProperty("jira.valid.runnable.statuses:-Done,Deployed").split(","); + String[] valid_statuses = this.getProperty("jira.valid.runnable.statuses", "Done,Deployed").split(","); String entity_current_status = this.getEntityStatus(entity).toUpperCase(); for (String status: valid_statuses) { @@ -97,12 +103,12 @@ public Boolean entityShouldRun(String entity) throws Exception { * @param new_status New status (i.e 'Done') * @throws Exception Exception */ - public void transitionEntityToGivenStatus(String entity, String new_status) throws Exception { + private void transitionEntityToGivenStatus(String entity, String new_status) throws Exception { int targetTransition = this.getTransitionIDForEntityByName(entity, new_status); - String jiraURL = this.getProperty("jira.server.url"); - String jiraToken = this.getProperty("jira.personal.access.token"); + String jiraURL = this.getProperty("jira.server.url", ""); + String jiraToken = this.getProperty("jira.personal.access.token", ""); Request postRequest = new RequestBuilder() .setMethod("POST") @@ -128,10 +134,10 @@ public void transitionEntityToGivenStatus(String entity, String new_status) thro * @return Id of the transition for that name * @throws Exception Exception */ - public int getTransitionIDForEntityByName(String entity, String transitionName) throws Exception { + private int getTransitionIDForEntityByName(String entity, String transitionName) throws Exception { - String jiraURL = this.getProperty("jira.server.url"); - String jiraToken = this.getProperty("jira.personal.access.token"); + String jiraURL = this.getProperty("jira.server.url", ""); + String jiraToken = this.getProperty("jira.personal.access.token", ""); Request getRequest = new RequestBuilder() .setMethod("GET") @@ -165,8 +171,8 @@ public int getTransitionIDForEntityByName(String entity, String transitionName) */ public void postCommentToEntity(String entity, String message) throws Exception { - String jiraURL = this.getProperty("jira.server.url"); - String jiraToken = this.getProperty("jira.personal.access.token"); + String jiraURL = this.getProperty("jira.server.url", ""); + String jiraToken = this.getProperty("jira.personal.access.token", ""); Request postRequest = new RequestBuilder() .setMethod("POST") @@ -192,8 +198,8 @@ public void postCommentToEntity(String entity, String message) throws Exception */ public void transitionEntity(String entity) throws Exception { - String jiraTransitionToStatus = this.getProperty("jira.transition.if.fail.status:-TO REVIEW"); - Boolean jiraTransition = Boolean.valueOf(this.getProperty("jira.transition.if.fail:-true")); + String jiraTransitionToStatus = this.getProperty("jira.transition.if.fail.status", "TO REVIEW"); + Boolean jiraTransition = Boolean.valueOf(this.getProperty("jira.transition.if.fail", "true")); if (jiraTransition) { this.transitionEntityToGivenStatus(entity, jiraTransitionToStatus); diff --git a/src/test/java/com/privalia/qa/ATests/JiraTagTest.java b/src/test/java/com/privalia/qa/ATests/JiraTagTest.java index 64762f28..5fec350f 100644 --- a/src/test/java/com/privalia/qa/ATests/JiraTagTest.java +++ b/src/test/java/com/privalia/qa/ATests/JiraTagTest.java @@ -8,7 +8,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; + public class JiraTagTest { @@ -16,43 +16,9 @@ public class JiraTagTest { @BeforeTest public void setUp() throws Exception { - this.jc.transitionEntityToGivenStatus("QMS-990", "Done"); - String statusName = this.jc.getEntityStatus("QMS-990"); - assertThat(statusName).isEqualToIgnoringCase("Done"); - } - - @Test - public void shouldGetDataFromPropertiesFile() { - String jiraServerURL = this.jc.getProperty("jira.server.url"); - assertThat(jiraServerURL).isEqualTo("https://jira.vptech.eu"); - } - - @Test - public void shouldUseSystemVariableIfPresent() { - System.setProperty("jira.server.url", "http://dummy-server.com/"); - String jiraServerURL = this.jc.getProperty("jira.server.url"); - assertThat(jiraServerURL).isEqualTo("http://dummy-server.com/"); - } - - @Test - public void shouldUseDefaultValueIfProvided() { - String jiraServerURL = this.jc.getProperty("not.real.key:-test"); - assertThat(jiraServerURL).isEqualTo("test"); - } - - @Test - public void shouldThrowExceptionIfVariableNotFound() { - assertThatThrownBy(() -> { - this.jc.getProperty("not.real.property.key"); - }).isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("Cannot resolve variable"); - } - - - @Test(enabled = false) - public void shouldRetrieveCorrectEntityStatus() throws Exception { - String statusName = this.jc.getEntityStatus("QMS-990"); - assertThat(statusName).isEqualToIgnoringCase("Done"); + System.setProperty("jira.transition.if.fail.status", "Done"); + this.jc.transitionEntity("QMS-990"); + System.clearProperty("jira.transition.if.fail.status"); } @Test(enabled = false) @@ -69,33 +35,6 @@ public void shouldReturnFalseIfEntitiyStatusDifferentFronRunnableStatuses() thro System.clearProperty("jira.valid.runnable.statuses"); } - @Test(enabled = false) - public void shouldReturnAllPossibleTransitionsForEntity() throws Exception { - int transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "In Progress"); - assertThat(transitionID).isEqualTo(31); - - transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "Backlog"); - assertThat(transitionID).isEqualTo(11); - - transitionID = this.jc.getTransitionIDForEntityByName("QMS-990", "Done"); - assertThat(transitionID).isEqualTo(41); - } - - - @Test(enabled = false) - public void shouldTransitionEntityToGivenStatus() throws Exception { - this.jc.transitionEntityToGivenStatus("QMS-990", "In Progress"); - String statusName = this.jc.getEntityStatus("QMS-990"); - assertThat(statusName).isEqualToIgnoringCase("In Progress"); - } - - @Test(enabled = false) - public void shouldTransitionEntity() throws Exception { - this.jc.transitionEntity("QMS-990"); - String statusName = this.jc.getEntityStatus("QMS-990"); - assertThat(statusName).isEqualToIgnoringCase("In Progress"); - } - @Test(enabled = false) public void shouldAddANewCommentToEntity() throws Exception { this.jc.postCommentToEntity("QMS-990", "This is a test message"); From c8cb8d451777cf594a18fab4c683909a811b8c3e Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Mon, 12 Jul 2021 10:33:48 +0200 Subject: [PATCH 11/12] Added minor unit test --- src/test/java/com/privalia/qa/ATests/JiraTagTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/privalia/qa/ATests/JiraTagTest.java b/src/test/java/com/privalia/qa/ATests/JiraTagTest.java index 5fec350f..f4879f61 100644 --- a/src/test/java/com/privalia/qa/ATests/JiraTagTest.java +++ b/src/test/java/com/privalia/qa/ATests/JiraTagTest.java @@ -8,13 +8,14 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class JiraTagTest { JiraConnector jc = new JiraConnector(); - @BeforeTest + @BeforeTest(enabled = false) public void setUp() throws Exception { System.setProperty("jira.transition.if.fail.status", "Done"); this.jc.transitionEntity("QMS-990"); @@ -40,6 +41,14 @@ public void shouldAddANewCommentToEntity() throws Exception { this.jc.postCommentToEntity("QMS-990", "This is a test message"); } + @Test(enabled = false) + public void shouldReturnExceptionIkFeyVariablesNotFound() { + assertThatThrownBy(() -> { + System.setProperty("jira.server.url", null); + this.jc.transitionEntity("QMS-990"); + }).isInstanceOf(NullPointerException.class); + } + @Test public void shouldReturnTheTicketFromTheTag() throws Exception { List tags = Arrays.asList("@jira(QMS-990)", "@ignore", "@jira(QMS-123)"); From 70df537d2cdaa65d12d42fadc0aeef3436be05eb Mon Sep 17 00:00:00 2001 From: "jose.fernandez" Date: Mon, 12 Jul 2021 11:01:57 +0200 Subject: [PATCH 12/12] Added brief documentation in jira.properties file, improved feature file and removed private credentials --- src/test/resources/features/jiraTag.feature | 11 ++++++---- src/test/resources/jira.properties | 24 +++++++++++++++++++-- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/test/resources/features/jiraTag.feature b/src/test/resources/features/jiraTag.feature index c128a4e0..30c74bfc 100644 --- a/src/test/resources/features/jiraTag.feature +++ b/src/test/resources/features/jiraTag.feature @@ -1,4 +1,4 @@ - +@ignore Feature: Testing the Jira Integration Using the @jira() you can control the execution of scenarios based on its status @@ -7,6 +7,9 @@ Feature: Testing the Jira Integration configuration file located at src/test/resources/jira.properties @jira(QMS-990) - Scenario: This scenario should run only if ticket QMS-990 is Done or Deployed status - Given I wait '1' seconds - Then 'a' matches 'b' \ No newline at end of file + Scenario: A new element is inserted via a POST call + Given I send requests to '${REST_SERVER_HOST}:3000' + When I send a 'POST' request to '/posts' based on 'schemas/mytestdata.json' as 'json' + Then the service response status must be '201' + And I save element '$.title' in environment variable 'TITLE' + Then '${TITLE}' matches 'This is a test' \ No newline at end of file diff --git a/src/test/resources/jira.properties b/src/test/resources/jira.properties index 1980b7b2..fb1ce15f 100644 --- a/src/test/resources/jira.properties +++ b/src/test/resources/jira.properties @@ -1,5 +1,25 @@ -jira.server.url=https://jira.vptech.eu -jira.personal.access.token=NzA2NTkyMTgyMDY0OmNVPKWy7BIFOvR+76hB22d+Im4Q +# The Jira connector allows you to annotate your scenarios with a reference to an entity +# in Jira, for example @jira(QMS-123) +# +# This will allow you to control the execution of scenarios based on the status +# of the linked entity in Jira as well as to update the status of the entity +# in Jira if the Scenario fails. You must create a file like this one under +# src/test/resources/jira.properties +# +# All variables can also be set with maven variables (i.e -Djira.personal.access.token=abcdefg123456) +# in case you would like to obfuscate them for privacy reasons + +# Base URL of the Jira server +jira.server.url=https://my.jira.server + +# Personal authentication token +jira.personal.access.token=abcdefg123456 + +# Will run the scenario only if the linked Jira entity is in one of the given statuses (if not specified, defaults to 'Done,Deployed') jira.valid.runnable.statuses=Done,Deployed + +# Change linked Jira entity status if scenario fails (if not specified, defaults to 'true') jira.transition.if.fail=true + +# If jira.transition.if.fail=true, the linked Jira entity will transition to this status (if not specified, defaults to 'TO REVIEW') jira.transition.if.fail.status=TO REVIEW \ No newline at end of file