a4199be63b4077ee11b2f40d7993b986b0c05c75
[idea/community.git] / plugins / tasks / tasks-tests / test / com / intellij / tasks / integration / JiraIntegrationTest.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.tasks.integration;
17
18 import com.google.gson.Gson;
19 import com.google.gson.JsonArray;
20 import com.google.gson.JsonObject;
21 import com.intellij.tasks.Task;
22 import com.intellij.tasks.TaskBundle;
23 import com.intellij.tasks.TaskManagerTestCase;
24 import com.intellij.tasks.TaskState;
25 import com.intellij.tasks.config.TaskSettings;
26 import com.intellij.tasks.impl.LocalTaskImpl;
27 import com.intellij.tasks.impl.TaskUtil;
28 import com.intellij.tasks.jira.JiraRepository;
29 import com.intellij.tasks.jira.JiraRepositoryType;
30 import com.intellij.tasks.jira.JiraVersion;
31 import org.apache.commons.httpclient.HttpClient;
32 import org.apache.commons.httpclient.methods.GetMethod;
33 import org.apache.commons.httpclient.methods.PostMethod;
34 import org.apache.commons.httpclient.methods.StringRequestEntity;
35 import org.apache.xmlrpc.CommonsXmlRpcTransport;
36 import org.apache.xmlrpc.XmlRpcClient;
37 import org.apache.xmlrpc.XmlRpcRequest;
38 import org.intellij.lang.annotations.Language;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41
42 import java.net.URL;
43 import java.text.SimpleDateFormat;
44 import java.util.*;
45
46 import static com.intellij.tasks.jira.JiraRemoteApi.ApiType.REST_2_0;
47 import static com.intellij.tasks.jira.JiraRemoteApi.ApiType.REST_2_0_ALPHA;
48
49 /**
50  * @author Dmitry Avdeev
51  *         Date: 1/15/13
52  */
53 public class JiraIntegrationTest extends TaskManagerTestCase {
54
55   /**
56    * JIRA 4.4.5, REST API 2.0.alpha1
57    */
58   @NonNls private static final String JIRA_4_TEST_SERVER_URL = "http://idea-qa-task-2.labs.intellij.net:8014";
59
60   /**
61    * JIRA 5.0.6, REST API 2.0
62    */
63   @NonNls private static final String JIRA_5_TEST_SERVER_URL = "http://trackers-tests.labs.intellij.net:8015";
64
65   private static final SimpleDateFormat SHORT_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");
66   static {
67     SHORT_TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
68   }
69
70   private JiraRepository myRepository;
71
72   public void testGerman() throws Exception {
73     myRepository.setUsername("german");
74     myRepository.setPassword("german");
75     Task[] issues = myRepository.getIssues(null, 50, 0);
76     assertEquals(3, issues.length);
77     assertEquals(TaskState.OPEN, issues[0].getState());
78     assertFalse(issues[0].isClosed());
79   }
80
81   public void testLogin() throws Exception {
82     myRepository.setUsername("german");
83     myRepository.setUsername("wrong password");
84     //noinspection ConstantConditions
85     Exception exception = myRepository.createCancellableConnection().call();
86     assertNotNull(exception);
87     assertEquals(TaskBundle.message("failure.login"), exception.getMessage());
88   }
89
90   public void testVersionDiscovery() throws Exception {
91     myRepository.setUrl(JIRA_5_TEST_SERVER_URL);
92     assertEquals(REST_2_0, myRepository.discoverApiVersion().getType());
93     myRepository.setUrl(JIRA_4_TEST_SERVER_URL);
94     assertEquals(REST_2_0_ALPHA, myRepository.discoverApiVersion().getType());
95   }
96
97   public void testJqlQuery() throws Exception {
98     myRepository.setSearchQuery("assignee = currentUser() AND (summary ~ 'foo' or resolution = Fixed)");
99     assertEquals(2, myRepository.getIssues("", 50, 0).length);
100
101     // test that user part of query is prepended to existing one
102     myRepository.setSearchQuery("assignee = currentUser() order by updated");
103     assertEquals(1, myRepository.getIssues("foo", 50, 0).length);
104   }
105
106   /**
107    * Should return null, not throw exceptions by contact.
108    */
109   public void testIssueNotExists() throws Exception {
110     assertNull(myRepository.findTask("FOO-42"));
111   }
112
113   /**
114    * If query string looks like task ID, separate request will be made to download issue.
115    */
116   public void testFindSingleIssue() throws Exception {
117     Task[] found = myRepository.getIssues("UT-6", 0, 1, true);
118     assertEquals(1, found.length);
119     assertEquals("Summary contains 'bar'", found[0].getSummary());
120   }
121
122   /**
123    * Holds only for JIRA > 5.x.x
124    */
125   public void testExtractedErrorMessage() throws Exception {
126     myRepository.setSearchQuery("foo < bar");
127     try {
128       myRepository.getIssues("", 50, 0);
129       fail();
130     }
131     catch (Exception e) {
132       assertEquals("Request failed. Reason: \"Field 'foo' does not exist or you do not have permission to view it.\"", e.getMessage());
133     }
134   }
135
136   // Our test servers poorly handles frequent state updates of single dedicated issue. As a workaround
137   // we create new issue for every test run (via XML-RPC API in JIRA 4.x and REST API in JIRA 5+)
138
139   public void testSetTaskStateInJira5() throws Exception {
140     myRepository.setUrl(JIRA_5_TEST_SERVER_URL);
141     final String id = createIssueViaRestApi("BTSU", "Test issue for state updates (" + SHORT_TIMESTAMP_FORMAT.format(new Date()) + ")");
142     changeTaskStateAndCheck(id);
143   }
144
145   // We can use XML-RPC in JIRA 5+ too, but nonetheless it's useful to have REST-based implementation as well
146   private String createIssueViaRestApi(@NotNull String project, @NotNull String summary) throws Exception {
147     final HttpClient client = myRepository.getHttpClient();
148     final PostMethod method = new PostMethod(myRepository.getUrl() + "/rest/api/latest/issue");
149     try {
150       // For simplicity assume that project, summary and username don't contain illegal characters
151       @Language("JSON")
152       final String json = "{\"fields\": {\n" +
153                           "  \"project\": {\n" +
154                           "    \"key\": \"" + project + "\"\n" +
155                           "  },\n" +
156                           "  \"issuetype\": {\n" +
157                           "    \"name\": \"Bug\"\n" +
158                           "  },\n" +
159                           "  \"assignee\": {\n" +
160                           "    \"name\": \"" + myRepository.getUsername() + "\"\n" +
161                           "  },\n" +
162                           "  \"summary\": \"" + summary + "\"\n" +
163                           "}}";
164       method.setRequestEntity(new StringRequestEntity(json, "application/json", "utf-8"));
165       client.executeMethod(method);
166       return new Gson().fromJson(method.getResponseBodyAsString(), JsonObject.class).get("id").getAsString();
167     }
168     finally {
169       method.releaseConnection();
170     }
171   }
172
173   public void testSetTaskStateInJira4() throws Exception {
174     myRepository.setUrl(JIRA_4_TEST_SERVER_URL);
175     final String id = createIssueViaXmlRpc("BTSU", "Test issue for state updates (" + SHORT_TIMESTAMP_FORMAT.format(new Date()) + ")");
176     changeTaskStateAndCheck(id);
177   }
178
179   @SuppressWarnings("UseOfObsoleteCollectionType")
180   @NotNull
181   private String createIssueViaXmlRpc(@NotNull String project, @NotNull String summary) throws Exception {
182     final URL url = new URL(myRepository.getUrl() + "/rpc/xmlrpc");
183     final XmlRpcClient xmlRpcClient = new XmlRpcClient(url);
184     final Map<String, Object> issue = new Hashtable<String, Object>();
185     issue.put("summary", summary);
186     issue.put("project", project);
187     issue.put("assignee", myRepository.getUsername());
188     issue.put("type", 1); // Bug
189     issue.put("state", 1); // Open
190
191     final Vector<Object> params = new Vector<Object>(Arrays.asList("", issue)); // empty token because of HTTP basic auth
192     final Hashtable result = (Hashtable)xmlRpcClient.execute(new XmlRpcRequest("jira1.createIssue", params),
193                                                              new CommonsXmlRpcTransport(url, myRepository.getHttpClient()));
194     return (String)result.get("key");
195   }
196
197   private void changeTaskStateAndCheck(@NotNull String issueKey) throws Exception {
198     final Task original = myRepository.findTask(issueKey);
199     assertNotNull(original);
200     myRepository.setTaskState(original, TaskState.IN_PROGRESS);
201     final Task updated = myRepository.findTask(issueKey);
202     assertNotNull(updated);
203     assertEquals(TaskState.IN_PROGRESS, updated.getState());
204   }
205
206   public void testSetTimeSpend() throws Exception {
207     // only REST API 2.0 supports this feature
208     myRepository.setUrl(JIRA_5_TEST_SERVER_URL);
209     Task task = myRepository.findTask("UT-9");
210     assertNotNull("Test task not found", task);
211
212     // timestamp as comment
213     String comment = "Timestamp: " + TaskUtil.formatDate(new Date());
214
215     // semi-unique duration as timeSpend
216     // should be no longer than 8 hours in total, because it's considered as one full day
217     int minutes = (int)(System.currentTimeMillis() % 240) + 1;
218     String duration = String.format("%dh %dm", minutes / 60, minutes % 60);
219     myRepository.updateTimeSpent(new LocalTaskImpl(task), duration, comment);
220
221     // possible race conditions?
222     GetMethod request = new GetMethod(myRepository.getRestUrl("issue", task.getId(), "worklog"));
223     String response = myRepository.executeMethod(request);
224     JsonObject object = new Gson().fromJson(response, JsonObject.class);
225     JsonArray worklogs = object.get("worklogs").getAsJsonArray();
226     JsonObject last = worklogs.get(worklogs.size() - 1).getAsJsonObject();
227
228     assertEquals(comment, last.get("comment").getAsString());
229     // don't depend on concrete response format: zero hours stripping, zero padding and so on
230     assertEquals(minutes * 60, last.get("timeSpentSeconds").getAsInt());
231   }
232
233   public void testParseVersionNumbers() throws Exception {
234     assertEquals(new JiraVersion("6.1-OD-09-WN").toString(), "6.1.9");
235     assertEquals(new JiraVersion("5.0.6").toString(), "5.0.6");
236     assertEquals(new JiraVersion("4.4.5").toString(), "4.4.5");
237   }
238
239   @Override
240   public void setUp() throws Exception {
241     super.setUp();
242     TaskSettings.getInstance().CONNECTION_TIMEOUT = 20000;
243     myRepository = new JiraRepository(new JiraRepositoryType());
244     myRepository.setUrl(JIRA_5_TEST_SERVER_URL);
245     myRepository.setUsername("buildtest");
246     myRepository.setPassword("buildtest");
247   }
248 }