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.rest.client.IntegrationTestUtil;
20  import com.atlassian.jira.rest.client.IssueRestClient;
21  import com.atlassian.jira.rest.client.IterableMatcher;
22  import com.atlassian.jira.rest.client.NullProgressMonitor;
23  import com.atlassian.jira.rest.client.TestUtil;
24  import com.atlassian.jira.rest.client.annotation.Restore;
25  import com.atlassian.jira.rest.client.domain.Attachment;
26  import com.atlassian.jira.rest.client.domain.Comment;
27  import com.atlassian.jira.rest.client.domain.Issue;
28  import com.atlassian.jira.rest.client.domain.IssueLink;
29  import com.atlassian.jira.rest.client.domain.IssueLinkType;
30  import com.atlassian.jira.rest.client.domain.Transition;
31  import com.atlassian.jira.rest.client.domain.input.AttachmentInput;
32  import com.atlassian.jira.rest.client.domain.input.FieldInput;
33  import com.atlassian.jira.rest.client.domain.input.LinkIssuesInput;
34  import com.atlassian.jira.rest.client.domain.input.TransitionInput;
35  import com.atlassian.jira.rest.client.internal.ServerVersionConstants;
36  import com.atlassian.jira.rest.client.internal.json.TestConstants;
37  import com.google.common.base.Function;
38  import com.google.common.base.Predicate;
39  import com.google.common.collect.Iterables;
40  import org.apache.commons.io.IOUtils;
41  import org.hamcrest.Matchers;
42  import org.joda.time.DateTime;
43  import org.junit.Assert;
44  import org.junit.Test;
45  
46  import javax.annotation.Nullable;
47  import javax.ws.rs.core.Response;
48  import java.io.ByteArrayInputStream;
49  import java.io.File;
50  import java.io.FileInputStream;
51  import java.io.FileWriter;
52  import java.io.IOException;
53  import java.text.NumberFormat;
54  import java.util.Arrays;
55  import java.util.Locale;
56  import java.util.regex.Matcher;
57  import java.util.regex.Pattern;
58  
59  import static com.atlassian.jira.rest.client.IntegrationTestUtil.*;
60  import static com.atlassian.jira.rest.client.TestUtil.assertErrorCode;
61  import static com.atlassian.jira.rest.client.internal.json.TestConstants.USER1_USERNAME;
62  import static com.atlassian.jira.rest.client.internal.json.TestConstants.USER2_USERNAME;
63  import static com.atlassian.jira.rest.client.internal.json.TestConstants.ADMIN_USERNAME;
64  import static com.atlassian.jira.rest.client.internal.json.TestConstants.ADMIN_PASSWORD;
65  import static org.junit.Assert.*;
66  
67  
68  @Restore(TestConstants.DEFAULT_JIRA_DUMP_FILE)
69  public class JerseyIssueRestClientTest extends AbstractJerseyRestClientTest {
70  
71  	@Test
72  	public void testTransitionWithNumericCustomFieldPolishLocale() throws Exception {
73  		final double newValue = 123.45;
74  		final FieldInput fieldInput;
75  		if (IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER) {
76  			fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, Double.valueOf(newValue));
77  		} else {
78  			fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, NumberFormat.getNumberInstance(new Locale("pl")).format(newValue));
79  		}
80  		assertTransitionWithNumericCustomField(fieldInput, newValue);
81  	}
82  
83  	@Test
84  	public void testTransitionWithNumericCustomFieldEnglishLocale() throws Exception {
85  		setUser1();
86  		final double newValue = 123.45;
87  		final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID,
88  				NumberFormat.getNumberInstance(new Locale("pl")).format(newValue));
89  
90  		assertErrorCode(Response.Status.BAD_REQUEST, IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER
91  				? "Operation value must be a number" : ("'" + fieldInput.getValue() + "' is an invalid number"), new Runnable() {
92  			@Override
93  			public void run() {
94  				assertTransitionWithNumericCustomField(fieldInput, newValue);
95  			}
96  		});
97  
98  		final FieldInput fieldInput2 = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue); // this will be serialized always with "." according to JSL
99  		assertTransitionWithNumericCustomField(fieldInput2, newValue);
100 
101 	}
102 
103 
104 	private void assertTransitionWithNumericCustomField(FieldInput fieldInput, Double expectedValue) {
105 		final Issue issue = client.getIssueClient().getIssue("TST-1", new NullProgressMonitor());
106 		assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
107 		final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue, pm);
108 
109 		final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
110 		assertNotNull(transitionFound);
111 		assertTrue(Iterables.contains(transitionFound.getFields(),
112 				new Transition.Field(NUMERIC_CUSTOMFIELD_ID, false, IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? NUMERIC_CUSTOMFIELD_TYPE_V5 : NUMERIC_CUSTOMFIELD_TYPE)));
113 		client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), Arrays.asList(fieldInput),
114 				Comment.valueOf("My test comment")), new NullProgressMonitor());
115 		final Issue changedIssue = client.getIssueClient().getIssue("TST-1", pm);
116 		assertTrue(changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue().equals(expectedValue));
117 	}
118 
119 	@Test
120 	public void testTransitionWithNumericCustomFieldAndInteger() throws Exception {
121 		final Issue issue = client.getIssueClient().getIssue("TST-1", pm);
122 		assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
123 		final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue, pm);
124 		Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
125 
126 		assertNotNull(transitionFound);
127 		assertTrue(Iterables.contains(transitionFound.getFields(),
128 				new Transition.Field(NUMERIC_CUSTOMFIELD_ID, false, IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? NUMERIC_CUSTOMFIELD_TYPE_V5 : NUMERIC_CUSTOMFIELD_TYPE)));
129 		final double newValue = 123;
130 		final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, newValue);
131 		client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), Arrays.asList(fieldInput),
132 				Comment.valueOf("My test comment")), pm);
133 		final Issue changedIssue = client.getIssueClient().getIssue("TST-1", pm);
134 		assertEquals(newValue, changedIssue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
135 	}
136 
137 	@Test
138 	public void testTransitionWithInvalidNumericField() throws Exception {
139 		final Issue issue = client.getIssueClient().getIssue("TST-1", pm);
140 		assertNull(issue.getField(NUMERIC_CUSTOMFIELD_ID).getValue());
141 		final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue, pm);
142 		final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
143 
144 		assertNotNull(transitionFound);
145 		assertTrue(Iterables.contains(transitionFound.getFields(),
146 				new Transition.Field(NUMERIC_CUSTOMFIELD_ID, false, TESTING_JIRA_5_OR_NEWER ? NUMERIC_CUSTOMFIELD_TYPE_V5 : NUMERIC_CUSTOMFIELD_TYPE)));
147 		final FieldInput fieldInput = new FieldInput(NUMERIC_CUSTOMFIELD_ID, "]432jl");
148 		// warning: Polish language here - I am asserting if the messages are indeed localized
149 		// since 5.0 messages are changed and not localized
150 		assertErrorCode(Response.Status.BAD_REQUEST, TESTING_JIRA_5_OR_NEWER
151 				? "Operation value must be a number" : "']432jl' nie jest prawid\u0142ow\u0105 liczb\u0105", new Runnable() {
152 			@Override
153 			public void run() {
154 				client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), Arrays.asList(fieldInput),
155 						Comment.valueOf("My test comment")), pm);
156 			}
157 		});
158 	}
159 
160 	@Test
161 	public void testTransitionWithNoRoleOrGroup() {
162 		Comment comment = Comment.valueOf("My text which I am just adding " + new DateTime());
163 		testTransitionImpl(comment);
164 	}
165 
166 	@Test
167 	public void testTransitionWithRoleLevel() {
168 		Comment comment = Comment.createWithRoleLevel("My text which I am just adding " + new DateTime(), "Users");
169 		testTransitionImpl(comment);
170 	}
171 
172 	@Test
173 	public void testTransitionWithGroupLevel() {
174 		Comment comment = Comment.createWithGroupLevel("My text which I am just adding " + new DateTime(), "jira-users");
175 		testTransitionImpl(comment);
176 	}
177 
178 	@Test
179 	public void testTransitionWithInvalidRole() {
180 		final Comment comment = Comment.createWithRoleLevel("My text which I am just adding " + new DateTime(), "some-fake-role");
181 		if (IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER) {
182 			assertInvalidCommentInput(comment, "Invalid role level specified.");
183 		} else {
184 			assertInvalidCommentInput(comment, "Invalid role [some-fake-role]");
185 		}
186 	}
187 
188 	@Test
189 	public void testTransitionWithInvalidGroup() {
190 		final Comment comment = Comment.createWithGroupLevel("My text which I am just adding " + new DateTime(), "some-fake-group");
191 		assertInvalidCommentInput(comment, "Group: some-fake-group does not exist.");
192 	}
193 
194 	private void assertInvalidCommentInput(final Comment comment, String expectedErrorMsg) {
195 		final Issue issue = client.getIssueClient().getIssue("TST-1", pm);
196 		final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue, pm);
197 		final Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
198 		final String errorMsg = doesJiraServeCorrectlyErrorMessagesForBadRequestWhileTransitioningIssue()
199 				? expectedErrorMsg : null;
200 		assertErrorCode(Response.Status.BAD_REQUEST, errorMsg, new Runnable() {
201 			@Override
202 			public void run() {
203 				client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), comment), pm);
204 			}
205 		});
206 	}
207 
208 	private void testTransitionImpl(Comment comment) {
209 		final Issue issue = client.getIssueClient().getIssue("TST-1", pm);
210 		final Iterable<Transition> transitions = client.getIssueClient().getTransitions(issue, pm);
211 		Transition transitionFound = TestUtil.getTransitionByName(transitions, "Estimate");
212 		DateTime now = new DateTime();
213 		client.getIssueClient().transition(issue, new TransitionInput(transitionFound.getId(), comment), pm);
214 
215 		final Issue changedIssue = client.getIssueClient().getIssue("TST-1", pm);
216 		final Comment lastComment = Iterables.getLast(changedIssue.getComments());
217 		assertEquals(comment.getBody(), lastComment.getBody());
218 		assertEquals(USER_ADMIN, lastComment.getAuthor());
219 		assertEquals(USER_ADMIN, lastComment.getUpdateAuthor());
220 		assertEquals(lastComment.getCreationDate(), lastComment.getUpdateDate());
221 		assertTrue(lastComment.getCreationDate().isAfter(now) || lastComment.getCreationDate().isEqual(now));
222 		assertEquals(comment.getVisibility(), lastComment.getVisibility());
223 	}
224 
225 	@Test
226 	public void testVoteUnvote() {
227 		final Issue issue1 = client.getIssueClient().getIssue("TST-1", pm);
228 		assertFalse(issue1.getVotes().hasVoted());
229 		assertEquals(1, issue1.getVotes().getVotes()); // the other user has voted
230 		final String expectedMessage = isJira5xOrNewer() // JIRA 5.0 comes without Polish translation OOB
231 				? "You cannot vote for an issue you have reported."
232 				: "Nie mo\u017cesz g\u0142osowa\u0107 na zadanie kt\u00f3re utworzy\u0142e\u015b.";
233 
234 		// I hope that such Polish special characters (for better testing local specific behaviour of REST
235 		assertErrorCode(Response.Status.NOT_FOUND, expectedMessage, new Runnable() {
236 			@Override
237 			public void run() {
238 				client.getIssueClient().vote(issue1.getVotesUri(), pm);
239 			}
240 		});
241 
242 
243 		final String issueKey = "TST-7";
244 		Issue issue = client.getIssueClient().getIssue(issueKey, pm);
245 		assertFalse(issue.getVotes().hasVoted());
246 		assertEquals(0, issue.getVotes().getVotes());
247 
248 		client.getIssueClient().vote(issue.getVotesUri(), pm);
249 		issue = client.getIssueClient().getIssue(issueKey, pm);
250 		assertTrue(issue.getVotes().hasVoted());
251 		assertEquals(1, issue.getVotes().getVotes());
252 
253 		client.getIssueClient().unvote(issue.getVotesUri(), pm);
254 		issue = client.getIssueClient().getIssue(issueKey, pm);
255 		assertFalse(issue.getVotes().hasVoted());
256 		assertEquals(0, issue.getVotes().getVotes());
257 
258 		setUser2();
259 		issue = client.getIssueClient().getIssue(issueKey, pm);
260 		assertFalse(issue.getVotes().hasVoted());
261 		assertEquals(0, issue.getVotes().getVotes());
262 		final Issue finalIssue = issue;
263 		assertErrorCode(Response.Status.NOT_FOUND, "Cannot remove a vote for an issue that the user has not already voted for.",
264 				new Runnable() {
265 			@Override
266 			public void run() {
267 				client.getIssueClient().unvote(finalIssue.getVotesUri(), pm);
268 			}
269 		});
270 
271 
272 		issue = client.getIssueClient().getIssue(issueKey, pm);
273 		assertFalse(issue.getVotes().hasVoted());
274 		assertEquals(0, issue.getVotes().getVotes());
275 		client.getIssueClient().vote(issue.getVotesUri(), pm);
276 		issue = client.getIssueClient().getIssue(issueKey, pm);
277 		assertTrue(issue.getVotes().hasVoted());
278 		assertEquals(1, issue.getVotes().getVotes());
279 
280 		setClient(ADMIN_USERNAME, ADMIN_PASSWORD);
281 		client.getIssueClient().vote(issue.getVotesUri(), pm);
282 		issue = client.getIssueClient().getIssue(issueKey, pm);
283 		assertTrue(issue.getVotes().hasVoted());
284 		assertEquals(2, issue.getVotes().getVotes());
285 	}
286 
287 	@Test
288 	public void testWatchUnwatch() {
289 		final IssueRestClient issueClient = client.getIssueClient();
290 		final Issue issue1 = issueClient.getIssue("TST-1", pm);
291 
292 		Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf(), pm).getUsers(),
293 				Matchers.not(IterableMatcher.contains(USER_ADMIN)));
294 
295 		issueClient.watch(issue1.getWatchers().getSelf(), pm);
296 		Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf(), pm).getUsers(), IterableMatcher.contains(USER_ADMIN));
297 
298 		issueClient.unwatch(issue1.getWatchers().getSelf(), pm);
299 		Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf(), pm).getUsers(), Matchers.not(IterableMatcher.contains(USER_ADMIN)));
300 
301 		Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf(), pm).getUsers(), IterableMatcher.contains(USER1));
302 		issueClient.removeWatcher(issue1.getWatchers().getSelf(), USER1.getName(), pm);
303 		Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf(), pm).getUsers(), Matchers.not(IterableMatcher.contains(USER1)));
304 		issueClient.addWatcher(issue1.getWatchers().getSelf(), USER1.getName(), pm);
305 		Assert.assertThat(issueClient.getWatchers(issue1.getWatchers().getSelf(), pm).getUsers(), IterableMatcher.contains(USER1));
306 	}
307 
308 	@Test
309 	public void testRemoveWatcherUnauthorized() {
310 		final IssueRestClient issueClient = client.getIssueClient();
311 		final Issue issue1 = issueClient.getIssue("TST-1", pm);
312 		issueClient.watch(issue1.getWatchers().getSelf(), pm);
313 
314 		setUser1();
315 		final IssueRestClient issueClient2 = client.getIssueClient();
316 		assertErrorCode(Response.Status.UNAUTHORIZED,
317 				"User 'wseliga' is not allowed to remove watchers from issue 'TST-1'", new Runnable() {
318 			@Override
319 			public void run() {
320 				issueClient2.removeWatcher(issue1.getWatchers().getSelf(), ADMIN_USERNAME, pm);
321 			}
322 		});
323 	}
324 
325 
326 	@Test
327 	public void testWatchAlreadyWatched() {
328 		setUser1();
329 		final IssueRestClient issueClient = client.getIssueClient();
330 		final Issue issue = issueClient.getIssue("TST-1", pm);
331 		Assert.assertThat(client.getIssueClient().getWatchers(issue.getWatchers().getSelf(), pm).getUsers(), IterableMatcher.contains(USER1));
332 		// JIRA allows to watch already watched issue by you - such action effectively has no effect
333 		issueClient.watch(issue.getWatchers().getSelf(), pm);
334 		Assert.assertThat(client.getIssueClient().getWatchers(issue.getWatchers().getSelf(), pm).getUsers(), IterableMatcher.contains(USER1));
335 	}
336 
337 	@Test
338 	public void testAddWatcherUnauthorized() {
339 		final IssueRestClient issueClient = client.getIssueClient();
340 		final Issue issue1 = issueClient.getIssue("TST-1", pm);
341 		issueClient.addWatcher(issue1.getWatchers().getSelf(), USER1_USERNAME, pm);
342 		assertThat(client.getIssueClient().getWatchers(issue1.getWatchers().getSelf(), pm).getUsers(), IterableMatcher.contains(USER1));
343 
344 		setUser1();
345 		assertTrue(client.getIssueClient().getIssue("TST-1", pm).getWatchers().isWatching());
346 		String expectedErrorMsg = isJraDev3516Fixed() ? ("User '" + USER1_USERNAME
347 				+ "' is not allowed to add watchers to issue 'TST-1'") : null;
348 		assertErrorCode(Response.Status.UNAUTHORIZED, expectedErrorMsg, new Runnable() {
349 			@Override
350 			public void run() {
351 				client.getIssueClient().addWatcher(issue1.getWatchers().getSelf(), ADMIN_USERNAME, pm);
352 			}
353 		});
354 	}
355 
356 	private boolean isJraDev3516Fixed() {
357 		return client.getMetadataClient().getServerInfo(pm).getBuildNumber() >= ServerVersionConstants.BN_JIRA_4_3;
358 	}
359 
360 	//@Test restore when JRADEV-3666 is fixed (I don't want to pollute JRJC integration test results)
361 	public void xtestAddWatcherWhoDoesNotHaveViewIssuePermissions() {
362 		final IssueRestClient issueClient = client.getIssueClient();
363 		final Issue issue1 = issueClient.getIssue("RST-1", pm);
364 		assertErrorCode(Response.Status.BAD_REQUEST, "The user \"" + USER2_USERNAME
365 				+ "\" does not have permission to view this issue. This user will not be added to the watch list.",
366 				new Runnable() {
367 					@Override
368 					public void run() {
369 						issueClient.addWatcher(issue1.getWatchers().getSelf(), USER2_USERNAME, pm);
370 					}
371 				});
372 
373 	}
374 
375 	@Test
376 	public void testLinkIssuesWithRoleLevel() {
377 		testLinkIssuesImpl(Comment.createWithRoleLevel("A comment about linking", "Administrators"));
378 	}
379 
380 	@Test
381 	public void testLinkIssuesWithGroupLevel() {
382 		testLinkIssuesImpl(Comment.createWithGroupLevel("A comment about linking", "jira-administrators"));
383 	}
384 
385 	@Test
386 	public void testLinkIssuesWithSimpleComment() {
387 		testLinkIssuesImpl(Comment.valueOf("A comment about linking"));
388 	}
389 
390 	@Test
391 	public void testLinkIssuesWithoutComment() {
392 		testLinkIssuesImpl(null);
393 	}
394 
395 	@Test
396 	public void testLinkIssuesWithInvalidParams() {
397 		if (!doesJiraSupportRestIssueLinking()) {
398 			return;
399 		}
400 		assertErrorCode(Response.Status.NOT_FOUND,
401 				IntegrationTestUtil.TESTING_JIRA_5_OR_NEWER ? "Issue Does Not Exist" : "The issue no longer exists.", new Runnable() {
402 			@Override
403 			public void run() {
404 				client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "FAKEKEY-1", "Duplicate", null), pm);
405 			}
406 		});
407 
408 		assertErrorCode(Response.Status.NOT_FOUND, "No issue link type with name 'NonExistingLinkType' found.", new Runnable() {
409 			@Override
410 			public void run() {
411 				client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "NonExistingLinkType", null), pm);
412 			}
413 		});
414 
415 		setUser1();
416 		final String optionalDot = isJira5xOrNewer() ? "." : "";
417 		assertErrorCode(Response.Status.NOT_FOUND, "You do not have the permission to see the specified issue" + optionalDot, new Runnable() {
418 			@Override
419 			public void run() {
420 				client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "RST-1", "Duplicate", null), pm);
421 			}
422 		});
423 		assertErrorCode(Response.Status.BAD_REQUEST, "Failed to create comment for issue 'TST-6'\nYou are currently not a member of the project role: Administrators.", new Runnable() {
424 			@Override
425 			public void run() {
426 				client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate",
427 						Comment.createWithRoleLevel("my body", "Administrators")), pm);
428 			}
429 		});
430 		assertErrorCode(Response.Status.BAD_REQUEST, "You are currently not a member of the group: jira-administrators.", new Runnable() {
431 			@Override
432 			public void run() {
433 				client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate",
434 						Comment.createWithGroupLevel("my body", "jira-administrators")), pm);
435 			}
436 		});
437 		assertErrorCode(Response.Status.BAD_REQUEST, "Group: somefakegroup does not exist.", new Runnable() {
438 			@Override
439 			public void run() {
440 				client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate",
441 						Comment.createWithGroupLevel("my body", "somefakegroup")), pm);
442 			}
443 		});
444 
445 
446 		setUser2();
447 		assertErrorCode(Response.Status.UNAUTHORIZED, "No Link Issue Permission for issue 'TST-7'", new Runnable() {
448 			@Override
449 			public void run() {
450 				client.getIssueClient().linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate", null), pm);
451 			}
452 		});
453 
454 	}
455 
456 
457 	private void testLinkIssuesImpl(@Nullable Comment commentInput) {
458 		if (!doesJiraSupportRestIssueLinking()) {
459 			return;
460 		}
461 
462 		final IssueRestClient issueClient = client.getIssueClient();
463 		final Issue originalIssue = issueClient.getIssue("TST-7", pm);
464 		int origNumComments = Iterables.size(originalIssue.getComments());
465 		assertFalse(originalIssue.getIssueLinks().iterator().hasNext());
466 
467 		issueClient.linkIssue(new LinkIssuesInput("TST-7", "TST-6", "Duplicate", commentInput), pm);
468 
469 		final Issue linkedIssue = issueClient.getIssue("TST-7", pm);
470 		assertEquals(1, Iterables.size(linkedIssue.getIssueLinks()));
471 		final IssueLink addedLink = linkedIssue.getIssueLinks().iterator().next();
472 		assertEquals("Duplicate", addedLink.getIssueLinkType().getName());
473 		assertEquals("TST-6", addedLink.getTargetIssueKey());
474 		assertEquals(IssueLinkType.Direction.OUTBOUND, addedLink.getIssueLinkType().getDirection());
475 
476 		final int expectedNumComments = commentInput != null ? origNumComments + 1 : origNumComments;
477 		assertEquals(expectedNumComments, Iterables.size(linkedIssue.getComments()));
478 		if (commentInput != null) {
479 			final Comment comment = linkedIssue.getComments().iterator().next();
480 			assertEquals(commentInput.getBody(), comment.getBody());
481 			assertEquals(USER_ADMIN, comment.getAuthor());
482 			assertEquals(commentInput.getVisibility(), comment.getVisibility());
483 		} else {
484 			assertFalse(linkedIssue.getComments().iterator().hasNext());
485 		}
486 
487 
488 		final Issue targetIssue = issueClient.getIssue("TST-6", pm);
489 		final IssueLink targetLink = targetIssue.getIssueLinks().iterator().next();
490 		assertEquals(IssueLinkType.Direction.INBOUND, targetLink.getIssueLinkType().getDirection());
491 		assertEquals("Duplicate", targetLink.getIssueLinkType().getName());
492 	}
493 
494 	private boolean doesJiraSupportAddingAttachment() {
495 		return client.getMetadataClient().getServerInfo(pm).getBuildNumber() >= ServerVersionConstants.BN_JIRA_4_3;
496 	}
497 
498 	private boolean doesJiraServeCorrectlyErrorMessagesForBadRequestWhileTransitioningIssue() {
499 		return client.getMetadataClient().getServerInfo(pm).getBuildNumber() >= ServerVersionConstants.BN_JIRA_4_3;
500 	}
501 
502 	@Test
503 	public void testAddAttachment() throws IOException {
504 		if (!doesJiraSupportAddingAttachment()) {
505 			return;
506 		}
507 		final IssueRestClient issueClient = client.getIssueClient();
508 		final Issue issue = issueClient.getIssue("TST-3", pm);
509 		assertFalse(issue.getAttachments().iterator().hasNext());
510 
511 		String str = "Wojtek";
512 		final String filename1 = "my-test-file";
513 		issueClient.addAttachment(pm, issue.getAttachmentsUri(), new ByteArrayInputStream(str.getBytes("UTF-8")), filename1);
514 		final String filename2 = "my-picture.png";
515 		issueClient.addAttachment(pm, issue.getAttachmentsUri(), JerseyIssueRestClientTest.class.getResourceAsStream("/attachment-test/transparent-png.png"), filename2);
516 
517 		final Issue issueWithAttachments = issueClient.getIssue("TST-3", pm);
518 		final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
519 		assertEquals(2, Iterables.size(attachments));
520 		final Iterable<String> attachmentsNames = Iterables.transform(attachments, new Function<Attachment, String>() {
521 			@Override
522 			public String apply(@Nullable Attachment from) {
523 				return from.getFilename();
524 			}
525 		});
526 		assertThat(attachmentsNames, IterableMatcher.hasOnlyElements(filename1, filename2));
527 		final Attachment pictureAttachment = Iterables.find(attachments, new Predicate<Attachment>() {
528 			@Override
529 			public boolean apply(@Nullable Attachment input) {
530 				return filename2.equals(input.getFilename());
531 			}
532 		});
533 
534 		// let's download it now and compare it's binary content
535 
536 		assertTrue(
537 				IOUtils.contentEquals(JerseyIssueRestClientTest.class.getResourceAsStream("/attachment-test/transparent-png.png"),
538 						issueClient.getAttachment(pm, pictureAttachment.getContentUri())));
539 	}
540 
541 	@Test
542 	public void testAddAttachments() throws IOException {
543 		if (!doesJiraSupportAddingAttachment()) {
544 			return;
545 		}
546 		final IssueRestClient issueClient = client.getIssueClient();
547 		final Issue issue = issueClient.getIssue("TST-4", pm);
548 		assertFalse(issue.getAttachments().iterator().hasNext());
549 
550 		final AttachmentInput[] attachmentInputs = new AttachmentInput[3];
551 		for (int i = 1; i <= 3; i++) {
552 			attachmentInputs[i - 1] = new AttachmentInput("my-test-file-" + i + ".txt", new ByteArrayInputStream(("content-of-the-file-" + i).getBytes("UTF-8")));
553 		}
554 		issueClient.addAttachments(pm, issue.getAttachmentsUri(), attachmentInputs);
555 
556 		final Issue issueWithAttachments = issueClient.getIssue("TST-4", pm);
557 		final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
558 		assertEquals(3, Iterables.size(attachments));
559 		Pattern pattern = Pattern.compile("my-test-file-(\\d)\\.txt");
560 		for (Attachment attachment : attachments) {
561 			assertTrue(pattern.matcher(attachment.getFilename()).matches());
562 			final Matcher matcher = pattern.matcher(attachment.getFilename());
563 			matcher.find();
564 			final String interfix = matcher.group(1);
565 			assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(("content-of-the-file-" + interfix).getBytes("UTF-8")),
566 					issueClient.getAttachment(pm, attachment.getContentUri())));
567 
568 		}
569 	}
570 
571 	@Test
572 	public void testAddFileAttachments() throws IOException {
573 		if (!doesJiraSupportAddingAttachment()) {
574 			return;
575 		}
576 		final IssueRestClient issueClient = client.getIssueClient();
577 		final Issue issue = issueClient.getIssue("TST-5", pm);
578 		assertFalse(issue.getAttachments().iterator().hasNext());
579 
580 		final File tempFile = File.createTempFile("jim-integration-test", ".txt");
581 		tempFile.deleteOnExit();
582 		FileWriter writer = new FileWriter(tempFile);
583 		writer.write("This is the content of my file which I am going to upload to JIRA for testing.");
584 		writer.close();
585 		issueClient.addAttachments(pm, issue.getAttachmentsUri(), tempFile);
586 
587 		final Issue issueWithAttachments = issueClient.getIssue("TST-5", pm);
588 		final Iterable<Attachment> attachments = issueWithAttachments.getAttachments();
589 		assertEquals(1, Iterables.size(attachments));
590 		assertTrue(IOUtils.contentEquals(new FileInputStream(tempFile),
591 				issueClient.getAttachment(pm, attachments.iterator().next().getContentUri())));
592 	}
593 
594 
595 	@Test
596 	public void testFetchingUnassignedIssue() {
597 		administration.generalConfiguration().setAllowUnassignedIssues(true);
598 		assertEquals(USER_ADMIN, client.getIssueClient().getIssue("TST-5", pm).getAssignee());
599 
600 		navigation.userProfile().changeUserLanguage("angielski (UK)");
601 		navigation.issue().unassignIssue("TST-5", "unassigning issue");
602 		// this single line does instead of 2 above - func test suck with non-English locale
603 		// but it does not work yet with JIRA 5.0-resthack...
604 		//navigation.issue().assignIssue("TST-5", "unassigning issue", "Nieprzydzielone");
605 
606 		assertNull(client.getIssueClient().getIssue("TST-5", pm).getAssignee());
607 	}
608 
609 	@Test
610 	public void testFetchingIssueWithAnonymousComment() {
611 		navigation.userProfile().changeUserLanguage("angielski (UK)");
612 		administration.permissionSchemes().scheme("Anonymous Permission Scheme").grantPermissionToGroup(15, "");
613 		assertEquals(USER_ADMIN, client.getIssueClient().getIssue("TST-5", pm).getAssignee());
614 		navigation.logout();
615 		navigation.issue().addComment("ANNON-1", "my nice comment");
616 		final Issue issue = client.getIssueClient().getIssue("ANNON-1", pm);
617 		assertEquals(1, Iterables.size(issue.getComments()));
618 		final Comment comment = issue.getComments().iterator().next();
619 		assertEquals("my nice comment", comment.getBody());
620 		assertNull(comment.getAuthor());
621 		assertNull(comment.getUpdateAuthor());
622 
623 	}
624 
625 }