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 com.atlassian.jira.rest.client.internal.json;
18  
19  import com.atlassian.jira.rest.client.BasicComponentNameExtractionFunction;
20  import com.atlassian.jira.rest.client.domain.Attachment;
21  import com.atlassian.jira.rest.client.domain.BasicComponent;
22  import com.atlassian.jira.rest.client.domain.BasicIssueType;
23  import com.atlassian.jira.rest.client.domain.BasicPriority;
24  import com.atlassian.jira.rest.client.domain.BasicProject;
25  import com.atlassian.jira.rest.client.domain.BasicUser;
26  import com.atlassian.jira.rest.client.domain.BasicWatchers;
27  import com.atlassian.jira.rest.client.domain.ChangelogGroup;
28  import com.atlassian.jira.rest.client.domain.ChangelogItem;
29  import com.atlassian.jira.rest.client.domain.Comment;
30  import com.atlassian.jira.rest.client.domain.Field;
31  import com.atlassian.jira.rest.client.domain.Issue;
32  import com.atlassian.jira.rest.client.domain.IssueLink;
33  import com.atlassian.jira.rest.client.domain.IssueLinkType;
34  import com.atlassian.jira.rest.client.domain.Subtask;
35  import com.atlassian.jira.rest.client.domain.TimeTracking;
36  import com.atlassian.jira.rest.client.domain.Visibility;
37  import com.atlassian.jira.rest.client.domain.Worklog;
38  import com.google.common.collect.ImmutableList;
39  import com.google.common.collect.Iterables;
40  import org.codehaus.jettison.json.JSONException;
41  import org.codehaus.jettison.json.JSONObject;
42  import org.hamcrest.collection.IsEmptyCollection;
43  import org.hamcrest.collection.IsEmptyIterable;
44  import org.joda.time.format.ISODateTimeFormat;
45  import org.junit.Assert;
46  import org.junit.Test;
47  
48  import java.util.Iterator;
49  
50  import static com.atlassian.jira.rest.client.TestUtil.toDateTime;
51  import static com.atlassian.jira.rest.client.TestUtil.toDateTimeFromIsoDate;
52  import static com.atlassian.jira.rest.client.TestUtil.toUri;
53  import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
54  import static org.junit.Assert.*;
55  
56  // Ignore "May produce NPE" warnings, as we know what we are doing in tests
57  @SuppressWarnings("ConstantConditions")
58  public class IssueJsonParserTest {
59  	@Test
60  	public void testParseIssue() throws Exception {
61  		final Issue issue = parseIssue("/json/issue/valid-all-expanded.json");
62  		assertExpectedIssue(issue);
63  		assertEquals(new BasicIssueType(toUri("http://localhost:8090/jira/rest/api/latest/issueType/1"), 1L, "Bug", false),
64  				issue.getIssueType());
65  	}
66  
67  	@Test
68  	public void testParseIssueJira4x2() throws Exception {
69  		final Issue issue = parseIssue("/json/issue/valid-all-expanded-jira-4-2.json");
70  		assertExpectedIssue(issue);
71  		assertEquals(new BasicIssueType(toUri("http://localhost:8090/jira/rest/api/latest/issueType/1"), null, "Bug", false),
72  				issue.getIssueType());
73  	}
74  
75  	private void assertExpectedIssue(Issue issue) {
76  		assertEquals("Testing issue", issue.getSummary());
77  		assertEquals("TST-2", issue.getKey());
78  		assertEquals(new BasicProject(toUri("http://localhost:8090/jira/rest/api/latest/project/TST"), "TST", null), issue.getProject());
79  		assertEquals("Major", issue.getPriority().getName());
80  		assertNull(issue.getResolution());
81  		assertEquals(toDateTime("2010-07-26T13:29:18.262+0200"), issue.getCreationDate());
82  		assertEquals(toDateTime("2010-08-27T15:00:02.107+0200"), issue.getUpdateDate());
83  		assertEquals(null, issue.getDueDate());
84  
85  		assertEquals(TestConstants.USER_ADMIN, issue.getReporter());
86  		assertEquals(TestConstants.USER1, issue.getAssignee());
87  
88  		// issue links
89  		Assert.assertThat(issue.getIssueLinks(), containsInAnyOrder(
90  				new IssueLink("TST-1", toUri("http://localhost:8090/jira/rest/api/latest/issue/TST-1"),
91  						new IssueLinkType("Duplicate", "duplicates", IssueLinkType.Direction.OUTBOUND)),
92  				new IssueLink("TST-1", toUri("http://localhost:8090/jira/rest/api/latest/issue/TST-1"),
93  						new IssueLinkType("Duplicate", "is duplicated by", IssueLinkType.Direction.INBOUND))
94  		));
95  
96  
97  		// watchers
98  		final BasicWatchers watchers = issue.getWatchers();
99  		assertFalse(watchers.isWatching());
100 		assertEquals(toUri("http://localhost:8090/jira/rest/api/latest/issue/TST-2/watchers"), watchers.getSelf());
101 		assertEquals(1, watchers.getNumWatchers());
102 		assertEquals(new TimeTracking(0, 0, 145), issue.getTimeTracking());
103 
104 		// attachments
105 		final Iterable<Attachment> attachments = issue.getAttachments();
106 		assertEquals(3, Iterables.size(attachments));
107 		final Attachment attachment = attachments.iterator().next();
108 		assertEquals("jira_logo.gif", attachment.getFilename());
109 		assertEquals(TestConstants.USER_ADMIN, attachment.getAuthor());
110 		assertEquals(2517, attachment.getSize());
111 		assertEquals(toUri("http://localhost:8090/jira/secure/thumbnail/10036/10036_jira_logo.gif"), attachment.getThumbnailUri());
112 		final Iterator<Attachment> attachmentIt = attachments.iterator();
113 		attachmentIt.next();
114 		attachmentIt.next();
115 		final Attachment lastAttachment = attachmentIt.next();
116 		assertEquals("transparent-png.png", lastAttachment.getFilename());
117 
118 		// worklogs
119 		final Iterable<Worklog> worklogs = issue.getWorklogs();
120 		assertEquals(5, Iterables.size(worklogs));
121 		final Worklog worklog = Iterables.get(worklogs, 2);
122 		assertEquals(new Worklog(toUri("http://localhost:8090/jira/rest/api/latest/worklog/10012"),
123 				toUri("http://localhost:8090/jira/rest/api/latest/issue/TST-2"), TestConstants.USER1,
124 				TestConstants.USER1, "a worklog viewable just by jira-users",
125 				toDateTime("2010-08-17T16:53:15.848+0200"), toDateTime("2010-08-17T16:53:15.848+0200"),
126 				toDateTime("2010-08-11T16:52:00.000+0200"), 3, Visibility.group("jira-users")), worklog);
127 
128 		final Worklog worklog3 = Iterables.get(worklogs, 3);
129 		assertEquals("", worklog3.getComment());
130 
131 		// comments
132 		assertEquals(3, Iterables.size(issue.getComments()));
133 		final Comment comment = issue.getComments().iterator().next();
134 		assertEquals(Visibility.Type.ROLE, comment.getVisibility().getType());
135 		assertEquals(TestConstants.USER_ADMIN, comment.getAuthor());
136 		assertEquals(TestConstants.USER_ADMIN, comment.getUpdateAuthor());
137 	}
138 
139 	private Issue parseIssue(final String resourcePath) throws JSONException {
140 		final JSONObject issueJson = ResourceUtil.getJsonObjectFromResource(resourcePath);
141 		final IssueJsonParser parser = new IssueJsonParser();
142 		return parser.parse(issueJson);
143 	}
144 
145 	@Test
146 	public void testParseIssueWithResolution() throws JSONException {
147 		final Issue issue = parseIssue("/json/issue/valid-all-expanded-with-resolution.json");
148 		assertEquals("Incomplete", issue.getResolution().getName());
149 
150 	}
151 
152 	@Test
153 	public void testParseIssueWhenWatchersAndVotersAreSwitchedOff() throws JSONException {
154 		final Issue issue = parseIssue("/json/issue/valid-no-votes-no-watchers.json");
155 		assertNull(issue.getWatchers());
156 		assertNull(issue.getVotes());
157 	}
158 
159 	@Test
160 	public void testParseUnassignedIssue() throws JSONException {
161 		final Issue issue = parseIssue("/json/issue/valid-unassigned.json");
162 		assertNull(issue.getAssignee());
163 	}
164 
165 	@Test
166 	public void testParseNoTimeTrackingInfo() throws JSONException {
167 		final Issue issue = parseIssue("/json/issue/valid-unassigned.json");
168 		assertNull(issue.getTimeTracking());
169 	}
170 
171 	@Test
172 	public void testParseUnassignedIssueJira4x3() throws JSONException {
173 		final Issue issue = parseIssue("/json/issue/valid-unassigned-jira-4.3.json");
174 		assertNull(issue.getAssignee());
175 	}
176 
177 	@Test
178 	public void testParseIssueWithAnonymousComment() throws JSONException {
179 		final Issue issue = parseIssue("/json/issue/valid-anonymous-comment-jira-4.3.json");
180 		assertEquals(1, Iterables.size(issue.getComments()));
181 		final Comment comment = issue.getComments().iterator().next();
182 		assertEquals("A comment from anonymous user", comment.getBody());
183 		assertNull(comment.getAuthor());
184 
185 	}
186 
187 	@Test
188 	public void testParseIssueWithVisibilityJira4x3() throws JSONException {
189 		final Issue issue = parseIssue("/json/issue/valid-visibility-jira-4.3.json");
190 		assertEquals(Visibility.role("Administrators"), issue.getComments().iterator().next().getVisibility());
191 		assertEquals(Visibility.role("Developers"), Iterables.get(issue.getWorklogs(), 1).getVisibility());
192 		assertEquals(Visibility.group("jira-users"), Iterables.get(issue.getWorklogs(), 2).getVisibility());
193 	}
194 
195 	@Test
196 	public void testParseIssueWithUserPickerCustomFieldFilledOut() throws JSONException {
197 		final Issue issue = parseIssue("/json/issue/valid-user-picker-custom-field-filled-out.json");
198 		final Field extraUserField = issue.getFieldByName("Extra User");
199 		assertNotNull(extraUserField);
200 		assertEquals(BasicUser.class, extraUserField.getValue().getClass());
201 		assertEquals(TestConstants.USER1, extraUserField.getValue());
202 	}
203 
204 	@Test
205 	public void testParseIssueWithUserPickerCustomFieldEmpty() throws JSONException {
206 		final Issue issue = parseIssue("/json/issue/valid-user-picker-custom-field-empty.json");
207 		final Field extraUserField = issue.getFieldByName("Extra User");
208 		assertNotNull(extraUserField);
209 		assertNull(extraUserField.getValue());
210 	}
211 
212 	@Test
213 	public void testParseIssueJira5x0Representation() throws JSONException {
214 		final Issue issue = parseIssue("/json/issue/valid-5.0.json");
215 		assertEquals(3, Iterables.size(issue.getComments()));
216 		final BasicPriority priority = issue.getPriority();
217 		assertNotNull(priority);
218 		assertEquals("Major", priority.getName());
219 		assertEquals("my description", issue.getDescription());
220 		assertEquals("TST", issue.getProject().getKey());
221 		assertNotNull(issue.getDueDate());
222 		assertEquals(toDateTimeFromIsoDate("2010-07-05"), issue.getDueDate());
223 		assertEquals(4, Iterables.size(issue.getAttachments()));
224 		assertEquals(1, Iterables.size(issue.getIssueLinks()));
225 		assertEquals(1.457, issue.getField("customfield_10000").getValue());
226 		assertThat(Iterables.transform(issue.getComponents(), new BasicComponentNameExtractionFunction()), containsInAnyOrder("Component A", "Component B"));
227 		assertEquals(2, Iterables.size(issue.getWorklogs()));
228 		assertEquals(1, issue.getWatchers().getNumWatchers());
229 		assertFalse(issue.getWatchers().isWatching());
230 		assertEquals(new TimeTracking(2700, 2220, 180), issue.getTimeTracking());
231 
232 		assertEquals(Visibility.role("Developers"), issue.getWorklogs().iterator().next().getVisibility());
233 		assertEquals(Visibility.group("jira-users"), Iterables.get(issue.getWorklogs(), 1).getVisibility());
234 
235 	}
236 
237 	@Test
238 	public void testParseIssueJira50Representation() throws JSONException {
239 		final Issue issue = parseIssue("/json/issue/valid-5.0-1.json");
240 		assertEquals(0, Iterables.size(issue.getComments()));
241 		final BasicPriority priority = issue.getPriority();
242 		assertNull(priority);
243 		assertEquals("Pivotal Tracker provides time tracking information on the project level.\n"
244 				+ "JIRA stores time tracking information on issue level, so this issue has been created to store imported time tracking information.", issue.getDescription());
245 		assertEquals("TIMETRACKING", issue.getProject().getKey());
246 		assertNull(issue.getDueDate());
247 		assertEquals(0, Iterables.size(issue.getAttachments()));
248 		assertNull(issue.getIssueLinks());
249 		assertNull(issue.getField("customfield_10000").getValue());
250 		assertThat(issue.getComponents(), IsEmptyIterable.<BasicComponent>emptyIterable());
251 		assertEquals(2, Iterables.size(issue.getWorklogs()));
252 		assertEquals(0, issue.getWatchers().getNumWatchers());
253 		assertFalse(issue.getWatchers().isWatching());
254 		assertEquals(new TimeTracking(null, null, 840), issue.getTimeTracking());
255 
256 		assertNull(issue.getWorklogs().iterator().next().getVisibility());
257 		assertNull(Iterables.get(issue.getWorklogs(), 1).getVisibility());
258 	}
259 
260     @Test
261     public void testParseIssueWithProjectNamePresentInRepresentation() throws JSONException {
262         final Issue issue = parseIssue("/json/issue/issue-with-project-name-present.json");
263         assertEquals("My Test Project", issue.getProject().getName());
264     }
265 
266     @Test
267     public void testParseIssueJiraRepresentationJrjc49() throws JSONException {
268         final Issue issue = parseIssue("/json/issue/jrjc49.json");
269         final Iterable<Worklog> worklogs = issue.getWorklogs();
270         assertEquals(1, Iterables.size(worklogs));
271         final Worklog worklog = Iterables.get(worklogs, 0);
272         assertNull(worklog.getComment());
273         assertEquals(180, worklog.getMinutesSpent());
274         assertEquals("Sample, User", worklog.getAuthor().getDisplayName());
275 
276     }
277 
278 	@Test
279 	public void testParseIssueJira5x0RepresentationNullCustomField() throws JSONException {
280 		final Issue issue = parseIssue("/json/issue/valid-5.0-null-custom-field.json");
281 		assertEquals(null, issue.getField("customfield_10000").getValue());
282 		assertNull(issue.getIssueLinks());
283 	}
284 
285 	@Test
286 	public void issueWithSubtasks() throws JSONException {
287 		final Issue issue = parseIssue("/json/issue/subtasks-5.json");
288 		Iterable<Subtask> subtasks = issue.getSubtasks();
289 		assertEquals(1, Iterables.size(subtasks));
290 		Subtask subtask = Iterables.get(subtasks, 0, null);
291 		assertNotNull(subtask);
292 		assertEquals("SAM-2", subtask.getIssueKey());
293 		assertEquals("Open", subtask.getStatus().getName());
294 		assertEquals("Subtask", subtask.getIssueType().getName());
295 	}
296 
297 	@Test
298 	public void issueWithChangelog() throws JSONException {
299 		final Issue issue = parseIssue("/json/issue/valid-5.0-with-changelog.json");
300 		assertEquals("HST-1", issue.getKey());
301 
302 		final Iterable<ChangelogGroup> changelog = issue.getChangelog();
303 		assertNotNull(changelog);
304 
305 		assertEquals(4, Iterables.size(changelog));
306 		final Iterator<ChangelogGroup> iterator = changelog.iterator();
307 
308 		final BasicUser user1 = new BasicUser(toUri("http://localhost:2990/jira/rest/api/2/user?username=user1"), "user1", "User One");
309 		final BasicUser user2 = new BasicUser(toUri("http://localhost:2990/jira/rest/api/2/user?username=user2"), "user2", "User Two");
310 
311 		verifyChangelog(iterator.next(),
312 				"2012-04-12T14:28:28.255+0200",
313 				user1,
314 				ImmutableList.of(
315 						new ChangelogItem(ChangelogItem.FieldType.JIRA, "duedate", null, null, "2012-04-12", "2012-04-12 00:00:00.0"),
316 						new ChangelogItem(ChangelogItem.FieldType.CUSTOM, "Radio Field", null, null, "10000", "One")
317 				));
318 
319 		verifyChangelog(iterator.next(),
320 				"2012-04-12T14:28:44.079+0200",
321 				user1,
322 				ImmutableList.of(
323 						new ChangelogItem(ChangelogItem.FieldType.JIRA, "assignee", "user1", "User One", "user2", "User Two")
324 				));
325 
326 		verifyChangelog(iterator.next(),
327 				"2012-04-12T14:30:09.690+0200",
328 				user2,
329 				ImmutableList.of(
330 						new ChangelogItem(ChangelogItem.FieldType.JIRA, "summary", null, "Simple history test", null, "Simple history test - modified"),
331 						new ChangelogItem(ChangelogItem.FieldType.JIRA, "issuetype", "1", "Bug", "2", "New Feature"),
332 						new ChangelogItem(ChangelogItem.FieldType.JIRA, "priority", "3", "Major", "4", "Minor"),
333 						new ChangelogItem(ChangelogItem.FieldType.JIRA, "description", null, "Initial Description", null, "Modified Description"),
334 						new ChangelogItem(ChangelogItem.FieldType.CUSTOM, "Date Field", "2012-04-11T14:26+0200", "11/Apr/12 2:26 PM", "2012-04-12T14:26+0200", "12/Apr/12 2:26 PM"),
335 						new ChangelogItem(ChangelogItem.FieldType.JIRA, "duedate", "2012-04-12", "2012-04-12 00:00:00.0", "2012-04-13", "2012-04-13 00:00:00.0"),
336 						new ChangelogItem(ChangelogItem.FieldType.CUSTOM, "Radio Field", "10000", "One", "10001", "Two"),
337 						new ChangelogItem(ChangelogItem.FieldType.CUSTOM, "Text Field", null, "Initial text field value", null, "Modified text field value")
338 				));
339 
340 		verifyChangelog(iterator.next(),
341 				"2012-04-12T14:28:44.079+0200",
342 				null,
343 				ImmutableList.of(
344 						new ChangelogItem(ChangelogItem.FieldType.JIRA, "assignee", "user1", "User One", "user2", "User Two")
345 				));
346 	}
347 
348 	private static void verifyChangelog(ChangelogGroup changelogGroup, String createdDate, BasicUser author, Iterable<ChangelogItem> expectedItems) {
349 		assertEquals(ISODateTimeFormat.dateTime().parseDateTime(createdDate), changelogGroup.getCreated());
350 		assertEquals(author, changelogGroup.getAuthor());
351 		assertEquals(expectedItems, changelogGroup.getItems());
352 	}
353 
354 	@Test
355 	public void testParseIssueWithLabelsForJira5x0() throws JSONException {
356 		final Issue issue = parseIssue("/json/issue/valid-5.0-with-labels.json");
357 		assertThat(issue.getLabels(), containsInAnyOrder("a", "bcds"));
358 	}
359 
360 	@Test
361 	public void testParseIssueWithLabels() throws JSONException {
362 		final Issue issue = parseIssue("/json/issue/valid-5.0-with-labels.json");
363 		assertThat(issue.getLabels(), containsInAnyOrder("a", "bcds"));
364 	}
365 
366 	@Test
367 	public void testParseIssueWithoutLabelsForJira5x0() throws JSONException {
368 		final Issue issue = parseIssue("/json/issue/valid-5.0-without-labels.json");
369 		assertThat(issue.getLabels(), IsEmptyCollection.<String>empty());
370 	}
371 
372 	@Test
373 	public void testParseIssueWithoutLabels() throws JSONException {
374 		final Issue issue = parseIssue("/json/issue/valid-without-labels.json");
375 		assertThat(issue.getLabels(), IsEmptyCollection.<String>empty());
376 	}
377 
378 }