diff options
author | Jon Marius Venstad <venstad@gmail.com> | 2020-09-29 20:50:28 +0200 |
---|---|---|
committer | Jon Marius Venstad <venstad@gmail.com> | 2020-09-30 10:23:35 +0200 |
commit | 85bffffb34ea959576baeef0affd73ea803fc93a (patch) | |
tree | b0c496b35b67a41fc7cd311964e11fb46b0a3155 /vespaclient-container-plugin | |
parent | 574989151329e44cd49b510b8e53aa822ec2b413 (diff) |
More response tests — some more try-catch
Diffstat (limited to 'vespaclient-container-plugin')
3 files changed, 259 insertions, 178 deletions
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java index 08a1b16e527..ab221274634 100644 --- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java +++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java @@ -244,12 +244,19 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { }, // TODO jonmv: make streaming — first doc indicates 200 OK anyway — unless session dies, which is a semi-200 anyway document -> { - synchronized (monitor) { // Putting things into the slime is not thread safe, so need synchronization. - SlimeUtils.copyObject(SlimeUtils.jsonToSlime(JsonWriter.toByteArray(document)).get(), - documents.addObject()); + try { + synchronized (monitor) { // Putting things into the slime is not thread safe, so need synchronization. + SlimeUtils.copyObject(SlimeUtils.jsonToSlime(JsonWriter.toByteArray(document)).get(), + documents.addObject()); + } + } + // TODO jonmv: This shouldn't happen much, but ... expose errors too? + catch (RuntimeException e) { + log.log(WARNING, "Exception serializing document in document/v1 visit response", e); } }); } + private ContentChannel getDocument(HttpRequest request, DocumentPath path, ResponseHandler handler) { DocumentId id = path.id(); DocumentOperationParameters parameters = parameters(); @@ -259,13 +266,18 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler { parameters, new OperationContext((type, message) -> handleError(request, type, message, responseRoot(request, id), handler), document -> { - Cursor root = responseRoot(request, id); - document.map(JsonWriter::toByteArray) - .map(SlimeUtils::jsonToSlime) - .ifPresent(doc -> SlimeUtils.copyObject(doc.get().field("fields"), root.setObject("fields"))); - respond(document.isPresent() ? 200 : 404, - root, - handler); + try { + Cursor root = responseRoot(request, id); + document.map(JsonWriter::toByteArray) + .map(SlimeUtils::jsonToSlime) + .ifPresent(doc -> SlimeUtils.copyObject(doc.get().field("fields"), root.setObject("fields"))); + respond(document.isPresent() ? 200 : 404, + root, + handler); + } + catch (Exception e) { + serverError(request, new RuntimeException(e), handler); + } })); return ignoredContent; } diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/DocumentOperationExecutorMock.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/DocumentOperationExecutorMock.java index 95c90e1a4ca..3d350adab87 100644 --- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/DocumentOperationExecutorMock.java +++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/DocumentOperationExecutorMock.java @@ -50,6 +50,9 @@ public class DocumentOperationExecutorMock implements DocumentOperationExecutor @Override public String routeToCluster(String cluster) { + if ("throw-me".equals(cluster)) + throw new IllegalArgumentException(cluster); + return "route-to-" + cluster; } diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java index b13af7fd067..82d0a530600 100644 --- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java +++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java @@ -7,24 +7,17 @@ import com.yahoo.document.Document; import com.yahoo.document.DocumentGet; import com.yahoo.document.DocumentPut; import com.yahoo.document.DocumentRemove; -import com.yahoo.document.DocumentType; import com.yahoo.document.DocumentTypeManager; -import com.yahoo.document.DocumentUpdate; import com.yahoo.document.TestAndSetCondition; import com.yahoo.document.config.DocumentmanagerConfig; -import com.yahoo.document.datatypes.StringFieldValue; -import com.yahoo.document.restapi.DocumentOperationExecutor; import com.yahoo.document.restapi.DocumentOperationExecutor.Group; import com.yahoo.document.restapi.DocumentOperationExecutor.VisitorOptions; import com.yahoo.document.restapi.DocumentOperationExecutorMock; import com.yahoo.document.restapi.resource.DocumentV1ApiHandler.DocumentOperationParser; -import com.yahoo.document.update.FieldUpdate; import com.yahoo.documentapi.DocumentAccessParams; import com.yahoo.documentapi.local.LocalDocumentAccess; import com.yahoo.jdisc.Metric; -import com.yahoo.jdisc.http.HttpRequest; import com.yahoo.metrics.simple.MetricReceiver; -import com.yahoo.prelude.fastsearch.StringField; import com.yahoo.searchdefinition.derived.Deriver; import com.yahoo.slime.JsonFormat; import com.yahoo.slime.SlimeUtils; @@ -40,8 +33,13 @@ import java.util.Optional; import static com.yahoo.document.restapi.DocumentOperationExecutor.ErrorType.BAD_REQUEST; import static com.yahoo.document.restapi.DocumentOperationExecutor.ErrorType.ERROR; +import static com.yahoo.document.restapi.DocumentOperationExecutor.ErrorType.OVERLOAD; +import static com.yahoo.document.restapi.DocumentOperationExecutor.ErrorType.PRECONDITION_FAILED; +import static com.yahoo.document.restapi.DocumentOperationExecutor.ErrorType.TIMEOUT; import static com.yahoo.documentapi.DocumentOperationParameters.parameters; import static com.yahoo.jdisc.http.HttpRequest.Method.DELETE; +import static com.yahoo.jdisc.http.HttpRequest.Method.OPTIONS; +import static com.yahoo.jdisc.http.HttpRequest.Method.PATCH; import static com.yahoo.jdisc.http.HttpRequest.Method.POST; import static com.yahoo.jdisc.http.HttpRequest.Method.PUT; import static java.nio.charset.StandardCharsets.UTF_8; @@ -88,176 +86,244 @@ public class DocumentV1ApiTest { @Test public void testResponses() { - try (RequestHandlerTestDriver driver = new RequestHandlerTestDriver(handler)) { - // GET at non-existent path returns 404 with available paths - var response = driver.sendRequest("http://localhost/document/v1/not-found"); - assertSameJson("{" + - " \"pathId\": \"/document/v1/not-found\"," + - " \"message\": \"Nothing at '/document/v1/not-found'. Available paths are:\\n" + - "/document/v1/\\n" + - "/document/v1/{namespace}/{documentType}/docid/\\n" + - "/document/v1/{namespace}/{documentType}/group/{group}/\\n" + - "/document/v1/{namespace}/{documentType}/number/{number}/\\n" + - "/document/v1/{namespace}/{documentType}/docid/{docid}\\n" + - "/document/v1/{namespace}/{documentType}/group/{group}/{docid}\\n" + - "/document/v1/{namespace}/{documentType}/number/{number}/{docid}\"" + - "}", - response.readAll()); - assertEquals("application/json; charset=UTF-8", response.getResponse().headers().getFirst("Content-Type")); - assertEquals(404, response.getStatus()); + RequestHandlerTestDriver driver = new RequestHandlerTestDriver(handler); + // GET at non-existent path returns 404 with available paths + var response = driver.sendRequest("http://localhost/document/v1/not-found"); + assertSameJson("{" + + " \"pathId\": \"/document/v1/not-found\"," + + " \"message\": \"Nothing at '/document/v1/not-found'. Available paths are:\\n" + + "/document/v1/\\n" + + "/document/v1/{namespace}/{documentType}/docid/\\n" + + "/document/v1/{namespace}/{documentType}/group/{group}/\\n" + + "/document/v1/{namespace}/{documentType}/number/{number}/\\n" + + "/document/v1/{namespace}/{documentType}/docid/{docid}\\n" + + "/document/v1/{namespace}/{documentType}/group/{group}/{docid}\\n" + + "/document/v1/{namespace}/{documentType}/number/{number}/{docid}\"" + + "}", + response.readAll()); + assertEquals("application/json; charset=UTF-8", response.getResponse().headers().getFirst("Content-Type")); + assertEquals(404, response.getStatus()); - // GET at root is a visit. Numeric parameters have an upper bound. - response = driver.sendRequest("http://localhost/document/v1?cluster=lackluster&bucketSpace=default&wantedDocumentCount=1025&concurrency=123" + - "&selection=all%20the%20things&fieldSet=[id]&continuation=token"); - executor.lastVisitContext().document(doc1); - executor.lastVisitContext().document(doc2); - executor.lastVisitContext().document(doc3); - executor.lastVisitContext().success(Optional.of("token")); - assertSameJson("{" + - " \"pathId\": \"/document/v1\"," + - " \"documents\": [" + - " {" + - " \"id\": \"id:space:music::one\"," + - " \"fields\": {" + - " \"artist\": \"Tom Waits\"" + - " }" + - " }," + - " {" + - " \"id\": \"id:space:music:n=1:two\"," + - " \"fields\": {" + - " \"artist\": \"Asa-Chan & Jun-Ray\"" + - " }" + - " }," + - " {" + - " \"id\": \"id:space:music:g=a:three\"," + - " \"fields\": {}" + - " }" + - " ]," + - " \"continuation\": \"token\"" + - "}", - response.readAll()); - assertEquals(200, response.getStatus()); - assertEquals(VisitorOptions.builder().cluster("lackluster").bucketSpace("default").wantedDocumentCount(1024) - .concurrency(100).selection("all the things").fieldSet("[id]").continuation("token").build(), - executor.lastOptions()); + // GET at root is a visit. Numeric parameters have an upper bound. + response = driver.sendRequest("http://localhost/document/v1?cluster=lackluster&bucketSpace=default&wantedDocumentCount=1025&concurrency=123" + + "&selection=all%20the%20things&fieldSet=[id]&continuation=token"); + executor.lastVisitContext().document(doc1); + executor.lastVisitContext().document(doc2); + executor.lastVisitContext().document(doc3); + executor.lastVisitContext().success(Optional.of("token")); + assertSameJson("{" + + " \"pathId\": \"/document/v1\"," + + " \"documents\": [" + + " {" + + " \"id\": \"id:space:music::one\"," + + " \"fields\": {" + + " \"artist\": \"Tom Waits\"" + + " }" + + " }," + + " {" + + " \"id\": \"id:space:music:n=1:two\"," + + " \"fields\": {" + + " \"artist\": \"Asa-Chan & Jun-Ray\"" + + " }" + + " }," + + " {" + + " \"id\": \"id:space:music:g=a:three\"," + + " \"fields\": {}" + + " }" + + " ]," + + " \"continuation\": \"token\"" + + "}", + response.readAll()); + assertEquals(200, response.getStatus()); + assertEquals(VisitorOptions.builder().cluster("lackluster").bucketSpace("default").wantedDocumentCount(1024) + .concurrency(100).selection("all the things").fieldSet("[id]").continuation("token").build(), + executor.lastOptions()); - // GET with namespace and document type is a restricted visit. - response = driver.sendRequest("http://localhost/document/v1/space/music/docid"); - executor.lastVisitContext().error(BAD_REQUEST, "nope"); - assertSameJson("{" + - " \"pathId\": \"/document/v1/space/music/docid\"," + - " \"documents\": []," + - " \"message\": \"nope\"" + - "}", - response.readAll()); - assertEquals(400, response.getStatus()); - assertEquals(VisitorOptions.builder().namespace("space").documentType("music").build(), - executor.lastOptions()); + // GET with namespace and document type is a restricted visit. + response = driver.sendRequest("http://localhost/document/v1/space/music/docid"); + executor.lastVisitContext().error(BAD_REQUEST, "nope"); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/docid\"," + + " \"documents\": []," + + " \"message\": \"nope\"" + + "}", + response.readAll()); + assertEquals(400, response.getStatus()); + assertEquals(VisitorOptions.builder().namespace("space").documentType("music").build(), + executor.lastOptions()); - // GET with namespace, document type and group is a restricted visit. - response = driver.sendRequest("http://localhost/document/v1/space/music/group/best"); - executor.lastVisitContext().error(ERROR, "error"); - assertSameJson("{" + - " \"pathId\": \"/document/v1/space/music/group/best\"," + - " \"documents\": []," + - " \"message\": \"error\"" + - "}", - response.readAll()); - assertEquals(500, response.getStatus()); - assertEquals(VisitorOptions.builder().namespace("space").documentType("music").group(Group.of("best")).build(), - executor.lastOptions()); + // GET with namespace, document type and group is a restricted visit. + response = driver.sendRequest("http://localhost/document/v1/space/music/group/best"); + executor.lastVisitContext().error(ERROR, "error"); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/group/best\"," + + " \"documents\": []," + + " \"message\": \"error\"" + + "}", + response.readAll()); + assertEquals(500, response.getStatus()); + assertEquals(VisitorOptions.builder().namespace("space").documentType("music").group(Group.of("best")).build(), + executor.lastOptions()); - // GET with namespace, document type and number is a restricted visit. - response = driver.sendRequest("http://localhost/document/v1/space/music/number/123"); - executor.lastVisitContext().success(Optional.empty()); - assertSameJson("{" + - " \"pathId\": \"/document/v1/space/music/number/123\"," + - " \"documents\": []" + - "}", - response.readAll()); - assertEquals(200, response.getStatus()); - assertEquals(VisitorOptions.builder().namespace("space").documentType("music").group(Group.of(123)).build(), - executor.lastOptions()); + // GET with namespace, document type and number is a restricted visit. + response = driver.sendRequest("http://localhost/document/v1/space/music/number/123"); + executor.lastVisitContext().success(Optional.empty()); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/number/123\"," + + " \"documents\": []" + + "}", + response.readAll()); + assertEquals(200, response.getStatus()); + assertEquals(VisitorOptions.builder().namespace("space").documentType("music").group(Group.of(123)).build(), + executor.lastOptions()); - // GET with full document ID is a document get operation which returns 404 when no document is found - response = driver.sendRequest("http://localhost/document/v1/space/music/docid/one?cluster=lackluster&fieldSet=go"); - executor.lastOperationContext().success(Optional.empty()); - assertSameJson("{" + - " \"pathId\": \"/document/v1/space/music/docid/one\"," + - " \"id\": \"id:space:music::one\"" + - "}", - response.readAll()); - assertEquals(404, response.getStatus()); - assertEquals(new DocumentGet(doc1.getId()), executor.lastOperation()); - assertEquals(parameters().withRoute("route-to-lackluster").withFieldSet("go"), executor.lastParameters()); + // GET with full document ID is a document get operation which returns 404 when no document is found + response = driver.sendRequest("http://localhost/document/v1/space/music/docid/one?cluster=lackluster&fieldSet=go"); + executor.lastOperationContext().success(Optional.empty()); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/docid/one\"," + + " \"id\": \"id:space:music::one\"" + + "}", + response.readAll()); + assertEquals(404, response.getStatus()); + assertEquals(new DocumentGet(doc1.getId()), executor.lastOperation()); + assertEquals(parameters().withRoute("route-to-lackluster").withFieldSet("go"), executor.lastParameters()); - // GET with full document ID is a document get operation. - response = driver.sendRequest("http://localhost/document/v1/space/music/docid/one?"); - executor.lastOperationContext().success(Optional.of(doc1)); - assertSameJson("{" + - " \"pathId\": \"/document/v1/space/music/docid/one\"," + - " \"id\": \"id:space:music::one\"," + - " \"fields\": {" + - " \"artist\": \"Tom Waits\"" + - " }" + - "}", - response.readAll()); - assertEquals(200, response.getStatus()); - assertEquals(new DocumentGet(doc1.getId()), executor.lastOperation()); - assertEquals(parameters(), executor.lastParameters()); + // GET with full document ID is a document get operation. + response = driver.sendRequest("http://localhost/document/v1/space/music/docid/one?"); + executor.lastOperationContext().success(Optional.of(doc1)); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/docid/one\"," + + " \"id\": \"id:space:music::one\"," + + " \"fields\": {" + + " \"artist\": \"Tom Waits\"" + + " }" + + "}", + response.readAll()); + assertEquals(200, response.getStatus()); + assertEquals(new DocumentGet(doc1.getId()), executor.lastOperation()); + assertEquals(parameters(), executor.lastParameters()); - // POST with a document payload is a document put operation. - response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two?condition=test%20it", POST, - "{" + - " \"fields\": {" + - " \"artist\": \"Asa-Chan & Jun-Ray\"" + - " }" + - "}"); - executor.lastOperationContext().success(Optional.empty()); - assertSameJson("{" + - " \"pathId\": \"/document/v1/space/music/number/1/two\"," + - " \"id\": \"id:space:music:n=1:two\"" + - "}", - response.readAll()); - assertEquals(200, response.getStatus()); - DocumentPut put = new DocumentPut(doc2); - put.setCondition(new TestAndSetCondition("test it")); - assertEquals(put, executor.lastOperation()); - assertEquals(parameters(), executor.lastParameters()); + // GET with not encoded / in user specified part of document id is a 404 + response = driver.sendRequest("http://localhost/document/v1/space/music/docid/one/two/three"); + response.readAll(); // Must drain body. + assertEquals(404, response.getStatus()); - // PUT with a document update payload is a document update operation. - response = driver.sendRequest("http://localhost/document/v1/space/music/group/a/three?create=true", PUT, - "{" + - " \"fields\": {" + - " \"artist\": { \"assign\": \"Lisa Ekdahl\" }" + - " }" + - "}"); - executor.lastOperationContext().success(Optional.empty()); - assertSameJson("{" + - " \"pathId\": \"/document/v1/space/music/group/a/three\"," + - " \"id\": \"id:space:music:g=a:three\"" + - "}", - response.readAll()); - assertEquals(200, response.getStatus()); - DocumentUpdate update = new DocumentUpdate(manager.getDocumentType("music"), "id:space:music:g=a:three"); - update.addFieldUpdate(FieldUpdate.createAssign(manager.getDocumentType("music").getField("artist"), - new StringFieldValue("Lisa Ekdahl"))); - update.setCreateIfNonExistent(true); - assertEquals(update, executor.lastOperation()); - assertEquals(parameters(), executor.lastParameters()); + // POST with a document payload is a document put operation. + response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two?condition=test%20it", POST, + "{" + + " \"fields\": {" + + " \"artist\": \"Asa-Chan & Jun-Ray\"" + + " }" + + "}"); + executor.lastOperationContext().success(Optional.empty()); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/number/1/two\"," + + " \"id\": \"id:space:music:n=1:two\"" + + "}", + response.readAll()); + assertEquals(200, response.getStatus()); + DocumentPut put = new DocumentPut(doc2); + put.setCondition(new TestAndSetCondition("test it")); + assertEquals(put, executor.lastOperation()); + assertEquals(parameters(), executor.lastParameters()); - // DELETE with full document ID is a document remove operation. - response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two?route=route", DELETE); - executor.lastOperationContext().success(Optional.empty()); - assertSameJson("{" + - " \"pathId\": \"/document/v1/space/music/number/1/two\"," + - " \"id\": \"id:space:music:n=1:two\"" + - "}", - response.readAll()); - assertEquals(200, response.getStatus()); - assertEquals(new DocumentRemove(doc2.getId()), executor.lastOperation()); - assertEquals(parameters().withRoute("route"), executor.lastParameters()); - } + // POST with illegal payload is a 400 + response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two?condition=test%20it", POST, + "{" + + " ┻━┻︵ \\(°□°)/ ︵ ┻━┻" + + "}"); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/number/1/two\"," + + " \"message\": \"Unexpected character ('┻' (code 9531 / 0x253b)): was expecting double-quote to start field name\\n at [Source: com.yahoo.jdisc.handler.UnsafeContentInputStream@50ecde95; line: 1, column: 7]\"" + + "}", + response.readAll()); + assertEquals(400, response.getStatus()); + + // PUT on a unknown document type is a 400 + response = driver.sendRequest("http://localhost/document/v1/space/house/group/a/three?create=true", PUT, + "{" + + " \"fields\": {" + + " \"artist\": { \"assign\": \"Lisa Ekdahl\" }" + + " }" + + "}"); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/house/group/a/three\"," + + " \"message\": \"Document type house does not exist\"" + + "}", + response.readAll()); + assertEquals(400, response.getStatus()); + + // DELETE with full document ID is a document remove operation. + response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two?route=route", DELETE); + executor.lastOperationContext().success(Optional.empty()); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/number/1/two\"," + + " \"id\": \"id:space:music:n=1:two\"" + + "}", + response.readAll()); + assertEquals(200, response.getStatus()); + assertEquals(new DocumentRemove(doc2.getId()), executor.lastOperation()); + assertEquals(parameters().withRoute("route"), executor.lastParameters()); + + // GET with non-existent cluster is a 400 + response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two?cluster=throw-me"); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/number/1/two\"," + + " \"message\": \"throw-me\"" + + "}", + response.readAll()); + assertEquals(400, response.getStatus()); + + // TIMEOUT is a 504 + response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two"); + executor.lastOperationContext().error(TIMEOUT, "timeout"); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/number/1/two\"," + + " \"id\": \"id:space:music:n=1:two\"," + + " \"message\": \"timeout\"" + + "}", + response.readAll()); + assertEquals(504, response.getStatus()); + + // OVERLOAD is a 429 + response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two"); + executor.lastOperationContext().error(OVERLOAD, "overload"); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/number/1/two\"," + + " \"id\": \"id:space:music:n=1:two\"," + + " \"message\": \"overload\"" + + "}", + response.readAll()); + assertEquals(429, response.getStatus()); + + // PRECONDITION_FAILED is a 412 + response = driver.sendRequest("http://localhost/document/v1/space/music/number/1/two"); + executor.lastOperationContext().error(PRECONDITION_FAILED, "no dice"); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/number/1/two\"," + + " \"id\": \"id:space:music:n=1:two\"," + + " \"message\": \"no dice\"" + + "}", + response.readAll()); + assertEquals(412, response.getStatus()); + + // OPTIONS gets options + response = driver.sendRequest("https://localhost/document/v1/space/music/docid/one", OPTIONS); + assertEquals("", response.readAll()); + assertEquals(204, response.getStatus()); + assertEquals("GET,POST,PUT,DELETE", response.getResponse().headers().getFirst("Allow")); + + // PATCH is not allowed + response = driver.sendRequest("https://localhost/document/v1/space/music/docid/one", PATCH); + assertSameJson("{" + + " \"pathId\": \"/document/v1/space/music/docid/one\"," + + " \"message\": \"'PATCH' not allowed at '/document/v1/space/music/docid/one'. Allowed methods are: GET, POST, PUT, DELETE\"" + + "}", + response.readAll()); + assertEquals(405, response.getStatus()); + + driver.close(); } void assertSameJson(String expected, String actual) { |