diff --git a/src/main/java/io/stargate/sgv2/jsonapi/StargateJsonApi.java b/src/main/java/io/stargate/sgv2/jsonapi/StargateJsonApi.java index 59571016f1..d0e7384277 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/StargateJsonApi.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/StargateJsonApi.java @@ -284,8 +284,8 @@ } """), @ExampleObject( - name = "resultRead", - summary = "Read command result", + name = "resultFind", + summary = "`find` command result", value = """ { @@ -315,6 +315,28 @@ } } """), + @ExampleObject( + name = "resultFindOne", + summary = "`findOne` command result", + value = + """ + { + "data": { + "docs": [ + { + "_id": "1", + "location": "London", + "race": { + "competitors": 100, + "start_date": "2022-08-15" + }, + "tags": [ "eu" ] + } + ], + "count": 1 + } + } + """), @ExampleObject( name = "resultFindOneAndUpdate", summary = "`findOneAndUpdate` command result", @@ -330,24 +352,37 @@ "competitors": 100, "start_date": "2022-08-15" }, - "tags": [ - "eu" - ], "count": 3 } ], "count": 1, "status": { - "upsertedId": "1", - "matchedCount": 0, + "matchedCount": 1, "modifiedCount": 1 - }, - "errors": [ + } + } + } + """), + @ExampleObject( + name = "resultFindOneAndUpdateUpsert", + summary = "`findOneAndUpdate` command with upsert result", + value = + """ + { + "data": { + "docs": [ { - "message": "Failed to update document with _id doc1: Unable to complete transaction due to concurrent transactions", - "errorCode": "CONCURRENCY_FAILURE" + "_id": "1", + "location": "New York", + "count": 3 } - ] + ], + "count": 1, + "status": { + "upsertedId": "1", + "matchedCount": 0, + "modifiedCount": 1 + } } } """), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneAndUpdateCommand.java b/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneAndUpdateCommand.java index 5c29c31b54..2328317b32 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneAndUpdateCommand.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneAndUpdateCommand.java @@ -8,6 +8,7 @@ import io.stargate.sgv2.jsonapi.api.model.command.clause.update.UpdateClause; import javax.annotation.Nullable; import javax.validation.Valid; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import org.eclipse.microprofile.openapi.annotations.media.Schema; @@ -17,15 +18,26 @@ @JsonTypeName("findOneAndUpdate") public record FindOneAndUpdateCommand( @Valid @JsonProperty("filter") FilterClause filterClause, - @Valid @JsonProperty("update") UpdateClause updateClause, + @NotNull @Valid @JsonProperty("update") UpdateClause updateClause, @Valid @Nullable Options options) implements ReadCommand, Filterable { + + @Schema( + name = "FindOneAndUpdateCommand.Options", + description = "Options for `findOneAndUpdate` command.") public record Options( - @Valid - @Nullable + @Nullable @Pattern( regexp = "(after|before)", message = "returnDocument value can only be 'before' or 'after'") + @Schema( + description = + "Specifies which document to perform the projection on. If `before` the projection is performed on the document before the update is applied, if `after` the document projection is from the document after the update.", + defaultValue = "before") String returnDocument, - boolean upsert) {} + @Schema( + description = + "When `true`, if no documents match the `filter` clause the command will create a new _empty_ document and apply the `update` clause and all equality filters to the empty document.", + defaultValue = "false") + boolean upsert) {} } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneCommand.java b/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneCommand.java index 4fc763e8f2..be9caeb95a 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneCommand.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneCommand.java @@ -6,7 +6,6 @@ import io.stargate.sgv2.jsonapi.api.model.command.ReadCommand; import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.FilterClause; import io.stargate.sgv2.jsonapi.api.model.command.clause.sort.SortClause; -import javax.annotation.Nullable; import javax.validation.Valid; import org.eclipse.microprofile.openapi.annotations.media.Schema; @@ -14,8 +13,5 @@ @JsonTypeName("findOne") public record FindOneCommand( @Valid @JsonProperty("filter") FilterClause filterClause, - @Valid @JsonProperty("sort") SortClause sortClause, - @Nullable Options options) - implements ReadCommand, Filterable { - public record Options() {} -} + @Valid @JsonProperty("sort") SortClause sortClause) + implements ReadCommand, Filterable {} diff --git a/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/UpdateManyCommand.java b/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/UpdateManyCommand.java index 19355e3be6..7ddbac492c 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/UpdateManyCommand.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/UpdateManyCommand.java @@ -25,7 +25,7 @@ public record UpdateManyCommand( public record Options( @Schema( description = - "When `true`, if no documents match the `filter` clause the command will create a new _empty_ document and apply the `update` clause to the empty document.", + "When `true`, if no documents match the `filter` clause the command will create a new _empty_ document and apply the `update` clause and all equality filters to the empty document.", defaultValue = "false") boolean upsert) {} } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/UpdateOneCommand.java b/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/UpdateOneCommand.java index 57bb2bf808..316f2ae74c 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/UpdateOneCommand.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/api/model/command/impl/UpdateOneCommand.java @@ -25,7 +25,7 @@ public record UpdateOneCommand( public record Options( @Schema( description = - "When `true`, if no documents match the `filter` clause the command will create a new _empty_ document and apply the `update` clause to the empty document.", + "When `true`, if no documents match the `filter` clause the command will create a new _empty_ document and apply the `update` clause and all equality filters to the empty document.", defaultValue = "false") boolean upsert) {} } diff --git a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/CollectionResource.java b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/CollectionResource.java index 8874196a63..3e5487af13 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/CollectionResource.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/CollectionResource.java @@ -84,8 +84,8 @@ public CollectionResource(CommandProcessor commandProcessor) { @ExampleObject(ref = "countDocuments"), @ExampleObject(ref = "deleteOne"), @ExampleObject(ref = "deleteMany"), - @ExampleObject(ref = "findOne"), @ExampleObject(ref = "find"), + @ExampleObject(ref = "findOne"), @ExampleObject(ref = "findOneAndUpdate"), @ExampleObject(ref = "insertOne"), @ExampleObject(ref = "insertMany"), @@ -105,8 +105,10 @@ public CollectionResource(CommandProcessor commandProcessor) { @ExampleObject(ref = "resultCount"), @ExampleObject(ref = "resultDeleteOne"), @ExampleObject(ref = "resultDeleteMany"), - @ExampleObject(ref = "resultRead"), + @ExampleObject(ref = "resultFind"), + @ExampleObject(ref = "resultFindOne"), @ExampleObject(ref = "resultFindOneAndUpdate"), + @ExampleObject(ref = "resultFindOneAndUpdateUpsert"), @ExampleObject(ref = "resultInsert"), @ExampleObject(ref = "resultUpdateOne"), @ExampleObject(ref = "resultUpdateOneUpsert"), diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/impl/FindOperation.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/impl/FindOperation.java index 3be24672d8..9f0c6947fb 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/impl/FindOperation.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/model/impl/FindOperation.java @@ -33,40 +33,48 @@ public record FindOperation( @Override public Uni> execute(QueryExecutor queryExecutor) { + // get FindResponse return getDocuments(queryExecutor, pagingState(), null) - .onItem() - .transform(docs -> new ReadOperationPage(docs.docs(), docs.pagingState())); + + // map the response to result + .map(docs -> new ReadOperationPage(docs.docs(), docs.pagingState())); } @Override public Uni getDocuments( QueryExecutor queryExecutor, String pagingState, DBFilterBase.IDFilter additionalIdFilter) { + + // ensure we pass failure down if read type is not DOCUMENT or KEY + // COUNT is not supported switch (readType) { - case DOCUMENT: - case KEY: - { - QueryOuterClass.Query query = buildSelectQuery(additionalIdFilter); - return findDocument( - queryExecutor, - query, - pagingState, - pageSize, - ReadType.DOCUMENT == readType, - objectMapper); - } - default: - throw new JsonApiException( - ErrorCode.UNSUPPORTED_OPERATION, "Unsupported find operation read type " + readType); + case DOCUMENT, KEY -> { + QueryOuterClass.Query query = buildSelectQuery(additionalIdFilter); + return findDocument( + queryExecutor, + query, + pagingState, + pageSize, + ReadType.DOCUMENT == readType, + objectMapper); + } + default -> { + JsonApiException failure = + new JsonApiException( + ErrorCode.UNSUPPORTED_OPERATION, + "Unsupported find operation read type " + readType); + return Uni.createFrom().failure(failure); + } } } + /** {@inheritDoc} */ @Override public ReadDocument getNewDocument() { ObjectNode rootNode = objectMapper().createObjectNode(); DocumentId documentId = null; for (DBFilterBase filter : filters) { - if (filter instanceof DBFilterBase.IDFilter) { - documentId = ((DBFilterBase.IDFilter) filter).value; + if (filter instanceof DBFilterBase.IDFilter idFilter) { + documentId = idFilter.value; rootNode.putIfAbsent(filter.getPath(), filter.asJson(objectMapper().getNodeFactory())); } else { if (filter.canAddField()) { @@ -78,20 +86,28 @@ public ReadDocument getNewDocument() { } } } - ReadDocument doc = new ReadDocument(documentId, null, rootNode); - return doc; + + return new ReadDocument(documentId, null, rootNode); } + // builds select query private QueryOuterClass.Query buildSelectQuery(DBFilterBase.IDFilter additionalIdFilter) { List conditions = new ArrayList<>(filters.size()); + + // if we have id filter overwrite ignore existing IDFilter + boolean idFilterOverwrite = additionalIdFilter != null; for (DBFilterBase filter : filters) { - if (additionalIdFilter == null - || (additionalIdFilter != null && !(filter instanceof DBFilterBase.IDFilter))) + if (!(idFilterOverwrite && filter instanceof DBFilterBase.IDFilter)) { conditions.add(filter.get()); + } } - if (additionalIdFilter != null) { + + // then add id overwrite if there + if (idFilterOverwrite) { conditions.add(additionalIdFilter.get()); } + + // create query return new QueryBuilder() .select() .column(ReadType.DOCUMENT == readType ? documentColumns : documentKeyColumns) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateCommandResolver.java b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateCommandResolver.java index 230f547d20..6bd51b66e1 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateCommandResolver.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateCommandResolver.java @@ -39,9 +39,14 @@ public Class getCommandClass() { public Operation resolveCommand(CommandContext ctx, FindOneAndUpdateCommand command) { ReadOperation readOperation = resolve(ctx, command); DocumentUpdater documentUpdater = DocumentUpdater.construct(command.updateClause()); + + // resolve options + FindOneAndUpdateCommand.Options options = command.options(); boolean returnUpdatedDocument = - command.options() != null && "after".equals(command.options().returnDocument()); + options != null && "after".equals(command.options().returnDocument()); boolean upsert = command.options() != null && command.options().upsert(); + + // return return new ReadAndUpdateOperation( ctx, readOperation, diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/configuration/ObjectMapperConfigurationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/configuration/ObjectMapperConfigurationTest.java index 0a69a47e2f..c45a37c4d9 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/configuration/ObjectMapperConfigurationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/configuration/ObjectMapperConfigurationTest.java @@ -49,8 +49,7 @@ public void happyPath() throws Exception { "user.name", "-user.age" ], - "filter": {"username": "aaron"}, - "options": {} + "filter": {"username": "aaron"} } } """; diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneAndUpdateCommandTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneAndUpdateCommandTest.java new file mode 100644 index 0000000000..43f001ffb1 --- /dev/null +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/model/command/impl/FindOneAndUpdateCommandTest.java @@ -0,0 +1,93 @@ +package io.stargate.sgv2.jsonapi.api.model.command.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import io.stargate.sgv2.common.testprofiles.NoGlobalResourcesTestProfile; +import java.util.Set; +import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@TestProfile(NoGlobalResourcesTestProfile.Impl.class) +class FindOneAndUpdateCommandTest { + + @Inject ObjectMapper objectMapper; + + @Inject Validator validator; + + @Nested + class Validation { + + @Test + public void noUpdateClause() throws Exception { + String json = + """ + { + "findOneAndUpdate": { + "filter": {"name": "Aaron"} + } + } + """; + + FindOneAndUpdateCommand command = objectMapper.readValue(json, FindOneAndUpdateCommand.class); + Set> result = validator.validate(command); + + assertThat(result) + .isNotEmpty() + .extracting(ConstraintViolation::getMessage) + .contains("must not be null"); + } + + @Test + public void invalidReturnDocumentOption() throws Exception { + String json = + """ + { + "findOneAndUpdate": { + "filter": {"name": "Aaron"}, + "update": { "$set": {"name": "Tatu"}}, + "options": { + "returnDocument": "yes" + } + } + } + """; + + FindOneAndUpdateCommand command = objectMapper.readValue(json, FindOneAndUpdateCommand.class); + Set> result = validator.validate(command); + + assertThat(result) + .isNotEmpty() + .extracting(ConstraintViolation::getMessage) + .contains("returnDocument value can only be 'before' or 'after'"); + } + + @Test + public void validReturnDocumentOption() throws Exception { + String json = + """ + { + "findOneAndUpdate": { + "filter": {"name": "Aaron"}, + "update": { "$set": {"name": "Tatu"}}, + "options": { + "returnDocument": "after" + } + } + } + """; + + FindOneAndUpdateCommand command = objectMapper.readValue(json, FindOneAndUpdateCommand.class); + Set> result = validator.validate(command); + + assertThat(result).isEmpty(); + } + } +} diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindIntegrationTest.java index eb159ad46a..bb74712536 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindIntegrationTest.java @@ -773,251 +773,4 @@ public void findByBooleanColumn() { .body("data.docs[0]", jsonEquals(expected)); } } - - @Nested - @TestMethodOrder(MethodOrderer.OrderAnnotation.class) - class FindOne { - @Test - @Order(1) - public void setUp() { - String json = - """ - { - "insertOne": { - "document": { - "_id": "doc1", - "username": "user1", - "active_user" : true - } - } - } - """; - - insert(json); - json = - """ - { - "insertOne": { - "document": { - "_id": "doc2", - "username": "user2", - "subdoc" : { - "id" : "abc" - }, - "array" : [ - "value1" - ] - } - } - } - """; - - insert(json); - - json = - """ - { - "insertOne": { - "document": { - "_id": "doc3", - "username": "user3", - "tags" : ["tag1", "tag2", "tag1234567890123456789012345", null, 1, true], - "nestedArray" : [["tag1", "tag2"], ["tag1234567890123456789012345", null]] - } - } - } - """; - - insert(json); - - json = - """ - { - "insertOne": { - "document": { - "_id": "doc4", - "indexedObject" : { "0": "value_0", "1": "value_1" } - } - } - } - """; - - insert(json); - - json = - """ - { - "insertOne": { - "document": { - "_id": "doc5", - "username": "user5", - "sub_doc" : { "a": 5, "b": { "c": "v1", "d": false } } - } - } - } - """; - - insert(json); - } - - private void insert(String json) { - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200); - } - - @Test - @Order(2) - public void findOneNoFilter() { - String json = - """ - { - "findOne": { - } - } - """; - String expected = "{\"username\": \"user1\"}"; - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200) - .body("data.count", is(1)); - } - - @Test - @Order(2) - public void findOneById() { - String json = - """ - { - "findOne": { - "filter" : {"_id" : "doc1"} - } - } - """; - String expected = "{\"_id\":\"doc1\", \"username\":\"user1\", \"active_user\":true}"; - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200) - .body("data.count", is(1)) - .body("data.docs[0]", jsonEquals(expected)); - } - - @Test - @Order(2) - public void findOneByColumn() { - String json = - """ - { - "findOne": { - "filter" : {"username" : "user1"} - } - } - """; - String expected = "{\"_id\":\"doc1\", \"username\":\"user1\", \"active_user\":true}"; - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200) - .body("data.count", is(1)) - .body("data.docs[0]", jsonEquals(expected)); - } - - @Test - @Order(2) - public void findOneWithExistsOperator() { - String json = - """ - { - "findOne": { - "filter" : {"active_user" : {"$exists" : true}} - } - } - """; - - String expected = "{\"_id\":\"doc1\", \"username\":\"user1\", \"active_user\":true}"; - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200) - .body("data.count", is(1)) - .body("data.docs[0]", jsonEquals(expected)); - } - - @Test - @Order(2) - public void findOneWithAllOperator() { - String json = - """ - { - "findOne": { - "filter" : {"tags" : {"$all" : ["tag1", "tag2"]}} - } - } - """; - String expected = - """ - {"_id": "doc3","username": "user3","tags" : ["tag1", "tag2", "tag1234567890123456789012345", null, 1, true], "nestedArray" : [["tag1", "tag2"], ["tag1234567890123456789012345", null]]} - """; - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200) - .body("data.count", is(1)) - .body("data.docs[0]", jsonEquals(expected)); - } - - @Test - @Order(2) - public void findOneWithSizeOperator() { - String json = - """ - { - "findOne": { - "filter" : {"tags" : {"$size" : 6}} - } - } - """; - String expected = - """ - {"_id": "doc3","username": "user3","tags" : ["tag1", "tag2", "tag1234567890123456789012345", null, 1, true], "nestedArray" : [["tag1", "tag2"], ["tag1234567890123456789012345", null]]} - """; - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200) - .body("data.count", is(1)) - .body("data.docs[0]", jsonEquals(expected)); - } - } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java index f741a76cc5..c730a6ed84 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneAndUpdateIntegrationTest.java @@ -3,6 +3,7 @@ import static io.restassured.RestAssured.given; import static io.stargate.sgv2.common.IntegrationTestUtils.getAuthToken; import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; @@ -24,45 +25,26 @@ class FindOneAndUpdate { @Test public void findByIdAndSet() { - String json = + String document = """ { - "insertOne": { - "document": { - "_id": "doc3", - "username": "user3", - "active_user" : true - } - } + "_id": "doc3", + "username": "user3", + "active_user" : true } """; + insertDoc(document); - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200); - - json = + String json = """ { "findOneAndUpdate": { "filter" : {"_id" : "doc3"}, - "update" : {"$set" : {"active_user": false}} + "update" : {"$set" : {"active_user": false}}, + "options": {"upsert": true} } } """; - String expected = - """ - { - "_id": "doc3", - "username": "user3", - "active_user": true - } - """; given() .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) .contentType(ContentType.JSON) @@ -71,13 +53,13 @@ public void findByIdAndSet() { .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) .then() .statusCode(200) - .body("data.docs[0]", jsonEquals(expected)) + .body("data.docs[0]", jsonEquals(document)) .body("status.matchedCount", is(1)) .body("status.modifiedCount", is(1)) .body("errors", is(nullValue())); // assert state after update - expected = + String expected = """ { "_id": "doc3", @@ -105,20 +87,16 @@ public void findByIdAndSet() { } @Test - public void findByIdReturnDocumentAfter() { + public void findByIdAndSetNotFound() { String json = """ { - "insertOne": { - "document": { - "_id": "afterDoc3", - "username": "afterUser3", - "active_user" : true - } + "findOneAndUpdate": { + "filter" : {"_id" : "doc3"}, + "update" : {"$set" : {"active_user": false}} } } """; - given() .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) .contentType(ContentType.JSON) @@ -126,9 +104,26 @@ public void findByIdReturnDocumentAfter() { .when() .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) .then() - .statusCode(200); + .statusCode(200) + .body("data.docs", is(empty())) + .body("status.matchedCount", is(0)) + .body("status.modifiedCount", is(0)) + .body("errors", is(nullValue())); + } - json = + @Test + public void findByIdReturnDocumentAfter() { + String document = + """ + { + "_id": "afterDoc3", + "username": "afterUser3", + "active_user" : true + } + """; + insertDoc(document); + + String json = """ { "findOneAndUpdate": { @@ -159,6 +154,7 @@ public void findByIdReturnDocumentAfter() { .body("status.modifiedCount", is(1)) .body("errors", is(nullValue())); + // assert state after update json = """ { @@ -234,28 +230,16 @@ public void findByIdUpsert() { @Test public void findByColumnAndSet() { - String json = + String document = """ { - "insertOne": { - "document": { - "_id": "doc4", - "username": "user4" - } - } + "_id": "doc4", + "username": "user4" } """; + insertDoc(document); - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200); - - json = + String json = """ { "findOneAndUpdate": { @@ -264,13 +248,6 @@ public void findByColumnAndSet() { } } """; - String expected = - """ - { - "_id":"doc4", - "username":"user4" - } - """; given() .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) .contentType(ContentType.JSON) @@ -279,13 +256,13 @@ public void findByColumnAndSet() { .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) .then() .statusCode(200) - .body("data.docs[0]", jsonEquals(expected)) + .body("data.docs[0]", jsonEquals(document)) .body("status.matchedCount", is(1)) .body("status.modifiedCount", is(1)) .body("errors", is(nullValue())); // assert state after update - expected = + String expected = """ { "_id":"doc4", @@ -314,29 +291,17 @@ public void findByColumnAndSet() { @Test public void findByIdAndUnset() { - String json = + String document = """ { - "insertOne": { - "document": { - "_id": "doc5", - "username": "user5", - "unset_col": "val" - } - } + "_id": "doc5", + "username": "user5", + "unset_col": "val" } """; + insertDoc(document); - given() - .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) - .contentType(ContentType.JSON) - .body(json) - .when() - .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) - .then() - .statusCode(200); - - json = + String json = """ { "findOneAndUpdate": { @@ -345,14 +310,6 @@ public void findByIdAndUnset() { } } """; - String expected = - """ - { - "_id":"doc5", - "username":"user5", - "unset_col":"val" - } - """; given() .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) .contentType(ContentType.JSON) @@ -361,12 +318,12 @@ public void findByIdAndUnset() { .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) .then() .statusCode(200) - .body("data.docs[0]", jsonEquals(expected)) + .body("data.docs[0]", jsonEquals(document)) .body("status.matchedCount", is(1)) .body("status.modifiedCount", is(1)) .body("errors", is(nullValue())); - expected = + String expected = """ { "_id":"doc5", diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java new file mode 100644 index 0000000000..2e2aab6c7c --- /dev/null +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/FindOneIntegrationTest.java @@ -0,0 +1,493 @@ +package io.stargate.sgv2.jsonapi.api.v1; + +import static io.restassured.RestAssured.given; +import static io.stargate.sgv2.common.IntegrationTestUtils.getAuthToken; +import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.restassured.http.ContentType; +import io.stargate.sgv2.api.common.config.constants.HttpConstants; +import io.stargate.sgv2.jsonapi.testresource.DseTestResource; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +@QuarkusIntegrationTest +@QuarkusTestResource(DseTestResource.class) +public class FindOneIntegrationTest extends CollectionResourceBaseIntegrationTest { + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class FindOne { + + private static final String DOC1_JSON = + """ + { + "_id": "doc1", + "username": "user1", + "active_user" : true + } + """; + private static final String DOC2_JSON = + """ + { + "_id": "doc2", + "username": "user2", + "subdoc" : { + "id" : "abc" + }, + "array" : [ + "value1" + ] + } + """; + private static final String DOC3_JSON = + """ + { + "_id": "doc3", + "username": "user3", + "tags" : ["tag1", "tag2", "tag1234567890123456789012345", null, 1, true], + "nestedArray" : [["tag1", "tag2"], ["tag1234567890123456789012345", null]] + } + """; + private static final String DOC4_JSON = + """ + { + "_id": "doc4", + "indexedObject" : { "0": "value_0", "1": "value_1" } + } + """; + private static final String DOC5_JSON = + """ + { + "_id": "doc5", + "username": "user5", + "sub_doc" : { "a": 5, "b": { "c": "v1", "d": false } } + } + """; + + @Test + @Order(1) + public void setUp() { + insertDoc(DOC1_JSON); + insertDoc(DOC2_JSON); + insertDoc(DOC3_JSON); + insertDoc(DOC4_JSON); + insertDoc(DOC5_JSON); + } + + @Test + @Order(-1) // executed before insert + public void findOneNoFilterNoDocuments() { + String json = + """ + { + "findOne": { + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(0)) + .body("data.docs", is(empty())) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneNoFilter() { + String json = + """ + { + "findOne": { + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(1)) + .body("data.docs", hasSize(1)) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneById() { + String json = + """ + { + "findOne": { + "filter" : {"_id" : "doc1"} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(1)) + .body("data.docs", hasSize(1)) + .body("data.docs[0]", jsonEquals(DOC1_JSON)) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneByIdNotFound() { + String json = + """ + { + "findOne": { + "filter" : {"_id" : "none"} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(0)) + .body("data.docs", is(empty())) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneByColumn() { + String json = + """ + { + "findOne": { + "filter" : {"username" : "user1"} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(1)) + .body("data.docs", hasSize(1)) + .body("data.docs[0]", jsonEquals(DOC1_JSON)) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneByColumnMissing() { + String json = + """ + { + "findOne": { + "filter" : {"nickname" : "user1"} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(0)) + .body("data.docs", is(empty())) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneByColumnNotMatching() { + String json = + """ + { + "findOne": { + "filter" : {"username" : "batman"} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(0)) + .body("data.docs", is(empty())) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneWithExistsOperator() { + String json = + """ + { + "findOne": { + "filter" : {"active_user" : {"$exists" : true}} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(1)) + .body("data.docs", hasSize(1)) + .body("data.docs[0]", jsonEquals(DOC1_JSON)) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneWithExistsOperatorFalse() { + String json = + """ + { + "findOne": { + "filter" : {"active_user" : {"$exists" : false}} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data", is(nullValue())) + .body("status", is(nullValue())) + .body("errors[0].message", is("$exists is supported only with true option")) + .body("errors[0].errorCode", is("UNSUPPORTED_FILTER_DATA_TYPE")); + } + + @Test + public void findOneWithExistsNotMatching() { + String json = + """ + { + "findOne": { + "filter" : {"power_rating" : {"$exists" : true}} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(0)) + .body("data.docs", is(empty())) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneWithAllOperatorMissing() { + String json = + """ + { + "findOne": { + "filter" : {"tags-and-button" : {"$all" : ["tag1", "tag2"]}} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(0)) + .body("data.docs", is(empty())) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneWithAllOperatorNotMatching() { + String json = + """ + { + "findOne": { + "filter" : {"tags" : {"$all" : ["tag1", "tag2", "tag-not-there"]}} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(0)) + .body("data.docs", is(empty())) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneWithAllOperatorNotArray() { + String json = + """ + { + "findOne": { + "filter" : {"tags" : {"$all" : 1}} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data", is(nullValue())) + .body("status", is(nullValue())) + .body( + "errors[0].message", + is("Filter type not supported, unable to resolve to a filtering strategy")) + .body("errors[0].errorCode", is("FILTER_UNRESOLVABLE")); + } + + @Test + public void findOneWithSizeOperator() { + String json = + """ + { + "findOne": { + "filter" : {"tags" : {"$size" : 6}} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(1)) + .body("data.docs", hasSize(1)) + .body("data.docs[0]", jsonEquals(DOC3_JSON)) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneWithSizeOperatorNotMatching() { + String json = + """ + { + "findOne": { + "filter" : {"tags" : {"$size" : 78}} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data.count", is(0)) + .body("data.docs", is(empty())) + .body("status", is(nullValue())) + .body("errors", is(nullValue())); + } + + @Test + public void findOneWithSizeOperatorNotNumber() { + String json = + """ + { + "findOne": { + "filter" : {"tags" : {"$size" : true}} + } + } + """; + + given() + .header(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, getAuthToken()) + .contentType(ContentType.JSON) + .body(json) + .when() + .post(CollectionResource.BASE_PATH, keyspaceId.asInternal(), collectionName) + .then() + .statusCode(200) + .body("data", is(nullValue())) + .body("status", is(nullValue())) + .body( + "errors[0].message", + is("Filter type not supported, unable to resolve to a filtering strategy")) + .body("errors[0].errorCode", is("FILTER_UNRESOLVABLE")); + } + } +} diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/model/impl/FindOperationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/model/impl/FindOperationTest.java index 4ce62e3ce6..c42b14d248 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/operation/model/impl/FindOperationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/operation/model/impl/FindOperationTest.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; +import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; import io.stargate.bridge.grpc.TypeSpecs; import io.stargate.bridge.grpc.Values; import io.stargate.bridge.proto.QueryOuterClass; @@ -13,6 +14,8 @@ import io.stargate.sgv2.common.testprofiles.NoGlobalResourcesTestProfile; import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; import io.stargate.sgv2.jsonapi.api.model.command.CommandResult; +import io.stargate.sgv2.jsonapi.exception.ErrorCode; +import io.stargate.sgv2.jsonapi.exception.JsonApiException; import io.stargate.sgv2.jsonapi.service.bridge.executor.QueryExecutor; import io.stargate.sgv2.jsonapi.service.bridge.serializer.CustomValueSerializers; import io.stargate.sgv2.jsonapi.service.operation.model.ReadOperation; @@ -34,36 +37,38 @@ public class FindOperationTest extends AbstractValidatingStargateBridgeTest { private static final String KEYSPACE_NAME = RandomStringUtils.randomAlphanumeric(16); private static final String COLLECTION_NAME = RandomStringUtils.randomAlphanumeric(16); - private CommandContext commandContext = new CommandContext(KEYSPACE_NAME, COLLECTION_NAME); + private static final CommandContext COMMAND_CONTEXT = + new CommandContext(KEYSPACE_NAME, COLLECTION_NAME); @Inject QueryExecutor queryExecutor; @Inject ObjectMapper objectMapper; @Nested - class FindOperationsTest { + class Execute { @Test public void findAll() throws Exception { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" LIMIT %s" - .formatted(KEYSPACE_NAME, COLLECTION_NAME, 2); + .formatted(KEYSPACE_NAME, COLLECTION_NAME, 20); + String doc1 = """ - { - "_id": "doc1", - "username": "user1" - } - """; + { + "_id": "doc1", + "username": "user1" + } + """; String doc2 = """ - { - "_id": "doc2", - "username": "user2" - } - """; + { + "_id": "doc2", + "username": "user2" + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery(collectionReadCql) - .withPageSize(2) + .withPageSize(20) .withColumnSpec( List.of( QueryOuterClass.ColumnSpec.newBuilder() @@ -92,17 +97,29 @@ public void findAll() throws Exception { DocumentId.fromString("doc2"))), Values.of(UUID.randomUUID()), Values.of(doc2)))); - FindOperation findOperation = - new FindOperation(commandContext, List.of(), null, 2, 2, ReadType.DOCUMENT, objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + + FindOperation operation = + new FindOperation( + COMMAND_CONTEXT, List.of(), null, 20, 20, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(2); - }); + assertThat(result.data().docs()) + .hasSize(2) + .containsOnly(objectMapper.readTree(doc1), objectMapper.readTree(doc2)); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } @Test @@ -112,11 +129,11 @@ public void findWithId() throws Exception { .formatted(KEYSPACE_NAME, COLLECTION_NAME); String doc1 = """ - { - "_id": "doc1", - "username": "user1" - } - """; + { + "_id": "doc1", + "username": "user1" + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery( collectionReadCql, @@ -145,33 +162,38 @@ public void findWithId() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); - FindOperation findOperation = + + DBFilterBase.IDFilter filter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1")); + FindOperation operation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.IDFilter( - DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1"))), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(1); - }); + assertThat(result.data().docs()).hasSize(1).containsOnly(objectMapper.readTree(doc1)); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } @Test - public void findWithIdNoData() throws Exception { + public void findWithIdNoData() { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE key = ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery( collectionReadCql, @@ -193,26 +215,30 @@ public void findWithIdNoData() throws Exception { .setType(TypeSpecs.VARCHAR) .build())) .returning(List.of()); - FindOperation findOperation = + + DBFilterBase.IDFilter filter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1")); + FindOperation operation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.IDFilter( - DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1"))), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(0); - }); + assertThat(result.data().docs()).isEmpty(); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } @Test @@ -220,13 +246,14 @@ public void findWithDynamic() throws Exception { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE array_contains CONTAINS ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1" - } - """; + { + "_id": "doc1", + "username": "user1" + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery( collectionReadCql, @@ -254,26 +281,29 @@ public void findWithDynamic() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); - FindOperation findOperation = + + DBFilterBase.TextFilter filter = + new DBFilterBase.TextFilter("username", DBFilterBase.MapFilterBase.Operator.EQ, "user1"); + FindOperation operation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.TextFilter( - "username", DBFilterBase.MapFilterBase.Operator.EQ, "user1")), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(1); - }); + assertThat(result.data().docs()).hasSize(1).containsOnly(objectMapper.readTree(doc1)); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } @Test @@ -281,14 +311,15 @@ public void findWithBooleanFilter() throws Exception { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE array_contains CONTAINS ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1", - "registration_active" : true - } - """; + { + "_id": "doc1", + "username": "user1", + "registration_active" : true + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery( collectionReadCql, @@ -316,26 +347,30 @@ public void findWithBooleanFilter() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); - FindOperation findOperation = + + DBFilterBase.BoolFilter filter = + new DBFilterBase.BoolFilter( + "registration_active", DBFilterBase.MapFilterBase.Operator.EQ, true); + FindOperation operation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.BoolFilter( - "registration_active", DBFilterBase.MapFilterBase.Operator.EQ, true)), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(1); - }); + assertThat(result.data().docs()).hasSize(1).containsOnly(objectMapper.readTree(doc1)); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } @Test @@ -343,14 +378,15 @@ public void findWithExistsFilter() throws Exception { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE exist_keys CONTAINS ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1", - "registration_active" : true - } - """; + { + "_id": "doc1", + "username": "user1", + "registration_active" : true + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery(collectionReadCql, Values.of("registration_active")) .withPageSize(1) @@ -376,24 +412,28 @@ public void findWithExistsFilter() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); - FindOperation findOperation = + + DBFilterBase.ExistsFilter filter = new DBFilterBase.ExistsFilter("registration_active", true); + FindOperation operation = new FindOperation( - commandContext, - List.of(new DBFilterBase.ExistsFilter("registration_active", true)), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(1); - }); + assertThat(result.data().docs()).hasSize(1).containsOnly(objectMapper.readTree(doc1)); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } @Test @@ -401,15 +441,16 @@ public void findWithAllFilter() throws Exception { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE array_contains CONTAINS ? AND array_contains CONTAINS ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1", - "registration_active" : true, - "tags": ["tag1", "tag2"] - } - """; + { + "_id": "doc1", + "username": "user1", + "registration_active" : true, + "tags": ["tag1", "tag2"] + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery(collectionReadCql, Values.of("tags Stag1"), Values.of("tags Stag2")) .withPageSize(1) @@ -435,26 +476,30 @@ public void findWithAllFilter() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); - FindOperation findOperation = - new FindOperation( - commandContext, - List.of( - new DBFilterBase.AllFilter(new DocValueHasher(), "tags", "tag1"), - new DBFilterBase.AllFilter(new DocValueHasher(), "tags", "tag2")), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + + List filters = + List.of( + new DBFilterBase.AllFilter(new DocValueHasher(), "tags", "tag1"), + new DBFilterBase.AllFilter(new DocValueHasher(), "tags", "tag2")); + FindOperation operation = + new FindOperation(COMMAND_CONTEXT, filters, null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(1); - }); + assertThat(result.data().docs()).hasSize(1).containsOnly(objectMapper.readTree(doc1)); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } @Test @@ -462,16 +507,16 @@ public void findWithSizeFilter() throws Exception { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE array_size[?] = ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1", - "registration_active" : true, - "tags" : ["tag1","tag2"] - } - """; - + { + "_id": "doc1", + "username": "user1", + "registration_active" : true, + "tags" : ["tag1","tag2"] + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery(collectionReadCql, Values.of("tags"), Values.of(2)) .withPageSize(1) @@ -497,24 +542,28 @@ public void findWithSizeFilter() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); - FindOperation findOperation = + + DBFilterBase.SizeFilter filter = new DBFilterBase.SizeFilter("tags", 2); + FindOperation operation = new FindOperation( - commandContext, - List.of(new DBFilterBase.SizeFilter("tags", 2)), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(1); - }); + assertThat(result.data().docs()).hasSize(1).containsOnly(objectMapper.readTree(doc1)); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } @Test @@ -522,15 +571,16 @@ public void findWithArrayEqualFilter() throws Exception { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE array_equals[?] = ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1", - "registration_active" : true, - "tags" : ["tag1","tag2"] - } - """; + { + "_id": "doc1", + "username": "user1", + "registration_active" : true, + "tags" : ["tag1","tag2"] + } + """; final String hash = new DocValueHasher().getHash(List.of("tag1", "tag2")).hash(); ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery(collectionReadCql, Values.of("tags"), Values.of(hash)) @@ -557,26 +607,29 @@ public void findWithArrayEqualFilter() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); - FindOperation findOperation = + + DBFilterBase.ArrayEqualsFilter filter = + new DBFilterBase.ArrayEqualsFilter(new DocValueHasher(), "tags", List.of("tag1", "tag2")); + FindOperation operation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.ArrayEqualsFilter( - new DocValueHasher(), "tags", List.of("tag1", "tag2"))), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(1); - }); + assertThat(result.data().docs()).hasSize(1).containsOnly(objectMapper.readTree(doc1)); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } @Test @@ -584,15 +637,16 @@ public void findWithSubDocEqualFilter() throws Exception { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE sub_doc_equals[?] = ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1", - "registration_active" : true, - "sub_doc" : {"col":"val"} - } - """; + { + "_id": "doc1", + "username": "user1", + "registration_active" : true, + "sub_doc" : {"col":"val"} + } + """; final String hash = new DocValueHasher().getHash(Map.of("col", "val")).hash(); ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery(collectionReadCql, Values.of("sub_doc"), Values.of(hash)) @@ -619,44 +673,132 @@ public void findWithSubDocEqualFilter() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); - FindOperation findOperation = + + DBFilterBase.SubDocEqualsFilter filter = + new DBFilterBase.SubDocEqualsFilter( + new DocValueHasher(), "sub_doc", Map.of("col", "val")); + FindOperation operation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.SubDocEqualsFilter( - new DocValueHasher(), "sub_doc", Map.of("col", "val"))), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Supplier execute = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(1); - }); + assertThat(result.data().docs()).hasSize(1).containsOnly(objectMapper.readTree(doc1)); + assertThat(result.status()).isNullOrEmpty(); + assertThat(result.errors()).isNullOrEmpty(); } + ///////////////////// + /// FAILURES /// + ///////////////////// + @Test - public void findWithNoResult() throws Exception { + public void failurePropagated() { String collectionReadCql = - "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE array_contains CONTAINS ? LIMIT 1" + "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE key = ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + + RuntimeException exception = new RuntimeException("Ivan breaks tests."); + ValidatingStargateBridge.QueryAssert candidatesAssert = + withQuery( + collectionReadCql, + Values.of( + CustomValueSerializers.getDocumentIdValue(DocumentId.fromString("doc1")))) + .withPageSize(1) + .withColumnSpec( + List.of( + QueryOuterClass.ColumnSpec.newBuilder() + .setName("key") + .setType(TypeSpecs.tuple(TypeSpecs.TINYINT, TypeSpecs.VARCHAR)) + .build(), + QueryOuterClass.ColumnSpec.newBuilder() + .setName("tx_id") + .setType(TypeSpecs.UUID) + .build(), + QueryOuterClass.ColumnSpec.newBuilder() + .setName("doc_json") + .setType(TypeSpecs.VARCHAR) + .build())) + .returningFailure(exception); + + DBFilterBase.IDFilter filter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1")); + FindOperation operation = + new FindOperation( + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + Throwable failure = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitFailure() + .getFailure(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result + assertThat(failure).isEqualTo(exception); + } + + @Test + public void countNotSupported() { + DBFilterBase.IDFilter filter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1")); + FindOperation operation = + new FindOperation( + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.COUNT, objectMapper); + + Throwable failure = + operation + .execute(queryExecutor) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitFailure() + .getFailure(); + + assertThat(failure) + .isInstanceOfSatisfying( + JsonApiException.class, + e -> assertThat(e.getErrorCode()).isEqualTo(ErrorCode.UNSUPPORTED_OPERATION)); + } + } + + @Nested + class GetDocuments { + + @Test + public void findWithId() { + String collectionReadCql = + "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE key = ? LIMIT 1" + .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1" - } - """; + { + "_id": "doc1", + "username": "user1" + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery( collectionReadCql, - Values.of("username " + new DocValueHasher().getHash("user1").hash())) + Values.of( + CustomValueSerializers.getDocumentIdValue(DocumentId.fromString("doc1")))) .withPageSize(1) .withColumnSpec( List.of( @@ -672,41 +814,51 @@ public void findWithNoResult() throws Exception { .setName("doc_json") .setType(TypeSpecs.VARCHAR) .build())) - .returning(List.of()); + .returning( + List.of( + List.of( + Values.of( + CustomValueSerializers.getDocumentIdValue( + DocumentId.fromString("doc1"))), + Values.of(UUID.randomUUID()), + Values.of(doc1)))); + + DBFilterBase.IDFilter filter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1")); FindOperation findOperation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.TextFilter( - "username", DBFilterBase.MapFilterBase.Operator.EQ, "user1")), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final Supplier execute = - findOperation.execute(queryExecutor).subscribeAsCompletionStage().get(); - CommandResult result = execute.get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.data()).isNotNull(); - assertThat(result.data().docs()).hasSize(0); - }); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + ReadOperation.FindResponse result = + findOperation + .getDocuments(queryExecutor, null, null) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result + assertThat(result.docs()).isNotNull(); + assertThat(result.docs()).hasSize(1); } @Test - public void findWithIdWithIdRetry() throws Exception { + public void findWithIdWithIdRetry() { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE key = ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1" - } - """; + { + "_id": "doc1", + "username": "user1" + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery( collectionReadCql, @@ -735,46 +887,43 @@ public void findWithIdWithIdRetry() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); + + DBFilterBase.IDFilter filter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1")); FindOperation findOperation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.IDFilter( - DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1"))), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final ReadOperation.FindResponse result = + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + ReadOperation.FindResponse result = findOperation - .getDocuments( - queryExecutor, - null, - new DBFilterBase.IDFilter( - DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1"))) - .subscribeAsCompletionStage() - .get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.docs()).isNotNull(); - assertThat(result.docs()).hasSize(1); - }); + .getDocuments(queryExecutor, null, filter) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result + assertThat(result.docs()).isNotNull(); + assertThat(result.docs()).hasSize(1); } @Test - public void findWithDynamicGetDocument() throws Exception { + public void findWithDynamic() { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE array_contains CONTAINS ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1" - } - """; + { + "_id": "doc1", + "username": "user1" + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery( collectionReadCql, @@ -802,39 +951,42 @@ public void findWithDynamicGetDocument() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); + + DBFilterBase.TextFilter filter = + new DBFilterBase.TextFilter("username", DBFilterBase.MapFilterBase.Operator.EQ, "user1"); FindOperation findOperation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.TextFilter( - "username", DBFilterBase.MapFilterBase.Operator.EQ, "user1")), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final ReadOperation.FindResponse result = - findOperation.getDocuments(queryExecutor, null, null).subscribeAsCompletionStage().get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.docs()).isNotNull(); - assertThat(result.docs()).hasSize(1); - }); + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + ReadOperation.FindResponse result = + findOperation + .getDocuments(queryExecutor, null, null) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result + assertThat(result.docs()).isNotNull(); + assertThat(result.docs()).hasSize(1); } @Test - public void findWithDynamicWithIdRetry() throws Exception { + public void findWithDynamicWithIdRetry() { String collectionReadCql = "SELECT key, tx_id, doc_json FROM \"%s\".\"%s\" WHERE array_contains CONTAINS ? AND key = ? LIMIT 1" .formatted(KEYSPACE_NAME, COLLECTION_NAME); + String doc1 = """ - { - "_id": "doc1", - "username": "user1" - } - """; + { + "_id": "doc1", + "username": "user1" + } + """; ValidatingStargateBridge.QueryAssert candidatesAssert = withQuery( collectionReadCql, @@ -864,32 +1016,30 @@ public void findWithDynamicWithIdRetry() throws Exception { DocumentId.fromString("doc1"))), Values.of(UUID.randomUUID()), Values.of(doc1)))); + + DBFilterBase.TextFilter filter = + new DBFilterBase.TextFilter("username", DBFilterBase.MapFilterBase.Operator.EQ, "user1"); FindOperation findOperation = new FindOperation( - commandContext, - List.of( - new DBFilterBase.TextFilter( - "username", DBFilterBase.MapFilterBase.Operator.EQ, "user1")), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - final ReadOperation.FindResponse result = + COMMAND_CONTEXT, List.of(filter), null, 1, 1, ReadType.DOCUMENT, objectMapper); + + DBFilterBase.IDFilter idFilter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1")); + ReadOperation.FindResponse result = findOperation - .getDocuments( - queryExecutor, - null, - new DBFilterBase.IDFilter( - DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("doc1"))) - .subscribeAsCompletionStage() - .get(); - assertThat(result) - .satisfies( - commandResult -> { - assertThat(result.docs()).isNotNull(); - assertThat(result.docs()).hasSize(1); - }); + .getDocuments(queryExecutor, null, idFilter) + .subscribe() + .withSubscriber(UniAssertSubscriber.create()) + .awaitItem() + .getItem(); + + // assert query execution + candidatesAssert.assertExecuteCount().isOne(); + + // then result + assertThat(result.docs()).isNotNull(); + assertThat(result.docs()).hasSize(1); } } } diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateCommandResolverTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateCommandResolverTest.java new file mode 100644 index 0000000000..6c6cbdaa54 --- /dev/null +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateCommandResolverTest.java @@ -0,0 +1,213 @@ +package io.stargate.sgv2.jsonapi.service.resolver.model.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.test.Mock; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import io.stargate.sgv2.common.testprofiles.NoGlobalResourcesTestProfile; +import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; +import io.stargate.sgv2.jsonapi.api.model.command.clause.update.UpdateClause; +import io.stargate.sgv2.jsonapi.api.model.command.clause.update.UpdateOperator; +import io.stargate.sgv2.jsonapi.api.model.command.impl.FindOneAndUpdateCommand; +import io.stargate.sgv2.jsonapi.service.bridge.config.DocumentConfig; +import io.stargate.sgv2.jsonapi.service.operation.model.Operation; +import io.stargate.sgv2.jsonapi.service.operation.model.ReadType; +import io.stargate.sgv2.jsonapi.service.operation.model.impl.DBFilterBase; +import io.stargate.sgv2.jsonapi.service.operation.model.impl.FindOperation; +import io.stargate.sgv2.jsonapi.service.operation.model.impl.ReadAndUpdateOperation; +import io.stargate.sgv2.jsonapi.service.shredding.Shredder; +import io.stargate.sgv2.jsonapi.service.shredding.model.DocumentId; +import io.stargate.sgv2.jsonapi.service.testutil.DocumentUpdaterUtils; +import io.stargate.sgv2.jsonapi.service.updater.DocumentUpdater; +import javax.inject.Inject; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@QuarkusTest +@TestProfile(NoGlobalResourcesTestProfile.Impl.class) +public class FindOneAndUpdateCommandResolverTest { + @Inject ObjectMapper objectMapper; + @Inject DocumentConfig documentConfig; + @Inject Shredder shredder; + @Inject FindOneAndUpdateCommandResolver resolver; + + @Nested + class Resolve { + + @Mock CommandContext commandContext; + + @Test + public void idFilterCondition() throws Exception { + String json = + """ + { + "findOneAndUpdate": { + "filter" : {"_id" : "id"}, + "update" : {"$set" : {"location" : "New York"}} + } + } + """; + + FindOneAndUpdateCommand command = objectMapper.readValue(json, FindOneAndUpdateCommand.class); + Operation operation = resolver.resolveCommand(commandContext, command); + + assertThat(operation) + .isInstanceOfSatisfying( + ReadAndUpdateOperation.class, + op -> { + assertThat(op.commandContext()).isEqualTo(commandContext); + assertThat(op.returnDocumentInResponse()).isTrue(); + assertThat(op.returnUpdatedDocument()).isFalse(); + assertThat(op.upsert()).isFalse(); + assertThat(op.shredder()).isEqualTo(shredder); + assertThat(op.updateLimit()).isEqualTo(1); + assertThat(op.retryLimit()).isEqualTo(documentConfig.lwt().retries()); + assertThat(op.documentUpdater()) + .isInstanceOfSatisfying( + DocumentUpdater.class, + updater -> { + UpdateClause updateClause = + DocumentUpdaterUtils.updateClause( + UpdateOperator.SET, + objectMapper.createObjectNode().put("location", "New York")); + + assertThat(updater.updateOperations()) + .isEqualTo(updateClause.buildOperations()); + }); + assertThat(op.readOperation()) + .isInstanceOfSatisfying( + FindOperation.class, + find -> { + DBFilterBase.IDFilter filter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("id")); + + assertThat(find.objectMapper()).isEqualTo(objectMapper); + assertThat(find.commandContext()).isEqualTo(commandContext); + assertThat(find.pageSize()).isEqualTo(1); + assertThat(find.limit()).isEqualTo(1); + assertThat(find.pagingState()).isNull(); + assertThat(find.readType()).isEqualTo(ReadType.DOCUMENT); + assertThat(find.filters()).singleElement().isEqualTo(filter); + }); + }); + } + + @Test + public void idFilterConditionWithOptions() throws Exception { + String json = + """ + { + "findOneAndUpdate": { + "filter" : {"_id" : "id"}, + "update" : {"$set" : {"location" : "New York"}}, + "options" : {"returnDocument" : "after", "upsert": true } + } + } + """; + + FindOneAndUpdateCommand command = objectMapper.readValue(json, FindOneAndUpdateCommand.class); + Operation operation = resolver.resolveCommand(commandContext, command); + + assertThat(operation) + .isInstanceOfSatisfying( + ReadAndUpdateOperation.class, + op -> { + assertThat(op.commandContext()).isEqualTo(commandContext); + assertThat(op.returnDocumentInResponse()).isTrue(); + assertThat(op.returnUpdatedDocument()).isTrue(); + assertThat(op.upsert()).isTrue(); + assertThat(op.shredder()).isEqualTo(shredder); + assertThat(op.updateLimit()).isEqualTo(1); + assertThat(op.retryLimit()).isEqualTo(documentConfig.lwt().retries()); + assertThat(op.documentUpdater()) + .isInstanceOfSatisfying( + DocumentUpdater.class, + updater -> { + UpdateClause updateClause = + DocumentUpdaterUtils.updateClause( + UpdateOperator.SET, + objectMapper.createObjectNode().put("location", "New York")); + + assertThat(updater.updateOperations()) + .isEqualTo(updateClause.buildOperations()); + }); + assertThat(op.readOperation()) + .isInstanceOfSatisfying( + FindOperation.class, + find -> { + DBFilterBase.IDFilter filter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("id")); + + assertThat(find.objectMapper()).isEqualTo(objectMapper); + assertThat(find.commandContext()).isEqualTo(commandContext); + assertThat(find.pageSize()).isEqualTo(1); + assertThat(find.limit()).isEqualTo(1); + assertThat(find.pagingState()).isNull(); + assertThat(find.readType()).isEqualTo(ReadType.DOCUMENT); + assertThat(find.filters()).singleElement().isEqualTo(filter); + }); + }); + } + + @Test + public void dynamicFilterCondition() throws Exception { + String json = + """ + { + "findOneAndUpdate": { + "filter" : {"col" : "val"}, + "update" : {"$set" : {"location" : "New York"}} + } + } + """; + + FindOneAndUpdateCommand command = objectMapper.readValue(json, FindOneAndUpdateCommand.class); + Operation operation = resolver.resolveCommand(commandContext, command); + + assertThat(operation) + .isInstanceOfSatisfying( + ReadAndUpdateOperation.class, + op -> { + assertThat(op.commandContext()).isEqualTo(commandContext); + assertThat(op.returnDocumentInResponse()).isTrue(); + assertThat(op.returnUpdatedDocument()).isFalse(); + assertThat(op.upsert()).isFalse(); + assertThat(op.shredder()).isEqualTo(shredder); + assertThat(op.updateLimit()).isEqualTo(1); + assertThat(op.retryLimit()).isEqualTo(documentConfig.lwt().retries()); + assertThat(op.documentUpdater()) + .isInstanceOfSatisfying( + DocumentUpdater.class, + updater -> { + UpdateClause updateClause = + DocumentUpdaterUtils.updateClause( + UpdateOperator.SET, + objectMapper.createObjectNode().put("location", "New York")); + + assertThat(updater.updateOperations()) + .isEqualTo(updateClause.buildOperations()); + }); + assertThat(op.readOperation()) + .isInstanceOfSatisfying( + FindOperation.class, + find -> { + DBFilterBase.TextFilter filter = + new DBFilterBase.TextFilter( + "col", DBFilterBase.MapFilterBase.Operator.EQ, "val"); + + assertThat(find.objectMapper()).isEqualTo(objectMapper); + assertThat(find.commandContext()).isEqualTo(commandContext); + assertThat(find.pageSize()).isEqualTo(1); + assertThat(find.limit()).isEqualTo(1); + assertThat(find.pagingState()).isNull(); + assertThat(find.readType()).isEqualTo(ReadType.DOCUMENT); + assertThat(find.filters()).singleElement().isEqualTo(filter); + }); + }); + } + } +} diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateResolverTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateResolverTest.java deleted file mode 100644 index 7a08720926..0000000000 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneAndUpdateResolverTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package io.stargate.sgv2.jsonapi.service.resolver.model.impl; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.fasterxml.jackson.databind.ObjectMapper; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.TestProfile; -import io.stargate.sgv2.common.testprofiles.NoGlobalResourcesTestProfile; -import io.stargate.sgv2.jsonapi.api.model.command.CommandContext; -import io.stargate.sgv2.jsonapi.api.model.command.clause.update.UpdateOperator; -import io.stargate.sgv2.jsonapi.api.model.command.impl.FindOneAndUpdateCommand; -import io.stargate.sgv2.jsonapi.service.operation.model.Operation; -import io.stargate.sgv2.jsonapi.service.operation.model.ReadOperation; -import io.stargate.sgv2.jsonapi.service.operation.model.ReadType; -import io.stargate.sgv2.jsonapi.service.operation.model.impl.DBFilterBase; -import io.stargate.sgv2.jsonapi.service.operation.model.impl.FindOperation; -import io.stargate.sgv2.jsonapi.service.operation.model.impl.ReadAndUpdateOperation; -import io.stargate.sgv2.jsonapi.service.shredding.Shredder; -import io.stargate.sgv2.jsonapi.service.shredding.model.DocumentId; -import io.stargate.sgv2.jsonapi.service.testutil.DocumentUpdaterUtils; -import io.stargate.sgv2.jsonapi.service.updater.DocumentUpdater; -import java.util.List; -import javax.inject.Inject; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -@QuarkusTest -@TestProfile(NoGlobalResourcesTestProfile.Impl.class) -public class FindOneAndUpdateResolverTest { - @Inject ObjectMapper objectMapper; - @Inject Shredder shredder; - @Inject FindOneAndUpdateCommandResolver findOneAndUpdateCommandResolver; - - @Nested - class FindAndUpdateCommandResolveCommand { - - @Test - public void idFilterConditionBsonType() throws Exception { - String json = - """ - { - "findOneAndUpdate": { - "filter" : {"_id" : "id"}, - "update" : {"$set" : {"location" : "New York"}} - } - } - """; - - FindOneAndUpdateCommand findOneAndUpdateCommand = - objectMapper.readValue(json, FindOneAndUpdateCommand.class); - final CommandContext commandContext = new CommandContext("namespace", "collection"); - final Operation operation = - findOneAndUpdateCommandResolver.resolveCommand(commandContext, findOneAndUpdateCommand); - ReadOperation readOperation = - new FindOperation( - commandContext, - List.of( - new DBFilterBase.IDFilter( - DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("id"))), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - - DocumentUpdater documentUpdater = - DocumentUpdater.construct( - DocumentUpdaterUtils.updateClause( - UpdateOperator.SET, - objectMapper.getNodeFactory().objectNode().put("location", "New York"))); - ReadAndUpdateOperation expected = - new ReadAndUpdateOperation( - commandContext, readOperation, documentUpdater, true, false, false, shredder, 1, 3); - assertThat(operation) - .isInstanceOf(ReadAndUpdateOperation.class) - .satisfies( - op -> { - assertThat(op).isEqualTo(expected); - }); - } - - @Test - public void idFilterConditionWithOptions() throws Exception { - String json = - """ - { - "findOneAndUpdate": { - "filter" : {"_id" : "id"}, - "update" : {"$set" : {"location" : "New York"}}, - "options" : {"returnDocument" : "after", "upsert": true } - } - } - """; - - FindOneAndUpdateCommand findOneAndUpdateCommand = - objectMapper.readValue(json, FindOneAndUpdateCommand.class); - final CommandContext commandContext = new CommandContext("namespace", "collection"); - final Operation operation = - findOneAndUpdateCommandResolver.resolveCommand(commandContext, findOneAndUpdateCommand); - ReadOperation readOperation = - new FindOperation( - commandContext, - List.of( - new DBFilterBase.IDFilter( - DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("id"))), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - - DocumentUpdater documentUpdater = - DocumentUpdater.construct( - DocumentUpdaterUtils.updateClause( - UpdateOperator.SET, - objectMapper.getNodeFactory().objectNode().put("location", "New York"))); - ReadAndUpdateOperation expected = - new ReadAndUpdateOperation( - commandContext, readOperation, documentUpdater, true, true, true, shredder, 1, 3); - assertThat(operation) - .isInstanceOf(ReadAndUpdateOperation.class) - .satisfies( - op -> { - assertThat(op).isEqualTo(expected); - }); - } - - @Test - public void dynamicFilterCondition() throws Exception { - String json = - """ - { - "findOneAndUpdate": { - "filter" : {"col" : "val"}, - "update" : {"$set" : {"location" : "New York"}} - } - } - """; - - FindOneAndUpdateCommand findOneAndUpdateCommand = - objectMapper.readValue(json, FindOneAndUpdateCommand.class); - final CommandContext commandContext = new CommandContext("namespace", "collection"); - final Operation operation = - findOneAndUpdateCommandResolver.resolveCommand(commandContext, findOneAndUpdateCommand); - ReadOperation readOperation = - new FindOperation( - commandContext, - List.of( - new DBFilterBase.TextFilter( - "col", DBFilterBase.MapFilterBase.Operator.EQ, "val")), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); - - DocumentUpdater documentUpdater = - DocumentUpdater.construct( - DocumentUpdaterUtils.updateClause( - UpdateOperator.SET, - objectMapper.getNodeFactory().objectNode().put("location", "New York"))); - ReadAndUpdateOperation expected = - new ReadAndUpdateOperation( - commandContext, readOperation, documentUpdater, true, false, false, shredder, 1, 3); - assertThat(operation) - .isInstanceOf(ReadAndUpdateOperation.class) - .satisfies( - op -> { - assertThat(op).isEqualTo(expected); - }); - } - } -} diff --git a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneCommandResolverTest.java b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneCommandResolverTest.java index c4c799ca50..96ec9bf646 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneCommandResolverTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/service/resolver/model/impl/FindOneCommandResolverTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.test.Mock; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; import io.stargate.sgv2.common.testprofiles.NoGlobalResourcesTestProfile; @@ -13,7 +14,6 @@ import io.stargate.sgv2.jsonapi.service.operation.model.impl.DBFilterBase; import io.stargate.sgv2.jsonapi.service.operation.model.impl.FindOperation; import io.stargate.sgv2.jsonapi.service.shredding.model.DocumentId; -import java.util.List; import javax.inject.Inject; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -22,46 +22,46 @@ @TestProfile(NoGlobalResourcesTestProfile.Impl.class) public class FindOneCommandResolverTest { @Inject ObjectMapper objectMapper; - @Inject FindOneCommandResolver findOneCommandResolver; + @Inject FindOneCommandResolver resolver; @Nested - class FindOneCommandResolveCommand { + class Resolve { + + @Mock CommandContext commandContext; @Test public void idFilterCondition() throws Exception { String json = """ - { - "findOne": { - "sort": [ - "user.name", - "-user.age" - ], - "filter" : {"_id" : "id"} - } - } - """; - - FindOneCommand findOneCommand = objectMapper.readValue(json, FindOneCommand.class); - final CommandContext commandContext = new CommandContext("namespace", "collection"); - final Operation operation = - findOneCommandResolver.resolveCommand(commandContext, findOneCommand); - FindOperation expected = - new FindOperation( - commandContext, - List.of( - new DBFilterBase.IDFilter( - DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("id"))), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); + { + "findOne": { + "sort": [ + "user.name", + "-user.age" + ], + "filter" : {"_id" : "id"} + } + } + """; + + FindOneCommand command = objectMapper.readValue(json, FindOneCommand.class); + Operation operation = resolver.resolveCommand(commandContext, command); + assertThat(operation) - .isInstanceOf(FindOperation.class) - .satisfies( + .isInstanceOfSatisfying( + FindOperation.class, op -> { - assertThat(op).isEqualTo(expected); + DBFilterBase.IDFilter filter = + new DBFilterBase.IDFilter( + DBFilterBase.IDFilter.Operator.EQ, DocumentId.fromString("id")); + + assertThat(op.objectMapper()).isEqualTo(objectMapper); + assertThat(op.commandContext()).isEqualTo(commandContext); + assertThat(op.limit()).isEqualTo(1); + assertThat(op.pageSize()).isEqualTo(1); + assertThat(op.pagingState()).isNull(); + assertThat(op.readType()).isEqualTo(ReadType.DOCUMENT); + assertThat(op.filters()).singleElement().isEqualTo(filter); }); } @@ -79,17 +79,20 @@ public void noFilterCondition() throws Exception { } """; - FindOneCommand findOneCommand = objectMapper.readValue(json, FindOneCommand.class); - final CommandContext commandContext = new CommandContext("namespace", "collection"); - final Operation operation = - findOneCommandResolver.resolveCommand(commandContext, findOneCommand); - FindOperation expected = - new FindOperation(commandContext, List.of(), null, 1, 1, ReadType.DOCUMENT, objectMapper); + FindOneCommand command = objectMapper.readValue(json, FindOneCommand.class); + Operation operation = resolver.resolveCommand(commandContext, command); + assertThat(operation) - .isInstanceOf(FindOperation.class) - .satisfies( + .isInstanceOfSatisfying( + FindOperation.class, op -> { - assertThat(op).isEqualTo(expected); + assertThat(op.objectMapper()).isEqualTo(objectMapper); + assertThat(op.commandContext()).isEqualTo(commandContext); + assertThat(op.limit()).isEqualTo(1); + assertThat(op.pageSize()).isEqualTo(1); + assertThat(op.pagingState()).isNull(); + assertThat(op.readType()).isEqualTo(ReadType.DOCUMENT); + assertThat(op.filters()).isEmpty(); }); } @@ -108,26 +111,24 @@ public void dynamicFilterCondition() throws Exception { } """; - FindOneCommand findOneCommand = objectMapper.readValue(json, FindOneCommand.class); - final CommandContext commandContext = new CommandContext("namespace", "collection"); - final Operation operation = - findOneCommandResolver.resolveCommand(commandContext, findOneCommand); - FindOperation expected = - new FindOperation( - commandContext, - List.of( - new DBFilterBase.TextFilter( - "col", DBFilterBase.MapFilterBase.Operator.EQ, "val")), - null, - 1, - 1, - ReadType.DOCUMENT, - objectMapper); + FindOneCommand command = objectMapper.readValue(json, FindOneCommand.class); + Operation operation = resolver.resolveCommand(commandContext, command); + assertThat(operation) - .isInstanceOf(FindOperation.class) - .satisfies( + .isInstanceOfSatisfying( + FindOperation.class, op -> { - assertThat(op).isEqualTo(expected); + DBFilterBase.TextFilter filter = + new DBFilterBase.TextFilter( + "col", DBFilterBase.MapFilterBase.Operator.EQ, "val"); + + assertThat(op.objectMapper()).isEqualTo(objectMapper); + assertThat(op.commandContext()).isEqualTo(commandContext); + assertThat(op.limit()).isEqualTo(1); + assertThat(op.pageSize()).isEqualTo(1); + assertThat(op.pagingState()).isNull(); + assertThat(op.readType()).isEqualTo(ReadType.DOCUMENT); + assertThat(op.filters()).singleElement().isEqualTo(filter); }); } }