DATACOUCH-287 - Add CAS operations to RxJavaCouchbaseOperations.

Original pull request: #135.
This commit is contained in:
Alexander Derkach
2017-03-28 18:38:02 +03:00
committed by Subhashni Balakrishnan
parent 49045b031c
commit 6429dca5e6
6 changed files with 488 additions and 89 deletions

View File

@@ -1,5 +1,7 @@
package org.springframework.data.couchbase.core;
import rx.observers.TestSubscriber;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -17,4 +19,37 @@ public class AsyncUtils {
future.get(numThreads, TimeUnit.SECONDS);
}
}
public static <T> void awaitCompleted(TestSubscriber<T> testSubscriber) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertNoErrors();
testSubscriber.assertNoValues();
testSubscriber.assertCompleted();
}
public static <T> void awaitCompletedWithAnyValue(TestSubscriber<T> testSubscriber) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertNoErrors();
testSubscriber.assertCompleted();
}
public static <T> void awaitCompletedWithValueCount(TestSubscriber<T> testSubscriber, int count) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertNoErrors();
testSubscriber.assertValueCount(count);
testSubscriber.assertCompleted();
}
public static <T> void awaitError(TestSubscriber<T> testSubscriber, Class<? extends Throwable> throwableClazz) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertError(throwableClazz);
testSubscriber.assertNoValues();
}
public static <T> void awaitValue(TestSubscriber<T> testSubscriber, T value) {
testSubscriber.awaitTerminalEvent();
testSubscriber.assertNoErrors();
testSubscriber.assertValue(value);
testSubscriber.assertCompleted();
}
}

View File

@@ -22,6 +22,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.*;
import com.couchbase.client.java.Bucket;
@@ -37,12 +38,15 @@ import com.couchbase.client.java.view.Stale;
import com.couchbase.client.java.view.ViewQuery;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.ReactiveIntegrationTestApplicationConfig;
@@ -51,9 +55,11 @@ import org.springframework.data.couchbase.core.mapping.Document;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import rx.observers.TestSubscriber;
/**
* @author Subhashni Balakrishnan
* @author Alex Derkach
**/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ReactiveIntegrationTestApplicationConfig.class)
@@ -70,77 +76,199 @@ public class RxJavaCouchbaseTemplateTests {
private RxJavaCouchbaseOperations template;
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String DEFAULT_ID = "reactivebeers:awesome-stout";
private static final String DEFAULT_NAME = "The Awesome Stout";
private static final boolean DEFAULT_ACTIVE = false;
private static final String DEFAULT_DESCRIPTION = "";
@Before
public void setUp() throws Exception {
removeIfExist(DEFAULT_ID);
}
private void removeIfExist(String key) {
template.remove(key).subscribe(
v -> {},
err -> {}
);
TestSubscriber<Object> subscriber = TestSubscriber.create();
template.remove(key)
.subscribe(subscriber);
subscriber.awaitTerminalEvent();
}
private void removeCollectionIfExist(Collection<ReactiveBeer> beers) {
TestSubscriber<Object> subscriber = TestSubscriber.create();
template.remove(beers, PersistTo.MASTER, ReplicateTo.NONE)
.subscribe(
v -> {},
err -> {}
);
.subscribe(subscriber);
subscriber.awaitTerminalEvent();
}
@Test
public void saveSimpleEntityCorrectly() throws Exception {
String id = "reactivebeers:awesome-stout";
removeIfExist(id);
public void upsertNonVersionedEntityCorrectlyWhenSaveIsCalled() throws Exception {
String newName = DEFAULT_NAME + "Second";
ReactiveBeer firstBeer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
ReactiveBeer secondBeer = new ReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
TestSubscriber<ReactiveBeer> firstSaveSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> secondSaveSubscriber = TestSubscriber.create();
String name = "The Awesome Stout";
boolean active = false;
ReactiveBeer beer = new ReactiveBeer(id, name, active, "");
template.save(firstBeer).subscribe(firstSaveSubscriber);
template.save(secondBeer).subscribe(secondSaveSubscriber);
template.save(beer)
.subscribe();
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
String result = resultDoc.content();
assertNotNull(result);
Map<String, Object> resultConv = MAPPER.readValue(result, new TypeReference<Map<String, Object>>() {
});
AsyncUtils.awaitCompletedWithAnyValue(firstSaveSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(secondSaveSubscriber);
validateBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, ReactiveBeer.class);
}
assertNotNull(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertNull(resultConv.get("javaClass"));
assertEquals("org.springframework.data.couchbase.core.ReactiveBeer", resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertEquals(false, resultConv.get("is_active"));
assertEquals("The Awesome Stout", resultConv.get("name"));
removeIfExist(id);
@Test
public void replaceVersionedEntityCorrectlyWhenSaveIsCalledAndCasIsNotZero() throws Exception {
String newName = DEFAULT_NAME + "Second";
VersionedReactiveBeer firstBeer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
VersionedReactiveBeer secondBeer = new VersionedReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
long version = template.save(firstBeer).toBlocking().single().getVersion();
assertTrue(version > 0);
secondBeer.setVersion(version);
long newVersion = template.save(secondBeer).toBlocking().single().getVersion();
assertTrue(newVersion > 0);
assertNotEquals(version, newVersion);
validateBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class);
}
@Test
public void throwExceptionWhenSaveIsCalledAndCasIsMissmatched() throws Exception {
String newName = DEFAULT_NAME + "Second";
VersionedReactiveBeer firstBeer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
VersionedReactiveBeer secondBeer = new VersionedReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
TestSubscriber<VersionedReactiveBeer> secondSaveSubscriber = TestSubscriber.create();
long version = template.save(firstBeer).toBlocking().single().getVersion();
assertTrue(version > 0);
secondBeer.setVersion(version + 1234);
template.save(secondBeer).subscribe(secondSaveSubscriber);
AsyncUtils.awaitError(secondSaveSubscriber, OptimisticLockingFailureException.class);
validateBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class);
}
@Test
public void throwExceptionWhenSaveIsCalledAndCasIsZeroAndEntityAlreadyExists() throws Exception {
String newName = DEFAULT_NAME + "Second";
VersionedReactiveBeer firstBeer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
VersionedReactiveBeer secondBeer = new VersionedReactiveBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
TestSubscriber<VersionedReactiveBeer> secondSaveSubscriber = TestSubscriber.create();
long version = template.save(firstBeer).toBlocking().single().getVersion();
assertTrue(version > 0);
template.save(secondBeer).subscribe(secondSaveSubscriber);
AsyncUtils.awaitError(secondSaveSubscriber, OptimisticLockingFailureException.class);
validateBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class);
}
@Test
public void saveCollectionCorrectly() throws Exception {
Collection<ReactiveBeer> beers = new ArrayList<>();
String name = "The Awesome Stout";
for (int i=0; i < 10000; i++) {
TestSubscriber<ReactiveBeer> testSubscriber = TestSubscriber.create();
String name = DEFAULT_NAME;
int collectionSize = 10000;
for (int i = 0; i < collectionSize; i++) {
beers.add(new ReactiveBeer("beerCollItem" + i, name + i, false, ""));
}
removeCollectionIfExist(beers);
template.save(beers).subscribe();
template.save(beers).subscribe(testSubscriber);
AsyncUtils.awaitCompletedWithValueCount(testSubscriber, collectionSize);
}
@Test
public void insertSimpleEntityCorrectly() throws Exception {
TestSubscriber<ReactiveBeer> testSubscriber = TestSubscriber.create();
ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.insert(beer).subscribe(testSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(testSubscriber);
validateBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, ReactiveBeer.class);
}
@Test
public void expectErrorWhenDocumentExistsAndInsertIsCalled() throws Exception {
TestSubscriber<ReactiveBeer> firstInsertSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> secondInsertSubscriber = TestSubscriber.create();
ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.insert(beer).subscribe(firstInsertSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(firstInsertSubscriber);
template.insert(beer).subscribe(secondInsertSubscriber);
AsyncUtils.awaitError(secondInsertSubscriber, OptimisticLockingFailureException.class);
}
@Test
public void insertCollectionCorrectly() throws Exception {
TestSubscriber<ReactiveBeer> testSubscriber = TestSubscriber.create();
Collection<ReactiveBeer> beers = new ArrayList<>();
String name = DEFAULT_NAME;
int collectionSize = 10000;
for (int i = 0; i < collectionSize; i++) {
beers.add(new ReactiveBeer("beerCollItem" + i, name + i, false, ""));
}
removeCollectionIfExist(beers);
template.insert(beers).subscribe(testSubscriber);
AsyncUtils.awaitCompletedWithValueCount(testSubscriber, collectionSize);
}
@Test
public void replaceSimpleEntityCorrectly() throws Exception {
TestSubscriber<VersionedReactiveBeer> firstInsertSubscriber = TestSubscriber.create();
TestSubscriber<VersionedReactiveBeer> replaceTestSubscriber = TestSubscriber.create();
String newName = DEFAULT_NAME + " New";
VersionedReactiveBeer beer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.insert(beer).subscribe(firstInsertSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(firstInsertSubscriber);
template.findById(DEFAULT_ID, VersionedReactiveBeer.class)
.doOnNext(v -> v.setName(newName))
.flatMap(v -> template.update(v))
.subscribe(replaceTestSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(replaceTestSubscriber);
validateBeer(DEFAULT_ID, newName, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION, VersionedReactiveBeer.class);
}
@Test
public void expectErrorWhenReplacingSimpleEntityWhichDoesNotExist() throws Exception {
TestSubscriber<VersionedReactiveBeer> testSubscriber = TestSubscriber.create();
VersionedReactiveBeer beer = new VersionedReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.update(beer).subscribe(testSubscriber);
AsyncUtils.awaitError(testSubscriber, DataRetrievalFailureException.class);
}
@Test
public void removeDocument() {
String id = "beers:to-delete-stout";
ReactiveBeer beer = new ReactiveBeer(id, "", false, "");
removeIfExist(id);
TestSubscriber<ReactiveBeer> firstInsertSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> firstFindSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> removalTestSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> secondFindSubscriber = TestSubscriber.create();
ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
template.save(beer).subscribe();
Object result = template.findById(id, ReactiveBeer.class).toBlocking().single();
assertNotNull(result);
template.save(beer).subscribe(firstInsertSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(firstInsertSubscriber);
template.remove(beer).subscribe();
result = client.get(id);
assertNull(result);
template.findById(DEFAULT_ID, ReactiveBeer.class).subscribe(firstFindSubscriber);
AsyncUtils.awaitCompletedWithValueCount(firstFindSubscriber, 1);
template.remove(beer).subscribe(removalTestSubscriber);
AsyncUtils.awaitCompletedWithValueCount(removalTestSubscriber, 1);
template.findById(DEFAULT_ID, ReactiveBeer.class).subscribe(secondFindSubscriber);
AsyncUtils.awaitValue(secondFindSubscriber, null);
}
@Test
public void storeListsAndMaps() {
String id = "persons:lots-of-names";
@@ -171,18 +299,16 @@ public class RxJavaCouchbaseTemplateTests {
@Test
public void validFindById() {
String id = "reactive beers:findme-stout";
String name = "Findme Stout";
boolean active = true;
ReactiveBeer beer = new ReactiveBeer(id, name, active, "");
template.save(beer).subscribe();
TestSubscriber<ReactiveBeer> saveSubscriber = TestSubscriber.create();
TestSubscriber<ReactiveBeer> findSubscriber = TestSubscriber.create();
ReactiveBeer found = template.findById(id, ReactiveBeer.class).toBlocking().single();
ReactiveBeer beer = new ReactiveBeer(DEFAULT_ID, DEFAULT_NAME, DEFAULT_ACTIVE, DEFAULT_DESCRIPTION);
assertNotNull(found);
assertEquals(id, found.getId());
assertEquals(name, found.getName());
assertEquals(active, found.getActive());
template.save(beer).subscribe(saveSubscriber);
AsyncUtils.awaitCompletedWithAnyValue(saveSubscriber);
template.findById(DEFAULT_ID, ReactiveBeer.class).subscribe(findSubscriber);
AsyncUtils.awaitValue(findSubscriber, beer);
}
@Test
@@ -297,6 +423,21 @@ public class RxJavaCouchbaseTemplateTests {
}
}
private void validateBeer(String id, String name, boolean active, String description, Class<?> clazz) throws IOException {
RawJsonDocument resultDoc = client.get(id, RawJsonDocument.class);
assertNotNull(resultDoc);
String result = resultDoc.content();
assertNotNull(result);
Map<String, Object> resultConv = MAPPER.readValue(result, new TypeReference<Map<String, Object>>() {});
assertNotNull(resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertNull(resultConv.get("javaClass"));
assertEquals(clazz.getCanonicalName(), resultConv.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT));
assertEquals(active, resultConv.get("is_active"));
assertEquals(name, resultConv.get("name"));
assertEquals(description, resultConv.get("desc"));
}
/**
* A sample document with just an id and property.
*/