001/* 002 * Licensed to DuraSpace under one or more contributor license agreements. 003 * See the NOTICE file distributed with this work for additional information 004 * regarding copyright ownership. 005 * 006 * DuraSpace licenses this file to you under the Apache License, 007 * Version 2.0 (the "License"); you may not use this file except in 008 * compliance with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.fcrepo.http.api; 019 020import com.google.common.base.Charsets; 021import com.google.common.collect.ImmutableMap; 022import com.google.common.collect.ImmutableSet; 023import org.apache.commons.io.IOUtils; 024import org.apache.jena.graph.Triple; 025import org.apache.jena.rdf.model.Model; 026import org.apache.jena.rdf.model.RDFNode; 027 028import org.fcrepo.common.db.DbTransactionExecutor; 029import org.fcrepo.config.FedoraPropsConfig; 030import org.fcrepo.http.api.services.EtagService; 031import org.fcrepo.http.api.services.HttpRdfService; 032import org.fcrepo.http.commons.api.rdf.HttpIdentifierConverter; 033import org.fcrepo.http.commons.domain.MultiPrefer; 034import org.fcrepo.http.commons.domain.PreferTag; 035import org.fcrepo.http.commons.responses.RdfNamespacedStream; 036import org.fcrepo.kernel.api.Transaction; 037import org.fcrepo.kernel.api.exception.CannotCreateResourceException; 038import org.fcrepo.kernel.api.exception.ExternalMessageBodyException; 039import org.fcrepo.kernel.api.exception.InsufficientStorageException; 040import org.fcrepo.kernel.api.exception.InvalidChecksumException; 041import org.fcrepo.kernel.api.exception.MalformedRdfException; 042import org.fcrepo.kernel.api.exception.PathNotFoundException; 043import org.fcrepo.kernel.api.exception.PreconditionException; 044import org.fcrepo.kernel.api.exception.UnsupportedAlgorithmException; 045import org.fcrepo.kernel.api.exception.UnsupportedMediaTypeException; 046import org.fcrepo.kernel.api.identifiers.FedoraId; 047import org.fcrepo.kernel.api.models.Binary; 048import org.fcrepo.kernel.api.models.Container; 049import org.fcrepo.kernel.api.models.FedoraResource; 050import org.fcrepo.kernel.api.models.NonRdfSourceDescription; 051import org.fcrepo.kernel.api.models.ResourceFactory; 052import org.fcrepo.kernel.api.models.ResourceHelper; 053import org.fcrepo.kernel.api.rdf.DefaultRdfStream; 054import org.fcrepo.kernel.api.rdf.LdpTriplePreferences; 055import org.fcrepo.kernel.api.rdf.RdfNamespaceRegistry; 056import org.fcrepo.kernel.api.services.CreateResourceService; 057import org.fcrepo.kernel.api.services.DeleteResourceService; 058import org.fcrepo.kernel.api.services.ReplacePropertiesService; 059import org.fcrepo.kernel.api.services.ResourceTripleService; 060import org.fcrepo.kernel.api.services.UpdatePropertiesService; 061import org.junit.Before; 062import org.junit.Ignore; 063import org.junit.Test; 064import org.junit.runner.RunWith; 065import org.mockito.Mock; 066import org.mockito.junit.MockitoJUnitRunner; 067import org.mockito.stubbing.Answer; 068import org.slf4j.Logger; 069import org.springframework.mock.web.MockHttpServletResponse; 070 071import javax.servlet.ServletContext; 072import javax.servlet.http.HttpServletResponse; 073import javax.ws.rs.BadRequestException; 074import javax.ws.rs.ClientErrorException; 075import javax.ws.rs.core.EntityTag; 076import javax.ws.rs.core.HttpHeaders; 077import javax.ws.rs.core.Link; 078import javax.ws.rs.core.MediaType; 079import javax.ws.rs.core.Request; 080import javax.ws.rs.core.Response; 081import javax.ws.rs.core.SecurityContext; 082import java.io.IOException; 083import java.io.InputStream; 084import java.net.URI; 085import java.security.Principal; 086import java.util.ArrayList; 087import java.util.Arrays; 088import java.util.Date; 089import java.util.HashMap; 090import java.util.HashSet; 091import java.util.List; 092import java.util.Set; 093import java.util.stream.Stream; 094 095import static com.google.common.base.Predicates.containsPattern; 096import static java.nio.charset.StandardCharsets.UTF_8; 097import static java.util.Arrays.asList; 098import static java.util.Collections.singleton; 099import static java.util.Collections.singletonList; 100import static java.util.stream.Stream.of; 101import static javax.ws.rs.core.HttpHeaders.CONTENT_LENGTH; 102import static javax.ws.rs.core.HttpHeaders.LINK; 103import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM; 104import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE; 105import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; 106import static javax.ws.rs.core.Response.Status.CREATED; 107import static javax.ws.rs.core.Response.Status.NOT_MODIFIED; 108import static javax.ws.rs.core.Response.Status.NO_CONTENT; 109import static javax.ws.rs.core.Response.Status.OK; 110import static javax.ws.rs.core.Response.Status.TEMPORARY_REDIRECT; 111import static org.apache.commons.io.IOUtils.toInputStream; 112import static org.apache.jena.graph.NodeFactory.createURI; 113import static org.apache.jena.riot.WebContent.contentTypeSPARQLUpdate; 114import static org.fcrepo.config.ServerManagedPropsMode.STRICT; 115import static org.fcrepo.http.api.ContentExposingResource.buildLink; 116import static org.fcrepo.http.api.ContentExposingResource.getSimpleContentType; 117import static org.fcrepo.http.api.FedoraLdp.HTTP_HEADER_ACCEPT_PATCH; 118import static org.fcrepo.http.commons.domain.RDFMediaType.NTRIPLES_TYPE; 119import static org.fcrepo.http.commons.test.util.TestHelpers.getServletContextImpl; 120import static org.fcrepo.http.commons.test.util.TestHelpers.getUriInfoImpl; 121import static org.fcrepo.kernel.api.FedoraTypes.FCR_METADATA; 122import static org.fcrepo.kernel.api.RdfCollectors.toModel; 123import static org.fcrepo.kernel.api.RdfLexicon.BASIC_CONTAINER; 124import static org.fcrepo.kernel.api.RdfLexicon.DIRECT_CONTAINER; 125import static org.fcrepo.kernel.api.RdfLexicon.EXTERNAL_CONTENT; 126import static org.fcrepo.kernel.api.RdfLexicon.FEDORA_NON_RDF_SOURCE_DESCRIPTION_URI; 127import static org.fcrepo.kernel.api.RdfLexicon.INBOUND_REFERENCES; 128import static org.fcrepo.kernel.api.RdfLexicon.INDIRECT_CONTAINER; 129import static org.fcrepo.kernel.api.RdfLexicon.NON_RDF_SOURCE; 130import static org.fcrepo.kernel.api.RdfLexicon.PREFER_CONTAINMENT; 131import static org.fcrepo.kernel.api.RdfLexicon.PREFER_MEMBERSHIP; 132import static org.fcrepo.kernel.api.RdfLexicon.RDF_SOURCE; 133import static org.fcrepo.kernel.api.RdfLexicon.RESOURCE; 134import static org.fcrepo.kernel.api.RdfLexicon.VERSIONED_RESOURCE; 135import static org.junit.Assert.assertEquals; 136import static org.junit.Assert.assertFalse; 137import static org.junit.Assert.assertTrue; 138import static org.junit.Assert.fail; 139import static org.mockito.ArgumentMatchers.any; 140import static org.mockito.ArgumentMatchers.anyInt; 141import static org.mockito.ArgumentMatchers.anyList; 142import static org.mockito.ArgumentMatchers.anyLong; 143import static org.mockito.ArgumentMatchers.anyString; 144import static org.mockito.ArgumentMatchers.contains; 145import static org.mockito.ArgumentMatchers.eq; 146import static org.mockito.ArgumentMatchers.isNull; 147import static org.mockito.ArgumentMatchers.nullable; 148import static org.mockito.Mockito.doAnswer; 149import static org.mockito.Mockito.doReturn; 150import static org.mockito.Mockito.doThrow; 151import static org.mockito.Mockito.mock; 152import static org.mockito.Mockito.spy; 153import static org.mockito.Mockito.times; 154import static org.mockito.Mockito.verify; 155import static org.mockito.Mockito.when; 156import static org.slf4j.LoggerFactory.getLogger; 157import static org.springframework.test.util.ReflectionTestUtils.setField; 158 159/** 160 * @author cabeer 161 * @author ajs6f 162 */ 163@RunWith(MockitoJUnitRunner.Silent.class) 164public class FedoraLdpTest { 165 166 private final String path = "/some/path"; 167 private final String binaryPath = "/some/binary/path"; 168 private final String binaryDescriptionPath = binaryPath + "/" + FCR_METADATA; 169 170 private final FedoraId pathId = FedoraId.create(path); 171 private final FedoraId binaryPathId = FedoraId.create(binaryPath); 172 private final FedoraId binaryDescId = binaryPathId.resolve(FCR_METADATA); 173 174 private FedoraLdp testObj; 175 176 private final List<String> nonRDFSourceLink = singletonList( 177 Link.fromUri(NON_RDF_SOURCE.toString()).rel("type").build().toString()); 178 179 private final HttpIdentifierConverter identifierConverter = new HttpIdentifierConverter( 180 getUriInfoImpl().getBaseUriBuilder().clone().path("/{path: .*}")); 181 182 private final InputStream emptyStream = IOUtils.toInputStream("", Charsets.UTF_8); 183 184 @Mock 185 private Request mockRequest; 186 187 private HttpServletResponse mockResponse; 188 189 @Mock 190 private Transaction mockTransaction; 191 192 @Mock 193 private Container mockContainer; 194 195 @Mock 196 private NonRdfSourceDescription mockNonRdfSourceDescription; 197 198 @Mock 199 private Binary mockBinary; 200 201 @Mock 202 private ResourceFactory resourceFactory; 203 204 @Mock 205 private ResourceHelper resourceHelper; 206 207 @Mock 208 private FedoraHttpConfiguration mockHttpConfiguration; 209 210 @Mock 211 private RdfNamespaceRegistry rdfNamespaceRegistry; 212 213 @Mock 214 private HttpHeaders mockHeaders; 215 216 @Mock 217 private SecurityContext mockSecurityContext; 218 219 @Mock 220 private ServletContext mockServletContext; 221 222 @Mock 223 private MultiPrefer prefer; 224 225 @Mock 226 private PreferTag preferTag; 227 228 @Mock 229 private ExternalContentHandlerFactory extContentHandlerFactory; 230 231 @Mock 232 private ReplacePropertiesService replacePropertiesService; 233 234 @Mock 235 private UpdatePropertiesService updatePropertiesService; 236 237 @Mock 238 private DeleteResourceService deleteResourceService; 239 240 @Mock 241 private CreateResourceService createResourceService; 242 243 @Mock 244 private Principal principal; 245 246 @Mock 247 private ResourceTripleService resourceTripleService; 248 249 @Mock 250 private EtagService etagService; 251 252 @Mock 253 private FedoraPropsConfig fedoraPropsConfig; 254 255 private final List<URI> typeList = new ArrayList<>(); 256 257 private static final Logger log = getLogger(FedoraLdpTest.class); 258 259 @Before 260 public void setUp() { 261 testObj = spy(new FedoraLdp(path)); 262 263 when(resourceHelper.doesResourceExist(any(Transaction.class), eq(pathId), eq(false))).thenReturn(true); 264 265 mockResponse = new MockHttpServletResponse(); 266 267 final HttpRdfService httpRdfService = new HttpRdfService(); 268 setField(httpRdfService, "fedoraPropsConfig", fedoraPropsConfig); 269 when(fedoraPropsConfig.getServerManagedPropsMode()).thenReturn(STRICT); 270 271 setField(testObj, "request", mockRequest); 272 setField(testObj, "servletResponse", mockResponse); 273 setField(testObj, "uriInfo", getUriInfoImpl()); 274 setField(testObj, "headers", mockHeaders); 275 setField(testObj, "resourceFactory", resourceFactory); 276 setField(testObj, "httpConfiguration", mockHttpConfiguration); 277 setField(testObj, "transaction", mockTransaction); 278 setField(testObj, "securityContext", mockSecurityContext); 279 setField(testObj, "prefer", prefer); 280 setField(testObj, "context", getServletContextImpl()); 281 setField(testObj, "extContentHandlerFactory", extContentHandlerFactory); 282 setField(testObj, "namespaceRegistry", rdfNamespaceRegistry); 283 setField(testObj, "deleteResourceService", deleteResourceService); 284 setField(testObj, "resourceTripleService", resourceTripleService); 285 setField(testObj, "httpRdfService", httpRdfService); 286 setField(testObj, "createResourceService", createResourceService); 287 setField(testObj, "replacePropertiesService", replacePropertiesService); 288 setField(testObj, "updatePropertiesService", updatePropertiesService); 289 setField(testObj, "resourceHelper", resourceHelper); 290 setField(testObj, "etagService", etagService); 291 setField(testObj, "dbTransactionExecutor", new DbTransactionExecutor()); 292 293 when(rdfNamespaceRegistry.getNamespaces()).thenReturn(new HashMap<>()); 294 295 when(mockHttpConfiguration.putRequiresIfMatch()).thenReturn(false); 296 297 when(principal.getName()).thenReturn("testUser"); 298 when(mockSecurityContext.getUserPrincipal()).thenReturn(principal); 299 300 when(mockContainer.getEtagValue()).thenReturn(""); 301 when(mockContainer.getStateToken()).thenReturn(""); 302 when(mockContainer.getDescription()).thenReturn(mockContainer); 303 when(mockContainer.getDescribedResource()).thenReturn(mockContainer); 304 when(mockContainer.getFedoraId()).thenReturn(pathId); 305 306 when(resourceHelper.doesResourceExist(mockTransaction, pathId, true)).thenReturn(false); 307 308 when(mockNonRdfSourceDescription.getEtagValue()).thenReturn(""); 309 when(mockNonRdfSourceDescription.getStateToken()).thenReturn(""); 310 when(mockNonRdfSourceDescription.getDescribedResource()).thenReturn(mockBinary); 311 when(mockNonRdfSourceDescription.getOriginalResource()).thenReturn(mockNonRdfSourceDescription); 312 when(mockNonRdfSourceDescription.getFedoraId()).thenReturn(binaryDescId); 313 314 when(mockBinary.getEtagValue()).thenReturn(""); 315 when(mockBinary.getStateToken()).thenReturn(""); 316 when(mockBinary.getDescription()).thenReturn(mockNonRdfSourceDescription); 317 when(mockBinary.getDescribedResource()).thenReturn(mockBinary); 318 when(mockBinary.getOriginalResource()).thenReturn(mockBinary); 319 when(mockBinary.getFedoraId()).thenReturn(binaryPathId); 320 321 when(mockHeaders.getHeaderString("user-agent")).thenReturn("Test UserAgent"); 322 when(mockHeaders.getHeaderString("X-If-State-Token")).thenReturn(null); 323 324 when(mockTransaction.getId()).thenReturn("foo1234"); 325 when(mockTransaction.isShortLived()).thenReturn(true); 326 327 when(mockServletContext.getContextPath()).thenReturn("/"); 328 329 when(prefer.getReturn()).thenReturn(preferTag); 330 331 doAnswer((Answer<HttpServletResponse>) invocation -> { 332 mockResponse.addHeader("Preference-Applied", "return=representation"); 333 return null; 334 }).when(preferTag).addResponseHeaders(mockResponse); 335 336 when(etagService.getRdfResourceEtag(nullable(Transaction.class), any(FedoraResource.class), 337 nullable(LdpTriplePreferences.class), any())).thenReturn("etagval"); 338 } 339 340 private FedoraResource setResource(final Class<? extends FedoraResource> klass) { 341 final List<tripleTypes> defaultTriples = List.of( 342 tripleTypes.PROPERTIES, 343 tripleTypes.LDP_CONTAINMENT, 344 tripleTypes.SERVER_MANAGED, 345 tripleTypes.LDP_MEMBERSHIP 346 ); 347 return setResource(klass, defaultTriples); 348 } 349 350 private FedoraResource setResource(final Class<? extends FedoraResource> klass, 351 final List<tripleTypes> tripleTypesList) { 352 final FedoraResource mockResource = mock(klass); 353 typeList.add(URI.create(RESOURCE.toString())); 354 if (mockResource instanceof Binary) { 355 when(((Binary) mockResource).getContentSize()).thenReturn(1L); 356 when(mockResource.getOriginalResource()).thenReturn(mockResource); 357 when(mockResource.getDescription()).thenReturn(mockNonRdfSourceDescription); 358 typeList.add(URI.create(NON_RDF_SOURCE.toString())); 359 } else if (mockResource instanceof NonRdfSourceDescription) { 360 when(mockResource.getOriginalResource()).thenReturn(mockBinary); 361 when(mockBinary.getDescription()).thenReturn(mockResource); 362 typeList.addAll(List.of(URI.create(FEDORA_NON_RDF_SOURCE_DESCRIPTION_URI), 363 URI.create(RDF_SOURCE.toString()))); 364 } else if (mockResource instanceof Container) { 365 typeList.addAll(List.of(URI.create(BASIC_CONTAINER.toString()), URI.create(RDF_SOURCE.toString()))); 366 } 367 when(mockResource.getTypes()).thenReturn(typeList); 368 369 doReturn(mockResource).when(testObj).resource(); 370 when(mockResource.getFedoraId()).thenReturn(FedoraId.create(path)); 371 when(mockResource.getEtagValue()).thenReturn(""); 372 when(mockResource.getStateToken()).thenReturn(""); 373 when(mockResource.getDescribedResource()).thenReturn(mockResource); 374 setupResourceService(mockResource, tripleTypesList); 375 return mockResource; 376 } 377 378 private enum tripleTypes { 379 PROPERTIES, SERVER_MANAGED, LDP_CONTAINMENT, INBOUND_REFERENCES, LDP_MEMBERSHIP 380 } 381 382 private void setupResourceService(final FedoraResource mockResource, final List<tripleTypes> response) { 383 final var testUri = createURI("test"); 384 final List<Triple> triples = new ArrayList<>(); 385 if (response.contains(tripleTypes.PROPERTIES)) { 386 triples.add(Triple.create(testUri, createURI("called"), createURI("PROPERTIES"))); 387 } 388 if (response.contains(tripleTypes.SERVER_MANAGED)) { 389 triples.add(Triple.create(testUri, createURI("managed"), createURI("SERVER_MANAGED"))); 390 } 391 if (response.contains(tripleTypes.LDP_CONTAINMENT)) { 392 triples.add(Triple.create(testUri, createURI("contains"), createURI("LDP_CONTAINMENT"))); 393 } 394 if (response.contains(tripleTypes.INBOUND_REFERENCES)) { 395 triples.add(Triple.create(testUri, createURI("references"), createURI("INBOUND_REFERENCES"))); 396 } 397 if (response.contains(tripleTypes.LDP_MEMBERSHIP)) { 398 triples.add(Triple.create(testUri, createURI("membership"), createURI("LDP_MEMBERSHIP"))); 399 } 400 final Answer<Stream<Triple>> answer = invocationOnMock -> triples.stream(); 401 when(resourceTripleService.getResourceTriples(any(Transaction.class), 402 eq(mockResource), any(LdpTriplePreferences.class), anyInt())).thenAnswer(answer); 403 } 404 405 @Test 406 public void testHead() throws Exception { 407 setResource(FedoraResource.class); 408 when(mockRequest.getMethod()).thenReturn("HEAD"); 409 final Response actual = testObj.head(); 410 assertEquals(OK.getStatusCode(), actual.getStatus()); 411 assertTrue("Should have a Link header", mockResponse.containsHeader(LINK)); 412 assertTrue("Should have an Allow header", mockResponse.containsHeader("Allow")); 413 assertTrue("Should have a Preference-Applied header", mockResponse.containsHeader("Preference-Applied")); 414 assertTrue("Should have a Vary header", mockResponse.containsHeader("Vary")); 415 assertTrue("Should be an LDP Resource", 416 mockResponse.getHeaders(LINK).contains("<" + RESOURCE + ">; rel=\"type\"")); 417 } 418 419 @Test 420 public void testHeadWithObject() throws Exception { 421 setResource(Container.class); 422 when(mockRequest.getMethod()).thenReturn("HEAD"); 423 final Response actual = testObj.head(); 424 assertEquals(OK.getStatusCode(), actual.getStatus()); 425 assertTrue("Should advertise Accept-Post flavors", mockResponse.containsHeader("Accept-Post")); 426 assertTrue("Should advertise Accept-Patch flavors", mockResponse.containsHeader( 427 FedoraLdp.HTTP_HEADER_ACCEPT_PATCH)); 428 } 429 430 @Test 431 public void testHeadWithDefaultContainer() throws Exception { 432 setResource(Container.class); 433 when(mockRequest.getMethod()).thenReturn("HEAD"); 434 final Response actual = testObj.head(); 435 assertEquals(OK.getStatusCode(), actual.getStatus()); 436 assertShouldBeAnLDPBasicContainer(); 437 } 438 439 private void assertShouldBeAnLDPBasicContainer() { 440 assertTrue("Should be an LDP BasicContainer", 441 mockResponse.getHeaders(LINK).contains("<" + BASIC_CONTAINER.getURI() + ">; rel=\"type\"")); 442 } 443 444 @Test 445 public void testHeadWithBasicContainer() throws Exception { 446 final FedoraResource resource = setResource(Container.class); 447 when(mockRequest.getMethod()).thenReturn("HEAD"); 448 when(resource.hasType(BASIC_CONTAINER.toString())).thenReturn(true); 449 final Response actual = testObj.head(); 450 assertEquals(OK.getStatusCode(), actual.getStatus()); 451 assertShouldBeAnLDPBasicContainer(); 452 } 453 454 @Test 455 public void testHeadWithDirectContainer() throws Exception { 456 final FedoraResource resource = setResource(Container.class); 457 when(mockRequest.getMethod()).thenReturn("HEAD"); 458 typeList.add(URI.create(DIRECT_CONTAINER.toString())); 459 final Response actual = testObj.head(); 460 assertEquals(OK.getStatusCode(), actual.getStatus()); 461 assertShouldBeAnLDPDirectContainer(); 462 } 463 464 private void assertShouldBeAnLDPDirectContainer() { 465 assertTrue("Should be an LDP DirectContainer", 466 mockResponse.getHeaders(LINK).contains("<" + DIRECT_CONTAINER.getURI() + ">; rel=\"type\"")); 467 } 468 469 @Test 470 public void testHeadWithIndirectContainer() throws Exception { 471 final FedoraResource resource = setResource(Container.class); 472 when(mockRequest.getMethod()).thenReturn("HEAD"); 473 typeList.add(URI.create(INDIRECT_CONTAINER.toString())); 474 final Response actual = testObj.head(); 475 assertEquals(OK.getStatusCode(), actual.getStatus()); 476 assertShouldBeAnLDPIndirectContainer(); 477 } 478 479 private void assertShouldBeAnLDPIndirectContainer() { 480 assertTrue("Should be an LDP IndirectContainer", 481 mockResponse.getHeaders(LINK).contains("<" + INDIRECT_CONTAINER.getURI() + ">; rel=\"type\"")); 482 } 483 484 @Test 485 public void testHeadWithBinary() throws Exception { 486 final Binary mockResource = (Binary)setResource(Binary.class); 487 when(mockRequest.getMethod()).thenReturn("HEAD"); 488 when(mockResource.getDescription()).thenReturn(mockNonRdfSourceDescription); 489 when(mockResource.getOriginalResource()).thenReturn(mockResource); 490 when(mockResource.getMimeType()).thenReturn("image/jpeg"); 491 final Response actual = testObj.head(); 492 assertEquals(OK.getStatusCode(), actual.getStatus()); 493 assertContentLengthGreaterThan0(mockResponse.getHeader(CONTENT_LENGTH)); 494 assertShouldBeAnLDPNonRDFSource(); 495 assertShouldNotAdvertiseAcceptPatchFlavors(); 496 assertShouldContainLinkToBinaryDescription(); 497 } 498 499 private void assertContentLengthGreaterThan0(final String contentLength) { 500 assertTrue("Should have a content length header greater than 0", Integer.parseInt(contentLength) > 0); 501 } 502 503 private void assertShouldContainLinkToBinaryDescription() { 504 final String described = identifierConverter.toDomain(binaryPath + "/fcr:metadata"); 505 assertTrue("Should contain a link to the binary description", 506 mockResponse.getHeaders(LINK) 507 .contains("<" + described + ">; rel=\"describedby\"")); 508 } 509 510 private void assertShouldNotAdvertiseAcceptPatchFlavors() { 511 assertFalse("Should not advertise Accept-Patch flavors", mockResponse.containsHeader(HTTP_HEADER_ACCEPT_PATCH)); 512 } 513 514 private void assertShouldNotAdvertiseAcceptPostFlavors() { 515 assertFalse("Should not advertise Accept-Post flavors", mockResponse.containsHeader("Accept-Post")); 516 } 517 518 private void assertShouldHaveAcceptExternalContentHandlingHeader() { 519 assertTrue("Should have Accept-External-Content-Handling header", 520 mockResponse.containsHeader(FedoraLdp.ACCEPT_EXTERNAL_CONTENT)); 521 assertEquals("Should support copy, redirect, and proxy", "copy,redirect,proxy", 522 mockResponse.getHeader(FedoraLdp.ACCEPT_EXTERNAL_CONTENT)); 523 } 524 525 @Test 526 public void testHeadWithExternalBinary() throws Exception { 527 final Binary mockResource = (Binary)setResource(Binary.class); 528 when(mockRequest.getMethod()).thenReturn("HEAD"); 529 final String url = "http://example.com/some/url"; 530 when(mockResource.getDescription()).thenReturn(mockNonRdfSourceDescription); 531 when(mockResource.getMimeType()).thenReturn("text/plain"); 532 when(mockResource.isProxy()).thenReturn(false); 533 when(mockResource.isRedirect()).thenReturn(true); 534 when(mockResource.getOriginalResource()).thenReturn(mockResource); 535 when(mockResource.getExternalURL()).thenReturn(url); 536 when(mockResource.getExternalURI()).thenReturn(URI.create(url)); 537 final Response actual = testObj.head(); 538 assertEquals(TEMPORARY_REDIRECT.getStatusCode(), actual.getStatus()); 539 assertEquals(new URI(url), actual.getLocation()); 540 assertShouldBeAnLDPNonRDFSource(); 541 assertShouldNotAdvertiseAcceptPatchFlavors(); 542 assertShouldContainLinkToBinaryDescription(); 543 } 544 545 @Test 546 public void testHeadWithBinaryDescription() throws Exception { 547 final NonRdfSourceDescription mockResource 548 = (NonRdfSourceDescription)setResource(NonRdfSourceDescription.class); 549 when(mockRequest.getMethod()).thenReturn("HEAD"); 550 when(mockResource.getDescribedResource()).thenReturn(mockBinary); 551 when(mockResource.getOriginalResource()).thenReturn(mockResource); 552 final Response actual = testObj.head(); 553 assertEquals(OK.getStatusCode(), actual.getStatus()); 554 assertTrue("Should be an LDP RDFSource", 555 mockResponse.getHeaders(LINK).contains(buildLink(RDF_SOURCE.getURI(), "type"))); 556 assertShouldNotAdvertiseAcceptPostFlavors(); 557 assertShouldAdvertiseAcceptPatchFlavors(); 558 assertShouldContainLinkToTheBinary(); 559 assertShouldAllowOnlyResourceDescriptionMethods(); 560 } 561 562 private void assertShouldContainLinkToTheBinary() { 563 assertTrue("Should contain a link to the binary", 564 mockResponse.getHeaders(LINK) 565 .contains("<" + identifierConverter.toDomain(binaryPath) + ">; rel=\"describes\"")); 566 } 567 568 private void assertShouldAdvertiseAcceptPatchFlavors() { 569 assertTrue("Should advertise Accept-Patch flavors", mockResponse.containsHeader(HTTP_HEADER_ACCEPT_PATCH)); 570 } 571 572 @Test 573 public void testOption() { 574 setResource(FedoraResource.class); 575 final Response actual = testObj.options(); 576 assertEquals(OK.getStatusCode(), actual.getStatus()); 577 assertTrue("Should have an Allow header", mockResponse.containsHeader("Allow")); 578 assertShouldHaveAcceptExternalContentHandlingHeader(); 579 } 580 581 @Test 582 public void testOptionWithLDPRS() { 583 setResource(Container.class); 584 final Response actual = testObj.options(); 585 assertEquals(OK.getStatusCode(), actual.getStatus()); 586 assertTrue("Should advertise Accept-Post flavors", mockResponse.containsHeader("Accept-Post")); 587 assertShouldAdvertiseAcceptPatchFlavors(); 588 assertShouldHaveAcceptExternalContentHandlingHeader(); 589 } 590 591 @Test 592 public void testOptionWithBinary() throws Exception { 593 setField(testObj, "externalPath", binaryPath); 594 when(resourceFactory.getResource(mockTransaction, binaryPathId)).thenReturn(mockBinary); 595 final Response actual = testObj.options(); 596 assertEquals(OK.getStatusCode(), actual.getStatus()); 597 assertShouldNotAdvertiseAcceptPostFlavors(); 598 assertShouldNotAdvertiseAcceptPatchFlavors(); 599 assertShouldContainLinkToBinaryDescription(); 600 assertShouldHaveAcceptExternalContentHandlingHeader(); 601 } 602 603 @Test 604 public void testOptionWithBinaryDescription() throws Exception { 605 setField(testObj, "externalPath", binaryDescriptionPath); 606 when(resourceFactory.getResource(mockTransaction, binaryDescId)) 607 .thenReturn(mockNonRdfSourceDescription); 608 final Response actual = testObj.options(); 609 assertEquals(OK.getStatusCode(), actual.getStatus()); 610 assertShouldNotAdvertiseAcceptPostFlavors(); 611 assertShouldAdvertiseAcceptPatchFlavors(); 612 assertShouldContainLinkToTheBinary(); 613 assertShouldHaveAcceptExternalContentHandlingHeader(); 614 } 615 616 617 @Test 618 public void testGet() throws Exception { 619 final var resource = setResource(Container.class); 620 setField(testObj, "externalPath", path); 621 when(mockRequest.getMethod()).thenReturn("GET"); 622 when(resourceFactory.getResource((Transaction)any(), any(FedoraId.class))).thenReturn(resource); 623 final Response actual = testObj.getResource(null); 624 assertEquals(OK.getStatusCode(), actual.getStatus()); 625 assertTrue("Should have a Link header", mockResponse.containsHeader(LINK)); 626 assertTrue("Should have an Allow header", mockResponse.containsHeader("Allow")); 627 assertTrue("Should be an LDP Resource", 628 mockResponse.getHeaders(LINK).contains("<" + RESOURCE + ">; rel=\"type\"")); 629 630 try (final RdfNamespacedStream entity = (RdfNamespacedStream) actual.getEntity()) { 631 final Model model = entity.stream.collect(toModel()); 632 final List<String> rdfNodes = model.listObjects().mapWith(RDFNode::toString).toList(); 633 assertTrue("Expected RDF contexts missing", rdfNodes.containsAll(ImmutableSet.of( 634 "LDP_CONTAINMENT", "PROPERTIES", "SERVER_MANAGED", "LDP_MEMBERSHIP"))); 635 } 636 } 637 638 @Test 639 public void testGetWithObject() throws Exception { 640 setResource(Container.class); 641 when(mockRequest.getMethod()).thenReturn("GET"); 642 final Response actual = testObj.getResource(null); 643 assertEquals(OK.getStatusCode(), actual.getStatus()); 644 assertTrue("Should advertise Accept-Post flavors", mockResponse.containsHeader("Accept-Post")); 645 assertShouldAdvertiseAcceptPatchFlavors(); 646 647 try (final RdfNamespacedStream entity = (RdfNamespacedStream) actual.getEntity()) { 648 final Model model = entity.stream.collect(toModel()); 649 final List<String> rdfNodes = model.listObjects().mapWith(RDFNode::toString).toList(); 650 assertTrue("Expected RDF contexts missing", rdfNodes.containsAll(ImmutableSet.of( 651 "LDP_CONTAINMENT", "PROPERTIES", "SERVER_MANAGED"))); 652 // TODO: Above list is missing LDP_MEMBERSHIP - https://jira.lyrasis.org/browse/FCREPO-3165 653 } 654 } 655 656 657 @Test 658 public void testGetWithBasicContainer() throws Exception { 659 final FedoraResource resource = setResource(Container.class); 660 when(mockRequest.getMethod()).thenReturn("GET"); 661 final Response actual = testObj.getResource(null); 662 assertEquals(OK.getStatusCode(), actual.getStatus()); 663 assertShouldBeAnLDPBasicContainer(); 664 } 665 666 @Test 667 public void testGetWithDirectContainer() throws Exception { 668 final FedoraResource resource = setResource(Container.class); 669 when(mockRequest.getMethod()).thenReturn("GET"); 670 typeList.add(URI.create(DIRECT_CONTAINER.toString())); 671 final Response actual = testObj.getResource(null); 672 assertEquals(OK.getStatusCode(), actual.getStatus()); 673 assertShouldBeAnLDPDirectContainer(); 674 } 675 676 @Test 677 public void testGetWithIndirectContainer() throws Exception { 678 final FedoraResource resource = setResource(Container.class); 679 when(mockRequest.getMethod()).thenReturn("GET"); 680 typeList.add(URI.create(INDIRECT_CONTAINER.toString())); 681 final Response actual = testObj.getResource(null); 682 assertEquals(OK.getStatusCode(), actual.getStatus()); 683 assertShouldBeAnLDPIndirectContainer(); 684 } 685 686 @Ignore("Requires minimal representation - https://jira.lyrasis.org/browse/FCREPO-3334") 687 @Test 688 public void testGetWithObjectPreferMinimal() throws Exception { 689 setResource(Container.class); 690 when(mockRequest.getMethod()).thenReturn("GET"); 691 setField(testObj, "prefer", new MultiPrefer("return=minimal")); 692 final Response actual = testObj.getResource(null); 693 assertEquals(OK.getStatusCode(), actual.getStatus()); 694 695 try (final RdfNamespacedStream entity = (RdfNamespacedStream) actual.getEntity()) { 696 final Model model = entity.stream.collect(toModel()); 697 final List<String> rdfNodes = model.listObjects().mapWith(RDFNode::toString).toList(); 698 assertTrue("Expected RDF contexts missing", rdfNodes.stream() 699 .anyMatch(x -> x.contains("PROPERTIES") && x.contains("MINIMAL"))); 700 assertFalse("Included non-minimal contexts", rdfNodes.contains("LDP_MEMBERSHIP")); 701 assertFalse("Included non-minimal contexts", rdfNodes.contains("LDP_CONTAINMENT")); 702 } 703 704 } 705 706 /** 707 * Emulates an 'If-None-Match' precondition failing for a GET request. There should not be any entity body set 708 * on the response. 709 * 710 */ 711 @Test 712 public void testGetWhenIfNoneMatchPreconditionFails() { 713 setResource(FedoraResource.class); 714 715 // Set up the expectations for the ResponseBuilder 716 final Response.ResponseBuilder builder = mock(Response.ResponseBuilder.class); 717 final Response response = mock(Response.class); 718 719 when(builder.entity(any())).thenReturn(builder); 720 when(builder.cacheControl(any())).thenReturn(builder); 721 when(builder.lastModified((any()))).thenReturn(builder); 722 when(builder.tag(any(EntityTag.class))).thenReturn(builder); 723 when(builder.build()).thenReturn(response); 724 when(response.getEntity()).thenReturn("precondition failed message"); 725 when(response.getStatus()).thenReturn(NOT_MODIFIED.getStatusCode()); 726 727 // Set up expectations for the Request; returning a ResponseBuilder from evaluatePreconditions(...) indicates 728 // that satisfying the precondition fails, which is what we want to simulate in this test 729 when(mockRequest.evaluatePreconditions(any(EntityTag.class))).thenReturn(builder); 730 731 // Execute the method under test. Preconditions should fail, resulting in an exception being thrown. 732 try { 733 testObj.evaluateRequestPreconditions(mockRequest, mockResponse, testObj.resource(), 734 mockTransaction, true); 735 fail("Expected " + PreconditionException.class.getName() + " to be thrown."); 736 } catch (final PreconditionException e) { 737 // expected 738 } 739 740 // an entity body should _not_ be set under these conditions 741 verify(mockRequest).evaluatePreconditions(any(EntityTag.class)); 742 verify(builder, times(0)).entity(any()); 743 } 744 745 /** 746 * Emulates an 'If-Modified-Since' precondition failing for a GET request. There should not be any entity body set 747 * on the response. 748 * 749 */ 750 @Test 751 public void testGetWhenIfModifiedSincePreconditionFails() { 752 setResource(FedoraResource.class); 753 754 // Set up the expectations for the ResponseBuilder 755 final Response.ResponseBuilder builder = mock(Response.ResponseBuilder.class); 756 final Response response = mock(Response.class); 757 758 when(builder.entity(any())).thenReturn(builder); 759 when(builder.cacheControl(any())).thenReturn(builder); 760 when(builder.lastModified((any()))).thenReturn(builder); 761 when(builder.tag(any(EntityTag.class))).thenReturn(builder); 762 when(builder.build()).thenReturn(response); 763 when(response.getStatus()).thenReturn(NOT_MODIFIED.getStatusCode()); 764 765 when(response.getEntity()).thenReturn("precondition failed message"); 766 // Set up expectations for the Request; returning a ResponseBuilder from evaluatePreconditions(...) indicates 767 // that satisfying the precondition fails, which is what we want to simulate in this test 768 when(mockRequest.evaluatePreconditions(any(Date.class))).thenReturn(builder); 769 770 // Execute the method under test. Preconditions should fail, resulting in an exception being thrown. 771 try { 772 testObj.evaluateRequestPreconditions(mockRequest, mockResponse, testObj.resource(), 773 mockTransaction, true); 774 fail("Expected " + PreconditionException.class.getName() + " to be thrown."); 775 } catch (final PreconditionException e) { 776 // expected 777 } 778 779 // an entity body should _not_ be set under these conditions 780 verify(mockRequest).evaluatePreconditions(any(Date.class)); 781 verify(builder, times(0)).entity(any()); 782 } 783 784 @Ignore("Needs membership triples - FCREPO-3165") 785 @Test 786 public void testGetWithObjectOmitContainment() throws Exception { 787 setResource(Container.class); 788 when(mockRequest.getMethod()).thenReturn("GET"); 789 setField(testObj, "prefer", 790 new MultiPrefer("return=representation; omit=\"" + PREFER_CONTAINMENT + "\"")); 791 final Response actual = testObj.getResource( null); 792 assertEquals(OK.getStatusCode(), actual.getStatus()); 793 794 try (final RdfNamespacedStream entity = (RdfNamespacedStream) actual.getEntity()) { 795 final Model model = entity.stream.collect(toModel()); 796 final List<String> rdfNodes = model.listObjects().mapWith(RDFNode::toString).toList(); 797 assertTrue("Should include membership contexts", rdfNodes.contains("LDP_MEMBERSHIP")); 798 assertFalse("Should not include containment contexts", rdfNodes.contains("LDP_CONTAINMENT")); 799 } 800 } 801 802 @Ignore("Membership Triples not implemented - FCREPO-3165") 803 @Test 804 public void testGetWithObjectOmitMembership() throws Exception { 805 setResource(Container.class); 806 when(mockRequest.getMethod()).thenReturn("GET"); 807 setField(testObj, "prefer", 808 new MultiPrefer("return=representation; omit=\"" + PREFER_MEMBERSHIP + "\"")); 809 final Response actual = testObj.getResource(null); 810 assertEquals(OK.getStatusCode(), actual.getStatus()); 811 812 try (final RdfNamespacedStream entity = (RdfNamespacedStream) actual.getEntity()) { 813 final Model model = entity.stream.collect(toModel()); 814 final List<String> rdfNodes = model.listObjects().mapWith(RDFNode::toString).toList(); 815 assertFalse("Should not include membership contexts", rdfNodes.contains("LDP_MEMBERSHIP")); 816 assertTrue("Should include containment contexts", rdfNodes.contains("LDP_CONTAINMENT")); 817 } 818 } 819 820 @Test 821 public void testGetWithObjectIncludeReferences() 822 throws IOException, UnsupportedAlgorithmException { 823 final List<tripleTypes> triples = List.of( 824 tripleTypes.PROPERTIES, 825 tripleTypes.LDP_CONTAINMENT, 826 tripleTypes.SERVER_MANAGED, 827 tripleTypes.LDP_MEMBERSHIP, 828 tripleTypes.INBOUND_REFERENCES 829 ); 830 setResource(Container.class, triples); 831 when(mockRequest.getMethod()).thenReturn("GET"); 832 setField(testObj, "prefer", new MultiPrefer("return=representation; include=\"" + INBOUND_REFERENCES + "\"")); 833 final Response actual = testObj.getResource(null); 834 assertEquals(OK.getStatusCode(), actual.getStatus()); 835 836 try (final RdfNamespacedStream entity = (RdfNamespacedStream) actual.getEntity()) { 837 final Model model = entity.stream.collect(toModel()); 838 839 final List<String> rdfNodes = model.listObjects().mapWith(RDFNode::toString).toList(); 840 log.debug("Received RDF nodes: {}", rdfNodes); 841 assertTrue("Should include references contexts", 842 rdfNodes.stream().anyMatch(containsPattern("REFERENCES")::apply)); 843 } 844 } 845 846 @Test 847 public void testGetWithBinary() throws Exception { 848 final Binary mockResource = (Binary)setResource(Binary.class); 849 when(mockRequest.getMethod()).thenReturn("GET"); 850 when(mockResource.getDescription()).thenReturn(mockNonRdfSourceDescription); 851 when(mockResource.getMimeType()).thenReturn("text/plain"); 852 when(mockResource.getContent()).thenReturn(toInputStream("xyz", UTF_8)); 853 when(mockResource.getOriginalResource()).thenReturn(mockResource); 854 final Response actual = testObj.getResource(null); 855 assertEquals(OK.getStatusCode(), actual.getStatus()); 856 assertShouldBeAnLDPNonRDFSource(); 857 assertShouldNotAdvertiseAcceptPatchFlavors(); 858 assertShouldContainLinkToBinaryDescription(); 859 assertEquals("xyz", IOUtils.toString((InputStream) actual.getEntity(), UTF_8)); 860 } 861 862 private void assertShouldBeAnLDPNonRDFSource() { 863 assertTrue("Should be an LDP NonRDFSource", 864 mockResponse.getHeaders(LINK).contains("<" + NON_RDF_SOURCE + ">; rel=\"type\"")); 865 assertShouldNotAdvertiseAcceptPostFlavors(); 866 } 867 868 @Test 869 public void testGetWithExternalMessageBinary() throws Exception { 870 final Binary mockResource = (Binary)setResource(Binary.class); 871 when(mockRequest.getMethod()).thenReturn("GET"); 872 final String url = "http://example.com/some/url"; 873 when(mockResource.getDescription()).thenReturn(mockNonRdfSourceDescription); 874 when(mockResource.getMimeType()).thenReturn("text/plain"); 875 when(mockResource.isProxy()).thenReturn(false); 876 when(mockResource.isRedirect()).thenReturn(true); 877 when(mockResource.getOriginalResource()).thenReturn(mockResource); 878 when(mockResource.getExternalURI()).thenReturn(URI.create(url)); 879 when(mockResource.getExternalURL()).thenReturn(url); 880 when(mockResource.getContent()).thenReturn(toInputStream("xyz", UTF_8)); 881 final Response actual = testObj.getResource(null); 882 assertEquals(TEMPORARY_REDIRECT.getStatusCode(), actual.getStatus()); 883 assertTrue("Should be an LDP NonRDFSource", mockResponse.getHeaders(LINK).contains("<" + 884 NON_RDF_SOURCE + ">; rel=\"type\"")); 885 assertShouldContainLinkToBinaryDescription(); 886 assertEquals(new URI(url), actual.getLocation()); 887 } 888 889 @Test(expected = ExternalMessageBodyException.class) 890 public void testGetWithExternalMessageMissingURLBinary() throws Exception { 891 when(extContentHandlerFactory.createFromLinks(anyList())) 892 .thenThrow(new ExternalMessageBodyException("")); 893 894 final String badExternal = Link.fromUri("http://test.com") 895 .rel(EXTERNAL_CONTENT.toString()) 896 .param("handling", "proxy") 897 .type("text/plain") 898 .build() 899 .toString() 900 .replaceAll("<.*>", "< >"); 901 902 testObj.createObject(null, null, null, null, singletonList(badExternal), null); 903 } 904 905 @Test(expected = ExternalMessageBodyException.class) 906 public void testPostWithExternalMessageBadHandling() throws Exception { 907 when(extContentHandlerFactory.createFromLinks(anyList())) 908 .thenThrow(new ExternalMessageBodyException("")); 909 910 final String badExternal = Link.fromUri("http://test.com") 911 .rel(EXTERNAL_CONTENT.toString()).param("handling", "boogie").type("text/plain").build().toString(); 912 testObj.createObject(null, null, null, null, singletonList(badExternal), null); 913 } 914 915 @Test 916 public void testGetWithBinaryDescription() 917 throws IOException, UnsupportedAlgorithmException { 918 919 final NonRdfSourceDescription mockResource 920 = (NonRdfSourceDescription)setResource(NonRdfSourceDescription.class); 921 when(mockRequest.getMethod()).thenReturn("GET"); 922 when(mockResource.getDescribedResource()).thenReturn(mockBinary); 923 when(mockResource.getOriginalResource()).thenReturn(mockResource); 924 when(mockBinary.getTriples()) 925 .thenReturn(new DefaultRdfStream(createURI("mockBinary"))); 926 when(mockBinary.getTriples()) 927 .thenReturn(new DefaultRdfStream(createURI("mockBinary"), of(new Triple 928 (createURI("mockBinary"), createURI("called"), createURI("child:properties"))))); 929 final Response actual = testObj.getResource(null); 930 assertEquals(OK.getStatusCode(), actual.getStatus()); 931 932 assertTrue("Should be an LDP RDFSource", 933 mockResponse.getHeaders(LINK).contains(buildLink(RDF_SOURCE.getURI(), "type"))); 934 assertShouldNotAdvertiseAcceptPostFlavors(); 935 assertShouldAdvertiseAcceptPatchFlavors(); 936 assertShouldContainLinkToTheBinary(); 937 assertShouldAllowOnlyResourceDescriptionMethods(); 938 939 final Model model = ((RdfNamespacedStream) actual.getEntity()).stream.collect(toModel()); 940 final List<String> rdfNodes = model.listObjects().mapWith(RDFNode::toString).toList(); 941 log.info("Found RDF objects\n{}", rdfNodes); 942 assertTrue("Expected RDF contexts missing", rdfNodes.containsAll(ImmutableSet.of( 943 "LDP_CONTAINMENT", "PROPERTIES", "SERVER_MANAGED"))); 944 // TODO: Above list is missing LDP_MEMBERSHIP - https://jira.lyrasis.org/browse/FCREPO-3165 945 946 } 947 948 private void assertShouldAllowOnlyResourceDescriptionMethods() { 949 final String[] allows = mockResponse.getHeader(HttpHeaders.ALLOW).split(","); 950 951 final Set<String> allowedMethods = new HashSet<>(Arrays.asList(allows)); 952 final Set<String> validMethods = ImmutableSet.of("GET", "HEAD", "DELETE", "PUT", "PATCH", 953 "OPTIONS"); 954 955 allowedMethods.removeAll(validMethods); 956 957 assertEquals("Allow header contains invalid methods: " + allowedMethods, 0, allowedMethods.size()); 958 } 959 960 @Test 961 public void testDelete() throws Exception { 962 when(resourceFactory.getResource(mockTransaction, pathId)).thenReturn(mockContainer); 963 when(mockRequest.getMethod()).thenReturn("DELETE"); 964 final Response actual = testObj.deleteObject(); 965 assertEquals(NO_CONTENT.getStatusCode(), actual.getStatus()); 966 verify(deleteResourceService).perform( 967 eq(mockTransaction), 968 eq(mockContainer), 969 anyString()); 970 } 971 972 @Test 973 public void testPutNewObject() throws Exception { 974 setField(testObj, "externalPath", "some/path"); 975 final Binary mockObject = (Binary)setResource(Binary.class); 976 when(mockRequest.getMethod()).thenReturn("PUT"); 977 when(resourceFactory.getResource(mockTransaction, FedoraId.create("some/path"))).thenReturn(mockObject); 978 979 final Response actual = testObj.createOrReplaceObjectRdf(null, emptyStream, 980 null, null, null, null); 981 982 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 983 } 984 985 @Test(expected = CannotCreateResourceException.class) 986 public void testPutNewObjectLdpr() throws Exception { 987 testObj.createOrReplaceObjectRdf(null, null, null, null, 988 singletonList("<http://www.w3.org/ns/ldp#Resource>; rel=\"type\""), null); 989 } 990 991 @Test 992 public void testPutNewObjectWithRdf() throws Exception { 993 994 when(mockRequest.getMethod()).thenReturn("PUT"); 995 when(resourceFactory.getResource(mockTransaction, pathId)).thenReturn(mockContainer); 996 997 final Response actual = testObj.createOrReplaceObjectRdf(NTRIPLES_TYPE, 998 toInputStream("_:a <info:x> _:c .", UTF_8), null, null, null, null); 999 1000 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1001 verify(createResourceService).perform( 1002 eq(mockTransaction), 1003 anyString(), 1004 eq(pathId), 1005 isNull(), 1006 any(Model.class)); 1007 } 1008 1009 @Test 1010 public void testPutNewBinary() throws Exception { 1011 setField(testObj, "externalPath", "some/path"); 1012 final Binary mockObject = (Binary)setResource(Binary.class); 1013 when(mockRequest.getMethod()).thenReturn("PUT"); 1014 when(resourceFactory.getResource(mockTransaction, FedoraId.create("some/path"))).thenReturn(mockObject); 1015 1016 final Response actual = testObj.createOrReplaceObjectRdf(TEXT_PLAIN_TYPE, 1017 toInputStream("xyz", UTF_8), null, null, nonRDFSourceLink, null); 1018 1019 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1020 } 1021 1022 @Test 1023 public void testPutReplaceRdfObject() throws Exception { 1024 1025 setField(testObj, "externalPath", "some/path"); 1026 final Container mockObject = (Container)setResource(Container.class); 1027 when(mockRequest.getMethod()).thenReturn("PUT"); 1028 when(resourceFactory.getResource(mockTransaction, pathId)).thenReturn(mockObject); 1029 when(resourceHelper.doesResourceExist(mockTransaction, pathId, true)).thenReturn(true); 1030 1031 final Response actual = testObj.createOrReplaceObjectRdf(NTRIPLES_TYPE, 1032 toInputStream("_:a <info:x> _:c .", UTF_8), null, null, null, null); 1033 1034 assertEquals(NO_CONTENT.getStatusCode(), actual.getStatus()); 1035 verify(replacePropertiesService).perform(eq(mockTransaction), anyString(), eq(pathId), 1036 any(Model.class)); 1037 } 1038 1039 @Test(expected = ClientErrorException.class) 1040 public void testPutWithStrictIfMatchHandling() throws Exception { 1041 1042 when(mockHttpConfiguration.putRequiresIfMatch()).thenReturn(true); 1043 when(resourceFactory.getResource(mockTransaction, pathId)).thenReturn(mockContainer); 1044 when(resourceHelper.doesResourceExist(mockTransaction, pathId, true)).thenReturn(true); 1045 1046 testObj.createOrReplaceObjectRdf(NTRIPLES_TYPE, 1047 toInputStream("_:a <info:x> _:c .", UTF_8), null, null, null, null); 1048 1049 } 1050 1051 @Test 1052 public void testPatchObject() throws Exception { 1053 1054 setResource(Container.class); 1055 when(mockRequest.getMethod()).thenReturn("PATCH"); 1056 testObj.updateSparql(toInputStream("INSERT DATA { <> <http://some/predicate> \"xyz\" }", UTF_8)); 1057 } 1058 1059 1060 @Test 1061 public void testPatchBinaryDescription() throws Exception { 1062 setField(testObj, "externalPath", binaryDescriptionPath); 1063 when(mockRequest.getMethod()).thenReturn("PATCH"); 1064 1065 when(mockNonRdfSourceDescription.getTriples()) 1066 .thenReturn(new DefaultRdfStream(createURI("mockBinary"), 1067 of(new Triple(createURI("mockBinary"), createURI("called"), 1068 createURI("child:properties"))))); 1069 1070 when(resourceFactory.getResource(mockTransaction, binaryDescId)) 1071 .thenReturn(mockNonRdfSourceDescription); 1072 testObj.updateSparql(toInputStream("INSERT DATA { <> <http://some/predicate> \"xyz\" }", UTF_8)); 1073 verify(updatePropertiesService).updateProperties( 1074 eq(mockTransaction), 1075 anyString(), 1076 eq(binaryDescId), 1077 contains("<http://some/predicate> \"xyz\"") 1078 ); 1079 } 1080 1081 @Test(expected = BadRequestException.class) 1082 public void testPatchWithoutContent() throws MalformedRdfException, IOException { 1083 testObj.updateSparql(null); 1084 } 1085 1086 @Test(expected = BadRequestException.class) 1087 public void testPatchWithMissingContent() throws MalformedRdfException, IOException { 1088 setResource(Container.class); 1089 testObj.updateSparql(toInputStream("", UTF_8)); 1090 } 1091 1092 @Test(expected = BadRequestException.class) 1093 public void testPatchBinary() throws MalformedRdfException, IOException { 1094 setResource(Binary.class); 1095 testObj.updateSparql(toInputStream("", UTF_8)); 1096 } 1097 1098 @Test 1099 public void testCreateNewObject() throws Exception { 1100 final var resource = setResource(Container.class); 1101 final var finalId = pathId.resolve("b"); 1102 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(resource); 1103 final Response actual = testObj.createObject(null, null, "b", 1104 emptyStream, null, null); 1105 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1106 } 1107 1108 @Test 1109 public void testCreateNewObjectWithVersionedResource() throws Exception { 1110 final var resource = setResource(Container.class); 1111 final var finalId = pathId.resolve("b"); 1112 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(resource); 1113 final String versionedResourceLink = "<" + VERSIONED_RESOURCE.getURI() + ">;rel=\"type\""; 1114 final Response actual = testObj.createObject(null, null, "b", 1115 toInputStream("", UTF_8), singletonList(versionedResourceLink), null); 1116 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1117 } 1118 1119 @Test(expected = UnsupportedMediaTypeException.class) 1120 public void testCreateNewObjectWithSparql() throws Exception { 1121 final var resource = setResource(Container.class); 1122 final var finalId = pathId.resolve("b"); 1123 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(resource); 1124 final Response actual = testObj.createObject(null, 1125 MediaType.valueOf(contentTypeSPARQLUpdate), 1126 "b", 1127 toInputStream("INSERT DATA { <> <http://example.org/somePredicate> \"x\" }", UTF_8), 1128 null, 1129 null); 1130 } 1131 1132 @Test 1133 public void testCreateNewObjectWithRdf() throws Exception { 1134 final var resource = setResource(Container.class); 1135 final var finalId = pathId.resolve("b"); 1136 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(resource); 1137 final Response actual = testObj.createObject(null, NTRIPLES_TYPE, "b", 1138 toInputStream("_:a <info:b> _:c .", UTF_8), null, null); 1139 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1140 verify(createResourceService).perform( 1141 eq(mockTransaction), 1142 anyString(), 1143 eq(finalId), 1144 any(), 1145 any(Model.class)); 1146 } 1147 1148 1149 @Test 1150 public void testCreateNewBinary() throws MalformedRdfException, InvalidChecksumException, 1151 IOException, UnsupportedAlgorithmException, PathNotFoundException { 1152 setResource(Container.class); 1153 final var finalId = pathId.resolve("b"); 1154 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(mockBinary); 1155 try (final InputStream content = toInputStream("x", UTF_8)) { 1156 final Response actual = testObj.createObject(null, APPLICATION_OCTET_STREAM_TYPE, "b", content, 1157 nonRDFSourceLink, null); 1158 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1159 verify(createResourceService).perform( 1160 eq(mockTransaction), 1161 any(), 1162 eq(finalId), 1163 eq(APPLICATION_OCTET_STREAM), 1164 eq(""), 1165 anyLong(), 1166 any(), 1167 any(), 1168 eq(content), 1169 eq(null)); 1170 } 1171 } 1172 1173 @Ignore("Insufficient space root exception not thrown and checkForInsufficientStorageException is not called") 1174 @Test(expected = InsufficientStorageException.class) 1175 public void testCreateNewBinaryWithInsufficientResources() throws Exception { 1176 final var finalId = pathId.resolve("b"); 1177 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(mockBinary); 1178 1179 try (final InputStream content = toInputStream("x", UTF_8)) { 1180 final RuntimeException ex = new RuntimeException(new IOException("root exception", new IOException( 1181 FedoraLdp.INSUFFICIENT_SPACE_IDENTIFYING_MESSAGE))); 1182 doThrow(ex).when(createResourceService).perform(any(), anyString(), eq(finalId), anyString(), 1183 anyString(), any(Long.class), anyList(), isNull(), any(InputStream.class), isNull()); 1184 1185 testObj.createObject(null, APPLICATION_OCTET_STREAM_TYPE, "b", content, nonRDFSourceLink, null); 1186 } 1187 } 1188 1189 @Test 1190 public void testCreateNewBinaryWithContentTypeWithParams() throws MalformedRdfException, 1191 InvalidChecksumException, IOException, UnsupportedAlgorithmException, PathNotFoundException { 1192 1193 setResource(Container.class); 1194 final var finalId = pathId.resolve("b"); 1195 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(mockBinary); 1196 try (final InputStream content = toInputStream("x", UTF_8)) { 1197 final MediaType requestContentType = MediaType.valueOf("some/mime-type; with=some; param=s"); 1198 final Response actual = testObj.createObject(null, requestContentType, "b", content, nonRDFSourceLink, 1199 null); 1200 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1201 verify(createResourceService).perform( 1202 any(), 1203 any(), 1204 eq(finalId), 1205 eq(requestContentType.toString()), 1206 eq(""), 1207 anyLong(), 1208 eq(nonRDFSourceLink), 1209 any(), 1210 eq(content), 1211 eq(null)); 1212 } 1213 } 1214 1215 @Test 1216 public void testCreateNewBinaryWithChecksumSHA() throws MalformedRdfException, 1217 InvalidChecksumException, IOException, UnsupportedAlgorithmException, PathNotFoundException { 1218 1219 final var finalId = pathId.resolve("b"); 1220 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(mockBinary); 1221 try (final InputStream content = toInputStream("x", UTF_8)) { 1222 final MediaType requestContentType = MediaType.valueOf("some/mime-type; with=some; param=s"); 1223 final String sha = "07a4d371f3b7b6283a8e1230b7ec6764f8287bf2"; 1224 final String requestSHA = "sha=" + sha; 1225 final Set<URI> shaURI = singleton(URI.create("urn:sha1:" + sha)); 1226 final Response actual = testObj.createObject(null, requestContentType, "b", content, nonRDFSourceLink, 1227 requestSHA); 1228 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1229 verify(createResourceService).perform( 1230 any(), 1231 any(), 1232 eq(finalId), 1233 eq(requestContentType.toString()), 1234 eq(""), 1235 anyLong(), 1236 eq(nonRDFSourceLink), 1237 eq(shaURI), 1238 eq(content), 1239 eq(null)); 1240 } 1241 } 1242 1243 @Test 1244 public void testCreateNewBinaryWithChecksumSHA256() throws MalformedRdfException, 1245 InvalidChecksumException, IOException, UnsupportedAlgorithmException, PathNotFoundException { 1246 1247 final var finalId = pathId.resolve("b"); 1248 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(mockBinary); 1249 try (final InputStream content = toInputStream("x", UTF_8)) { 1250 final MediaType requestContentType = MediaType.valueOf("some/mime-type; with=some; param=s"); 1251 final String sha = "73cb3858a687a8494ca3323053016282f3dad39d42cf62ca4e79dda2aac7d9ac"; 1252 final String requestSHA = "sha-256=" + sha; 1253 final Set<URI> shaURI = singleton(URI.create("urn:sha-256:" + sha)); 1254 final Response actual = testObj.createObject(null, requestContentType, "b", content, nonRDFSourceLink, 1255 requestSHA); 1256 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1257 verify(createResourceService).perform( 1258 any(), 1259 any(), 1260 eq(finalId), 1261 eq(requestContentType.toString()), 1262 eq(""), 1263 anyLong(), 1264 eq(nonRDFSourceLink), 1265 eq(shaURI), 1266 eq(content), 1267 eq(null)); 1268 } 1269 } 1270 1271 @Test 1272 public void testCreateNewBinaryWithChecksumMD5() throws MalformedRdfException, 1273 InvalidChecksumException, IOException, UnsupportedAlgorithmException, PathNotFoundException { 1274 1275 final var finalId = pathId.resolve("b"); 1276 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(mockBinary); 1277 try (final InputStream content = toInputStream("x", UTF_8)) { 1278 final MediaType requestContentType = MediaType.valueOf("some/mime-type; with=some; param=s"); 1279 final String md5 = "HUXZLQLMuI/KZ5KDcJPcOA=="; 1280 final String requestMD5 = "md5=" + md5; 1281 final Set<URI> md5URI = singleton(URI.create("urn:md5:" + md5)); 1282 final Response actual = testObj.createObject(null, requestContentType, "b", content, nonRDFSourceLink, 1283 requestMD5); 1284 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1285 verify(createResourceService).perform( 1286 any(), 1287 any(), 1288 eq(finalId), 1289 eq(requestContentType.toString()), 1290 eq(""), 1291 anyLong(), 1292 eq(nonRDFSourceLink), 1293 eq(md5URI), 1294 eq(content), 1295 eq(null)); 1296 } 1297 } 1298 1299 @Test 1300 public void testCreateNewBinaryWithChecksumSHAandMD5() throws MalformedRdfException, 1301 InvalidChecksumException, IOException, UnsupportedAlgorithmException, PathNotFoundException { 1302 1303 final var finalId = pathId.resolve("b"); 1304 when(resourceFactory.getResource((Transaction)any(), eq(finalId))).thenReturn(mockBinary); 1305 try (final InputStream content = toInputStream("x", UTF_8)) { 1306 final MediaType requestContentType = MediaType.valueOf("some/mime-type; with=some; param=s"); 1307 1308 final String sha = "07a4d371f3b7b6283a8e1230b7ec6764f8287bf2"; 1309 final String requestSHA = "sha=" + sha; 1310 final URI shaURI = URI.create("urn:sha1:" + sha); 1311 1312 final String md5 = "HUXZLQLMuI/KZ5KDcJPcOA=="; 1313 final String requestMD5 = "md5=" + md5; 1314 final URI md5URI = URI.create("urn:md5:" + md5); 1315 1316 final String requestChecksum = requestSHA + "," + requestMD5; 1317 final HashSet<URI> checksumURIs = new HashSet<>(asList(shaURI, md5URI)); 1318 1319 final Response actual = testObj.createObject(null, requestContentType, "b", content, nonRDFSourceLink, 1320 requestChecksum); 1321 assertEquals(CREATED.getStatusCode(), actual.getStatus()); 1322 verify(createResourceService).perform( 1323 any(), 1324 any(), 1325 eq(finalId), 1326 eq(requestContentType.toString()), 1327 eq(""), 1328 anyLong(), 1329 eq(nonRDFSourceLink), 1330 eq(checksumURIs), 1331 eq(content), 1332 eq(null)); 1333 } 1334 } 1335 1336 @Test(expected = CannotCreateResourceException.class) 1337 public void testLDPRNotImplemented() throws Exception { 1338 final var resource = setResource(Container.class); 1339 when(resourceFactory.getResource(mockTransaction, pathId.resolve("x"))).thenReturn(resource); 1340 testObj.createObject(null, null, "x", null, 1341 singletonList("<http://www.w3.org/ns/ldp#Resource>; rel=\"type\""), null); 1342 } 1343 1344 @Test(expected = ClientErrorException.class) 1345 public void testLDPRNotImplementedInvalidLink() throws Exception { 1346 final var resource = setResource(Container.class); 1347 when(resourceFactory.getResource(mockTransaction, pathId.resolve("x"))).thenReturn(resource); 1348 testObj.createObject(null, null, "x", null, singletonList("<http://foo;rel=\"type\""), null); 1349 } 1350 1351 @Test 1352 public void testGetSimpleContentType() { 1353 final MediaType mediaType = new MediaType("text", "plain", ImmutableMap.of("charset", "UTF-8")); 1354 final MediaType sanitizedMediaType = MediaType.valueOf(getSimpleContentType(mediaType)); 1355 assertEquals("text/plain", sanitizedMediaType.toString()); 1356 } 1357 1358}