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