From 7814e1aad7bcb91a24b115029f9d009d08e2b10b Mon Sep 17 00:00:00 2001
From: Tom Blench
Date: Tue, 27 Feb 2018 15:17:29 +0000
Subject: [PATCH 01/17] Upgrade tests to JUnit 5 (JUnit Jupiter)
* Refactor tests to use JUnit 5 (org.junit.jupiter.api.*) APIs
* Convert existing Resources/Rules etc to JUnit 5 Extensions
* Add base classes in com.cloudant.tests.base package for common test
patterns eg remote-data-base-per-class, remote-dat abase-per-method
* Convert parameterised tests to TestTemplates which use helper
classes to provide parameters
* Convert JUnit 4 Categories to JUnit 5 Tags
* Upgrade gradle to 4.6
* Add custom JavaExec task to build.gradle to invoke JUnit Platform
Console Launcher, due to lack of flexibility in current gradle
support for JUnit Platform.
---
Jenkinsfile | 4 +-
build.gradle | 4 +-
cloudant-client/build.gradle | 63 +--
.../cloudant/api/query/FieldAssertHelper.java | 16 +-
.../api/query/IndexCreationTests.java | 12 +-
.../api/query/IndexDeletionTests.java | 15 +-
.../api/query/IndexLifecycleTest.java | 68 ++-
.../cloudant/api/query/IndexListTests.java | 45 +-
.../cloudant/test/main/RequiresCloudant.java | 17 +-
.../test/main/RequiresCloudantLocal.java | 18 +-
.../test/main/RequiresCloudantService.java | 18 +-
.../com/cloudant/test/main/RequiresCouch.java | 17 +-
.../com/cloudant/test/main/RequiresDB.java | 16 +-
.../com/cloudant/tests/AttachmentsTest.java | 131 +++---
.../com/cloudant/tests/BulkDocumentTest.java | 29 +-
.../tests/ChangeNotificationsTest.java | 60 ++-
.../com/cloudant/tests/ClientLoadTest.java | 38 +-
.../tests/CloudFoundryServiceTest.java | 152 +++++--
.../cloudant/tests/CloudantClientHelper.java | 3 +
.../cloudant/tests/CloudantClientTests.java | 290 +++++++------
.../tests/ComplexKeySerializationTest.java | 4 +-
.../com/cloudant/tests/CouchDbUtilTest.java | 4 +-
.../java/com/cloudant/tests/DBServerTest.java | 34 +-
.../java/com/cloudant/tests/DatabaseTest.java | 71 ++--
.../cloudant/tests/DatabaseURIHelperTest.java | 191 +++++----
.../cloudant/tests/DesignDocumentTest.java | 105 +++--
.../cloudant/tests/DesignDocumentsTest.java | 166 ++++----
.../com/cloudant/tests/DocumentsCRUDTest.java | 121 +++---
.../src/test/java/com/cloudant/tests/Foo.java | 3 +-
.../tests/HierarchicalUriComponentsTest.java | 4 +-
.../java/com/cloudant/tests/HttpIamTest.java | 159 +++----
.../com/cloudant/tests/HttpProxyTest.java | 188 ++++++---
.../java/com/cloudant/tests/HttpTest.java | 395 ++++++++++--------
.../java/com/cloudant/tests/IndexTests.java | 90 ++--
.../java/com/cloudant/tests/LoggingTest.java | 70 ++--
.../java/com/cloudant/tests/QueryTests.java | 54 +--
.../com/cloudant/tests/ReplicationTest.java | 24 +-
.../com/cloudant/tests/ReplicatorTest.java | 26 +-
.../java/com/cloudant/tests/ResponseTest.java | 47 +--
.../java/com/cloudant/tests/SearchTests.java | 38 +-
.../tests/SessionInterceptorExpiryTests.java | 161 ++++---
.../cloudant/tests/SslAuthenticationTest.java | 164 +++++---
.../java/com/cloudant/tests/URIBaseTest.java | 21 +-
.../java/com/cloudant/tests/UnicodeTest.java | 47 +--
.../com/cloudant/tests/UpdateHandlerTest.java | 33 +-
.../cloudant/tests/ViewPaginationTests.java | 186 ++++++---
.../java/com/cloudant/tests/ViewsTest.java | 277 +++++++-----
.../com/cloudant/tests/base/TestWithDb.java | 30 ++
.../tests/base/TestWithDbPerTest.java | 35 ++
.../TestWithMockedServer.java} | 39 +-
.../TestWithReplication.java} | 42 +-
.../extensions/AbstractClientExtension.java | 11 +
.../CloudantClientExtension.java} | 27 +-
.../CloudantClientMockServerExtension.java | 60 +++
.../DatabaseExtension.java} | 93 +++--
.../extensions/MockWebServerExtension.java | 42 ++
.../tests/extensions/MultiExtension.java | 75 ++++
.../cloudant/tests/util/CheckPagination.java | 12 +-
.../CloudantClientMockServerResource.java | 36 --
.../util/HttpFactoryParameterizedTest.java | 25 +-
.../java/com/cloudant/tests/util/TestLog.java | 35 --
.../java/com/cloudant/tests/util/Utils.java | 16 +-
gradle/wrapper/gradle-wrapper.jar | Bin 53319 -> 54334 bytes
gradle/wrapper/gradle-wrapper.properties | 3 +-
gradlew | 26 +-
gradlew.bat | 6 -
66 files changed, 2536 insertions(+), 1776 deletions(-)
create mode 100644 cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDb.java
create mode 100644 cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java
rename cloudant-client/src/test/java/com/cloudant/tests/{util/MockedServerTest.java => base/TestWithMockedServer.java} (50%)
rename cloudant-client/src/test/java/com/cloudant/tests/{ReplicateBaseTest.java => base/TestWithReplication.java} (56%)
create mode 100644 cloudant-client/src/test/java/com/cloudant/tests/extensions/AbstractClientExtension.java
rename cloudant-client/src/test/java/com/cloudant/tests/{util/CloudantClientResource.java => extensions/CloudantClientExtension.java} (70%)
create mode 100644 cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientMockServerExtension.java
rename cloudant-client/src/test/java/com/cloudant/tests/{util/DatabaseResource.java => extensions/DatabaseExtension.java} (60%)
create mode 100644 cloudant-client/src/test/java/com/cloudant/tests/extensions/MockWebServerExtension.java
create mode 100644 cloudant-client/src/test/java/com/cloudant/tests/extensions/MultiExtension.java
delete mode 100644 cloudant-client/src/test/java/com/cloudant/tests/util/CloudantClientMockServerResource.java
delete mode 100644 cloudant-client/src/test/java/com/cloudant/tests/util/TestLog.java
diff --git a/Jenkinsfile b/Jenkinsfile
index 99e321357..290eec3b9 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -31,7 +31,7 @@ def runTests(testEnv, isServiceTests) {
try {
sh './gradlew -Dtest.couch.username=$DB_USER -Dtest.couch.password=$DB_PASSWORD -Dtest.couch.host=$DB_HOST -Dtest.couch.port=$DB_PORT -Dtest.couch.http=$DB_HTTP $GRADLE_TARGET'
} finally {
- junit '**/build/test-results/*.xml'
+ junit '**/build/test-results/**/*.xml'
}
}
}
@@ -75,7 +75,7 @@ stage('QA') {
// For the master branch, add additional axes to the coverage matrix for Couch 1.6, 2.0
// and Cloudant Local
- if (env.BRANCH_NAME == "master") {
+ if (env.BRANCH_NAME == "master" || env.BRANCH_NAME == "junit5") {
axes.putAll(
Couch1_6: {
runTests(COUCH1_6_ENV, false)
diff --git a/build.gradle b/build.gradle
index 759f15d88..55bf5f2fa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -28,8 +28,8 @@ subprojects {
// If the version says "snapshot" anywhere assume it is not a release
ext.isReleaseVersion = !version.toUpperCase(Locale.ENGLISH).contains("SNAPSHOT")
- sourceCompatibility = 1.6
- targetCompatibility = 1.6
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
repositories {
mavenLocal()
diff --git a/cloudant-client/build.gradle b/cloudant-client/build.gradle
index 291f56778..92ea8299e 100644
--- a/cloudant-client/build.gradle
+++ b/cloudant-client/build.gradle
@@ -25,7 +25,10 @@ dependencies {
compile project(':cloudant-http')
linkableJavadoc project(':cloudant-http')
//test dependencies
- testCompile group: 'junit', name: 'junit', version: '4.12'
+ testRuntime group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.1.0'
+ testRuntime group: 'org.junit.platform', name: 'junit-platform-console', version: '1.1.0'
+ testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.1.0'
+ testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3'
testCompile group: 'com.squareup.okhttp3', name: 'mockwebserver', version: '3.8.1'
testCompile group: 'org.jmockit', name: 'jmockit', version: '1.34'
testCompile group: 'org.littleshoot', name: 'littleproxy', version: '1.1.0'
@@ -65,35 +68,45 @@ tasks.withType(Test) {
}
}
-test {
- // Run tests for any DB
- useJUnit {
- excludeCategories 'com.cloudant.test.main.RequiresCloudant',
- 'com.cloudant.test.main.RequiresCouch'
+// hacky - see https://discuss.gradle.org/t/passing-arguments-to-a-task/8427/9
+def CustomJunitPlatformTask(include, exclude) {
+ return tasks.create(name: "exec_${include}_${exclude}", type: JavaExec, dependsOn: testClasses) {
+ classpath = sourceSets.main.runtimeClasspath + sourceSets.test.runtimeClasspath
+ main = 'org.junit.platform.console.ConsoleLauncher'
+ // arguments to pass to the application
+ args ('--reports-dir',testResultsDir,
+ '--scan-class-path')
+ // build up includes
+ if (include.size > 0) {
+ args += '--include-tag'
+ args += include.join("|")
+ }
+ // build up excludes
+ if (exclude.size > 0) {
+ args += '--exclude-tag'
+ args += exclude.join("|")
+ }
+ // inherit system properties
+ systemProperties System.properties
+ // some tests expect user.dir to be same as working dir, which
+ // defaults to project dir
+ systemProperty "user.dir", project.projectDir
}
}
-task noDBTest(type: Test, dependsOn: testClasses) {
- // Run unit tests that do not need a running database
- useJUnit {
- excludeCategories 'com.cloudant.test.main.RequiresDB'
- }
-}
+// Run tests which are compatible with all versions of Couch or
+// Cloudant.
+// This is the default test target.
+task test (dependsOn:CustomJunitPlatformTask([],['RequiresCouch','RequiresCloudant']), overwrite: true){}
-task cloudantTest(type: Test, dependsOn: testClasses) {
- // Run tests that can use any Cloudant
- useJUnit {
- excludeCategories 'com.cloudant.test.main.RequiresCloudantService'
- }
-}
+// Run 'unit' tests which do not require a database.
+task noDBTest(dependsOn: CustomJunitPlatformTask([],['RequiresDB'])) {}
-task cloudantServiceTest(type: Test, dependsOn: testClasses) {
- // Run all Cloudant service tests
- useJUnit {
- excludeCategories 'com.cloudant.test.main.RequiresCloudantLocal',
- 'com.cloudant.test.main.RequiresCouch'
- }
-}
+// Run tests that can use any Cloudant.
+task cloudantTest(dependsOn: CustomJunitPlatformTask([],['RequiresCloudantService'])) {}
+
+// Run all Cloudant service tests.
+task cloudantServiceTest(dependsOn: CustomJunitPlatformTask([],['RequiresCloudantLocal', 'RequiresCouch'])) {}
//task for generating a client properties file
class ClientProperties extends DefaultTask {
diff --git a/cloudant-client/src/test/java/com/cloudant/api/query/FieldAssertHelper.java b/cloudant-client/src/test/java/com/cloudant/api/query/FieldAssertHelper.java
index 100ad355d..96264255a 100644
--- a/cloudant-client/src/test/java/com/cloudant/api/query/FieldAssertHelper.java
+++ b/cloudant-client/src/test/java/com/cloudant/api/query/FieldAssertHelper.java
@@ -14,8 +14,8 @@
package com.cloudant.api.query;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import com.cloudant.client.api.query.Field;
import com.cloudant.client.api.query.JsonIndex;
@@ -40,7 +40,7 @@ public static class Json extends FieldAssertHelper
@Override
protected void assertField(JsonIndex.Field field, Sort.Order order) {
- assertEquals("The order should be the same", order, field.getOrder());
+ assertEquals(order, field.getOrder(), "The order should be the same");
}
}
@@ -53,20 +53,20 @@ public static class Text extends FieldAssertHelper {
@Override
protected void assertField(TextIndex.Field field, Type type) {
- assertEquals("The type should be the same", type, field.getType());
+ assertEquals(type, field.getType(), "The type should be the same");
}
}
public void assertFields(List actualFields) {
- assertEquals("There should be the correct number of fields", expectedFields.size(),
- actualFields.size());
+ assertEquals(expectedFields.size(),
+ actualFields.size(), "There should be the correct number of fields");
for (F field : actualFields) {
assertNotNull("The field should have a name", field.getName());
T expected = expectedFields.remove(field.getName());
- assertNotNull("Unexpected field " + field.getName() + " found.", expected);
+ assertNotNull(expected, "Unexpected field " + field.getName() + " found.");
assertField(field, expected);
}
- assertEquals("All fields should be asserted.", 0, expectedFields.size());
+ assertEquals(0, expectedFields.size(), "All fields should be asserted.");
}
protected abstract void assertField(F field, T type);
diff --git a/cloudant-client/src/test/java/com/cloudant/api/query/IndexCreationTests.java b/cloudant-client/src/test/java/com/cloudant/api/query/IndexCreationTests.java
index f062f21d8..4d59f65d6 100644
--- a/cloudant-client/src/test/java/com/cloudant/api/query/IndexCreationTests.java
+++ b/cloudant-client/src/test/java/com/cloudant/api/query/IndexCreationTests.java
@@ -15,24 +15,24 @@
package com.cloudant.api.query;
import static com.cloudant.client.api.query.Expression.gt;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import com.cloudant.client.api.query.JsonIndex;
import com.cloudant.client.api.query.Selector;
import com.cloudant.client.api.query.TextIndex;
import com.cloudant.client.internal.query.Helpers;
-import com.cloudant.tests.util.MockedServerTest;
+import com.cloudant.tests.base.TestWithMockedServer;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.RecordedRequest;
import java.util.concurrent.TimeUnit;
-public class IndexCreationTests extends MockedServerTest {
+public class IndexCreationTests extends TestWithMockedServer {
private static MockResponse CREATED = new MockResponse().setBody("{\"result\": \"created\"}");
@@ -122,7 +122,7 @@ public void createNamedTextIndex() throws Exception {
@Test
public void createTextIndexInDesignDoc() throws Exception {
createIndexTest(TextIndex.builder()
- .designDocument("testddoc")
+ .designDocument("testddoc")
.definition(),
"{type: \"text\", ddoc: \"testddoc\", index: {}}");
}
@@ -216,6 +216,6 @@ private void createIndexTest(String definition, String expected) throws Exceptio
db.createIndex(definition);
RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS);
JsonObject actual = new Gson().fromJson(request.getBody().readUtf8(), JsonObject.class);
- assertEquals("The request body should match the expected", exp, actual);
+ assertEquals(exp, actual, "The request body should match the expected");
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/api/query/IndexDeletionTests.java b/cloudant-client/src/test/java/com/cloudant/api/query/IndexDeletionTests.java
index 237573df1..b0bf7ecde 100644
--- a/cloudant-client/src/test/java/com/cloudant/api/query/IndexDeletionTests.java
+++ b/cloudant-client/src/test/java/com/cloudant/api/query/IndexDeletionTests.java
@@ -14,21 +14,21 @@
package com.cloudant.api.query;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import com.cloudant.tests.util.MockWebServerResources;
-import com.cloudant.tests.util.MockedServerTest;
+import com.cloudant.tests.base.TestWithMockedServer;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import okhttp3.mockwebserver.RecordedRequest;
import java.util.concurrent.TimeUnit;
-public class IndexDeletionTests extends MockedServerTest {
+public class IndexDeletionTests extends TestWithMockedServer {
- @Before
+ @BeforeEach
public void enqueueOK() {
server.enqueue(MockWebServerResources.JSON_OK);
}
@@ -53,7 +53,6 @@ public void deleteTextIndex() throws Exception {
private void assertDelete(String name, String ddoc, String type) throws Exception {
RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS);
- assertEquals("The request body should match the expected", "/" + dbResource.getDatabaseName() +
- "/_index/_design/" + ddoc + "/" + type + "/" + name, request.getPath());
+ assertEquals("/" + dbResource.getDatabaseName() + "/_index/_design/" + ddoc + "/" + type + "/" + name, request.getPath(), "The request body should match the expected");
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/api/query/IndexLifecycleTest.java b/cloudant-client/src/test/java/com/cloudant/api/query/IndexLifecycleTest.java
index 7e11454b4..9a9a0ad89 100644
--- a/cloudant-client/src/test/java/com/cloudant/api/query/IndexLifecycleTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/api/query/IndexLifecycleTest.java
@@ -14,7 +14,7 @@
package com.cloudant.api.query;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import com.cloudant.client.api.Database;
import com.cloudant.client.api.query.Field;
@@ -24,22 +24,20 @@
import com.cloudant.client.api.query.TextIndex;
import com.cloudant.client.api.query.Type;
import com.cloudant.test.main.RequiresCloudant;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.extensions.CloudantClientExtension;
+import com.cloudant.tests.extensions.DatabaseExtension;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.Collections;
import java.util.List;
// This really requires Couch2.0 + text index support, but we don't have a way of expressing that
-@Category(RequiresCloudant.class)
+@RequiresCloudant
/**
* Index lifecycle test.
@@ -49,14 +47,14 @@
*/
public class IndexLifecycleTest {
- private CloudantClientResource clientResource = new CloudantClientResource();
- private DatabaseResource dbResource = new DatabaseResource(clientResource);
- @Rule
- public RuleChain chain = RuleChain.outerRule(clientResource).around(dbResource);
+ @RegisterExtension
+ public static CloudantClientExtension clientResource = new CloudantClientExtension();
+ @RegisterExtension
+ public static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest(clientResource);
- private Database db;
+ private static Database db;
- @Before
+ @BeforeEach
public void createIndexes() throws Exception {
db = dbResource.get();
@@ -84,14 +82,14 @@ public void listIndexes() throws Exception {
{
// JSON index
List jIndexes = db.listIndexes().jsonIndexes();
- assertEquals("There should be one JSON index", 1, jIndexes.size());
+ assertEquals(1, jIndexes.size(), "There should be one JSON index");
JsonIndex jIndex = jIndexes.get(0);
- assertEquals("The ddoc should be correct", "_design/indexlifecycle", jIndex
- .getDesignDocumentID());
- assertEquals("The name should be correct", "testjson", jIndex.getName());
- assertEquals("The type should be correct", "json", jIndex.getType());
+ assertEquals("_design/indexlifecycle", jIndex
+ .getDesignDocumentID(), "The ddoc should be correct");
+ assertEquals("testjson", jIndex.getName(), "The name should be correct");
+ assertEquals("json", jIndex.getType(), "The type should be correct");
List fields = jIndex.getFields();
- assertEquals("There should be two fields", 2, fields.size());
+ assertEquals(2, fields.size(), "There should be two fields");
// Field assertions
new FieldAssertHelper.Json(Collections.singletonMap("testDefaultAsc", Sort.Order.ASC)
, Collections.singletonMap("testAsc", Sort.Order.ASC)).assertFields(fields);
@@ -100,14 +98,14 @@ public void listIndexes() throws Exception {
{
// Text index
List tIndexes = db.listIndexes().textIndexes();
- assertEquals("There should be one text index", 1, tIndexes.size());
+ assertEquals(1, tIndexes.size(), "There should be one text index");
TextIndex tIndex = tIndexes.get(0);
- assertEquals("The ddoc should be correct", "_design/indexlifecycle", tIndex
- .getDesignDocumentID());
- assertEquals("The name should be correct", "testtext", tIndex.getName());
- assertEquals("The type should be correct", "text", tIndex.getType());
+ assertEquals("_design/indexlifecycle", tIndex
+ .getDesignDocumentID(), "The ddoc should be correct");
+ assertEquals("testtext", tIndex.getName(), "The name should be correct");
+ assertEquals("text", tIndex.getType(), "The type should be correct");
List fields = tIndex.getFields();
- assertEquals("There should be three fields", 3, fields.size());
+ assertEquals(3, fields.size(), "There should be three fields");
// Field assertions
new FieldAssertHelper.Text(Collections.singletonMap("testString", Type.STRING),
Collections.singletonMap("testNumber", Type.NUMBER),
@@ -117,23 +115,23 @@ public void listIndexes() throws Exception {
{
// All indexes
List> allIndexes = db.listIndexes().allIndexes();
- assertEquals("There should be three total indexes", 3, allIndexes.size());
+ assertEquals(3, allIndexes.size(), "There should be three total indexes");
for (Index index : allIndexes) {
if (index.getType().equals("special")) {
- assertEquals("The name should be correct", "_all_docs", index.getName());
- assertEquals("There should be 1 field", 1, index.getFields().size());
- assertEquals("There field should be called _id", "_id", index.getFields().get(0)
- .getName());
+ assertEquals("_all_docs", index.getName(), "The name should be correct");
+ assertEquals(1, index.getFields().size(), "There should be 1 field");
+ assertEquals("_id", index.getFields().get(0)
+ .getName(), "There field should be called _id");
}
}
}
}
- @After
+ @AfterEach
public void deleteIndexes() throws Exception {
db.deleteIndex("testjson", "indexlifecycle", "json");
db.deleteIndex("testtext", "indexlifecycle", "text");
List> allIndexes = db.listIndexes().allIndexes();
- assertEquals("There should be one (special) index", 1, allIndexes.size());
+ assertEquals(1, allIndexes.size(), "There should be one (special) index");
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/api/query/IndexListTests.java b/cloudant-client/src/test/java/com/cloudant/api/query/IndexListTests.java
index 8f28f2061..42d3345ac 100644
--- a/cloudant-client/src/test/java/com/cloudant/api/query/IndexListTests.java
+++ b/cloudant-client/src/test/java/com/cloudant/api/query/IndexListTests.java
@@ -14,8 +14,8 @@
package com.cloudant.api.query;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
import com.cloudant.client.api.query.Field;
import com.cloudant.client.api.query.Index;
@@ -23,10 +23,10 @@
import com.cloudant.client.api.query.Sort;
import com.cloudant.client.api.query.TextIndex;
import com.cloudant.client.api.query.Type;
-import com.cloudant.tests.util.MockedServerTest;
+import com.cloudant.tests.base.TestWithMockedServer;
import org.apache.commons.io.IOUtils;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import okhttp3.mockwebserver.MockResponse;
@@ -37,7 +37,7 @@
import java.util.List;
import java.util.Map;
-public class IndexListTests extends MockedServerTest {
+public class IndexListTests extends TestWithMockedServer {
private static String SELECTOR_STRING = "{\"year\":{\"$gt\":2010}}";
@@ -92,12 +92,10 @@ private void assertIndex(Index index, String name, String type, String selector)
private void assertIndex(Index index, String name, String ddoc, String type, String selector)
throws Exception {
- assertEquals("The index should have the correct name", name, index.getName());
- assertEquals("The index should have the correct ddoc", ddoc, index
- .getDesignDocumentID());
- assertEquals("The index should have the correct type", type, index.getType());
- assertEquals("The index should have the correct selector", selector, index
- .getPartialFilterSelector());
+ assertEquals(name, index.getName(), "The index should have the correct name");
+ assertEquals(ddoc, index.getDesignDocumentID(), "The index should have the correct ddoc");
+ assertEquals(type, index.getType(), "The index should have the correct type");
+ assertEquals(selector, index.getPartialFilterSelector(), "The index should have the correct selector");
}
private void assertJsonIndex(JsonIndex index, String name, String selector, Map...
expectedFields) throws Exception {
assertIndex(index, name, "text", selector);
- assertEquals("The analyzer should be correct", analyzer, index.getAnalyzer());
- assertEquals("The default field should be correct", defaultField, index.getDefaultField());
+ assertEquals(analyzer, index.getAnalyzer(), "The analyzer should be correct");
+ assertEquals(defaultField, index.getDefaultField(), "The default field should be correct");
// Assert the fields
new FieldAssertHelper.Text(expectedFields).assertFields(index.getFields());
}
@@ -146,7 +144,7 @@ private void assertComplexText(TextIndex index) throws Exception {
public void listSimpleJsonIndex() throws Exception {
enqueueList(JSON_SIMPLE);
List indexes = db.listIndexes().jsonIndexes();
- assertEquals("There should be 1 JSON index", 1, indexes.size());
+ assertEquals(1, indexes.size(), "There should be 1 JSON index");
JsonIndex simple = indexes.get(0);
assertSimpleJson(simple);
}
@@ -155,7 +153,7 @@ public void listSimpleJsonIndex() throws Exception {
public void listComplexJsonIndex() throws Exception {
enqueueList(JSON_COMPLEX);
List indexes = db.listIndexes().jsonIndexes();
- assertEquals("There should be 1 JSON index", 1, indexes.size());
+ assertEquals(1, indexes.size(), "There should be 1 JSON index");
JsonIndex complex = indexes.get(0);
assertComplexJson(complex);
@@ -165,7 +163,7 @@ public void listComplexJsonIndex() throws Exception {
public void listMultipleJsonIndexes() throws Exception {
enqueueList(JSON_SIMPLE, JSON_COMPLEX);
List indexes = db.listIndexes().jsonIndexes();
- assertEquals("There should be 2 JSON indexes", 2, indexes.size());
+ assertEquals(2, indexes.size(), "There should be 2 JSON indexes");
JsonIndex simple = indexes.get(0);
assertSimpleJson(simple);
JsonIndex complex = indexes.get(1);
@@ -176,7 +174,7 @@ public void listMultipleJsonIndexes() throws Exception {
public void listSimpleTextIndex() throws Exception {
enqueueList(TEXT_SIMPLE);
List indexes = db.listIndexes().textIndexes();
- assertEquals("There should be 1 text index", 1, indexes.size());
+ assertEquals(1, indexes.size(), "There should be 1 text index");
TextIndex simple = indexes.get(0);
assertSimpleText(simple);
}
@@ -191,7 +189,7 @@ public void listSimpleTextIndex() throws Exception {
public void listSimpleTextIndexWithSelector() throws Exception {
enqueueList(TEXT_SIMPLE_SELECTOR);
List indexes = db.listIndexes().textIndexes();
- assertEquals("There should be 1 text index", 1, indexes.size());
+ assertEquals(1, indexes.size(), "There should be 1 text index");
TextIndex simple = indexes.get(0);
assertTextIndex(simple, "simpleselector", SELECTOR_STRING, "\"keyword\"", "{}",
Collections.singletonMap
@@ -202,7 +200,7 @@ public void listSimpleTextIndexWithSelector() throws Exception {
public void listComplexTextIndex() throws Exception {
enqueueList(TEXT_COMPLEX);
List indexes = db.listIndexes().textIndexes();
- assertEquals("There should be 1 text index", 1, indexes.size());
+ assertEquals(1, indexes.size(), "There should be 1 text index");
TextIndex complex = indexes.get(0);
assertComplexText(complex);
}
@@ -211,7 +209,7 @@ public void listComplexTextIndex() throws Exception {
public void listMultipleTextIndexes() throws Exception {
enqueueList(TEXT_SIMPLE, TEXT_COMPLEX, TEXT_ALL_FIELDS);
List indexes = db.listIndexes().textIndexes();
- assertEquals("There should be 3 text indexes", 3, indexes.size());
+ assertEquals(3, indexes.size(), "There should be 3 text indexes");
TextIndex simple = indexes.get(0);
assertSimpleText(simple);
TextIndex complex = indexes.get(1);
@@ -226,7 +224,7 @@ public void listAllIndexes() throws Exception {
enqueueList(JSON_SIMPLE, JSON_COMPLEX, TEXT_SIMPLE, TEXT_COMPLEX, TEXT_ALL_FIELDS);
List> indexes = db.listIndexes().allIndexes();
// Note 5 listed here, plus the special index that is always included
- assertEquals("There should be 6 indexes", 6, indexes.size());
+ assertEquals(6, indexes.size(), "There should be 6 indexes");
for (int i = 0; i < indexes.size(); i++) {
String name;
String type;
@@ -235,9 +233,8 @@ public void listAllIndexes() throws Exception {
case 0:
Index a = indexes.get(i);
assertIndex(a, "_all_docs", null, "special", null);
- assertEquals("There should be 1 field", 1, a.getFields().size());
- assertEquals("There field should be called _id", "_id", a.getFields().get(0)
- .getName());
+ assertEquals(1, a.getFields().size(), "There should be 1 field");
+ assertEquals("_id", a.getFields().get(0).getName(), "There field should be called _id");
return;
case 1:
name = "simplejson";
diff --git a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudant.java b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudant.java
index a750a3471..40f1ac768 100644
--- a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudant.java
+++ b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudant.java
@@ -14,8 +14,19 @@
package com.cloudant.test.main;
+import org.junit.jupiter.api.Tag;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
/**
- * JUnit category to label tests which require Cloudant Service or Cloudant Local
+ * JUnit tag to label tests which require Cloudant Service or Cloudant Local
*/
-public class RequiresCloudant extends RequiresDB {
-}
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Tag("RequiresCloudant")
+@Tag("RequiresDB")
+public @interface RequiresCloudant {
+}
\ No newline at end of file
diff --git a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudantLocal.java b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudantLocal.java
index 5a55614d7..bda903355 100644
--- a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudantLocal.java
+++ b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudantLocal.java
@@ -14,8 +14,20 @@
package com.cloudant.test.main;
+import org.junit.jupiter.api.Tag;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
/**
- * JUnit category to label tests which require Cloudant local
+ * JUnit tag to label tests which require Cloudant local
*/
-public class RequiresCloudantLocal extends RequiresCloudant {
-}
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Tag("RequiresCloudantLocal")
+@Tag("RequiresCloudant")
+@Tag("RequiresDB")
+public @interface RequiresCloudantLocal {
+}
\ No newline at end of file
diff --git a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudantService.java b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudantService.java
index b62243347..790964b64 100644
--- a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudantService.java
+++ b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCloudantService.java
@@ -14,8 +14,20 @@
package com.cloudant.test.main;
+import org.junit.jupiter.api.Tag;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
/**
- * JUnit category to label tests which require the Cloudant service
+ * JUnit tag to label tests which require the Cloudant service
*/
-public class RequiresCloudantService extends RequiresCloudant {
-}
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Tag("RequiresCloudantService")
+@Tag("RequiresCloudant")
+@Tag("RequiresDB")
+public @interface RequiresCloudantService {
+}
\ No newline at end of file
diff --git a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCouch.java b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCouch.java
index b36edcec4..6364beb33 100644
--- a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCouch.java
+++ b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresCouch.java
@@ -14,8 +14,19 @@
package com.cloudant.test.main;
+import org.junit.jupiter.api.Tag;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
/**
- * JUnit category to label tests which require a running DB
+ * JUnit tag to label tests which require a running DB
*/
-public class RequiresCouch extends RequiresDB {
-}
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Tag("RequiresCouch")
+@Tag("RequiresDB")
+public @interface RequiresCouch {
+}
\ No newline at end of file
diff --git a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresDB.java b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresDB.java
index b706bd0df..dd69b50f9 100644
--- a/cloudant-client/src/test/java/com/cloudant/test/main/RequiresDB.java
+++ b/cloudant-client/src/test/java/com/cloudant/test/main/RequiresDB.java
@@ -14,8 +14,18 @@
package com.cloudant.test.main;
+import org.junit.jupiter.api.Tag;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
/**
- * JUnit category to label tests which require a running DB
+ * JUnit tag to label tests which require a running DB
*/
-public class RequiresDB {
-}
+@Target({ ElementType.TYPE, ElementType.METHOD })
+@Retention(RetentionPolicy.RUNTIME)
+@Tag("RequiresDB")
+public @interface RequiresDB {
+}
\ No newline at end of file
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java b/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java
index 0207ebac1..b571562f0 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java
@@ -14,32 +14,25 @@
*/
package com.cloudant.tests;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertThat;
-
-import com.cloudant.client.api.Database;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
import com.cloudant.client.api.model.Attachment;
import com.cloudant.client.api.model.Document;
import com.cloudant.client.api.model.Params;
import com.cloudant.client.api.model.Response;
-import com.cloudant.client.internal.DatabaseURIHelper;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.base.TestWithDb;
import com.cloudant.tests.util.Utils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -47,21 +40,8 @@
import java.io.InputStream;
import java.net.URISyntaxException;
-@Category(RequiresDB.class)
-public class AttachmentsTest {
-
- public static CloudantClientResource clientResource = new CloudantClientResource();
- public static DatabaseResource dbResource = new DatabaseResource(clientResource);
- @ClassRule
- public static RuleChain chain = RuleChain.outerRule(clientResource).around(dbResource);
-
- private static Database db;
-
- @BeforeClass
- public static void setUp() {
- db = dbResource.get();
- }
-
+@RequiresDB
+public class AttachmentsTest extends TestWithDb {
@Test
public void attachmentInline() {
@@ -177,9 +157,9 @@ public void addNewAttachmentToExistingDocument() throws Exception {
Response attResponse = db.saveAttachment(bytesIn, "foo.txt", "text/plain", response.getId
(), response.getRev());
- assertEquals("The document ID should be the same", response.getId(), attResponse.getId());
- assertTrue("The response code should be a 20x", attResponse.getStatusCode() / 100 == 2);
- assertNull("There should be no error saving the attachment", attResponse.getError());
+ assertEquals(response.getId(), attResponse.getId(), "The document ID should be the same");
+ assertTrue(attResponse.getStatusCode() / 100 == 2, "The response code should be a 20x");
+ assertNull(attResponse.getError(), "There should be no error saving the attachment");
// Assert the attachment is correct
Document doc = db.find(Document.class, response.getId(), attResponse.getRev());
@@ -227,7 +207,7 @@ public void attachmentStandaloneGivenId() throws IOException, URISyntaxException
// Save the attachment to a doc with the given ID
Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", docId, null);
- assertEquals("The saved document ID should match", docId, response.getId());
+ assertEquals(docId, response.getId(), "The saved document ID should match");
InputStream in = db.getAttachment(docId, "foo.txt");
@@ -241,26 +221,42 @@ public void attachmentStandaloneGivenId() throws IOException, URISyntaxException
}
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void attachmentStandaloneNullDocNonNullRev() {
- byte[] bytesToDB = "binary data".getBytes();
- ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB);
- Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", null, "1-abcdef");
+ assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ byte[] bytesToDB = "binary data".getBytes();
+ ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB);
+ Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", null,
+ "1-abcdef");
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void attachmentStandaloneEmptyDocId() {
- byte[] bytesToDB = "binary data".getBytes();
- ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB);
- Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", "", "1-abcdef");
+ assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ byte[] bytesToDB = "binary data".getBytes();
+ ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB);
+ Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", "", "1-abcdef");
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test()
public void attachmentStandaloneDocIdEmptyRev() {
- String docId = Utils.generateUUID();
- byte[] bytesToDB = "binary data".getBytes();
- ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB);
- Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", docId, "");
+ assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ String docId = Utils.generateUUID();
+ byte[] bytesToDB = "binary data".getBytes();
+ ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesToDB);
+ Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", docId, "");
+ }
+ });
}
@Test
@@ -284,23 +280,38 @@ public void removeAttachment() {
assertNull(bar3.getAttachments());
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void removeAttachmentNullIdNonNullRev() {
- String attachmentName = "txt_1.txt";
- Response response = db.removeAttachment(null, "1-abcdef", attachmentName);
+ assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ String attachmentName = "txt_1.txt";
+ Response response = db.removeAttachment(null, "1-abcdef", attachmentName);
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void removeAttachmentNonNullIdNullRev() {
- String docId = Utils.generateUUID();
- String attachmentName = "txt_1.txt";
- Response response = db.removeAttachment(docId, null, attachmentName);
+ assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ String docId = Utils.generateUUID();
+ String attachmentName = "txt_1.txt";
+ Response response = db.removeAttachment(docId, null, attachmentName);
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void removeAttachmentNonNullIdNonNullRevNullAttachmentName() {
- String docId = Utils.generateUUID();
- String rev = "1-abcdef";
- Response response = db.removeAttachment(docId, rev, null);
+ assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ String docId = Utils.generateUUID();
+ String rev = "1-abcdef";
+ Response response = db.removeAttachment(docId, rev, null);
+ }
+ });
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/BulkDocumentTest.java b/cloudant-client/src/test/java/com/cloudant/tests/BulkDocumentTest.java
index d704f8646..320fdfa1a 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/BulkDocumentTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/BulkDocumentTest.java
@@ -15,39 +15,20 @@
package com.cloudant.tests;
import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static org.hamcrest.MatcherAssert.assertThat;
-import com.cloudant.client.api.Database;
import com.cloudant.client.api.model.Response;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.base.TestWithDb;
import com.google.gson.JsonObject;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
-@Category(RequiresDB.class)
-public class BulkDocumentTest {
-
- public static CloudantClientResource clientResource = new CloudantClientResource();
- public static DatabaseResource dbResource = new DatabaseResource(clientResource);
- @ClassRule
- public static RuleChain chain = RuleChain.outerRule(clientResource).around(dbResource);
-
-
- private static Database db;
-
- @BeforeClass
- public static void setUp() {
- db = dbResource.get();
- }
+@RequiresDB
+public class BulkDocumentTest extends TestWithDb {
@Test
public void bulkModifyDocs() {
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java
index d05687ea1..66cacc378 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java
@@ -15,10 +15,10 @@
package com.cloudant.tests;
import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.cloudant.client.api.Changes;
import com.cloudant.client.api.CloudantClient;
@@ -28,15 +28,13 @@
import com.cloudant.client.api.model.DbInfo;
import com.cloudant.client.api.model.Response;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.base.TestWithDb;
+import com.cloudant.tests.extensions.MockWebServerExtension;
import com.google.gson.JsonObject;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
@@ -45,22 +43,16 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
-@Category(RequiresDB.class)
-public class ChangeNotificationsTest {
+@RequiresDB
+public class ChangeNotificationsTest extends TestWithDb {
- @ClassRule
- public static CloudantClientResource clientResource = new CloudantClientResource();
- @ClassRule
- public static MockWebServer mockWebServer = new MockWebServer();
+ @RegisterExtension
+ public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension();
+ private static MockWebServer mockWebServer;
- @Rule
- public DatabaseResource dbResource = new DatabaseResource(clientResource);
-
- private Database db;
-
- @Before
+ @BeforeEach
public void setup() {
- db = dbResource.get();
+ mockWebServer = mockWebServerExt.get();
}
@Test
@@ -129,9 +121,9 @@ public void changesDescending() throws Exception {
mockWebServer.enqueue(new MockResponse().setBody("{\"results\": []}"));
db.changes().descending(true).getChanges();
RecordedRequest request = mockWebServer.takeRequest(1, TimeUnit.SECONDS);
- assertNotNull("There should be a changes request", request);
- assertTrue("There should be a descending parameter on the request", request.getPath()
- .contains("descending=true"));
+ assertNotNull(request, "There should be a changes request");
+ assertTrue(request.getPath()
+ .contains("descending=true"), "There should be a descending parameter on the request");
}
/**
@@ -146,11 +138,11 @@ public void changesCustomParameter() throws Exception {
mockWebServer.enqueue(new MockResponse().setBody("{\"results\": []}"));
db.changes().filter("myFilter").parameter("myParam", "paramValue").getChanges();
RecordedRequest request = mockWebServer.takeRequest(1, TimeUnit.SECONDS);
- assertNotNull("There should be a changes request", request);
- assertTrue("There should be a filter parameter on the request", request.getPath()
- .contains("filter=myFilter"));
- assertTrue("There should be a custom parameter on the request", request.getPath()
- .contains("myParam=paramValue"));
+ assertNotNull(request, "There should be a changes request");
+ assertTrue(request.getPath()
+ .contains("filter=myFilter"), "There should be a filter parameter on the request");
+ assertTrue(request.getPath()
+ .contains("myParam=paramValue"), "There should be a custom parameter on the request");
}
/**
@@ -189,8 +181,8 @@ public void changesFeedLastSeq() throws Exception {
c.next();
}
RecordedRequest request = mockWebServer.takeRequest(1, TimeUnit.SECONDS);
- assertNotNull("There should be a changes request", request);
- assertEquals("There should be 14 changes", 14, nChanges);
+ assertNotNull(request, "There should be a changes request");
+ assertEquals(14, nChanges, "There should be 14 changes");
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ClientLoadTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ClientLoadTest.java
index 2b03e9975..96fb864d2 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/ClientLoadTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/ClientLoadTest.java
@@ -16,21 +16,19 @@
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
-import com.cloudant.tests.util.TestLog;
+import com.cloudant.tests.extensions.CloudantClientExtension;
+import com.cloudant.tests.extensions.DatabaseExtension;
-import org.junit.After;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-@Ignore
+@Disabled
public class ClientLoadTest {
/**
@@ -38,21 +36,17 @@ public class ClientLoadTest {
*/
private static final int MAX_CONNECTIONS = 20;
- @ClassRule
- public static final TestLog log = new TestLog();
-
- public static CloudantClientResource clientResource = new CloudantClientResource
- (CloudantClientHelper.getClientBuilder()
- .maxConnections(MAX_CONNECTIONS));
- public static DatabaseResource dbResource = new DatabaseResource(clientResource);
- @ClassRule
- public static RuleChain chain = RuleChain.outerRule(clientResource).around(dbResource);
+ @RegisterExtension
+ public static CloudantClientExtension clientResource = new CloudantClientExtension(
+ CloudantClientHelper.getClientBuilder().maxConnections(MAX_CONNECTIONS));
+ @RegisterExtension
+ public static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest(clientResource);
private static CloudantClient dbClient;
private static Database db;
- @BeforeClass
- public static void setUp() {
+ @BeforeEach
+ public void setUp() {
dbClient = clientResource.get();
db = dbResource.get();
}
@@ -61,7 +55,7 @@ public static void setUp() {
private static final int DOCS_PER_THREAD = 10;
- @After
+ @AfterEach
public void tearDown() {
dbClient.shutdown();
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/CloudFoundryServiceTest.java b/cloudant-client/src/test/java/com/cloudant/tests/CloudFoundryServiceTest.java
index c1969ca54..cb28d8df6 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/CloudFoundryServiceTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudFoundryServiceTest.java
@@ -17,7 +17,9 @@
import com.cloudant.client.api.ClientBuilder;
import com.google.gson.GsonBuilder;
-import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
import java.util.ArrayList;
import java.util.HashMap;
@@ -90,22 +92,33 @@ public void vcapValidServiceNameSpecified() {
ClientBuilder.bluemix(vcap.toJson(), serviceName, "test_bluemix_service_1").build().serverVersion();
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapMissingServiceNameSpecified() {
- VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
- vcap.createNewService("test_bluemix_service_1",
- CloudantClientHelper.SERVER_URI_WITH_USER_INFO,
- CloudantClientHelper.COUCH_USERNAME, CloudantClientHelper.COUCH_PASSWORD);
- ClientBuilder.bluemix(vcap.toJson(), "missingService", "test_bluemix_service_1").build();
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
+ vcap.createNewService("test_bluemix_service_1",
+ CloudantClientHelper.SERVER_URI_WITH_USER_INFO,
+ CloudantClientHelper.COUCH_USERNAME, CloudantClientHelper.COUCH_PASSWORD);
+ ClientBuilder.bluemix(vcap.toJson(), "missingService", "test_bluemix_service_1")
+ .build();
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapNullServiceNameSpecified() {
- VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
- vcap.createNewService("test_bluemix_service_1",
- CloudantClientHelper.SERVER_URI_WITH_USER_INFO,
- CloudantClientHelper.COUCH_USERNAME, CloudantClientHelper.COUCH_PASSWORD);
- ClientBuilder.bluemix(vcap.toJson(), null, "test_bluemix_service_1").build();
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
+ vcap.createNewService("test_bluemix_service_1",
+ CloudantClientHelper.SERVER_URI_WITH_USER_INFO,
+ CloudantClientHelper.COUCH_USERNAME, CloudantClientHelper.COUCH_PASSWORD);
+ ClientBuilder.bluemix(vcap.toJson(), null, "test_bluemix_service_1").build();
+ }
+ });
}
@Test
@@ -126,18 +139,29 @@ public void vcapSingleServiceNoNameSpecified() {
ClientBuilder.bluemix(vcap.toJson()).build().serverVersion();
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapSingleServiceMissingNamedService() {
- VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
- vcap.createNewService("test_bluemix_service_1","http://foo1.bar", "admin1", "pass1");
- ClientBuilder.bluemix(vcap.toJson(), "test_bluemix_service_2");
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
+ vcap.createNewService("test_bluemix_service_1", "http://foo1.bar", "admin1",
+ "pass1");
+ ClientBuilder.bluemix(vcap.toJson(), "test_bluemix_service_2");
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapSingleServiceEmptyCredentials() {
- VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
- vcap.createNewServiceWithEmptyCredentials("test_bluemix_service_1");
- ClientBuilder.bluemix(vcap.toJson(), "test_bluemix_service_1");
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
+ vcap.createNewServiceWithEmptyCredentials("test_bluemix_service_1");
+ ClientBuilder.bluemix(vcap.toJson(), "test_bluemix_service_1");
+ }
+ });
}
@Test
@@ -151,45 +175,83 @@ public void vcapMultiService() {
ClientBuilder.bluemix(vcap.toJson(), "test_bluemix_service_3").build().serverVersion();
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapMultiServiceNoNameSpecified() {
- VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
- vcap.createNewService("test_bluemix_service_1","http://foo1.bar", "admin1", "pass1");
- vcap.createNewService("test_bluemix_service_2","http://foo2.bar", "admin2", "pass2");
- vcap.createNewService("test_bluemix_service_3","http://foo3.bar", "admin3", "pass3");
- ClientBuilder.bluemix(vcap.toJson());
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
+ vcap.createNewService("test_bluemix_service_1", "http://foo1.bar", "admin1",
+ "pass1");
+ vcap.createNewService("test_bluemix_service_2", "http://foo2.bar", "admin2",
+ "pass2");
+ vcap.createNewService("test_bluemix_service_3", "http://foo3.bar", "admin3",
+ "pass3");
+ ClientBuilder.bluemix(vcap.toJson());
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapMultiServiceMissingNamedService() {
- VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
- vcap.createNewService("test_bluemix_service_1","http://foo1.bar", "admin1", "pass1");
- vcap.createNewService("test_bluemix_service_2","http://foo2.bar", "admin2", "pass2");
- vcap.createNewService("test_bluemix_service_3","http://foo3.bar", "admin3", "pass3");
- ClientBuilder.bluemix(vcap.toJson(), "test_bluemix_service_4");
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
+ vcap.createNewService("test_bluemix_service_1", "http://foo1.bar", "admin1",
+ "pass1");
+ vcap.createNewService("test_bluemix_service_2", "http://foo2.bar", "admin2",
+ "pass2");
+ vcap.createNewService("test_bluemix_service_3", "http://foo3.bar", "admin3",
+ "pass3");
+ ClientBuilder.bluemix(vcap.toJson(), "test_bluemix_service_4");
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapMultiServiceEmptyCredentials() {
- VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
- vcap.createNewService("test_bluemix_service_1","http://foo1.bar", "admin1", "pass1");
- vcap.createNewService("test_bluemix_service_2","http://foo2.bar", "admin2", "pass2");
- vcap.createNewServiceWithEmptyCredentials("test_bluemix_service_3");
- ClientBuilder.bluemix(vcap.toJson(), "test_bluemix_service_3");
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ VCAPGenerator vcap = new CloudFoundryServiceTest.VCAPGenerator();
+ vcap.createNewService("test_bluemix_service_1", "http://foo1.bar", "admin1",
+ "pass1");
+ vcap.createNewService("test_bluemix_service_2", "http://foo2.bar", "admin2",
+ "pass2");
+ vcap.createNewServiceWithEmptyCredentials("test_bluemix_service_3");
+ ClientBuilder.bluemix(vcap.toJson(), "test_bluemix_service_3");
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapNoServicesPresent() {
- ClientBuilder.bluemix(new CloudFoundryServiceTest.VCAPGenerator().toJson());
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ClientBuilder.bluemix(new CloudFoundryServiceTest.VCAPGenerator().toJson());
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapInvalidJSON() {
- ClientBuilder.bluemix("{\"cloudantNoSQLDB\":[]"); // invalid JSON
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ClientBuilder.bluemix("{\"cloudantNoSQLDB\":[]"); // invalid JSON
+ }
+ });
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void vcapNotPresent() {
- ClientBuilder.bluemix(null);
+ Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ClientBuilder.bluemix(null);
+ }
+ });
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientHelper.java b/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientHelper.java
index 77912c75b..b5cb8a217 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientHelper.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientHelper.java
@@ -16,6 +16,7 @@
import com.cloudant.client.api.ClientBuilder;
import com.cloudant.client.api.CloudantClient;
+
import okhttp3.mockwebserver.MockWebServer;
import java.net.MalformedURLException;
@@ -47,6 +48,8 @@ public abstract class CloudantClientHelper {
try {
//a URL might be supplied, otherwise use the separate properties
String URL = System.getProperty("test.couch.url");
+ System.out.println(System.getProperties());
+ System.out.println("*** using "+URL);
if (URL != null) {
URL couch = new URL(URL);
HTTP_PROTOCOL = couch.getProtocol();
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java b/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java
index ddad4e403..56249cb1f 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java
@@ -15,9 +15,10 @@
package com.cloudant.tests;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.createPost;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.cloudant.client.api.ClientBuilder;
import com.cloudant.client.api.CloudantClient;
@@ -35,16 +36,16 @@
import com.cloudant.test.main.RequiresCloudant;
import com.cloudant.test.main.RequiresCloudantService;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
+import com.cloudant.tests.base.TestWithDb;
+import com.cloudant.tests.extensions.MockWebServerExtension;
import com.cloudant.tests.util.MockWebServerResources;
-import com.cloudant.tests.util.TestLog;
import com.cloudant.tests.util.Utils;
import org.apache.commons.io.IOUtils;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.function.Executable;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
@@ -74,20 +75,20 @@
/**
* Note some tests in this class use Java 1.7 features
*/
-public class CloudantClientTests {
+public class CloudantClientTests extends TestWithDb {
- @ClassRule
- public static final TestLog TEST_LOG = new TestLog();
+ @RegisterExtension
+ public MockWebServerExtension mockWebServerExt = new MockWebServerExtension();
- @ClassRule
- public static CloudantClientResource clientResource = new CloudantClientResource();
- private CloudantClient account = clientResource.get();
+ public MockWebServer server;
- @Rule
- public MockWebServer server = new MockWebServer();
+ @BeforeEach
+ public void beforeEach() {
+ server = mockWebServerExt.get();
+ }
@Test
- @Category(RequiresCloudantService.class)
+ @RequiresCloudantService
public void apiKey() {
ApiKey key = account.generateApiKey();
assertNotNull(key);
@@ -96,14 +97,14 @@ public void apiKey() {
}
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void activeTasks() {
List tasks = account.getActiveTasks();
assertNotNull(tasks);
}
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void membership() {
Membership mship = account.getMembership();
assertNotNull(mship);
@@ -114,7 +115,7 @@ public void membership() {
}
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void cookieTest() {
Membership membership = account.getMembership();
@@ -143,9 +144,7 @@ public void testUserAgentHeaderString() throws Exception {
String userAgentHeader = new UserAgentInterceptor(UserAgentInterceptor.class
.getClassLoader(),
"META-INF/com.cloudant.client.properties").getUserAgent();
- assertTrue("The value of the User-Agent header: " + userAgentHeader + " should match the " +
- "format: " + userAgentFormat,
- userAgentHeader.matches(userAgentUnknownRegex));
+ assertTrue(userAgentHeader.matches(userAgentUnknownRegex), "The value of the User-Agent header: " + userAgentHeader + " should match the " + "format: " + userAgentFormat);
}
@Test
@@ -170,9 +169,7 @@ public InputStream getResourceAsStream(String name) {
return super.getResourceAsStream(name);
}
}, "META-INF/com.cloudant.client.properties").getUserAgent();
- assertTrue("The value of the User-Agent header: " + userAgentHeader + " should match the " +
- "format: " + userAgentFormat,
- userAgentHeader.matches(userAgentRegex));
+ assertTrue(userAgentHeader.matches(userAgentRegex), "The value of the User-Agent header: " + userAgentHeader + " should match the " + "format: " + userAgentFormat);
}
/**
@@ -190,35 +187,37 @@ public void testUserAgentHeaderIsAddedToRequest() throws Exception {
.build();
String response = client.executeRequest(createPost(client.getBaseUri(), null,
"application/json")).responseAsString();
- assertTrue("There should be no response body on the mock response", response.isEmpty());
+ assertTrue(response.isEmpty(), "There should be no response body on the mock response");
//assert that the request had the expected header
String userAgentHeader = server.takeRequest(10, TimeUnit.SECONDS)
.getHeader("User-Agent");
- assertNotNull("The User-Agent header should be present on the request",
- userAgentHeader);
- assertTrue("The value of the User-Agent header " + userAgentHeader + " on the request" +
- " should match the format " + userAgentFormat,
- userAgentHeader.matches(userAgentUnknownRegex));
+ assertNotNull(userAgentHeader, "The User-Agent header should be present on the request");
+ assertTrue(userAgentHeader.matches(userAgentUnknownRegex), "The value of the User-Agent header " + userAgentHeader + " on the request" + " should match the format " + userAgentFormat);
}
/**
* Test a NoDocumentException is thrown when trying an operation on a DB that doesn't exist
*/
- @Test(expected = NoDocumentException.class)
- @Category(RequiresDB.class)
+ @Test
+ @RequiresDB
public void nonExistentDatabaseException() {
- //try and get a DB that doesn't exist
- Database db = account.database("not_really_there", false);
- //try an operation against the non-existant DB
- db.info();
+ assertThrows(NoDocumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ //try and get a DB that doesn't exist
+ Database db = account.database("not_really_there", false);
+ //try an operation against the non-existant DB
+ db.info();
+ }
+ });
}
/**
* Validate that no exception bubbles up when trying to create a DB that already exists
*/
@Test
- @Category(RequiresDB.class)
+ @RequiresDB
public void existingDatabaseCreateException() {
String id = Utils.generateUUID();
String dbName = "existing" + id;
@@ -238,22 +237,28 @@ public void existingDatabaseCreateException() {
* Validate that a PreconditionFailedException is thrown when using the createDB method to
* create a database that already exists.
*/
- @Test(expected = PreconditionFailedException.class)
- @Category(RequiresDB.class)
+ @Test
+ @RequiresDB
public void existingDatabaseCreateDBException() {
- String id = Utils.generateUUID();
- String dbName = "existing" + id;
- try {
- //create a DB for this test
- account.createDB(dbName);
+ assertThrows(PreconditionFailedException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
- //do a get with create true for the already existing DB
- account.createDB(dbName);
+ String id = Utils.generateUUID();
+ String dbName = "existing" + id;
+ try {
+ //create a DB for this test
+ account.createDB(dbName);
- } finally {
- //clean up the DB created by this test
- account.deleteDB(dbName);
- }
+ //do a get with create true for the already existing DB
+ account.createDB(dbName);
+
+ } finally {
+ //clean up the DB created by this test
+ account.deleteDB(dbName);
+ }
+ }
+ });
}
@Test
@@ -262,56 +267,68 @@ public void testDefaultPorts() throws Exception {
c = CloudantClientHelper.newTestAddressClient().build();
- assertEquals("The http port should be 80", 80, c.getBaseUri().getPort());
+ assertEquals(80, c.getBaseUri().getPort(), "The http port should be 80");
c = CloudantClientHelper.newHttpsTestAddressClient().build();
- assertEquals("The http port should be 443", 443, c.getBaseUri().getPort());
+ assertEquals(443, c.getBaseUri().getPort(), "The http port should be 443");
}
/**
* Check that the connection timeout throws a SocketTimeoutException when it can't connect
* within the timeout.
*/
- @Test(expected = SocketTimeoutException.class)
+ @Test
public void connectionTimeout() throws Throwable {
- // Do this test on the loopback
- InetAddress loopback = InetAddress.getLoopbackAddress();
- ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(0, 1,
- loopback);
-
- int port = serverSocket.getLocalPort();
- //block the single connection to our server
- Socket socket = new Socket(loopback.getHostAddress(), port);
-
- //now try to connect, but should timeout because there is no connection available
- try {
- CloudantClient c = ClientBuilder.url(new URL("http", loopback.getHostAddress(), port,
- "")).connectTimeout(100, TimeUnit.MILLISECONDS)
- // Unfortunately openjdk doesn't honour the connect timeout so we set the read
- // timeout as well so that the test doesn't take too long on that platform
- .readTimeout(250, TimeUnit.MILLISECONDS)
- .build();
-
- // Make a request
- c.getAllDbs();
- } catch (CouchDbException e) {
- //unwrap the CouchDbException
- if (e.getCause() != null) {
- //whilst it would be really nice to actually assert that this was a connect
- //exception and not some other SocketTimeoutException there are JVM differences in
- //this respect (i.e. OpenJDK does not appear to distinguish between read/connect)
- //in its exception messages
- throw e.getCause();
- } else {
- throw e;
+ assertThrows(SocketTimeoutException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+
+ // Do this test on the loopback
+ InetAddress loopback = InetAddress.getLoopbackAddress();
+ ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket
+ (0, 1,
+ loopback);
+
+ int port = serverSocket.getLocalPort();
+ //block the single connection to our server
+ Socket socket = new Socket(loopback.getHostAddress(), port);
+
+ //now try to connect, but should timeout because there is no connection available
+ try {
+ CloudantClient c = ClientBuilder.url(new URL("http", loopback.getHostAddress
+ (), port,
+ "")).connectTimeout(100, TimeUnit.MILLISECONDS)
+ // Unfortunately openjdk doesn't honour the connect timeout so we set
+ // the read
+ // timeout as well so that the test doesn't take too long on that
+ // platform
+ .readTimeout(250, TimeUnit.MILLISECONDS)
+ .build();
+
+ // Make a request
+ c.getAllDbs();
+ } catch (CouchDbException e) {
+ //unwrap the CouchDbException
+ if (e.getCause() != null) {
+ //whilst it would be really nice to actually assert that this was a connect
+ //exception and not some other SocketTimeoutException there are JVM
+ // differences in
+ //this respect (i.e. OpenJDK does not appear to distinguish between
+ // read/connect)
+ //in its exception messages
+ throw e.getCause();
+ } else {
+ throw e;
+ }
+ } finally {
+ //make sure we close the sockets
+ IOUtils.closeQuietly(socket);
+ IOUtils.closeQuietly(serverSocket);
+ }
}
- } finally {
- //make sure we close the sockets
- IOUtils.closeQuietly(socket);
- IOUtils.closeQuietly(serverSocket);
- }
+ });
}
/**
@@ -319,48 +336,61 @@ public void connectionTimeout() throws Throwable {
* server thread never sends a response. If things are working
* correctly then the client should see a SocketTimeoutException for the read.
*/
- @Test(expected = SocketTimeoutException.class)
+ @Test
public void readTimeout() throws Throwable {
- // Don't respond so the read will timeout
- server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE));
-
- try {
- CloudantClient c = CloudantClientHelper.newMockWebServerClientBuilder(server)
- .readTimeout(25, TimeUnit.MILLISECONDS).build();
-
- //do a call that expects a response
- c.getAllDbs();
- } catch (CouchDbException e) {
- //unwrap the CouchDbException
- if (e.getCause() != null) {
- throw e.getCause();
- } else {
- throw e;
+ assertThrows(SocketTimeoutException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ // Don't respond so the read will timeout
+ server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE));
+ try {
+ CloudantClient c = CloudantClientHelper.newMockWebServerClientBuilder(server)
+ .readTimeout(25, TimeUnit.MILLISECONDS).build();
+
+ //do a call that expects a response
+ c.getAllDbs();
+ } catch (CouchDbException e) {
+ //unwrap the CouchDbException
+ if (e.getCause() != null) {
+ throw e.getCause();
+ } else {
+ throw e;
+ }
+ }
}
- }
+ });
}
/**
* This tests that a CouchDbException is thrown if the user is null, but the password is
* supplied.
*/
- @Test(expected = CouchDbException.class)
+ @Test
public void nullUser() throws Exception {
- CloudantClientHelper.newTestAddressClient()
- .password(":0-myPassword")
- .build();
-
+ assertThrows(CouchDbException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ CloudantClientHelper.newTestAddressClient()
+ .password(":0-myPassword")
+ .build();
+ }
+ });
}
/**
* This tests that a CouchDbException is thrown if the user is supplied, but the password is
* null.
*/
- @Test(expected = CouchDbException.class)
+ @Test
public void nullPassword() throws Exception {
- CloudantClientHelper.newTestAddressClient()
- .username("user")
- .build();
+ assertThrows(CouchDbException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ CloudantClientHelper.newTestAddressClient()
+ .username("user")
+ .build();
+ }
+ });
}
/**
@@ -422,7 +452,7 @@ private void credentialsCheck(ClientBuilder b, String encodedUser, String encode
assertNotNull(responseStr);
// One request to _session then one to get info
- assertEquals("There should be two requests", 2, server.getRequestCount());
+ assertEquals(2, server.getRequestCount(), "There should be two requests");
// Get the _session request
RecordedRequest request = server.takeRequest();
@@ -430,11 +460,10 @@ private void credentialsCheck(ClientBuilder b, String encodedUser, String encode
// body should be of form:
// name=YourUserName&password=YourPassword
Matcher m = CREDENTIALS.matcher(body);
- assertTrue("The _session request should match the regex", m.matches());
- assertEquals("There should be a username group and a password group in the creds", 2, m
- .groupCount());
- assertEquals("The username should match", encodedUser, m.group(1));
- assertEquals("The password should match", encodedPassword, m.group(2));
+ assertTrue(m.matches(), "The _session request should match the regex");
+ assertEquals(2, m.groupCount(), "There should be a username group and a password group in the creds");
+ assertEquals(encodedUser, m.group(1), "The username should match");
+ assertEquals(encodedPassword, m.group(2), "The password should match");
//ensure that building a URL from it does not throw any exceptions
new URL(c.getBaseUri().toString());
@@ -449,15 +478,15 @@ public void sessionDeleteOnShutdown() throws Exception {
c.shutdown();
RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS);
- assertEquals("The request method should be DELETE", "DELETE", request.getMethod());
- assertEquals("The request should be to the _session path", "/_session", request.getPath());
+ assertEquals("DELETE", request.getMethod(), "The request method should be DELETE");
+ assertEquals("/_session", request.getPath(), "The request should be to the _session path");
}
/**
* Test that adding the Basic Authentication interceptor to CloudantClient works.
*/
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void testBasicAuth() throws IOException {
BasicAuthInterceptor interceptor =
new BasicAuthInterceptor(CloudantClientHelper.COUCH_USERNAME
@@ -527,8 +556,13 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
*
* @throws Exception
*/
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void nullURLThrowsIAE() throws Exception {
- ClientBuilder.url(null);
+ assertThrows(IllegalArgumentException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ClientBuilder.url(null);
+ }
+ });
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ComplexKeySerializationTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ComplexKeySerializationTest.java
index 34adbc5ad..9acc1e354 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/ComplexKeySerializationTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/ComplexKeySerializationTest.java
@@ -14,13 +14,13 @@
package com.cloudant.tests;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import com.cloudant.client.api.views.Key;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
public class ComplexKeySerializationTest {
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/CouchDbUtilTest.java b/cloudant-client/src/test/java/com/cloudant/tests/CouchDbUtilTest.java
index c0a8c02bb..05b7920f6 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/CouchDbUtilTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/CouchDbUtilTest.java
@@ -15,14 +15,14 @@
package com.cloudant.tests;
-import static org.junit.Assert.assertEquals;
import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.jsonToObject;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
public class CouchDbUtilTest {
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DBServerTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DBServerTest.java
index 1019859ef..b9c32291f 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/DBServerTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/DBServerTest.java
@@ -16,42 +16,20 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
-import com.cloudant.client.api.CloudantClient;
-import com.cloudant.client.api.Database;
import com.cloudant.client.api.model.DbInfo;
import com.cloudant.client.api.model.MetaInformation;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.base.TestWithDb;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.Test;
import java.util.List;
-@Category(RequiresDB.class)
-public class DBServerTest {
-
- public static CloudantClientResource clientResource = new CloudantClientResource();
- public static DatabaseResource dbResource = new DatabaseResource(clientResource);
- @ClassRule
- public static RuleChain chain = RuleChain.outerRule(clientResource).around(dbResource);
-
- private static CloudantClient account;
- private static Database db;
-
- @BeforeClass
- public static void setUp() {
- account = clientResource.get();
- db = dbResource.get();
- }
-
+@RequiresDB
+public class DBServerTest extends TestWithDb {
@Test
public void dbInfo() {
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java
index aac8db689..9c67e498a 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java
@@ -14,8 +14,9 @@
package com.cloudant.tests;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
@@ -28,16 +29,16 @@
import com.cloudant.test.main.RequiresCloudantService;
import com.cloudant.test.main.RequiresCouch;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.base.TestWithDb;
+import com.cloudant.tests.extensions.MockWebServerExtension;
import com.cloudant.tests.util.MockWebServerResources;
import com.google.gson.GsonBuilder;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.function.Executable;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
@@ -48,35 +49,32 @@
import java.util.List;
import java.util.Map;
-@Category(RequiresDB.class)
-public class DatabaseTest {
+@RequiresDB
+public class DatabaseTest extends TestWithDb {
- public static CloudantClientResource clientResource = new CloudantClientResource();
- public static DatabaseResource dbResource = new DatabaseResource(clientResource);
+ @RegisterExtension
+ public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension();
- @ClassRule
- public static RuleChain chain = RuleChain.outerRule(clientResource).around(dbResource);
- @ClassRule
- public static MockWebServer mockWebServer = new MockWebServer();
+ private static MockWebServer mockWebServer;
- private static Database db;
- private static CloudantClient account;
-
- @BeforeClass
- public static void setUp() throws Exception {
- account = clientResource.get();
- db = dbResource.get();
+ // TODO before class?
+ @BeforeEach
+ public void beforeEach() {
+ mockWebServer = mockWebServerExt.get();
+ }
+ @BeforeAll
+ public static void beforeAll() throws Exception {
//replicate animaldb for tests
com.cloudant.client.api.Replication r = account.replication();
- r.source("http://clientlibs-test.cloudant.com/animaldb");
+ r.source("https://clientlibs-test.cloudant.com/animaldb");
r.createTarget(true);
r.target(dbResource.getDbURIWithUserInfo());
r.trigger();
}
@Test
- @Category(RequiresCloudantService.class)
+ @RequiresCloudantService
public void permissions() {
Map> userPerms = db.getPermissions();
assertNotNull(userPerms);
@@ -100,10 +98,15 @@ public void permissions() {
* Test that when called against a DB that is not a Cloudant service
* an UnsupportedOperationException is thrown
*/
- @Test(expected = UnsupportedOperationException.class)
- @Category({RequiresCouch.class, RequiresCloudantLocal.class})
+ @RequiresCouch
+ @RequiresCloudantLocal
public void testPermissionsException() {
- Map> userPerms = db.getPermissions();
+ assertThrows(UnsupportedOperationException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ Map> userPerms = db.getPermissions();
+ }
+ });
}
@Test
@@ -126,13 +129,13 @@ public void permissionsParsing() throws Exception {
try {
db.setPermissions("testUsername", EnumSet.allOf(Permissions.class));
} catch (CouchDbException e) {
- assertEquals("", testError, e.getError());
- assertEquals("", testReason, e.getReason());
+ assertEquals(testError, e.getError());
+ assertEquals(testReason, e.getReason());
}
}
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void shards() {
List shards = db.getShards();
assert (shards.size() > 0);
@@ -144,7 +147,7 @@ public void shards() {
}
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void shard() {
Shard s = db.getShard("snipe");
assertNotNull(s);
@@ -155,7 +158,7 @@ public void shard() {
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void QuorumTests() {
db.save(new Animal("human"), 2);
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseURIHelperTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseURIHelperTest.java
index d4cf83f95..e2d29c786 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseURIHelperTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseURIHelperTest.java
@@ -17,39 +17,82 @@
import com.cloudant.client.internal.DatabaseURIHelper;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.Extension;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.Arrays;
-import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import java.util.stream.Stream;
-@RunWith(Parameterized.class)
+@ExtendWith(DatabaseURIHelperTest.ParameterProvider.class)
public class DatabaseURIHelperTest {
- @Parameterized.Parameters
- public static Collection
*/
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
@Test
public void testJsonErrorStreamFromLB() throws Exception {
final AtomicBoolean badHeaderEnabled = new AtomicBoolean(false);
@@ -165,7 +155,7 @@ public void testJsonErrorStreamFromLB() throws Exception {
// Make a good request, which will set up the session etc
HttpConnection d = c.executeRequest(Http.GET(c.getBaseUri()));
d.responseAsString();
- assertTrue("The first request should succeed", d.getConnection().getResponseCode() / 100 == 2);
+ assertTrue(d.getConnection().getResponseCode() / 100 == 2, "The first request should succeed");
// Enable the bad headers and expect the exception on the next request
badHeaderEnabled.set(true);
@@ -174,10 +164,10 @@ public void testJsonErrorStreamFromLB() throws Exception {
} catch (CouchDbException e) {
//we expect a CouchDbException
- assertEquals("The exception should be for a bad request", 400, e.getStatusCode());
+ assertEquals(400, e.getStatusCode(), "The exception should be for a bad request");
- assertNotNull("The exception should have an error set", e.getError());
- assertEquals("The exception error should be bad request", "bad_request", e.getError());
+ assertNotNull(e.getError(), "The exception should have an error set");
+ assertEquals("bad_request", e.getError(), "The exception error should be bad request");
} finally {
// Disable the bad header to allow a clean shutdown
badHeaderEnabled.set(false);
@@ -195,16 +185,15 @@ public void testJsonErrorStreamFromLB() throws Exception {
*/
private void exceptionAsserts(CouchDbException e, int expectedCode, String expectedReason) {
assertExceptionStatusCode(e, expectedCode);
- assertNotNull("The error should not be null", e.getError());
+ assertNotNull(e.getError(), "The error should not be null");
if ("".equals(expectedReason)) {
- assertNotNull("The reason should not be null", e.getReason());
+ assertNotNull(e.getReason(), "The reason should not be null");
} else {
- assertEquals("The reason should be " + expectedReason, expectedReason, e.getReason());
+ assertEquals(expectedReason, e.getReason(), "The reason should be " + expectedReason);
}
}
private void assertExceptionStatusCode(CouchDbException e, int expectedCode) {
- assertEquals("The HTTP status code should be " + expectedCode, expectedCode, e
- .getStatusCode());
+ assertEquals(expectedCode, e.getStatusCode(), "The HTTP status code should be " + expectedCode);
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java b/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java
index d8f1ddfda..5fb63e1f0 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java
@@ -14,12 +14,10 @@
package com.cloudant.tests;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
-import com.cloudant.client.api.CloudantClient;
-import com.cloudant.client.api.Database;
import com.cloudant.client.api.DesignDocumentManager;
import com.cloudant.client.api.Search;
import com.cloudant.client.api.model.DesignDocument;
@@ -27,14 +25,10 @@
import com.cloudant.client.api.model.SearchResult.SearchResultRow;
import com.cloudant.client.internal.URIBase;
import com.cloudant.test.main.RequiresCloudant;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.base.TestWithDb;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
import java.io.File;
import java.net.URI;
@@ -45,24 +39,12 @@
import java.util.Map;
import java.util.Map.Entry;
-@Category(RequiresCloudant.class)
-public class SearchTests {
+@RequiresCloudant
+public class SearchTests extends TestWithDb {
- public static CloudantClientResource clientResource = new CloudantClientResource();
- public static DatabaseResource dbResource = new DatabaseResource(clientResource);
-
- @ClassRule
- public static RuleChain chain = RuleChain.outerRule(clientResource).around(dbResource);
-
- private static Database db;
- private static CloudantClient account;
-
- @BeforeClass
+ @BeforeAll
public static void setUp() throws Exception {
- account = clientResource.get();
- db = dbResource.get();
-
- // replciate the animals db for search tests
+ // replicate the animals db for search tests
com.cloudant.client.api.Replication r = account.replication();
r.source("https://clientlibs-test.cloudant.com/animaldb");
r.createTarget(true);
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/SessionInterceptorExpiryTests.java b/cloudant-client/src/test/java/com/cloudant/tests/SessionInterceptorExpiryTests.java
index 243e333c5..6f8f807c3 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/SessionInterceptorExpiryTests.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/SessionInterceptorExpiryTests.java
@@ -14,11 +14,11 @@
package com.cloudant.tests;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import com.cloudant.http.Http;
import com.cloudant.http.HttpConnection;
@@ -30,45 +30,97 @@
import com.cloudant.http.internal.ok.OkHttpClientHttpUrlConnectionFactory;
import com.cloudant.tests.util.HttpFactoryParameterizedTest;
import com.cloudant.tests.util.IamSystemPropertyMock;
+import com.cloudant.tests.extensions.MockWebServerExtension;
import com.cloudant.tests.util.MockWebServerResources;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runners.Parameterized;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.Extension;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
-import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
+@ExtendWith(SessionInterceptorExpiryTests.ParameterProvider.class)
public class SessionInterceptorExpiryTests extends HttpFactoryParameterizedTest {
- public static IamSystemPropertyMock iamSystemPropertyMock;
+ static class ParameterProvider implements TestTemplateInvocationContextProvider {
+ @Override
+ public boolean supportsTestTemplate(ExtensionContext context) {
+ return true;
+ }
- @Parameterized.Parameters(name = "Using okhttp: {0} for session path {1}")
- public static List testParams() {
- List tests = new ArrayList(4);
- tests.add(new Object[]{false, "/_session"});
- tests.add(new Object[]{true, "/_session"});
- tests.add(new Object[]{false, "/_iam_session"});
- tests.add(new Object[]{true, "/_iam_session"});
- return tests;
- }
+ @Override
+ public Stream provideTestTemplateInvocationContexts(ExtensionContext context) {
+ return Stream.of(invocationContext(false,"/_iam_session"),
+ invocationContext(false, "/_session"),
+ invocationContext(true, "/_iam_session"),
+ invocationContext(true,"/_session"));
+ }
- // Note Parameter(0) okUsable is inherited
+ // because we fill in the args from the left, we can fill in the single argument "okUsable"
+ // for the parent class' (HttpFactoryParameterizedTest) @BeforeEach
+ public static TestTemplateInvocationContext invocationContext(final boolean okUsable,
+ final String sessionPath) {
+ return new TestTemplateInvocationContext() {
+ @Override
+ public String getDisplayName(int invocationIndex) {
+ return String.format("path:%s,okUsable:%s", sessionPath, okUsable);
+ }
+
+ @Override
+ public List getAdditionalExtensions() {
+ return Collections.singletonList(new ParameterResolver() {
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext,
+ ExtensionContext extensionContext) {
+ switch(parameterContext.getIndex()) {
+ case 0:
+ return parameterContext.getParameter().getType().equals(boolean.class);
+ case 1:
+ return parameterContext.getParameter().getType().equals(String.class);
+ }
+ return false;
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext,
+ ExtensionContext extensionContext) {
+ switch(parameterContext.getIndex()) {
+ case 0:
+ return okUsable;
+ case 1:
+ return sessionPath;
+ }
+ return null;
+ }
+ });
+ }
+ };
+ }
+ }
- @Parameterized.Parameter(1)
- public String sessionPath;
+ public static IamSystemPropertyMock iamSystemPropertyMock;
- @Rule
- public MockWebServer mockWebServer = new MockWebServer();
- @Rule
- public MockWebServer mockIamServer = new MockWebServer();
+ @RegisterExtension
+ public MockWebServerExtension mockWebServerExt = new MockWebServerExtension();
+ public MockWebServer mockWebServer;
+ @RegisterExtension
+ public MockWebServerExtension mockIamServerExt = new MockWebServerExtension();
+ public MockWebServer mockIamServer;
private HttpConnectionRequestInterceptor rqInterceptor;
private HttpConnectionResponseInterceptor rpInterceptor;
@@ -76,13 +128,16 @@ public static List testParams() {
/**
* Before running this test class setup the property mock.
*/
- @BeforeClass
+ @BeforeAll
public static void setupIamSystemPropertyMock() {
iamSystemPropertyMock = new IamSystemPropertyMock();
}
- @Before
- public void setupSessionInterceptor() {
+ @BeforeEach
+ public void setupSessionInterceptor(boolean okUsable, String sessionPath) {
+ this.mockWebServer = mockWebServerExt.get();
+ this.mockIamServer = mockIamServerExt.get();
+
String baseUrl = mockWebServer.url("").toString();
if (sessionPath.equals("/_session")) {
@@ -102,7 +157,10 @@ public void setupSessionInterceptor() {
}
- private void queueResponses(Long expiry, String cookieValue) {
+ private void queueResponses(boolean okUsable,
+ String sessionPath,
+ Long expiry,
+ String cookieValue) {
// Queue up the session response
String cookieString;
if (sessionPath.equals("/_session")) {
@@ -123,10 +181,13 @@ private void queueResponses(Long expiry, String cookieValue) {
mockWebServer.enqueue(MockWebServerResources.JSON_OK);
}
- private void executeTest(Long expiryTime, String cookieValue) throws Exception {
- queueResponses(expiryTime, cookieValue);
+ private void executeTest(boolean okUsable,
+ String sessionPath,
+ Long expiryTime,
+ String cookieValue) throws Exception {
+ queueResponses(okUsable, sessionPath, expiryTime, cookieValue);
HttpConnection conn = Http.GET(mockWebServer.url("/").url());
- conn.connectionFactory = (okUsable) ? new OkHttpClientHttpUrlConnectionFactory() :
+ conn.connectionFactory = (isOkUsable) ? new OkHttpClientHttpUrlConnectionFactory() :
new DefaultHttpUrlConnectionFactory();
conn.requestInterceptors.add(rqInterceptor);
conn.responseInterceptors.add(rpInterceptor);
@@ -135,36 +196,32 @@ private void executeTest(Long expiryTime, String cookieValue) throws Exception {
// Consume response stream and assert ok: true
String responseStr = conn.responseAsString();
String okPattern = ".*\"ok\"\\s*:\\s*true.*";
- assertTrue("There should be an ok response: " + responseStr, Pattern.compile(okPattern,
- Pattern.DOTALL).matcher(responseStr).matches());
+ assertTrue(Pattern.compile(okPattern, Pattern.DOTALL).matcher(responseStr).matches(), "There should be an ok response: " + responseStr);
// Assert the _session request
RecordedRequest sessionRequest = mockWebServer.takeRequest(MockWebServerResources
.TIMEOUT, MockWebServerResources.TIMEOUT_UNIT);
- assertEquals("The interceptor should make a session request", sessionPath,
- sessionRequest.getPath());
- assertNull("There should be no existing cookie on the session request", sessionRequest
- .getHeader("Cookie"));
+ assertEquals(sessionPath, sessionRequest.getPath(), "The interceptor should make a session request");
+ assertNull(sessionRequest.getHeader("Cookie"), "There should be no existing cookie on the session request");
// Assert the GET request
RecordedRequest getRequest = mockWebServer.takeRequest(MockWebServerResources.TIMEOUT,
MockWebServerResources.TIMEOUT_UNIT);
- assertEquals("The request path should be correct", "/", getRequest.getPath());
- assertNotNull("There should be a cookie on the request", getRequest.getHeader("Cookie"));
+ assertEquals("/", getRequest.getPath(), "The request path should be correct");
+ assertNotNull(getRequest.getHeader("Cookie"), "There should be a cookie on the request");
String expectedCookie = ((sessionPath.equals("/_session")) ? MockWebServerResources.AUTH_COOKIE_NAME :
MockWebServerResources.IAM_COOKIE_NAME) + "=" + cookieValue;
- assertEquals("The cookie should be the correct session type", expectedCookie,
- getRequest.getHeader("Cookie"));
+ assertEquals(expectedCookie, getRequest.getHeader("Cookie"), "The cookie should be the correct session type");
}
/**
* Test the non-expiry case just to validate that things work normally
* @throws Exception
*/
- @Test
- public void testMakesCookieRequest() throws Exception {
- executeTest(null, MockWebServerResources.EXPECTED_OK_COOKIE);
+ @TestTemplate
+ public void testMakesCookieRequest(boolean okUsable, String sessionPath) throws Exception {
+ executeTest(okUsable, sessionPath, null, MockWebServerResources.EXPECTED_OK_COOKIE);
}
/**
@@ -174,10 +231,10 @@ public void testMakesCookieRequest() throws Exception {
*
* @throws Exception
*/
- @Test
- public void testNewCookieRequestMadeAfterExpiry() throws Exception {
+ @TestTemplate
+ public void testNewCookieRequestMadeAfterExpiry(boolean okUsable, String sessionPath) throws Exception {
// Make a GET request and get a cookie valid for 2 seconds
- executeTest(System.currentTimeMillis() + 2000, MockWebServerResources.EXPECTED_OK_COOKIE);
+ executeTest(okUsable, sessionPath, System.currentTimeMillis() + 2000, MockWebServerResources.EXPECTED_OK_COOKIE);
// Sleep 2 seconds and make another request
// Note 1 second appears to be insufficient probably due to rounding to the nearest second
@@ -186,7 +243,7 @@ public void testNewCookieRequestMadeAfterExpiry() throws Exception {
// Since the Cookie is expired it should follow the same sequence of POST /_session GET /
// If the expired Cookie was retrieved it would only do GET / and the test would fail.
- executeTest(null, MockWebServerResources.EXPECTED_OK_COOKIE_2);
+ executeTest(okUsable, sessionPath, null, MockWebServerResources.EXPECTED_OK_COOKIE_2);
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/SslAuthenticationTest.java b/cloudant-client/src/test/java/com/cloudant/tests/SslAuthenticationTest.java
index 40651323e..c6e4c7200 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/SslAuthenticationTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/SslAuthenticationTest.java
@@ -14,46 +14,99 @@
package com.cloudant.tests;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.org.lightcouch.CouchDbException;
import com.cloudant.test.main.RequiresCloudantService;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.MockWebServerResources;
import com.cloudant.tests.util.HttpFactoryParameterizedTest;
+import com.cloudant.tests.extensions.MockWebServerExtension;
+import com.cloudant.tests.util.MockWebServerResources;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.runners.Parameterized;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.Extension;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
+import org.junit.jupiter.api.function.Executable;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Stream;
+
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
+@ExtendWith(SslAuthenticationTest.ParameterProvider.class)
public class SslAuthenticationTest extends HttpFactoryParameterizedTest {
- @ClassRule
- public static CloudantClientResource dbClientResource = new CloudantClientResource();
- private static CloudantClient dbClient = dbClientResource.get();
+ static class ParameterProvider implements TestTemplateInvocationContextProvider {
+ @Override
+ public boolean supportsTestTemplate(ExtensionContext context) {
+ return true;
+ }
- @Rule
- public MockWebServer server = new MockWebServer();
+ @Override
+ public Stream provideTestTemplateInvocationContexts(ExtensionContext context) {
+ return Stream.of(invocationContext(false),
+ invocationContext(true));
+ }
- @Parameterized.Parameters(name = "Using okhttp: {0}")
- public static Object[] okUsable() {
- return new Object[]{true, false};
+ public static TestTemplateInvocationContext invocationContext(final boolean okUsable) {
+ return new TestTemplateInvocationContext() {
+ @Override
+ public String getDisplayName(int invocationIndex) {
+ return String.format("okUsable:%s", okUsable);
+ }
+
+ @Override
+ public List getAdditionalExtensions() {
+ return Collections.singletonList(new ParameterResolver() {
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext,
+ ExtensionContext extensionContext) {
+ switch(parameterContext.getIndex()) {
+ case 0:
+ return parameterContext.getParameter().getType().equals(boolean.class);
+ }
+ return false;
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext,
+ ExtensionContext extensionContext) {
+ switch(parameterContext.getIndex()) {
+ case 0:
+ return okUsable;
+ }
+ return null;
+ }
+ });
+ }
+ };
+ }
}
- @Before
- public void getMockWebServer() {
+
+ @RegisterExtension
+ public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension();
+
+ protected MockWebServer server;
+
+ @BeforeEach
+ public void beforeEach() {
+ server = mockWebServerExt.get();
server.useHttps(MockWebServerResources.getSSLSocketFactory(), false);
}
@@ -64,21 +117,19 @@ public void getMockWebServer() {
* @param e the exception.
*/
private static void validateClientAuthenticationException(CouchDbException e) {
- assertNotNull("Expected CouchDbException but got null", e);
+ assertNotNull(e, "Expected CouchDbException but got null");
Throwable t = e.getCause();
- assertTrue("Expected SSLHandshakeException caused by client certificate check but got " +
- t.getClass(),
- t instanceof SSLHandshakeException);
+ assertTrue(t instanceof SSLHandshakeException, "Expected SSLHandshakeException caused by client certificate check but got " + t.getClass());
}
/**
* Connect to the local simple https server with SSL authentication disabled.
*/
- @Test
+ @TestTemplate
public void localSslAuthenticationDisabled() throws Exception {
// Build a client that connects to the mock server with SSL authentication disabled
- dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server)
+ CloudantClient dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server)
.disableSSLAuthentication()
.build();
@@ -95,12 +146,12 @@ public void localSslAuthenticationDisabled() throws Exception {
* Connect to the local simple https server with SSL authentication enabled explicitly.
* This should throw an exception because the SSL authentication fails.
*/
- @Test
+ @TestTemplate
public void localSslAuthenticationEnabled() throws Exception {
CouchDbException thrownException = null;
try {
- dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server)
+ CloudantClient dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server)
.build();
// Queue a 200 OK response
@@ -119,11 +170,11 @@ public void localSslAuthenticationEnabled() throws Exception {
* This shouldn't throw an exception as the Cloudant server has a valid
* SSL certificate, so should be authenticated.
*/
- @Test
- @Category(RequiresCloudantService.class)
+ @TestTemplate
+ @RequiresCloudantService
public void remoteSslAuthenticationEnabledTest() {
- dbClient = CloudantClientHelper.getClientBuilder().build();
+ CloudantClient dbClient = CloudantClientHelper.getClientBuilder().build();
// Make an arbitrary connection to the DB.
dbClient.getAllDbs();
@@ -134,11 +185,11 @@ public void remoteSslAuthenticationEnabledTest() {
/**
* Connect to the remote Cloudant server with SSL Authentication disabled.
*/
- @Test
- @Category(RequiresCloudantService.class)
+ @TestTemplate
+ @RequiresCloudantService
public void remoteSslAuthenticationDisabledTest() {
- dbClient = CloudantClientHelper.getClientBuilder()
+ CloudantClient dbClient = CloudantClientHelper.getClientBuilder()
.disableSSLAuthentication()
.build();
@@ -152,34 +203,44 @@ public void remoteSslAuthenticationDisabledTest() {
* Assert that building a client with a custom SSL factory first, then setting the
* SSL Authentication disabled will throw an IllegalStateException.
*/
- @Test(expected = IllegalStateException.class)
+ @TestTemplate
public void testCustomSSLFactorySSLAuthDisabled() {
+ assertThrows(IllegalStateException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
- dbClient = CloudantClientHelper.getClientBuilder()
- .customSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault())
- .disableSSLAuthentication()
- .build();
+ CloudantClient dbClient = CloudantClientHelper.getClientBuilder()
+ .customSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault())
+
+ .disableSSLAuthentication()
+ .build();
+ }
+ });
}
/**
* Assert that building a client with SSL Authentication disabled first, then setting
* a custom SSL factory will throw an IllegalStateException.
*/
- @Test(expected = IllegalStateException.class)
+ @TestTemplate
public void testSSLAuthDisabledWithCustomSSLFactory() {
-
- dbClient = CloudantClientHelper.getClientBuilder()
- .disableSSLAuthentication()
- .customSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault())
- .build();
-
+ assertThrows(IllegalStateException.class, new Executable() {
+ @Override
+ public void execute() throws Throwable {
+
+ CloudantClient dbClient = CloudantClientHelper.getClientBuilder()
+ .disableSSLAuthentication()
+ .customSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault())
+ .build();
+ }
+ });
}
/**
* Repeat the localSSLAuthenticationDisabled, but with the cookie auth enabled.
* This test validates that the SSL settings also get applied to the cookie interceptor.
*/
- @Test
+ @TestTemplate
public void localSSLAuthenticationDisabledWithCookieAuth() throws Exception {
// Mock up an OK cookie response then an OK response for the getAllDbs()
@@ -187,7 +248,7 @@ public void localSSLAuthenticationDisabledWithCookieAuth() throws Exception {
server.enqueue(new MockResponse()); //OK 200
// Use a username and password to enable the cookie auth interceptor
- dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server).username("user")
+ CloudantClient dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server).username("user")
.password("password")
.disableSSLAuthentication()
.build();
@@ -199,7 +260,7 @@ public void localSSLAuthenticationDisabledWithCookieAuth() throws Exception {
* Repeat the localSSLAuthenticationEnabled, but with the cookie auth enabled.
* This test validates that the SSL settings also get applied to the cookie interceptor.
*/
- @Test
+ @TestTemplate
public void localSSLAuthenticationEnabledWithCookieAuth() throws Exception {
// Mock up an OK cookie response then an OK response for the getAllDbs()
@@ -207,7 +268,7 @@ public void localSSLAuthenticationEnabledWithCookieAuth() throws Exception {
server.enqueue(new MockResponse()); //OK 200
// Use a username and password to enable the cookie auth interceptor
- dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server).username("user")
+ CloudantClient dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server).username("user")
.password("password")
.build();
@@ -218,5 +279,6 @@ public void localSSLAuthenticationEnabledWithCookieAuth() throws Exception {
validateClientAuthenticationException(e);
}
}
+
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/URIBaseTest.java b/cloudant-client/src/test/java/com/cloudant/tests/URIBaseTest.java
index 5b6338ce8..ae920be27 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/URIBaseTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/URIBaseTest.java
@@ -4,33 +4,32 @@
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.internal.URIBase;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
+import com.cloudant.tests.extensions.CloudantClientExtension;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
import java.net.URI;
-@Category(RequiresDB.class)
+@RequiresDB
public class URIBaseTest {
- @ClassRule
- public static CloudantClientResource clientResource = new CloudantClientResource();
+ @RegisterExtension
+ public static CloudantClientExtension clientResource = new CloudantClientExtension();
@Test
public void buildAccountUri_noTrailingPathSeparator() throws Exception {
CloudantClient client = ClientBuilder.url(clientResource.get().getBaseUri().toURL())
.build();
- Assert.assertFalse(client.getBaseUri().toString().endsWith("/"));
+ Assertions.assertFalse(client.getBaseUri().toString().endsWith("/"));
URI clientUri = new URIBase(client.getBaseUri()).build();
- Assert.assertFalse(clientUri.toString().endsWith("/"));
+ Assertions.assertFalse(clientUri.toString().endsWith("/"));
//Check that path is not missing / separators
clientUri = new URIBase(client.getBaseUri()).path("").path("api").path("couch").build();
URI expectedAccountUri = new URI(clientResource.get().getBaseUri().toString()
+ "/api/couch");
- Assert.assertEquals(expectedAccountUri, clientUri);
+ Assertions.assertEquals(expectedAccountUri, clientUri);
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java b/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java
index c328e8b85..4b5c7ffc1 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java
@@ -14,11 +14,8 @@
package com.cloudant.tests;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
-import com.cloudant.client.api.Database;
-import com.cloudant.client.api.query.Field;
-import com.cloudant.client.api.query.Index;
import com.cloudant.client.api.query.JsonIndex;
import com.cloudant.client.api.views.Key;
import com.cloudant.client.internal.DatabaseURIHelper;
@@ -26,18 +23,12 @@
import com.cloudant.http.HttpConnection;
import com.cloudant.test.main.RequiresCloudant;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
-import com.cloudant.tests.util.TestLog;
+import com.cloudant.tests.base.TestWithDbPerTest;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.io.IOUtils;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
+import org.junit.jupiter.api.Test;
import java.io.BufferedInputStream;
import java.io.IOException;
@@ -52,15 +43,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-@Category(RequiresDB.class)
-public class UnicodeTest {
-
- @ClassRule
- public static final TestLog TEST_LOG = new TestLog();
- @ClassRule
- public static final CloudantClientResource clientResource = new CloudantClientResource();
- @Rule
- public final DatabaseResource dbResource = new DatabaseResource(clientResource);
+@RequiresDB
+public class UnicodeTest extends TestWithDbPerTest {
// According to JSON (ECMA-404, section 9 "Strings"):
// - All Unicode characters except those that must be escaped
@@ -70,13 +54,10 @@ public class UnicodeTest {
private static final String TESTSTRING = "Gr\u00fc\u00dfe \u65e5\u672c\u8a9e \uD834\uDD1E.";
private static final String TESTSTRING_ESCAPED = "Gr\\u00fc\\u00dfe \\u65e5\\u672c\\u8a9e " +
"\\uD834\\uDD1E.";
-
- private Database db;
-
- @Before
- public void setup() {
- db = dbResource.get();
- }
+ private static final String TESTJSON = "{\"_id\":\"literal\"," +
+ "\"_rev\":\"1-39933759c7250133b6039d94ea09134f\",\"foo\":\"Grüße 日本語 \uD834\uDD1E.\"}\n";
+ private static final String TESTJSON_ESCAPED = "{\"_id\":\"escaped\"," +
+ "\"_rev\":\"1-39933759c7250133b6039d94ea09134f\",\"foo\":\"Grüße 日本語 \uD834\uDD1E.\"}\n";
// ========================================================================
// REST request utilities.
@@ -277,7 +258,7 @@ public void testLiteralUnicode() throws Exception {
clientResource.get().executeRequest(conn);
assertEquals(200, conn.getConnection().getResponseCode());
String result = getPlainTextEntityAsString(conn, uri);
- TEST_LOG.logger.info("testLiteralUnicode: Result as returned in entity: " + result);
+ assertEquals(TESTJSON, result);
closeResponse(conn);
}
{
@@ -312,7 +293,7 @@ public void testEscapedUnicode() throws Exception {
clientResource.get().executeRequest(conn);
assertEquals(200, conn.getConnection().getResponseCode());
String result = getPlainTextEntityAsString(conn, uri);
- TEST_LOG.logger.info("testEscapedUnicode: Result as returned in entity: " + result);
+ assertEquals(TESTJSON_ESCAPED, result);
closeResponse(conn);
}
{
@@ -338,7 +319,7 @@ public static class MyObject {
// To reproduce: In Eclipse, use "Run > Run Configurations...", tab "Common",
// panel "Encoding", set the encoding to ISO-8859-1.
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void testUnicodeInObject() throws Exception {
db.createIndex(JsonIndex.builder()
.name("myview")
@@ -346,10 +327,6 @@ public void testUnicodeInObject() throws Exception {
.asc("foo")
.definition());
- // Show the indices.
- for (Index index : db.listIndexes().allIndexes()) {
- TEST_LOG.logger.info(index.toString());
- }
// Create an object.
MyObject object = new MyObject();
object.foo = TESTSTRING;
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/UpdateHandlerTest.java b/cloudant-client/src/test/java/com/cloudant/tests/UpdateHandlerTest.java
index 890a714cd..f2cc4820d 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/UpdateHandlerTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/UpdateHandlerTest.java
@@ -14,37 +14,24 @@
*/
package com.cloudant.tests;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
-import com.cloudant.client.api.Database;
import com.cloudant.client.api.model.Params;
import com.cloudant.client.api.model.Response;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.base.TestWithDb;
import com.cloudant.tests.util.Utils;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
-@Category(RequiresDB.class)
-public class UpdateHandlerTest {
+@RequiresDB
+public class UpdateHandlerTest extends TestWithDb {
- public static CloudantClientResource clientResource = new CloudantClientResource();
- public static DatabaseResource dbResource = new DatabaseResource(clientResource);
- @ClassRule
- public static RuleChain chain = RuleChain.outerRule(clientResource).around(dbResource);
-
- private static Database db;
-
- @BeforeClass
- public static void setUp() throws Exception {
- db = dbResource.get();
+ @BeforeAll
+ public static void beforeAll() throws Exception {
Utils.putDesignDocs(db);
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ViewPaginationTests.java b/cloudant-client/src/test/java/com/cloudant/tests/ViewPaginationTests.java
index 7275d6091..2b3f75ff5 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/ViewPaginationTests.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/ViewPaginationTests.java
@@ -14,68 +14,109 @@
package com.cloudant.tests;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
-import com.cloudant.client.api.Database;
import com.cloudant.client.api.views.Key;
import com.cloudant.client.api.views.ViewRequest;
import com.cloudant.client.api.views.ViewResponse;
+import com.cloudant.test.main.RequiresDB;
+import com.cloudant.tests.base.TestWithDbPerTest;
import com.cloudant.tests.util.CheckPagination;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
import com.cloudant.tests.util.Utils;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.Extension;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.ParameterContext;
+import org.junit.jupiter.api.extension.ParameterResolver;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
+import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
-@RunWith(Parameterized.class)
-public class ViewPaginationTests {
+@RequiresDB
+@ExtendWith(ViewPaginationTests.ParameterProvider.class)
+public class ViewPaginationTests extends TestWithDbPerTest {
- /**
- * Parameters for these tests so we run each test multiple times.
- * We run with a single key or a complex key and both ascending and descending.
- */
- @Parameterized.Parameters(name = "Key:{0},Descending:{1},Stateless:{2}")
- public static Iterable data() {
+ static class ParameterProvider implements TestTemplateInvocationContextProvider {
+ @Override
+ public boolean supportsTestTemplate(ExtensionContext context) {
+ return true;
+ }
+
+ @Override
+ public Stream provideTestTemplateInvocationContexts(ExtensionContext context) {
+ return StreamSupport.stream(data().spliterator(), false);
+ }
+
+ public static TestTemplateInvocationContext invocationContext(final CheckPagination.Type type,
+ final boolean descending,
+ final boolean stateless) {
+ return new TestTemplateInvocationContext() {
+ @Override
+ public String getDisplayName(int invocationIndex) {
+ return String.format("Key:%s,Descending:%s,Stateless:%s", type,
+ descending, stateless);
+ }
+
+ @Override
+ public List getAdditionalExtensions() {
+ return Collections.singletonList(new ParameterResolver() {
+ @Override
+ public boolean supportsParameter(ParameterContext parameterContext,
+ ExtensionContext extensionContext) {
+ switch(parameterContext.getIndex()) {
+ case 0:
+ return parameterContext.getParameter().getType().equals(CheckPagination.Type.class);
+ case 1:
+ return parameterContext.getParameter().getType().equals(boolean.class);
+ case 2:
+ return parameterContext.getParameter().getType().equals(boolean.class);
+ }
+ return false;
+ }
+
+ @Override
+ public Object resolveParameter(ParameterContext parameterContext,
+ ExtensionContext extensionContext) {
+ switch(parameterContext.getIndex()) {
+ case 0:
+ return type;
+ case 1:
+ return descending;
+ case 2:
+ return stateless;
+ }
+ return null;
+ }
+ });
+ }
+ };
+ }
+ }
+ public static Iterable data() {
+
+ List contexts = new ArrayList();
boolean[] tf = new boolean[]{true, false};
- List parameters = new ArrayList();
for (CheckPagination.Type type : CheckPagination.Type.values()) {
for (boolean descending : tf) {
for (boolean stateless : tf) {
- parameters.add(new Object[]{type, descending, stateless});
+ contexts.add(ParameterProvider.invocationContext(type, descending, stateless));
}
}
}
- return parameters;
+ return contexts;
}
- @Parameterized.Parameter
- public CheckPagination.Type type;
-
- @Parameterized.Parameter(value = 1)
- public boolean descending;
-
- @Parameterized.Parameter(value = 2)
- public boolean stateless;
-
- @ClassRule
- public static CloudantClientResource clientResource = new CloudantClientResource();
- @Rule
- public DatabaseResource dbResource = new DatabaseResource(clientResource);
-
- private Database db;
-
- @Before
+ @BeforeEach
public void setUp() throws Exception {
- db = dbResource.get();
Utils.putDesignDocs(db);
}
@@ -87,8 +128,10 @@ public void setUp() throws Exception {
* Page forward to the last page, back to the first page, forward to the last page and back to
* the first page.
*/
- @Test
- public void allTheWayInEachDirectionTwice() throws Exception {
+ @TestTemplate
+ public void allTheWayInEachDirectionTwice(CheckPagination.Type type,
+ boolean descending,
+ boolean stateless) throws Exception {
CheckPagination.newTest(type)
.descending(descending)
.docCount(6)
@@ -106,8 +149,10 @@ public void allTheWayInEachDirectionTwice() throws Exception {
* Page forward to the last page, back to the first page, forward to the last page and back to
* the first page.
*/
- @Test
- public void partialLastPageAllTheWayInEachDirectionTwice() throws Exception {
+ @TestTemplate
+ public void partialLastPageAllTheWayInEachDirectionTwice(CheckPagination.Type type,
+ boolean descending,
+ boolean stateless) throws Exception {
CheckPagination.newTest(type)
.descending(descending)
.docCount(5)
@@ -124,8 +169,10 @@ public void partialLastPageAllTheWayInEachDirectionTwice() throws Exception {
*
* Page part way forward, and part way back a few times before paging to the last page.
*/
- @Test
- public void partWayInEachDirection() throws Exception {
+ @TestTemplate
+ public void partWayInEachDirection(CheckPagination.Type type,
+ boolean descending,
+ boolean stateless) throws Exception {
CheckPagination.newTest(type)
.descending(descending)
.docCount(30)
@@ -142,8 +189,10 @@ public void partWayInEachDirection() throws Exception {
*
* Page part way forward, and part way back a few times before paging to the last page.
*/
- @Test
- public void partialLastPagePartWayInEachDirection() throws Exception {
+ @TestTemplate
+ public void partialLastPagePartWayInEachDirection(CheckPagination.Type type,
+ boolean descending,
+ boolean stateless) throws Exception {
CheckPagination.newTest(type)
.descending(descending)
.docCount(28)
@@ -157,21 +206,28 @@ public void partialLastPagePartWayInEachDirection() throws Exception {
* Check that we can page through a view where we use start and end keys.
* Assert that we don't exceed the limits of those keys.
*/
- @Test
- public void startAndEndKeyLimits() throws Exception {
- startAndEndKeyLimits(true);
+ @TestTemplate
+ public void startAndEndKeyLimits(CheckPagination.Type type,
+ boolean descending,
+ boolean stateless) throws Exception {
+ startAndEndKeyLimits(type, descending, stateless, true);
}
/**
* Check that we can page through a view where we use start and end keys.
* Assert that we don't exceed the limits of those keys.
*/
- @Test
- public void startAndEndKeyLimitsExclusiveEnd() throws Exception {
- startAndEndKeyLimits(false);
+ @TestTemplate
+ public void startAndEndKeyLimitsExclusiveEnd(CheckPagination.Type type,
+ boolean descending,
+ boolean stateless) throws Exception {
+ startAndEndKeyLimits(type, descending, stateless, false);
}
- private void startAndEndKeyLimits(boolean inclusiveEnd) throws Exception {
+ private void startAndEndKeyLimits(CheckPagination.Type type,
+ boolean descending,
+ boolean stateless,
+ boolean inclusiveEnd) throws Exception {
//use the CheckPagination to set-up for this test, but we need to do running differently
//since this page is not just simple paging
CheckPagination cp = CheckPagination.newTest(type)
@@ -193,7 +249,7 @@ private void startAndEndKeyLimits(boolean inclusiveEnd) throws Exception {
.newPaginatedRequest(Key.Type.STRING, String.class).reduce(false).descending
(descending).inclusiveEnd(inclusiveEnd).rowsPerPage(4).startKey
(startKey).endKey(endKey).build();
- runStartAndEndKeyLimits(request, startKey, expectedEndKey);
+ runStartAndEndKeyLimits(stateless, request, startKey, expectedEndKey);
} else {
//for multi we will use the creator_created view
Key.ComplexKey startKey = (descending) ? Key.complex("uuid").add(1011) : Key.complex
@@ -207,19 +263,21 @@ private void startAndEndKeyLimits(boolean inclusiveEnd) throws Exception {
.newPaginatedRequest(Key.Type.COMPLEX, String.class).reduce(false).descending
(descending).inclusiveEnd(inclusiveEnd).rowsPerPage(4).startKey
(startKey).endKey(endKey).build();
- runStartAndEndKeyLimits(request, startKey, expectedEndKey);
+ runStartAndEndKeyLimits(stateless, request, startKey, expectedEndKey);
}
}
- private void runStartAndEndKeyLimits(ViewRequest request, T expectedStartKey,
+ private void runStartAndEndKeyLimits(boolean stateless,
+ ViewRequest request,
+ T expectedStartKey,
T expectedEndKey)
throws Exception {
ViewResponse page = request.getResponse();
//check the start key is as expected
- assertEquals("The start key should be " + expectedStartKey, expectedStartKey, page
- .getKeys().get(0));
+ assertEquals(expectedStartKey, page.getKeys().get(0), "The start key should be " +
+ expectedStartKey);
//get the last page
while (page.hasNextPage()) {
@@ -230,10 +288,8 @@ private void runStartAndEndKeyLimits(ViewRequest request, T expec
}
}
//check the end key is as expected
- assertEquals("The end key should be " + expectedEndKey, expectedEndKey, page.getKeys()
- .get(page.getKeys()
- .size()
- - 1));
+ assertEquals(expectedEndKey, page.getKeys().get(page.getKeys().size() - 1), "The end key " +
+ "should be " + expectedEndKey);
//now page backwards and ensure the last key we get is the start key
while (page.hasPreviousPage()) {
@@ -244,7 +300,7 @@ private void runStartAndEndKeyLimits(ViewRequest request, T expec
}
}
//check the start key is as expected
- assertEquals("The start key should be " + expectedStartKey, expectedStartKey, page
- .getKeys().get(0));
+ assertEquals(expectedStartKey, page.getKeys().get(0), "The start key should be " +
+ expectedStartKey);
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java
index 1f4f89f44..6900b4b63 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java
@@ -16,13 +16,14 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
@@ -38,21 +39,22 @@
import com.cloudant.http.HttpConnectionInterceptorContext;
import com.cloudant.test.main.RequiresCloudant;
import com.cloudant.test.main.RequiresDB;
-import com.cloudant.tests.util.CloudantClientResource;
+import com.cloudant.tests.base.TestWithDbPerTest;
+import com.cloudant.tests.extensions.CloudantClientExtension;
import com.cloudant.tests.util.ContextCollectingInterceptor;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.extensions.DatabaseExtension;
+import com.cloudant.tests.extensions.MockWebServerExtension;
+import com.cloudant.tests.extensions.MultiExtension;
import com.cloudant.tests.util.Utils;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.function.Executable;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
@@ -69,30 +71,27 @@
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
-@Category(RequiresDB.class)
-public class ViewsTest {
+@RequiresDB
+public class ViewsTest extends TestWithDbPerTest {
- @ClassRule
- public static CloudantClientResource clientResource = new CloudantClientResource();
- @ClassRule
- public static MockWebServer mockWebServer = new MockWebServer();
+ public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension();
+ public static ContextCollectingInterceptor cci = new ContextCollectingInterceptor();
+ public static CloudantClientExtension interceptedClient = new CloudantClientExtension(CloudantClientHelper.getClientBuilder()
+ .interceptors(cci));
+ public static DatabaseExtension.PerClass interceptedDB = new DatabaseExtension.PerClass(interceptedClient);
- @Rule
- public DatabaseResource dbResource = new DatabaseResource(clientResource);
+ @RegisterExtension
+ public static MultiExtension extensions = new MultiExtension(
+ mockWebServerExt,
+ interceptedClient,
+ interceptedDB
+ );
+ protected MockWebServer mockWebServer;
- private static ContextCollectingInterceptor cci = new ContextCollectingInterceptor();
- public static CloudantClientResource interceptedClient = new CloudantClientResource
- (CloudantClientHelper.getClientBuilder().interceptors(cci));
- public static DatabaseResource interceptedDB = new DatabaseResource(interceptedClient);
- @ClassRule
- public static RuleChain intercepted = RuleChain.outerRule(interceptedClient).around
- (interceptedDB);
- private Database db;
-
- @Before
- public void setUp() throws Exception {
- db = dbResource.get();
+ @BeforeEach
+ public void beforeEach() throws Exception {
+ mockWebServer = mockWebServerExt.get();
Utils.putDesignDocs(db);
}
@@ -623,19 +622,14 @@ public void scalarValues() throws Exception {
@Test
public void viewWithNoResult_emptyList() throws IOException {
init();
- assertEquals("The results list should be of length 0", 0, db.getViewRequestBuilder
- ("example", "by_tag").newRequest(Key.Type.STRING, Object.class).keys
- ("javax").build().getResponse().getKeys().size());
+ assertEquals(0, db.getViewRequestBuilder("example", "by_tag").newRequest(Key.Type.STRING, Object.class).keys("javax").build().getResponse().getKeys().size(), "The results list should be of length 0");
}
@Test
public void viewWithNoResult_nullSingleResult() throws IOException {
init();
- assertNull("The single result should be null", db.getViewRequestBuilder("example",
- "by_tag").newRequest(Key.Type.STRING,
- Object.class).keys
- ("javax").build().getSingleValue());
+ assertNull(db.getViewRequestBuilder("example", "by_tag").newRequest(Key.Type.STRING, Object.class).keys("javax").build().getSingleValue(), "The single result should be null");
}
@@ -657,7 +651,7 @@ public void allDocs() throws Exception {
.getIdsAndRevs();
assertThat(idsAndRevs.size(), not(0));
for (Map.Entry doc : idsAndRevs.entrySet()) {
- assertNotNull("The document _rev value should not be null", doc.getValue());
+ assertNotNull(doc.getValue(), "The document _rev value should not be null");
}
}
@@ -694,7 +688,7 @@ public void allDocsWithOneNonExistingKey() throws Exception {
Map idsAndRevs = response.getIdsAndRevs();
assertThat(idsAndRevs.size(), is(1));
for (Map.Entry doc : idsAndRevs.entrySet()) {
- assertNotNull("The document _rev value should not be null", doc.getValue());
+ assertNotNull(doc.getValue(), "The document _rev value should not be null");
}
assertThat(errors.size(), is(1));
@@ -867,7 +861,7 @@ private void assertJsonArrayKeysAndValues(ArrayList expectedJson,
* @throws IOException
*/
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void multiRequest() throws IOException {
init();
ViewMultipleRequest multi = db.getViewRequestBuilder("example", "foo")
@@ -878,11 +872,10 @@ public void multiRequest() throws IOException {
.build();
int i = 1;
List> responses = multi.getViewResponses();
- assertEquals("There should be 3 respones for 3 requests", 3, responses.size());
+ assertEquals(3, responses.size(), "There should be 3 respones for 3 requests");
for (ViewResponse response : responses) {
- assertEquals("There should be 1 row in each response", 1, response.getRows().size());
- assertEquals("The returned key should be key-" + i, "key-" + i, response.getKeys()
- .get(0));
+ assertEquals(1, response.getRows().size(), "There should be 1 row in each response");
+ assertEquals("key-" + i, response.getKeys().get(0), "The returned key should be key-" + i);
i++;
}
}
@@ -949,24 +942,38 @@ public void multiRequestBuildParametersSecond() {
* Validate that an IllegalStateException is thrown if an attempt is made to build a multi
* request without calling add() before build() with two requests.
*/
- @Test(expected = IllegalStateException.class)
+ @Test
public void multiRequestBuildOnlyAfterAdd() {
- ViewMultipleRequest multi = db.getViewRequestBuilder("example", "foo")
- .newMultipleRequest(Key.Type.STRING, Object.class)
- .keys("key-1").add()
- .keys("key-2").build();
+ assertThrows(IllegalStateException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewMultipleRequest multi = db.getViewRequestBuilder
+ ("example", "foo")
+ .newMultipleRequest(Key.Type.STRING, Object.class)
+ .keys("key-1").add()
+ .keys("key-2").build();
+ }
+ });
}
/**
* Validate that an IllegalStateException is thrown if an attempt is made to build a multi
* request without calling add() before build() with a single request with parameters.
*/
- @Test(expected = IllegalStateException.class)
+ @Test
public void multiRequestBuildOnlyAfterAddSingle() {
- ViewMultipleRequest multi = db.getViewRequestBuilder("example", "foo")
- .newMultipleRequest(Key.Type.STRING, Object.class)
- .keys("key-1")
- .build();
+ assertThrows(IllegalStateException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewMultipleRequest multi = db.getViewRequestBuilder
+ ("example", "foo")
+ .newMultipleRequest(Key.Type.STRING, Object.class)
+ .keys("key-1")
+ .build();
+ }
+ });
}
/**
@@ -974,11 +981,18 @@ public void multiRequestBuildOnlyAfterAddSingle() {
* request without calling add() before build() with a single request with no view request
* parameter calls.
*/
- @Test(expected = IllegalStateException.class)
+ @Test
public void multiRequestBuildOnlyAfterAddNoParams() {
- ViewMultipleRequest multi = db.getViewRequestBuilder("example", "foo")
- .newMultipleRequest(Key.Type.STRING, Object.class)
- .build();
+ assertThrows(IllegalStateException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewMultipleRequest multi = db.getViewRequestBuilder
+ ("example", "foo")
+ .newMultipleRequest(Key.Type.STRING, Object.class)
+ .build();
+ }
+ });
}
/**
@@ -988,7 +1002,7 @@ public void multiRequestBuildOnlyAfterAddNoParams() {
* @throws IOException
*/
@Test
- @Category(RequiresCloudant.class)
+ @RequiresCloudant
public void multiRequestMixedReduced() throws IOException {
init();
ViewMultipleRequest multi = db.getViewRequestBuilder("example", "by_tag")
@@ -999,15 +1013,15 @@ public void multiRequestMixedReduced() throws IOException {
.build();
List> responses = multi.getViewResponses();
- assertEquals("There should be 2 respones for 2 requests", 2, responses.size());
+ assertEquals(2, responses.size(), "There should be 2 respones for 2 requests");
List javaTagKeys = responses.get(0).getKeys();
- assertEquals("There should be 1 java tag result", 1, javaTagKeys.size());
- assertEquals("The key should be java", "java", javaTagKeys.get(0));
+ assertEquals(1, javaTagKeys.size(), "There should be 1 java tag result");
+ assertEquals("java", javaTagKeys.get(0), "The key should be java");
List allTagsReduced = responses.get(1).getValues();
- assertEquals("There should be 1 reduced result", 1, allTagsReduced.size());
- assertEquals("The result should be 4", 4, ((Number) allTagsReduced.get(0)).intValue());
+ assertEquals(1, allTagsReduced.size(), "There should be 1 reduced result");
+ assertEquals(4, ((Number) allTagsReduced.get(0)).intValue(), "The result should be 4");
}
/**
@@ -1022,9 +1036,9 @@ public void assertNoPagesOnUnpaginated() throws Exception {
ViewResponse response = db.getViewRequestBuilder("example", "foo")
.newRequest(Key.Type.STRING,
Object.class).limit(2).build().getResponse();
- assertEquals("There should be 2 keys returned", 2, response.getKeys().size());
- assertFalse("There should be no additional pages", response.hasNextPage());
- assertNull("The next page should be null", response.nextPage());
+ assertEquals(2, response.getKeys().size(), "There should be 2 keys returned");
+ assertFalse(response.hasNextPage(), "There should be no additional pages");
+ assertNull(response.nextPage(), "The next page should be null");
}
/**
@@ -1041,8 +1055,8 @@ public void enhancedForPagination() throws Exception {
Object.class).rowsPerPage(1).build();
int i = 1;
for (ViewResponse page : paginatedQuery.getResponse()) {
- assertEquals("There should be one key on each page", 1, page.getKeys().size());
- assertEquals("The key should be key-" + i, "key-" + i, page.getKeys().get(0));
+ assertEquals(1, page.getKeys().size(), "There should be one key on each page");
+ assertEquals("key-" + i, page.getKeys().get(0), "The key should be key-" + i);
i++;
}
}
@@ -1052,11 +1066,18 @@ public void enhancedForPagination() throws Exception {
*
* @throws Exception
*/
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void rowsPerPageValidationMax() throws Exception {
- ViewRequest paginatedQuery = db.getViewRequestBuilder("example", "foo")
- .newPaginatedRequest(Key.Type.STRING,
- Object.class).rowsPerPage(Integer.MAX_VALUE).build();
+ assertThrows(IllegalArgumentException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewRequest paginatedQuery = db.getViewRequestBuilder
+ ("example", "foo")
+ .newPaginatedRequest(Key.Type.STRING,
+ Object.class).rowsPerPage(Integer.MAX_VALUE).build();
+ }
+ });
}
/**
@@ -1064,11 +1085,18 @@ public void rowsPerPageValidationMax() throws Exception {
*
* @throws Exception
*/
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void rowsPerPageValidationZero() throws Exception {
- ViewRequest paginatedQuery = db.getViewRequestBuilder("example", "foo")
- .newPaginatedRequest(Key.Type.STRING,
- Object.class).rowsPerPage(0).build();
+ assertThrows(IllegalArgumentException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewRequest paginatedQuery = db.getViewRequestBuilder
+ ("example", "foo")
+ .newPaginatedRequest(Key.Type.STRING,
+ Object.class).rowsPerPage(0).build();
+ }
+ });
}
/**
@@ -1076,11 +1104,18 @@ public void rowsPerPageValidationZero() throws Exception {
*
* @throws Exception
*/
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void rowsPerPageValidationMin() throws Exception {
- ViewRequest paginatedQuery = db.getViewRequestBuilder("example", "foo")
- .newPaginatedRequest(Key.Type.STRING,
- Object.class).rowsPerPage(-25).build();
+ assertThrows(IllegalArgumentException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewRequest paginatedQuery = db.getViewRequestBuilder
+ ("example", "foo")
+ .newPaginatedRequest(Key.Type.STRING,
+ Object.class).rowsPerPage(-25).build();
+ }
+ });
}
/**
@@ -1089,11 +1124,18 @@ public void rowsPerPageValidationMin() throws Exception {
*
* @throws Exception
*/
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void validationIncludeDocsReduceView() throws Exception {
- ViewRequest paginatedQuery = db.getViewRequestBuilder("example", "foo")
- .newRequest(Key.Type.STRING,
- Object.class).includeDocs(true).reduce(true).build();
+ assertThrows(IllegalArgumentException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewRequest paginatedQuery = db.getViewRequestBuilder
+ ("example", "foo")
+ .newRequest(Key.Type.STRING,
+ Object.class).includeDocs(true).reduce(true).build();
+ }
+ });
}
/**
@@ -1117,11 +1159,18 @@ public void noExceptionWhenReduceTrueByDefault() throws Exception {
*
* @throws Exception
*/
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void validationGroupLevelWithSimpleKey() throws Exception {
- ViewRequest paginatedQuery = db.getViewRequestBuilder("example", "foo")
- .newRequest(Key.Type.STRING,
- Object.class).groupLevel(1).build();
+ assertThrows(IllegalArgumentException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewRequest paginatedQuery = db.getViewRequestBuilder
+ ("example", "foo")
+ .newRequest(Key.Type.STRING,
+ Object.class).groupLevel(1).build();
+ }
+ });
}
/**
@@ -1130,11 +1179,18 @@ public void validationGroupLevelWithSimpleKey() throws Exception {
*
* @throws Exception
*/
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void validationGroupLevelWithNonReduce() throws Exception {
- ViewRequest paginatedQuery = db.getViewRequestBuilder("example", "foo")
- .newRequest(Key.Type.STRING,
- Object.class).reduce(false).groupLevel(1).build();
+ assertThrows(IllegalArgumentException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewRequest paginatedQuery = db.getViewRequestBuilder
+ ("example", "foo")
+ .newRequest(Key.Type.STRING,
+ Object.class).reduce(false).groupLevel(1).build();
+ }
+ });
}
/**
@@ -1143,11 +1199,18 @@ public void validationGroupLevelWithNonReduce() throws Exception {
*
* @throws Exception
*/
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void validationGroupWithNonReduce() throws Exception {
- ViewRequest paginatedQuery = db.getViewRequestBuilder("example", "foo")
- .newRequest(Key.Type.STRING,
- Object.class).reduce(false).group(true).build();
+ assertThrows(IllegalArgumentException.class,
+ new Executable() {
+ @Override
+ public void execute() throws Throwable {
+ ViewRequest paginatedQuery = db.getViewRequestBuilder
+ ("example", "foo")
+ .newRequest(Key.Type.STRING,
+ Object.class).reduce(false).group(true).build();
+ }
+ });
}
/**
@@ -1195,8 +1258,7 @@ public void testComplexKeyContainingIntTokenPagination() throws Exception {
// We want the last context
HttpConnectionInterceptorContext context = cci.contexts.get(cci.contexts.size() - 1);
String query = context.connection.url.getQuery();
- assertTrue("The query startkey should match.", query.contains("startkey=%5B%22uuid%22," +
- "1005%5D"));
+ assertTrue(query.contains("startkey=%5B%22uuid%22," + "1005%5D"), "The query startkey should match.");
}
/**
@@ -1283,9 +1345,8 @@ private void assertStaleParameter(ViewRequest viewRequest, Patt
mockWebServer.enqueue(mockResponse);
viewRequest.getSingleValue();
RecordedRequest request = mockWebServer.takeRequest(1, TimeUnit.SECONDS);
- assertNotNull("There should have been a view request", request);
- assertTrue("There request URL should match the pattern " + p.toString(), p.matcher
- (request.getPath()).matches());
+ assertNotNull(request, "There should have been a view request");
+ assertTrue(p.matcher(request.getPath()).matches(), "There request URL should match the pattern " + p.toString());
}
/**
@@ -1350,6 +1411,6 @@ public void getIdsAndRevsForDeletedIDsWithAllDocs() throws Exception {
// Do an _all_docs request using the 4 _ids of the generated docs.
Map allDocsIdsAndRevs = database.getAllDocsRequestBuilder().keys(idsAndRevs
.keySet().toArray(new String[4])).build().getResponse().getIdsAndRevs();
- assertEquals("The ids and revs should be equal", idsAndRevs, allDocsIdsAndRevs);
+ assertEquals(idsAndRevs, allDocsIdsAndRevs, "The ids and revs should be equal");
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDb.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDb.java
new file mode 100644
index 000000000..89fdb225d
--- /dev/null
+++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDb.java
@@ -0,0 +1,30 @@
+package com.cloudant.tests.base;
+
+import com.cloudant.client.api.CloudantClient;
+import com.cloudant.client.api.Database;
+import com.cloudant.tests.extensions.CloudantClientExtension;
+import com.cloudant.tests.extensions.DatabaseExtension;
+import com.cloudant.tests.extensions.MultiExtension;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+// Base class for tests which require a DB which is used throughout the lifetime of the test class
+public class TestWithDb {
+
+ protected static CloudantClientExtension clientResource = new CloudantClientExtension();
+ protected static DatabaseExtension.PerClass dbResource = new DatabaseExtension.PerClass(clientResource);
+
+ protected static CloudantClient account;
+ protected static Database db;
+
+ @RegisterExtension
+ protected static MultiExtension perClassExtensions = new MultiExtension(clientResource, dbResource);
+
+ @BeforeAll
+ public static void testWithDbBeforeAll() {
+ account = clientResource.get();
+ db = dbResource.get();
+ }
+
+}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java
new file mode 100644
index 000000000..59b32f8d2
--- /dev/null
+++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java
@@ -0,0 +1,35 @@
+package com.cloudant.tests.base;
+
+import com.cloudant.client.api.CloudantClient;
+import com.cloudant.client.api.Database;
+import com.cloudant.tests.extensions.CloudantClientExtension;
+import com.cloudant.tests.extensions.DatabaseExtension;
+import com.cloudant.tests.extensions.MultiExtension;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+// Base class for tests which require a new DB for each test method
+public class TestWithDbPerTest {
+
+ protected static CloudantClientExtension clientResource = new CloudantClientExtension();
+ protected static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest(clientResource);
+
+ protected static CloudantClient account;
+ protected static Database db;
+
+ @RegisterExtension
+ protected static MultiExtension perTestExtensions = new MultiExtension(clientResource, dbResource);
+
+ @BeforeEach
+ public void testWithDbBeforeEach() {
+ db = dbResource.get();
+ }
+
+ @BeforeAll
+ public static void testWithDbBeforeAll() {
+ account = clientResource.get();
+ }
+
+}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/util/MockedServerTest.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java
similarity index 50%
rename from cloudant-client/src/test/java/com/cloudant/tests/util/MockedServerTest.java
rename to cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java
index 020097b4a..6db654003 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/util/MockedServerTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java
@@ -12,14 +12,17 @@
* and limitations under the License.
*/
-package com.cloudant.tests.util;
+package com.cloudant.tests.base;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
+import com.cloudant.tests.extensions.CloudantClientMockServerExtension;
+import com.cloudant.tests.extensions.DatabaseExtension;
+import com.cloudant.tests.extensions.MockWebServerExtension;
+import com.cloudant.tests.extensions.MultiExtension;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.RegisterExtension;
import okhttp3.mockwebserver.MockWebServer;
@@ -27,21 +30,27 @@
* Class of tests that run against a MockWebServer. This class handles set up and tear down of the
* mock server and associated client and db objects before/after each test.
*/
-public abstract class MockedServerTest {
-
- protected MockWebServer server = new MockWebServer();
+public abstract class TestWithMockedServer {
+
+ public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension();
+ public static CloudantClientMockServerExtension clientResource = new CloudantClientMockServerExtension(mockWebServerExt);
+ public static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest(clientResource);
+ @RegisterExtension
+ public static MultiExtension extensions = new MultiExtension(
+ mockWebServerExt,
+ clientResource,
+ dbResource
+ );
+
+ protected MockWebServer server;
protected CloudantClient client;
protected Database db;
- protected CloudantClientMockServerResource clientResource = new
- CloudantClientMockServerResource(server);
- protected DatabaseResource dbResource = new DatabaseResource(clientResource);
- @Rule
- public RuleChain chain = RuleChain.outerRule(server).around(clientResource).around(dbResource);
-
- @Before
- public void setup() throws Exception {
+ @BeforeEach
+ public void beforeEach() {
+ server = mockWebServerExt.get();
client = clientResource.get();
db = dbResource.get();
}
+
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ReplicateBaseTest.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java
similarity index 56%
rename from cloudant-client/src/test/java/com/cloudant/tests/ReplicateBaseTest.java
rename to cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java
index 227e697a9..74052ffa2 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/ReplicateBaseTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java
@@ -12,42 +12,42 @@
* and limitations under the License.
*/
-package com.cloudant.tests;
+package com.cloudant.tests.base;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
import com.cloudant.client.api.views.Key;
import com.cloudant.client.api.views.ViewResponse;
-import com.cloudant.tests.util.CloudantClientResource;
-import com.cloudant.tests.util.DatabaseResource;
+import com.cloudant.tests.extensions.CloudantClientExtension;
+import com.cloudant.tests.extensions.DatabaseExtension;
+import com.cloudant.tests.extensions.MultiExtension;
import com.cloudant.tests.util.Utils;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.extension.RegisterExtension;
-public class ReplicateBaseTest {
+public class TestWithReplication {
- @ClassRule
- public static CloudantClientResource clientResource = new CloudantClientResource();
- protected CloudantClient account = clientResource.get();
+ protected static CloudantClientExtension clientResource = new CloudantClientExtension();
+ protected static CloudantClient account;
- @Rule
- public DatabaseResource db1Resource = new DatabaseResource(clientResource);
- @Rule
- public DatabaseResource db2Resource = new DatabaseResource(clientResource);
+ protected static DatabaseExtension.PerClass db1Resource = new DatabaseExtension.PerClass(clientResource);
+ protected static DatabaseExtension.PerClass db2Resource = new DatabaseExtension.PerClass(clientResource);
- protected Database db1;
+ @RegisterExtension
+ public static MultiExtension extensions = new MultiExtension(clientResource, db1Resource, db2Resource);
- protected Database db2;
+ protected static Database db1;
+ protected static Database db2;
protected static String db1URI;
protected static String db2URI;
- @Before
- public void setUp() throws Exception {
+ @BeforeAll
+ public static void setUp() throws Exception {
+ account = clientResource.get();
db1 = db1Resource.get();
db1URI = db1Resource.getDbURIWithUserInfo();
@@ -56,6 +56,7 @@ public void setUp() throws Exception {
db2 = db2Resource.get();
db2URI = db2Resource.getDbURIWithUserInfo();
Utils.putDesignDocs(db2);
+
}
protected void assertConflictsNotZero(Database db) throws Exception {
@@ -63,7 +64,6 @@ protected void assertConflictsNotZero(Database db) throws Exception {
("conflicts", "conflict").newRequest(Key.Type.COMPLEX, String.class).build()
.getResponse();
int conflictCount = conflicts.getRows().size();
- assertTrue("There should be at least 1 conflict, there were " + conflictCount,
- conflictCount > 0);
+ assertTrue(conflictCount > 0, "There should be at least 1 conflict, there were " + conflictCount);
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/extensions/AbstractClientExtension.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/AbstractClientExtension.java
new file mode 100644
index 000000000..4c21f5fee
--- /dev/null
+++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/AbstractClientExtension.java
@@ -0,0 +1,11 @@
+package com.cloudant.tests.extensions;
+
+import com.cloudant.client.api.CloudantClient;
+
+public abstract class AbstractClientExtension {
+
+ public abstract CloudantClient get();
+
+ public abstract String getBaseURIWithUserInfo();
+
+}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/util/CloudantClientResource.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientExtension.java
similarity index 70%
rename from cloudant-client/src/test/java/com/cloudant/tests/util/CloudantClientResource.java
rename to cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientExtension.java
index 375d16fb2..e22cfd4c5 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/util/CloudantClientResource.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientExtension.java
@@ -12,38 +12,44 @@
* and limitations under the License.
*/
-package com.cloudant.tests.util;
+package com.cloudant.tests.extensions;
import com.cloudant.client.api.ClientBuilder;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.tests.CloudantClientHelper;
-import org.junit.rules.ExternalResource;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class CloudantClientExtension extends AbstractClientExtension implements
+ BeforeAllCallback, AfterAllCallback {
-public class CloudantClientResource extends ExternalResource {
private ClientBuilder clientBuilder;
+
private CloudantClient client;
- public CloudantClientResource() {
+ public CloudantClientExtension() {
this.clientBuilder = CloudantClientHelper.getClientBuilder();
}
- public CloudantClientResource(ClientBuilder clientBuilder) {
+ public CloudantClientExtension(ClientBuilder clientBuilder) {
this.clientBuilder = clientBuilder;
}
@Override
- public void before() {
- client = clientBuilder.build();
+ public CloudantClient get() {
+ return this.client;
}
@Override
- public void after() {
+ public void afterAll(ExtensionContext context) throws Exception {
client.shutdown();
}
- public CloudantClient get() {
- return this.client;
+ @Override
+ public void beforeAll(ExtensionContext context) throws Exception {
+ client = clientBuilder.build();
}
/**
@@ -52,6 +58,7 @@ public CloudantClient get() {
*
* @return String representation of the URI
*/
+ @Override
public String getBaseURIWithUserInfo() {
return CloudantClientHelper.SERVER_URI_WITH_USER_INFO;
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientMockServerExtension.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientMockServerExtension.java
new file mode 100644
index 000000000..e9884d45c
--- /dev/null
+++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientMockServerExtension.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright © 2017 IBM Corp. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific language governing permissions
+ * and limitations under the License.
+ */
+
+package com.cloudant.tests.extensions;
+
+import com.cloudant.client.api.CloudantClient;
+import com.cloudant.tests.CloudantClientHelper;
+import com.cloudant.tests.util.MockWebServerResources;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import okhttp3.mockwebserver.MockWebServer;
+
+public class CloudantClientMockServerExtension extends AbstractClientExtension implements BeforeEachCallback, AfterEachCallback {
+
+ private final MockWebServerExtension mockWebServerExt;
+ private MockWebServer mockWebServer;
+ private CloudantClient client;
+
+ public CloudantClientMockServerExtension(MockWebServerExtension mockWebServerExt) {
+ this.mockWebServerExt = mockWebServerExt;
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext ctx) {
+ this.mockWebServer = this.mockWebServerExt.get();
+ this.client = CloudantClientHelper.newMockWebServerClientBuilder(this.mockWebServer).build();
+ }
+
+ @Override
+ public void afterEach(ExtensionContext ctx) {
+ // Queue a 200 for the _session DELETE that is called on shutdown.
+ mockWebServer.enqueue(MockWebServerResources.JSON_OK);
+ }
+
+ @Override
+ public CloudantClient get() {
+ return client;
+ }
+
+ @Override
+ public String getBaseURIWithUserInfo() {
+ return CloudantClientHelper.SERVER_URI_WITH_USER_INFO;
+ }
+
+
+}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/util/DatabaseResource.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java
similarity index 60%
rename from cloudant-client/src/test/java/com/cloudant/tests/util/DatabaseResource.java
rename to cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java
index 553369090..6c800728c 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/util/DatabaseResource.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java
@@ -12,25 +12,29 @@
* and limitations under the License.
*/
-package com.cloudant.tests.util;
+package com.cloudant.tests.extensions;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
+import com.cloudant.tests.util.Utils;
-import org.junit.rules.ExternalResource;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class DatabaseResource extends ExternalResource {
+public class DatabaseExtension {
// Get the maximum length for a database name, defaults to 128 chars
private static final int DB_NAME_SIZE = Integer.parseInt(System.getProperty("test.db.name" +
".length", "128"));
- private final CloudantClientResource clientResource;
+ private final AbstractClientExtension clientResource;
+
private CloudantClient client;
private String databaseName = Utils.generateUUID();
private Database database;
@@ -52,25 +56,16 @@ public class DatabaseResource extends ExternalResource {
*
* @param clientResource
*/
- public DatabaseResource(CloudantClientResource clientResource) {
+
+ protected DatabaseExtension(CloudantClientExtension clientResource) {
this.clientResource = clientResource;
}
- public DatabaseResource(CloudantClientMockServerResource mockServerResource) {
+ protected DatabaseExtension(CloudantClientMockServerExtension mockServerResource) {
this.clientResource = mockServerResource;
this.mock = true;
}
- @Override
- public Statement apply(Statement base, Description description) {
- String testClassName = description.getClassName();
- String testMethodName = description.getMethodName();
- String uniqueSuffix = Utils.generateUUID();
- databaseName = sanitizeDbName(testClassName + "-" + (testMethodName == null ? "" :
- testMethodName + "-") + uniqueSuffix);
- return super.apply(base, description);
- }
-
/**
* The database must start with a letter and can only contain lowercase letters (a-z), digits
* (0-9) and the following characters _, $, (, ), +, -, and /.
@@ -79,6 +74,14 @@ public Statement apply(Statement base, Description description) {
* @return sanitized name
*/
private static String sanitizeDbName(String name) {
+ //lowercase to remove any caps that will not be permitted
+ name = name.toLowerCase(Locale.ENGLISH);
+ //replace any non alphanum with underscores
+ name = name.replaceAll("[^a-z0-9]", "_");
+ //squash multiple underscores
+ name = name.replaceAll("(_){2,}", "_");
+ //remove leading underscore as it is reserved for internal couch use
+ name = name.replaceAll("^_", "");
//database name is limited to 128 characters on the Cloudant service
//forego the package name in favour of test details and UUID
int excess;
@@ -90,15 +93,12 @@ private static String sanitizeDbName(String name) {
name = m.group(1);
}
}
- //lowercase to remove any caps that will not be permitted
- name = name.toLowerCase(Locale.ENGLISH);
- //replace any characters that are not permitted with underscores
- name = name.replaceAll("[^a-z0-9_\\$\\(\\)\\+\\-/]", "_");
return name;
}
- @Override
- public void before() {
+ public void before(ExtensionContext context) {
+ String uniqueSuffix = Utils.generateUUID();
+ databaseName = sanitizeDbName(String.format("%s-%s", context.getUniqueId(), uniqueSuffix));
client = clientResource.get();
if (!mock) {
database = client.database(databaseName, true);
@@ -107,8 +107,7 @@ public void before() {
}
}
- @Override
- public void after() {
+ public void after(ExtensionContext context) {
if (!mock) {
client.deleteDB(databaseName);
}
@@ -130,6 +129,46 @@ public String getDatabaseName() {
* @return the URI for the DB with creds
*/
public String getDbURIWithUserInfo() throws Exception {
- return clientResource.getBaseURIWithUserInfo() + "/" + getDatabaseName();
+ String info = clientResource.getBaseURIWithUserInfo() + "/" + getDatabaseName();
+ System.out.println("*** "+info);
+ return info;
+ }
+
+ public static class PerClass extends DatabaseExtension implements BeforeAllCallback, AfterAllCallback {
+
+ public PerClass(CloudantClientExtension clientResource) {
+ super(clientResource);
+ }
+
+ @Override
+ public void afterAll(ExtensionContext extensionContext) throws Exception {
+ super.after(extensionContext);
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) throws Exception {
+ super.before(extensionContext);
+ }
+ }
+
+ public static class PerTest extends DatabaseExtension implements BeforeEachCallback, AfterEachCallback {
+
+ public PerTest(CloudantClientExtension clientResource) {
+ super(clientResource);
+ }
+
+ public PerTest(CloudantClientMockServerExtension mockServerResource) {
+ super(mockServerResource);
+ }
+
+ @Override
+ public void afterEach(ExtensionContext extensionContext) throws Exception {
+ super.after(extensionContext);
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) throws Exception {
+ super.before(extensionContext);
+ }
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/extensions/MockWebServerExtension.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/MockWebServerExtension.java
new file mode 100644
index 000000000..90edf5bdc
--- /dev/null
+++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/MockWebServerExtension.java
@@ -0,0 +1,42 @@
+package com.cloudant.tests.extensions;
+
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import okhttp3.mockwebserver.MockWebServer;
+
+public class MockWebServerExtension implements BeforeEachCallback, AfterEachCallback {
+
+ private MockWebServer mockWebServer;
+ private boolean started = false;
+
+ public MockWebServerExtension() {
+ }
+
+ public MockWebServer get() {
+ return this.mockWebServer;
+ }
+
+ // NB beforeEach/afterEach emulate before and after in the actual mock, because we can't call
+ // these directly as they are marked as protected
+
+ @Override
+ public synchronized void beforeEach(ExtensionContext context) throws Exception {
+ this.mockWebServer = new MockWebServer();
+ if (started) {
+ System.err.println("*** WARNING: MockWebServer already started");
+ return;
+ }
+ this.mockWebServer.start();
+ started = true;
+ }
+
+ @Override
+ public synchronized void afterEach(ExtensionContext context) throws Exception {
+ System.out.println("MWS shutdown");
+ this.mockWebServer.shutdown();
+ started = false;
+ }
+
+}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/extensions/MultiExtension.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/MultiExtension.java
new file mode 100644
index 000000000..22388815e
--- /dev/null
+++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/MultiExtension.java
@@ -0,0 +1,75 @@
+package com.cloudant.tests.extensions;
+
+import org.junit.jupiter.api.extension.AfterAllCallback;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
+import org.junit.jupiter.api.extension.BeforeAllCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
+import org.junit.jupiter.api.extension.Extension;
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+public class MultiExtension implements Extension, AfterAllCallback, AfterEachCallback,
+ AfterTestExecutionCallback, BeforeAllCallback, BeforeEachCallback,
+ BeforeTestExecutionCallback {
+
+ private final Extension[] extensions;
+
+ public MultiExtension(Extension... extensions) {
+ this.extensions = extensions;
+ }
+
+ @Override
+ public void afterAll(ExtensionContext extensionContext) throws Exception {
+ for (Extension extension : extensions) {
+ if (extension instanceof AfterAllCallback) {
+ ((AfterAllCallback) extension).afterAll(extensionContext);
+ }
+ }
+ }
+
+ @Override
+ public void afterEach(ExtensionContext extensionContext) throws Exception {
+ for (Extension extension : extensions) {
+ if (extension instanceof AfterEachCallback) {
+ ((AfterEachCallback) extension).afterEach(extensionContext);
+ }
+ }
+ }
+
+ @Override
+ public void afterTestExecution(ExtensionContext extensionContext) throws Exception {
+ for (Extension extension : extensions) {
+ if (extension instanceof AfterTestExecutionCallback) {
+ ((AfterTestExecutionCallback) extension).afterTestExecution(extensionContext);
+ }
+ }
+ }
+
+ @Override
+ public void beforeAll(ExtensionContext extensionContext) throws Exception {
+ for (Extension extension : extensions) {
+ if (extension instanceof BeforeAllCallback) {
+ ((BeforeAllCallback) extension).beforeAll(extensionContext);
+ }
+ }
+ }
+
+ @Override
+ public void beforeEach(ExtensionContext extensionContext) throws Exception {
+ for (Extension extension : extensions) {
+ if (extension instanceof BeforeEachCallback) {
+ ((BeforeEachCallback) extension).beforeEach(extensionContext);
+ }
+ }
+ }
+
+ @Override
+ public void beforeTestExecution(ExtensionContext extensionContext) throws Exception {
+ for (Extension extension : extensions) {
+ if (extension instanceof BeforeTestExecutionCallback) {
+ ((BeforeTestExecutionCallback) extension).beforeTestExecution(extensionContext);
+ }
+ }
+ }
+}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/util/CheckPagination.java b/cloudant-client/src/test/java/com/cloudant/tests/util/CheckPagination.java
index 949d02127..9aaa420a8 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/util/CheckPagination.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/util/CheckPagination.java
@@ -15,10 +15,10 @@
package com.cloudant.tests.util;
import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.cloudant.client.api.Database;
import com.cloudant.client.api.views.Key;
@@ -198,8 +198,8 @@ private void checkDocumentTitles(ViewResponse page, int docCount, int docsPerPag
}
List resultList = page.getDocsAs(Foo.class);
for (int i = 0; i < resultList.size(); ++i) {
- assertEquals("Document titles do not match", ViewsTest.docTitle(descending ? offset-- :
- offset++), resultList.get(i).getTitle());
+ assertEquals(ViewsTest.docTitle(descending ? offset-- :
+ offset++), resultList.get(i).getTitle(), "Document titles do not match");
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/util/CloudantClientMockServerResource.java b/cloudant-client/src/test/java/com/cloudant/tests/util/CloudantClientMockServerResource.java
deleted file mode 100644
index 0141a3c44..000000000
--- a/cloudant-client/src/test/java/com/cloudant/tests/util/CloudantClientMockServerResource.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright © 2017 IBM Corp. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
- */
-
-package com.cloudant.tests.util;
-
-import com.cloudant.tests.CloudantClientHelper;
-
-import okhttp3.mockwebserver.MockWebServer;
-
-public class CloudantClientMockServerResource extends CloudantClientResource {
-
- private final MockWebServer server;
-
- public CloudantClientMockServerResource(MockWebServer mockWebServer) {
- super(CloudantClientHelper.newMockWebServerClientBuilder(mockWebServer));
- this.server = mockWebServer;
- }
-
- @Override
- public void after() {
- // Queue a 200 for the _session DELETE that is called on shutdown.
- server.enqueue(MockWebServerResources.JSON_OK);
- super.after();
- }
-}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/util/HttpFactoryParameterizedTest.java b/cloudant-client/src/test/java/com/cloudant/tests/util/HttpFactoryParameterizedTest.java
index 19c11921e..bacfd00b4 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/util/HttpFactoryParameterizedTest.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/util/HttpFactoryParameterizedTest.java
@@ -14,27 +14,24 @@
package com.cloudant.tests.util;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import com.cloudant.http.internal.DefaultHttpUrlConnectionFactory;
import com.cloudant.http.internal.ok.OkHelper;
+import com.cloudant.tests.base.TestWithDb;
-import org.junit.Before;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.junit.jupiter.api.BeforeEach;
import mockit.Mock;
import mockit.MockUp;
-@RunWith(Parameterized.class)
-public abstract class HttpFactoryParameterizedTest {
+public abstract class HttpFactoryParameterizedTest extends TestWithDb {
/**
* A parameter governing whether to allow okhttp or not. This lets us exercise both
* HttpURLConnection types in these tests.
*/
- @Parameterized.Parameter
- public boolean okUsable;
+ public boolean isOkUsable;
/**
* A mock OkHelper that always returns false to force use of the JVM HttpURLConnection
@@ -47,14 +44,16 @@ public static boolean isOkUsable() {
}
}
- @Before
- public void changeHttpConnectionFactory() throws Exception {
- if (!okUsable) {
+ @BeforeEach
+ public void changeHttpConnectionFactory(boolean isOkUsable) throws Exception {
+ this.isOkUsable = isOkUsable;
+ System.out.println("***** is ok usable? "+isOkUsable);
+ if (!isOkUsable) {
// New up the mock that will stop okhttp's factory being used
new OkHelperMock();
}
// Verify that we are getting the behaviour we expect.
- assertEquals("The OK usable value was not what was expected for the test parameter.",
- okUsable, OkHelper.isOkUsable());
+ assertEquals(
+ isOkUsable, OkHelper.isOkUsable(), "The OK usable value was not what was expected for the test parameter.");
}
}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/util/TestLog.java b/cloudant-client/src/test/java/com/cloudant/tests/util/TestLog.java
deleted file mode 100644
index 0759afcb2..000000000
--- a/cloudant-client/src/test/java/com/cloudant/tests/util/TestLog.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2015 IBM Corp. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
- */
-
-package com.cloudant.tests.util;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.logging.Logger;
-
-public class TestLog implements TestRule {
-
- //default to a logger name for this class, but replace when the rule is applied
- public Logger logger = Logger.getLogger(TestLog.class.getName());
-
- @Override
- public Statement apply(final Statement base, Description description) {
- //set a logger for the name of the test class
- logger = Logger.getLogger(description.getClassName());
- return base;
- }
-
-}
diff --git a/cloudant-client/src/test/java/com/cloudant/tests/util/Utils.java b/cloudant-client/src/test/java/com/cloudant/tests/util/Utils.java
index a274ab000..a85fccea2 100644
--- a/cloudant-client/src/test/java/com/cloudant/tests/util/Utils.java
+++ b/cloudant-client/src/test/java/com/cloudant/tests/util/Utils.java
@@ -16,10 +16,10 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.cloudant.client.api.CloudantClient;
import com.cloudant.client.api.Database;
@@ -186,8 +186,8 @@ public static T findDocumentWithRetries(Database db, String docId, Class
*/
public static String[] splitAndAssert(String toSplit, String splitOn, int expectedNumber) {
String[] parts = toSplit.split(splitOn);
- assertEquals("There should be " + expectedNumber + " instances of " + splitOn + " in the " +
- "content", expectedNumber + 1, parts.length);
+ assertEquals( expectedNumber + 1, parts.length, "There should be " + expectedNumber + " instances of " + splitOn + " in the " +
+ "content");
return parts;
}
@@ -212,7 +212,7 @@ public static void putDesignDocs(Database db) throws FileNotFoundException {
}
public static void assertOKResponse(Response r) throws Exception {
- assertTrue("The response code should be 2XX was " + r.getStatusCode(), r.getStatusCode()
- / 100 == 2);
+ assertTrue(r.getStatusCode()
+ / 100 == 2, "The response code should be 2XX was " + r.getStatusCode());
}
}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index d3b83982b9b1bccad955349d702be9b884c6e049..8252d7e25c5b548b131082159ee03249a5d36511 100644
GIT binary patch
delta 38529
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