Java tutorial
package com.epam.gepard.rest.jira; /*========================================================================== Copyright 2004-2015 EPAM Systems This file is part of Gepard. Gepard is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gepard is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Gepard. If not, see <http://www.gnu.org/licenses/>. ===========================================================================*/ import com.epam.gepard.common.Environment; import com.epam.gepard.exception.SimpleGepardException; import com.epam.gepard.generic.GepardTestClass; import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.HttpMethod; import com.gargoylesoftware.htmlunit.UnexpectedPage; import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebRequest; import org.apache.xerces.impl.dv.util.Base64; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.junit.Assert; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; /** * This helper class provides JIRA Rest API connectivity features to Gepard. * * @author Tamas_Kohegyi */ public class JiraSiteHandler { private static final int HTTP_RESPONSE_OK = 200; private static final String STATUS_CHANGE_TEXT = "\"field\":\"status\""; private final Environment environment; private final WebClient webClient; /** * Constructor of the Jira Site Handler object. * * @param tc is the caller test case * @param environment provide the Environment object of Gepard, it must contain connectivity info to JIRA. */ public JiraSiteHandler(final GepardTestClass tc, final Environment environment) { this.environment = environment; webClient = new WebClient(); webClient.getOptions().setJavaScriptEnabled(false); webClient.getOptions().setThrowExceptionOnScriptError(false); webClient.getOptions().setUseInsecureSSL(true); String phrase = Base64.encode((environment.getProperty(Environment.JIRA_SITE_USERNAME) + ":" + environment.getProperty(Environment.JIRA_SITE_PASSWORD)).getBytes()); webClient.addRequestHeader("Authorization", "Basic " + phrase); try { JSONObject serverInfo = getJiraServerInfo(); String serverTitle = serverInfo.get("serverTitle").toString(); String version = serverInfo.get("version").toString(); tc.logComment("Connected to JIRA Server: \"" + serverTitle + "\", version: " + version); } catch (FailingHttpStatusCodeException | IOException | JSONException e) { throw new SimpleGepardException( "Cannot connect to JIRA properly, pls check the settings, reason: " + e.getMessage(), e); } } private String getJiraBaseUrl() { //in general: http://jira.blah.com return environment.getProperty(Environment.JIRA_SITE_URL); } private String getServerInfoUrl() { //http://jira.blah.com/rest/api/2/serverInfo return getJiraBaseUrl() + "/rest/api/2/serverInfo"; } /** * Build a URL to jira to get change history of a ticket. * * @param jiraTicket is the jira ticket id, like : BLAH-2345 * @return with the rest api url to jira */ private String getIssueChangeLogUrl(final String jiraTicket) { //in general: http://jira.blah.com/rest/api/2/issue/ISSUE-ID?expand=changelog return getJiraBaseUrl() + "/rest/api/2/issue/" + jiraTicket + "?expand=changelog"; } private String getIssueFieldValueUrl(final String jiraTicket) { //in general: http://jira.blah.com/rest/api/2/issue/ISSUE-ID?expand=projects.issuetypes.fields return getJiraBaseUrl() + "/rest/api/2/issue/" + jiraTicket + "?expand=projects.issuetypes.fields"; } private String getIssueFieldSetValueUrl(String ticket) { return getJiraBaseUrl() + "/rest/api/2/issue/" + ticket; } private String getIssueTransitionsUrl(String ticket) { return getJiraBaseUrl() + "/rest/api/2/issue/" + ticket + "/transitions"; } /** * URL --> https://jira.xxxxxx.com/rest/api/2/issue/TEST-53/transitions. */ private String getIssueSetTransitionsUrl(String ticket) { return getJiraBaseUrl() + "/rest/api/2/issue/" + ticket + "/transitions?expand=transitions.fields"; } private String getListOfIssuesByQueryUrl(final String query) { return getJiraBaseUrl() + "/rest/api/2/search?jql=" + makeJiraUrlFriendly(query) + "&maxResults=1000"; } private String makeJiraUrlFriendly(final String query) { try { return URLEncoder.encode(query, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new SimpleGepardException("Specified JIRA Query: \"" + query + "\" cannot be encoded to URL.", e); } } private String getTicketHistory(final GepardTestClass tc, final String ticketID) throws IOException { tc.logComment("Get JIRA history info on ticket: " + ticketID); String jiraInfoPage = getIssueChangeLogUrl(ticketID); UnexpectedPage infoPage = webClient.getPage(jiraInfoPage); String ticketInfo = infoPage.getWebResponse().getContentAsString(); return ticketInfo; } /** * Gets the main Jira Server Information. * * @return with JSON object with Jira Server Information * @throws IOException in case of problem * @throws JSONException in case of problem */ public JSONObject getJiraServerInfo() throws IOException, JSONException { String jiraServerInfoPage = getServerInfoUrl(); UnexpectedPage infoPage = webClient.getPage(jiraServerInfoPage); String serverInfo = infoPage.getWebResponse().getContentAsString(); JSONObject obj = new JSONObject(serverInfo); return obj; } /** * Determine the date when a ticket status reached a specific status. * * @param tc is the caller tc * @param ticket is the JIRA ticket id * @param statusName is the status we are looking for * @return with date string - from JIRA - when the expected status change has happened * @throws IOException in case of problem * @throws JSONException in case of problem */ public String getTicketStatusChangeDate(final GepardTestClass tc, final String ticket, final String statusName) throws IOException, JSONException { String newTicketInfo = ticket; String ticketHistory = getTicketHistory(tc, ticket); JSONObject obj = new JSONObject(ticketHistory); obj = obj.getJSONObject("changelog"); String startedDate = null; JSONArray arr = obj.getJSONArray("histories"); for (int l = 0; l < arr.length(); l++) { String changeDate = arr.getJSONObject(l).getString("created"); JSONArray changes = arr.getJSONObject(l).getJSONArray("items"); for (int m = 0; m < changes.length(); m++) { String change = changes.getString(m); // example: "\"toString\":\"In Progress\"" String statusChangeToName = "\"toString\":\"" + statusName + "\""; if (change.contains(STATUS_CHANGE_TEXT) && change.contains(statusChangeToName)) { if (startedDate == null) { startedDate = changeDate; tc.logComment( "Ticket: " + ticket + " was changed to \"" + statusName + "\" at " + changeDate); } } } } if (startedDate == null) { tc.logComment("Ticket: " + ticket + " change to \"" + statusName + "\" status cannot be identified."); } return startedDate; } /** * Update a field of a JIRA ticket to a specified value. * * @param tc is the caller test. * @param ticket is the jira ticket id * @param fieldName is the name of the field * @param value is the new value * @throws JSONException in case of problem * @throws IOException in case of problem */ public void updateTicketFieldValue(final GepardTestClass tc, final String ticket, final String fieldName, final String value) throws JSONException, IOException { String ticketFields = getTicketFields(ticket); JSONObject obj = new JSONObject(ticketFields); obj = obj.getJSONObject("fields"); if (obj.has(fieldName)) { Object o = obj.get(fieldName); setTicketFieldValue(tc, ticket, fieldName, value); } else { throw new SimpleGepardException("Ticket: " + ticket + " field: " + fieldName + " cannot find."); } } /** * Get a field value of a JIRA ticket. * * @param tc is the caller test. * @param ticket is the jira ticket id * @param fieldName is the name of the field * @return with the field value * @throws JSONException in case of problem * @throws IOException in case of problem */ public String getTicketFieldValue(final GepardTestClass tc, final String ticket, final String fieldName) throws JSONException, IOException { String ticketFields = getTicketFields(ticket); JSONObject obj = new JSONObject(ticketFields); obj = obj.getJSONObject("fields"); if (obj.has(fieldName)) { return obj.get(fieldName).toString(); } else { throw new SimpleGepardException("Ticket: " + ticket + " field: " + fieldName + " cannot find."); } } private void setTicketFieldValue(final GepardTestClass tc, final String ticket, final String fieldName, final String value) throws IOException { String jiraSetFieldPage = getIssueFieldSetValueUrl(ticket); WebRequest requestSettings = new WebRequest(new URL(jiraSetFieldPage), HttpMethod.PUT); requestSettings.setAdditionalHeader("Content-type", "application/json"); requestSettings.setRequestBody("{ \"fields\": {\"" + fieldName + "\": \"" + value + "\"} }"); try { UnexpectedPage infoPage = webClient.getPage(requestSettings); if (infoPage.getWebResponse().getStatusCode() == HTTP_RESPONSE_OK) { tc.logComment("Field: '" + fieldName + "' of Ticket: " + ticket + " was updated to value: '" + value + "' successfully."); } else { throw new SimpleGepardException("Ticket: " + ticket + " field: " + fieldName + " update failed, status code: " + infoPage.getWebResponse().getStatusCode()); } } catch (FailingHttpStatusCodeException e) { throw new SimpleGepardException("Ticket: " + ticket + " field: " + fieldName + " update failed.", e); } } private String getTicketFields(String ticket) throws IOException { String jiraInfoPage = getIssueFieldValueUrl(ticket); UnexpectedPage infoPage = webClient.getPage(jiraInfoPage); String ticketInfo = infoPage.getWebResponse().getContentAsString(); return ticketInfo; } /** * Get a Jira query result in a map form. * * @param query is the jira query * @return with the map of JIRA_ID - ticketInfo in JSONObject pairs as result of a Jira query. * @throws IOException in case of problem * @throws JSONException in case of problem */ public ConcurrentHashMap<String, JSONObject> collectIssues(final String query) throws IOException, JSONException { String jqlURL = getListOfIssuesByQueryUrl(query); WebRequest requestSettings = new WebRequest(new URL(jqlURL), HttpMethod.GET); requestSettings.setAdditionalHeader("Content-type", "application/json"); UnexpectedPage infoPage = webClient.getPage(requestSettings); if (infoPage.getWebResponse().getStatusCode() == HTTP_RESPONSE_OK) { String ticketList = infoPage.getWebResponse().getContentAsString(); JSONObject obj = new JSONObject(ticketList); String maxResults = obj.getString("maxResults"); String total = obj.getString("total"); if (Integer.valueOf(maxResults) < Integer.valueOf(total)) { throw new SimpleGepardException("ERROR: Too many issues belong to given query (" + total + "), please change the query to shorten the result list."); } JSONArray array = obj.getJSONArray("issues"); ConcurrentHashMap<String, JSONObject> map = new ConcurrentHashMap<>(); for (int i = 0; i < array.length(); i++) { JSONObject o = (JSONObject) array.get(i); map.put(o.getString("key"), o); } return map; } throw new SimpleGepardException("ERROR: Cannot fetch Issue list from JIRA, status code:" + infoPage.getWebResponse().getStatusCode()); } /** * Detect the actual workflow possibilities of the ticket, from its actual status. * * @param ticket is the id * @return with String representation of the list of possibilities, in form of statusTransferId;newStatusName strings * @throws IOException in case of problem * @throws JSONException in case of problem */ public String detectWorkflow(final String ticket) throws IOException, JSONException { String jqlURL; //first detect status String ticketFields = getTicketFields(ticket); JSONObject fieldObj = new JSONObject(ticketFields); fieldObj = fieldObj.getJSONObject("fields"); fieldObj = fieldObj.getJSONObject("status"); String status = "@" + fieldObj.get("name").toString(); //then collect possible transactions jqlURL = getIssueTransitionsUrl(ticket); WebRequest requestSettings = new WebRequest(new URL(jqlURL), HttpMethod.GET); requestSettings.setAdditionalHeader("Content-type", "application/json"); UnexpectedPage infoPage = webClient.getPage(requestSettings); if (infoPage.getWebResponse().getStatusCode() == HTTP_RESPONSE_OK) { String ticketList = infoPage.getWebResponse().getContentAsString(); JSONObject obj = new JSONObject(ticketList); JSONArray array = obj.getJSONArray("transitions"); List<String> toList = new ArrayList<>(); toList.add(status); for (int i = 0; i < array.length(); i++) { JSONObject o = (JSONObject) array.get(i); JSONObject o2 = o.getJSONObject("to"); String toPossibility = o2.get("name").toString(); String toPossibilityID = o.get("id").toString(); //it is the transition id not the status id toList.add(toPossibilityID + ";" + toPossibility); } return toList.toString(); } throw new SimpleGepardException("ERROR: Cannot fetch Issue transition possibilities from JIRA, for ticket: " + ticket + ", Status code:" + infoPage.getWebResponse().getStatusCode()); } /** * Transition a ticket to next status in its workflow. * URL --> https://jira.xxxxxx.com/rest/api/2/issue/TEST-53/transitions?expand=transitions.fields * POST data --> {"transition":{"id":91}} * Returned HTTP response --> 204 * * @param ticket is the specific ticket * @param statusTransferId is the id of the transaction that should be used to transfer the ticket * @param expectedStatusName is the name of the expected new status * @param comment that will be attached to the ticket as comment of the transaction * @return with the new status */ private String transferTicketToStatus(final GepardTestClass tc, final String ticket, final String statusTransferId, final String expectedStatusName, final String comment) throws IOException, JSONException { String updateString = "{ \"update\": { \"comment\": [ { \"add\": { \"body\": \"" + comment + "\" } } ] }, \"transition\": { \"id\": \"" + statusTransferId + "\" } }"; String oldStatus = detectActualStatus(ticket); String newStatus; String jiraSetFieldPage = getIssueSetTransitionsUrl(ticket); WebRequest requestSettings = new WebRequest(new URL(jiraSetFieldPage), HttpMethod.POST); requestSettings.setAdditionalHeader("Content-type", "application/json"); requestSettings.setRequestBody(updateString); try { UnexpectedPage infoPage = webClient.getPage(requestSettings); if (infoPage.getWebResponse().getStatusCode() == HTTP_RESPONSE_OK) { newStatus = detectActualStatus(ticket); Assert.assertEquals("Transferring ticket: " + ticket + " to new status failed,", expectedStatusName, newStatus); tc.logComment("Ticket: " + ticket + " was transferred from status: \"" + oldStatus + "\" to status: \"" + newStatus + "\" successfully."); } else { throw new SimpleGepardException("ERROR: Status update failed for ticket: " + ticket + ", Status code:" + infoPage.getWebResponse().getStatusCode()); } } catch (FailingHttpStatusCodeException e) { throw new SimpleGepardException("ERROR: Status update failed for ticket: " + ticket + ".", e); } return newStatus; } private String detectActualStatus(final String ticket) throws IOException, JSONException { String ticketFields = getTicketFields(ticket); JSONObject fieldObj = new JSONObject(ticketFields); fieldObj = fieldObj.getJSONObject("fields"); fieldObj = fieldObj.getJSONObject("status"); String status = "@" + fieldObj.get("name").toString(); return status; } }