/******************************************************************************* * Copyright 2013 SAP AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.sap.core.odata.fit.ref; import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo; import static org.custommonkey.xmlunit.XMLAssert.assertXpathExists; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import org.apache.http.HttpResponse; import org.junit.Test; import com.sap.core.odata.api.commons.HttpContentType; import com.sap.core.odata.api.commons.HttpHeaders; import com.sap.core.odata.api.commons.HttpStatusCodes; import com.sap.core.odata.api.commons.ODataHttpMethod; import com.sap.core.odata.api.edm.Edm; import com.sap.core.odata.testutil.helper.StringHelper; /** * Tests employing the reference scenario changing entities in XML format. * @author SAP AG */ public class EntryXmlChangeTest extends AbstractRefXmlTest { @Test public void create() throws Exception { // Create an entry for a type that has no media resource. String requestBody = getBody(callUri("Teams('1')")) .replace("'1'", "'9'") .replace("Id>1", "Id>9") .replace("Team 1", "Team X") .replaceAll("<link.+?/>", ""); HttpResponse response = postUri("Teams()", requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.CREATED); checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry"); assertEquals(getEndpoint() + "Teams('4')", response.getFirstHeader(HttpHeaders.LOCATION).getValue()); assertNull(response.getFirstHeader(HttpHeaders.ETAG)); assertXpathEvaluatesTo("Team X", "/atom:entry/atom:content/m:properties/d:Name", getBody(response)); // Create an entry for a type that has no media resource. // Add navigation to Employee('4') and Employee('5'). requestBody = "<entry xmlns=\"" + Edm.NAMESPACE_ATOM_2005 + "\"" + "\n" + " xmlns:d=\"" + Edm.NAMESPACE_D_2007_08 + "\"" + "\n" + " xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\">" + "\n" + " <author><name>no author</name></author>" + "\n" + " <content type=\"application/xml\">" + "\n" + " <m:properties>" + "\n" + " <d:Id>109</d:Id>" + "\n" + " <d:Name/>" + "\n" + " <d:Seats>4</d:Seats>" + "\n" + " <d:Version>2</d:Version>" + "\n" + " </m:properties>" + "\n" + " </content>" + "\n" + " <id>Rooms('104')</id>" + "\n" + " <title>Room 104</title>" + "\n" + " <updated>2011-08-10T12:00:23Z</updated>" + "\n" + " <link href=\"Employees('4')\"" + "\n" + " rel=\"" + Edm.NAMESPACE_REL_2007_08 + "nr_Employees\"" + "\n" + " type=\"" + HttpContentType.APPLICATION_ATOM_XML_FEED_UTF8 + "\"/>" + "\n" + " <link href=\"Employees('5')\"" + "\n" + " rel=\"" + Edm.NAMESPACE_REL_2007_08 + "nr_Employees\"" + "\n" + " type=\"" + HttpContentType.APPLICATION_ATOM_XML_FEED_UTF8 + "\"/>" + "\n" + "</entry>"; response = postUri("Rooms", requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.CREATED); checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry"); assertEquals(getEndpoint() + "Rooms('104')", response.getFirstHeader(HttpHeaders.LOCATION).getValue()); checkEtag(response, "W/\"2\""); assertXpathEvaluatesTo("4", "/atom:entry/atom:content/m:properties/d:Seats", getBody(response)); checkUri("Rooms('104')/nr_Employees('4')"); checkUri("Rooms('104')/nr_Employees('5')"); } @Test public void createWithLargeProperty() throws Exception { final String largeTeamName = StringHelper.generateData(888888); // Create an entry for a type that has no media resource. final String requestBody = getBody(callUri("Teams('1')")) .replace("'1'", "'9'") .replace("Id>1", "Id>9") .replace("Team 1", largeTeamName) .replaceAll("<link.+?/>", ""); HttpResponse response = postUri("Teams()", requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.CREATED); checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry"); assertEquals(getEndpoint() + "Teams('4')", response.getFirstHeader(HttpHeaders.LOCATION).getValue()); assertNull(response.getFirstHeader(HttpHeaders.ETAG)); assertXpathEvaluatesTo(largeTeamName, "/atom:entry/atom:content/m:properties/d:Name", getBody(response)); } @Test public void createMinimal() throws Exception { final String requestBody = "<entry xmlns=\"" + Edm.NAMESPACE_ATOM_2005 + "\"" + "\n" + " xmlns:d=\"" + Edm.NAMESPACE_D_2007_08 + "\"" + "\n" + " xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\">" + "\n" + " <content><m:properties><d:Id>99</d:Id></m:properties></content>" + "\n" + "</entry>"; final HttpResponse response = postUri("Teams()", requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.CREATED); checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry"); assertEquals(getEndpoint() + "Teams('4')", response.getFirstHeader(HttpHeaders.LOCATION).getValue()); } @Test public void createInvalidXml() throws Exception { getBody(callUri("Employees('7')", HttpStatusCodes.NOT_FOUND)); final String updateBody = "<invalidXml></invalid>"; final HttpResponse postResult = postUri("Employees", updateBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.CREATED); checkMediaType(postResult, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry"); assertXpathEvaluatesTo("7", "/atom:entry/m:properties/d:EmployeeId", getBody(postResult)); final String requestBodyAfter = getBody(callUri("Employees('7')")); assertXpathEvaluatesTo("7", "/atom:entry/m:properties/d:EmployeeId", requestBodyAfter); } @Test public void createMediaResource() throws Exception { HttpResponse response = postUri("Employees()", "plain text", HttpContentType.TEXT_PLAIN, HttpStatusCodes.CREATED); checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry"); assertEquals(getEndpoint() + "Employees('7')", response.getFirstHeader(HttpHeaders.LOCATION).getValue()); assertNull(response.getFirstHeader(HttpHeaders.ETAG)); assertXpathEvaluatesTo("7", "/atom:entry/m:properties/d:EmployeeId", getBody(response)); response = callUri("Employees('7')/$value"); checkMediaType(response, HttpContentType.TEXT_PLAIN); assertEquals("plain text", getBody(response)); response = postUri("Container2.Photos", "dummy", HttpContentType.TEXT_PLAIN, HttpStatusCodes.CREATED); checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry"); assertEquals(getEndpoint() + "Container2.Photos(Id=5,Type='application%2Foctet-stream')", response.getFirstHeader(HttpHeaders.LOCATION).getValue()); checkEtag(response, "W/\"5\""); assertXpathEvaluatesTo("Photo 5", "/atom:entry/m:properties/d:Name", getBody(response)); response = callUri("Container2.Photos(Id=5,Type='application%2Foctet-stream')/$value"); checkMediaType(response, HttpContentType.TEXT_PLAIN); assertEquals("dummy", getBody(response)); } @Test public void createMediaResourceWithNavigation() throws Exception { HttpResponse response = postUri("Teams('1')/nt_Employees", "X", HttpContentType.TEXT_PLAIN, HttpStatusCodes.CREATED); checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry"); assertEquals(getEndpoint() + "Employees('7')", response.getFirstHeader(HttpHeaders.LOCATION).getValue()); assertXpathEvaluatesTo("7", "/atom:entry/m:properties/d:EmployeeId", getBody(response)); response = callUri("Employees('7')/$value"); checkMediaType(response, HttpContentType.TEXT_PLAIN); assertEquals("X", getBody(response)); } @Test public void createEntryWithInlineFeed() throws Exception { final String buildingWithRooms = "<atom:entry xml:base=\"" + getEndpoint() + "\"" + " xmlns:atom=\"" + Edm.NAMESPACE_ATOM_2005 + "\"" + " xmlns:d=\"" + Edm.NAMESPACE_D_2007_08 + "\"" + " xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\">" + "<atom:content type=\"application/xml\">" + " <m:properties><d:Id>1</d:Id><d:Name>Building 1</d:Name></m:properties>" + "</atom:content>" + "<atom:id>Buildings('1')</atom:id>" + "<atom:link href=\"Buildings('1')/nb_Rooms\" rel=\"" + Edm.NAMESPACE_REL_2007_08 + "nb_Rooms\"" + " type=\"application/atom+xml;type=feed\" title=\"included Rooms\">" + " <m:inline>" + " <atom:feed>" + " <atom:author><atom:name/></atom:author>" + " <atom:id>Rooms</atom:id>" + " <atom:entry>" + " <atom:content type=\"application/xml\">" + " <m:properties>" + " <d:Id>1</d:Id><d:Name>Room 1</d:Name><d:Seats>1</d:Seats><d:Version>1</d:Version>" + " </m:properties>" + " </atom:content>" + " <atom:id>Rooms('1')</atom:id>" + " </atom:entry>" + " <atom:entry>" + " <atom:content type=\"application/xml\">" + " <m:properties>" + " <d:Id>2</d:Id><d:Name>Room 2</d:Name><d:Seats>5</d:Seats><d:Version>2</d:Version>" + " </m:properties>" + " </atom:content>" + " <atom:id>Rooms('2')</atom:id>" + " </atom:entry>" + " </atom:feed>" + " </m:inline>" + "</atom:link>" + "<atom:title type=\"text\">Buildings('1')</atom:title>" + "<atom:updated>2012-02-29T11:59:59Z</atom:updated>" + "</atom:entry>"; HttpResponse response = postUri("Buildings", buildingWithRooms, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.CREATED); checkMediaType(response, HttpContentType.APPLICATION_ATOM_XML_UTF8 + ";type=entry"); assertEquals(getEndpoint() + "Buildings('4')", response.getFirstHeader(HttpHeaders.LOCATION).getValue()); final String body = getBody(response); assertXpathEvaluatesTo("4", "/atom:entry/atom:content/m:properties/d:Id", body); assertXpathEvaluatesTo("105", "/atom:entry/atom:link[@rel='" + Edm.NAMESPACE_REL_2007_08 + "nb_Rooms']/m:inline/atom:feed/atom:entry[2]/atom:content/m:properties/d:Id", body); checkUri("Buildings('4')"); checkUri("Rooms('104')"); checkUri("Buildings('4')/nb_Rooms('104')"); checkUri("Rooms('104')/nr_Building"); checkUri("Rooms('105')"); checkUri("Buildings('4')/nb_Rooms('105')"); checkUri("Rooms('105')/nr_Building"); assertEquals("5", getBody(callUri("Buildings('4')/nb_Rooms('105')/Seats/$value"))); } @Test public void update() throws Exception { final String requestBody = getBody(callUri("Employees('2')")) .replace("'2'", "'9'") .replace("EmployeeId>2", "EmployeeId>9") .replace(EMPLOYEE_2_NAME, "Mister X") .replace("<d:Age>" + EMPLOYEE_2_AGE + "</d:Age>", "") .replace(">2003-07-01T00:00:00", " m:null='true'>") .replaceAll("<link.+?/>", ""); final HttpResponse response = callUri(ODataHttpMethod.PUT, "Employees('1')", null, null, requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.NO_CONTENT); assertFalse(response.containsHeader(HttpHeaders.LOCATION)); final String body = getBody(callUri("Employees('1')")); assertXpathEvaluatesTo("Mister X", "/atom:entry/m:properties/d:EmployeeName", body); assertXpathEvaluatesTo("0", "/atom:entry/m:properties/d:Age", body); assertXpathEvaluatesTo("true", "/atom:entry/m:properties/d:EntryDate/@m:null", body); } @Test public void updateUnknownProperty() throws Exception { final String requestBody = getBody(callUri("Employees('2')")) .replace("<d:Age>" + EMPLOYEE_2_AGE + "</d:Age>", "<d:Age>33</d:Age><d:SomeUnknownTag>SomeUnknownValue</d:SomeUnknownTag>"); putUri("Employees('2')", requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.BAD_REQUEST); // check nothing has changed assertXpathEvaluatesTo(EMPLOYEE_2_AGE, "/atom:entry/m:properties/d:Age", getBody(callUri("Employees('2')"))); } @Test public void updateInvalidXml() throws Exception { final String requestBodyBefore = getBody(callUri("Employees('2')")); putUri("Employees('2')", "<invalidXml></invalid>", HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.BAD_REQUEST); assertEquals(requestBodyBefore, getBody(callUri("Employees('2')"))); } @Test public void patchAndMerge() throws Exception { String requestBody = "<entry xmlns=\"" + Edm.NAMESPACE_ATOM_2005 + "\"" + "\n" + " xmlns:d=\"" + Edm.NAMESPACE_D_2007_08 + "\"" + "\n" + " xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\">" + "\n" + " <content/>" + "\n" + " <m:properties>" + "\n" + " <d:Location>" + "\n" + " <d:City>" + "\n" + " <d:PostalCode>69124</d:PostalCode>" + "\n" + " <d:CityName>" + CITY_1_NAME + "</d:CityName>" + "\n" + " </d:City>" + "\n" + " <d:Country>Germany</d:Country>" + "\n" + " </d:Location>" + "\n" + " <d:EntryDate m:null=\"true\"/>" + "\n" + " </m:properties>" + "\n" + "</entry>"; callUri(ODataHttpMethod.PATCH, "Employees('2')", null, null, requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.NO_CONTENT); final String body = getBody(callUri("Employees('2')")); assertXpathEvaluatesTo(CITY_1_NAME, "/atom:entry/m:properties/d:Location/d:City/d:CityName", body); assertXpathEvaluatesTo(EMPLOYEE_2_AGE, "/atom:entry/m:properties/d:Age", body); requestBody = "<entry xmlns=\"" + Edm.NAMESPACE_ATOM_2005 + "\">" + "\n" + " <content xmlns:d=\"" + Edm.NAMESPACE_D_2007_08 + "\"" + "\n" + " xmlns:m=\"" + Edm.NAMESPACE_M_2007_08 + "\"" + "\n" + " type=\"" + HttpContentType.APPLICATION_XML_UTF8 + "\">" + "\n" + " <m:properties><d:Name>Room X</d:Name></m:properties>" + "\n" + " </content>" + "\n" + "</entry>"; HttpResponse response = callUri(ODataHttpMethod.MERGE, "Rooms('3')", HttpHeaders.IF_MATCH, "W/\"3\"", requestBody, HttpContentType.APPLICATION_ATOM_XML_ENTRY, HttpStatusCodes.NO_CONTENT); checkEtag(response, "W/\"3\""); assertXpathEvaluatesTo("Room X", "/atom:entry/atom:content/m:properties/d:Name", getBody(callUri("Rooms('3')"))); } @Test public void delete() throws Exception { final String uri = "Employees('2')"; deleteUriOk(uri); final String requestBody = getBody(callUri(uri, HttpStatusCodes.NOT_FOUND)); assertXpathExists("/m:error", requestBody); assertXpathEvaluatesTo("Requested entity could not be found.", "/m:error/m:message", requestBody); } }