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