001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.api.util; 015 016import org.gbif.api.model.common.DOI; 017import org.gbif.api.model.registry.CitationContact; 018import org.gbif.api.model.registry.Contact; 019import org.gbif.api.model.registry.Dataset; 020import org.gbif.api.model.registry.Endpoint; 021import org.gbif.api.model.registry.Organization; 022import org.gbif.api.vocabulary.ContactType; 023import org.gbif.api.vocabulary.DatasetType; 024 025import java.time.LocalDate; 026import java.time.ZoneId; 027import java.util.Arrays; 028import java.util.Collections; 029import java.util.Date; 030import java.util.EnumSet; 031import java.util.List; 032import java.util.Set; 033import java.util.UUID; 034 035import org.gbif.api.vocabulary.EndpointType; 036import org.junit.jupiter.api.Test; 037 038import static org.gbif.api.model.common.DOI.TEST_PREFIX; 039import static org.gbif.api.util.CitationGenerator.getAuthors; 040import static org.junit.jupiter.api.Assertions.assertEquals; 041import static org.junit.jupiter.api.Assertions.assertNotNull; 042import static org.junit.jupiter.api.Assertions.assertTrue; 043 044/** Unit tests related to {@link CitationGenerator}. */ 045public class CitationGeneratorTest { 046 047 @Test 048 public void testCamtrapCitation() { 049 Organization org = new Organization(); 050 org.setTitle("Research Institute for Nature and Forest (INBO)"); 051 052 Dataset dataset = getCamtrapDataset(); 053 dataset.getContacts().add(createContact("Jim", "Casaer", ContactType.ORIGINATOR)); 054 dataset.getContacts().add(createContact("Niko", "Boone", ContactType.ORIGINATOR)); 055 dataset.getContacts().add(createContact("Jan", "Vercammen", ContactType.ORIGINATOR)); 056 dataset.getContacts().add(createContact("Sander", "Devisscher", ContactType.ORIGINATOR)); 057 dataset.getContacts().add(createContact("Lynn", "Pallemaerts", ContactType.ORIGINATOR)); 058 dataset.getContacts().add(createContact("Anneleen", "Rutten", ContactType.ORIGINATOR)); 059 dataset.getContacts().add(createContact("Martijn", "Bollen", ContactType.ORIGINATOR)); 060 dataset.getContacts().add(createContact("Peter", "Desmet", ContactType.ORIGINATOR)); 061 dataset.getContacts().add(createContact("Sanne", "Govaert", ContactType.ORIGINATOR)); 062 dataset.getContacts().add(createContact("Jim", "Casaer", ContactType.METADATA_AUTHOR)); 063 dataset.getContacts().add(createContact("Jim", "Casaer", ContactType.ADMINISTRATIVE_POINT_OF_CONTACT)); 064 065 CitationGenerator.CitationData citation = CitationGenerator.generateCitation(dataset, org); 066 067 String expectedCitation = "Casaer J, Boone N, Vercammen J, Devisscher S, Pallemaerts L, Rutten A, Bollen M, " 068 + "Desmet P, Govaert S (2025). GMU8_LEUVEN - Camera trap observations in natural habitats south of Leuven " 069 + "(Belgium). Research Institute for Nature and Forest (INBO). " 070 + "Occurrence dataset https://doi.org/10.15468/4u3wm4 accessed via GBIF.org on " 071 + LocalDate.now(ZoneId.of("UTC")) 072 + "."; 073 074 assertEquals(expectedCitation, citation.getCitation().getText()); 075 } 076 077 @Test 078 public void testAuthorNames() { 079 Contact c = new Contact(); 080 c.setLastName("Doe"); 081 c.setFirstName("John D."); 082 assertEquals("Doe J D", CitationGenerator.getAuthorName(c)); 083 assertEquals(0, getAuthors(Collections.singletonList(c)).size()); 084 085 // test with missing first name 086 c = new Contact(); 087 c.setLastName("Doe"); 088 c.setOrganization("Awesome Organization"); 089 assertEquals("Doe", CitationGenerator.getAuthorName(c)); 090 assertEquals(0, getAuthors(Collections.singletonList(c)).size()); 091 092 // test with missing parts 093 c = new Contact(); 094 c.setFirstName("John"); 095 c.setOrganization("Awesome Organization"); 096 assertEquals("Awesome Organization", CitationGenerator.getAuthorName(c)); 097 assertEquals(0, getAuthors(Collections.singletonList(c)).size()); 098 } 099 100 @Test 101 public void testCompleteCitation() { 102 Organization org = new Organization(); 103 org.setTitle("Cited Organization"); 104 105 Dataset dataset = getTestDatasetObject(); 106 dataset.getContacts().add(createContact("John D.", "Doe", ContactType.ORIGINATOR)); 107 108 CitationGenerator.CitationData citation = CitationGenerator.generateCitation(dataset, org); 109 110 assertEquals( 111 "Doe J D (2009). Dataset to be cited. Version 2.1. Cited Organization. " 112 + "Sampling event dataset https://doi.org/10.21373/abcd accessed via GBIF.org on " 113 + LocalDate.now(ZoneId.of("UTC")) 114 + ".", 115 citation.getCitation().getText()); 116 assertEquals(1, citation.getContacts().size()); 117 } 118 119 @Test 120 public void testCompleteCitationUserWithoutName() { 121 Organization org = new Organization(); 122 org.setTitle("Cited Organization"); 123 124 Dataset dataset = getTestDatasetObject(); 125 dataset.getContacts().add(createContact("John D.", "Doe", ContactType.ORIGINATOR)); 126 dataset.getContacts().add(createContact(" ", "Smith", ContactType.ORIGINATOR)); 127 dataset.getContacts().add(createContact("John", null, ContactType.ORIGINATOR)); 128 dataset.getContacts().add(createContact(null, "Mendez", ContactType.ORIGINATOR)); 129 130 CitationGenerator.CitationData citation = CitationGenerator.generateCitation(dataset, org); 131 132 assertEquals( 133 "Doe J D, Smith, Mendez (2009). Dataset to be cited. Version 2.1. Cited Organization. " 134 + "Sampling event dataset https://doi.org/10.21373/abcd accessed via GBIF.org on " 135 + LocalDate.now(ZoneId.of("UTC")) 136 + ".", 137 citation.getCitation().getText()); 138 139 assertEquals(3, citation.getContacts().size()); 140 } 141 142 @Test 143 public void testCompleteCitationNoAuthors() { 144 Organization org = new Organization(); 145 org.setTitle("Cited Organization"); 146 147 Dataset dataset = getTestDatasetObject(); 148 dataset 149 .getContacts() 150 .add( 151 createContact( 152 null, 153 null, 154 "We are not using this field int the citation", 155 ContactType.ORIGINATOR)); 156 157 CitationGenerator.CitationData citation = CitationGenerator.generateCitation(dataset, org); 158 159 assertEquals( 160 "Cited Organization (2009). Dataset to be cited. Version 2.1. " 161 + "Sampling event dataset https://doi.org/10.21373/abcd accessed via GBIF.org on " 162 + LocalDate.now(ZoneId.of("UTC")) 163 + ".", 164 citation.getCitation().getText()); 165 166 assertEquals(0, citation.getContacts().size()); 167 } 168 169 @Test 170 public void testCompleteCitationNoYear() { 171 Organization org = new Organization(); 172 org.setTitle("Cited Organization"); 173 174 Dataset dataset = getTestDatasetObject(); 175 dataset.setPubDate(null); 176 dataset.getContacts().add(createContact("John", "Doe", ContactType.ORIGINATOR)); 177 178 CitationGenerator.CitationData citation = CitationGenerator.generateCitation(dataset, org); 179 180 assertEquals( 181 "Doe J. Dataset to be cited. Version 2.1. Cited Organization. " 182 + "Sampling event dataset https://doi.org/10.21373/abcd accessed via GBIF.org on " 183 + LocalDate.now(ZoneId.of("UTC")) 184 + ".", 185 citation.getCitation().getText()); 186 187 assertEquals(1, citation.getContacts().size()); 188 } 189 190 @Test 191 public void testCompleteCitationAuthorMultipleRoles() { 192 Organization org = new Organization(); 193 org.setTitle("Cited Organization"); 194 195 Dataset dataset = getTestDatasetObject(); 196 197 dataset.getContacts().add(createContact("John D.", "Doe", ContactType.ORIGINATOR)); 198 dataset.getContacts().add(createContact("Jim", "Carey", ContactType.PROGRAMMER)); 199 dataset.getContacts().add(createContact("John D.", "Doe", ContactType.METADATA_AUTHOR)); 200 201 CitationGenerator.CitationData citation = CitationGenerator.generateCitation(dataset, org); 202 203 assertEquals( 204 "Doe J D (2009). Dataset to be cited. Version 2.1. Cited Organization. " 205 + "Sampling event dataset https://doi.org/10.21373/abcd accessed via GBIF.org on " 206 + LocalDate.now(ZoneId.of("UTC")) 207 + ".", 208 citation.getCitation().getText()); 209 210 assertEquals(1, citation.getContacts().size()); 211 } 212 213 @Test 214 public void testCompleteCitationCamtrapAuthorMultipleRoles() { 215 Organization org = new Organization(); 216 org.setTitle("Cited Organization"); 217 218 Dataset dataset = getTestCamtrapOccurrenceDatasetObject(); 219 220 dataset.getContacts().add(createContact("Tim", "Robertson", ContactType.ORIGINATOR)); 221 dataset.getContacts().add(createContact("John D.", "Doe", ContactType.POINT_OF_CONTACT)); 222 dataset.getContacts().add(createContact("Jim", "Carey", ContactType.PRINCIPAL_INVESTIGATOR)); 223 dataset.getContacts().add(createContact("Jack", "White", ContactType.CONTENT_PROVIDER)); 224 dataset.getContacts().add(createContact("", "Rights Holder", ContactType.OWNER)); 225 dataset.getContacts().add(createContact("", "Publisher", ContactType.DISTRIBUTOR)); 226 227 CitationGenerator.CitationData citation = CitationGenerator.generateCitation(dataset, org); 228 229 assertEquals( 230 "Robertson T (2009). Dataset to be cited. Version 2.1. Cited Organization. " 231 + "Occurrence dataset https://doi.org/10.21373/abcd accessed via GBIF.org on " 232 + LocalDate.now(ZoneId.of("UTC")) 233 + ".", 234 citation.getCitation().getText()); 235 236 assertEquals(1, citation.getContacts().size()); 237 } 238 239 @Test 240 public void testCompleteCitationNoOriginator() { 241 Organization org = new Organization(); 242 org.setTitle("Cited Organization"); 243 Dataset dataset = getTestDatasetObject(); 244 dataset.getContacts().add(createContact("John D.", "Doe", ContactType.METADATA_AUTHOR)); 245 246 CitationGenerator.CitationData citation = CitationGenerator.generateCitation(dataset, org); 247 248 assertEquals( 249 "Cited Organization (2009). Dataset to be cited. Version 2.1. " 250 + "Sampling event dataset https://doi.org/10.21373/abcd accessed via GBIF.org on " 251 + LocalDate.now(ZoneId.of("UTC")) 252 + ".", 253 citation.getCitation().getText()); 254 assertEquals(0, citation.getContacts().size()); 255 } 256 257 @Test 258 public void testCompleteCitationOriginatorNoName() { 259 Organization org = new Organization(); 260 org.setTitle("Cited Organization"); 261 Dataset dataset = getTestDatasetObject(); 262 263 dataset.getContacts().add(createContact(null, null, "Test Org.", ContactType.ORIGINATOR)); 264 dataset.getContacts().add(createContact("John D.", "Doe", ContactType.METADATA_AUTHOR)); 265 266 CitationGenerator.CitationData citation = CitationGenerator.generateCitation(dataset, org); 267 268 assertEquals( 269 "Cited Organization (2009). Dataset to be cited. Version 2.1. " 270 + "Sampling event dataset https://doi.org/10.21373/abcd accessed via GBIF.org on " 271 + LocalDate.now(ZoneId.of("UTC")) 272 + ".", 273 citation.getCitation().getText()); 274 275 assertEquals(0, citation.getContacts().size()); 276 } 277 278 @Test 279 public void testAuthors() { 280 Organization org = new Organization(); 281 org.setTitle("Cited Organization"); 282 283 Dataset dataset = getTestDatasetObject(); 284 285 dataset.getContacts().add(createContact("John D.", "Doe", ContactType.ORIGINATOR)); 286 dataset 287 .getContacts() 288 .add(createContact("John D.", "Doe", "Awesome Organization", ContactType.ORIGINATOR)); 289 // author with incomplete name 290 dataset.getContacts().add(createContact("Programmer", "Last", ContactType.PROGRAMMER)); 291 292 // we expect 1 author since the names (first and last) are mandatory 293 assertEquals(1, getAuthors(dataset.getContacts()).size()); 294 295 // but, we can only generate the name for one of them 296 assertEquals( 297 1, CitationGenerator.generateAuthorsName(getAuthors(dataset.getContacts())).size()); 298 } 299 300 @Test 301 public void testRepeatedAuthor() { 302 Organization org = new Organization(); 303 org.setTitle("Cited Organization"); 304 305 Dataset dataset = getTestDatasetObject(); 306 Contact contact1 = createContact("John D.", "Doe", ContactType.ORIGINATOR); 307 contact1.setUserId(Collections.singletonList("user1")); 308 309 Contact contact2 = createContact("John D.", "Doe", ContactType.METADATA_AUTHOR); 310 contact2.setUserId(Arrays.asList("user1", "user2")); 311 312 dataset.getContacts().add(contact1); 313 dataset.getContacts().add(contact2); 314 315 List<CitationContact> authors = getAuthors(dataset.getContacts()); 316 317 // Only one author added 318 assertEquals(1, authors.size()); 319 320 // The authors keep the 2 roles 321 assertTrue( 322 authors 323 .get(0) 324 .getRoles() 325 .containsAll(EnumSet.of(ContactType.ORIGINATOR, ContactType.METADATA_AUTHOR))); 326 327 Set<String> firstAuthorUserId = authors.get(0).getUserId(); 328 assertNotNull(firstAuthorUserId); 329 330 // The author has 2 users 331 assertTrue(firstAuthorUserId.containsAll(Arrays.asList("user1", "user2"))); 332 333 // Repeated user is not added twice 334 assertEquals(2, firstAuthorUserId.size()); 335 336 // we can only generate the name for one of them 337 assertEquals( 338 1, CitationGenerator.generateAuthorsName(getAuthors(dataset.getContacts())).size()); 339 } 340 341 private Dataset getCamtrapDataset() { 342 Dataset dataset = new Dataset(); 343 dataset.setTitle("GMU8_LEUVEN - Camera trap observations in natural habitats south of Leuven (Belgium)"); 344 dataset.setDoi(new DOI("10.15468/4u3wm4")); 345 dataset.setType(DatasetType.OCCURRENCE); 346 dataset.setPubDate( 347 new Date( 348 LocalDate.of(2025, 9, 25).atStartOfDay(ZoneId.of("UTC")).toInstant().toEpochMilli())); 349 350 dataset.setPublishingOrganizationKey(UUID.fromString("1cd669d0-80ea-11de-a9d0-f1765f95f18b")); 351 352 return dataset; 353 } 354 355 private Dataset getTestDatasetObject() { 356 Dataset dataset = new Dataset(); 357 dataset.setTitle("Dataset to be cited"); 358 dataset.setVersion("2.1"); 359 dataset.setDoi(new DOI(TEST_PREFIX + "/abcd")); 360 dataset.setPubDate( 361 new Date( 362 LocalDate.of(2009, 2, 8).atStartOfDay(ZoneId.of("UTC")).toInstant().toEpochMilli())); 363 364 dataset.setType(DatasetType.SAMPLING_EVENT); 365 366 return dataset; 367 } 368 369 private Dataset getTestCamtrapOccurrenceDatasetObject() { 370 Dataset dataset = new Dataset(); 371 dataset.setTitle("Dataset to be cited"); 372 dataset.setVersion("2.1"); 373 dataset.setDoi(new DOI(TEST_PREFIX + "/abcd")); 374 dataset.setPubDate( 375 new Date( 376 LocalDate.of(2009, 2, 8).atStartOfDay(ZoneId.of("UTC")).toInstant().toEpochMilli())); 377 378 dataset.setType(DatasetType.OCCURRENCE); 379 380 Endpoint camtrapEndpoint = new Endpoint(); 381 camtrapEndpoint.setType(EndpointType.CAMTRAP_DP); 382 dataset.addEndpoint(camtrapEndpoint); 383 384 return dataset; 385 } 386 387 private Contact createContact(String firstName, String lastName, ContactType ct) { 388 return createContact(firstName, lastName, null, ct); 389 } 390 391 private Contact createContact( 392 String firstName, String lastName, String organization, ContactType ct) { 393 Contact c = new Contact(); 394 c.setFirstName(firstName); 395 c.setLastName(lastName); 396 c.setOrganization(organization); 397 c.setType(ct); 398 return c; 399 } 400}