Skip to content

Commit

Permalink
Convert "CountOperationTest" unit test to use native CQL driver (#672)
Browse files Browse the repository at this point in the history
  • Loading branch information
tatu-at-datastax authored Nov 22, 2023
1 parent a4d5254 commit b643007
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,63 +1,64 @@
package io.stargate.sgv2.jsonapi.service.operation.model.impl;

import static org.assertj.core.api.Assertions.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.protocol.internal.ProtocolConstants;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import io.smallrye.mutiny.Uni;
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;
import io.stargate.sgv2.common.bridge.AbstractValidatingStargateBridgeTest;
import io.stargate.sgv2.common.bridge.ValidatingStargateBridge;
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.api.model.command.CommandStatus;
import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.ComparisonExpression;
import io.stargate.sgv2.jsonapi.api.model.command.clause.filter.LogicalExpression;
import io.stargate.sgv2.jsonapi.service.cqldriver.executor.QueryExecutor;
import io.stargate.sgv2.jsonapi.service.operation.model.CountOperation;
import io.stargate.sgv2.jsonapi.service.shredding.model.DocValueHasher;
import jakarta.inject.Inject;
import io.stargate.sgv2.jsonapi.service.testutil.MockAsyncResultSet;
import io.stargate.sgv2.jsonapi.service.testutil.MockRow;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

@QuarkusTest
@Disabled
@TestProfile(NoGlobalResourcesTestProfile.Impl.class)
public class CountOperationTest extends AbstractValidatingStargateBridgeTest {
private static final String KEYSPACE_NAME = RandomStringUtils.randomAlphanumeric(16);
private static final String COLLECTION_NAME = RandomStringUtils.randomAlphanumeric(16);
private static final CommandContext CONTEXT = new CommandContext(KEYSPACE_NAME, COLLECTION_NAME);

@Inject QueryExecutor queryExecutor;

public class CountOperationTest extends OperationTestBase {
@Nested
class Execute {
private final ColumnDefinitions COUNT_RESULT_COLUMNS =
buildColumnDefs(Arrays.asList(TestColumn.of("count", ProtocolConstants.DataType.BIGINT)));

@Test
public void countWithNoFilter() {
String collectionReadCql =
"SELECT COUNT(1) AS count FROM \"%s\".\"%s\"".formatted(KEYSPACE_NAME, COLLECTION_NAME);
SimpleStatement stmt = SimpleStatement.newInstance(collectionReadCql);
List<Row> rows =
Arrays.asList(new MockRow(COUNT_RESULT_COLUMNS, 0, Arrays.asList(byteBufferFrom(5L))));
AsyncResultSet mockResults = new MockAsyncResultSet(COUNT_RESULT_COLUMNS, rows, null);
final AtomicInteger callCount = new AtomicInteger();
QueryExecutor queryExecutor = mock(QueryExecutor.class);
when(queryExecutor.executeRead(eq(stmt), any(), anyInt()))
.then(
invocation -> {
callCount.incrementAndGet();
return Uni.createFrom().item(mockResults);
});

ValidatingStargateBridge.QueryAssert candidatesAssert =
withQuery(collectionReadCql)
.withPageSize(1)
.withColumnSpec(
List.of(
QueryOuterClass.ColumnSpec.newBuilder()
.setName("count")
.setType(TypeSpecs.INT)
.build()))
.returning(List.of(List.of(Values.of(5))));

LogicalExpression implicitAnd = LogicalExpression.and();
CountOperation countOperation = new CountOperation(CONTEXT, implicitAnd);
CountOperation countOperation = new CountOperation(CONTEXT, LogicalExpression.and());
Supplier<CommandResult> execute =
countOperation
.execute(queryExecutor)
Expand All @@ -67,15 +68,15 @@ public void countWithNoFilter() {
.getItem();

// assert query execution
candidatesAssert.assertExecuteCount().isOne();
assertThat(callCount.get()).isEqualTo(1);

// then result
CommandResult result = execute.get();
assertThat(result)
.satisfies(
commandResult -> {
assertThat(result.status().get(CommandStatus.COUNTED_DOCUMENT)).isNotNull();
assertThat(result.status().get(CommandStatus.COUNTED_DOCUMENT)).isEqualTo(5);
assertThat(result.status().get(CommandStatus.COUNTED_DOCUMENT)).isEqualTo(5L);
});
}

Expand All @@ -84,19 +85,19 @@ public void countWithDynamic() {
String collectionReadCql =
"SELECT COUNT(1) AS count FROM \"%s\".\"%s\" WHERE array_contains CONTAINS ?"
.formatted(KEYSPACE_NAME, COLLECTION_NAME);

ValidatingStargateBridge.QueryAssert candidatesAssert =
withQuery(
collectionReadCql,
Values.of("username " + new DocValueHasher().getHash("user1").hash()))
.withPageSize(1)
.withColumnSpec(
List.of(
QueryOuterClass.ColumnSpec.newBuilder()
.setName("count")
.setType(TypeSpecs.INT)
.build()))
.returning(List.of(List.of(Values.of(2))));
final String filterValue = "username " + new DocValueHasher().getHash("user1").hash();
SimpleStatement stmt = SimpleStatement.newInstance(collectionReadCql, filterValue);
List<Row> rows =
Arrays.asList(new MockRow(COUNT_RESULT_COLUMNS, 0, Arrays.asList(byteBufferFrom(2))));
AsyncResultSet mockResults = new MockAsyncResultSet(COUNT_RESULT_COLUMNS, rows, null);
final AtomicInteger callCount = new AtomicInteger();
QueryExecutor queryExecutor = mock(QueryExecutor.class);
when(queryExecutor.executeRead(eq(stmt), any(), anyInt()))
.then(
invocation -> {
callCount.incrementAndGet();
return Uni.createFrom().item(mockResults);
});

LogicalExpression implicitAnd = LogicalExpression.and();
implicitAnd.comparisonExpressions.add(new ComparisonExpression(null, null, null));
Expand All @@ -117,15 +118,15 @@ public void countWithDynamic() {
.getItem();

// assert query execution
candidatesAssert.assertExecuteCount().isOne();
assertThat(callCount.get()).isEqualTo(1);

// then result
CommandResult result = execute.get();
assertThat(result)
.satisfies(
commandResult -> {
assertThat(result.status().get(CommandStatus.COUNTED_DOCUMENT)).isNotNull();
assertThat(result.status().get(CommandStatus.COUNTED_DOCUMENT)).isEqualTo(2);
assertThat(result.status().get(CommandStatus.COUNTED_DOCUMENT)).isEqualTo(2L);
});
}

Expand All @@ -134,19 +135,19 @@ public void countWithDynamicNoMatch() {
String collectionReadCql =
"SELECT COUNT(1) AS count FROM \"%s\".\"%s\" WHERE array_contains CONTAINS ?"
.formatted(KEYSPACE_NAME, COLLECTION_NAME);

ValidatingStargateBridge.QueryAssert candidatesAssert =
withQuery(
collectionReadCql,
Values.of("username " + new DocValueHasher().getHash("user_all").hash()))
.withPageSize(1)
.withColumnSpec(
List.of(
QueryOuterClass.ColumnSpec.newBuilder()
.setName("count")
.setType(TypeSpecs.INT)
.build()))
.returning(List.of(List.of(Values.of(0))));
final String filterValue = "username " + new DocValueHasher().getHash("user_all").hash();
SimpleStatement stmt = SimpleStatement.newInstance(collectionReadCql, filterValue);
List<Row> rows =
Arrays.asList(new MockRow(COUNT_RESULT_COLUMNS, 0, Arrays.asList(byteBufferFrom(0L))));
AsyncResultSet mockResults = new MockAsyncResultSet(COUNT_RESULT_COLUMNS, rows, null);
final AtomicInteger callCount = new AtomicInteger();
QueryExecutor queryExecutor = mock(QueryExecutor.class);
when(queryExecutor.executeRead(eq(stmt), any(), anyInt()))
.then(
invocation -> {
callCount.incrementAndGet();
return Uni.createFrom().item(mockResults);
});

LogicalExpression implicitAnd = LogicalExpression.and();
implicitAnd.comparisonExpressions.add(new ComparisonExpression(null, null, null));
Expand All @@ -168,36 +169,33 @@ public void countWithDynamicNoMatch() {
.getItem();

// assert query execution
candidatesAssert.assertExecuteCount().isOne();
assertThat(callCount.get()).isEqualTo(1);

// then result
CommandResult result = execute.get();
assertThat(result)
.satisfies(
commandResult -> {
assertThat(result.status().get(CommandStatus.COUNTED_DOCUMENT)).isNotNull();
assertThat(result.status().get(CommandStatus.COUNTED_DOCUMENT)).isEqualTo(0);
assertThat(result.status().get(CommandStatus.COUNTED_DOCUMENT)).isEqualTo(0L);
});
}

@Test
public void error() {
// failures are propagated down
RuntimeException failure = new RuntimeException("Ivan fails the test.");

String collectionReadCql =
"SELECT COUNT(1) AS count FROM \"%s\".\"%s\"".formatted(KEYSPACE_NAME, COLLECTION_NAME);

ValidatingStargateBridge.QueryAssert candidatesAssert =
withQuery(collectionReadCql)
.withPageSize(1)
.withColumnSpec(
List.of(
QueryOuterClass.ColumnSpec.newBuilder()
.setName("count")
.setType(TypeSpecs.INT)
.build()))
.returningFailure(failure);
SimpleStatement stmt = SimpleStatement.newInstance(collectionReadCql);
final AtomicInteger callCount = new AtomicInteger();
QueryExecutor queryExecutor = mock(QueryExecutor.class);
when(queryExecutor.executeRead(eq(stmt), any(), anyInt()))
.then(
invocation -> {
callCount.incrementAndGet();
return Uni.createFrom().failure(failure);
});

LogicalExpression implicitAnd = LogicalExpression.and();
CountOperation countOperation = new CountOperation(CONTEXT, implicitAnd);
Expand All @@ -210,7 +208,7 @@ public void error() {
.getFailure();

// assert query execution
candidatesAssert.assertExecuteCount().isOne();
assertThat(callCount.get()).isEqualTo(1);

// then result
assertThat(result).isEqualTo(failure);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.stargate.sgv2.jsonapi.service.operation.model.impl;

import com.datastax.oss.driver.api.core.ProtocolVersion;
import com.datastax.oss.driver.api.core.cql.ColumnDefinition;
import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
import com.datastax.oss.driver.api.core.detach.AttachmentPoint;
import com.datastax.oss.driver.api.core.type.codec.TypeCodecs;
import com.datastax.oss.driver.internal.core.cql.DefaultColumnDefinition;
import com.datastax.oss.driver.internal.core.cql.DefaultColumnDefinitions;
import com.datastax.oss.protocol.internal.response.result.ColumnSpec;
import com.datastax.oss.protocol.internal.response.result.RawType;
import io.stargate.sgv2.jsonapi.api.model.command.CommandContext;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.RandomStringUtils;

public class OperationTestBase {
protected final String KEYSPACE_NAME = RandomStringUtils.randomAlphanumeric(16);
protected final String COLLECTION_NAME = RandomStringUtils.randomAlphanumeric(16);
protected final CommandContext CONTEXT = new CommandContext(KEYSPACE_NAME, COLLECTION_NAME);

protected ColumnDefinitions buildColumnDefs(List<TestColumn> columns) {
return buildColumnDefs(KEYSPACE_NAME, COLLECTION_NAME, columns);
}

protected ColumnDefinitions buildColumnDefs(
String ks, String tableName, List<TestColumn> columns) {
List<ColumnDefinition> columnDefs = new ArrayList<>();
for (int ix = 0, end = columns.size(); ix < end; ++ix) {
columnDefs.add(
new DefaultColumnDefinition(
new ColumnSpec(
ks,
tableName,
columns.get(ix).name(),
ix,
RawType.PRIMITIVES.get(columns.get(ix).type())),
AttachmentPoint.NONE));
}
return DefaultColumnDefinitions.valueOf(columnDefs);
}

protected ByteBuffer byteBufferFrom(long value) {
return TypeCodecs.BIGINT.encode(value, ProtocolVersion.DEFAULT);
}

protected record TestColumn(String name, int type) {
static TestColumn of(String name, int type) {
return new TestColumn(name, type);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.stargate.sgv2.jsonapi.service.testutil;

import static org.mockito.Mockito.mock;

import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
import com.datastax.oss.driver.api.core.cql.Row;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletionStage;

public class MockAsyncResultSet implements AsyncResultSet {

private final List<Row> rows;
private final Iterator<Row> iterator;
private final CompletionStage<AsyncResultSet> nextPage;
private final ExecutionInfo executionInfo = mock(ExecutionInfo.class);
private final ColumnDefinitions columnDefs;
private int remaining;

public MockAsyncResultSet(
ColumnDefinitions columnDefs, List<Row> rows, CompletionStage<AsyncResultSet> nextPage) {
this.columnDefs = columnDefs;
this.rows = rows;
iterator = rows.iterator();
remaining = rows.size();
this.nextPage = nextPage;
}

@Override
public Row one() {
Row next = iterator.next();
remaining--;
return next;
}

@Override
public int remaining() {
return remaining;
}

@Override
public List<Row> currentPage() {
return new ArrayList<>(rows);
}

@Override
public boolean hasMorePages() {
return nextPage != null;
}

@Override
public CompletionStage<AsyncResultSet> fetchNextPage() throws IllegalStateException {
return nextPage;
}

@Override
public ColumnDefinitions getColumnDefinitions() {
return columnDefs;
}

@Override
public ExecutionInfo getExecutionInfo() {
return executionInfo;
}

@Override
public boolean wasApplied() {
return true;
}
}
Loading

0 comments on commit b643007

Please sign in to comment.