View Javadoc

1   /*
2    * Copyright (C) 2010 Atlassian
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  
17  package it;
18  
19  import com.atlassian.jira.functest.framework.UserProfile;
20  import com.atlassian.jira.nimblefunctests.annotation.JiraBuildNumberDependent;
21  import com.atlassian.jira.nimblefunctests.annotation.LongCondition;
22  import com.atlassian.jira.rest.client.IntegrationTestUtil;
23  import com.atlassian.jira.rest.client.TestUtil;
24  import com.atlassian.jira.rest.client.api.GetCreateIssueMetadataOptionsBuilder;
25  import com.atlassian.jira.rest.client.api.IssueRestClient;
26  import com.atlassian.jira.rest.client.api.RestClientException;
27  import com.atlassian.jira.rest.client.api.domain.Attachment;
28  import com.atlassian.jira.rest.client.api.domain.BasicIssue;
29  import com.atlassian.jira.rest.client.api.domain.CimIssueType;
30  import com.atlassian.jira.rest.client.api.domain.CimProject;
31  import com.atlassian.jira.rest.client.api.domain.Comment;
32  import com.atlassian.jira.rest.client.api.domain.Issue;
33  import com.atlassian.jira.rest.client.api.domain.IssueLink;
34  import com.atlassian.jira.rest.client.api.domain.IssueLinkType;
35  import com.atlassian.jira.rest.client.api.domain.Transition;
36  import com.atlassian.jira.rest.client.api.domain.input.AttachmentInput;
37  import com.atlassian.jira.rest.client.api.domain.input.ComplexIssueInputFieldValue;
38  import com.atlassian.jira.rest.client.api.domain.input.FieldInput;
39  import com.atlassian.jira.rest.client.api.domain.input.IssueInput;
40  import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder;
41  import com.atlassian.jira.rest.client.api.domain.input.LinkIssuesInput;
42  import com.atlassian.jira.rest.client.api.domain.input.TransitionInput;
43  import com.atlassian.jira.rest.client.api.domain.util.ErrorCollection;
44  import com.atlassian.jira.rest.client.internal.json.TestConstants;
45  import com.google.common.base.Function;
46  import com.google.common.base.Predicate;
47  import com.google.common.collect.ImmutableList;
48  import com.google.common.collect.Iterables;
49  import org.apache.commons.io.IOUtils;
50  import org.joda.time.DateTime;
51  import org.junit.Assert;
52  import org.junit.Before;
53  import org.junit.Rule;
54  import org.junit.Test;
55  import org.junit.rules.ExpectedException;
56  
57  import javax.annotation.Nullable;
58  import javax.ws.rs.core.Response;
59  import java.io.ByteArrayInputStream;
60  import java.io.File;
61  import java.io.FileInputStream;
62  import java.io.FileWriter;
63  import java.io.IOException;
64  import java.lang.reflect.InvocationTargetException;
65  import java.lang.reflect.Method;
66  import java.text.NumberFormat;
67  import java.util.Arrays;
68  import java.util.Collections;
69  import java.util.Locale;
70  import java.util.regex.Matcher;
71  import java.util.regex.Pattern;
72  
73  import static com.atlassian.jira.rest.client.IntegrationTestUtil.NUMERIC_CUSTOMFIELD_ID;
74  import static com.atlassian.jira.rest.client.IntegrationTestUtil.NUMERIC_CUSTOMFIELD_TYPE;
75  import static com.atlassian.jira.rest.client.IntegrationTestUtil.NUMERIC_CUSTOMFIELD_TYPE_V5;
76  import static com.atlassian.jira.rest.client.IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER;
77  import static com.atlassian.jira.rest.client.IntegrationTestUtil.TEXT_CUSTOMFIELD_ID;
78  import static com.atlassian.jira.rest.client.IntegrationTestUtil.USER2;
79  import static com.atlassian.jira.rest.client.TestUtil.assertErrorCode;
80  import static com.atlassian.jira.rest.client.TestUtil.assertExpectedErrorCollection;
81  import static com.atlassian.jira.rest.client.api.domain.EntityHelper.findEntityByName;
82  import static com.atlassian.jira.rest.client.internal.ServerVersionConstants.BN_JIRA_4_3;
83  import static com.atlassian.jira.rest.client.internal.ServerVersionConstants.BN_JIRA_5;
84  import static com.atlassian.jira.rest.client.internal.json.TestConstants.ADMIN_PASSWORD;
85  import static com.atlassian.jira.rest.client.internal.json.TestConstants.ADMIN_USERNAME;
86  import static com.atlassian.jira.rest.client.internal.json.TestConstants.USER1_USERNAME;
87  import static com.atlassian.jira.rest.client.internal.json.TestConstants.USER2_USERNAME;
88  import static com.atlassian.jira.rest.client.test.matchers.RestClientExceptionMatchers.rceWithSingleError;
89  import static org.hamcrest.Matchers.equalTo;
90  import static org.hamcrest.Matchers.hasItem;
91  import static org.hamcrest.Matchers.not;
92  import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
93  import static org.junit.Assert.assertEquals;
94  import static org.junit.Assert.assertFalse;
95  import static org.junit.Assert.assertNotNull;
96  import static org.junit.Assert.assertNull;
97  import static org.junit.Assert.assertThat;
98  import static org.junit.Assert.assertTrue;
99  import static org.junit.Assert.fail;
100 
101 
102 // Ignore "May produce NPE" warnings, as we know what we are doing in tests
103 @SuppressWarnings("ConstantConditions")
104 public class AsynchronousIssueRestClientTest extends AbstractAsynchronousRestClientTest {
105 
106     @Rule
107     public ExpectedException expectedException = ExpectedException.none();
108 
109     public static final String UTF8_FILE_BODY = "File body encoded in utf8: Ka\u017a\u0144 i \u017c\u00f3\u0142to\u015b\u0107 b\u0119d\u0105! | \u1f55\u03b1\u03bb\u03bf\u03bd \u03d5\u03b1\u03b3\u03b5\u1fd6\u03bd \u03b4\u1f7b\u03bd\u03b1\u03bc\u03b1\u03b9\u0387 \u03c4\u03bf\u1fe6\u03c4\u03bf \u03bf\u1f54 \u03bc\u03b5 \u03b2\u03bb\u1f71\u03c0\u03c4\u03b5\u03b9 \u0411\u0438 \u0448\u0438\u043b \u0438\u0434\u044d\u0439 \u0447\u0430\u0434\u043d\u0430, \u043d\u0430\u0434\u0430\u0434 \u0445\u043e\u0440\u0442\u043e\u0439 \u0431\u0438\u0448., or 2\u03c0R";
110     public static final String UTF8_FILE_NAME = "utf8 file name Ka\u017a\u0144 i \u017c\u00f3\u0142to\u015b\u0107 b\u0119d\u0105! \u1f55\u03b1\u03bb\u03bf\u03bd \u03d5\u03b1\u03b3\u03b5\u1fd6\u03bd \u03b4\u1f7b\u03bd\u03b1\u03bc\u03b1\u03b9\u0387 \u03c4\u03bf\u1fe6\u03c4\u03bf \u03bf\u1f54 \u03bc\u03b5 \u03b2\u03bb\u1f71\u03c0\u03c4\u03b5\u03b9 \u0411\u0438 \u0448\u0438\u043b \u0438\u0434\u044d\u0439 \u0447\u0430\u0434\u043d\u0430, \u043d\u0430\u0434\u0430\u0434 \u0445\u043e\u0440\u0442\u043e\u0439 \u0431\u0438\u0448., or 2\u03c0R";
111 
112     @Before
113     public void setup() {
114         IntegrationTestUtil.restoreAppropriateJiraData(TestConstants.DEFAULT_JIRA_DUMP_FILE, administration);
115     }
116 
117     @Test
118     public void testTransitionWithNumericCustomFieldPolishLocale() throws Exception {
119         final double newValue = 123.45;
120         final FieldInput fieldInput;
121         if (IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER) {
122             fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue);
123         } else {
124             fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, NumberFormat.getNumberInstance(new Locale("pl"))
125                     .format(newValue));
126         }
127         assertTransitionWithNumericCustomField(fieldInput, newValue);
128     }
129 
130     @Test
131     public void testTransitionWithNumericCustomFieldEnglishLocale() throws Exception {
132         setUser1();
133         final double newValue = 123.45;
134         final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID,
135                 NumberFormat.getNumberInstance(new Locale("pl")).format(newValue));
136 
137         assertErrorCode(Response.Status.BAD_REQUEST, IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER
138                 ? "Operation value must be a number" : ("'" + fieldInput.getValue() + "' is an invalid number"), new Runnable() {
139             @Override
140             public void run() {
141                 assertTransitionWithNumericCustomField(fieldInput, newValue);
142             }
143         });
144 
145         final FieldInput fieldInput2 = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue); // this will be serialized always with "." according to JSL
146         assertTransitionWithNumericCustomField(fieldInput2, newValue);
147     }
148 
149 
150     private void assertTransitionWithNumericCustomField(FieldInput fieldInput, Double expectedValue) {
151         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
152         assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
153         final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
154 
155         final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
156         assertNotNull(transitionFound);
157         assertTrue(Iterables.contains(transitionFound.getFields(),
158                 new Transition.Field(NUMERIC_CUSTOMFIELD_ID, false,
159                         IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? NUMERIC_CUSTOMFIELD_TYPE_V5 : NUMERIC_CUSTOMFIELD_TYPE)));
160         client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), Arrays.asList(fieldInput),
161                 Comment.valueOf("My test comment"))).claim();
162         final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
163         assertTrue(changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue().equals(expectedValue));
164     }
165 
166     @Test
167     public void testDeleteIssue() {
168         final IssueRestClient issueClient = client.getIssueClient();
169 
170         // verify that issue exist
171         final String issueKey = "TST-1";
172         final Issue issue = issueClient.getIssue(issueKey).claim();
173         assertEquals(issueKey, issue.getKey());
174 
175         // delete issue
176         issueClient.deleteIssue(issueKey, false).claim();
177 
178         // verify
179         assertThatIssueNotExists(issueKey);
180     }
181 
182     @Test
183     public void testDeleteIssueWithSubtasks() {
184         final IssueRestClient issueClient = client.getIssueClient();
185 
186         // verify that issue exist and create subtask
187         final String issueKey = "TST-1";
188         final Issue issue = issueClient.getIssue(issueKey).claim();
189         assertEquals(issueKey, issue.getKey());
190         final BasicIssue subtask = addSubtaskToIssue(issue);
191         System.out.println(subtask);
192 
193         // delete issue
194         issueClient.deleteIssue(issueKey, true).claim();
195 
196         // verify
197         assertThatIssueNotExists(issueKey);
198         assertThatIssueNotExists(subtask.getKey());
199     }
200 
201     @Test
202     public void testDeleteIssueWithSubtasksWhenDeleteSubtasksIsFalse() {
203         final IssueRestClient issueClient = client.getIssueClient();
204 
205         // verify that issue exist and create subtask
206         final String issueKey = "TST-1";
207         final Issue issue = issueClient.getIssue(issueKey).claim();
208         assertEquals(issueKey, issue.getKey());
209         BasicIssue subtask = addSubtaskToIssue(issue);
210         System.out.println(subtask);
211 
212         // delete issue
213         expectedException.expect(rceWithSingleError(400, String.format("The issue '%s' has subtasks.  "
214                 + "You must specify the 'deleteSubtasks' parameter to delete this issue and all its subtasks.", issueKey)));
215         issueClient.deleteIssue(issueKey, false).claim();
216     }
217 
218     @Test
219     public void testDeleteIssueWhenNoSuchIssue() {
220         final IssueRestClient issueClient = client.getIssueClient();
221 
222         // verify that issue exist
223         final String issueKey = "TST-999";
224         assertThatIssueNotExists(issueKey);
225 
226         // delete issue should thrown 404
227         expectedException.expect(rceWithSingleError(404, "Issue Does Not Exist"));
228         issueClient.deleteIssue(issueKey, false).claim();
229     }
230 
231     @Test
232     public void testDeleteIssueWithoutDeletePermission() {
233         setAnonymousMode();
234         final IssueRestClient issueClient = client.getIssueClient();
235 
236         // verify that issue doesn't exist
237         final String issueKey = "ANONEDIT-2";
238         final Issue issue = issueClient.getIssue(issueKey).claim();
239         assertEquals(issueKey, issue.getKey());
240 
241         // delete issue should thrown 401
242         expectedException.expect(rceWithSingleError(401, "You do not have permission to delete issues in this project."));
243         issueClient.deleteIssue(issueKey, false).claim();
244     }
245 
246     @JiraBuildNumberDependent(BN_JIRA_5)
247     @Test
248     public void testUpdateField() {
249         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
250         assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
251         final double newValue = 123;
252         final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue);
253         client.getIssueClient().updateIssue(issue.getKey(), IssueInput.createWithFields(fieldInput)).claim();
254         final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
255         assertEquals(newValue, changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
256     }
257 
258     @JiraBuildNumberDependent(BN_JIRA_5)
259     @Test
260     public void testUpdateMultipleFields() {
261         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
262         assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
263         final double newNumericValue = 123;
264         final String newTextValue = "my new text";
265 
266         final IssueInputBuilder issueInputBuilder = new IssueInputBuilder()
267                 .setFieldValue(NUMERIC_CUSTOMFIELD_ID, newNumericValue)
268                 .setFieldValue(TEXT_CUSTOMFIELD_ID, newTextValue);
269 
270         client.getIssueClient().updateIssue(issue.getKey(), issueInputBuilder.build()).claim();
271         final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
272         assertNotNull(changedIssue);
273         assertEquals(newNumericValue, changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
274         assertEquals(newTextValue, changedIssue.getField(TEXT_CUSTOMFIELD_ID).getValue());
275     }
276 
277     @JiraBuildNumberDependent(BN_JIRA_5)
278     @Test
279     public void testUpdateIssueWithInvalidAdditionalField() {
280         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
281         final String fieldId = "invalidField";
282 
283         expectedException.expect(RestClientException.class);
284         expectedException.expectMessage(String.format(
285                 "Field '%s' cannot be set. It is not on the appropriate screen, or unknown.", fieldId));
286         final FieldInput fieldInput = new FieldInput(fieldId, "who cares?");
287         client.getIssueClient().updateIssue(issue.getKey(), IssueInput.createWithFields(fieldInput)).claim();
288     }
289 
290     @JiraBuildNumberDependent(BN_JIRA_5)
291     @Test
292     public void testUpdateIssueWithoutPermissions() {
293         setUser2();
294 
295         expectedException.expect(RestClientException.class);
296         expectedException.expectMessage(String.format(
297                 "Field '%s' cannot be set. It is not on the appropriate screen, or unknown.", NUMERIC_CUSTOMFIELD_ID));
298         final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, 1.23d);
299         client.getIssueClient().updateIssue("TST-1", IssueInput.createWithFields(fieldInput)).claim();
300     }
301 
302     @Test
303     public void testTransitionWithNumericCustomFieldAndInteger() throws Exception {
304         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
305         assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
306         final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
307         final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
308 
309         assertNotNull(transitionFound);
310         assertTrue(Iterables.contains(transitionFound.getFields(),
311                 new Transition.Field(NUMERIC_CUSTOMFIELD_ID, false,
312                         IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? NUMERIC_CUSTOMFIELD_TYPE_V5 : NUMERIC_CUSTOMFIELD_TYPE)));
313         final double newValue = 123;
314         final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue);
315         client.getIssueClient().transition(issue.getTransitionsUri(), new TransitionInput(transitionFound.getId(), Arrays
316                 .asList(fieldInput),
317                 Comment.valueOf("My test comment"))).claim();
318         final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
319         assertEquals(newValue, changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
320     }
321 
322     @Test
323     public void testTransitionWithInvalidNumericField() throws Exception {
324         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
325         assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
326         final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
327         final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
328 
329         assertNotNull(transitionFound);
330         assertTrue(Iterables.contains(transitionFound.getFields(),
331                 new Transition.Field(NUMERIC_CUSTOMFIELD_ID, false,
332                         TESTING_JIRA_5_OR_NEWER ? NUMERIC_CUSTOMFIELD_TYPE_V5 : NUMERIC_CUSTOMFIELD_TYPE)));
333         final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, "]432jl");
334         // warning: Polish language here - I am asserting if the messages are indeed localized
335         // since 5.0 messages are changed and not localized
336         assertErrorCode(Response.Status.BAD_REQUEST, TESTING_JIRA_5_OR_NEWER
337                 ? "Operation value must be a number" : "']432jl' nie jest prawid\u0142ow\u0105 liczb\u0105", new Runnable() {
338             @Override
339             public void run() {
340                 client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), Arrays.asList(fieldInput),
341                         Comment.valueOf("My test comment"))).claim();
342             }
343         });
344     }
345 
346     @Test
347     public void testTransitionWithNoRoleOrGroup() {
348         Comment comment = Comment.valueOf("My text which I am just adding " + new DateTime());
349         testTransitionImpl(comment);
350     }
351 
352     @Test
353     public void testTransitionWithRoleLevel() {
354         Comment comment = Comment.createWithRoleLevel("My text which I am just adding " + new DateTime(), "Users");
355         testTransitionImpl(comment);
356     }
357 
358     @Test
359     public void testTransitionWithGroupLevel() {
360         Comment comment = Comment.createWithGroupLevel("My text which I am just adding " + new DateTime(), "jira-users");
361         testTransitionImpl(comment);
362     }
363 
364     @Test
365     public void testTransitionWithInvalidRole() {
366         final Comment comment = Comment.createWithRoleLevel("My text which I am just adding " + new DateTime(), "some-fake-role");
367         if (IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER) {
368             assertInvalidCommentInput(comment, "Invalid role level specified.");
369         } else {
370             assertInvalidCommentInput(comment, "Invalid role [some-fake-role]");
371         }
372     }
373 
374     @Test
375     public void testTransitionWithInvalidGroup() {
376         final Comment comment = Comment.createWithGroupLevel(
377                 "My text which I am just adding " + new DateTime(), "some-fake-group");
378         assertInvalidCommentInput(comment, "Group: some-fake-group does not exist.");
379     }
380 
381     private void assertInvalidCommentInput(final Comment comment, String expectedErrorMsg) {
382         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
383         final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
384         final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
385         final String errorMsg = doesJiraServeCorrectlyErrorMessagesForBadRequestWhileTransitioningIssue()
386                 ? expectedErrorMsg : null;
387         assertErrorCode(Response.Status.BAD_REQUEST, errorMsg, new Runnable() {
388             @Override
389             public void run() {
390                 client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), comment)).claim();
391             }
392         });
393     }
394 
395     private void testTransitionImpl(Comment comment) {
396         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
397         final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
398         Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
399         DateTime now = new DateTime();
400         client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), comment)).claim();
401 
402         final Issue changedIssue = client.getIssueClient().getIssue("TST-1").claim();
403         final Comment lastComment = Iterables.getLast(changedIssue.getComments());
404         assertEquals(comment.getBody(), lastComment.getBody());
405         assertEquals(IntegrationTestUtil.USER_ADMIN, lastComment.getAuthor());
406         assertEquals(IntegrationTestUtil.USER_ADMIN, lastComment.getUpdateAuthor());
407         assertEquals(lastComment.getCreationDate(), lastComment.getUpdateDate());
408         assertTrue(lastComment.getCreationDate().isAfter(now) || lastComment.getCreationDate().isEqual(now));
409         assertEquals(comment.getVisibility(), lastComment.getVisibility());
410     }
411 
412     @Test
413     public void testVoteUnvote() {
414         final Issue issue1 = client.getIssueClient().getIssue("TST-1").claim();
415         assertFalse(issue1.getVotes().hasVoted());
416         assertEquals(1, issue1.getVotes().getVotes()); // the other user has voted
417         final String expectedMessage = isJira5xOrNewer() // JIRA 5.0 comes without Polish translation OOB
418                 ? "You cannot vote for an issue you have reported."
419                 : "Nie mo\u017cesz g\u0142osowa\u0107 na zadanie kt\u00f3re utworzy\u0142e\u015b.";
420 
421         // I hope that such Polish special characters (for better testing local specific behaviour of REST
422         assertErrorCode(Response.Status.NOT_FOUND, expectedMessage, new Runnable() {
423             @Override
424             public void run() {
425                 client.getIssueClient().vote(issue1.getVotesUri()).claim();
426             }
427         });
428 
429 
430         final String issueKey = "TST-7";
431         Issue issue = client.getIssueClient().getIssue(issueKey).claim();
432         assertFalse(issue.getVotes().hasVoted());
433         assertEquals(0, issue.getVotes().getVotes());
434 
435         client.getIssueClient().vote(issue.getVotesUri()).claim();
436         issue = client.getIssueClient().getIssue(issueKey).claim();
437         assertTrue(issue.getVotes().hasVoted());
438         assertEquals(1, issue.getVotes().getVotes());
439 
440         client.getIssueClient().unvote(issue.getVotesUri()).claim();
441         issue = client.getIssueClient().getIssue(issueKey).claim();
442         assertFalse(issue.getVotes().hasVoted());
443         assertEquals(0, issue.getVotes().getVotes());
444 
445         setUser2();
446         issue = client.getIssueClient().getIssue(issueKey).claim();
447         assertFalse(issue.getVotes().hasVoted());
448         assertEquals(0, issue.getVotes().getVotes());
449         final Issue finalIssue = issue;
450         if (isJira6_3_7_OrNewer()) {
451             client.getIssueClient().unvote(finalIssue.getVotesUri()).claim();
452             issue = client.getIssueClient().getIssue(issueKey).claim();
453             assertEquals(0, issue.getVotes().getVotes());
454         } else {
455             assertErrorCode(Response.Status.NOT_FOUND, "Cannot remove a vote for an issue that the user has not already voted for.",
456                     new Runnable() {
457                         @Override
458                         public void run() {
459                             client.getIssueClient().unvote(finalIssue.getVotesUri()).claim();
460                         }
461                     });
462         }
463 
464         issue = client.getIssueClient().getIssue(issueKey).claim();
465         assertFalse(issue.getVotes().hasVoted());
466         assertEquals(0, issue.getVotes().getVotes());
467         client.getIssueClient().vote(issue.getVotesUri()).claim();
468         issue = client.getIssueClient().getIssue(issueKey).claim();
469         assertTrue(issue.getVotes().hasVoted());
470         assertEquals(1, issue.getVotes().getVotes());
471 
472         setClient(ADMIN_USERNAME, ADMIN_PASSWORD);
473         client.getIssueClient().vote(issue.getVotesUri()).claim();
474         issue = client.getIssueClient().getIssue(issueKey).claim();
475         assertTrue(issue.getVotes().hasVoted());
476         assertEquals(2, issue.getVotes().getVotes());
477     }
478 
479     @Test
480     public void testWatchUnwatch() {
481         final IssueRestClient issueClient = client.getIssueClient();
482         final Issue issue1 = issueClient.getIssue("TST-1").claim();
483 
484         Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim()
485                 .getUsers(), not(hasItem(IntegrationTestUtil.USER_ADMIN)));
486 
487         issueClient.watch(issue1.getWatchers().getSelf()).claim();
488         Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim()
489                 .getUsers(), hasItem(IntegrationTestUtil.USER_ADMIN));
490 
491         issueClient.unwatch(issue1.getWatchers().getSelf()).claim();
492         Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim()
493                 .getUsers(), not(hasItem(IntegrationTestUtil.USER_ADMIN)));
494 
495         Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim()
496                 .getUsers(), hasItem(IntegrationTestUtil.USER1));
497         issueClient.removeWatcher(issue1.getWatchers().getSelf(), IntegrationTestUtil.USER1.getName()).claim();
498         Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim()
499                 .getUsers(), not(hasItem(IntegrationTestUtil.USER1)));
500         issueClient.addWatcher(issue1.getWatchers().getSelf(), IntegrationTestUtil.USER1.getName()).claim();
501         Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf()).claim()
502                 .getUsers(), hasItem(IntegrationTestUtil.USER1));
503     }
504 
505     @Test
506     public void testRemoveWatcherUnauthorized() {
507         final IssueRestClient issueClient = client.getIssueClient();
508         final Issue issue1 = issueClient.getIssue("TST-1").claim();
509         issueClient.watch(issue1.getWatchers().getSelf()).claim();
510 
511         setUser1();
512         final IssueRestClient issueClient2 = client.getIssueClient();
513         assertErrorCode(Response.Status.UNAUTHORIZED,
514                 "User 'wseliga' is not allowed to remove watchers from issue 'TST-1'", new Runnable() {
515                     @Override
516                     public void run() {
517                         issueClient2.removeWatcher(issue1.getWatchers().getSelf(), ADMIN_USERNAME).claim();
518                     }
519                 });
520     }
521 
522 
523     @Test
524     public void testWatchAlreadyWatched() {
525         setUser1();
526         final IssueRestClient issueClient = client.getIssueClient();
527         final Issue issue = issueClient.getIssue("TST-1").claim();
528         Assert.assertThat(client.getIssueClient().getWatchers(issue.getWatchers().getSelf()).claim()
529                 .getUsers(), hasItem(IntegrationTestUtil.USER1));
530         // JIRA allows to watch already watched issue by you - such action effectively has no effect
531         issueClient.watch(issue.getWatchers().getSelf()).claim();
532         Assert.assertThat(client.getIssueClient().getWatchers(issue.getWatchers().getSelf()).claim()
533                 .getUsers(), hasItem(IntegrationTestUtil.USER1));
534     }
535 
536     @Test
537     public void testAddWatcherUnauthorized() {
538         final IssueRestClient issueClient = client.getIssueClient();
539         final Issue issue1 = issueClient.getIssue("TST-1").claim();
540         issueClient.addWatcher(issue1.getWatchers().getSelf(), USER1_USERNAME).claim();
541         assertThat(client.getIssueClient().getWatchers(issue1.getWatchers().getSelf()).claim()
542                 .getUsers(), hasItem(IntegrationTestUtil.USER1));
543 
544         setUser1();
545         assertTrue(client.getIssueClient().getIssue("TST-1").claim().getWatchers().isWatching());
546         String expectedErrorMsg = isJraDev3516Fixed() ? ("User '" + USER1_USERNAME
547                 + "' is not allowed to add watchers to issue 'TST-1'") : null;
548         assertErrorCode(Response.Status.UNAUTHORIZED, expectedErrorMsg, new Runnable() {
549             @Override
550             public void run() {
551                 client.getIssueClient().addWatcher(issue1.getWatchers().getSelf(), ADMIN_USERNAME).claim();
552             }
553         });
554     }
555 
556     private boolean isJraDev3516Fixed() {
557         return client.getMetadataClient().getServerInfo().claim().getBuildNumber() >= BN_JIRA_4_3;
558     }
559 
560     @Test
561     public void testAddWatcherWhoDoesNotHaveViewIssuePermissions() {
562         final IssueRestClient issueClient = client.getIssueClient();
563         final String issueKey = "RST-1";
564         final Issue issue1 = issueClient.getIssue(issueKey).claim();
565         final String expectedErrorMessage;
566 
567         if (isJira5xOrNewer()) {
568             expectedErrorMessage = "The user \"" + USER2_USERNAME + "\" does not have permission to view this issue."
569                     + " This user will not be added to the watch list.";
570         } else if (isJira43xOrNewer()) {
571             expectedErrorMessage = "User '" + ADMIN_USERNAME + "' is not allowed to add watchers to issue '" + issueKey + "'";
572         } else {
573             expectedErrorMessage = "com.sun.jersey.api.client.UniformInterfaceException: Client response status: 401";
574         }
575 
576         assertErrorCode(Response.Status.UNAUTHORIZED, expectedErrorMessage,
577                 new Runnable() {
578                     @Override
579                     public void run() {
580                         issueClient.addWatcher(issue1.getWatchers().getSelf(), USER2_USERNAME).claim();
581                     }
582                 });
583     }
584 
585     @JiraBuildNumberDependent(BN_JIRA_4_3)
586     @Test
587     public void testLinkIssuesWithRoleLevel() {
588         testLinkIssuesImpl(Comment.createWithRoleLevel("A comment about linking", "Administrators"));
589     }
590 
591     @JiraBuildNumberDependent(BN_JIRA_4_3)
592     @Test
593     public void testLinkIssuesWithGroupLevel() {
594         testLinkIssuesImpl(Comment.createWithGroupLevel("A comment about linking", "jira-administrators"));
595     }
596 
597     @JiraBuildNumberDependent(BN_JIRA_4_3)
598     @Test
599     public void testLinkIssuesWithSimpleComment() {
600         testLinkIssuesImpl(Comment.valueOf("A comment about linking"));
601     }
602 
603     @JiraBuildNumberDependent(BN_JIRA_4_3)
604     @Test
605     public void testLinkIssuesWithoutComment() {
606         testLinkIssuesImpl(null);
607     }
608 
609     @JiraBuildNumberDependent(BN_JIRA_4_3)
610     @Test
611     public void testLinkIssuesWithInvalidParams() {
612         assertErrorCode(Response.Status.NOT_FOUND,
613                 IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? "Issue Does Not Exist"
614                         : "The issue no longer exists.", new Runnable() {
615                     @Override
616                     public void run() {
617                         client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "FAKEKEY-1", "Duplicate", null)).claim();
618                     }
619                 });
620 
621         assertErrorCode(Response.Status.NOT_FOUND, "No issue link type with name 'NonExistingLinkType' found.", new Runnable() {
622             @Override
623             public void run() {
624                 client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "NonExistingLinkType", null)).claim();
625             }
626         });
627 
628         setUser1();
629         final String optionalDot = isJira5xOrNewer() ? "." : "";
630         assertErrorCode(Response.Status.NOT_FOUND,
631                 "You do not have the permission to see the specified issue" + optionalDot, new Runnable() {
632                     @Override
633                     public void run() {
634                         client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "RST-1", "Duplicate", null)).claim();
635                     }
636                 });
637         final ErrorCollection.Builder ecb = ErrorCollection.builder();
638         ecb.status(Response.Status.BAD_REQUEST.getStatusCode())
639                 .errorMessage("Failed to create comment for issue 'TST-6'")
640                 .error("commentLevel", "You are currently not a member of the project role: Administrators.");
641         final ImmutableList<ErrorCollection> errorCollections = ImmutableList.of(ecb.build());
642 
643         assertExpectedErrorCollection(errorCollections, new Runnable() {
644             @Override
645             public void run() {
646                 client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate",
647                         Comment.createWithRoleLevel("my body", "Administrators"))).claim();
648             }
649         });
650         assertErrorCode(Response.Status.BAD_REQUEST, "You are currently not a member of the group: jira-administrators.", new Runnable() {
651             @Override
652             public void run() {
653                 client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate",
654                         Comment.createWithGroupLevel("my body", "jira-administrators"))).claim();
655             }
656         });
657         assertErrorCode(Response.Status.BAD_REQUEST, "Group: somefakegroup does not exist.", new Runnable() {
658             @Override
659             public void run() {
660                 client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate",
661                         Comment.createWithGroupLevel("my body", "somefakegroup"))).claim();
662             }
663         });
664     }
665 
666     @JiraBuildNumberDependent(condition = LongCondition.LESS_THAN, value = 6211)
667     @Test
668     public void testLinkIssuesWithInvalidParamsBeforeUpgradeTask6211() {
669         setUser2();
670         assertErrorCode(Response.Status.UNAUTHORIZED, "No Link Issue Permission for issue 'TST-7'", new Runnable() {
671             @Override
672             public void run() {
673                 client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate", null)).claim();
674             }
675         });
676     }
677 
678     @JiraBuildNumberDependent(6211)
679     @Test
680     public void testLinkIssuesForUserRoleLevelAfterUpgradeTask6211() {
681         testLinkIssuesImpl(Comment.createWithRoleLevel("A comment about linking", "Users"));
682     }
683 
684     private void testLinkIssuesImpl(@Nullable Comment commentInput) {
685         final IssueRestClient issueClient = client.getIssueClient();
686         final Issue originalIssue = issueClient.getIssue("TST-7").claim();
687         int origNumComments = Iterables.size(originalIssue.getComments());
688         assertFalse(originalIssue.getIssueLinks().iterator().hasNext());
689 
690         issueClient.linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate", commentInput)).claim();
691 
692         final Issue linkedIssue = issueClient.getIssue("TST-7").claim();
693         assertEquals(1, Iterables.size(linkedIssue.getIssueLinks()));
694         final IssueLink addedLink = linkedIssue.getIssueLinks().iterator().next();
695         assertEquals("Duplicate", addedLink.getIssueLinkType().getName());
696         assertEquals("TST-6", addedLink.getTargetIssueKey());
697         assertEquals(IssueLinkType.Direction.OUTBOUND, addedLink.getIssueLinkType().getDirection());
698 
699         final int expectedNumComments = commentInput != null ? origNumComments + 1 : origNumComments;
700         assertEquals(expectedNumComments, Iterables.size(linkedIssue.getComments()));
701         if (commentInput != null) {
702             final Comment comment = linkedIssue.getComments().iterator().next();
703             assertEquals(commentInput.getBody(), comment.getBody());
704             assertEquals(IntegrationTestUtil.USER_ADMIN, comment.getAuthor());
705             assertEquals(commentInput.getVisibility(), comment.getVisibility());
706         } else {
707             assertFalse(linkedIssue.getComments().iterator().hasNext());
708         }
709 
710 
711         final Issue targetIssue = issueClient.getIssue("TST-6").claim();
712         final IssueLink targetLink = targetIssue.getIssueLinks().iterator().next();
713         assertEquals(IssueLinkType.Direction.INBOUND, targetLink.getIssueLinkType().getDirection());
714         assertEquals("Duplicate", targetLink.getIssueLinkType().getName());
715     }
716 
717     private boolean doesJiraSupportAddingAttachment() {
718         return client.getMetadataClient().getServerInfo().claim().getBuildNumber() >= BN_JIRA_4_3;
719     }
720 
721     private boolean doesJiraServeCorrectlyErrorMessagesForBadRequestWhileTransitioningIssue() {
722         return client.getMetadataClient().getServerInfo().claim().getBuildNumber() >= BN_JIRA_4_3;
723     }
724 
725     @Test
726     // TODO: implement
727     public void testAddAttachment() throws IOException {
728 
729         if (!doesJiraSupportAddingAttachment()) {
730             return;
731         }
732         final IssueRestClient issueClient = client.getIssueClient();
733         final Issue issue = issueClient.getIssue("TST-3").claim();
734         assertFalse(issue.getAttachments().iterator().hasNext());
735 
736         String str = "Wojtek";
737         final String filename1 = "my-test-file";
738         issueClient.addAttachment(issue.getAttachmentsUri(), new ByteArrayInputStream(str.getBytes("UTF-8")), filename1).claim();
739         final String filename2 = "my-picture.png";
740         issueClient.addAttachment(issue.getAttachmentsUri(), AsynchronousIssueRestClientTest.class
741                 .getResourceAsStream("/attachment-test/transparent-png.png"), filename2).claim();
742 
743         final Issue issueWithAttachments = issueClient.getIssue("TST-3").claim();
744         final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
745         assertEquals(2, Iterables.size(attachments));
746         final Iterable<String> attachmentsNames = Iterables.transform(attachments, new Function<Attachment, String>() {
747             @Override
748             public String apply(@Nullable Attachment from) {
749                 return from.getFilename();
750             }
751         });
752         assertThat(attachmentsNames, containsInAnyOrder(filename1, filename2));
753         final Attachment pictureAttachment = Iterables.find(attachments, new Predicate<Attachment>() {
754             @Override
755             public boolean apply(@Nullable Attachment input) {
756                 return filename2.equals(input.getFilename());
757             }
758         });
759 
760         // let's download it now and compare it's binary content
761 
762         assertTrue(
763                 IOUtils.contentEquals(AsynchronousIssueRestClientTest.class
764                                 .getResourceAsStream("/attachment-test/transparent-png.png"),
765                         issueClient.getAttachment(pictureAttachment.getContentUri()).claim()));
766     }
767 
768     @Test
769     public void testAddAttachmentWithUtf8InNameAndBody() throws IOException {
770         final IssueRestClient issueClient = client.getIssueClient();
771         final Issue issue = issueClient.getIssue("TST-3").claim();
772         assertFalse(issue.getAttachments().iterator().hasNext());
773 
774         final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(UTF8_FILE_BODY.getBytes("UTF-8"));
775         issueClient.addAttachment(issue.getAttachmentsUri(), byteArrayInputStream, UTF8_FILE_NAME).claim();
776 
777         final Issue issueWithAttachments = issueClient.getIssue("TST-3").claim();
778         final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
779         assertEquals(1, Iterables.size(attachments));
780         final Attachment attachment = attachments.iterator().next();
781         assertThat(attachment.getFilename(), equalTo(UTF8_FILE_NAME));
782 
783         assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(UTF8_FILE_BODY.getBytes("UTF-8")),
784                 issueClient.getAttachment(attachment.getContentUri()).claim()));
785     }
786 
787     @Test
788     // TODO: implement
789     public void testAddAttachments() throws IOException {
790         if (!doesJiraSupportAddingAttachment()) {
791             return;
792         }
793         final IssueRestClient issueClient = client.getIssueClient();
794         final Issue issue = issueClient.getIssue("TST-4").claim();
795         assertFalse(issue.getAttachments().iterator().hasNext());
796 
797         final AttachmentInput[] attachmentInputs = new AttachmentInput[3];
798         for (int i = 1; i <= 3; i++) {
799             attachmentInputs[i - 1] = new AttachmentInput("my-test-file-" + i + ".txt", new ByteArrayInputStream((
800                     "content-of-the-file-" + i).getBytes("UTF-8")));
801         }
802         issueClient.addAttachments(issue.getAttachmentsUri(), attachmentInputs).claim();
803 
804         final Issue issueWithAttachments = issueClient.getIssue("TST-4").claim();
805         final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
806         assertEquals(3, Iterables.size(attachments));
807         Pattern pattern = Pattern.compile("my-test-file-(\\d)\\.txt");
808         for (Attachment attachment : attachments) {
809             assertTrue(pattern.matcher(attachment.getFilename()).matches());
810             final Matcher matcher = pattern.matcher(attachment.getFilename());
811             matcher.find();
812             final String interfix = matcher.group(1);
813             assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(("content-of-the-file-" + interfix).getBytes("UTF-8")),
814                     issueClient.getAttachment(attachment.getContentUri()).claim()));
815 
816         }
817     }
818 
819     @Test
820     public void testAddAttachmentsWithUtf8InNameAndBody() throws IOException {
821         final IssueRestClient issueClient = client.getIssueClient();
822         final Issue issue = issueClient.getIssue("TST-4").claim();
823         assertFalse(issue.getAttachments().iterator().hasNext());
824 
825         final AttachmentInput[] attachmentInputs = new AttachmentInput[3];
826         final String[] names = new String[3];
827         final String[] contents = new String[3];
828         for (int i = 0; i < 3; i++) {
829             names[i] = UTF8_FILE_NAME + "-" + i + ".txt";
830             contents[i] = "content-of-the-file-" + i + " with some utf8: " + UTF8_FILE_BODY;
831             attachmentInputs[i] = new AttachmentInput(names[i], new ByteArrayInputStream(contents[i].getBytes("UTF-8")));
832         }
833         issueClient.addAttachments(issue.getAttachmentsUri(), attachmentInputs).claim();
834 
835         final Issue issueWithAttachments = issueClient.getIssue("TST-4").claim();
836         final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
837         assertEquals(3, Iterables.size(attachments));
838         Pattern pattern = Pattern.compile(".*-(\\d)\\.txt");
839         for (Attachment attachment : attachments) {
840             assertTrue(pattern.matcher(attachment.getFilename()).matches());
841             final Matcher matcher = pattern.matcher(attachment.getFilename());
842             matcher.find();
843             final int attachmentNum = Integer.parseInt(matcher.group(1));
844             assertThat(attachment.getFilename(), equalTo(names[attachmentNum]));
845             assertTrue(IOUtils.contentEquals(new ByteArrayInputStream((contents[attachmentNum]).getBytes("UTF-8")),
846                     issueClient.getAttachment(attachment.getContentUri()).claim()));
847 
848         }
849     }
850 
851     @Test
852     // TODO: implement
853     public void testAddFileAttachments() throws IOException {
854         if (!doesJiraSupportAddingAttachment()) {
855             return;
856         }
857         final IssueRestClient issueClient = client.getIssueClient();
858         final Issue issue = issueClient.getIssue("TST-5").claim();
859         assertFalse(issue.getAttachments().iterator().hasNext());
860 
861         final File tempFile = File.createTempFile("jim-integration-test", ".txt");
862         tempFile.deleteOnExit();
863         FileWriter writer = new FileWriter(tempFile);
864         writer.write("This is the content of my file which I am going to upload to JIRA for testing.");
865         writer.close();
866         issueClient.addAttachments(issue.getAttachmentsUri(), tempFile).claim();
867 
868         final Issue issueWithAttachments = issueClient.getIssue("TST-5").claim();
869         final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
870         assertEquals(1, Iterables.size(attachments));
871         assertTrue(IOUtils.contentEquals(new FileInputStream(tempFile),
872                 issueClient.getAttachment(attachments.iterator().next().getContentUri()).claim()));
873     }
874 
875     @Test
876     public void testAddFileAttachmentWithUtf8InNameAndBody() throws IOException {
877         final IssueRestClient issueClient = client.getIssueClient();
878         final Issue issue = issueClient.getIssue("TST-5").claim();
879         assertFalse(issue.getAttachments().iterator().hasNext());
880 
881         final File tempFile = File.createTempFile(UTF8_FILE_NAME, ".txt");
882         tempFile.deleteOnExit();
883         FileWriter writer = new FileWriter(tempFile);
884         writer.write(UTF8_FILE_BODY);
885         writer.close();
886         issueClient.addAttachments(issue.getAttachmentsUri(), tempFile).claim();
887 
888         final Issue issueWithAttachments = issueClient.getIssue("TST-5").claim();
889         final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
890         assertEquals(1, Iterables.size(attachments));
891         final Attachment firstAttachment = attachments.iterator().next();
892         assertTrue(IOUtils.contentEquals(new FileInputStream(tempFile),
893                 issueClient.getAttachment(firstAttachment.getContentUri()).claim()));
894         assertThat(firstAttachment.getFilename(), equalTo(tempFile.getName()));
895     }
896 
897     private void setUserLanguageToEnUk() {
898         changeUserLanguageByValueOrName("en_UK", "angielski (UK)");
899     }
900 
901     private void changeUserLanguageByValueOrName(String value, String name) {
902         final UserProfile userProfile = navigation.userProfile();
903         boolean fallbackToChangeByValue = false;
904         try {
905             Method changeUserLanguageByValue = userProfile.getClass().getMethod("changeUserLanguageByValue", String.class);
906             changeUserLanguageByValue.invoke(userProfile, value);
907         } catch (NoSuchMethodException e) {
908             // fallbackToChangeByValue to value - for JIRA < 5.1
909             fallbackToChangeByValue = true;
910         } catch (InvocationTargetException e) {
911             fallbackToChangeByValue = true;
912         } catch (IllegalAccessException e) {
913             fallbackToChangeByValue = true;
914         }
915 
916         if (fallbackToChangeByValue) {
917             userProfile.changeUserLanguage(name);
918         }
919     }
920 
921     @Test
922     public void testFetchingUnassignedIssue() {
923         administration.generalConfiguration().setAllowUnassignedIssues(true);
924         assertEquals(IntegrationTestUtil.USER_ADMIN, client.getIssueClient().getIssue("TST-5").claim().getAssignee());
925 
926         setUserLanguageToEnUk();
927         navigation.issue().unassignIssue("TST-5", "unassigning issue");
928         // this single line does instead of 2 above - func test suck with non-English locale
929         // but it does not work yet with JIRA 5.0-resthack...
930         //navigation.issue().assignIssue("TST-5", "unassigning issue", "Nieprzydzielone");
931 
932         assertNull(client.getIssueClient().getIssue("TST-5").claim().getAssignee());
933     }
934 
935     @Test
936     public void testFetchingIssueWithAnonymousComment() {
937         setUserLanguageToEnUk();
938         final String commentText = "my nice comment";
939         final String issueKey = "ANONEDIT-1";
940 
941         navigation.logout();
942         navigation.issue().addComment(issueKey, commentText);
943 
944         final Issue issue = client.getIssueClient().getIssue(issueKey).claim();
945         assertEquals(1, Iterables.size(issue.getComments()));
946         final Comment comment = issue.getComments().iterator().next();
947         assertEquals(commentText, comment.getBody());
948         if (isJira5xOrNewer()) {
949             assertNotNull(comment.getId());
950         } else {
951             assertNull(comment.getId());
952         }
953         assertNull(comment.getAuthor());
954         assertNull(comment.getUpdateAuthor());
955     }
956 
957     @Test
958     public void testGetIssueWithNoViewWatchersPermission() {
959         setUser1();
960         assertTrue(client.getIssueClient().getIssue("TST-1").claim().getWatchers().isWatching());
961 
962         setUser2();
963         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
964         assertFalse(issue.getWatchers().isWatching());
965         client.getIssueClient().watch(issue.getWatchers().getSelf()).claim();
966         final Issue watchedIssue = client.getIssueClient().getIssue("TST-1").claim();
967         assertTrue(watchedIssue.getWatchers().isWatching());
968         assertEquals(2, watchedIssue.getWatchers().getNumWatchers());
969 
970         // although there are 2 watchers, only one is listed with details - the caller itself, as the caller does not
971         // have view watchers and voters permission
972         assertThat(client.getIssueClient().getWatchers(watchedIssue.getWatchers().getSelf()).claim().getUsers(),
973                 containsInAnyOrder(USER2));
974     }
975 
976     @Test
977     public void testTransition() throws Exception {
978         final Issue issue = client.getIssueClient().getIssue("TST-1").claim();
979         final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue).claim();
980         assertEquals(4, Iterables.size(transitions));
981         final Transition startProgressTransition = new Transition("Start Progress", IntegrationTestUtil.START_PROGRESS_TRANSITION_ID, Collections
982                 .<Transition.Field>emptyList());
983         assertTrue(Iterables.contains(transitions, startProgressTransition));
984 
985         client.getIssueClient().transition(issue, new TransitionInput(IntegrationTestUtil.START_PROGRESS_TRANSITION_ID,
986                 Collections.<FieldInput>emptyList(), Comment.valueOf("My test comment"))).claim();
987         final Issue transitionedIssue = client.getIssueClient().getIssue("TST-1").claim();
988         assertEquals("In Progress", transitionedIssue.getStatus().getName());
989         final Iterable<Transition> transitionsAfterTransition = client.getIssueClient().getTransitions(issue).claim();
990         assertFalse(Iterables.contains(transitionsAfterTransition, startProgressTransition));
991         final Transition stopProgressTransition = new Transition("Stop Progress", IntegrationTestUtil.STOP_PROGRESS_TRANSITION_ID, Collections
992                 .<Transition.Field>emptyList());
993         assertTrue(Iterables.contains(transitionsAfterTransition, stopProgressTransition));
994     }
995 
996     private void assertThatIssueNotExists(String issueKey) {
997         try {
998             final Issue issue = client.getIssueClient().getIssue(issueKey).claim();
999             fail("It looks that issue exists, and it should not be here! issue = " + issue);
1000         } catch (RestClientException ex) {
1001             assertThat(ex, rceWithSingleError(404, "Issue Does Not Exist"));
1002         }
1003     }
1004 
1005     private BasicIssue addSubtaskToIssue(final Issue issue) {
1006         // collect CreateIssueMetadata for project with key TST
1007         final IssueRestClient issueClient = client.getIssueClient();
1008         final Iterable<CimProject> metadataProjects = issueClient.getCreateIssueMetadata(
1009                 new GetCreateIssueMetadataOptionsBuilder().withProjectKeys(issue.getProject().getKey())
1010                         .withExpandedIssueTypesFields().build()).claim();
1011 
1012         // select project and issue
1013         assertEquals(1, Iterables.size(metadataProjects));
1014         final CimProject project = metadataProjects.iterator().next();
1015         final CimIssueType issueType = findEntityByName(project.getIssueTypes(), "Sub-task");
1016 
1017         // build issue input
1018         final String summary = "Some subtask";
1019         final String description = "Some description for substask";
1020 
1021         // prepare IssueInput
1022         final IssueInputBuilder issueInputBuilder = new IssueInputBuilder(project, issueType, summary)
1023                 .setDescription(description)
1024                 .setFieldValue("parent", ComplexIssueInputFieldValue.with("key", issue.getKey()));
1025 
1026         // create
1027         final BasicIssue basicCreatedIssue = issueClient.createIssue(issueInputBuilder.build()).claim();
1028         assertNotNull(basicCreatedIssue.getKey());
1029 
1030         return basicCreatedIssue;
1031     }
1032 }