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