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 data() { - return Arrays.asList(new Object[][]{ - {""}, {"/api/couch/account_2128459498a75498"} - }); + static class ParameterProvider implements TestTemplateInvocationContextProvider { + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(invocationContext(""), invocationContext("/api/couch/account_2128459498a75498")); + } + + public static TestTemplateInvocationContext invocationContext(final String path) { + return new TestTemplateInvocationContext() { + @Override + public String getDisplayName(int invocationIndex) { + return String.format("path:%s", path); + } + + @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(String.class); + } + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + switch(parameterContext.getIndex()) { + case 0: + return path; + } + return null; + } + }); + } + }; + } } String protocol = "http"; String hostname = "127.0.0.1"; int port = 5984; - @Parameterized.Parameter - public String path; String uriBase; - @Before - public void setup() { + @BeforeEach + public void setup(String path) { uriBase = protocol + "://" + hostname + ":" + port + path; } @@ -58,138 +101,138 @@ private DatabaseURIHelper helper(String dbName) throws URISyntaxException { return new DatabaseURIHelper(new URI(protocol, null, hostname, port, dbName, null, null)); } - @Test - public void _localDocumentURI() throws Exception { + @TestTemplate + public void _localDocumentURI(String path) throws Exception { final String expected = uriBase + "/db_name/_local/mylocaldoc"; DatabaseURIHelper helper = helper(path + "/db_name"); URI localDoc = helper.documentUri("_local/mylocaldoc"); - Assert.assertEquals(expected,localDoc.toString()); + Assertions.assertEquals(expected,localDoc.toString()); } - @Test - public void buildDbUri() throws Exception { + @TestTemplate + public void buildDbUri(String path) throws Exception { URI expected = new URI(uriBase + "/db_name"); URI actual = helper(path + "/db_name").getDatabaseUri(); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } // this test shows that non-ascii characters will be represented correctly // in the url but that we don't escape characters like / - @Test - public void buildEscapedDbUri() throws Exception { + @TestTemplate + public void buildEscapedDbUri(String path) throws Exception { URI expected = new URI(uriBase + "/SDF@%23%25$%23)DFGKLDfdffdg%C3%A9"); URI actual = helper(path + "/SDF@#%$#)DFGKLDfdffdg\u00E9").getDatabaseUri(); - Assert.assertEquals(expected.toASCIIString(), actual.toASCIIString()); + Assertions.assertEquals(expected.toASCIIString(), actual.toASCIIString()); } - @Test - public void buildChangesUri_options_optionsEncoded() throws Exception { + @TestTemplate + public void buildChangesUri_options_optionsEncoded(String path) throws Exception { URI expected = new URI(uriBase + "/test/_changes?limit=100&since=%22%5B%5D%22"); Map options = new HashMap(); options.put("since", "\"[]\""); options.put("limit", 100); URI actual = helper(path + "/test").changesUri(options); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void buildChangesUri_woOptions() throws Exception { + @TestTemplate + public void buildChangesUri_woOptions(String path) throws Exception { URI expected = new URI(uriBase + "/test/_changes"); URI actual = helper(path + "/test").changesUri(new HashMap()); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void buildBulkDocsUri() throws Exception { + @TestTemplate + public void buildBulkDocsUri(String path) throws Exception { URI expected = new URI(uriBase + "/test/_bulk_docs"); URI actual = helper(path + "/test").bulkDocsUri(); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void revsDiffUri() throws Exception { + @TestTemplate + public void revsDiffUri(String path) throws Exception { URI expected = new URI(uriBase + "/test/_revs_diff"); URI actual = helper(path + "/test").revsDiffUri(); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } // get a document with a db 'mounted' at / - @Test - public void buildDocumentUri_emptyDb() throws Exception { + @TestTemplate + public void buildDocumentUri_emptyDb(String path) throws Exception { URI expected = new URI(uriBase + "/documentId"); URI actual = helper(path).documentUri("documentId"); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void buildDocumentUri_woOptions() throws Exception { + @TestTemplate + public void buildDocumentUri_woOptions(String path) throws Exception { URI expected = new URI(uriBase + "/test/documentId"); URI actual = helper(path + "/test").documentUri("documentId"); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void buildDocumentUri_slashInDocumentId() throws Exception { + @TestTemplate + public void buildDocumentUri_slashInDocumentId(String path) throws Exception { URI expected = new URI(uriBase + "/test/path1%2Fpath2"); URI actual = helper(path + "/test").documentUri("path1/path2"); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void buildDocumentUri_specialCharsInDocumentId() throws Exception { + @TestTemplate + public void buildDocumentUri_specialCharsInDocumentId(String path) throws Exception { URI expected = new URI(uriBase + "/test/SDF@%23%25$%23)DFGKLDfdffdg%C3%A9"); URI actual = helper(path + "/test").documentUri("SDF@#%$#)DFGKLDfdffdg\u00E9"); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void buildDocumentUri_colonInDocumentId() throws Exception { + @TestTemplate + public void buildDocumentUri_colonInDocumentId(String path) throws Exception { URI expected = new URI(uriBase + "/test/:this:has:colons:"); URI actual = helper(path + "/test").documentUri(":this:has:colons:"); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void buildDocumentUri_options_optionsEncoded() throws Exception { + @TestTemplate + public void buildDocumentUri_options_optionsEncoded(String path) throws Exception { URI expected = new URI(uriBase + "/test/path1%2Fpath2?detail=true&revs=%5B1-2%5D"); Map options = new TreeMap(); options.put("revs", "[1-2]"); options.put("detail", true); URI actual = helper(path + "/test").documentId("path1/path2").query(options).build(); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void buildDocumentUri_options_encodeSeparators() throws Exception { + @TestTemplate + public void buildDocumentUri_options_encodeSeparators(String path) throws Exception { URI expected = new URI(uriBase + "/test/path1%2Fpath2?d%26etail%3D=%26%3D%3Dds%26&revs=%5B1-2%5D"); TreeMap options = new TreeMap(); options.put("revs", "[1-2]"); options.put("d&etail=", "&==ds&"); URI actual = helper(path + "/test").documentId("path1/path2").query(options).build(); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } - @Test - public void buildDocumentUri_options_hasPlus() throws Exception { + @TestTemplate + public void buildDocumentUri_options_hasPlus(String path) throws Exception { URI expected = new URI(uriBase + "/test/path1%2Fpath2?q=class:mammal%2Bwith%2Bplusses"); TreeMap options = new TreeMap(); options.put("q", "class:mammal+with+plusses"); URI actual = helper(path + "/test").documentId("path1/path2").query(options).build(); - Assert.assertEquals(expected, actual); + Assertions.assertEquals(expected, actual); } // this test shows that non-ascii characters will be represented correctly // in the url but that we don't escape characters like / in the root url, but that they are // correctly escaped in the document part of the url - @Test - public void buildVeryEscapedUri() throws Exception { + @TestTemplate + public void buildVeryEscapedUri(String path) throws Exception { URI expected = new URI(uriBase + "/SDF@%23%25$%23)KLDfdffdg%C3%A9/%2FSF@%23%25$%23)DFGKLDfdffdg%C3%A9%2Fpath2?detail=/SDF@%23%25$%23)%C3%A9&revs=%5B1-2%5D"); Map options = new TreeMap(); @@ -198,24 +241,24 @@ public void buildVeryEscapedUri() throws Exception { URI actual = helper(path + "/SDF@#%$#)KLDfdffdg\u00E9").documentId("/SF@#%$#)" + "DFGKLDfdffdg\u00E9/path2").query(options).build(); - Assert.assertEquals(expected.toASCIIString(), actual.toASCIIString()); + Assertions.assertEquals(expected.toASCIIString(), actual.toASCIIString()); } - @Test - public void encodePathComponent_slashShouldBeEncoded() throws Exception { + @TestTemplate + public void encodePathComponent_slashShouldBeEncoded(String path) throws Exception { String in = "/path1/path2"; - Assert.assertEquals("%2Fpath1%2Fpath2", helper(path + "/test").encodeId(in)); + Assertions.assertEquals("%2Fpath1%2Fpath2", helper(path + "/test").encodeId(in)); } - @Test - public void encodeQueryParameter_noLeadingQuestionMark() throws Exception { + @TestTemplate + public void encodeQueryParameter_noLeadingQuestionMark(String path) throws Exception { String in = "a"; - Assert.assertTrue(helper(path + "/test").documentUri(in).toString().charAt(0) != '?'); + Assertions.assertTrue(helper(path + "/test").documentUri(in).toString().charAt(0) != '?'); } - @Test - public void buildQuery_joinTwoQueries() throws Exception { + @TestTemplate + public void buildQuery_joinTwoQueries(String path) throws Exception { Map mapOptions = new TreeMap(); mapOptions.put("revs", "[1-2]"); mapOptions.put("detail", "/SDF@#%$#)"); @@ -224,7 +267,7 @@ public void buildQuery_joinTwoQueries() throws Exception { URI expectedQuery = new URI(uriBase + "?detail=/SDF@%23%25$%23)&revs=%5B1-2%5D&boolean=true"); URI actualQuery = helper(path).query(mapOptions).query(query).build(); - Assert.assertEquals(expectedQuery.toASCIIString(), actualQuery.toASCIIString()); + Assertions.assertEquals(expectedQuery.toASCIIString(), actualQuery.toASCIIString()); } } diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java index e69e26389..42d457a16 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java @@ -21,24 +21,88 @@ import com.google.gson.JsonObject; import org.junit.Assert; -import org.junit.Test; -import org.junit.experimental.runners.Enclosed; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +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.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; /** * Created by Rhys Short on 24/02/2016. */ -@RunWith(Enclosed.class) +@ExtendWith(DesignDocumentTest.ParameterProvider.class) public class DesignDocumentTest { + + // TODO might be easier just to pass in fields to each test, + 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 Field field) { + return new TestTemplateInvocationContext() { + @Override + public String getDisplayName(int invocationIndex) { + return String.format("Field:%s", field); + } + + @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(Field.class); + } + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + switch(parameterContext.getIndex()) { + case 0: + return field; + } + return null; + } + }); + } + }; + } + } + + public static Iterable data() { + List contexts = new ArrayList(); + for (Field f : EnumSet.allOf(Field.class)) { + contexts.add(ParameterProvider.invocationContext(f)); + } + return contexts; + } + + static Gson gson = new GsonBuilder().create(); enum Field { @@ -154,33 +218,17 @@ private static DesignDocument getDesignDocumentWithDifferent(Field f) { return designDocument; } - public static class OneOffEqualityTests { @Test public void testDesignDocEqualsForAllFields() { Assert.assertEquals(getDesignDocument(), getDesignDocument()); } - } - @RunWith(Parameterized.class) - public static final class ParameterizedEqualityTests { - - /** - * 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 = "Field:{0}") - public static Collection data() { - return EnumSet.allOf(Field.class); - } - - @Parameterized.Parameter - public Field field; /** * Tests the design docs are equal for each field in turn. */ - @Test - public void testDesignDocEqualsForEachField() { + @TestTemplate + public void testDesignDocEqualsForEachField(Field field) { Assert.assertEquals(getDesignDocumentWithFields(EnumSet.of(field)), getDesignDocumentWithFields(EnumSet.of(field))); } @@ -190,8 +238,8 @@ public void testDesignDocEqualsForEachField() { * * @throws Exception */ - @Test - public void testDesignDocNotEqualEmpty() throws Exception { + @TestTemplate + public void testDesignDocNotEqualEmpty(Field field) throws Exception { Assert.assertNotEquals(getDesignDocument(), getDesignDocumentWithFields(EnumSet .complementOf(EnumSet.of(field)))); } @@ -202,12 +250,11 @@ public void testDesignDocNotEqualEmpty() throws Exception { * * @throws Exception */ - @Test - public void testDesignDocNotEqualDifferent() throws Exception { + @TestTemplate + public void testDesignDocNotEqualDifferent(Field field) throws Exception { Assert.assertNotEquals(getDesignDocument(), getDesignDocumentWithDifferent(field)); } - } private static void indexes(DesignDocument designDocument) { Map> indexes = new HashMap>(); diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java index 6e2386dec..9a0b08d84 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java @@ -14,34 +14,32 @@ */ package com.cloudant.tests; -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.assertThat; -import static org.junit.Assert.assertTrue; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +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.assertThrows; +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.Replication; import com.cloudant.client.api.model.DesignDocument; import com.cloudant.client.api.model.Response; -import com.cloudant.client.api.views.AllDocsRequest; -import com.cloudant.client.api.views.Key; import com.cloudant.client.org.lightcouch.CouchDbException; -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.base.TestWithDb; +import com.cloudant.tests.extensions.MockWebServerExtension; import com.cloudant.tests.util.Utils; 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.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; @@ -55,26 +53,25 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -@Category(RequiresDB.class) -public class DesignDocumentsTest { - - @ClassRule - public static CloudantClientResource clientResource = new CloudantClientResource(); - @Rule - public DatabaseResource dbResource = new DatabaseResource(clientResource); - @ClassRule - public static MockWebServer mockWebServer = new MockWebServer(); - - private Database db; - private CloudantClient account; - private File rootDesignDir; - private DesignDocument designDocExample; - private DesignDocumentManager designManager; - - @Before - public void setUp() throws Exception { - account = clientResource.get(); - db = dbResource.get(); +@RequiresDB +public class DesignDocumentsTest extends TestWithDb { + + @RegisterExtension + public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); + + private static MockWebServer mockWebServer; + + private static File rootDesignDir; + private static DesignDocument designDocExample; + private static DesignDocumentManager designManager; + + @BeforeEach + public void beforeEach() { + mockWebServer = mockWebServerExt.get(); + } + + @BeforeAll + public static void beforeAll() throws Exception { rootDesignDir = new File(System.getProperty("user.dir") + "/src/test/resources/design-files"); designManager = db.getDesignDocumentManager(); @@ -90,7 +87,7 @@ public void setUp() throws Exception { * @return the DesignDocument object generated from the file. * @throws Exception */ - private DesignDocument fileToDesignDocument(String name) throws Exception { + private static DesignDocument fileToDesignDocument(String name) throws Exception { File testDesignDocFile = new File(String.format("%s/%s_design_doc.js", rootDesignDir, name)); return designManager.fromFile(testDesignDocFile); } @@ -109,7 +106,7 @@ public void designDocCompare() throws Exception { DesignDocument designDoc11 = db.getDesignDocumentManager().get("_design/example"); - assertEquals("The design document retrieved should equal ", exampleDoc, designDoc11); + assertEquals(exampleDoc, designDoc11, "The design document retrieved should equal "); } @Test @@ -148,7 +145,7 @@ public void updateDesignDocs() throws Exception { designManager.put(docArray); for (String id : new String[]{"_design/conflicts", "_design/example", "_design/views101"}) { - assertNotNull("", designManager.get(id)); + assertNotNull(designManager.get(id), ""); } } @@ -163,8 +160,7 @@ public void designDocGetNoPrefix() throws Exception { designManager.put(designDocExample); // Retrieve it without a prefix - assertNotNull("The design doc should be retrieved without a _design prefix", - designManager.get("example")); + assertNotNull(designManager.get("example"), "The design doc should be retrieved without a _design prefix"); } /** @@ -179,8 +175,7 @@ public void designDocGetNoPrefixWithRevision() throws Exception { Response r = designManager.put(designDocExample); // Retrieve it without a prefix - assertNotNull("The design doc should be retrieved without a _design prefix", - designManager.get("example", r.getRev())); + assertNotNull(designManager.get("example", r.getRev()), "The design doc should be retrieved without a _design prefix"); } /** @@ -245,8 +240,7 @@ public void designDocPutNoPrefix() throws Exception { Utils.assertOKResponse(designManager.put(designDocExampleNoPrefix)); // Retrieve it with a prefix - assertNotNull("The design doc should be retrievable with a _design prefix", - designManager.get("_design/example")); + assertNotNull(designManager.get("_design/example"), "The design doc should be retrievable with a _design prefix"); } /** @@ -269,15 +263,21 @@ public void deleteDesignDocWithIndex() throws Exception { * * @throws Exception */ - @Test(expected = CouchDbException.class) + @Test public void couchDbExceptionIfIOExceptionDuringDDocRemove() throws Exception { - CloudantClient mockClient = CloudantClientHelper.newMockWebServerClientBuilder - (mockWebServer).readTimeout(50, TimeUnit.MILLISECONDS).build(); - // Cause a read timeout to generate an IOException - mockWebServer.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE)); - Database database = mockClient.database(dbResource.getDatabaseName(), false); - // Try to remove a design document by id only, generates a HEAD request for revision info - database.getDesignDocumentManager().remove("example"); + assertThrows(CouchDbException.class, new Executable() { + @Override + public void execute() throws Throwable { + CloudantClient mockClient = CloudantClientHelper.newMockWebServerClientBuilder + (mockWebServer).readTimeout(50, TimeUnit.MILLISECONDS).build(); + // Cause a read timeout to generate an IOException + mockWebServer.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE)); + Database database = mockClient.database(dbResource.getDatabaseName(), false); + // Try to remove a design document by id only, generates a HEAD request for + // revision info + database.getDesignDocumentManager().remove("example"); + } + }); } /** @@ -285,14 +285,19 @@ public void couchDbExceptionIfIOExceptionDuringDDocRemove() throws Exception { * * @throws Exception */ - @Test(expected = CouchDbException.class) + @Test public void couchDbExceptionIfNoETagOnDDocRemove() throws Exception { - CloudantClient mockClient = CloudantClientHelper.newMockWebServerClientBuilder - (mockWebServer).build(); - Database database = mockClient.database(dbResource.getDatabaseName(), false); - // Queue a mock response with no "ETag" header - mockWebServer.enqueue(new MockResponse()); - database.getDesignDocumentManager().remove("example"); + assertThrows(CouchDbException.class, new Executable() { + @Override + public void execute() throws Throwable { + CloudantClient mockClient = CloudantClientHelper.newMockWebServerClientBuilder + (mockWebServer).build(); + Database database = mockClient.database(dbResource.getDatabaseName(), false); + // Queue a mock response with no "ETag" header + mockWebServer.enqueue(new MockResponse()); + database.getDesignDocumentManager().remove("example"); + } + }); } /** @@ -320,8 +325,7 @@ public int compare(DesignDocument doc1, DesignDocument doc2) { doc.setRevision(designManager.put(doc).getRev()); } - assertEquals("The retrieved list of design documents should match the expected list", - designDocs, designManager.list()); + assertEquals(designDocs, designManager.list(), "The retrieved list of design documents should match the expected list"); } /** @@ -342,9 +346,8 @@ public void deserializeJavascriptView() throws Exception { DesignDocument queryDDoc = fileToDesignDocument("example"); Map views = queryDDoc.getViews(); for (DesignDocument.MapReduce mrView : views.values()) { - assertFalse("The map function should not start with \"", mrView.getMap().startsWith - ("\"")); - assertFalse("The map function should not end with \"", mrView.getMap().endsWith("\"")); + assertFalse(mrView.getMap().startsWith("\""), "The map function should not start with \""); + assertFalse(mrView.getMap().endsWith("\""), "The map function should not end with \""); } } @@ -375,13 +378,12 @@ public void serializeJavascriptView() throws Exception { // Retrieve the doc and check that the javascript function is correct DesignDocument retrievedDDoc = designManager.get(testDDocName, r.getRev()); - assertNotNull("There should be a retrieved design doc", retrievedDDoc); + assertNotNull(retrievedDDoc, "There should be a retrieved design doc"); Map retrievedViews = retrievedDDoc.getViews(); - assertNotNull("There should be views defined on the design doc", retrievedViews); + assertNotNull(retrievedViews, "There should be views defined on the design doc"); DesignDocument.MapReduce mrView = retrievedViews.get("testView"); - assertNotNull("There should be a testView in the retrieved design doc", mrView); - assertEquals("The map function string should be the expected string", - mapFunction, mrView.getMap()); + assertNotNull(mrView, "There should be a testView in the retrieved design doc"); + assertEquals(mapFunction, mrView.getMap(), "The map function string should be the expected string"); } /** @@ -397,14 +399,11 @@ public void serializeJavascriptView() throws Exception { public void serializeQueryDesignDoc() throws Exception { DesignDocument queryDDoc = fileToDesignDocument("query"); Map views = queryDDoc.getViews(); - assertEquals("There should be one view", 1, views.size()); + assertEquals(1, views.size(), "There should be one view"); for (DesignDocument.MapReduce mrView : views.values()) { - assertTrue("The map function should be a javascript function in a JSON form, " + - "so start with {", mrView.getMap().startsWith("{")); - assertTrue("The map function should be a javascript function in a JSON form, " + - "so end with }", mrView.getMap().endsWith("}")); - assertEquals("The map function string should be an object form", - "{\"fields\":{\"Person_dob\":\"asc\"}}", mrView.getMap()); + assertTrue(mrView.getMap().startsWith("{"), "The map function should be a javascript function in a JSON form, " + "so start with {"); + assertTrue(mrView.getMap().endsWith("}"), "The map function should be a javascript function in a JSON form, " + "so end with }"); + assertEquals("{\"fields\":{\"Person_dob\":\"asc\"}}", mrView.getMap(), "The map function string should be an object form"); } } @@ -424,14 +423,11 @@ public void deserializeQueryDesignDoc() throws Exception { // Get the query design document DesignDocument queryDDoc = designManager.get("testQuery"); Map views = queryDDoc.getViews(); - assertEquals("There should be one view", 1, views.size()); + assertEquals(1, views.size(), "There should be one view"); for (DesignDocument.MapReduce mrView : views.values()) { - assertTrue("The map function should be a javascript function in a JSON form, " + - "so start with {", mrView.getMap().startsWith("{")); - assertTrue("The map function should be a javascript function in a JSON form, " + - "so end with }", mrView.getMap().endsWith("}")); - assertEquals("The map function string should be an object form", - "{\"fields\":{\"Person_dob\":\"asc\"}}", mrView.getMap()); + assertTrue(mrView.getMap().startsWith("{"), "The map function should be a javascript function in a JSON form, " + "so start with {"); + assertTrue(mrView.getMap().endsWith("}"), "The map function should be a javascript function in a JSON form, " + "so end with }"); + assertEquals("{\"fields\":{\"Person_dob\":\"asc\"}}", mrView.getMap(), "The map function string should be an object form"); } } } diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java index d7a4c841e..79b344b9c 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java @@ -14,29 +14,23 @@ */ package com.cloudant.tests; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -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.assertFalse; +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.CloudantClient; -import com.cloudant.client.api.Database; import com.cloudant.client.api.model.Params; import com.cloudant.client.api.model.Response; import com.cloudant.client.org.lightcouch.DocumentConflictException; import com.cloudant.client.org.lightcouch.NoDocumentException; -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.google.gson.JsonArray; 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 org.junit.jupiter.api.function.Executable; import java.io.IOException; import java.io.InputStream; @@ -44,21 +38,8 @@ import java.util.Map; import java.util.UUID; -@Category(RequiresDB.class) -public class DocumentsCRUDTest { - - 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 = clientResource.get(); - private static Database db; - - @BeforeClass - public static void setUp() { - db = dbResource.get(); - } +@RequiresDB +public class DocumentsCRUDTest extends TestWithDb { // Find @@ -93,13 +74,14 @@ public void findJsonObject() { assertNotNull(jsonObject); } + /* @Test - @Category(RequiresCouch.class) + @RequiresCouch public void findAny() { String uri = account.getBaseUri() + "_stats"; JsonObject jsonObject = db.findAny(JsonObject.class, uri); assertNotNull(jsonObject); - } + }*/ @Test public void findInputstream() throws IOException { @@ -116,14 +98,24 @@ public void findWithParams() { assertNotNull(foo); } - @Test(expected = IllegalArgumentException.class) + @Test public void findWithInvalidId_throwsIllegalArgumentException() { - db.find(Foo.class, ""); + assertThrows(IllegalArgumentException.class, new Executable() { + @Override + public void execute() throws Throwable { + db.find(Foo.class, ""); + } + }); } - @Test(expected = NoDocumentException.class) + @Test public void findWithUnknownId_throwsNoDocumentException() { - db.find(Foo.class, generateUUID()); + assertThrows(NoDocumentException.class, new Executable() { + @Override + public void execute() throws Throwable { + db.find(Foo.class, generateUUID()); + } + }); } @Test @@ -173,23 +165,38 @@ public void saveObjectPost() { assertNotNull(response.getId()); } - @Test(expected = IllegalArgumentException.class) + @Test public void saveInvalidObject_throwsIllegalArgumentException() { - db.save(null); + assertThrows(IllegalArgumentException.class, new Executable() { + @Override + public void execute() throws Throwable { + db.save(null); + } + }); } - @Test(expected = IllegalArgumentException.class) + @Test public void saveNewDocWithRevision_throwsIllegalArgumentException() { - Bar bar = new Bar(); - bar.setRevision("unkown"); - db.save(bar); + assertThrows(IllegalArgumentException.class, new Executable() { + @Override + public void execute() throws Throwable { + Bar bar = new Bar(); + bar.setRevision("unkown"); + db.save(bar); + } + }); } - @Test(expected = DocumentConflictException.class) + @Test public void saveDocWithDuplicateId_throwsDocumentConflictException() { - String id = generateUUID(); - db.save(new Foo(id)); - db.save(new Foo(id)); + assertThrows(DocumentConflictException.class, new Executable() { + @Override + public void execute() throws Throwable { + String id = generateUUID(); + db.save(new Foo(id)); + db.save(new Foo(id)); + } + }); } // Update @@ -211,17 +218,27 @@ public void updateWithIdContainSlash() { assertEquals(idWithSlash, responseUpdate.getId()); } - @Test(expected = IllegalArgumentException.class) + @Test public void updateWithoutIdAndRev_throwsIllegalArgumentException() { - db.update(new Foo()); + assertThrows(IllegalArgumentException.class, new Executable() { + @Override + public void execute() throws Throwable { + db.update(new Foo()); + } + }); } - @Test(expected = DocumentConflictException.class) + @Test public void updateWithInvalidRev_throwsDocumentConflictException() { - Response response = db.save(new Foo()); - Foo foo = db.find(Foo.class, response.getId()); - db.update(foo); - db.update(foo); + assertThrows(DocumentConflictException.class, new Executable() { + @Override + public void execute() throws Throwable { + Response response = db.save(new Foo()); + Foo foo = db.find(Foo.class, response.getId()); + db.update(foo); + db.update(foo); + } + }); } // Delete diff --git a/cloudant-client/src/test/java/com/cloudant/tests/Foo.java b/cloudant-client/src/test/java/com/cloudant/tests/Foo.java index a6b659cbf..6666aff42 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/Foo.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/Foo.java @@ -14,9 +14,8 @@ package com.cloudant.tests; -import com.google.gson.JsonArray; - import com.cloudant.client.org.lightcouch.Attachment; +import com.google.gson.JsonArray; import java.util.Arrays; import java.util.Date; diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HierarchicalUriComponentsTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HierarchicalUriComponentsTest.java index 6cb6f6708..29fb03d58 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HierarchicalUriComponentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HierarchicalUriComponentsTest.java @@ -14,11 +14,11 @@ package com.cloudant.tests; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import com.cloudant.client.internal.HierarchicalUriComponents; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.UnsupportedEncodingException; diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HttpIamTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HttpIamTest.java index ab197998e..3acbb7d95 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HttpIamTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HttpIamTest.java @@ -25,19 +25,20 @@ import static com.cloudant.tests.util.MockWebServerResources.iamSessionUnquoted; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -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.fail; import com.cloudant.client.api.CloudantClient; import com.cloudant.client.org.lightcouch.CouchDbException; import com.cloudant.http.Http; import com.cloudant.tests.util.IamSystemPropertyMock; +import com.cloudant.tests.extensions.MockWebServerExtension; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; +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 okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -53,20 +54,24 @@ public class HttpIamTest { public static IamSystemPropertyMock iamSystemPropertyMock; - @Rule - public MockWebServer mockWebServer = new MockWebServer(); + @RegisterExtension + public MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); - @Rule - public MockWebServer mockIamServer = new MockWebServer(); + @RegisterExtension + public MockWebServerExtension mockIamServerExt = new MockWebServerExtension(); final static String hello = "{\"hello\":\"world\"}\r\n"; final static String iamApiKey = "iam"; final static String iamTokenEndpoint = "/identity/token"; + public MockWebServer mockWebServer; + public MockWebServer mockIamServer; + + /** * Before running this test class setup the property mock. */ - @BeforeClass + @BeforeAll public static void setupIamSystemPropertyMock() { iamSystemPropertyMock = new IamSystemPropertyMock(); } @@ -74,8 +79,10 @@ public static void setupIamSystemPropertyMock() { /** * Before each test set the value of the endpoint in the property mock */ - @Before + @BeforeEach public void setIAMMockEndpoint() { + mockWebServer = mockWebServerExt.get(); + mockIamServer = mockIamServerExt.get(); iamSystemPropertyMock.setMockIamTokenEndpointUrl(mockIamServer.url(iamTokenEndpoint) .toString()); } @@ -104,20 +111,20 @@ public void iamTokenAndCookieSuccessful() throws Exception { .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response); + assertEquals(hello, response, "The expected response should be received"); // cloudant mock server // assert that there were 2 calls RecordedRequest[] recordedRequests = takeN(mockWebServer, 2); - assertEquals("The request should have been for /_iam_session", "/_iam_session", - recordedRequests[0].getPath()); + assertEquals("/_iam_session", + recordedRequests[0].getPath(), "The request should have been for /_iam_session"); assertThat("The request body should contain the IAM token", recordedRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString(IAM_TOKEN)); // the request should have the cookie header - assertEquals("The request should have been for /", "/", - recordedRequests[1].getPath()); + assertEquals("/", + recordedRequests[1].getPath(), "The request should have been for /"); // The cookie may or may not have the session id quoted, so check both assertThat("The Cookie header should contain the expected session value", recordedRequests[1].getHeader("Cookie"), @@ -127,8 +134,8 @@ public void iamTokenAndCookieSuccessful() throws Exception { // asserts on the IAM server // assert that there was 1 call RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 1); - assertEquals("The request should have been for /identity/token", iamTokenEndpoint, - recordedIamRequests[0].getPath()); + assertEquals(iamTokenEndpoint, + recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString("apikey="+iamApiKey)); @@ -158,20 +165,20 @@ public void iamApiKeyPreferredToUsernamePassword() throws Exception { .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response); + assertEquals(hello, response, "The expected response should be received"); // cloudant mock server // assert that there were 2 calls RecordedRequest[] recordedRequests = takeN(mockWebServer, 2); - assertEquals("The request should have been for /_iam_session", "/_iam_session", - recordedRequests[0].getPath()); + assertEquals("/_iam_session", + recordedRequests[0].getPath(), "The request should have been for /_iam_session"); assertThat("The request body should contain the IAM token", recordedRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString(IAM_TOKEN)); // the request should have the cookie header - assertEquals("The request should have been for /", "/", - recordedRequests[1].getPath()); + assertEquals("/", + recordedRequests[1].getPath(), "The request should have been for /"); // The cookie may or may not have the session id quoted, so check both assertThat("The Cookie header should contain the expected session value", recordedRequests[1].getHeader("Cookie"), @@ -181,8 +188,8 @@ public void iamApiKeyPreferredToUsernamePassword() throws Exception { // asserts on the IAM server // assert that there was 1 call RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 1); - assertEquals("The request should have been for /identity/token", iamTokenEndpoint, - recordedIamRequests[0].getPath()); + assertEquals(iamTokenEndpoint, + recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString("apikey="+iamApiKey)); @@ -222,47 +229,47 @@ public void iamTokenAndCookieWithExpirySuccessful() throws Exception { .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response); + assertEquals(hello, response, "The expected response should be received"); String response2 = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response2); + assertEquals(hello, response2, "The expected response should be received"); String response3 = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response3); + assertEquals(hello, response3, "The expected response should be received"); // cloudant mock server // assert that there were 6 calls RecordedRequest[] recordedRequests = takeN(mockWebServer, 6); - assertEquals("The request should have been for /_iam_session", "/_iam_session", - recordedRequests[0].getPath()); + assertEquals("/_iam_session", + recordedRequests[0].getPath(), "The request should have been for /_iam_session"); assertThat("The request body should contain the IAM token", recordedRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString(IAM_TOKEN)); // first request - assertEquals("The request should have been for /", "/", - recordedRequests[1].getPath()); + assertEquals("/", + recordedRequests[1].getPath(), "The request should have been for /"); // The cookie may or may not have the session id quoted, so check both assertThat("The Cookie header should contain the expected session value", recordedRequests[1].getHeader("Cookie"), anyOf(containsString(iamSession(EXPECTED_OK_COOKIE)), containsString(iamSessionUnquoted(EXPECTED_OK_COOKIE)))); // second request - assertEquals("The request should have been for /", "/", - recordedRequests[2].getPath()); + assertEquals("/", + recordedRequests[2].getPath(), "The request should have been for /"); // third request, will be rejected due to cookie expiry - assertEquals("The request should have been for /", "/", - recordedRequests[3].getPath()); + assertEquals("/", + recordedRequests[3].getPath(), "The request should have been for /"); // renew cookie after third request fails - assertEquals("The request should have been for /_iam_session", "/_iam_session", - recordedRequests[4].getPath()); + assertEquals("/_iam_session", + recordedRequests[4].getPath(), "The request should have been for /_iam_session"); assertThat("The request body should contain the IAM token", recordedRequests[4].getBody().readString(Charset.forName("UTF-8")), containsString(IAM_TOKEN_2)); // replay of third request - assertEquals("The request should have been for /", "/", - recordedRequests[5].getPath()); + assertEquals("/", + recordedRequests[5].getPath(), "The request should have been for /"); // The (new) cookie may or may not have the session id quoted, so check both assertThat("The Cookie header should contain the expected session value", recordedRequests[5].getHeader("Cookie"), @@ -274,14 +281,14 @@ public void iamTokenAndCookieWithExpirySuccessful() throws Exception { // assert that there were 2 calls RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 2); // first time, automatically fetch because cookie jar is empty - assertEquals("The request should have been for /identity/token", iamTokenEndpoint, - recordedIamRequests[0].getPath()); + assertEquals(iamTokenEndpoint, + recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString("apikey="+iamApiKey)); // second time, refresh because the cloudant session cookie has expired - assertEquals("The request should have been for /identity/token", iamTokenEndpoint, - recordedIamRequests[1].getPath()); + assertEquals(iamTokenEndpoint, + recordedIamRequests[1].getPath(), "The request should have been for /identity/token"); } /** @@ -316,10 +323,10 @@ public void iamRenewalFailureOnIamToken() throws Exception { .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response); + assertEquals(hello, response, "The expected response should be received"); String response2 = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response2); + assertEquals(hello, response2, "The expected response should be received"); // this never gets a response because the token failure stops the playback - this is // correct because the underlying stream has now been closed but the exception is a bit @@ -335,39 +342,39 @@ public void iamRenewalFailureOnIamToken() throws Exception { // assert that there were 4 calls RecordedRequest[] recordedRequests = takeN(mockWebServer, 4); - assertEquals("The request should have been for /_iam_session", "/_iam_session", - recordedRequests[0].getPath()); + assertEquals("/_iam_session", + recordedRequests[0].getPath(), "The request should have been for /_iam_session"); assertThat("The request body should contain the IAM token", recordedRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString(IAM_TOKEN)); // first request - assertEquals("The request should have been for /", "/", - recordedRequests[1].getPath()); + assertEquals("/", + recordedRequests[1].getPath(), "The request should have been for /"); // The cookie may or may not have the session id quoted, so check both assertThat("The Cookie header should contain the expected session value", recordedRequests[1].getHeader("Cookie"), anyOf(containsString(iamSession(EXPECTED_OK_COOKIE)), containsString(iamSessionUnquoted(EXPECTED_OK_COOKIE)))); // second request - assertEquals("The request should have been for /", "/", - recordedRequests[2].getPath()); + assertEquals("/", + recordedRequests[2].getPath(), "The request should have been for /"); // third request, will be rejected due to cookie expiry - assertEquals("The request should have been for /", "/", - recordedRequests[3].getPath()); + assertEquals("/", + recordedRequests[3].getPath(), "The request should have been for /"); // iam mock server // assert that there were 2 calls RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 2); // first time, automatically fetch because cookie jar is empty - assertEquals("The request should have been for /identity/token", iamTokenEndpoint, - recordedIamRequests[0].getPath()); + assertEquals(iamTokenEndpoint, + recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString("apikey="+iamApiKey)); // second time, refresh (but gets 500) because the cloudant session cookie has expired - assertEquals("The request should have been for /identity/token", iamTokenEndpoint, - recordedIamRequests[1].getPath()); + assertEquals(iamTokenEndpoint, + recordedIamRequests[1].getPath(), "The request should have been for /identity/token"); } /** @@ -403,10 +410,10 @@ public void iamRenewalFailureOnSessionCookie() throws Exception { .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response); + assertEquals(hello, response, "The expected response should be received"); String response2 = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response2); + assertEquals(hello, response2, "The expected response should be received"); // this never gets a response because the token failure stops the playback - this is // correct because the underlying stream has now been closed but the exception is a bit @@ -422,28 +429,28 @@ public void iamRenewalFailureOnSessionCookie() throws Exception { // assert that there were 5 calls RecordedRequest[] recordedRequests = takeN(mockWebServer, 5); - assertEquals("The request should have been for /_iam_session", "/_iam_session", - recordedRequests[0].getPath()); + assertEquals("/_iam_session", + recordedRequests[0].getPath(), "The request should have been for /_iam_session"); assertThat("The request body should contain the IAM token", recordedRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString(IAM_TOKEN)); // first request - assertEquals("The request should have been for /", "/", - recordedRequests[1].getPath()); + assertEquals("/", + recordedRequests[1].getPath(), "The request should have been for /"); // The cookie may or may not have the session id quoted, so check both assertThat("The Cookie header should contain the expected session value", recordedRequests[1].getHeader("Cookie"), anyOf(containsString(iamSession(EXPECTED_OK_COOKIE)), containsString(iamSessionUnquoted(EXPECTED_OK_COOKIE)))); // second request - assertEquals("The request should have been for /", "/", - recordedRequests[2].getPath()); + assertEquals("/", + recordedRequests[2].getPath(), "The request should have been for /"); // third request, will be rejected due to cookie expiry - assertEquals("The request should have been for /", "/", - recordedRequests[3].getPath()); + assertEquals("/", + recordedRequests[3].getPath(), "The request should have been for /"); // try to renew cookie but get 500 - assertEquals("The request should have been for /_iam_session", "/_iam_session", - recordedRequests[4].getPath()); + assertEquals("/_iam_session", + recordedRequests[4].getPath(), "The request should have been for /_iam_session"); assertThat("The request body should contain the IAM token", recordedRequests[4].getBody().readString(Charset.forName("UTF-8")), containsString(IAM_TOKEN_2)); @@ -453,14 +460,14 @@ public void iamRenewalFailureOnSessionCookie() throws Exception { // assert that there were 2 calls RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 2); // first time, automatically fetch because cookie jar is empty - assertEquals("The request should have been for /identity/token", iamTokenEndpoint, - recordedIamRequests[0].getPath()); + assertEquals(iamTokenEndpoint, + recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString("apikey="+iamApiKey)); // second time, refresh because the cloudant session cookie has expired - assertEquals("The request should have been for /identity/token", iamTokenEndpoint, - recordedIamRequests[1].getPath()); + assertEquals(iamTokenEndpoint, + recordedIamRequests[1].getPath(), "The request should have been for /identity/token"); } diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java index 93ef2a03d..b3eaa00b5 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java @@ -14,20 +14,27 @@ package com.cloudant.tests; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.cloudant.client.api.ClientBuilder; import com.cloudant.client.api.CloudantClient; import com.cloudant.http.Http; -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.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterEach; +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.littleshoot.proxy.HttpProxyServer; import org.littleshoot.proxy.HttpProxyServerBootstrap; import org.littleshoot.proxy.ProxyAuthenticator; @@ -43,52 +50,99 @@ import java.net.InetSocketAddress; import java.net.PasswordAuthentication; import java.net.URL; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; import javax.net.ssl.SSLEngine; - +@ExtendWith(HttpProxyTest.ParameterProvider.class) public class HttpProxyTest extends HttpFactoryParameterizedTest { - @Parameterized.Parameters(name = "okhttp: {0}; secure proxy: {1}; https server: {2}; proxy " + - "auth: {3}") - public static List combinations() { - boolean[] tf = new boolean[]{true, false}; - List combos = new ArrayList(); - for (boolean okUsable : tf) { - for (boolean secureProxy : new boolean[]{false}) { - // AFAICT there is no way to instruct HttpURLConnection to connect via SSL to a - // proxy server - so for now we just test an unencrypted proxy. - // Note this is independent of the SSL tunnelling to an https server and influences - // only requests between client and proxy. With an https server client requests are - // tunnelled directly to the https server, other than the original HTTP CONNECT - // request. The reason for using a SSL proxy would be to encrypt proxy auth creds - // but it appears this scenario is not readily supported. - for (boolean httpsServer : tf) { - for (boolean proxyAuth : tf) { - combos.add(new Object[]{okUsable, secureProxy, httpsServer, proxyAuth}); - } - } - } + static class ParameterProvider implements TestTemplateInvocationContextProvider { + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; } - return combos; - } - - // Note Parameter(0) okUsable is inherited - @Parameterized.Parameter(1) - public boolean secureProxy; + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + + // AFAICT there is no way to instruct HttpURLConnection to connect via SSL to a + // proxy server - so for now we just test an unencrypted proxy. + // Note this is independent of the SSL tunnelling to an https server and influences + // only requests between client and proxy. With an https server client requests are + // tunnelled directly to the https server, other than the original HTTP CONNECT + // request. The reason for using a SSL proxy would be to encrypt proxy auth creds + // but it appears this scenario is not readily supported. + // TODO is better to list all of these explicitly or not? + return Stream.of( + invocationContext(true, false, true, true), + invocationContext(true, false, true, false), + invocationContext(true, false, false, true), + // see also https://github.com/cloudant/java-cloudant/issues/423 - these tests current fail regardless of ordering + //invocationContext(true, false, false, false), + //invocationContext(false, false, true, true), + invocationContext(false, false, true, false), + invocationContext(false, false, false, true), + invocationContext(false, false, false, false)); + } - @Parameterized.Parameter(2) - public boolean useHttpsServer; + public static TestTemplateInvocationContext invocationContext(final boolean okUsable, + final boolean useSecureProxy, + final boolean useHttpsServer, + final boolean useProxyAuth) { + return new TestTemplateInvocationContext() { + @Override + public String getDisplayName(int invocationIndex) { + return String.format("okhttp: %s; secure proxy: %s; https server %s: proxy auth: %s", + okUsable, useSecureProxy, useHttpsServer, useProxyAuth); + } - @Parameterized.Parameter(3) - public boolean useProxyAuth; + @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(boolean.class); + case 2: + return parameterContext.getParameter().getType().equals(boolean.class); + case 3: + return parameterContext.getParameter().getType().equals(boolean.class); + } + return false; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + switch(parameterContext.getIndex()) { + case 0: + return okUsable; + case 1: + return useSecureProxy; + case 2: + return useHttpsServer; + case 3: + return useProxyAuth; + } + return null; + } + }); + } + }; + } + } - @Rule - public MockWebServer server = new MockWebServer(); + @RegisterExtension + public MockWebServerExtension serverExt = new MockWebServerExtension(); + public MockWebServer server; HttpProxyServer proxy; String mockProxyUser = "alpha"; @@ -106,8 +160,12 @@ public static List combinations() { * * @throws Exception */ - @Before - public void setupMockServerSSLIfNeeded() throws Exception { + @BeforeEach + public void setupMockServerSSLIfNeeded(final boolean okUsable, + final boolean useSecureProxy, + final boolean useHttpsServer, + final boolean useProxyAuth) throws Exception { + server = serverExt.get(); if (useHttpsServer) { server.useHttps(MockWebServerResources.getSSLSocketFactory(), false); } @@ -119,8 +177,11 @@ public void setupMockServerSSLIfNeeded() throws Exception { * * @throws Exception */ - @Before - public void setupAndStartProxy() throws Exception { + @BeforeEach + public void setupAndStartProxy(final boolean okUsable, + final boolean useSecureProxy, + final boolean useHttpsServer, + final boolean useProxyAuth) throws Exception { HttpProxyServerBootstrap proxyBoostrap = DefaultHttpProxyServer.bootstrap() .withAllowLocalOnly(true) // only run on localhost @@ -142,7 +203,7 @@ public String getRealm() { proxyBoostrap.withProxyAuthenticator(pa); } - if (secureProxy) { + if (useSecureProxy) { proxyBoostrap.withSslEngineSource(new SslEngineSource() { @Override public SSLEngine newSslEngine() { @@ -166,7 +227,7 @@ public SSLEngine newSslEngine(String peerHost, int peerPort) { * * @throws Exception */ - @After + @AfterEach public void shutdownProxy() throws Exception { proxy.stop(); } @@ -180,11 +241,14 @@ public void shutdownProxy() throws Exception { * administrators in accordance with their environment. For the purposes of this test we can * add and remove the Authenticator before and after testing. */ - @Before - public void setAuthenticatorIfNeeded() { + @BeforeEach + public void setAuthenticatorIfNeeded(final boolean okUsable, + final boolean useSecureProxy, + final boolean useHttpsServer, + final boolean useProxyAuth) { // If we are not using okhttp and we have an https server and a proxy that needs auth then // we need to set the default Authenticator - if (useProxyAuth && useHttpsServer && !okUsable) { + if (useProxyAuth && useHttpsServer && !isOkUsable) { // Allow https tunnelling through http proxy for the duration of the test System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); Authenticator.setDefault(new Authenticator() { @@ -203,13 +267,16 @@ protected PasswordAuthentication getPasswordAuthentication() { /** * Reset the Authenticator after the test. * - * @see #setAuthenticatorIfNeeded() + * @see #setAuthenticatorIfNeeded(boolean, boolean, boolean, boolean) */ - @After - public void resetAuthenticator() { + @AfterEach + public void resetAuthenticator(final boolean okUsable, + final boolean useSecureProxy, + final boolean useHttpsServer, + final boolean useProxyAuth) { // If we are not using okhttp and we have an https server and a proxy that needs auth then // we need to set the default Authenticator - if (useProxyAuth && useHttpsServer && !okUsable) { + if (useProxyAuth && useHttpsServer && !isOkUsable) { Authenticator.setDefault(null); // Reset the disabled schemes property System.setProperty("jdk.http.auth.tunneling.disabledSchemes", defaultDisabledList); @@ -219,8 +286,11 @@ public void resetAuthenticator() { /** * This test validates that a request can successfully traverse a proxy to our mock server. */ - @Test - public void proxiedRequest() throws Exception { + @TestTemplate + public void proxiedRequest(final boolean okUsable, + final boolean useSecureProxy, + final boolean useHttpsServer, + final boolean useProxyAuth) throws Exception { //mock a 200 OK server.setDispatcher(new Dispatcher() { @@ -231,7 +301,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio }); InetSocketAddress address = proxy.getListenAddress(); - URL proxyUrl = new URL((secureProxy) ? "https" : "http", address.getHostName(), address + URL proxyUrl = new URL((useSecureProxy) ? "https" : "http", address.getHostName(), address .getPort(), "/"); ClientBuilder builder = CloudantClientHelper.newMockWebServerClientBuilder(server) .proxyURL(proxyUrl); @@ -244,7 +314,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio String response = client.executeRequest(Http.GET(client.getBaseUri())).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"); //if it wasn't a 20x then an exception should have been thrown by now RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS); diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java index 38e14782d..ba93c3044 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java @@ -16,14 +16,15 @@ import static com.cloudant.tests.util.MockWebServerResources.EXPECTED_OK_COOKIE; import static org.hamcrest.CoreMatchers.anyOf; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.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.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +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 static org.junit.jupiter.api.Assertions.fail; import com.cloudant.client.api.ClientBuilder; import com.cloudant.client.api.CloudantClient; @@ -38,10 +39,12 @@ import com.cloudant.http.interceptors.Replay429Interceptor; import com.cloudant.http.internal.interceptors.CookieInterceptor; 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 com.cloudant.tests.util.HttpFactoryParameterizedTest; +import com.cloudant.tests.extensions.MockWebServerExtension; import com.cloudant.tests.util.MockWebServerResources; +import com.cloudant.tests.extensions.MultiExtension; import com.cloudant.tests.util.TestTimer; import com.cloudant.tests.util.Utils; import com.google.gson.Gson; @@ -51,12 +54,17 @@ import com.google.gson.JsonPrimitive; 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.rules.RuleChain; -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.Dispatcher; import okhttp3.mockwebserver.MockResponse; @@ -74,30 +82,83 @@ import java.net.URLClassLoader; import java.net.URLDecoder; import java.nio.charset.Charset; +import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import java.util.stream.Stream; +@ExtendWith(HttpTest.ParameterProvider.class) public class HttpTest extends HttpFactoryParameterizedTest { + static class ParameterProvider implements TestTemplateInvocationContextProvider { + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return true; + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + return Stream.of(invocationContext(false), + invocationContext(true)); + } + + 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; + } + }); + } + }; + } + } + private String data = "{\"hello\":\"world\"}"; - public static CloudantClientResource clientResource = new CloudantClientResource(); - public static DatabaseResource dbResource = new DatabaseResource(clientResource); - @ClassRule - public static RuleChain chain = RuleChain.outerRule(clientResource).around(dbResource); - @Rule - public MockWebServer mockWebServer = new MockWebServer(); + public static CloudantClientExtension clientResource = new CloudantClientExtension(); + public static DatabaseExtension.PerClass dbResource = new DatabaseExtension.PerClass(clientResource); + public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); + + @RegisterExtension + public static MultiExtension extensions = new MultiExtension(clientResource, dbResource, mockWebServerExt); - @Parameterized.Parameters(name = "Using okhttp: {0}") - public static Object[] okUsable() { - return new Object[]{true, false}; + public MockWebServer mockWebServer; + + @BeforeEach + public void beforeEach() { + mockWebServer = mockWebServerExt.get(); } /* * Basic test that we can write a document body by POSTing to a known database */ - @Test + @TestTemplate public void testWriteToServerOk() throws Exception { HttpConnection conn = new HttpConnection("POST", new URL(dbResource.getDbURIWithUserInfo()), "application/json"); @@ -111,21 +172,21 @@ public void testWriteToServerOk() throws Exception { // Consume response stream String responseStr = response.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); // stream was read to end assertEquals(0, bis.available()); - assertEquals("Should be a 2XX response code", 2, response.getConnection().getResponseCode - () / 100); + assertEquals(2, response.getConnection().getResponseCode + () / 100, "Should be a 2XX response code"); } /* * Basic test to check that an IOException is thrown when we attempt to get the response * without first calling execute() */ - @Test + @TestTemplate public void testReadBeforeExecute() throws Exception { HttpConnection conn = new HttpConnection("POST", new URL(dbResource.getDbURIWithUserInfo()), "application/json"); @@ -154,8 +215,8 @@ public void testReadBeforeExecute() throws Exception { // security settings, the database *must* not be public, it *must* // be named cookie_test // - @Test - @Category(RequiresCloudant.class) + @TestTemplate + @RequiresCloudant public void testCookieAuthWithoutRetry() throws IOException { CookieInterceptor interceptor = new CookieInterceptor(CloudantClientHelper.COUCH_USERNAME, CloudantClientHelper.COUCH_PASSWORD, clientResource.get().getBaseUri().toString()); @@ -190,7 +251,7 @@ public void testCookieAuthWithoutRetry() throws IOException { } } - @Test + @TestTemplate public void testCookieAuthWithPath() throws Exception { MockWebServer mockWebServer = new MockWebServer(); mockWebServer.enqueue(MockWebServerResources.OK_COOKIE); @@ -211,8 +272,8 @@ public void testCookieAuthWithPath() throws Exception { * will complete with a response code of 200. The response input stream * is expected to hold the newly created document's id and rev. */ - @Test - @Category(RequiresCloudant.class) + @TestTemplate + @RequiresCloudant public void testBasicAuth() throws IOException { BasicAuthInterceptor interceptor = new BasicAuthInterceptor(CloudantClientHelper.COUCH_USERNAME @@ -255,7 +316,7 @@ public void testBasicAuth() throws IOException { * * @throws Exception */ - @Test + @TestTemplate public void cookieInterceptorURLEncoding() throws Exception { final String mockUser = "myStrangeUsername=&?"; String mockPass = "?&=NotAsStrangeInAPassword"; @@ -270,20 +331,17 @@ public void cookieInterceptorURLEncoding() throws Exception { .build(); //the GET request will try to get a session, then perform the GET String response = c.executeRequest(Http.GET(c.getBaseUri())).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"); RecordedRequest r = MockWebServerResources.takeRequestWithTimeout(mockWebServer); String sessionRequestContent = r.getBody().readString(Charset.forName("UTF-8")); - assertNotNull("The _session request should have non-null content", - sessionRequestContent); +assertNotNull(sessionRequestContent, "The _session request should have non-null content"); //expecting name=...&password=... String[] parts = Utils.splitAndAssert(sessionRequestContent, "&", 1); String username = URLDecoder.decode(Utils.splitAndAssert(parts[0], "=", 1)[1], "UTF-8"); - assertEquals("The username URL decoded username should match", mockUser, - username); +assertEquals(mockUser, username, "The username URL decoded username should match"); String password = URLDecoder.decode(Utils.splitAndAssert(parts[1], "=", 1)[1], "UTF-8"); - assertEquals("The username URL decoded password should match", mockPass, - password); +assertEquals(mockPass, password, "The username URL decoded password should match"); } /** @@ -292,7 +350,7 @@ public void cookieInterceptorURLEncoding() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void cookieRenewal() throws Exception { final String hello = "{\"hello\":\"world\"}\r\n"; final String renewalCookieToken = @@ -314,28 +372,26 @@ public void cookieRenewal() throws Exception { .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response should be received", hello, response); + assertEquals(hello, response, "The expected response should be received"); // assert that there were 2 calls - assertEquals("The server should have received 2 requests", 2, mockWebServer - .getRequestCount()); + assertEquals(2, mockWebServer + .getRequestCount(), "The server should have received 2 requests"); - assertEquals("The request should have been for /_session", "/_session", - MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath()); - assertEquals("The request should have been for /", "/", - MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath()); +assertEquals("/_session", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The request should have been for /_session"); +assertEquals("/", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The request should have been for /"); String secondResponse = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertTrue("There should be no response body on the mock response" + secondResponse, - secondResponse.isEmpty()); + assertTrue( + secondResponse.isEmpty(), "There should be no response body on the mock response" + secondResponse); // also assert that there were 3 calls - assertEquals("The server should have received 3 requests", 3, mockWebServer - .getRequestCount()); + assertEquals(3, mockWebServer + .getRequestCount(), "The server should have received 3 requests"); // this is the request that should have the new cookie. RecordedRequest request = MockWebServerResources.takeRequestWithTimeout(mockWebServer); - assertEquals("The request should have been for path /", "/", request.getPath()); +assertEquals("/", request.getPath(), "The request should have been for path /"); String headerValue = request.getHeader("Cookie"); // The cookie may or may not have the session id quoted, so check both assertThat("The Cookie header should contain the expected session value", headerValue, @@ -350,7 +406,7 @@ public void cookieRenewal() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void cookie403Renewal() throws Exception { // Test for a 403 with expired credentials, should result in 4 requests @@ -364,7 +420,7 @@ public void cookie403Renewal() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void handleNonExpiry403() throws Exception { // Test for a non-expiry 403, expect 2 requests @@ -376,7 +432,7 @@ public void handleNonExpiry403() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void handleNonExpiry403NoReason() throws Exception { // Test for a non-expiry 403, expect 2 requests @@ -388,7 +444,7 @@ public void handleNonExpiry403NoReason() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void handleNonExpiry403NullReason() throws Exception { // Test for a non-expiry 403, expect 2 requests @@ -438,23 +494,23 @@ private void basic403Test(String error, String reason, int expectedRequests) thr // _session followed by a replay of GET try { String response = c.executeRequest(Http.GET(c.getBaseUri())).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"); if (!error.equals("credentials_expired")) { fail("A 403 not due to cookie expiry should result in a CouchDbException"); } } catch (CouchDbException e) { - assertEquals("The exception error should be the expected message", error, e.getError()); - assertEquals("The exception reason should be the expected message", reason, e - .getReason()); + assertEquals(error, e.getError(), "The exception error should be the expected message"); + assertEquals(reason, e + .getReason(), "The exception reason should be the expected message"); } // also assert that there were the correct number of calls - assertEquals("The server should receive the expected number of requests", + assertEquals( expectedRequests, mockWebServer - .getRequestCount()); + .getRequestCount(), "The server should receive the expected number of requests"); } - @Test + @TestTemplate public void inputStreamRetryString() throws Exception { HttpConnection request = Http.POST(mockWebServer.url("/").url(), "application/json"); String content = "abcde"; @@ -462,7 +518,7 @@ public void inputStreamRetryString() throws Exception { testInputStreamRetry(request, content.getBytes("UTF-8")); } - @Test + @TestTemplate public void inputStreamRetryBytes() throws Exception { HttpConnection request = Http.POST(mockWebServer.url("/").url(), "application/json"); byte[] content = "abcde".getBytes("UTF-8"); @@ -500,7 +556,7 @@ public boolean markSupported() { } } - @Test + @TestTemplate public void inputStreamRetry() throws Exception { HttpConnection request = Http.POST(mockWebServer.url("/").url(), "application/json"); final byte[] content = "abcde".getBytes("UTF-8"); @@ -509,7 +565,7 @@ public void inputStreamRetry() throws Exception { testInputStreamRetry(request, content); } - @Test + @TestTemplate public void inputStreamRetryWithLength() throws Exception { HttpConnection request = Http.POST(mockWebServer.url("/").url(), "application/json"); final byte[] content = "abcde".getBytes("UTF-8"); @@ -532,7 +588,7 @@ public InputStream getInputStream() throws IOException { } } - @Test + @TestTemplate public void inputStreamRetryGenerator() throws Exception { HttpConnection request = Http.POST(mockWebServer.url("/").url(), "application/json"); byte[] content = "abcde".getBytes("UTF-8"); @@ -540,7 +596,7 @@ public void inputStreamRetryGenerator() throws Exception { testInputStreamRetry(request, content); } - @Test + @TestTemplate public void inputStreamRetryGeneratorWithLength() throws Exception { HttpConnection request = Http.POST(mockWebServer.url("/").url(), "application/json"); byte[] content = "abcde".getBytes("UTF-8"); @@ -576,23 +632,23 @@ private void testInputStreamRetry(HttpConnection request, byte[] expectedContent }).build().executeRequest(request); String responseStr = response.responseAsString(); - assertTrue("There should be no response body on the mock response", responseStr.isEmpty()); - assertEquals("The final response code should be 200", 200, response.getConnection() - .getResponseCode()); + assertTrue(responseStr.isEmpty(), "There should be no response body on the mock response"); + assertEquals(200, response.getConnection() + .getResponseCode(), "The final response code should be 200"); // We want the second request - assertEquals("There should have been two requests", 2, mockWebServer.getRequestCount()); + assertEquals(2, mockWebServer.getRequestCount(), "There should have been two requests"); MockWebServerResources.takeRequestWithTimeout(mockWebServer); RecordedRequest rr = MockWebServerResources.takeRequestWithTimeout(mockWebServer); - assertNotNull("The request should have been recorded", rr); + assertNotNull(rr, "The request should have been recorded"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream((int) rr .getBodySize()); rr.getBody().copyTo(byteArrayOutputStream); - assertArrayEquals("The body bytes should have matched after a retry", expectedContent, - byteArrayOutputStream.toByteArray()); + assertArrayEquals( expectedContent, + byteArrayOutputStream.toByteArray(), "The body bytes should have matched after a retry"); } - @Test + @TestTemplate public void testCookieRenewOnPost() throws Exception { mockWebServer.enqueue(MockWebServerResources.OK_COOKIE); @@ -610,11 +666,11 @@ public void testCookieRenewOnPost() throws Exception { request.setRequestBody("{\"some\": \"json\"}"); HttpConnection response = c.executeRequest(request); String responseStr = response.responseAsString(); - assertTrue("There should be no response body on the mock response", responseStr.isEmpty()); + assertTrue(responseStr.isEmpty(), "There should be no response body on the mock response"); response.getConnection().getResponseCode(); } - @Test + @TestTemplate public void testCustomHeader() throws Exception { mockWebServer.enqueue(new MockResponse()); final String headerName = "Test-Header"; @@ -630,12 +686,12 @@ public void testCustomHeader() throws Exception { } }).build(); client.getAllDbs(); - assertEquals("There should have been 1 request", 1, mockWebServer.getRequestCount()); + assertEquals(1, mockWebServer.getRequestCount(), "There should have been 1 request"); RecordedRequest request = MockWebServerResources.takeRequestWithTimeout(mockWebServer); - assertNotNull("The recorded request should not be null", request); - assertNotNull("The custom header should have been present", request.getHeader(headerName)); - assertEquals("The custom header should have the specified value", headerValue, request - .getHeader(headerName)); + assertNotNull(request, "The recorded request should not be null"); + assertNotNull(request.getHeader(headerName), "The custom header should have been present"); + assertEquals(headerValue, request + .getHeader(headerName), "The custom header should have the specified value"); } /** @@ -643,7 +699,7 @@ public void testCustomHeader() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void testChunking() throws Exception { mockWebServer.enqueue(new MockResponse()); final int chunkSize = 1024 * 8; @@ -655,20 +711,20 @@ public void testChunking() throws Exception { "text/plain") .setRequestBody(new RandomInputStreamGenerator(chunks * chunkSize))) .responseAsString(); - assertTrue("There should be no response body on the mock response", response.isEmpty()); - assertEquals("There should have been 1 request", 1, mockWebServer - .getRequestCount()); + assertTrue(response.isEmpty(), "There should be no response body on the mock response"); + assertEquals(1, mockWebServer + .getRequestCount(), "There should have been 1 request"); RecordedRequest request = MockWebServerResources.takeRequestWithTimeout(mockWebServer); - assertNotNull("The recorded request should not be null", request); - assertNull("There should be no Content-Length header", request.getHeader("Content-Length")); - assertEquals("The Transfer-Encoding should be chunked", "chunked", request.getHeader - ("Transfer-Encoding")); + assertNotNull(request, "The recorded request should not be null"); + assertNull(request.getHeader("Content-Length"), "There should be no Content-Length header"); + assertEquals("chunked", request.getHeader + ("Transfer-Encoding"), "The Transfer-Encoding should be chunked"); // It would be nice to assert that we got the chunk sizes we were expecting, but sadly the // HttpURLConnection and ChunkedOutputStream only use the chunkSize as a suggestion and seem // to use the buffer size instead. The best assertion we can make is that we did receive // multiple chunks. - assertTrue("There should have been at least 2 chunks", request.getChunkSizes().size() > 1); + assertTrue(request.getChunkSizes().size() > 1, "There should have been at least 2 chunks"); } private static final class RandomInputStreamGenerator implements HttpConnection @@ -692,7 +748,7 @@ public InputStream getInputStream() throws IOException { * * @throws Exception */ - @Test + @TestTemplate public void test429Backoff() throws Exception { mockWebServer.enqueue(MockWebServerResources.get429()); mockWebServer.enqueue(new MockResponse()); @@ -701,9 +757,9 @@ public void test429Backoff() throws Exception { .interceptors(Replay429Interceptor.WITH_DEFAULTS) .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).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"); - assertEquals("There should be 2 requests", 2, mockWebServer.getRequestCount()); + assertEquals(2, mockWebServer.getRequestCount(), "There should be 2 requests"); } /** @@ -712,7 +768,7 @@ public void test429Backoff() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void test429BackoffMaxDefault() throws Exception { // Always respond 429 for this test @@ -728,10 +784,10 @@ public void test429BackoffMaxDefault() throws Exception { } catch (TooManyRequestsException e) { long duration = t.stopTimer(TimeUnit.MILLISECONDS); // 3 backoff periods for 4 attempts: 250 + 500 + 1000 = 1750 ms - assertTrue("The duration should be at least 1750 ms, but was " + duration, duration >= - 1750); - assertEquals("There should be 4 request attempts", 4, mockWebServer - .getRequestCount()); + assertTrue(duration >= + 1750, "The duration should be at least 1750 ms, but was " + duration); + assertEquals(4, mockWebServer + .getRequestCount(), "There should be 4 request attempts"); } } @@ -741,7 +797,7 @@ public void test429BackoffMaxDefault() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void test429BackoffMaxConfigured() throws Exception { // Always respond 429 for this test @@ -758,10 +814,10 @@ public void test429BackoffMaxConfigured() throws Exception { } catch (TooManyRequestsException e) { long duration = t.stopTimer(TimeUnit.MILLISECONDS); // 9 backoff periods for 10 attempts: 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 = 511 ms - assertTrue("The duration should be at least 511 ms, but was " + duration, duration >= - 511); - assertEquals("There should be 10 request attempts", 10, mockWebServer - .getRequestCount()); + assertTrue(duration >= + 511, "The duration should be at least 511 ms, but was " + duration); + assertEquals(10, mockWebServer + .getRequestCount(), "There should be 10 request attempts"); } } @@ -770,7 +826,7 @@ public void test429BackoffMaxConfigured() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void test429BackoffMaxMoreThanRetriesAllowed() throws Exception { // Always respond 429 for this test @@ -784,8 +840,8 @@ public void test429BackoffMaxMoreThanRetriesAllowed() throws Exception { .responseAsString(); fail("There should be a TooManyRequestsException instead had response " + response); } catch (TooManyRequestsException e) { - assertEquals("There should be 3 request attempts", 3, mockWebServer - .getRequestCount()); + assertEquals(3, mockWebServer + .getRequestCount(), "There should be 3 request attempts"); } } @@ -795,7 +851,7 @@ public void test429BackoffMaxMoreThanRetriesAllowed() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void test429BackoffRetryAfter() throws Exception { mockWebServer.enqueue(MockWebServerResources.get429().addHeader("Retry-After", "1")); @@ -806,16 +862,16 @@ public void test429BackoffRetryAfter() throws Exception { .interceptors(Replay429Interceptor.WITH_DEFAULTS) .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).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"); long duration = t.stopTimer(TimeUnit.MILLISECONDS); - assertTrue("The duration should be at least 1000 ms, but was " + duration, duration >= - 1000); - assertEquals("There should be 2 request attempts", 2, mockWebServer - .getRequestCount()); + assertTrue(duration >= + 1000, "The duration should be at least 1000 ms, but was " + duration); + assertEquals(2, mockWebServer + .getRequestCount(), "There should be 2 request attempts"); } - @Test + @TestTemplate public void test429IgnoreRetryAfter() throws Exception { mockWebServer.enqueue(MockWebServerResources.get429().addHeader("Retry-After", "1")); mockWebServer.enqueue(new MockResponse()); @@ -826,13 +882,13 @@ public void test429IgnoreRetryAfter() throws Exception { .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).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"); long duration = t.stopTimer(TimeUnit.MILLISECONDS); - assertTrue("The duration should be less than 1000 ms, but was " + duration, duration < - 1000); - assertEquals("There should be 2 request attempts", 2, mockWebServer - .getRequestCount()); + assertTrue(duration < + 1000, "The duration should be less than 1000 ms, but was " + duration); + assertEquals(2, mockWebServer + .getRequestCount(), "There should be 2 request attempts"); } /** @@ -840,7 +896,7 @@ public void test429IgnoreRetryAfter() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void testHttpConnectionRetries() throws Exception { // Just return 200 OK mockWebServer.setDispatcher(new MockWebServerResources.ConstantResponseDispatcher(200)); @@ -865,10 +921,10 @@ public void testHttpConnectionRetries() throws Exception { String response = c.executeRequest(Http.GET(c.getBaseUri()).setNumberOfRetries(5)) .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"); - assertEquals("There should be 5 request attempts", 5, mockWebServer - .getRequestCount()); + assertEquals(5, mockWebServer + .getRequestCount(), "There should be 5 request attempts"); } /** @@ -877,15 +933,21 @@ public void testHttpConnectionRetries() throws Exception { * * @throws Exception */ - @Test(expected = IllegalArgumentException.class) + @TestTemplate public void httpsProxyIllegalArgumentException() throws Exception { + assertThrows(IllegalArgumentException.class, new Executable() { + @Override + public void execute() throws Throwable { + // Get a client pointing to an https proxy CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder(mockWebServer) .proxyURL(new URL("https://192.0.2.0")).build(); String response = client.executeRequest(Http.GET(client.getBaseUri())).responseAsString(); fail("There should be an IllegalStateException for an https proxy."); + } + }); } /** @@ -894,7 +956,7 @@ public void httpsProxyIllegalArgumentException() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void cookieAppliedToDifferentURL() throws Exception { mockWebServer.enqueue(MockWebServerResources.OK_COOKIE); mockWebServer.enqueue(new MockResponse().setBody("first")); @@ -909,36 +971,34 @@ public void cookieAppliedToDifferentURL() throws Exception { URI baseURI = c.getBaseUri(); URL first = new URL(baseURI.getScheme(), baseURI.getHost(), baseURI.getPort(), "/testdb"); String response = c.executeRequest(Http.GET(first)).responseAsString(); - assertEquals("The correct response body should be present", "first", response); +assertEquals("first", response, "The correct response body should be present"); // There should be a request for a cookie followed by a the real request - assertEquals("There should be 2 requests", 2, mockWebServer.getRequestCount()); + assertEquals(2, mockWebServer.getRequestCount(), "There should be 2 requests"); - assertEquals("The first request should have been for a cookie", "/_session", - MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath()); +assertEquals("/_session", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The first request should have been for a cookie"); RecordedRequest request = MockWebServerResources.takeRequestWithTimeout(mockWebServer); - assertEquals("The second request should have been for /testdb", "/testdb", - request.getPath()); - assertNotNull("There should be a cookie on the request", request.getHeader("Cookie")); + assertEquals("/testdb", + request.getPath(), "The second request should have been for /testdb"); +assertNotNull(request.getHeader("Cookie"), "There should be a cookie on the request"); // Now make a request to another URL URL second = new URL(baseURI.getScheme(), baseURI.getHost(), baseURI.getPort(), "/_all_dbs"); response = c.executeRequest(Http.GET(second)).responseAsString(); - assertEquals("The correct response body should be present", "second", response); +assertEquals("second", response, "The correct response body should be present"); // There should now be an additional request - assertEquals("There should be 3 requests", 3, mockWebServer.getRequestCount()); + assertEquals(3, mockWebServer.getRequestCount(), "There should be 3 requests"); request = MockWebServerResources.takeRequestWithTimeout(mockWebServer); - assertEquals("The second request should have been for /_all_dbs", "/_all_dbs", request - .getPath()); +assertEquals("/_all_dbs", request.getPath(), "The second request should have been for /_all_dbs"); String cookieHeader = request.getHeader("Cookie"); - assertNotNull("There should be a cookie on the request", cookieHeader); - assertTrue("The cookie header " + cookieHeader + " should contain the expected value.", - request.getHeader("Cookie").contains(EXPECTED_OK_COOKIE)); +assertNotNull(cookieHeader, "There should be a cookie on the request"); + assertTrue( + request.getHeader("Cookie").contains(EXPECTED_OK_COOKIE), "The cookie header " + cookieHeader + " should contain the expected value."); } /** @@ -946,7 +1006,7 @@ public void cookieAppliedToDifferentURL() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void badCredsDisablesCookie() throws Exception { mockWebServer.setDispatcher(new Dispatcher() { private int counter = 0; @@ -969,25 +1029,24 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response body should be received", "TEST", response); + assertEquals("TEST", response, "The expected response body should be received"); // There should only be two requests: an initial auth failure followed by an ok response. // If the cookie interceptor keeps trying then there will be more _session requests. - assertEquals("There should be 2 requests", 2, mockWebServer.getRequestCount()); + assertEquals(2, mockWebServer.getRequestCount(), "There should be 2 requests"); - assertEquals("The first request should have been for a cookie", "/_session", - MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath()); - assertEquals("The second request should have been for /", "/", - MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath()); + assertEquals( "/_session", + MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The first request should have been for a cookie"); + assertEquals( "/", + MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The second request should have been for /"); response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response body should be received", "TEST", response); +assertEquals("TEST", response, "The expected response body should be received"); // Make another request, the cookie interceptor should not try again so there should only be // one more request. - assertEquals("There should be 3 requests", 3, mockWebServer.getRequestCount()); - assertEquals("The third request should have been for /", "/", - MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath()); + assertEquals(3, mockWebServer.getRequestCount(), "There should be 3 requests"); +assertEquals("/", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The third request should have been for /"); } /** @@ -996,9 +1055,13 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio * * @throws Exception */ - @Test(expected = CouchDbException.class) + @TestTemplate public void noErrorStream403() throws Exception { + assertThrows(CouchDbException.class, new Executable() { + @Override + public void execute() throws Throwable { + // Respond with a cookie init to the first request to _session mockWebServer.enqueue(MockWebServerResources.OK_COOKIE); // Respond to the executeRequest GET of / with a 403 with no body @@ -1010,6 +1073,8 @@ public void noErrorStream403() throws Exception { String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); fail("There should be an exception, but received response " + response); + } + }); } /** @@ -1018,7 +1083,7 @@ public void noErrorStream403() throws Exception { * * @throws Exception */ - @Test + @TestTemplate public void noErrorStream401() throws Exception { // Respond with a cookie init to the first request to _session @@ -1036,7 +1101,7 @@ public void noErrorStream401() throws Exception { .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - assertEquals("The expected response body should be received", "TEST", response); +assertEquals("TEST", response, "The expected response body should be received"); } /** @@ -1047,15 +1112,15 @@ public void noErrorStream401() throws Exception { * @throws ClassNotFoundException if the load tries to use any classes it shouldn't * @throws Exception if there is another issue in the test */ - @Test + @TestTemplate public void okUsableClassLoad() throws ClassNotFoundException, Exception { // Point to the built classes, it's a bit awkward but we need to load the class cleanly - File f = new File("../cloudant-http/build/classes/main/"); + File f = new File("../cloudant-http/build/classes/java/main/"); ClassLoader loader = new CloudantHttpIsolationClassLoader(new URL[]{f.toURI().toURL()}); Class okHelperClass = Class.forName("com.cloudant.http.internal.ok.OkHelper" , true, loader); - assertEquals("The isOkUsable value should be correct", okUsable, okHelperClass.getMethod - ("isOkUsable").invoke(null)); + assertEquals(isOkUsable, okHelperClass.getMethod + ("isOkUsable").invoke(null), "The isOkUsable value should be correct"); } /** @@ -1068,7 +1133,7 @@ public class CloudantHttpIsolationClassLoader extends URLClassLoader { public CloudantHttpIsolationClassLoader(URL[] urls) { // If we are testing okhttp then allow the parent classloader, otherwise use null // to isolate okhttp classes from the test load - super(urls, okUsable ? CloudantHttpIsolationClassLoader.class.getClassLoader() : null); + super(urls, isOkUsable ? CloudantHttpIsolationClassLoader.class.getClassLoader() : null); } @Override @@ -1079,8 +1144,8 @@ protected Class findClass(String name) throws ClassNotFoundException { // helper - assert that _n_ requests were made on the mock server and return them in an array public static RecordedRequest[] takeN(MockWebServer server, int n) throws Exception { - assertEquals(String.format(Locale.ENGLISH, "The server should have %d received requests", n), n, - server.getRequestCount()); + assertEquals(n, + server.getRequestCount(), String.format(Locale.ENGLISH, "The server should have %d received requests", n)); RecordedRequest[] recordedRequests = new RecordedRequest[n]; for (int i=0; i flds = i.getFields(); - assertTrue("The fields should not be empty", flds.size() > 0); + assertTrue(flds.size() > 0, "The fields should not be empty"); for (JsonIndex.Field field : flds) { - assertNotNull("The field name should not be null", field.getName()); - assertNotNull("The sort order should not be null", field.getOrder()); + assertNotNull(field.getName(), "The field name should not be null"); + assertNotNull(field.getOrder(), "The sort order should not be null"); } } } @@ -272,7 +270,7 @@ public void testBookmarks() { pageCount++; } } while (!moviesPage.getDocs().isEmpty()); - Assert.assertEquals(3, pageCount); + assertEquals(3, pageCount); } @@ -289,12 +287,12 @@ public void useIndexDesignDocJsonTypeIsString() throws Exception { } private void assertUseIndexString(JsonElement useIndex) throws Exception { - assertNotNull("The use_index property should not be null", useIndex); - assertTrue("The use_index property should be a JsonPrimitive", useIndex.isJsonPrimitive()); + assertNotNull(useIndex, "The use_index property should not be null"); + assertTrue(useIndex.isJsonPrimitive(), "The use_index property should be a JsonPrimitive"); JsonPrimitive useIndexPrimitive = useIndex.getAsJsonPrimitive(); - assertTrue("The use_index property should be a string", useIndexPrimitive.isString()); + assertTrue(useIndexPrimitive.isString(), "The use_index property should be a string"); String useIndexString = useIndexPrimitive.getAsString(); - assertEquals("The use_index property should be Movie_year", "Movie_year", useIndexString); + assertEquals("Movie_year", useIndexString, "The use_index property should be Movie_year"); } /** @@ -306,14 +304,12 @@ private void assertUseIndexString(JsonElement useIndex) throws Exception { public void useIndexDesignDocAndIndexNameJsonTypeIsArray() throws Exception { JsonElement useIndex = getUseIndexFromRequest(new QueryBuilder(empty()). useIndex("Movie_year", "Person_name")); - assertNotNull("The use_index property should not be null", useIndex); - assertTrue("The use_index property should be a JsonArray", useIndex.isJsonArray()); + assertNotNull(useIndex, "The use_index property should not be null"); + assertTrue(useIndex.isJsonArray(), "The use_index property should be a JsonArray"); JsonArray useIndexArray = useIndex.getAsJsonArray(); - assertEquals("The use_index array should have two elements", 2, useIndexArray.size()); - assertEquals("The use_index design document should be Movie_year", "Movie_year", - useIndexArray.get(0).getAsString()); - assertEquals("The use_index index name should be Person_name", "Person_name", - useIndexArray.get(1).getAsString()); + assertEquals(2, useIndexArray.size(), "The use_index array should have two elements"); + assertEquals("Movie_year", useIndexArray.get(0).getAsString(), "The use_index design document should be Movie_year"); + assertEquals("Person_name", useIndexArray.get(1).getAsString(), "The use_index index name should be Person_name"); } /** @@ -324,7 +320,7 @@ public void useIndexDesignDocAndIndexNameJsonTypeIsArray() throws Exception { @Test public void useIndexNotSpecified() throws Exception { JsonElement useIndex = getUseIndexFromRequest(new QueryBuilder(empty())); - assertNull("The use_index property should be null (i.e. was not specified)", useIndex); + assertNull(useIndex, "The use_index property should be null (i.e. was not specified)"); } /** diff --git a/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java b/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java index 77083489c..cd16960ec 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java @@ -14,26 +14,22 @@ package com.cloudant.tests; -import static org.junit.Assert.assertEquals; -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.assertTrue; import com.cloudant.client.api.ClientBuilder; import com.cloudant.client.api.CloudantClient; import com.cloudant.http.Http; import com.cloudant.http.HttpConnection; +import com.cloudant.tests.extensions.MockWebServerExtension; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; +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 mockit.Expectations; import mockit.Mocked; -import mockit.StrictExpectations; -import mockit.Verifications; - import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; @@ -56,15 +52,17 @@ public class LoggingTest { private static final String logPrefixPattern = "[0-9,a-f]+-\\d+ "; - @ClassRule - public static MockWebServer mockWebServer = new MockWebServer(); + @RegisterExtension + public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); + public static MockWebServer mockWebServer; private static CloudantClient client; private volatile Logger logger; private VerificationLogHandler handler; - @BeforeClass - public static void setupMockWebServer() throws Exception { + @BeforeEach + public void setupMockWebServer() throws Exception { + mockWebServer = mockWebServerExt.get(); // Set a dispatcher that always returns 200 OK mockWebServer.setDispatcher(new Dispatcher() { @Override @@ -76,12 +74,12 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio .build(); } - @Before + @BeforeEach public void createHandler() { handler = new VerificationLogHandler(); } - @After + @AfterEach public void teardown() throws Exception { // Remove the handler from the logger logger.removeHandler(handler); @@ -94,7 +92,7 @@ public void teardown() throws Exception { public void httpLoggingEnabled() throws Exception { logger = setupLogger(HttpConnection.class, Level.ALL); client.executeRequest(Http.GET(client.getBaseUri())).responseAsString(); - assertTrue("There should be at least 1 log entry", handler.logEntries.size() > 0); + assertTrue(handler.logEntries.size() > 0, "There should be at least 1 log entry"); } @Test @@ -113,7 +111,7 @@ public void urlRegexLogging() throws Exception { .responseAsString(); // Check there were two log messages one for request and one for response - assertEquals("There should be 2 log messages", 2, handler.logEntries.size()); + assertEquals(2, handler.logEntries.size(), "There should be 2 log messages"); // Check the messages were the ones we expected assertHttpMessage("GET .*/testdb request", 0); assertHttpMessage("GET .*/testdb response 200 OK", 1); @@ -123,8 +121,7 @@ public void urlRegexLogging() throws Exception { // Make a second request to a different URL and check that nothing else was logged client.executeRequest(Http.GET(client.getBaseUri())).responseAsString(); - assertEquals("There should have been no more log entries", logsize, handler.logEntries - .size()); + assertEquals(logsize, handler.logEntries.size(), "There should have been no more log entries"); } private String methodFilterPropName = "com.cloudant.http.filter.method"; @@ -137,7 +134,7 @@ public void httpMethodFilterLogging() throws Exception { client.executeRequest(Http.GET(client.getBaseUri())).responseAsString(); // Check there were two log messages one for request and one for response - assertEquals("There should be 2 log messages", 2, handler.logEntries.size()); + assertEquals(2, handler.logEntries.size(), "There should be 2 log messages"); // Check the messages were the ones we expected assertHttpMessage("GET .* request", 0); assertHttpMessage("GET .* response 200 OK", 1); @@ -148,8 +145,7 @@ public void httpMethodFilterLogging() throws Exception { // Make a PUT request to a different URL and check that nothing else was logged client.executeRequest(Http.PUT(client.getBaseUri(), "text/plain").setRequestBody("")) .responseAsString(); - assertEquals("There should have been no more log entries", logsize, handler.logEntries - .size()); + assertEquals(logsize, handler.logEntries.size(), "There should have been no more log entries"); } @Test @@ -161,7 +157,7 @@ public void httpMethodFilterLoggingList() throws Exception { client.executeRequest(Http.GET(client.getBaseUri())).responseAsString(); // Check there were two log messages one for request and one for response - assertEquals("There should be 2 log messages", 2, handler.logEntries.size()); + assertEquals(2, handler.logEntries.size(), "There should be 2 log messages"); // Check the messages were the ones we expected assertHttpMessage("GET .* request", 0); assertHttpMessage("GET .* response 200 OK", 1); @@ -170,7 +166,7 @@ public void httpMethodFilterLoggingList() throws Exception { client.executeRequest(Http.PUT(client.getBaseUri(), "text/plain").setRequestBody("")) .responseAsString(); - assertEquals("There should now be 4 log messages", 4, handler.logEntries.size()); + assertEquals(4, handler.logEntries.size(), "There should now be 4 log messages"); // Check the messages were the ones we expected assertHttpMessage("PUT .* request", 2); assertHttpMessage("PUT .* response 200 OK", 3); @@ -180,7 +176,7 @@ public void httpMethodFilterLoggingList() throws Exception { public void clientBuilderLogging() throws Exception { logger = setupLogger(ClientBuilder.class, Level.CONFIG); CloudantClientHelper.newMockWebServerClientBuilder(mockWebServer).build(); - assertEquals("There should be 5 log entries", 5, handler.logEntries.size()); + assertEquals(5, handler.logEntries.size(), "There should be 5 log entries"); // Validate each of the 5 entries are what we expect assertLogMessage("URL: .*", 0); assertLogMessage("Building client using URL: .*", 1); @@ -218,7 +214,7 @@ private void basicDnsLogTest(String cacheValue) throws Exception { public void dnsNoWarningLessThan30() throws Exception { basicDnsLogTest("29"); // Assert no warning was received - assertEquals("There should be no log entry", 0, handler.logEntries.size()); + assertEquals(0, handler.logEntries.size(), "There should be no log entry"); } /** @@ -230,7 +226,7 @@ public void dnsNoWarningLessThan30() throws Exception { public void dnsNoWarning0() throws Exception { basicDnsLogTest("0"); // Assert no warning was received - assertEquals("There should be no log entry", 0, handler.logEntries.size()); + assertEquals(0, handler.logEntries.size(), "There should be no log entry"); } /** @@ -242,7 +238,7 @@ public void dnsNoWarning0() throws Exception { public void dnsWarningForever() throws Exception { basicDnsLogTest("-1"); // Assert a warning was received - assertEquals("There should be 1 log entry", 1, handler.logEntries.size()); + assertEquals(1, handler.logEntries.size(), "There should be 1 log entry"); // Assert that it matches the expected pattern assertLogMessage("DNS cache lifetime may be too long\\. .*", 0); } @@ -256,7 +252,7 @@ public void dnsWarningForever() throws Exception { public void dnsNoWarning30() throws Exception { basicDnsLogTest("30"); // Assert no warning was received - assertEquals("There should be no log entry", 0, handler.logEntries.size()); + assertEquals(0, handler.logEntries.size(), "There should be no log entry"); } @@ -269,7 +265,7 @@ public void dnsNoWarning30() throws Exception { public void dnsWarning31() throws Exception { basicDnsLogTest("31"); // Assert a warning was received - assertEquals("There should be 1 log entry", 1, handler.logEntries.size()); + assertEquals(1, handler.logEntries.size(), "There should be 1 log entry"); // Assert that it matches the expected pattern assertLogMessage("DNS cache lifetime may be too long\\. .*", 0); } @@ -302,7 +298,7 @@ public void dnsWarningPermissionDenied(@Mocked final SecurityManager mockSecurit System.setSecurityManager(null); } // Assert a warning was received - assertEquals("There should be 1 log entry", 1, handler.logEntries.size()); + assertEquals(1, handler.logEntries.size(), "There should be 1 log entry"); // Assert that it matches the expected pattern assertLogMessage("Permission denied to check Java DNS cache TTL\\. .*", 0); } @@ -336,7 +332,7 @@ public void dnsWarningDefaultWithSecurityManager(@Mocked final SecurityManager System.setSecurityManager(null); } // Assert a warning was received - assertEquals("There should be 1 log entry", 1, handler.logEntries.size()); + assertEquals(1, handler.logEntries.size(), "There should be 1 log entry"); // Assert that it matches the expected pattern assertLogMessage("DNS cache lifetime may be too long\\. .*", 0); } @@ -348,8 +344,7 @@ public void dnsWarningDefaultWithSecurityManager(@Mocked final SecurityManager private void setAndAssertLogProperty(String name, String value) throws Exception { LogManager.getLogManager().readConfiguration(new ByteArrayInputStream((name + "=" + value).getBytes())); - assertEquals("The log property should be the test value", value, LogManager - .getLogManager().getProperty(name)); + assertEquals(value, LogManager.getLogManager().getProperty(name), "The log property should be the test value"); } /** @@ -361,8 +356,7 @@ private void setAndAssertLogProperty(String name, String value) throws Exception private void assertLogMessage(String pattern, int index) { Pattern p = Pattern.compile(pattern); String msg = handler.logEntries.get(index).getMessage(); - assertTrue("The log entry \"" + msg + "\" should match pattern " + pattern, p.matcher - (msg).matches()); + assertTrue(p.matcher(msg).matches(), "The log entry \"" + msg + "\" should match pattern " + pattern); } /** diff --git a/cloudant-client/src/test/java/com/cloudant/tests/QueryTests.java b/cloudant-client/src/test/java/com/cloudant/tests/QueryTests.java index fd1c99703..9d0d762e3 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/QueryTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/QueryTests.java @@ -34,8 +34,8 @@ import com.cloudant.client.api.query.Sort; import com.cloudant.client.api.query.Type; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; public class QueryTests { @@ -43,7 +43,7 @@ public class QueryTests { @Test public void basicSelector1() { QueryBuilder qb = new QueryBuilder(eq("director", "Lars von Trier")); - Assert.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}}", qb.build()); } // "Selector with two fields" @@ -52,21 +52,21 @@ public void basicSelector2() { QueryBuilder qb = new QueryBuilder(and( eq("name", "Paul"), eq("location", "Boston"))); - Assert.assertEquals("{\"selector\": {\"$and\": [{\"name\": {\"$eq\": \"Paul\"}}, {\"location\": {\"$eq\": \"Boston\"}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"name\": {\"$eq\": \"Paul\"}}, {\"location\": {\"$eq\": \"Boston\"}}]}}", qb.build()); } // "SUBFIELDS" @Test public void basicSelector3() { QueryBuilder qb = new QueryBuilder(eq("imdb.rating", 8)); - Assert.assertEquals("{\"selector\": {\"imdb.rating\": {\"$eq\": 8}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"imdb.rating\": {\"$eq\": 8}}}", qb.build()); } // "Example selector using an operator to match any document, where the age field has a value greater than 20:" @Test public void basicSelector4() { QueryBuilder qb = new QueryBuilder(gt("year", 2018)); - Assert.assertEquals("{\"selector\": {\"year\": {\"$gt\": 2018}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"year\": {\"$gt\": 2018}}}", qb.build()); } // "$and operator used with full text indexing" @@ -76,7 +76,7 @@ public void basicSelector5() { eq("$text", "Schwarzenegger"), in("year", 1984, 1991) )); - Assert.assertEquals("{\"selector\": {\"$and\": [{\"$text\": {\"$eq\": \"Schwarzenegger\"}}, {\"year\": {\"$in\": [1984, 1991]}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"$text\": {\"$eq\": \"Schwarzenegger\"}}, {\"year\": {\"$in\": [1984, 1991]}}]}}", qb.build()); } // "$and operator used with full text indexing" @@ -86,7 +86,7 @@ public void basicSelector5_single() { eq("$text", "Schwarzenegger"), in("year", 1984) )); - Assert.assertEquals("{\"selector\": {\"$and\": [{\"$text\": {\"$eq\": \"Schwarzenegger\"}}, {\"year\": {\"$in\": [1984]}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"$text\": {\"$eq\": \"Schwarzenegger\"}}, {\"year\": {\"$in\": [1984]}}]}}", qb.build()); } // "$or operator used with full text indexing" @@ -96,7 +96,7 @@ public void basicSelector6() { eq("director", "George Lucas"), eq("director", "Steven Spielberg") )); - Assert.assertEquals("{\"selector\": {\"$or\": [{\"director\": {\"$eq\": \"George Lucas\"}}, {\"director\": {\"$eq\": \"Steven Spielberg\"}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$or\": [{\"director\": {\"$eq\": \"George Lucas\"}}, {\"director\": {\"$eq\": \"Steven Spielberg\"}}]}}", qb.build()); } // "$or operator used with database indexed on the field "year" @@ -109,7 +109,7 @@ public void basicSelector7() { eq("director", "Steven Spielberg") ) )); - Assert.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$eq\": 1977}}, {\"$or\": [{\"director\": {\"$eq\": \"George Lucas\"}}, {\"director\": {\"$eq\": \"Steven Spielberg\"}}]}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$eq\": 1977}}, {\"$or\": [{\"director\": {\"$eq\": \"George Lucas\"}}, {\"director\": {\"$eq\": \"Steven Spielberg\"}}]}]}}", qb.build()); } // "$not operator used with database indexed on the field "year"" @@ -120,7 +120,7 @@ public void basicSelector8() { lte("year", 1903), not(eq("year", 1901)) )); - Assert.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gte\": 1900}}, {\"year\": {\"$lte\": 1903}}, {\"$not\": {\"year\": {\"$eq\": 1901}}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gte\": 1900}}, {\"year\": {\"$lte\": 1903}}, {\"$not\": {\"year\": {\"$eq\": 1901}}}]}}", qb.build()); } // "$nor operator used with database indexed on the field "year"" @@ -134,28 +134,28 @@ public void basicSelector9() { eq("year", 1905), eq("year", 1907)) )); - Assert.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gte\": 1900}}, {\"year\": {\"$lte\": 1910}}, {\"$nor\": [{\"year\": {\"$eq\": 1901}}, {\"year\": {\"$eq\": 1905}}, {\"year\": {\"$eq\": 1907}}]}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gte\": 1900}}, {\"year\": {\"$lte\": 1910}}, {\"$nor\": [{\"year\": {\"$eq\": 1901}}, {\"year\": {\"$eq\": 1905}}, {\"year\": {\"$eq\": 1907}}]}]}}", qb.build()); } // "$all operator used with full text indexing" @Test public void basicSelector10() { QueryBuilder qb = new QueryBuilder(all("genre", "Comedy", "Short")); - Assert.assertEquals("{\"selector\": {\"genre\": {\"$all\": [\"Comedy\", \"Short\"]}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"genre\": {\"$all\": [\"Comedy\", \"Short\"]}}}", qb.build()); } // "$all operator used with full text indexing" @Test public void basicSelector10_single() { QueryBuilder qb = new QueryBuilder(all("genre", "Comedy")); - Assert.assertEquals("{\"selector\": {\"genre\": {\"$all\": [\"Comedy\"]}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"genre\": {\"$all\": [\"Comedy\"]}}}", qb.build()); } // "elemMatch operator used with full text indexing" @Test public void basicSelector11() { QueryBuilder qb = new QueryBuilder(elemMatch("genre", PredicateExpression.eq("Horror"))); - Assert.assertEquals("{\"selector\": {\"genre\": {\"$elemMatch\": {\"$eq\": \"Horror\"}}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"genre\": {\"$elemMatch\": {\"$eq\": \"Horror\"}}}}", qb.build()); } // "$lt operator used with database indexed on the field "year"" @@ -163,35 +163,35 @@ public void basicSelector11() { @Test public void basicSelector12() { QueryBuilder qb = new QueryBuilder(lt("year", 1900)); - Assert.assertEquals("{\"selector\": {\"year\": {\"$lt\": 1900}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"year\": {\"$lt\": 1900}}}", qb.build()); } // "$exists operator used with database indexed on the field "year"" @Test public void basicSelector13() { QueryBuilder qb = new QueryBuilder(and(eq("year", 2015), exists("title", true))); - Assert.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$eq\": 2015}}, {\"title\": {\"$exists\": true}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$eq\": 2015}}, {\"title\": {\"$exists\": true}}]}}", qb.build()); } // "$type operator used with full text indexing" @Test public void basicSelector14() { QueryBuilder qb = new QueryBuilder(type("year", Type.NUMBER)); - Assert.assertEquals("{\"selector\": {\"year\": {\"$type\": \"number\"}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"year\": {\"$type\": \"number\"}}}", qb.build()); } // "$in operator used with full text indexing" @Test public void basicSelector15() { QueryBuilder qb = new QueryBuilder(in("year", 2010, 2015)); - Assert.assertEquals("{\"selector\": {\"year\": {\"$in\": [2010, 2015]}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"year\": {\"$in\": [2010, 2015]}}}", qb.build()); } // "$in operator used with full text indexing" @Test public void basicSelector15_single() { QueryBuilder qb = new QueryBuilder(in("year", 2010)); - Assert.assertEquals("{\"selector\": {\"year\": {\"$in\": [2010]}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"year\": {\"$in\": [2010]}}}", qb.build()); } // "$nin operator used with full text indexing" @@ -199,7 +199,7 @@ public void basicSelector15_single() { public void basicSelector16() { QueryBuilder qb = new QueryBuilder(and(gt("year", 2009), nin("year", 2010, 2015))); - Assert.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, {\"year\": {\"$nin\": [2010, 2015]}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, {\"year\": {\"$nin\": [2010, 2015]}}]}}", qb.build()); } // "$nin operator used with full text indexing" @@ -207,14 +207,14 @@ public void basicSelector16() { public void basicSelector16_single() { QueryBuilder qb = new QueryBuilder(and(gt("year", 2009), nin("year", 2010))); - Assert.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, {\"year\": {\"$nin\": [2010]}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, {\"year\": {\"$nin\": [2010]}}]}}", qb.build()); } @Test public void complexSelector1() { QueryBuilder qb = new QueryBuilder(not(and(gt("year", 2009), nin("year", 2010, 2015)))); - Assert.assertEquals("{\"selector\": {\"$not\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, {\"year\": {\"$nin\": [2010, 2015]}}]}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$not\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, {\"year\": {\"$nin\": [2010, 2015]}}]}}}", qb.build()); } @Test @@ -227,14 +227,14 @@ public void complexSelector2() { eq("Actor","de Vito"), eq("Year", 2001)) )); - Assert.assertEquals("{\"selector\": {\"$or\": [{\"$and\": [{\"Actor\": {\"$eq\": \"Schwarzenegger\"}}, {\"Year\": {\"$eq\": 2012}}]}, {\"$and\": [{\"Actor\": {\"$eq\": \"de Vito\"}}, {\"Year\": {\"$eq\": 2001}}]}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$or\": [{\"$and\": [{\"Actor\": {\"$eq\": \"Schwarzenegger\"}}, {\"Year\": {\"$eq\": 2012}}]}, {\"$and\": [{\"Actor\": {\"$eq\": \"de Vito\"}}, {\"Year\": {\"$eq\": 2001}}]}]}}", qb.build()); } // "Selector basics" @Test public void basicSelector1WithFields() { QueryBuilder qb = new QueryBuilder(eq("director", "Lars von Trier")).fields("_id", "_rev", "year", "title"); - Assert.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}, " + + Assertions.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}, " + "\"fields\": [\"_id\", \"_rev\", \"year\", \"title\"]}", qb.build()); } @@ -242,7 +242,7 @@ public void basicSelector1WithFields() { @Test public void basicSelector1WithSort() { QueryBuilder qb = new QueryBuilder(eq("director", "Lars von Trier")).sort(Sort.asc("year"), Sort.desc("director")); - Assert.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}, " + + Assertions.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}, " + "\"sort\": [{\"year\": \"asc\"}, {\"director\": \"desc\"}]}", qb.build()); } @@ -254,7 +254,7 @@ public void basicSelector1WithAllOptions() { sort(Sort.asc("year"), Sort.desc("director")). limit(10). skip(0); - Assert.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}, " + + Assertions.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}, " + "\"fields\": [\"_id\", \"_rev\", \"year\", \"title\"], " + "\"sort\": [{\"year\": \"asc\"}, {\"director\": \"desc\"}], \"limit\": 10, " + "\"skip\": 0}", qb.build()); diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ReplicationTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ReplicationTest.java index 7d77dc8b3..2c3c517d4 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ReplicationTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ReplicationTest.java @@ -15,24 +15,24 @@ package com.cloudant.tests; import static org.hamcrest.CoreMatchers.not; -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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.cloudant.client.api.model.ReplicationResult; import com.cloudant.client.api.model.ReplicationResult.ReplicationHistory; import com.cloudant.test.main.RequiresDB; +import com.cloudant.tests.base.TestWithReplication; import com.cloudant.tests.util.Utils; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.List; import java.util.Map; -@Category(RequiresDB.class) -public class ReplicationTest extends ReplicateBaseTest { +@RequiresDB +public class ReplicationTest extends TestWithReplication { @Test public void replication() { @@ -42,7 +42,7 @@ public void replication() { .target(db2URI) .trigger(); - assertTrue("The replication should complete ok", result.isOk()); + assertTrue(result.isOk(), "The replication should complete ok"); List histories = result.getHistories(); assertThat(histories.size(), not(0)); @@ -61,7 +61,7 @@ public void replication_filteredWithQueryParams() { .queryParams(queryParams) .trigger(); - assertTrue("The replication should complete ok", result.isOk()); + assertTrue(result.isOk(), "The replication should complete ok"); } @Test @@ -75,10 +75,10 @@ public void replicateDatabaseUsingReplicationTrigger() throws Exception { ReplicationResult result = account.replication().source(db1URI) .target(db2URI).createTarget(true).trigger(); - assertTrue("The replication should complete ok", result.isOk()); + assertTrue(result.isOk(), "The replication should complete ok"); Foo fooOnDb2 = Utils.findDocumentWithRetries(db2, docId, Foo.class, 20); - assertNotNull("The document should have been replicated", fooOnDb2); + assertNotNull(fooOnDb2, "The document should have been replicated"); } @Test @@ -96,7 +96,7 @@ public void replication_conflict() throws Exception { ReplicationResult result = account.replication().source(db1URI) .target(db2URI).trigger(); - assertTrue("The replication should complete ok", result.isOk()); + assertTrue(result.isOk(), "The replication should complete ok"); //we replicated with a doc with the same ID but different content in each DB, we should get //a conflict diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java index 34eea6710..d5e880438 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java @@ -16,34 +16,34 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; -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.assertTrue; import com.cloudant.client.api.model.ReplicatorDocument; import com.cloudant.client.api.model.Response; import com.cloudant.test.main.RequiresDB; +import com.cloudant.tests.base.TestWithReplication; import com.cloudant.tests.util.Utils; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.List; import java.util.Map; -@Category(RequiresDB.class) -public class ReplicatorTest extends ReplicateBaseTest { +@RequiresDB +public class ReplicatorTest extends TestWithReplication { private String repDocId; - @Before + @BeforeEach public void generateReplicatorDocId() { repDocId = Utils.generateUUID(); } - @After + @AfterEach public void cleanUpReplicatorDoc() throws Exception { Utils.removeReplicatorTestDoc(account, repDocId); } @@ -59,8 +59,7 @@ public void replication() throws Exception { // find and remove replicator doc ReplicatorDocument repDoc = Utils.waitForReplicatorToComplete(account, response.getId()); - assertTrue("The replicator should reach completed state", "completed".equalsIgnoreCase - (repDoc.getReplicationState())); + assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator should reach completed state"); } @Test @@ -79,8 +78,7 @@ public void replication_filteredWithQueryParams() throws Exception { // find and remove replicator doc ReplicatorDocument repDoc = Utils.waitForReplicatorToComplete(account, response.getId()); - assertTrue("The replicator should reach completed state", "completed".equalsIgnoreCase - (repDoc.getReplicationState())); + assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator should reach completed state"); } @Test diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java index 8178f4704..fe53b523c 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java @@ -13,10 +13,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.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.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import com.cloudant.client.api.CloudantClient; import com.cloudant.client.api.Database; @@ -29,15 +29,11 @@ import com.cloudant.http.HttpConnectionInterceptorContext; import com.cloudant.http.HttpConnectionRequestInterceptor; 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 com.cloudant.tests.util.Utils; -import org.junit.Before; -import org.junit.ClassRule; -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 java.util.ArrayList; import java.util.List; @@ -47,19 +43,13 @@ * Test cases to verify status code from Response object. * Assert status codes in CouchDbException and its subclasses. */ -public class ResponseTest { +public class ResponseTest 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 Foo foo; - private static Database db; - @Before + @BeforeEach public void setup() { - db = dbResource.get(); foo = new Foo(Utils.generateUUID()); } @@ -141,7 +131,7 @@ public void verifyBulkDocumentRequest() { * handling path, but it is worth checking that we do work with these error types. *

*/ - @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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 YAo3)7E0~2~ofrIBf+f8AD(bKQ0LkT8Jpcdz delta 37624 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 bUnA5qxQmYwgb5zcSQHmGs5%Otf1v&gMTSsq diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7e36e6166..bf3de2183 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Thu Oct 06 15:51:02 MSK 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-bin.zip diff --git a/gradlew b/gradlew index 27309d923..cccdd3d51 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -33,11 +33,11 @@ DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f6d5974e7..e95643d6a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line From 62e82add35d0333e0e41e321fd0f63ea7e125bf0 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Mon, 12 Mar 2018 17:13:15 +0000 Subject: [PATCH 02/17] Use Java 1.6 for production code and 1.8 for test code --- build.gradle | 4 ++-- cloudant-client/build.gradle | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 55bf5f2fa..759f15d88 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.8 - targetCompatibility = 1.8 + sourceCompatibility = 1.6 + targetCompatibility = 1.6 repositories { mavenLocal() diff --git a/cloudant-client/build.gradle b/cloudant-client/build.gradle index 92ea8299e..4f7855e78 100644 --- a/cloudant-client/build.gradle +++ b/cloudant-client/build.gradle @@ -57,6 +57,12 @@ gradle.projectsEvaluated { } } +// we need Java 1.8 features for JUnit 5 features, but our production code is 1.6 +compileTestJava { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 +} + tasks.withType(Test) { def jMockit doFirst { From 96ff13278ad5f83daa463626d8ee0973983ec681 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 13 Mar 2018 10:40:22 +0000 Subject: [PATCH 03/17] Remove debug printlns --- .../src/test/java/com/cloudant/api/query/IndexListTests.java | 1 - .../src/test/java/com/cloudant/tests/CloudantClientHelper.java | 2 -- .../java/com/cloudant/tests/extensions/DatabaseExtension.java | 1 - .../com/cloudant/tests/extensions/MockWebServerExtension.java | 1 - .../com/cloudant/tests/util/HttpFactoryParameterizedTest.java | 1 - 5 files changed, 6 deletions(-) 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 42d3345ac..e0fa9e839 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 @@ -57,7 +57,6 @@ public class IndexListTests extends TestWithMockedServer { * @return */ private static String fromFile(String resourceFileName) { - System.out.println(new File(".").getAbsolutePath()); try { return IOUtils.toString(new BufferedInputStream(new FileInputStream("" + "./src/test/resources/query-tests/" + resourceFileName + ".js")), "UTF-8"); 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 b5cb8a217..5979362cd 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientHelper.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientHelper.java @@ -48,8 +48,6 @@ 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/extensions/DatabaseExtension.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java index 6c800728c..fb7aeda76 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java @@ -130,7 +130,6 @@ public String getDatabaseName() { */ public String getDbURIWithUserInfo() throws Exception { String info = clientResource.getBaseURIWithUserInfo() + "/" + getDatabaseName(); - System.out.println("*** "+info); return info; } 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 index 90edf5bdc..e67977df0 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/MockWebServerExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/MockWebServerExtension.java @@ -34,7 +34,6 @@ public synchronized void beforeEach(ExtensionContext context) throws Exception { @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/util/HttpFactoryParameterizedTest.java b/cloudant-client/src/test/java/com/cloudant/tests/util/HttpFactoryParameterizedTest.java index bacfd00b4..92ce15acc 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 @@ -47,7 +47,6 @@ public static boolean isOkUsable() { @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(); From 1d426978f11d8660f91e1cae554a50e7a4eb52d7 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 13 Mar 2018 11:07:22 +0000 Subject: [PATCH 04/17] Remove comment mockWebServer needs to be fetched from extension before each test because the extension is a "per test" extension (overrides beforeEach/afterEach) --- .../src/test/java/com/cloudant/tests/DatabaseTest.java | 1 - 1 file changed, 1 deletion(-) 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 9c67e498a..e808a26a3 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java @@ -57,7 +57,6 @@ public class DatabaseTest extends TestWithDb { private static MockWebServer mockWebServer; - // TODO before class? @BeforeEach public void beforeEach() { mockWebServer = mockWebServerExt.get(); From 0b3645df7d2b99b62337a9452a93e9acddb41fed Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 13 Mar 2018 14:20:37 +0000 Subject: [PATCH 05/17] Remove parens --- .../src/test/java/com/cloudant/tests/AttachmentsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b571562f0..a80bd9a50 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java @@ -246,7 +246,7 @@ public void execute() throws Throwable { }); } - @Test() + @Test public void attachmentStandaloneDocIdEmptyRev() { assertThrows(IllegalArgumentException.class, new Executable() { @Override From 3e9d2318cf99d3bc03791a45ef9f991f36e30eee Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Mon, 26 Mar 2018 10:58:22 +0100 Subject: [PATCH 06/17] Fix findAny() to use _all_dbs which is available everywhere --- .../test/java/com/cloudant/tests/DocumentsCRUDTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java index 79b344b9c..3ecc13a78 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java @@ -24,6 +24,7 @@ import com.cloudant.client.api.model.Response; import com.cloudant.client.org.lightcouch.DocumentConflictException; import com.cloudant.client.org.lightcouch.NoDocumentException; +import com.cloudant.test.main.RequiresCouch; import com.cloudant.test.main.RequiresDB; import com.cloudant.tests.base.TestWithDb; import com.google.gson.JsonArray; @@ -74,14 +75,12 @@ public void findJsonObject() { assertNotNull(jsonObject); } - /* @Test - @RequiresCouch public void findAny() { - String uri = account.getBaseUri() + "_stats"; + String uri = account.getBaseUri() + "_all_dbs"; JsonObject jsonObject = db.findAny(JsonObject.class, uri); assertNotNull(jsonObject); - }*/ + } @Test public void findInputstream() throws IOException { From 209e8e398ad5d893b360fdfb9e9f69fa3b4b3223 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Mon, 26 Mar 2018 11:07:22 +0100 Subject: [PATCH 07/17] Make per-test member non static --- .../test/java/com/cloudant/api/query/IndexLifecycleTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9a9a0ad89..70886082c 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 @@ -50,7 +50,7 @@ public class IndexLifecycleTest { @RegisterExtension public static CloudantClientExtension clientResource = new CloudantClientExtension(); @RegisterExtension - public static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest(clientResource); + public DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest(clientResource); private static Database db; From e8a6acb78b0847ed07f21afa5838d5e7a0dc267e Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Mon, 26 Mar 2018 11:19:45 +0100 Subject: [PATCH 08/17] Clarify logic and variable naming in UnicodeTest --- .../java/com/cloudant/tests/UnicodeTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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 4b5c7ffc1..b306492ed 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java @@ -51,13 +51,13 @@ public class UnicodeTest extends TestWithDbPerTest { // (U+0000..U+001F, U+0022, U+005C) may be placed in a string. // - All Unicode characters may be included as Unicode escapes // (after conversion to UTF-16). + private static final String TESTSTRING_KEY = "teststring"; 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 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"; + private static final String EXPECTED_JSON = "{\"_id\":\""+TESTSTRING_KEY+"\"," + + "\"_rev\":\"1-39933759c7250133b6039d94ea09134f\",\"foo\":\"Gr\u00fc\u00dfe " + + "\u65e5\u672c\u8a9e \uD834\uDD1E.\"}\n"; // ======================================================================== // REST request utilities. @@ -243,7 +243,7 @@ private static void closeResponse(HttpConnection response) throws Exception { */ @Test public void testLiteralUnicode() throws Exception { - URI uri = new DatabaseURIHelper(db.getDBUri()).path("literal").build(); + URI uri = new DatabaseURIHelper(db.getDBUri()).path(TESTSTRING_KEY).build(); { HttpConnection conn = Http.PUT(uri, "application/json"); conn.requestProperties.put("Accept", "application/json"); @@ -258,7 +258,7 @@ public void testLiteralUnicode() throws Exception { clientResource.get().executeRequest(conn); assertEquals(200, conn.getConnection().getResponseCode()); String result = getPlainTextEntityAsString(conn, uri); - assertEquals(TESTJSON, result); + assertEquals(EXPECTED_JSON, result); closeResponse(conn); } { @@ -278,7 +278,7 @@ public void testLiteralUnicode() throws Exception { */ @Test public void testEscapedUnicode() throws Exception { - URI uri = new DatabaseURIHelper(db.getDBUri()).path("escaped").build(); + URI uri = new DatabaseURIHelper(db.getDBUri()).path(TESTSTRING_KEY).build(); { HttpConnection conn = Http.PUT(uri, "application/json"); conn.requestProperties.put("Accept", "application/json"); @@ -293,7 +293,7 @@ public void testEscapedUnicode() throws Exception { clientResource.get().executeRequest(conn); assertEquals(200, conn.getConnection().getResponseCode()); String result = getPlainTextEntityAsString(conn, uri); - assertEquals(TESTJSON_ESCAPED, result); + assertEquals(EXPECTED_JSON, result); closeResponse(conn); } { From f378a13e70bcb431f06317cddf44b4dfe3862cdd Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Mon, 26 Mar 2018 11:22:14 +0100 Subject: [PATCH 09/17] Typos --- .../src/test/java/com/cloudant/tests/ViewsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 6900b4b63..403cd3903 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java @@ -872,7 +872,7 @@ public void multiRequest() throws IOException { .build(); int i = 1; List> responses = multi.getViewResponses(); - assertEquals(3, responses.size(), "There should be 3 respones for 3 requests"); + assertEquals(3, responses.size(), "There should be 3 responses for 3 requests"); for (ViewResponse response : responses) { 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); @@ -1013,7 +1013,7 @@ public void multiRequestMixedReduced() throws IOException { .build(); List> responses = multi.getViewResponses(); - assertEquals(2, responses.size(), "There should be 2 respones for 2 requests"); + assertEquals(2, responses.size(), "There should be 2 responses for 2 requests"); List javaTagKeys = responses.get(0).getKeys(); assertEquals(1, javaTagKeys.size(), "There should be 1 java tag result"); From 6be59bb2cb01b019890afd92627a1ab8fec20c92 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Mon, 26 Mar 2018 16:47:05 +0100 Subject: [PATCH 10/17] Abstract class TestWithDB for per test/class variants --- .../com/cloudant/tests/AttachmentsTest.java | 4 ++-- .../com/cloudant/tests/BulkDocumentTest.java | 4 ++-- .../tests/ChangeNotificationsTest.java | 4 ++-- .../cloudant/tests/CloudantClientTests.java | 4 ++-- .../java/com/cloudant/tests/DBServerTest.java | 4 ++-- .../java/com/cloudant/tests/DatabaseTest.java | 4 ++-- .../cloudant/tests/DesignDocumentsTest.java | 4 ++-- .../com/cloudant/tests/DocumentsCRUDTest.java | 5 ++-- .../java/com/cloudant/tests/IndexTests.java | 4 ++-- .../java/com/cloudant/tests/ResponseTest.java | 4 ++-- .../java/com/cloudant/tests/SearchTests.java | 4 ++-- .../com/cloudant/tests/UpdateHandlerTest.java | 4 ++-- .../com/cloudant/tests/base/TestWithDb.java | 19 ++------------- .../tests/base/TestWithDbPerClass.java | 23 +++++++++++++++++++ .../tests/base/TestWithDbPerTest.java | 9 +------- .../util/HttpFactoryParameterizedTest.java | 4 ++-- 16 files changed, 52 insertions(+), 52 deletions(-) create mode 100644 cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java 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 a80bd9a50..8b16d9b7a 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java @@ -26,7 +26,7 @@ import com.cloudant.client.api.model.Params; import com.cloudant.client.api.model.Response; import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.cloudant.tests.util.Utils; import org.apache.commons.codec.binary.Base64; @@ -41,7 +41,7 @@ import java.net.URISyntaxException; @RequiresDB -public class AttachmentsTest extends TestWithDb { +public class AttachmentsTest extends TestWithDbPerClass { @Test public void attachmentInline() { 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 320fdfa1a..342a77114 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/BulkDocumentTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/BulkDocumentTest.java @@ -19,7 +19,7 @@ import com.cloudant.client.api.model.Response; import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.google.gson.JsonObject; import org.junit.jupiter.api.Test; @@ -28,7 +28,7 @@ import java.util.List; @RequiresDB -public class BulkDocumentTest extends TestWithDb { +public class BulkDocumentTest extends TestWithDbPerClass { @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 66cacc378..c5f3d23c0 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java @@ -28,7 +28,7 @@ import com.cloudant.client.api.model.DbInfo; import com.cloudant.client.api.model.Response; import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.cloudant.tests.extensions.MockWebServerExtension; import com.google.gson.JsonObject; @@ -44,7 +44,7 @@ import java.util.concurrent.TimeUnit; @RequiresDB -public class ChangeNotificationsTest extends TestWithDb { +public class ChangeNotificationsTest extends TestWithDbPerClass { @RegisterExtension public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); 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 56249cb1f..00da9866d 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java @@ -36,7 +36,7 @@ import com.cloudant.test.main.RequiresCloudant; import com.cloudant.test.main.RequiresCloudantService; import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.cloudant.tests.extensions.MockWebServerExtension; import com.cloudant.tests.util.MockWebServerResources; import com.cloudant.tests.util.Utils; @@ -75,7 +75,7 @@ /** * Note some tests in this class use Java 1.7 features */ -public class CloudantClientTests extends TestWithDb { +public class CloudantClientTests extends TestWithDbPerClass { @RegisterExtension public MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); 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 b9c32291f..c82e58b0f 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DBServerTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DBServerTest.java @@ -22,14 +22,14 @@ import com.cloudant.client.api.model.DbInfo; import com.cloudant.client.api.model.MetaInformation; import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import org.junit.jupiter.api.Test; import java.util.List; @RequiresDB -public class DBServerTest extends TestWithDb { +public class DBServerTest extends TestWithDbPerClass { @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 e808a26a3..0ea52f7cb 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java @@ -29,7 +29,7 @@ import com.cloudant.test.main.RequiresCloudantService; import com.cloudant.test.main.RequiresCouch; import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.cloudant.tests.extensions.MockWebServerExtension; import com.cloudant.tests.util.MockWebServerResources; import com.google.gson.GsonBuilder; @@ -50,7 +50,7 @@ import java.util.Map; @RequiresDB -public class DatabaseTest extends TestWithDb { +public class DatabaseTest extends TestWithDbPerClass { @RegisterExtension public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java index 9a0b08d84..3c5147c98 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java @@ -30,7 +30,7 @@ import com.cloudant.client.api.model.Response; import com.cloudant.client.org.lightcouch.CouchDbException; import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.cloudant.tests.extensions.MockWebServerExtension; import com.cloudant.tests.util.Utils; import com.google.gson.JsonObject; @@ -54,7 +54,7 @@ import java.util.concurrent.TimeUnit; @RequiresDB -public class DesignDocumentsTest extends TestWithDb { +public class DesignDocumentsTest extends TestWithDbPerClass { @RegisterExtension public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java index 3ecc13a78..d4d817356 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java @@ -24,9 +24,8 @@ import com.cloudant.client.api.model.Response; import com.cloudant.client.org.lightcouch.DocumentConflictException; import com.cloudant.client.org.lightcouch.NoDocumentException; -import com.cloudant.test.main.RequiresCouch; import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -40,7 +39,7 @@ import java.util.UUID; @RequiresDB -public class DocumentsCRUDTest extends TestWithDb { +public class DocumentsCRUDTest extends TestWithDbPerClass { // Find diff --git a/cloudant-client/src/test/java/com/cloudant/tests/IndexTests.java b/cloudant-client/src/test/java/com/cloudant/tests/IndexTests.java index 9f8a66edb..92d1bc2aa 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/IndexTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/IndexTests.java @@ -33,7 +33,7 @@ import com.cloudant.client.api.query.QueryResult; import com.cloudant.client.api.query.Sort; import com.cloudant.test.main.RequiresCloudant; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -53,7 +53,7 @@ import java.util.concurrent.TimeUnit; @RequiresCloudant -public class IndexTests extends TestWithDb { +public class IndexTests extends TestWithDbPerClass { @BeforeAll public static void setUp() throws Exception { diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java index fe53b523c..7e4ceeeea 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java @@ -29,7 +29,7 @@ import com.cloudant.http.HttpConnectionInterceptorContext; import com.cloudant.http.HttpConnectionRequestInterceptor; import com.cloudant.test.main.RequiresCloudant; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.cloudant.tests.util.Utils; import org.junit.jupiter.api.BeforeEach; @@ -43,7 +43,7 @@ * Test cases to verify status code from Response object. * Assert status codes in CouchDbException and its subclasses. */ -public class ResponseTest extends TestWithDb { +public class ResponseTest extends TestWithDbPerClass { private Foo foo; 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 5fb63e1f0..ea1e9f961 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java @@ -25,7 +25,7 @@ import com.cloudant.client.api.model.SearchResult.SearchResultRow; import com.cloudant.client.internal.URIBase; import com.cloudant.test.main.RequiresCloudant; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -40,7 +40,7 @@ import java.util.Map.Entry; @RequiresCloudant -public class SearchTests extends TestWithDb { +public class SearchTests extends TestWithDbPerClass { @BeforeAll public static void setUp() throws Exception { 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 f2cc4820d..9315fe82a 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/UpdateHandlerTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/UpdateHandlerTest.java @@ -21,14 +21,14 @@ import com.cloudant.client.api.model.Params; import com.cloudant.client.api.model.Response; import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import com.cloudant.tests.util.Utils; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @RequiresDB -public class UpdateHandlerTest extends TestWithDb { +public class UpdateHandlerTest extends TestWithDbPerClass { @BeforeAll public static void beforeAll() throws Exception { 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 index 89fdb225d..7dc185517 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDb.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDb.java @@ -3,28 +3,13 @@ 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 { +public abstract 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(); - } + protected static Database db; } diff --git a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java new file mode 100644 index 000000000..b7b913c5d --- /dev/null +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java @@ -0,0 +1,23 @@ +package com.cloudant.tests.base; + +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 TestWithDbPerClass extends TestWithDb { + + protected static DatabaseExtension.PerClass dbResource = new DatabaseExtension.PerClass(clientResource); + + @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 index 59b32f8d2..090069608 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java @@ -1,8 +1,5 @@ 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; @@ -11,14 +8,10 @@ import org.junit.jupiter.api.extension.RegisterExtension; // Base class for tests which require a new DB for each test method -public class TestWithDbPerTest { +public class TestWithDbPerTest extends TestWithDb { - 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); 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 92ce15acc..92548279f 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 @@ -18,14 +18,14 @@ import com.cloudant.http.internal.DefaultHttpUrlConnectionFactory; import com.cloudant.http.internal.ok.OkHelper; -import com.cloudant.tests.base.TestWithDb; +import com.cloudant.tests.base.TestWithDbPerClass; import org.junit.jupiter.api.BeforeEach; import mockit.Mock; import mockit.MockUp; -public abstract class HttpFactoryParameterizedTest extends TestWithDb { +public abstract class HttpFactoryParameterizedTest extends TestWithDbPerClass { /** * A parameter governing whether to allow okhttp or not. This lets us exercise both From f3249613632e1fa209a7a04c90169699bbc862fc Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 27 Mar 2018 15:21:55 +0100 Subject: [PATCH 11/17] Copyrights Note some files have had line endings converted to UNIX in the process --- .../cloudant/api/query/FieldAssertHelper.java | 2 +- .../api/query/IndexDeletionTests.java | 2 +- .../api/query/IndexLifecycleTest.java | 2 +- .../cloudant/api/query/IndexListTests.java | 2 +- .../cloudant/test/main/RequiresCloudant.java | 4 +- .../test/main/RequiresCloudantLocal.java | 4 +- .../test/main/RequiresCloudantService.java | 4 +- .../com/cloudant/test/main/RequiresCouch.java | 4 +- .../com/cloudant/test/main/RequiresDB.java | 4 +- .../com/cloudant/tests/AttachmentsTest.java | 2 +- .../com/cloudant/tests/BulkDocumentTest.java | 2 +- .../tests/ChangeNotificationsTest.java | 2 +- .../com/cloudant/tests/ClientLoadTest.java | 2 +- .../tests/CloudFoundryServiceTest.java | 2 +- .../cloudant/tests/CloudantClientHelper.java | 2 +- .../cloudant/tests/CloudantClientTests.java | 1136 ++++++++--------- .../tests/ComplexKeySerializationTest.java | 4 +- .../com/cloudant/tests/CouchDbUtilTest.java | 2 +- .../java/com/cloudant/tests/DatabaseTest.java | 402 +++--- .../cloudant/tests/DatabaseURIHelperTest.java | 3 +- .../cloudant/tests/DesignDocumentTest.java | 2 +- .../cloudant/tests/DesignDocumentsTest.java | 2 +- .../com/cloudant/tests/DocumentsCRUDTest.java | 2 +- .../src/test/java/com/cloudant/tests/Foo.java | 2 +- .../tests/HierarchicalUriComponentsTest.java | 2 +- .../com/cloudant/tests/HttpProxyTest.java | 2 +- .../java/com/cloudant/tests/HttpTest.java | 2 +- .../java/com/cloudant/tests/LoggingTest.java | 2 +- .../com/cloudant/tests/ReplicationTest.java | 2 +- .../com/cloudant/tests/ReplicatorTest.java | 256 ++-- .../java/com/cloudant/tests/ResponseTest.java | 2 +- .../java/com/cloudant/tests/SearchTests.java | 2 +- .../cloudant/tests/SslAuthenticationTest.java | 2 +- .../java/com/cloudant/tests/URIBaseTest.java | 13 + .../java/com/cloudant/tests/UnicodeTest.java | 2 +- .../com/cloudant/tests/UpdateHandlerTest.java | 2 +- .../cloudant/tests/ViewPaginationTests.java | 2 +- .../java/com/cloudant/tests/ViewsTest.java | 2 +- .../com/cloudant/tests/base/TestWithDb.java | 13 + .../tests/base/TestWithDbPerClass.java | 13 + .../tests/base/TestWithDbPerTest.java | 13 + .../tests/base/TestWithMockedServer.java | 2 +- .../tests/base/TestWithReplication.java | 2 +- .../extensions/AbstractClientExtension.java | 13 + .../extensions/CloudantClientExtension.java | 2 +- .../CloudantClientMockServerExtension.java | 2 +- .../tests/extensions/DatabaseExtension.java | 2 +- .../extensions/MockWebServerExtension.java | 13 + .../tests/extensions/MultiExtension.java | 13 + .../cloudant/tests/util/CheckPagination.java | 2 +- .../util/HttpFactoryParameterizedTest.java | 2 +- .../java/com/cloudant/tests/util/Utils.java | 2 +- 52 files changed, 1037 insertions(+), 945 deletions(-) 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 96264255a..ace2a2ade 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 @@ -1,5 +1,5 @@ /* - * Copyright © 2017 IBM Corp. All rights reserved. + * Copyright © 2017, 2018 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 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 b0bf7ecde..f385e9866 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 @@ -1,5 +1,5 @@ /* - * Copyright © 2017 IBM Corp. All rights reserved. + * Copyright © 2017, 2018 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 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 70886082c..a56733360 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 @@ -1,5 +1,5 @@ /* - * Copyright © 2017 IBM Corp. All rights reserved. + * Copyright © 2017, 2018 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 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 e0fa9e839..1095dd537 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 @@ -1,5 +1,5 @@ /* - * Copyright © 2017 IBM Corp. All rights reserved. + * Copyright © 2017, 2018 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 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 40f1ac768..060f50957 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 @@ -29,4 +29,4 @@ @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 bda903355..7c0c6e150 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 @@ -30,4 +30,4 @@ @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 790964b64..d453f472a 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 @@ -30,4 +30,4 @@ @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 6364beb33..59d1e6f95 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 @@ -29,4 +29,4 @@ @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 dd69b50f9..267e50b03 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 @@ -28,4 +28,4 @@ @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 8b16d9b7a..e6bd64ca8 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2011 lightcouch.org - * Copyright © 2015, 2016 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 342a77114..6fd1ac28f 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/BulkDocumentTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/BulkDocumentTest.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2011 lightcouch.org - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 c5f3d23c0..426ad947e 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2011 lightcouch.org - * Copyright © 2015, 2016 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 96fb864d2..14244328e 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ClientLoadTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ClientLoadTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 cb28d8df6..39045fdba 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/CloudFoundryServiceTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudFoundryServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2016 IBM Corp. All rights reserved. + * Copyright © 2016, 2018 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 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 5979362cd..08b06df89 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientHelper.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientHelper.java @@ -1,5 +1,5 @@ /* - * Copyright © 2015, 2016 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 00da9866d..c8c4b464a 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java @@ -1,568 +1,568 @@ -/* - * Copyright © 2015, 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; - -import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.createPost; -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; -import com.cloudant.client.api.Database; -import com.cloudant.client.api.model.ApiKey; -import com.cloudant.client.api.model.Membership; -import com.cloudant.client.api.model.Task; -import com.cloudant.client.org.lightcouch.CouchDbException; -import com.cloudant.client.org.lightcouch.NoDocumentException; -import com.cloudant.client.org.lightcouch.PreconditionFailedException; -import com.cloudant.http.Http; -import com.cloudant.http.HttpConnection; -import com.cloudant.http.interceptors.BasicAuthInterceptor; -import com.cloudant.http.internal.interceptors.UserAgentInterceptor; -import com.cloudant.test.main.RequiresCloudant; -import com.cloudant.test.main.RequiresCloudantService; -import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDbPerClass; -import com.cloudant.tests.extensions.MockWebServerExtension; -import com.cloudant.tests.util.MockWebServerResources; -import com.cloudant.tests.util.Utils; - -import org.apache.commons.io.IOUtils; -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; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import okhttp3.mockwebserver.SocketPolicy; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.InetAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketTimeoutException; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLEncoder; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.net.ServerSocketFactory; - -/** - * Note some tests in this class use Java 1.7 features - */ -public class CloudantClientTests extends TestWithDbPerClass { - - @RegisterExtension - public MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); - - public MockWebServer server; - - @BeforeEach - public void beforeEach() { - server = mockWebServerExt.get(); - } - - @Test - @RequiresCloudantService - public void apiKey() { - ApiKey key = account.generateApiKey(); - assertNotNull(key); - assertNotNull(key.getKey()); - assertNotNull(key.getPassword()); - } - - @Test - @RequiresCloudant - public void activeTasks() { - List tasks = account.getActiveTasks(); - assertNotNull(tasks); - } - - @Test - @RequiresCloudant - public void membership() { - Membership mship = account.getMembership(); - assertNotNull(mship); - assertNotNull(mship.getClusterNodes()); - assertNotNull(mship.getClusterNodes().hasNext()); - assertNotNull(mship.getAllNodes()); - assertNotNull(mship.getAllNodes().hasNext()); - } - - @Test - @RequiresCloudant - public void cookieTest() { - - Membership membership = account.getMembership(); - assertNotNull(membership); - } - - // java-cloudant/n.n.n or java-cloudant/unknown followed by 4 groups of /anything - private final String userAgentRegex = "java-cloudant/(?:(?:\\d+.\\d+.\\d+))" + - "(?:/{1}[^/]+){4}"; - - private final String userAgentUnknownRegex = "cloudant-http/(?:(?:unknown))" + - "(?:/{1}[^/]+){4}"; - - - private final String userAgentFormat = "java-cloudant/version/jvm.version/jvm.vendor/os" + - ".name/os.arch"; - - /** - * Assert that the User-Agent header is of the expected form. - */ - @Test - public void testUserAgentHeaderString() throws Exception { - - // This doesn't read the a properties file, since the tests do not run from the published - // jars. - String userAgentHeader = new UserAgentInterceptor(UserAgentInterceptor.class - .getClassLoader(), - "META-INF/com.cloudant.client.properties").getUserAgent(); - assertTrue(userAgentHeader.matches(userAgentUnknownRegex), "The value of the User-Agent header: " + userAgentHeader + " should match the " + "format: " + userAgentFormat); - } - - @Test - public void testUserAgentHeaderStringFromFile() throws Exception { - // This doesn't read the a properties file, since the tests do not run from the published - // jars. - // Point to the built classes, it's a bit awkward but we need to load the class cleanly - File f = new File("../cloudant-http/build/classes/main/"); - String userAgentHeader = new UserAgentInterceptor(new URLClassLoader(new URL[]{f.toURI() - .toURL()}) { - - @Override - public InputStream getResourceAsStream(String name) { - if (name.equals("META-INF/com.cloudant.client.properties")) { - try { - return new ByteArrayInputStream(("user.agent.name=java-cloudant\nuser" + - ".agent.version=1.6.1").getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - return super.getResourceAsStream(name); - } - }, "META-INF/com.cloudant.client.properties").getUserAgent(); - assertTrue(userAgentHeader.matches(userAgentRegex), "The value of the User-Agent header: " + userAgentHeader + " should match the " + "format: " + userAgentFormat); - } - - /** - * Assert that requests have the User-Agent header added. This test runs a local HTTP server - * process that can handle a single request to receive the request and validate the header. - */ - @Test - public void testUserAgentHeaderIsAddedToRequest() throws Exception { - - //send back an OK 200 - server.enqueue(new MockResponse()); - - //instantiating the client performs a single post request - CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder(server) - .build(); - String response = client.executeRequest(createPost(client.getBaseUri(), null, - "application/json")).responseAsString(); - 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(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 - @RequiresDB - public void nonExistentDatabaseException() { - 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 - @RequiresDB - public void existingDatabaseCreateException() { - String id = Utils.generateUUID(); - String dbName = "existing" + id; - try { - //create a DB for this test - account.createDB(dbName); - - // Get a database instance using create true for the already existing DB - account.database(dbName, true); - } finally { - //clean up the DB created by this test - account.deleteDB(dbName); - } - } - - /** - * Validate that a PreconditionFailedException is thrown when using the createDB method to - * create a database that already exists. - */ - @Test - @RequiresDB - public void existingDatabaseCreateDBException() { - assertThrows(PreconditionFailedException.class, new Executable() { - @Override - public void execute() throws Throwable { - - String id = Utils.generateUUID(); - String dbName = "existing" + id; - try { - //create a DB for this test - account.createDB(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 - public void testDefaultPorts() throws Exception { - CloudantClient c = null; - - c = CloudantClientHelper.newTestAddressClient().build(); - - assertEquals(80, c.getBaseUri().getPort(), "The http port should be 80"); - - - c = CloudantClientHelper.newHttpsTestAddressClient().build(); - - 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 - public void connectionTimeout() throws Throwable { - 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); - } - } - }); - } - - /** - * Checks that the read timeout works. The test sets a read timeout of 0.25 s and the mock - * server thread never sends a response. If things are working - * correctly then the client should see a SocketTimeoutException for the read. - */ - @Test - public void readTimeout() throws Throwable { - 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 - public void nullUser() throws Exception { - 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 - public void nullPassword() throws Exception { - assertThrows(CouchDbException.class, new Executable() { - @Override - public void execute() throws Throwable { - CloudantClientHelper.newTestAddressClient() - .username("user") - .build(); - } - }); - } - - /** - * Test that user info provided in a url is correctly removed and made into user name and - * password fields. - */ - @Test - public void testUserInfoInUrl() throws Exception { - urlCheck("user", "password"); - } - - // A String of all the URI reserved and "unsafe" characters, plus © and an emoji. Encodes to: - // %21*%27%28%29%3B%3A%40%26%3D%2B%24%2C%2F%3F%23%5B%5D+%22%25 - // -.%3C%3E%5C%5E_%60%7B%7C%7D%7E%C2%A9%F0%9F%94%92 - private static String SPECIALS = "!*'();:@&=+$,/?#[] \"%-.<>\\^_`{|}~©\uD83D\uDD12"; - - /** - * Test that user info provided in a url is correctly removed and made into user name and - * password fields when the user info includes URL encoded characters. - */ - @Test - public void testUserInfoInUrlWithSpecials() throws Exception { - String user = URLEncoder.encode("user" + SPECIALS, "UTF-8"); - String pw = URLEncoder.encode("password" + SPECIALS, "UTF-8"); - urlCheck(user, pw); - } - - /** - * Test that user info provided via the username and password methods including URL special - * characters is encoded correctly. - */ - @Test - public void testUserInfoWithSpecials() throws Exception { - String user = "user" + SPECIALS; - String pw = "password" + SPECIALS; - credentialsCheck(CloudantClientHelper.newMockWebServerClientBuilder(server).username - (user).password(pw), URLEncoder.encode(user, "UTF-8"), URLEncoder.encode(pw, - "UTF-8")); - } - - private static Pattern CREDENTIALS = Pattern.compile("name=([^&]+)&password=(.+)"); - - private void urlCheck(String encodedUser, String encodedPassword) throws Exception { - ClientBuilder b = ClientBuilder.url(server.url("").newBuilder().encodedUsername - (encodedUser).encodedPassword(encodedPassword).build().url()); - credentialsCheck(b, encodedUser, encodedPassword); - } - - private void credentialsCheck(ClientBuilder b, String encodedUser, String encodedPassword) - throws Exception { - CloudantClient c = b.build(); - - server.enqueue(MockWebServerResources.OK_COOKIE); - server.enqueue(MockWebServerResources.JSON_OK); - - HttpConnection conn = c.executeRequest(Http.GET(c.getBaseUri())); - // Consume response stream and assert ok: true - String responseStr = conn.responseAsString(); - assertNotNull(responseStr); - - // One request to _session then one to get info - assertEquals(2, server.getRequestCount(), "There should be two requests"); - - // Get the _session request - RecordedRequest request = server.takeRequest(); - String body = request.getBody().readUtf8(); - // body should be of form: - // name=YourUserName&password=YourPassword - Matcher m = CREDENTIALS.matcher(body); - 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()); - } - - @Test - public void sessionDeleteOnShutdown() throws Exception { - // Mock a 200 OK for the _session DELETE request - server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"ok\":\"true\"}")); - - CloudantClient c = CloudantClientHelper.newMockWebServerClientBuilder(server).build(); - c.shutdown(); - - RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS); - 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 - @RequiresCloudant - public void testBasicAuth() throws IOException { - BasicAuthInterceptor interceptor = - new BasicAuthInterceptor(CloudantClientHelper.COUCH_USERNAME - + ":" + CloudantClientHelper.COUCH_PASSWORD); - - CloudantClient client = ClientBuilder.account(CloudantClientHelper.COUCH_USERNAME) - .interceptors(interceptor).build(); - - // Test passes if there are no exceptions - client.getAllDbs(); - } - - /** - * Test that configuring the Basic Authentication interceptor from credentials and adding to - * the CloudantClient works. - */ - @Test - public void testBasicAuthFromCredentials() throws Exception { - BasicAuthInterceptor interceptor = - BasicAuthInterceptor.createFromCredentials("username", "password"); - - // send back a mock OK 200 - server.enqueue(new MockResponse()); - - CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder(server) - .interceptors(interceptor).build(); - - client.getAllDbs(); - - // expected 'username:password' - assertEquals("Basic dXNlcm5hbWU6cGFzc3dvcmQ=", server.takeRequest().getHeader("Authorization")); - } - - @Test - public void gatewayStyleURL() throws Exception { - - final String gatewayPath = "/gateway"; - - // Set a dispatcher that returns 200 if the requests have the correct path /gateway/_all_dbs - // Otherwise return 400. - server.setDispatcher(new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) throws InterruptedException { - if (request.getPath().equals(gatewayPath + "/_all_dbs")) { - return new MockResponse(); - } else { - return new MockResponse().setResponseCode(400); - } - } - }); - - // Build a client with a URL that includes a path - CloudantClient c = ClientBuilder.url(new URL(server.url(gatewayPath).toString())).build(); - // If the request path is wrong this call will return 400 and throw an exception failing the - // test. - c.getAllDbs(); - - // Build a client with a URL that includes a path with a trailing / - c = ClientBuilder.url(new URL(server.url(gatewayPath + "/").toString())).build(); - // If the request path is wrong this call will return 400 and throw an exception failing the - // test. - c.getAllDbs(); - } - - /** - * Assert that a {@code null} URL causes an IllegalArgumentException to be thrown. - * - * @throws Exception - */ - @Test - public void nullURLThrowsIAE() throws Exception { - assertThrows(IllegalArgumentException.class, new Executable() { - @Override - public void execute() throws Throwable { - ClientBuilder.url(null); - } - }); - } -} +/* + * Copyright © 2015, 2018 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; + +import static com.cloudant.client.org.lightcouch.internal.CouchDbUtil.createPost; +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; +import com.cloudant.client.api.Database; +import com.cloudant.client.api.model.ApiKey; +import com.cloudant.client.api.model.Membership; +import com.cloudant.client.api.model.Task; +import com.cloudant.client.org.lightcouch.CouchDbException; +import com.cloudant.client.org.lightcouch.NoDocumentException; +import com.cloudant.client.org.lightcouch.PreconditionFailedException; +import com.cloudant.http.Http; +import com.cloudant.http.HttpConnection; +import com.cloudant.http.interceptors.BasicAuthInterceptor; +import com.cloudant.http.internal.interceptors.UserAgentInterceptor; +import com.cloudant.test.main.RequiresCloudant; +import com.cloudant.test.main.RequiresCloudantService; +import com.cloudant.test.main.RequiresDB; +import com.cloudant.tests.base.TestWithDbPerClass; +import com.cloudant.tests.extensions.MockWebServerExtension; +import com.cloudant.tests.util.MockWebServerResources; +import com.cloudant.tests.util.Utils; + +import org.apache.commons.io.IOUtils; +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; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import okhttp3.mockwebserver.SocketPolicy; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLEncoder; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.net.ServerSocketFactory; + +/** + * Note some tests in this class use Java 1.7 features + */ +public class CloudantClientTests extends TestWithDbPerClass { + + @RegisterExtension + public MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); + + public MockWebServer server; + + @BeforeEach + public void beforeEach() { + server = mockWebServerExt.get(); + } + + @Test + @RequiresCloudantService + public void apiKey() { + ApiKey key = account.generateApiKey(); + assertNotNull(key); + assertNotNull(key.getKey()); + assertNotNull(key.getPassword()); + } + + @Test + @RequiresCloudant + public void activeTasks() { + List tasks = account.getActiveTasks(); + assertNotNull(tasks); + } + + @Test + @RequiresCloudant + public void membership() { + Membership mship = account.getMembership(); + assertNotNull(mship); + assertNotNull(mship.getClusterNodes()); + assertNotNull(mship.getClusterNodes().hasNext()); + assertNotNull(mship.getAllNodes()); + assertNotNull(mship.getAllNodes().hasNext()); + } + + @Test + @RequiresCloudant + public void cookieTest() { + + Membership membership = account.getMembership(); + assertNotNull(membership); + } + + // java-cloudant/n.n.n or java-cloudant/unknown followed by 4 groups of /anything + private final String userAgentRegex = "java-cloudant/(?:(?:\\d+.\\d+.\\d+))" + + "(?:/{1}[^/]+){4}"; + + private final String userAgentUnknownRegex = "cloudant-http/(?:(?:unknown))" + + "(?:/{1}[^/]+){4}"; + + + private final String userAgentFormat = "java-cloudant/version/jvm.version/jvm.vendor/os" + + ".name/os.arch"; + + /** + * Assert that the User-Agent header is of the expected form. + */ + @Test + public void testUserAgentHeaderString() throws Exception { + + // This doesn't read the a properties file, since the tests do not run from the published + // jars. + String userAgentHeader = new UserAgentInterceptor(UserAgentInterceptor.class + .getClassLoader(), + "META-INF/com.cloudant.client.properties").getUserAgent(); + assertTrue(userAgentHeader.matches(userAgentUnknownRegex), "The value of the User-Agent header: " + userAgentHeader + " should match the " + "format: " + userAgentFormat); + } + + @Test + public void testUserAgentHeaderStringFromFile() throws Exception { + // This doesn't read the a properties file, since the tests do not run from the published + // jars. + // Point to the built classes, it's a bit awkward but we need to load the class cleanly + File f = new File("../cloudant-http/build/classes/main/"); + String userAgentHeader = new UserAgentInterceptor(new URLClassLoader(new URL[]{f.toURI() + .toURL()}) { + + @Override + public InputStream getResourceAsStream(String name) { + if (name.equals("META-INF/com.cloudant.client.properties")) { + try { + return new ByteArrayInputStream(("user.agent.name=java-cloudant\nuser" + + ".agent.version=1.6.1").getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + return super.getResourceAsStream(name); + } + }, "META-INF/com.cloudant.client.properties").getUserAgent(); + assertTrue(userAgentHeader.matches(userAgentRegex), "The value of the User-Agent header: " + userAgentHeader + " should match the " + "format: " + userAgentFormat); + } + + /** + * Assert that requests have the User-Agent header added. This test runs a local HTTP server + * process that can handle a single request to receive the request and validate the header. + */ + @Test + public void testUserAgentHeaderIsAddedToRequest() throws Exception { + + //send back an OK 200 + server.enqueue(new MockResponse()); + + //instantiating the client performs a single post request + CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder(server) + .build(); + String response = client.executeRequest(createPost(client.getBaseUri(), null, + "application/json")).responseAsString(); + 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(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 + @RequiresDB + public void nonExistentDatabaseException() { + 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 + @RequiresDB + public void existingDatabaseCreateException() { + String id = Utils.generateUUID(); + String dbName = "existing" + id; + try { + //create a DB for this test + account.createDB(dbName); + + // Get a database instance using create true for the already existing DB + account.database(dbName, true); + } finally { + //clean up the DB created by this test + account.deleteDB(dbName); + } + } + + /** + * Validate that a PreconditionFailedException is thrown when using the createDB method to + * create a database that already exists. + */ + @Test + @RequiresDB + public void existingDatabaseCreateDBException() { + assertThrows(PreconditionFailedException.class, new Executable() { + @Override + public void execute() throws Throwable { + + String id = Utils.generateUUID(); + String dbName = "existing" + id; + try { + //create a DB for this test + account.createDB(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 + public void testDefaultPorts() throws Exception { + CloudantClient c = null; + + c = CloudantClientHelper.newTestAddressClient().build(); + + assertEquals(80, c.getBaseUri().getPort(), "The http port should be 80"); + + + c = CloudantClientHelper.newHttpsTestAddressClient().build(); + + 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 + public void connectionTimeout() throws Throwable { + 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); + } + } + }); + } + + /** + * Checks that the read timeout works. The test sets a read timeout of 0.25 s and the mock + * server thread never sends a response. If things are working + * correctly then the client should see a SocketTimeoutException for the read. + */ + @Test + public void readTimeout() throws Throwable { + 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 + public void nullUser() throws Exception { + 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 + public void nullPassword() throws Exception { + assertThrows(CouchDbException.class, new Executable() { + @Override + public void execute() throws Throwable { + CloudantClientHelper.newTestAddressClient() + .username("user") + .build(); + } + }); + } + + /** + * Test that user info provided in a url is correctly removed and made into user name and + * password fields. + */ + @Test + public void testUserInfoInUrl() throws Exception { + urlCheck("user", "password"); + } + + // A String of all the URI reserved and "unsafe" characters, plus © and an emoji. Encodes to: + // %21*%27%28%29%3B%3A%40%26%3D%2B%24%2C%2F%3F%23%5B%5D+%22%25 + // -.%3C%3E%5C%5E_%60%7B%7C%7D%7E%C2%A9%F0%9F%94%92 + private static String SPECIALS = "!*'();:@&=+$,/?#[] \"%-.<>\\^_`{|}~©\uD83D\uDD12"; + + /** + * Test that user info provided in a url is correctly removed and made into user name and + * password fields when the user info includes URL encoded characters. + */ + @Test + public void testUserInfoInUrlWithSpecials() throws Exception { + String user = URLEncoder.encode("user" + SPECIALS, "UTF-8"); + String pw = URLEncoder.encode("password" + SPECIALS, "UTF-8"); + urlCheck(user, pw); + } + + /** + * Test that user info provided via the username and password methods including URL special + * characters is encoded correctly. + */ + @Test + public void testUserInfoWithSpecials() throws Exception { + String user = "user" + SPECIALS; + String pw = "password" + SPECIALS; + credentialsCheck(CloudantClientHelper.newMockWebServerClientBuilder(server).username + (user).password(pw), URLEncoder.encode(user, "UTF-8"), URLEncoder.encode(pw, + "UTF-8")); + } + + private static Pattern CREDENTIALS = Pattern.compile("name=([^&]+)&password=(.+)"); + + private void urlCheck(String encodedUser, String encodedPassword) throws Exception { + ClientBuilder b = ClientBuilder.url(server.url("").newBuilder().encodedUsername + (encodedUser).encodedPassword(encodedPassword).build().url()); + credentialsCheck(b, encodedUser, encodedPassword); + } + + private void credentialsCheck(ClientBuilder b, String encodedUser, String encodedPassword) + throws Exception { + CloudantClient c = b.build(); + + server.enqueue(MockWebServerResources.OK_COOKIE); + server.enqueue(MockWebServerResources.JSON_OK); + + HttpConnection conn = c.executeRequest(Http.GET(c.getBaseUri())); + // Consume response stream and assert ok: true + String responseStr = conn.responseAsString(); + assertNotNull(responseStr); + + // One request to _session then one to get info + assertEquals(2, server.getRequestCount(), "There should be two requests"); + + // Get the _session request + RecordedRequest request = server.takeRequest(); + String body = request.getBody().readUtf8(); + // body should be of form: + // name=YourUserName&password=YourPassword + Matcher m = CREDENTIALS.matcher(body); + 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()); + } + + @Test + public void sessionDeleteOnShutdown() throws Exception { + // Mock a 200 OK for the _session DELETE request + server.enqueue(new MockResponse().setResponseCode(200).setBody("{\"ok\":\"true\"}")); + + CloudantClient c = CloudantClientHelper.newMockWebServerClientBuilder(server).build(); + c.shutdown(); + + RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS); + 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 + @RequiresCloudant + public void testBasicAuth() throws IOException { + BasicAuthInterceptor interceptor = + new BasicAuthInterceptor(CloudantClientHelper.COUCH_USERNAME + + ":" + CloudantClientHelper.COUCH_PASSWORD); + + CloudantClient client = ClientBuilder.account(CloudantClientHelper.COUCH_USERNAME) + .interceptors(interceptor).build(); + + // Test passes if there are no exceptions + client.getAllDbs(); + } + + /** + * Test that configuring the Basic Authentication interceptor from credentials and adding to + * the CloudantClient works. + */ + @Test + public void testBasicAuthFromCredentials() throws Exception { + BasicAuthInterceptor interceptor = + BasicAuthInterceptor.createFromCredentials("username", "password"); + + // send back a mock OK 200 + server.enqueue(new MockResponse()); + + CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder(server) + .interceptors(interceptor).build(); + + client.getAllDbs(); + + // expected 'username:password' + assertEquals("Basic dXNlcm5hbWU6cGFzc3dvcmQ=", server.takeRequest().getHeader("Authorization")); + } + + @Test + public void gatewayStyleURL() throws Exception { + + final String gatewayPath = "/gateway"; + + // Set a dispatcher that returns 200 if the requests have the correct path /gateway/_all_dbs + // Otherwise return 400. + server.setDispatcher(new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) throws InterruptedException { + if (request.getPath().equals(gatewayPath + "/_all_dbs")) { + return new MockResponse(); + } else { + return new MockResponse().setResponseCode(400); + } + } + }); + + // Build a client with a URL that includes a path + CloudantClient c = ClientBuilder.url(new URL(server.url(gatewayPath).toString())).build(); + // If the request path is wrong this call will return 400 and throw an exception failing the + // test. + c.getAllDbs(); + + // Build a client with a URL that includes a path with a trailing / + c = ClientBuilder.url(new URL(server.url(gatewayPath + "/").toString())).build(); + // If the request path is wrong this call will return 400 and throw an exception failing the + // test. + c.getAllDbs(); + } + + /** + * Assert that a {@code null} URL causes an IllegalArgumentException to be thrown. + * + * @throws Exception + */ + @Test + public void nullURLThrowsIAE() throws Exception { + 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 9acc1e354..c7768e92c 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ComplexKeySerializationTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ComplexKeySerializationTest.java @@ -1,8 +1,8 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 key copy of the License at + * except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * 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 05b7920f6..f1f374d24 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/CouchDbUtilTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/CouchDbUtilTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 0ea52f7cb..8dbf89968 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java @@ -1,201 +1,201 @@ -/* - * Copyright © 2015, 2016 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; - -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; -import com.cloudant.client.api.model.ApiKey; -import com.cloudant.client.api.model.Permissions; -import com.cloudant.client.api.model.Shard; -import com.cloudant.client.org.lightcouch.CouchDbException; -import com.cloudant.test.main.RequiresCloudant; -import com.cloudant.test.main.RequiresCloudantLocal; -import com.cloudant.test.main.RequiresCloudantService; -import com.cloudant.test.main.RequiresCouch; -import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithDbPerClass; -import com.cloudant.tests.extensions.MockWebServerExtension; -import com.cloudant.tests.util.MockWebServerResources; -import com.google.gson.GsonBuilder; - -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; - -import java.net.MalformedURLException; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@RequiresDB -public class DatabaseTest extends TestWithDbPerClass { - - @RegisterExtension - public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); - - private static MockWebServer mockWebServer; - - @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("https://clientlibs-test.cloudant.com/animaldb"); - r.createTarget(true); - r.target(dbResource.getDbURIWithUserInfo()); - r.trigger(); - } - - @Test - @RequiresCloudantService - public void permissions() { - Map> userPerms = db.getPermissions(); - assertNotNull(userPerms); - ApiKey key = account.generateApiKey(); - EnumSet p = EnumSet.of(Permissions._reader, Permissions._writer); - db.setPermissions(key.getKey(), p); - userPerms = db.getPermissions(); - assertNotNull(userPerms); - assertEquals(1, userPerms.size()); - assertEquals(p, userPerms.get(key.getKey())); - - p = EnumSet.noneOf(Permissions.class); - db.setPermissions(key.getKey(), p); - userPerms = db.getPermissions(); - assertNotNull(userPerms); - assertEquals(1, userPerms.size()); - assertEquals(p, userPerms.get(key.getKey())); - } - - /** - * Test that when called against a DB that is not a Cloudant service - * an UnsupportedOperationException is thrown - */ - @RequiresCouch - @RequiresCloudantLocal - public void testPermissionsException() { - assertThrows(UnsupportedOperationException.class, new Executable() { - @Override - public void execute() throws Throwable { - Map> userPerms = db.getPermissions(); - } - }); - } - - @Test - public void permissionsParsing() throws Exception { - CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder(mockWebServer) - .build(); - Database db = client.database("notarealdb", false); - - // Mock up a request of all permissions - mockWebServer.enqueue(MockWebServerResources.PERMISSIONS); // for GET _security - mockWebServer.enqueue(MockWebServerResources.JSON_OK); // for PUT _security - db.setPermissions("testUsername", EnumSet.allOf(Permissions.class)); - - // Mock up a failing request - String testError = "test error"; - String testReason = "test reason"; - mockWebServer.enqueue(MockWebServerResources.PERMISSIONS); // for GET _security - mockWebServer.enqueue(new MockResponse().setResponseCode(400).setBody("{\"reason\":\"" + - testReason + "\", \"error\":\"" + testError + "\"}")); - try { - db.setPermissions("testUsername", EnumSet.allOf(Permissions.class)); - } catch (CouchDbException e) { - assertEquals(testError, e.getError()); - assertEquals(testReason, e.getReason()); - } - } - - @Test - @RequiresCloudant - public void shards() { - List shards = db.getShards(); - assert (shards.size() > 0); - for (Shard s : shards) { - assertNotNull(s.getRange()); - assertNotNull(s.getNodes()); - assertNotNull(s.getNodes().hasNext()); - } - } - - @Test - @RequiresCloudant - public void shard() { - Shard s = db.getShard("snipe"); - assertNotNull(s); - assertNotNull(s.getRange()); - assertNotNull(s.getNodes()); - assert (s.getNodes().hasNext()); - } - - - @Test - @RequiresCloudant - public void QuorumTests() { - - db.save(new Animal("human"), 2); - Animal h = db.find(Animal.class, "human", new com.cloudant.client.api.model.Params() - .readQuorum(2)); - assertNotNull(h); - assertEquals("human", h.getId()); - - db.update(h.setClass("inhuman"), 2); - h = db.find(Animal.class, "human", new com.cloudant.client.api.model.Params().readQuorum - (2)); - assertEquals("inhuman", h.getclass()); - - db.post(new Animal("test"), 2); - h = db.find(Animal.class, "test", new com.cloudant.client.api.model.Params().readQuorum(3)); - assertEquals("test", h.getId()); - - - } - - //Test case for issue #31 - @Test - public void customGsonDeserializerTest() throws MalformedURLException { - GsonBuilder builder = new GsonBuilder(); - builder.setDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - - CloudantClient account = CloudantClientHelper.getClientBuilder() - .gsonBuilder(builder) - .build(); - - Database db = account.database(dbResource.getDatabaseName(), false); - - Map h = new HashMap(); - h.put("_id", "serializertest"); - h.put("date", "2015-01-23T18:25:43.511Z"); - db.save(h); - - db.find(Foo.class, "serializertest"); // should not throw a JsonSyntaxException - - } -} +/* + * Copyright © 2015, 2018 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; + +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; +import com.cloudant.client.api.model.ApiKey; +import com.cloudant.client.api.model.Permissions; +import com.cloudant.client.api.model.Shard; +import com.cloudant.client.org.lightcouch.CouchDbException; +import com.cloudant.test.main.RequiresCloudant; +import com.cloudant.test.main.RequiresCloudantLocal; +import com.cloudant.test.main.RequiresCloudantService; +import com.cloudant.test.main.RequiresCouch; +import com.cloudant.test.main.RequiresDB; +import com.cloudant.tests.base.TestWithDbPerClass; +import com.cloudant.tests.extensions.MockWebServerExtension; +import com.cloudant.tests.util.MockWebServerResources; +import com.google.gson.GsonBuilder; + +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; + +import java.net.MalformedURLException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RequiresDB +public class DatabaseTest extends TestWithDbPerClass { + + @RegisterExtension + public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); + + private static MockWebServer mockWebServer; + + @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("https://clientlibs-test.cloudant.com/animaldb"); + r.createTarget(true); + r.target(dbResource.getDbURIWithUserInfo()); + r.trigger(); + } + + @Test + @RequiresCloudantService + public void permissions() { + Map> userPerms = db.getPermissions(); + assertNotNull(userPerms); + ApiKey key = account.generateApiKey(); + EnumSet p = EnumSet.of(Permissions._reader, Permissions._writer); + db.setPermissions(key.getKey(), p); + userPerms = db.getPermissions(); + assertNotNull(userPerms); + assertEquals(1, userPerms.size()); + assertEquals(p, userPerms.get(key.getKey())); + + p = EnumSet.noneOf(Permissions.class); + db.setPermissions(key.getKey(), p); + userPerms = db.getPermissions(); + assertNotNull(userPerms); + assertEquals(1, userPerms.size()); + assertEquals(p, userPerms.get(key.getKey())); + } + + /** + * Test that when called against a DB that is not a Cloudant service + * an UnsupportedOperationException is thrown + */ + @RequiresCouch + @RequiresCloudantLocal + public void testPermissionsException() { + assertThrows(UnsupportedOperationException.class, new Executable() { + @Override + public void execute() throws Throwable { + Map> userPerms = db.getPermissions(); + } + }); + } + + @Test + public void permissionsParsing() throws Exception { + CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder(mockWebServer) + .build(); + Database db = client.database("notarealdb", false); + + // Mock up a request of all permissions + mockWebServer.enqueue(MockWebServerResources.PERMISSIONS); // for GET _security + mockWebServer.enqueue(MockWebServerResources.JSON_OK); // for PUT _security + db.setPermissions("testUsername", EnumSet.allOf(Permissions.class)); + + // Mock up a failing request + String testError = "test error"; + String testReason = "test reason"; + mockWebServer.enqueue(MockWebServerResources.PERMISSIONS); // for GET _security + mockWebServer.enqueue(new MockResponse().setResponseCode(400).setBody("{\"reason\":\"" + + testReason + "\", \"error\":\"" + testError + "\"}")); + try { + db.setPermissions("testUsername", EnumSet.allOf(Permissions.class)); + } catch (CouchDbException e) { + assertEquals(testError, e.getError()); + assertEquals(testReason, e.getReason()); + } + } + + @Test + @RequiresCloudant + public void shards() { + List shards = db.getShards(); + assert (shards.size() > 0); + for (Shard s : shards) { + assertNotNull(s.getRange()); + assertNotNull(s.getNodes()); + assertNotNull(s.getNodes().hasNext()); + } + } + + @Test + @RequiresCloudant + public void shard() { + Shard s = db.getShard("snipe"); + assertNotNull(s); + assertNotNull(s.getRange()); + assertNotNull(s.getNodes()); + assert (s.getNodes().hasNext()); + } + + + @Test + @RequiresCloudant + public void QuorumTests() { + + db.save(new Animal("human"), 2); + Animal h = db.find(Animal.class, "human", new com.cloudant.client.api.model.Params() + .readQuorum(2)); + assertNotNull(h); + assertEquals("human", h.getId()); + + db.update(h.setClass("inhuman"), 2); + h = db.find(Animal.class, "human", new com.cloudant.client.api.model.Params().readQuorum + (2)); + assertEquals("inhuman", h.getclass()); + + db.post(new Animal("test"), 2); + h = db.find(Animal.class, "test", new com.cloudant.client.api.model.Params().readQuorum(3)); + assertEquals("test", h.getId()); + + + } + + //Test case for issue #31 + @Test + public void customGsonDeserializerTest() throws MalformedURLException { + GsonBuilder builder = new GsonBuilder(); + builder.setDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + CloudantClient account = CloudantClientHelper.getClientBuilder() + .gsonBuilder(builder) + .build(); + + Database db = account.database(dbResource.getDatabaseName(), false); + + Map h = new HashMap(); + h.put("_id", "serializertest"); + h.put("date", "2015-01-23T18:25:43.511Z"); + db.save(h); + + db.find(Foo.class, "serializertest"); // should not throw a JsonSyntaxException + + } +} 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 e2d29c786..88911bf38 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseURIHelperTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseURIHelperTest.java @@ -1,5 +1,6 @@ -/** +/* * Copyright (c) 2015 Cloudant, Inc. All rights reserved. + * Copyright © 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java index 42d457a16..39d451f58 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 IBM Corp. All rights reserved. + * Copyright © 2016, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java index 3c5147c98..b9e559fdb 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2011 lightcouch.org - * Copyright © 2015, 2016 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java index d4d817356..2ac2c14f8 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2011 lightcouch.org - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/Foo.java b/cloudant-client/src/test/java/com/cloudant/tests/Foo.java index 6666aff42..45210546c 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/Foo.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/Foo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HierarchicalUriComponentsTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HierarchicalUriComponentsTest.java index 29fb03d58..8445743ae 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HierarchicalUriComponentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HierarchicalUriComponentsTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017 IBM Corp. All rights reserved. + * Copyright © 2017, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java index b3eaa00b5..c3047f063 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2015, 2016 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java index ba93c3044..8621f2a43 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017 IBM Corp. All rights reserved. + * Copyright © 2017, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java b/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java index cd16960ec..6c7e7e848 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2016 IBM Corp. All rights reserved. + * Copyright © 2016, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ReplicationTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ReplicationTest.java index 2c3c517d4..3d02f333c 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ReplicationTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ReplicationTest.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2011 lightcouch.org - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java index d5e880438..a573b3e5b 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java @@ -1,128 +1,128 @@ -/* - * 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; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.cloudant.client.api.model.ReplicatorDocument; -import com.cloudant.client.api.model.Response; -import com.cloudant.test.main.RequiresDB; -import com.cloudant.tests.base.TestWithReplication; -import com.cloudant.tests.util.Utils; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@RequiresDB -public class ReplicatorTest extends TestWithReplication { - - private String repDocId; - - @BeforeEach - public void generateReplicatorDocId() { - repDocId = Utils.generateUUID(); - } - - @AfterEach - public void cleanUpReplicatorDoc() throws Exception { - Utils.removeReplicatorTestDoc(account, repDocId); - } - - @Test - public void replication() throws Exception { - Response response = account.replicator() - .replicatorDocId(repDocId) - .createTarget(true) - .source(db1URI) - .target(db2URI) - .save(); - - // find and remove replicator doc - ReplicatorDocument repDoc = Utils.waitForReplicatorToComplete(account, response.getId()); - assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator should reach completed state"); - } - - @Test - public void replication_filteredWithQueryParams() throws Exception { - Map queryParams = new HashMap(); - queryParams.put("somekey1", "value 1"); - - Response response = account.replicator() - .createTarget(true) - .replicatorDocId(repDocId) - .source(db1URI) - .target(db2URI) - .filter("example/example_filter") - .queryParams(queryParams) - .save(); - - // find and remove replicator doc - ReplicatorDocument repDoc = Utils.waitForReplicatorToComplete(account, response.getId()); - assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator should reach completed state"); - } - - @Test - public void replicatorDB() throws Exception { - - // trigger a replication - Response response = account.replicator() - .replicatorDocId(repDocId) - .source(db1URI) - .target(db2URI).continuous(true) - .createTarget(true) - .save(); - - // we need the replication to start before continuing - Utils.waitForReplicatorToStart(account, response.getId()); - - // find all replicator docs - List replicatorDocs = account.replicator() - .findAll(); - assertThat(replicatorDocs.size(), is(not(0))); - - } - - @Test - public void replication_conflict() throws Exception { - String docId = Utils.generateUUID(); - Foo foodb1 = new Foo(docId, "titleX"); - Foo foodb2 = new Foo(docId, "titleY"); - - //save Foo(X) in DB1 - db1.save(foodb1); - //save Foo(Y) in DB2 - db2.save(foodb2); - - //replicate with DB1 with DB2 - Response response = account.replicator().source(db1URI) - .target(db2URI).replicatorDocId(repDocId) - .save(); - - // we need the replication to finish before continuing - Utils.waitForReplicatorToComplete(account, response.getId()); - - //we replicated with a doc with the same ID but different content in each DB, we should get - //a conflict - assertConflictsNotZero(db2); - } -} +/* + * Copyright © 2015, 2018 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; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.cloudant.client.api.model.ReplicatorDocument; +import com.cloudant.client.api.model.Response; +import com.cloudant.test.main.RequiresDB; +import com.cloudant.tests.base.TestWithReplication; +import com.cloudant.tests.util.Utils; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RequiresDB +public class ReplicatorTest extends TestWithReplication { + + private String repDocId; + + @BeforeEach + public void generateReplicatorDocId() { + repDocId = Utils.generateUUID(); + } + + @AfterEach + public void cleanUpReplicatorDoc() throws Exception { + Utils.removeReplicatorTestDoc(account, repDocId); + } + + @Test + public void replication() throws Exception { + Response response = account.replicator() + .replicatorDocId(repDocId) + .createTarget(true) + .source(db1URI) + .target(db2URI) + .save(); + + // find and remove replicator doc + ReplicatorDocument repDoc = Utils.waitForReplicatorToComplete(account, response.getId()); + assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator should reach completed state"); + } + + @Test + public void replication_filteredWithQueryParams() throws Exception { + Map queryParams = new HashMap(); + queryParams.put("somekey1", "value 1"); + + Response response = account.replicator() + .createTarget(true) + .replicatorDocId(repDocId) + .source(db1URI) + .target(db2URI) + .filter("example/example_filter") + .queryParams(queryParams) + .save(); + + // find and remove replicator doc + ReplicatorDocument repDoc = Utils.waitForReplicatorToComplete(account, response.getId()); + assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator should reach completed state"); + } + + @Test + public void replicatorDB() throws Exception { + + // trigger a replication + Response response = account.replicator() + .replicatorDocId(repDocId) + .source(db1URI) + .target(db2URI).continuous(true) + .createTarget(true) + .save(); + + // we need the replication to start before continuing + Utils.waitForReplicatorToStart(account, response.getId()); + + // find all replicator docs + List replicatorDocs = account.replicator() + .findAll(); + assertThat(replicatorDocs.size(), is(not(0))); + + } + + @Test + public void replication_conflict() throws Exception { + String docId = Utils.generateUUID(); + Foo foodb1 = new Foo(docId, "titleX"); + Foo foodb2 = new Foo(docId, "titleY"); + + //save Foo(X) in DB1 + db1.save(foodb1); + //save Foo(Y) in DB2 + db2.save(foodb2); + + //replicate with DB1 with DB2 + Response response = account.replicator().source(db1URI) + .target(db2URI).replicatorDocId(repDocId) + .save(); + + // we need the replication to finish before continuing + Utils.waitForReplicatorToComplete(account, response.getId()); + + //we replicated with a doc with the same ID but different content in each DB, we should get + //a conflict + assertConflictsNotZero(db2); + } +} diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java index 7e4ceeeea..10fbe1e91 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2016 IBM Corp. All rights reserved. + * Copyright © 2016, 2018 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 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 ea1e9f961..db497b25e 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 c6e4c7200..18b39337b 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/SslAuthenticationTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/SslAuthenticationTest.java @@ -1,5 +1,5 @@ /* - * Copyright © 2015, 2016 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 ae920be27..add92b3c4 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/URIBaseTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/URIBaseTest.java @@ -1,3 +1,16 @@ +/* + * Copyright © 2018 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; import com.cloudant.client.api.ClientBuilder; 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 b306492ed..b37e7e6f8 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 9315fe82a..3e3ec4de3 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/UpdateHandlerTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/UpdateHandlerTest.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2011 lightcouch.org - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 2b3f75ff5..53fb0b42a 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ViewPaginationTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ViewPaginationTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 403cd3903..a9463aa24 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2011 lightcouch.org - * Copyright © 2015, 2016 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 index 7dc185517..b701fa502 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDb.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDb.java @@ -1,3 +1,16 @@ +/* + * Copyright © 2018 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.base; import com.cloudant.client.api.CloudantClient; diff --git a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java index b7b913c5d..773812d00 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java @@ -1,3 +1,16 @@ +/* + * Copyright © 2018 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.base; import com.cloudant.tests.extensions.DatabaseExtension; 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 index 090069608..e1564ae1c 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java @@ -1,3 +1,16 @@ +/* + * Copyright © 2018 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.base; import com.cloudant.tests.extensions.DatabaseExtension; diff --git a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java index 6db654003..f7f8c7940 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017 IBM Corp. All rights reserved. + * Copyright © 2017, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java index 74052ffa2..38361ba77 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 index 4c21f5fee..160c34548 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/AbstractClientExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/AbstractClientExtension.java @@ -1,3 +1,16 @@ +/* + * Copyright © 2018 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; diff --git a/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientExtension.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientExtension.java index e22cfd4c5..4083ac700 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 index e9884d45c..c1f680249 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientMockServerExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientMockServerExtension.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017 IBM Corp. All rights reserved. + * Copyright © 2017, 2018 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 diff --git a/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java index fb7aeda76..94cd3534d 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 index e67977df0..0ea96dfb6 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/MockWebServerExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/MockWebServerExtension.java @@ -1,3 +1,16 @@ +/* + * Copyright © 2018 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 org.junit.jupiter.api.extension.AfterEachCallback; 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 index 22388815e..feb051cb6 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/MultiExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/MultiExtension.java @@ -1,3 +1,16 @@ +/* + * Copyright © 2018 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 org.junit.jupiter.api.extension.AfterAllCallback; 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 9aaa420a8..0dc0d1101 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 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 92548279f..4ff8d1953 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 @@ -1,5 +1,5 @@ /* - * Copyright © 2016 IBM Corp. All rights reserved. + * Copyright © 2016, 2018 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 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 a85fccea2..ef5a26c50 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 IBM Corp. All rights reserved. + * Copyright © 2015, 2018 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 From 5d5abf592ae8c3b2adae39322ef5d16f6c6a0e99 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 27 Mar 2018 15:31:02 +0100 Subject: [PATCH 12/17] Remove comment --- .../java/com/cloudant/tests/SessionInterceptorExpiryTests.java | 2 -- 1 file changed, 2 deletions(-) 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 6f8f807c3..1ba009f87 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/SessionInterceptorExpiryTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/SessionInterceptorExpiryTests.java @@ -71,8 +71,6 @@ public Stream provideTestTemplateInvocationContex invocationContext(true,"/_session")); } - // 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() { From d67d4641ce3d00f5f0ac446554d79b385b44e905 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 27 Mar 2018 15:39:16 +0100 Subject: [PATCH 13/17] Format code and optimise imports --- .../api/query/IndexDeletionTests.java | 5 +- .../cloudant/api/query/IndexListTests.java | 9 +- .../cloudant/test/main/RequiresCloudant.java | 2 +- .../test/main/RequiresCloudantLocal.java | 2 +- .../test/main/RequiresCloudantService.java | 2 +- .../com/cloudant/test/main/RequiresCouch.java | 2 +- .../com/cloudant/test/main/RequiresDB.java | 2 +- .../com/cloudant/tests/AttachmentsTest.java | 3 +- .../tests/ChangeNotificationsTest.java | 8 +- .../com/cloudant/tests/ClientLoadTest.java | 3 +- .../tests/CloudFoundryServiceTest.java | 7 +- .../cloudant/tests/CloudantClientTests.java | 24 ++-- .../java/com/cloudant/tests/DatabaseTest.java | 2 +- .../cloudant/tests/DatabaseURIHelperTest.java | 24 ++-- .../cloudant/tests/DesignDocumentTest.java | 77 ++++++----- .../cloudant/tests/DesignDocumentsTest.java | 40 ++++-- .../java/com/cloudant/tests/HttpIamTest.java | 50 ++++--- .../com/cloudant/tests/HttpProxyTest.java | 30 ++-- .../java/com/cloudant/tests/HttpTest.java | 128 ++++++++++-------- .../java/com/cloudant/tests/IndexTests.java | 11 +- .../java/com/cloudant/tests/LoggingTest.java | 15 +- .../java/com/cloudant/tests/QueryTests.java | 70 ++++++---- .../com/cloudant/tests/ReplicatorTest.java | 6 +- .../java/com/cloudant/tests/ResponseTest.java | 6 +- .../java/com/cloudant/tests/SearchTests.java | 3 +- .../tests/SessionInterceptorExpiryTests.java | 41 ++++-- .../cloudant/tests/SslAuthenticationTest.java | 23 ++-- .../java/com/cloudant/tests/UnicodeTest.java | 2 +- .../cloudant/tests/ViewPaginationTests.java | 26 ++-- .../java/com/cloudant/tests/ViewsTest.java | 39 ++++-- .../tests/base/TestWithDbPerClass.java | 6 +- .../tests/base/TestWithDbPerTest.java | 6 +- .../tests/base/TestWithMockedServer.java | 6 +- .../tests/base/TestWithReplication.java | 12 +- .../CloudantClientMockServerExtension.java | 6 +- .../tests/extensions/DatabaseExtension.java | 8 +- .../cloudant/tests/util/CheckPagination.java | 12 +- .../util/HttpFactoryParameterizedTest.java | 3 +- .../tests/util/IamSystemPropertyMock.java | 2 +- .../java/com/cloudant/tests/util/Utils.java | 6 +- 40 files changed, 447 insertions(+), 282 deletions(-) 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 f385e9866..4fdb5fe4d 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 @@ -16,8 +16,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import com.cloudant.tests.util.MockWebServerResources; import com.cloudant.tests.base.TestWithMockedServer; +import com.cloudant.tests.util.MockWebServerResources; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -53,6 +53,7 @@ public void deleteTextIndex() throws Exception { private void assertDelete(String name, String ddoc, String type) throws Exception { RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS); - assertEquals("/" + dbResource.getDatabaseName() + "/_index/_design/" + ddoc + "/" + type + "/" + name, request.getPath(), "The request body should match the expected"); + 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/IndexListTests.java b/cloudant-client/src/test/java/com/cloudant/api/query/IndexListTests.java index 1095dd537..9502b062b 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 @@ -31,7 +31,6 @@ import okhttp3.mockwebserver.MockResponse; import java.io.BufferedInputStream; -import java.io.File; import java.io.FileInputStream; import java.util.Collections; import java.util.List; @@ -61,7 +60,7 @@ private static String fromFile(String resourceFileName) { return IOUtils.toString(new BufferedInputStream(new FileInputStream("" + "./src/test/resources/query-tests/" + resourceFileName + ".js")), "UTF-8"); } catch (Exception e) { - fail("Error reading test resource: " + e.getMessage()); + fail("Error reading test resource: " + e.getMessage()); } return null; } @@ -94,7 +93,8 @@ private void assertIndex(Index index, String name, String ddoc, String type, Str 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"); + assertEquals(selector, index.getPartialFilterSelector(), "The index should have the " + + "correct selector"); } private void assertJsonIndex(JsonIndex index, String name, String selector, Map a = indexes.get(i); assertIndex(a, "_all_docs", null, "special", null); assertEquals(1, a.getFields().size(), "There should be 1 field"); - assertEquals("_id", a.getFields().get(0).getName(), "There field should be called _id"); + 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 060f50957..36a24e962 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 @@ -24,7 +24,7 @@ /** * JUnit tag to label tests which require Cloudant Service or Cloudant Local */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Tag("RequiresCloudant") @Tag("RequiresDB") 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 7c0c6e150..910435d9b 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 @@ -24,7 +24,7 @@ /** * JUnit tag to label tests which require Cloudant local */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Tag("RequiresCloudantLocal") @Tag("RequiresCloudant") 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 d453f472a..52bb5985d 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 @@ -24,7 +24,7 @@ /** * JUnit tag to label tests which require the Cloudant service */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Tag("RequiresCloudantService") @Tag("RequiresCloudant") 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 59d1e6f95..c19a3fec6 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 @@ -24,7 +24,7 @@ /** * JUnit tag to label tests which require a running DB */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Tag("RequiresCouch") @Tag("RequiresDB") 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 267e50b03..7d45bb389 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 @@ -24,7 +24,7 @@ /** * JUnit tag to label tests which require a running DB */ -@Target({ ElementType.TYPE, ElementType.METHOD }) +@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Tag("RequiresDB") public @interface RequiresDB { 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 e6bd64ca8..637093e0b 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/AttachmentsTest.java @@ -241,7 +241,8 @@ public void attachmentStandaloneEmptyDocId() { 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"); + Response response = db.saveAttachment(bytesIn, "foo.txt", "text/plain", "", + "1-abcdef"); } }); } 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 426ad947e..60338d9dd 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ChangeNotificationsTest.java @@ -123,7 +123,8 @@ public void changesDescending() throws Exception { RecordedRequest request = mockWebServer.takeRequest(1, TimeUnit.SECONDS); assertNotNull(request, "There should be a changes request"); assertTrue(request.getPath() - .contains("descending=true"), "There should be a descending parameter on the request"); + .contains("descending=true"), "There should be a descending parameter on the " + + "request"); } /** @@ -142,7 +143,8 @@ public void changesCustomParameter() throws Exception { 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"); + .contains("myParam=paramValue"), "There should be a custom parameter on the " + + "request"); } /** @@ -176,7 +178,7 @@ public void changesFeedLastSeq() throws Exception { Changes c = db.changes().continuousChanges(); int nChanges = 0; // check against regression where hasNext() will hang - while(c.hasNext()) { + while (c.hasNext()) { nChanges++; c.next(); } 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 14244328e..b3a4de5b1 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ClientLoadTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ClientLoadTest.java @@ -40,7 +40,8 @@ public class ClientLoadTest { public static CloudantClientExtension clientResource = new CloudantClientExtension( CloudantClientHelper.getClientBuilder().maxConnections(MAX_CONNECTIONS)); @RegisterExtension - public static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest(clientResource); + public static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest + (clientResource); private static CloudantClient dbClient; private static Database db; 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 39045fdba..857c0ff6c 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/CloudFoundryServiceTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudFoundryServiceTest.java @@ -89,7 +89,8 @@ public void vcapValidServiceNameSpecified() { vcap.createNewService("test_bluemix_service_1", CloudantClientHelper.SERVER_URI_WITH_USER_INFO, CloudantClientHelper.COUCH_USERNAME, CloudantClientHelper.COUCH_PASSWORD); - ClientBuilder.bluemix(vcap.toJson(), serviceName, "test_bluemix_service_1").build().serverVersion(); + ClientBuilder.bluemix(vcap.toJson(), serviceName, "test_bluemix_service_1").build() + .serverVersion(); } @Test @@ -167,8 +168,8 @@ public void execute() throws Throwable { @Test public void vcapMultiService() { 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_1", "http://foo1.bar", "admin1", "pass1"); + vcap.createNewService("test_bluemix_service_2", "http://foo2.bar", "admin2", "pass2"); vcap.createNewService("test_bluemix_service_3", CloudantClientHelper.SERVER_URI_WITH_USER_INFO, CloudantClientHelper.COUCH_USERNAME, CloudantClientHelper.COUCH_PASSWORD); 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 c8c4b464a..0c959fe67 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/CloudantClientTests.java @@ -144,7 +144,8 @@ public void testUserAgentHeaderString() throws Exception { String userAgentHeader = new UserAgentInterceptor(UserAgentInterceptor.class .getClassLoader(), "META-INF/com.cloudant.client.properties").getUserAgent(); - assertTrue(userAgentHeader.matches(userAgentUnknownRegex), "The value of the User-Agent header: " + userAgentHeader + " should match the " + "format: " + userAgentFormat); + assertTrue(userAgentHeader.matches(userAgentUnknownRegex), "The value of the User-Agent " + + "header: " + userAgentHeader + " should match the " + "format: " + userAgentFormat); } @Test @@ -169,7 +170,8 @@ public InputStream getResourceAsStream(String name) { return super.getResourceAsStream(name); } }, "META-INF/com.cloudant.client.properties").getUserAgent(); - assertTrue(userAgentHeader.matches(userAgentRegex), "The value of the User-Agent header: " + userAgentHeader + " should match the " + "format: " + userAgentFormat); + assertTrue(userAgentHeader.matches(userAgentRegex), "The value of the User-Agent header: " + + "" + userAgentHeader + " should match the " + "format: " + userAgentFormat); } /** @@ -193,7 +195,9 @@ public void testUserAgentHeaderIsAddedToRequest() throws Exception { String userAgentHeader = server.takeRequest(10, TimeUnit.SECONDS) .getHeader("User-Agent"); 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); + assertTrue(userAgentHeader.matches(userAgentUnknownRegex), "The value of the User-Agent " + + "header " + userAgentHeader + " on the request" + " should match the format " + + userAgentFormat); } /** @@ -289,7 +293,7 @@ public void execute() throws Throwable { InetAddress loopback = InetAddress.getLoopbackAddress(); ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket (0, 1, - loopback); + loopback); int port = serverSocket.getLocalPort(); //block the single connection to our server @@ -461,7 +465,8 @@ private void credentialsCheck(ClientBuilder b, String encodedUser, String encode // name=YourUserName&password=YourPassword Matcher m = CREDENTIALS.matcher(body); 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(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"); @@ -517,7 +522,8 @@ public void testBasicAuthFromCredentials() throws Exception { client.getAllDbs(); // expected 'username:password' - assertEquals("Basic dXNlcm5hbWU6cGFzc3dvcmQ=", server.takeRequest().getHeader("Authorization")); + assertEquals("Basic dXNlcm5hbWU6cGFzc3dvcmQ=", server.takeRequest().getHeader + ("Authorization")); } @Test @@ -559,10 +565,10 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio @Test public void nullURLThrowsIAE() throws Exception { assertThrows(IllegalArgumentException.class, new Executable() { - @Override - public void execute() throws Throwable { + @Override + public void execute() throws Throwable { ClientBuilder.url(null); - } + } }); } } 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 8dbf89968..5b7b21481 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseTest.java @@ -184,7 +184,7 @@ public void customGsonDeserializerTest() throws MalformedURLException { GsonBuilder builder = new GsonBuilder(); builder.setDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - CloudantClient account = CloudantClientHelper.getClientBuilder() + CloudantClient account = CloudantClientHelper.getClientBuilder() .gsonBuilder(builder) .build(); 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 88911bf38..1f732557c 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DatabaseURIHelperTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DatabaseURIHelperTest.java @@ -48,8 +48,10 @@ public boolean supportsTestTemplate(ExtensionContext context) { } @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(invocationContext(""), invocationContext("/api/couch/account_2128459498a75498")); + public Stream provideTestTemplateInvocationContexts + (ExtensionContext context) { + return Stream.of(invocationContext(""), invocationContext + ("/api/couch/account_2128459498a75498")); } public static TestTemplateInvocationContext invocationContext(final String path) { @@ -65,9 +67,10 @@ public List getAdditionalExtensions() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: - return parameterContext.getParameter().getType().equals(String.class); + return parameterContext.getParameter().getType().equals + (String.class); } return false; } @@ -75,7 +78,7 @@ public boolean supportsParameter(ParameterContext parameterContext, @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: return path; } @@ -109,7 +112,7 @@ public void _localDocumentURI(String path) throws Exception { DatabaseURIHelper helper = helper(path + "/db_name"); URI localDoc = helper.documentUri("_local/mylocaldoc"); - Assertions.assertEquals(expected,localDoc.toString()); + Assertions.assertEquals(expected, localDoc.toString()); } @TestTemplate @@ -209,7 +212,8 @@ public void buildDocumentUri_options_optionsEncoded(String path) throws Exceptio @TestTemplate public void buildDocumentUri_options_encodeSeparators(String path) throws Exception { - URI expected = new URI(uriBase + "/test/path1%2Fpath2?d%26etail%3D=%26%3D%3Dds%26&revs=%5B1-2%5D"); + URI expected = new URI(uriBase + + "/test/path1%2Fpath2?d%26etail%3D=%26%3D%3Dds%26&revs=%5B1-2%5D"); TreeMap options = new TreeMap(); options.put("revs", "[1-2]"); @@ -234,7 +238,8 @@ public void buildDocumentUri_options_hasPlus(String path) throws Exception { // correctly escaped in the document part of the url @TestTemplate public void buildVeryEscapedUri(String path) throws Exception { - URI expected = new URI(uriBase + "/SDF@%23%25$%23)KLDfdffdg%C3%A9/%2FSF@%23%25$%23)DFGKLDfdffdg%C3%A9%2Fpath2?detail=/SDF@%23%25$%23)%C3%A9&revs=%5B1-2%5D"); + URI expected = new URI(uriBase + "/SDF@%23%25$%23)KLDfdffdg%C3%A9/%2FSF@%23%25$%23)" + + "DFGKLDfdffdg%C3%A9%2Fpath2?detail=/SDF@%23%25$%23)%C3%A9&revs=%5B1-2%5D"); Map options = new TreeMap(); options.put("revs", "[1-2]"); @@ -266,7 +271,8 @@ public void buildQuery_joinTwoQueries(String path) throws Exception { String query = "boolean=true"; - URI expectedQuery = new URI(uriBase + "?detail=/SDF@%23%25$%23)&revs=%5B1-2%5D&boolean=true"); + URI expectedQuery = new URI(uriBase + "?detail=/SDF@%23%25$%23)" + + "&revs=%5B1-2%5D&boolean=true"); URI actualQuery = helper(path).query(mapOptions).query(query).build(); Assertions.assertEquals(expectedQuery.toASCIIString(), actualQuery.toASCIIString()); } diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java index 39d451f58..f76414a5f 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentTest.java @@ -55,7 +55,8 @@ public boolean supportsTestTemplate(ExtensionContext context) { } @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + public Stream provideTestTemplateInvocationContexts + (ExtensionContext context) { return StreamSupport.stream(data().spliterator(), false); } @@ -72,9 +73,10 @@ public List getAdditionalExtensions() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: - return parameterContext.getParameter().getType().equals(Field.class); + return parameterContext.getParameter().getType().equals(Field + .class); } return false; } @@ -82,7 +84,7 @@ public boolean supportsParameter(ParameterContext parameterContext, @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: return field; } @@ -95,7 +97,8 @@ public Object resolveParameter(ParameterContext parameterContext, } public static Iterable data() { - List contexts = new ArrayList(); + List contexts = new + ArrayList(); for (Field f : EnumSet.allOf(Field.class)) { contexts.add(ParameterProvider.invocationContext(f)); } @@ -218,42 +221,42 @@ private static DesignDocument getDesignDocumentWithDifferent(Field f) { return designDocument; } - @Test - public void testDesignDocEqualsForAllFields() { - Assert.assertEquals(getDesignDocument(), getDesignDocument()); - } + @Test + public void testDesignDocEqualsForAllFields() { + Assert.assertEquals(getDesignDocument(), getDesignDocument()); + } - /** - * Tests the design docs are equal for each field in turn. - */ - @TestTemplate - public void testDesignDocEqualsForEachField(Field field) { - Assert.assertEquals(getDesignDocumentWithFields(EnumSet.of(field)), - getDesignDocumentWithFields(EnumSet.of(field))); - } + /** + * Tests the design docs are equal for each field in turn. + */ + @TestTemplate + public void testDesignDocEqualsForEachField(Field field) { + Assert.assertEquals(getDesignDocumentWithFields(EnumSet.of(field)), + getDesignDocumentWithFields(EnumSet.of(field))); + } - /** - * Tests the design docs are not equal when each field is empty in one of the compared docs. - * - * @throws Exception - */ - @TestTemplate - public void testDesignDocNotEqualEmpty(Field field) throws Exception { - Assert.assertNotEquals(getDesignDocument(), getDesignDocumentWithFields(EnumSet - .complementOf(EnumSet.of(field)))); - } + /** + * Tests the design docs are not equal when each field is empty in one of the compared docs. + * + * @throws Exception + */ + @TestTemplate + public void testDesignDocNotEqualEmpty(Field field) throws Exception { + Assert.assertNotEquals(getDesignDocument(), getDesignDocumentWithFields(EnumSet + .complementOf(EnumSet.of(field)))); + } - /** - * Tests the design docs are not equal when each field is different in one of the - * compared docs. - * - * @throws Exception - */ - @TestTemplate - public void testDesignDocNotEqualDifferent(Field field) throws Exception { - Assert.assertNotEquals(getDesignDocument(), getDesignDocumentWithDifferent(field)); - } + /** + * Tests the design docs are not equal when each field is different in one of the + * compared docs. + * + * @throws Exception + */ + @TestTemplate + public void testDesignDocNotEqualDifferent(Field field) throws Exception { + Assert.assertNotEquals(getDesignDocument(), getDesignDocumentWithDifferent(field)); + } private static void indexes(DesignDocument designDocument) { diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java index b9e559fdb..441bab87d 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DesignDocumentsTest.java @@ -88,7 +88,8 @@ public static void beforeAll() throws Exception { * @throws Exception */ private static DesignDocument fileToDesignDocument(String name) throws Exception { - File testDesignDocFile = new File(String.format("%s/%s_design_doc.js", rootDesignDir, name)); + File testDesignDocFile = new File(String.format("%s/%s_design_doc.js", rootDesignDir, + name)); return designManager.fromFile(testDesignDocFile); } @@ -160,7 +161,8 @@ public void designDocGetNoPrefix() throws Exception { designManager.put(designDocExample); // Retrieve it without a prefix - assertNotNull(designManager.get("example"), "The design doc should be retrieved without a _design prefix"); + assertNotNull(designManager.get("example"), "The design doc should be retrieved without a" + + " _design prefix"); } /** @@ -175,7 +177,8 @@ public void designDocGetNoPrefixWithRevision() throws Exception { Response r = designManager.put(designDocExample); // Retrieve it without a prefix - assertNotNull(designManager.get("example", r.getRev()), "The design doc should be retrieved without a _design prefix"); + assertNotNull(designManager.get("example", r.getRev()), "The design doc should be " + + "retrieved without a _design prefix"); } /** @@ -240,11 +243,13 @@ public void designDocPutNoPrefix() throws Exception { Utils.assertOKResponse(designManager.put(designDocExampleNoPrefix)); // Retrieve it with a prefix - assertNotNull(designManager.get("_design/example"), "The design doc should be retrievable with a _design prefix"); + assertNotNull(designManager.get("_design/example"), "The design doc should be retrievable" + + " with a _design prefix"); } /** * Test that a design document with an index can be deleted. + * * @throws Exception */ @Test @@ -325,7 +330,8 @@ public int compare(DesignDocument doc1, DesignDocument doc2) { doc.setRevision(designManager.put(doc).getRev()); } - assertEquals(designDocs, designManager.list(), "The retrieved list of design documents should match the expected list"); + assertEquals(designDocs, designManager.list(), "The retrieved list of design documents " + + "should match the expected list"); } /** @@ -346,7 +352,8 @@ public void deserializeJavascriptView() throws Exception { DesignDocument queryDDoc = fileToDesignDocument("example"); Map views = queryDDoc.getViews(); for (DesignDocument.MapReduce mrView : views.values()) { - assertFalse(mrView.getMap().startsWith("\""), "The map function should not start with \""); + assertFalse(mrView.getMap().startsWith("\""), "The map function should not start with" + + " \""); assertFalse(mrView.getMap().endsWith("\""), "The map function should not end with \""); } } @@ -383,7 +390,8 @@ public void serializeJavascriptView() throws Exception { assertNotNull(retrievedViews, "There should be views defined on the design doc"); DesignDocument.MapReduce mrView = retrievedViews.get("testView"); assertNotNull(mrView, "There should be a testView in the retrieved design doc"); - assertEquals(mapFunction, mrView.getMap(), "The map function string should be the expected string"); + assertEquals(mapFunction, mrView.getMap(), "The map function string should be the " + + "expected string"); } /** @@ -401,9 +409,12 @@ public void serializeQueryDesignDoc() throws Exception { Map views = queryDDoc.getViews(); assertEquals(1, views.size(), "There should be one view"); for (DesignDocument.MapReduce mrView : views.values()) { - assertTrue(mrView.getMap().startsWith("{"), "The map function should be a javascript function in a JSON form, " + "so start with {"); - assertTrue(mrView.getMap().endsWith("}"), "The map function should be a javascript function in a JSON form, " + "so end with }"); - assertEquals("{\"fields\":{\"Person_dob\":\"asc\"}}", mrView.getMap(), "The map function string should be an object form"); + assertTrue(mrView.getMap().startsWith("{"), "The map function should be a javascript " + + "function in a JSON form, " + "so start with {"); + assertTrue(mrView.getMap().endsWith("}"), "The map function should be a javascript " + + "function in a JSON form, " + "so end with }"); + assertEquals("{\"fields\":{\"Person_dob\":\"asc\"}}", mrView.getMap(), "The map " + + "function string should be an object form"); } } @@ -425,9 +436,12 @@ public void deserializeQueryDesignDoc() throws Exception { Map views = queryDDoc.getViews(); assertEquals(1, views.size(), "There should be one view"); for (DesignDocument.MapReduce mrView : views.values()) { - assertTrue(mrView.getMap().startsWith("{"), "The map function should be a javascript function in a JSON form, " + "so start with {"); - assertTrue(mrView.getMap().endsWith("}"), "The map function should be a javascript function in a JSON form, " + "so end with }"); - assertEquals("{\"fields\":{\"Person_dob\":\"asc\"}}", mrView.getMap(), "The map function string should be an object form"); + assertTrue(mrView.getMap().startsWith("{"), "The map function should be a javascript " + + "function in a JSON form, " + "so start with {"); + assertTrue(mrView.getMap().endsWith("}"), "The map function should be a javascript " + + "function in a JSON form, " + "so end with }"); + assertEquals("{\"fields\":{\"Person_dob\":\"asc\"}}", mrView.getMap(), "The map " + + "function string should be an object form"); } } } diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HttpIamTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HttpIamTest.java index 3acbb7d95..c7b267129 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HttpIamTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HttpIamTest.java @@ -32,8 +32,8 @@ import com.cloudant.client.api.CloudantClient; import com.cloudant.client.org.lightcouch.CouchDbException; import com.cloudant.http.Http; -import com.cloudant.tests.util.IamSystemPropertyMock; import com.cloudant.tests.extensions.MockWebServerExtension; +import com.cloudant.tests.util.IamSystemPropertyMock; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -92,6 +92,7 @@ public void setIAMMockEndpoint() { * - GET a resource on the cloudant server * - Cookie jar empty, so get IAM token followed by session cookie * - GET now proceeds as normal, expected cookie value is sent in header + * * @throws Exception */ @Test @@ -135,15 +136,17 @@ public void iamTokenAndCookieSuccessful() throws Exception { // assert that there was 1 call RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 1); assertEquals(iamTokenEndpoint, - recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); + recordedIamRequests[0].getPath(), "The request should have been for " + + "/identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), - containsString("apikey="+iamApiKey)); + containsString("apikey=" + iamApiKey)); } /** * Assert that the IAM API key is preferred to username/password if both are supplied. * As above test but with different builder arguments. + * * @throws Exception */ @Test @@ -189,10 +192,11 @@ public void iamApiKeyPreferredToUsernamePassword() throws Exception { // assert that there was 1 call RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 1); assertEquals(iamTokenEndpoint, - recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); + recordedIamRequests[0].getPath(), "The request should have been for " + + "/identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), - containsString("apikey="+iamApiKey)); + containsString("apikey=" + iamApiKey)); } /** @@ -202,7 +206,8 @@ public void iamApiKeyPreferredToUsernamePassword() throws Exception { * - GET now proceeds as normal, expected cookie value is sent in header * - second GET on cloudant server, re-using session cookie * - third GET on cloudant server, cookie expired, get IAM token and session cookie and replay - * request + * request + * * @throws Exception */ @Test @@ -215,7 +220,8 @@ public void iamTokenAndCookieWithExpirySuccessful() throws Exception { mockWebServer.enqueue(new MockResponse().setResponseCode(200) .setBody(hello)); // cookie expired - mockWebServer.enqueue(new MockResponse().setResponseCode(401).setBody("{\"error\":\"credentials_expired\"}")); + mockWebServer.enqueue(new MockResponse().setResponseCode(401).setBody + ("{\"error\":\"credentials_expired\"}")); // response with new cookie mockWebServer.enqueue(OK_IAM_COOKIE_2); mockWebServer.enqueue(new MockResponse().setResponseCode(200) @@ -282,13 +288,15 @@ public void iamTokenAndCookieWithExpirySuccessful() throws Exception { RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 2); // first time, automatically fetch because cookie jar is empty assertEquals(iamTokenEndpoint, - recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); + recordedIamRequests[0].getPath(), "The request should have been for " + + "/identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), - containsString("apikey="+iamApiKey)); + containsString("apikey=" + iamApiKey)); // second time, refresh because the cloudant session cookie has expired assertEquals(iamTokenEndpoint, - recordedIamRequests[1].getPath(), "The request should have been for /identity/token"); + recordedIamRequests[1].getPath(), "The request should have been for " + + "/identity/token"); } /** @@ -298,7 +306,8 @@ public void iamTokenAndCookieWithExpirySuccessful() throws Exception { * - GET now proceeds as normal, expected cookie value is sent in header * - second GET on cloudant server, re-using session cookie * - third GET on cloudant server, cookie expired, subsequent IAM token fails, no more requests - * are made + * are made + * * @throws Exception */ @Test @@ -368,13 +377,15 @@ public void iamRenewalFailureOnIamToken() throws Exception { RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 2); // first time, automatically fetch because cookie jar is empty assertEquals(iamTokenEndpoint, - recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); + recordedIamRequests[0].getPath(), "The request should have been for " + + "/identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), - containsString("apikey="+iamApiKey)); + containsString("apikey=" + iamApiKey)); // second time, refresh (but gets 500) because the cloudant session cookie has expired assertEquals(iamTokenEndpoint, - recordedIamRequests[1].getPath(), "The request should have been for /identity/token"); + recordedIamRequests[1].getPath(), "The request should have been for " + + "/identity/token"); } /** @@ -384,7 +395,8 @@ public void iamRenewalFailureOnIamToken() throws Exception { * - GET now proceeds as normal, expected cookie value is sent in header * - second GET on cloudant server, re-using session cookie * - third GET on cloudant server, cookie expired, get IAM token, subsequent session cookie - * request fails, no more requests are made + * request fails, no more requests are made + * * @throws Exception */ @Test @@ -461,13 +473,15 @@ public void iamRenewalFailureOnSessionCookie() throws Exception { RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 2); // first time, automatically fetch because cookie jar is empty assertEquals(iamTokenEndpoint, - recordedIamRequests[0].getPath(), "The request should have been for /identity/token"); + recordedIamRequests[0].getPath(), "The request should have been for " + + "/identity/token"); assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), - containsString("apikey="+iamApiKey)); + containsString("apikey=" + iamApiKey)); // second time, refresh because the cloudant session cookie has expired assertEquals(iamTokenEndpoint, - recordedIamRequests[1].getPath(), "The request should have been for /identity/token"); + recordedIamRequests[1].getPath(), "The request should have been for " + + "/identity/token"); } diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java index c3047f063..f1330e8a0 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HttpProxyTest.java @@ -20,8 +20,8 @@ import com.cloudant.client.api.ClientBuilder; import com.cloudant.client.api.CloudantClient; import com.cloudant.http.Http; -import com.cloudant.tests.util.HttpFactoryParameterizedTest; import com.cloudant.tests.extensions.MockWebServerExtension; +import com.cloudant.tests.util.HttpFactoryParameterizedTest; import com.cloudant.tests.util.MockWebServerResources; import org.junit.jupiter.api.AfterEach; @@ -67,7 +67,8 @@ public boolean supportsTestTemplate(ExtensionContext context) { } @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + public Stream provideTestTemplateInvocationContexts + (ExtensionContext context) { // AFAICT there is no way to instruct HttpURLConnection to connect via SSL to a // proxy server - so for now we just test an unencrypted proxy. @@ -81,7 +82,8 @@ public Stream provideTestTemplateInvocationContex invocationContext(true, false, true, true), invocationContext(true, false, true, false), invocationContext(true, false, false, true), - // see also https://github.com/cloudant/java-cloudant/issues/423 - these tests current fail regardless of ordering + // see also https://github.com/cloudant/java-cloudant/issues/423 - these + // tests current fail regardless of ordering //invocationContext(true, false, false, false), //invocationContext(false, false, true, true), invocationContext(false, false, true, false), @@ -96,7 +98,8 @@ public static TestTemplateInvocationContext invocationContext(final boolean okUs return new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { - return String.format("okhttp: %s; secure proxy: %s; https server %s: proxy auth: %s", + return String.format("okhttp: %s; secure proxy: %s; https server %s: proxy " + + "auth: %s", okUsable, useSecureProxy, useHttpsServer, useProxyAuth); } @@ -106,15 +109,19 @@ public List getAdditionalExtensions() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: - return parameterContext.getParameter().getType().equals(boolean.class); + return parameterContext.getParameter().getType().equals + (boolean.class); case 1: - return parameterContext.getParameter().getType().equals(boolean.class); + return parameterContext.getParameter().getType().equals + (boolean.class); case 2: - return parameterContext.getParameter().getType().equals(boolean.class); + return parameterContext.getParameter().getType().equals + (boolean.class); case 3: - return parameterContext.getParameter().getType().equals(boolean.class); + return parameterContext.getParameter().getType().equals + (boolean.class); } return false; } @@ -122,7 +129,7 @@ public boolean supportsParameter(ParameterContext parameterContext, @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: return okUsable; case 1: @@ -255,7 +262,8 @@ public void setAuthenticatorIfNeeded(final boolean okUsable, @Override protected PasswordAuthentication getPasswordAuthentication() { if (getRequestorType() == RequestorType.PROXY) { - return new PasswordAuthentication(mockProxyUser, mockProxyPass.toCharArray()); + return new PasswordAuthentication(mockProxyUser, mockProxyPass + .toCharArray()); } else { return null; } diff --git a/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java b/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java index 8621f2a43..7e85e1f99 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/HttpTest.java @@ -41,10 +41,10 @@ import com.cloudant.test.main.RequiresCloudant; import com.cloudant.tests.extensions.CloudantClientExtension; import com.cloudant.tests.extensions.DatabaseExtension; -import com.cloudant.tests.util.HttpFactoryParameterizedTest; import com.cloudant.tests.extensions.MockWebServerExtension; -import com.cloudant.tests.util.MockWebServerResources; import com.cloudant.tests.extensions.MultiExtension; +import com.cloudant.tests.util.HttpFactoryParameterizedTest; +import com.cloudant.tests.util.MockWebServerResources; import com.cloudant.tests.util.TestTimer; import com.cloudant.tests.util.Utils; import com.google.gson.Gson; @@ -99,7 +99,8 @@ public boolean supportsTestTemplate(ExtensionContext context) { } @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + public Stream provideTestTemplateInvocationContexts + (ExtensionContext context) { return Stream.of(invocationContext(false), invocationContext(true)); } @@ -117,9 +118,10 @@ public List getAdditionalExtensions() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: - return parameterContext.getParameter().getType().equals(boolean.class); + return parameterContext.getParameter().getType().equals + (boolean.class); } return false; } @@ -127,7 +129,7 @@ public boolean supportsParameter(ParameterContext parameterContext, @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: return okUsable; } @@ -142,11 +144,13 @@ public Object resolveParameter(ParameterContext parameterContext, private String data = "{\"hello\":\"world\"}"; public static CloudantClientExtension clientResource = new CloudantClientExtension(); - public static DatabaseExtension.PerClass dbResource = new DatabaseExtension.PerClass(clientResource); + public static DatabaseExtension.PerClass dbResource = new DatabaseExtension.PerClass + (clientResource); public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); @RegisterExtension - public static MultiExtension extensions = new MultiExtension(clientResource, dbResource, mockWebServerExt); + public static MultiExtension extensions = new MultiExtension(clientResource, dbResource, + mockWebServerExt); public MockWebServer mockWebServer; @@ -173,7 +177,8 @@ public void testWriteToServerOk() throws Exception { String responseStr = response.responseAsString(); String okPattern = ".*\"ok\"\\s*:\\s*true.*"; assertTrue(Pattern.compile(okPattern, - Pattern.DOTALL).matcher(responseStr).matches(), "There should be an ok response: " + responseStr); + Pattern.DOTALL).matcher(responseStr).matches(), "There should be an ok response: " + + "" + responseStr); // stream was read to end assertEquals(0, bis.available()); @@ -335,13 +340,13 @@ public void cookieInterceptorURLEncoding() throws Exception { RecordedRequest r = MockWebServerResources.takeRequestWithTimeout(mockWebServer); String sessionRequestContent = r.getBody().readString(Charset.forName("UTF-8")); -assertNotNull(sessionRequestContent, "The _session request should have non-null content"); + assertNotNull(sessionRequestContent, "The _session request should have non-null content"); //expecting name=...&password=... String[] parts = Utils.splitAndAssert(sessionRequestContent, "&", 1); String username = URLDecoder.decode(Utils.splitAndAssert(parts[0], "=", 1)[1], "UTF-8"); -assertEquals(mockUser, username, "The username URL decoded username should match"); + assertEquals(mockUser, username, "The username URL decoded username should match"); String password = URLDecoder.decode(Utils.splitAndAssert(parts[1], "=", 1)[1], "UTF-8"); -assertEquals(mockPass, password, "The username URL decoded password should match"); + assertEquals(mockPass, password, "The username URL decoded password should match"); } /** @@ -378,12 +383,15 @@ public void cookieRenewal() throws Exception { assertEquals(2, mockWebServer .getRequestCount(), "The server should have received 2 requests"); -assertEquals("/_session", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The request should have been for /_session"); -assertEquals("/", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The request should have been for /"); + assertEquals("/_session", MockWebServerResources.takeRequestWithTimeout(mockWebServer) + .getPath(), "The request should have been for /_session"); + assertEquals("/", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), + "The request should have been for /"); String secondResponse = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); assertTrue( - secondResponse.isEmpty(), "There should be no response body on the mock response" + secondResponse); + secondResponse.isEmpty(), "There should be no response body on the mock response" + + secondResponse); // also assert that there were 3 calls assertEquals(3, mockWebServer @@ -391,7 +399,7 @@ public void cookieRenewal() throws Exception { // this is the request that should have the new cookie. RecordedRequest request = MockWebServerResources.takeRequestWithTimeout(mockWebServer); -assertEquals("/", request.getPath(), "The request should have been for path /"); + assertEquals("/", request.getPath(), "The request should have been for path /"); String headerValue = request.getHeader("Cookie"); // The cookie may or may not have the session id quoted, so check both assertThat("The Cookie header should contain the expected session value", headerValue, @@ -507,7 +515,8 @@ private void basic403Test(String error, String reason, int expectedRequests) thr // also assert that there were the correct number of calls assertEquals( expectedRequests, mockWebServer - .getRequestCount(), "The server should receive the expected number of requests"); + .getRequestCount(), "The server should receive the expected number of " + + "requests"); } @TestTemplate @@ -621,7 +630,9 @@ private void testInputStreamRetry(HttpConnection request, byte[] expectedContent context.replayRequest = true; // Close the error stream InputStream errors = context.connection.getConnection().getErrorStream(); - if (errors != null) IOUtils.closeQuietly(errors); + if (errors != null) { + IOUtils.closeQuietly(errors); + } } } catch (IOException e) { e.printStackTrace(); @@ -644,8 +655,9 @@ private void testInputStreamRetry(HttpConnection request, byte[] expectedContent ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream((int) rr .getBodySize()); rr.getBody().copyTo(byteArrayOutputStream); - assertArrayEquals( expectedContent, - byteArrayOutputStream.toByteArray(), "The body bytes should have matched after a retry"); + assertArrayEquals(expectedContent, + byteArrayOutputStream.toByteArray(), "The body bytes should have matched after a " + + "retry"); } @TestTemplate @@ -940,12 +952,14 @@ public void httpsProxyIllegalArgumentException() throws Exception { @Override public void execute() throws Throwable { - // Get a client pointing to an https proxy - CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder(mockWebServer) - .proxyURL(new URL("https://192.0.2.0")).build(); + // Get a client pointing to an https proxy + CloudantClient client = CloudantClientHelper.newMockWebServerClientBuilder + (mockWebServer) + .proxyURL(new URL("https://192.0.2.0")).build(); - String response = client.executeRequest(Http.GET(client.getBaseUri())).responseAsString(); - fail("There should be an IllegalStateException for an https proxy."); + String response = client.executeRequest(Http.GET(client.getBaseUri())) + .responseAsString(); + fail("There should be an IllegalStateException for an https proxy."); } }); } @@ -971,34 +985,37 @@ public void cookieAppliedToDifferentURL() throws Exception { URI baseURI = c.getBaseUri(); URL first = new URL(baseURI.getScheme(), baseURI.getHost(), baseURI.getPort(), "/testdb"); String response = c.executeRequest(Http.GET(first)).responseAsString(); -assertEquals("first", response, "The correct response body should be present"); + assertEquals("first", response, "The correct response body should be present"); // There should be a request for a cookie followed by a the real request assertEquals(2, mockWebServer.getRequestCount(), "There should be 2 requests"); -assertEquals("/_session", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The first request should have been for a cookie"); + assertEquals("/_session", MockWebServerResources.takeRequestWithTimeout(mockWebServer) + .getPath(), "The first request should have been for a cookie"); RecordedRequest request = MockWebServerResources.takeRequestWithTimeout(mockWebServer); assertEquals("/testdb", request.getPath(), "The second request should have been for /testdb"); -assertNotNull(request.getHeader("Cookie"), "There should be a cookie on the request"); + assertNotNull(request.getHeader("Cookie"), "There should be a cookie on the request"); // Now make a request to another URL URL second = new URL(baseURI.getScheme(), baseURI.getHost(), baseURI.getPort(), "/_all_dbs"); response = c.executeRequest(Http.GET(second)).responseAsString(); -assertEquals("second", response, "The correct response body should be present"); + assertEquals("second", response, "The correct response body should be present"); // There should now be an additional request assertEquals(3, mockWebServer.getRequestCount(), "There should be 3 requests"); request = MockWebServerResources.takeRequestWithTimeout(mockWebServer); -assertEquals("/_all_dbs", request.getPath(), "The second request should have been for /_all_dbs"); + assertEquals("/_all_dbs", request.getPath(), "The second request should have been for " + + "/_all_dbs"); String cookieHeader = request.getHeader("Cookie"); -assertNotNull(cookieHeader, "There should be a cookie on the request"); + assertNotNull(cookieHeader, "There should be a cookie on the request"); assertTrue( - request.getHeader("Cookie").contains(EXPECTED_OK_COOKIE), "The cookie header " + cookieHeader + " should contain the expected value."); + request.getHeader("Cookie").contains(EXPECTED_OK_COOKIE), "The cookie header " + + cookieHeader + " should contain the expected value."); } /** @@ -1035,18 +1052,21 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio // If the cookie interceptor keeps trying then there will be more _session requests. assertEquals(2, mockWebServer.getRequestCount(), "There should be 2 requests"); - assertEquals( "/_session", - MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The first request should have been for a cookie"); - assertEquals( "/", - MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The second request should have been for /"); + assertEquals("/_session", + MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The " + + "first request should have been for a cookie"); + assertEquals("/", + MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The " + + "second request should have been for /"); response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); -assertEquals("TEST", response, "The expected response body should be received"); + assertEquals("TEST", response, "The expected response body should be received"); // Make another request, the cookie interceptor should not try again so there should only be // one more request. assertEquals(3, mockWebServer.getRequestCount(), "There should be 3 requests"); -assertEquals("/", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), "The third request should have been for /"); + assertEquals("/", MockWebServerResources.takeRequestWithTimeout(mockWebServer).getPath(), + "The third request should have been for /"); } /** @@ -1062,17 +1082,17 @@ public void noErrorStream403() throws Exception { @Override public void execute() throws Throwable { - // Respond with a cookie init to the first request to _session - mockWebServer.enqueue(MockWebServerResources.OK_COOKIE); - // Respond to the executeRequest GET of / with a 403 with no body - mockWebServer.enqueue(new MockResponse().setResponseCode(403)); - CloudantClient c = CloudantClientHelper.newMockWebServerClientBuilder(mockWebServer) - .username("a") - .password("b") - .build(); - - String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); - fail("There should be an exception, but received response " + response); + // Respond with a cookie init to the first request to _session + mockWebServer.enqueue(MockWebServerResources.OK_COOKIE); + // Respond to the executeRequest GET of / with a 403 with no body + mockWebServer.enqueue(new MockResponse().setResponseCode(403)); + CloudantClient c = CloudantClientHelper.newMockWebServerClientBuilder(mockWebServer) + .username("a") + .password("b") + .build(); + + String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); + fail("There should be an exception, but received response " + response); } }); } @@ -1101,7 +1121,7 @@ public void noErrorStream401() throws Exception { .build(); String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString(); -assertEquals("TEST", response, "The expected response body should be received"); + assertEquals("TEST", response, "The expected response body should be received"); } /** @@ -1133,7 +1153,8 @@ public class CloudantHttpIsolationClassLoader extends URLClassLoader { public CloudantHttpIsolationClassLoader(URL[] urls) { // If we are testing okhttp then allow the parent classloader, otherwise use null // to isolate okhttp classes from the test load - super(urls, isOkUsable ? CloudantHttpIsolationClassLoader.class.getClassLoader() : null); + super(urls, isOkUsable ? CloudantHttpIsolationClassLoader.class.getClassLoader() : + null); } @Override @@ -1145,9 +1166,10 @@ protected Class findClass(String name) throws ClassNotFoundException { // helper - assert that _n_ requests were made on the mock server and return them in an array public static RecordedRequest[] takeN(MockWebServer server, int n) throws Exception { assertEquals(n, - server.getRequestCount(), String.format(Locale.ENGLISH, "The server should have %d received requests", n)); + server.getRequestCount(), String.format(Locale.ENGLISH, "The server should have " + + "%d received requests", n)); RecordedRequest[] recordedRequests = new RecordedRequest[n]; - for (int i=0; iIssue 137 */ @Test - public void testDeprecatedApiNotNullIndexMovieNameAndYearWithCompleteJsonObjectStringSelector() { + public void testDeprecatedApiNotNullIndexMovieNameAndYearWithCompleteJsonObjectStringSelector + () { List movies = db.findByIndex("{\"selector\": { \"Movie_year\": {\"$gt\": 1960}, " + "\"Person_name\": \"Alec Guinness\" } }", Movie.class, @@ -308,8 +309,10 @@ public void useIndexDesignDocAndIndexNameJsonTypeIsArray() throws Exception { assertTrue(useIndex.isJsonArray(), "The use_index property should be a JsonArray"); JsonArray useIndexArray = useIndex.getAsJsonArray(); assertEquals(2, useIndexArray.size(), "The use_index array should have two elements"); - assertEquals("Movie_year", useIndexArray.get(0).getAsString(), "The use_index design document should be Movie_year"); - assertEquals("Person_name", useIndexArray.get(1).getAsString(), "The use_index index name should be Person_name"); + assertEquals("Movie_year", useIndexArray.get(0).getAsString(), "The use_index design " + + "document should be Movie_year"); + assertEquals("Person_name", useIndexArray.get(1).getAsString(), "The use_index index name" + + " should be Person_name"); } /** diff --git a/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java b/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java index 6c7e7e848..ac3c33918 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/LoggingTest.java @@ -121,10 +121,12 @@ public void urlRegexLogging() throws Exception { // Make a second request to a different URL and check that nothing else was logged client.executeRequest(Http.GET(client.getBaseUri())).responseAsString(); - assertEquals(logsize, handler.logEntries.size(), "There should have been no more log entries"); + assertEquals(logsize, handler.logEntries.size(), "There should have been no more log " + + "entries"); } private String methodFilterPropName = "com.cloudant.http.filter.method"; + @Test public void httpMethodFilterLogging() throws Exception { setAndAssertLogProperty(methodFilterPropName, "GET"); @@ -145,7 +147,8 @@ public void httpMethodFilterLogging() throws Exception { // Make a PUT request to a different URL and check that nothing else was logged client.executeRequest(Http.PUT(client.getBaseUri(), "text/plain").setRequestBody("")) .responseAsString(); - assertEquals(logsize, handler.logEntries.size(), "There should have been no more log entries"); + assertEquals(logsize, handler.logEntries.size(), "There should have been no more log " + + "entries"); } @Test @@ -189,7 +192,6 @@ public void clientBuilderLogging() throws Exception { * A basic DNS log test that can be called with different values. * * @param cacheValue the value to set for the cache lifetime - * * @throws Exception if the test fails or errors */ private void basicDnsLogTest(String cacheValue) throws Exception { @@ -339,12 +341,12 @@ public void dnsWarningDefaultWithSecurityManager(@Mocked final SecurityManager /** * Set a LogManager configuration property and assert it was set correctly - * */ private void setAndAssertLogProperty(String name, String value) throws Exception { LogManager.getLogManager().readConfiguration(new ByteArrayInputStream((name + "=" + value).getBytes())); - assertEquals(value, LogManager.getLogManager().getProperty(name), "The log property should be the test value"); + assertEquals(value, LogManager.getLogManager().getProperty(name), "The log property " + + "should be the test value"); } /** @@ -356,7 +358,8 @@ private void setAndAssertLogProperty(String name, String value) throws Exception private void assertLogMessage(String pattern, int index) { Pattern p = Pattern.compile(pattern); String msg = handler.logEntries.get(index).getMessage(); - assertTrue(p.matcher(msg).matches(), "The log entry \"" + msg + "\" should match pattern " + pattern); + assertTrue(p.matcher(msg).matches(), "The log entry \"" + msg + "\" should match pattern " + + "" + pattern); } /** diff --git a/cloudant-client/src/test/java/com/cloudant/tests/QueryTests.java b/cloudant-client/src/test/java/com/cloudant/tests/QueryTests.java index 9d0d762e3..430c343be 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/QueryTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/QueryTests.java @@ -43,7 +43,8 @@ public class QueryTests { @Test public void basicSelector1() { QueryBuilder qb = new QueryBuilder(eq("director", "Lars von Trier")); - Assertions.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}}", + qb.build()); } // "Selector with two fields" @@ -52,7 +53,8 @@ public void basicSelector2() { QueryBuilder qb = new QueryBuilder(and( eq("name", "Paul"), eq("location", "Boston"))); - Assertions.assertEquals("{\"selector\": {\"$and\": [{\"name\": {\"$eq\": \"Paul\"}}, {\"location\": {\"$eq\": \"Boston\"}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"name\": {\"$eq\": \"Paul\"}}, " + + "{\"location\": {\"$eq\": \"Boston\"}}]}}", qb.build()); } // "SUBFIELDS" @@ -62,7 +64,8 @@ public void basicSelector3() { Assertions.assertEquals("{\"selector\": {\"imdb.rating\": {\"$eq\": 8}}}", qb.build()); } - // "Example selector using an operator to match any document, where the age field has a value greater than 20:" + // "Example selector using an operator to match any document, where the age field has a value + // greater than 20:" @Test public void basicSelector4() { QueryBuilder qb = new QueryBuilder(gt("year", 2018)); @@ -76,7 +79,8 @@ public void basicSelector5() { eq("$text", "Schwarzenegger"), in("year", 1984, 1991) )); - Assertions.assertEquals("{\"selector\": {\"$and\": [{\"$text\": {\"$eq\": \"Schwarzenegger\"}}, {\"year\": {\"$in\": [1984, 1991]}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"$text\": {\"$eq\": " + + "\"Schwarzenegger\"}}, {\"year\": {\"$in\": [1984, 1991]}}]}}", qb.build()); } // "$and operator used with full text indexing" @@ -86,7 +90,8 @@ public void basicSelector5_single() { eq("$text", "Schwarzenegger"), in("year", 1984) )); - Assertions.assertEquals("{\"selector\": {\"$and\": [{\"$text\": {\"$eq\": \"Schwarzenegger\"}}, {\"year\": {\"$in\": [1984]}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"$text\": {\"$eq\": " + + "\"Schwarzenegger\"}}, {\"year\": {\"$in\": [1984]}}]}}", qb.build()); } // "$or operator used with full text indexing" @@ -96,7 +101,8 @@ public void basicSelector6() { eq("director", "George Lucas"), eq("director", "Steven Spielberg") )); - Assertions.assertEquals("{\"selector\": {\"$or\": [{\"director\": {\"$eq\": \"George Lucas\"}}, {\"director\": {\"$eq\": \"Steven Spielberg\"}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$or\": [{\"director\": {\"$eq\": \"George " + + "Lucas\"}}, {\"director\": {\"$eq\": \"Steven Spielberg\"}}]}}", qb.build()); } // "$or operator used with database indexed on the field "year" @@ -109,7 +115,9 @@ public void basicSelector7() { eq("director", "Steven Spielberg") ) )); - Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$eq\": 1977}}, {\"$or\": [{\"director\": {\"$eq\": \"George Lucas\"}}, {\"director\": {\"$eq\": \"Steven Spielberg\"}}]}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$eq\": 1977}}, " + + "{\"$or\": [{\"director\": {\"$eq\": \"George Lucas\"}}, {\"director\": {\"$eq\":" + + " \"Steven Spielberg\"}}]}]}}", qb.build()); } // "$not operator used with database indexed on the field "year"" @@ -120,7 +128,9 @@ public void basicSelector8() { lte("year", 1903), not(eq("year", 1901)) )); - Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gte\": 1900}}, {\"year\": {\"$lte\": 1903}}, {\"$not\": {\"year\": {\"$eq\": 1901}}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gte\": 1900}}, " + + "{\"year\": {\"$lte\": 1903}}, {\"$not\": {\"year\": {\"$eq\": 1901}}}]}}", qb + .build()); } // "$nor operator used with database indexed on the field "year"" @@ -134,28 +144,33 @@ public void basicSelector9() { eq("year", 1905), eq("year", 1907)) )); - Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gte\": 1900}}, {\"year\": {\"$lte\": 1910}}, {\"$nor\": [{\"year\": {\"$eq\": 1901}}, {\"year\": {\"$eq\": 1905}}, {\"year\": {\"$eq\": 1907}}]}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gte\": 1900}}, " + + "{\"year\": {\"$lte\": 1910}}, {\"$nor\": [{\"year\": {\"$eq\": 1901}}, " + + "{\"year\": {\"$eq\": 1905}}, {\"year\": {\"$eq\": 1907}}]}]}}", qb.build()); } // "$all operator used with full text indexing" @Test public void basicSelector10() { QueryBuilder qb = new QueryBuilder(all("genre", "Comedy", "Short")); - Assertions.assertEquals("{\"selector\": {\"genre\": {\"$all\": [\"Comedy\", \"Short\"]}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"genre\": {\"$all\": [\"Comedy\", " + + "\"Short\"]}}}", qb.build()); } // "$all operator used with full text indexing" @Test public void basicSelector10_single() { QueryBuilder qb = new QueryBuilder(all("genre", "Comedy")); - Assertions.assertEquals("{\"selector\": {\"genre\": {\"$all\": [\"Comedy\"]}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"genre\": {\"$all\": [\"Comedy\"]}}}", qb.build + ()); } // "elemMatch operator used with full text indexing" @Test public void basicSelector11() { QueryBuilder qb = new QueryBuilder(elemMatch("genre", PredicateExpression.eq("Horror"))); - Assertions.assertEquals("{\"selector\": {\"genre\": {\"$elemMatch\": {\"$eq\": \"Horror\"}}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"genre\": {\"$elemMatch\": {\"$eq\": " + + "\"Horror\"}}}}", qb.build()); } // "$lt operator used with database indexed on the field "year"" @@ -170,7 +185,8 @@ public void basicSelector12() { @Test public void basicSelector13() { QueryBuilder qb = new QueryBuilder(and(eq("year", 2015), exists("title", true))); - Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$eq\": 2015}}, {\"title\": {\"$exists\": true}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$eq\": 2015}}, " + + "{\"title\": {\"$exists\": true}}]}}", qb.build()); } // "$type operator used with full text indexing" @@ -198,8 +214,9 @@ public void basicSelector15_single() { @Test public void basicSelector16() { QueryBuilder qb = new QueryBuilder(and(gt("year", 2009), - nin("year", 2010, 2015))); - Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, {\"year\": {\"$nin\": [2010, 2015]}}]}}", qb.build()); + nin("year", 2010, 2015))); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, " + + "{\"year\": {\"$nin\": [2010, 2015]}}]}}", qb.build()); } // "$nin operator used with full text indexing" @@ -207,14 +224,16 @@ public void basicSelector16() { public void basicSelector16_single() { QueryBuilder qb = new QueryBuilder(and(gt("year", 2009), nin("year", 2010))); - Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, {\"year\": {\"$nin\": [2010]}}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, " + + "{\"year\": {\"$nin\": [2010]}}]}}", qb.build()); } @Test public void complexSelector1() { QueryBuilder qb = new QueryBuilder(not(and(gt("year", 2009), nin("year", 2010, 2015)))); - Assertions.assertEquals("{\"selector\": {\"$not\": {\"$and\": [{\"year\": {\"$gt\": 2009}}, {\"year\": {\"$nin\": [2010, 2015]}}]}}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$not\": {\"$and\": [{\"year\": {\"$gt\": " + + "2009}}, {\"year\": {\"$nin\": [2010, 2015]}}]}}}", qb.build()); } @Test @@ -222,18 +241,21 @@ public void complexSelector2() { QueryBuilder qb = new QueryBuilder(or( and( eq("Actor", "Schwarzenegger"), - eq("Year",2012)), + eq("Year", 2012)), and( - eq("Actor","de Vito"), + eq("Actor", "de Vito"), eq("Year", 2001)) )); - Assertions.assertEquals("{\"selector\": {\"$or\": [{\"$and\": [{\"Actor\": {\"$eq\": \"Schwarzenegger\"}}, {\"Year\": {\"$eq\": 2012}}]}, {\"$and\": [{\"Actor\": {\"$eq\": \"de Vito\"}}, {\"Year\": {\"$eq\": 2001}}]}]}}", qb.build()); + Assertions.assertEquals("{\"selector\": {\"$or\": [{\"$and\": [{\"Actor\": {\"$eq\": " + + "\"Schwarzenegger\"}}, {\"Year\": {\"$eq\": 2012}}]}, {\"$and\": [{\"Actor\": " + + "{\"$eq\": \"de Vito\"}}, {\"Year\": {\"$eq\": 2001}}]}]}}", qb.build()); } // "Selector basics" @Test public void basicSelector1WithFields() { - QueryBuilder qb = new QueryBuilder(eq("director", "Lars von Trier")).fields("_id", "_rev", "year", "title"); + QueryBuilder qb = new QueryBuilder(eq("director", "Lars von Trier")).fields("_id", + "_rev", "year", "title"); Assertions.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}, " + "\"fields\": [\"_id\", \"_rev\", \"year\", \"title\"]}", qb.build()); } @@ -241,7 +263,8 @@ public void basicSelector1WithFields() { // "Selector basics" @Test public void basicSelector1WithSort() { - QueryBuilder qb = new QueryBuilder(eq("director", "Lars von Trier")).sort(Sort.asc("year"), Sort.desc("director")); + QueryBuilder qb = new QueryBuilder(eq("director", "Lars von Trier")).sort(Sort.asc + ("year"), Sort.desc("director")); Assertions.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}, " + "\"sort\": [{\"year\": \"asc\"}, {\"director\": \"desc\"}]}", qb.build()); } @@ -255,11 +278,10 @@ public void basicSelector1WithAllOptions() { limit(10). skip(0); Assertions.assertEquals("{\"selector\": {\"director\": {\"$eq\": \"Lars von Trier\"}}, " + - "\"fields\": [\"_id\", \"_rev\", \"year\", \"title\"], " + + "\"fields\": [\"_id\", \"_rev\", \"year\", \"title\"], " + "\"sort\": [{\"year\": \"asc\"}, {\"director\": \"desc\"}], \"limit\": 10, " + "\"skip\": 0}", qb.build()); } - } diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java index a573b3e5b..33c4a7a79 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ReplicatorTest.java @@ -59,7 +59,8 @@ public void replication() throws Exception { // find and remove replicator doc ReplicatorDocument repDoc = Utils.waitForReplicatorToComplete(account, response.getId()); - assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator should reach completed state"); + assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator " + + "should reach completed state"); } @Test @@ -78,7 +79,8 @@ public void replication_filteredWithQueryParams() throws Exception { // find and remove replicator doc ReplicatorDocument repDoc = Utils.waitForReplicatorToComplete(account, response.getId()); - assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator should reach completed state"); + assertTrue("completed".equalsIgnoreCase(repDoc.getReplicationState()), "The replicator " + + "should reach completed state"); } @Test diff --git a/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java b/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java index 10fbe1e91..187ca2a18 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ResponseTest.java @@ -155,7 +155,8 @@ 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(d.getConnection().getResponseCode() / 100 == 2, "The first request should succeed"); + 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); @@ -194,6 +195,7 @@ private void exceptionAsserts(CouchDbException e, int expectedCode, String expec } private void assertExceptionStatusCode(CouchDbException e, int expectedCode) { - assertEquals(expectedCode, e.getStatusCode(), "The HTTP status code should be " + expectedCode); + 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 db497b25e..d3e1dc895 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/SearchTests.java @@ -251,7 +251,8 @@ private void escapingTest(String expectedResult, String query) { String uriBaseString = account.getBaseUri().toASCIIString(); String expectedUriString = uriBaseString - + "/animaldb/_design/views101/_search/animals?include_docs=true&q=" + expectedResult; + + "/animaldb/_design/views101/_search/animals?include_docs=true&q=" + + expectedResult; String uriString = uri.toASCIIString(); assertEquals(expectedUriString, uriString); 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 1ba009f87..dc95cc97a 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/SessionInterceptorExpiryTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/SessionInterceptorExpiryTests.java @@ -28,9 +28,9 @@ import com.cloudant.http.internal.interceptors.CookieInterceptor; import com.cloudant.http.internal.interceptors.IamCookieInterceptor; import com.cloudant.http.internal.ok.OkHttpClientHttpUrlConnectionFactory; +import com.cloudant.tests.extensions.MockWebServerExtension; 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.jupiter.api.BeforeAll; @@ -64,11 +64,12 @@ public boolean supportsTestTemplate(ExtensionContext context) { } @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { - return Stream.of(invocationContext(false,"/_iam_session"), + public Stream provideTestTemplateInvocationContexts + (ExtensionContext context) { + return Stream.of(invocationContext(false, "/_iam_session"), invocationContext(false, "/_session"), invocationContext(true, "/_iam_session"), - invocationContext(true,"/_session")); + invocationContext(true, "/_session")); } public static TestTemplateInvocationContext invocationContext(final boolean okUsable, @@ -85,11 +86,13 @@ public List getAdditionalExtensions() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: - return parameterContext.getParameter().getType().equals(boolean.class); + return parameterContext.getParameter().getType().equals + (boolean.class); case 1: - return parameterContext.getParameter().getType().equals(String.class); + return parameterContext.getParameter().getType().equals + (String.class); } return false; } @@ -97,7 +100,7 @@ public boolean supportsParameter(ParameterContext parameterContext, @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: return okUsable; case 1: @@ -194,27 +197,33 @@ private void executeTest(boolean okUsable, // Consume response stream and assert ok: true String responseStr = conn.responseAsString(); String okPattern = ".*\"ok\"\\s*:\\s*true.*"; - assertTrue(Pattern.compile(okPattern, Pattern.DOTALL).matcher(responseStr).matches(), "There should be an ok response: " + responseStr); + 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(sessionPath, sessionRequest.getPath(), "The interceptor should make a session request"); - assertNull(sessionRequest.getHeader("Cookie"), "There should be no existing cookie on the session request"); + 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("/", 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 : + String expectedCookie = ((sessionPath.equals("/_session")) ? MockWebServerResources + .AUTH_COOKIE_NAME : MockWebServerResources.IAM_COOKIE_NAME) + "=" + cookieValue; - assertEquals(expectedCookie, getRequest.getHeader("Cookie"), "The cookie should be the correct session type"); + 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 */ @TestTemplate @@ -230,9 +239,11 @@ public void testMakesCookieRequest(boolean okUsable, String sessionPath) throws * @throws Exception */ @TestTemplate - public void testNewCookieRequestMadeAfterExpiry(boolean okUsable, String sessionPath) throws Exception { + public void testNewCookieRequestMadeAfterExpiry(boolean okUsable, String sessionPath) throws + Exception { // Make a GET request and get a cookie valid for 2 seconds - executeTest(okUsable, sessionPath, 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 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 18b39337b..302dc86c3 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/SslAuthenticationTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/SslAuthenticationTest.java @@ -22,8 +22,8 @@ import com.cloudant.client.api.CloudantClient; import com.cloudant.client.org.lightcouch.CouchDbException; import com.cloudant.test.main.RequiresCloudantService; -import com.cloudant.tests.util.HttpFactoryParameterizedTest; import com.cloudant.tests.extensions.MockWebServerExtension; +import com.cloudant.tests.util.HttpFactoryParameterizedTest; import com.cloudant.tests.util.MockWebServerResources; import org.junit.jupiter.api.BeforeEach; @@ -58,7 +58,8 @@ public boolean supportsTestTemplate(ExtensionContext context) { } @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + public Stream provideTestTemplateInvocationContexts + (ExtensionContext context) { return Stream.of(invocationContext(false), invocationContext(true)); } @@ -76,9 +77,10 @@ public List getAdditionalExtensions() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: - return parameterContext.getParameter().getType().equals(boolean.class); + return parameterContext.getParameter().getType().equals + (boolean.class); } return false; } @@ -86,7 +88,7 @@ public boolean supportsParameter(ParameterContext parameterContext, @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: return okUsable; } @@ -119,7 +121,8 @@ public void beforeEach() { private static void validateClientAuthenticationException(CouchDbException e) { assertNotNull(e, "Expected CouchDbException but got null"); Throwable t = e.getCause(); - assertTrue(t instanceof SSLHandshakeException, "Expected SSLHandshakeException caused by client certificate check but got " + t.getClass()); + assertTrue(t instanceof SSLHandshakeException, "Expected SSLHandshakeException caused by " + + "client certificate check but got " + t.getClass()); } /** @@ -248,7 +251,8 @@ public void localSSLAuthenticationDisabledWithCookieAuth() throws Exception { server.enqueue(new MockResponse()); //OK 200 // Use a username and password to enable the cookie auth interceptor - CloudantClient dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server).username("user") + CloudantClient dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server) + .username("user") .password("password") .disableSSLAuthentication() .build(); @@ -268,14 +272,15 @@ public void localSSLAuthenticationEnabledWithCookieAuth() throws Exception { server.enqueue(new MockResponse()); //OK 200 // Use a username and password to enable the cookie auth interceptor - CloudantClient dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server).username("user") + CloudantClient dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server) + .username("user") .password("password") .build(); try { dbClient.getAllDbs(); fail("The SSL authentication failure should result in a CouchDbException"); - } catch(CouchDbException e) { + } catch (CouchDbException e) { validateClientAuthenticationException(e); } } 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 b37e7e6f8..9881ba469 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/UnicodeTest.java @@ -55,7 +55,7 @@ public class UnicodeTest extends TestWithDbPerTest { 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 static final String EXPECTED_JSON = "{\"_id\":\""+TESTSTRING_KEY+"\"," + + private static final String EXPECTED_JSON = "{\"_id\":\"" + TESTSTRING_KEY + "\"," + "\"_rev\":\"1-39933759c7250133b6039d94ea09134f\",\"foo\":\"Gr\u00fc\u00dfe " + "\u65e5\u672c\u8a9e \uD834\uDD1E.\"}\n"; 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 53fb0b42a..52529ec37 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ViewPaginationTests.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ViewPaginationTests.java @@ -51,13 +51,15 @@ public boolean supportsTestTemplate(ExtensionContext context) { } @Override - public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + 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) { + public static TestTemplateInvocationContext invocationContext(final CheckPagination.Type + type, + final boolean descending, + final boolean stateless) { return new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { @@ -71,13 +73,16 @@ public List getAdditionalExtensions() { @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: - return parameterContext.getParameter().getType().equals(CheckPagination.Type.class); + return parameterContext.getParameter().getType().equals + (CheckPagination.Type.class); case 1: - return parameterContext.getParameter().getType().equals(boolean.class); + return parameterContext.getParameter().getType().equals + (boolean.class); case 2: - return parameterContext.getParameter().getType().equals(boolean.class); + return parameterContext.getParameter().getType().equals + (boolean.class); } return false; } @@ -85,7 +90,7 @@ public boolean supportsParameter(ParameterContext parameterContext, @Override public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { - switch(parameterContext.getIndex()) { + switch (parameterContext.getIndex()) { case 0: return type; case 1: @@ -103,7 +108,8 @@ public Object resolveParameter(ParameterContext parameterContext, public static Iterable data() { - List contexts = new ArrayList(); + List contexts = new + ArrayList(); boolean[] tf = new boolean[]{true, false}; for (CheckPagination.Type type : CheckPagination.Type.values()) { for (boolean descending : tf) { 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 a9463aa24..d9150d09e 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/ViewsTest.java @@ -41,10 +41,10 @@ import com.cloudant.test.main.RequiresDB; import com.cloudant.tests.base.TestWithDbPerTest; import com.cloudant.tests.extensions.CloudantClientExtension; -import com.cloudant.tests.util.ContextCollectingInterceptor; import com.cloudant.tests.extensions.DatabaseExtension; import com.cloudant.tests.extensions.MockWebServerExtension; import com.cloudant.tests.extensions.MultiExtension; +import com.cloudant.tests.util.ContextCollectingInterceptor; import com.cloudant.tests.util.Utils; import com.google.gson.Gson; import com.google.gson.JsonArray; @@ -76,9 +76,11 @@ public class ViewsTest extends TestWithDbPerTest { public static MockWebServerExtension mockWebServerExt = new MockWebServerExtension(); public static ContextCollectingInterceptor cci = new ContextCollectingInterceptor(); - public static CloudantClientExtension interceptedClient = new CloudantClientExtension(CloudantClientHelper.getClientBuilder() + public static CloudantClientExtension interceptedClient = new CloudantClientExtension + (CloudantClientHelper.getClientBuilder() .interceptors(cci)); - public static DatabaseExtension.PerClass interceptedDB = new DatabaseExtension.PerClass(interceptedClient); + public static DatabaseExtension.PerClass interceptedDB = new DatabaseExtension.PerClass + (interceptedClient); @RegisterExtension public static MultiExtension extensions = new MultiExtension( @@ -125,10 +127,11 @@ public void byKeys() throws Exception { public void byNonExistentAndExistingKey() throws Exception { init(); List> foos = db.getViewRequestBuilder("example", "foo") - .newRequest(Key.Type.STRING, Object.class).includeDocs(true).keys("key-1", "non-existent") + .newRequest(Key.Type.STRING, Object.class).includeDocs(true).keys("key-1", + "non-existent") .build().getResponse().getRows(); assertThat(foos.size(), is(1)); - for (ViewResponse.Row row: foos) { + for (ViewResponse.Row row : foos) { if (row.getError() == null) { assertThat(row.getKey().toString(), is("key-1")); } else { @@ -622,14 +625,18 @@ public void scalarValues() throws Exception { @Test public void viewWithNoResult_emptyList() throws IOException { init(); - 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"); + 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(db.getViewRequestBuilder("example", "by_tag").newRequest(Key.Type.STRING, Object.class).keys("javax").build().getSingleValue(), "The single result should be null"); + assertNull(db.getViewRequestBuilder("example", "by_tag").newRequest(Key.Type.STRING, + Object.class).keys("javax").build().getSingleValue(), "The single result should " + + "be null"); } @@ -875,7 +882,8 @@ public void multiRequest() throws IOException { assertEquals(3, responses.size(), "There should be 3 responses for 3 requests"); for (ViewResponse response : responses) { 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); + assertEquals("key-" + i, response.getKeys().get(0), "The returned key should be key-" + + i); i++; } } @@ -1258,7 +1266,8 @@ 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(query.contains("startkey=%5B%22uuid%22," + "1005%5D"), "The query startkey should match."); + assertTrue(query.contains("startkey=%5B%22uuid%22," + "1005%5D"), "The query startkey " + + "should match."); } /** @@ -1320,13 +1329,16 @@ public void staleParameterValues() throws Exception { assertStaleParameter(viewBuilder.build(), noStaleParameter); // Test the OK stale argument supplied case - assertStaleParameter(viewBuilder.stale(SettableViewParameters.STALE_OK).build(), staleParameterOK); + assertStaleParameter(viewBuilder.stale(SettableViewParameters.STALE_OK).build(), + staleParameterOK); // Test the update_after stale argument supplied case - assertStaleParameter(viewBuilder.stale(SettableViewParameters.STALE_UPDATE_AFTER).build(), staleParameterUpdate); + assertStaleParameter(viewBuilder.stale(SettableViewParameters.STALE_UPDATE_AFTER).build() + , staleParameterUpdate); // Test the NO stale argument supplied case - assertStaleParameter(viewBuilder.stale(SettableViewParameters.STALE_NO).build(), noStaleParameter); + assertStaleParameter(viewBuilder.stale(SettableViewParameters.STALE_NO).build(), + noStaleParameter); } /** @@ -1346,7 +1358,8 @@ private void assertStaleParameter(ViewRequest viewRequest, Patt viewRequest.getSingleValue(); RecordedRequest request = mockWebServer.takeRequest(1, TimeUnit.SECONDS); assertNotNull(request, "There should have been a view request"); - assertTrue(p.matcher(request.getPath()).matches(), "There request URL should match the pattern " + p.toString()); + assertTrue(p.matcher(request.getPath()).matches(), "There request URL should match the " + + "pattern " + p.toString()); } /** diff --git a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java index 773812d00..7d0a000d7 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerClass.java @@ -22,10 +22,12 @@ // Base class for tests which require a DB which is used throughout the lifetime of the test class public class TestWithDbPerClass extends TestWithDb { - protected static DatabaseExtension.PerClass dbResource = new DatabaseExtension.PerClass(clientResource); + protected static DatabaseExtension.PerClass dbResource = new DatabaseExtension.PerClass + (clientResource); @RegisterExtension - protected static MultiExtension perClassExtensions = new MultiExtension(clientResource, dbResource); + protected static MultiExtension perClassExtensions = new MultiExtension(clientResource, + dbResource); @BeforeAll public static void testWithDbBeforeAll() { 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 index e1564ae1c..3f7965dd4 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithDbPerTest.java @@ -23,10 +23,12 @@ // Base class for tests which require a new DB for each test method public class TestWithDbPerTest extends TestWithDb { - protected static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest(clientResource); + protected static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest + (clientResource); @RegisterExtension - protected static MultiExtension perTestExtensions = new MultiExtension(clientResource, dbResource); + protected static MultiExtension perTestExtensions = new MultiExtension(clientResource, + dbResource); @BeforeEach public void testWithDbBeforeEach() { diff --git a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java index f7f8c7940..c53b28ce3 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithMockedServer.java @@ -33,8 +33,10 @@ 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); + public static CloudantClientMockServerExtension clientResource = new + CloudantClientMockServerExtension(mockWebServerExt); + public static DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest + (clientResource); @RegisterExtension public static MultiExtension extensions = new MultiExtension( mockWebServerExt, diff --git a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java index 38361ba77..ad1ba61f9 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/base/TestWithReplication.java @@ -33,11 +33,14 @@ public class TestWithReplication { protected static CloudantClientExtension clientResource = new CloudantClientExtension(); protected static CloudantClient account; - protected static DatabaseExtension.PerClass db1Resource = new DatabaseExtension.PerClass(clientResource); - protected static DatabaseExtension.PerClass db2Resource = new DatabaseExtension.PerClass(clientResource); + protected static DatabaseExtension.PerClass db1Resource = new DatabaseExtension.PerClass + (clientResource); + protected static DatabaseExtension.PerClass db2Resource = new DatabaseExtension.PerClass + (clientResource); @RegisterExtension - public static MultiExtension extensions = new MultiExtension(clientResource, db1Resource, db2Resource); + public static MultiExtension extensions = new MultiExtension(clientResource, db1Resource, + db2Resource); protected static Database db1; protected static Database db2; @@ -64,6 +67,7 @@ protected void assertConflictsNotZero(Database db) throws Exception { ("conflicts", "conflict").newRequest(Key.Type.COMPLEX, String.class).build() .getResponse(); int conflictCount = conflicts.getRows().size(); - assertTrue(conflictCount > 0, "There should be at least 1 conflict, there were " + conflictCount); + 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/CloudantClientMockServerExtension.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientMockServerExtension.java index c1f680249..a3270d7e5 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientMockServerExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/CloudantClientMockServerExtension.java @@ -24,7 +24,8 @@ import okhttp3.mockwebserver.MockWebServer; -public class CloudantClientMockServerExtension extends AbstractClientExtension implements BeforeEachCallback, AfterEachCallback { +public class CloudantClientMockServerExtension extends AbstractClientExtension implements + BeforeEachCallback, AfterEachCallback { private final MockWebServerExtension mockWebServerExt; private MockWebServer mockWebServer; @@ -37,7 +38,8 @@ public CloudantClientMockServerExtension(MockWebServerExtension mockWebServerExt @Override public void beforeEach(ExtensionContext ctx) { this.mockWebServer = this.mockWebServerExt.get(); - this.client = CloudantClientHelper.newMockWebServerClientBuilder(this.mockWebServer).build(); + this.client = CloudantClientHelper.newMockWebServerClientBuilder(this.mockWebServer) + .build(); } @Override diff --git a/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java b/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java index 94cd3534d..da30a0490 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/extensions/DatabaseExtension.java @@ -129,11 +129,12 @@ public String getDatabaseName() { * @return the URI for the DB with creds */ public String getDbURIWithUserInfo() throws Exception { - String info = clientResource.getBaseURIWithUserInfo() + "/" + getDatabaseName(); + String info = clientResource.getBaseURIWithUserInfo() + "/" + getDatabaseName(); return info; } - public static class PerClass extends DatabaseExtension implements BeforeAllCallback, AfterAllCallback { + public static class PerClass extends DatabaseExtension implements BeforeAllCallback, + AfterAllCallback { public PerClass(CloudantClientExtension clientResource) { super(clientResource); @@ -150,7 +151,8 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception { } } - public static class PerTest extends DatabaseExtension implements BeforeEachCallback, AfterEachCallback { + public static class PerTest extends DatabaseExtension implements BeforeEachCallback, + AfterEachCallback { public PerTest(CloudantClientExtension clientResource) { super(clientResource); 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 0dc0d1101..ff4e94de1 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 @@ -116,9 +116,9 @@ public CheckPagination stateless(boolean stateless) { * @return the last page in the view. */ private void checkPagesForward(int currentPage, - int numberOfPages, - int docCount, - int docsPerPage) throws IOException { + int numberOfPages, + int docCount, + int docsPerPage) throws IOException { for (int i = 0; i < numberOfPages; ++i) { nextPage(); checkPage(page, docCount, docsPerPage, currentPage + i + 1, descending); @@ -135,9 +135,9 @@ private void checkPagesForward(int currentPage, * @return the first page in the view */ private void checkPagesBackward(int currentPage, - int numberOfPages, - int docCount, - int docsPerPage) throws IOException { + int numberOfPages, + int docCount, + int docsPerPage) throws IOException { for (int i = 0; i < numberOfPages; ++i) { previousPage(); checkPage(page, docCount, docsPerPage, currentPage - i - 1, descending); 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 4ff8d1953..ae1cb50d1 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 @@ -53,6 +53,7 @@ public void changeHttpConnectionFactory(boolean isOkUsable) throws Exception { } // Verify that we are getting the behaviour we expect. assertEquals( - isOkUsable, OkHelper.isOkUsable(), "The OK usable value was not what was expected for the test parameter."); + 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/IamSystemPropertyMock.java b/cloudant-client/src/test/java/com/cloudant/tests/util/IamSystemPropertyMock.java index 734457610..51585704b 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/util/IamSystemPropertyMock.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/util/IamSystemPropertyMock.java @@ -32,7 +32,7 @@ public class IamSystemPropertyMock extends MockUp { public void setMockIamTokenEndpointUrl(String mockIamTokenEndpointUrl) { this.mockIamTokenEndpointUrl.set(mockIamTokenEndpointUrl); } - + @Mock public String getProperty(Invocation inv, String key) { if (key.equals("com.cloudant.client.iamserver")) { 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 ef5a26c50..687aab4da 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 @@ -186,14 +186,16 @@ 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( expectedNumber + 1, parts.length, "There should be " + expectedNumber + " instances of " + splitOn + " in the " + + assertEquals(expectedNumber + 1, parts.length, "There should be " + expectedNumber + " " + + "instances of " + splitOn + " in the " + "content"); return parts; } /** * Test utility to put design documents under the testing resource folder in to the database. - * @param db database to put the design docs + * + * @param db database to put the design docs * @param directory location of design docs */ public static void putDesignDocs(Database db, File directory) throws FileNotFoundException { From adb4f2069d40927234713b63723668c6f2c3851c Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 27 Mar 2018 16:00:17 +0100 Subject: [PATCH 14/17] Fix findAny --- .../src/test/java/com/cloudant/tests/DocumentsCRUDTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java index 2ac2c14f8..45e67be12 100644 --- a/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java +++ b/cloudant-client/src/test/java/com/cloudant/tests/DocumentsCRUDTest.java @@ -76,9 +76,9 @@ public void findJsonObject() { @Test public void findAny() { - String uri = account.getBaseUri() + "_all_dbs"; - JsonObject jsonObject = db.findAny(JsonObject.class, uri); - assertNotNull(jsonObject); + String uri = account.getBaseUri() + "/_all_dbs"; + JsonArray jsonArray = db.findAny(JsonArray.class, uri); + assertNotNull(jsonArray); } @Test From fe2ecb7b4c6465691bad46bfe9ad5aa64818cc23 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 27 Mar 2018 16:30:31 +0100 Subject: [PATCH 15/17] extend TestWithDbPerTest --- .../cloudant/api/query/IndexLifecycleTest.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) 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 a56733360..90b95e7e7 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 @@ -16,7 +16,6 @@ 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; @@ -24,13 +23,11 @@ import com.cloudant.client.api.query.TextIndex; import com.cloudant.client.api.query.Type; import com.cloudant.test.main.RequiresCloudant; -import com.cloudant.tests.extensions.CloudantClientExtension; -import com.cloudant.tests.extensions.DatabaseExtension; +import com.cloudant.tests.base.TestWithDbPerTest; 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; @@ -45,18 +42,10 @@ * Test list indexes * After delete indexes */ -public class IndexLifecycleTest { - - @RegisterExtension - public static CloudantClientExtension clientResource = new CloudantClientExtension(); - @RegisterExtension - public DatabaseExtension.PerTest dbResource = new DatabaseExtension.PerTest(clientResource); - - private static Database db; +public class IndexLifecycleTest extends TestWithDbPerTest { @BeforeEach public void createIndexes() throws Exception { - db = dbResource.get(); // Create a JSON index db.createIndex(JsonIndex.builder() From b3a4d2993917b7305b7f7580d8233e97058602a7 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 27 Mar 2018 16:42:49 +0100 Subject: [PATCH 16/17] Copyrights --- Jenkinsfile | 2 +- build.gradle | 2 +- cloudant-client/build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 290eec3b9..7d5e093d9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,7 +1,7 @@ #!groovy /* - * Copyright © 2016, 2017 IBM Corp. All rights reserved. + * Copyright © 2016, 2018 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 diff --git a/build.gradle b/build.gradle index 759f15d88..efb1014f0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright © 2016 IBM Corp. All rights reserved. + * Copyright © 2016, 2018 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 diff --git a/cloudant-client/build.gradle b/cloudant-client/build.gradle index 4f7855e78..592931ac6 100644 --- a/cloudant-client/build.gradle +++ b/cloudant-client/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright © 2016, 2017 IBM Corp. All rights reserved. + * Copyright © 2016, 2018 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 From 6bb8b94ba8a1acb6426c88f26d921dc457c0efb8 Mon Sep 17 00:00:00 2001 From: Tom Blench Date: Tue, 27 Mar 2018 16:56:22 +0100 Subject: [PATCH 17/17] Revert full matrix test change --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7d5e093d9..4509d0ba5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -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" || env.BRANCH_NAME == "junit5") { + if (env.BRANCH_NAME == "master") { axes.putAll( Couch1_6: { runTests(COUCH1_6_ENV, false)