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