Java tutorial
/*- * #%L * SDK for CLX XMS * %% * Copyright (C) 2016 CLX Communications * %% * 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. * #L% */ package com.clxcommunications.xms; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.delete; import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.matching; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.put; import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.CoreMatchers.theInstance; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Queue; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.concurrent.FutureCallback; import org.apache.http.entity.ContentType; import org.eclipse.jetty.util.ConcurrentArrayQueue; import org.junit.Rule; import org.junit.Test; import org.threeten.bp.Clock; import org.threeten.bp.OffsetDateTime; import org.threeten.bp.ZoneOffset; import com.clxcommunications.testsupport.TestUtils; import com.clxcommunications.xms.api.ApiError; import com.clxcommunications.xms.api.BatchDeliveryReport; import com.clxcommunications.xms.api.BatchId; import com.clxcommunications.xms.api.DeliveryStatus; import com.clxcommunications.xms.api.GroupCreate; import com.clxcommunications.xms.api.GroupId; import com.clxcommunications.xms.api.GroupResult; import com.clxcommunications.xms.api.GroupUpdate; import com.clxcommunications.xms.api.MoBinarySms; import com.clxcommunications.xms.api.MoSms; import com.clxcommunications.xms.api.MoTextSms; import com.clxcommunications.xms.api.MtBatchBinarySmsCreate; import com.clxcommunications.xms.api.MtBatchBinarySmsResult; import com.clxcommunications.xms.api.MtBatchBinarySmsUpdate; import com.clxcommunications.xms.api.MtBatchDryRunResult; import com.clxcommunications.xms.api.MtBatchSmsCreate; import com.clxcommunications.xms.api.MtBatchSmsResult; import com.clxcommunications.xms.api.MtBatchTextSmsCreate; import com.clxcommunications.xms.api.MtBatchTextSmsResult; import com.clxcommunications.xms.api.MtBatchTextSmsUpdate; import com.clxcommunications.xms.api.Page; import com.clxcommunications.xms.api.PagedBatchResult; import com.clxcommunications.xms.api.PagedGroupResult; import com.clxcommunications.xms.api.PagedInboundsResult; import com.clxcommunications.xms.api.RecipientDeliveryReport; import com.clxcommunications.xms.api.Tags; import com.clxcommunications.xms.api.TagsUpdate; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.junit.WireMockRule; import uk.org.lidalia.slf4jtest.TestLoggerFactoryResetRule; public class ApiConnectionIT { /** * A convenient {@link FutureCallback} for use in tests. By default all * callback methods will call {@link #fail(String)}. Override the one that * should succeed. * * @param <T> * the callback result type */ private static class TestCallback<T> implements FutureCallback<T> { @Override public void failed(Exception e) { fail("API call unexpectedly failed with '" + e.getMessage() + "'"); } @Override public void completed(T result) { fail("API call unexpectedly completed with '" + result + "'"); } @Override public void cancelled() { fail("API call unexpectedly cancelled"); } } private final ApiObjectMapper json = new ApiObjectMapper(); @Rule public WireMockRule wm = new WireMockRule(WireMockConfiguration.options().dynamicPort().dynamicHttpsPort()); @Rule public TestLoggerFactoryResetRule testLoggerFactoryResetRule = new TestLoggerFactoryResetRule(); @Test public void canCreateBinaryBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.now(Clock.systemUTC()); MtBatchBinarySmsCreate request = ClxApi.batchBinarySms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("body".getBytes(TestUtils.US_ASCII)) .udh("udh".getBytes(TestUtils.US_ASCII)).build(); MtBatchBinarySmsResult expected = MtBatchBinarySmsResult.builder().sender(request.sender()) .recipients(request.recipients()).body(request.body()).udh(request.udh()).canceled(false) .id(batchId).createdAt(time).modifiedAt(time).build(); String path = "/v1/" + spid + "/batches"; stubPostResponse(expected, path, 201); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchBinarySmsResult actual = conn.createBatch(request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canCreateTextBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.of(2016, 10, 2, 9, 34, 28, 542000000, ZoneOffset.UTC); MtBatchTextSmsCreate request = ClxApi.batchTextSms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("Hello, world! ??!").build(); MtBatchTextSmsResult expected = MtBatchTextSmsResult.builder().sender(request.sender()) .recipients(request.recipients()).body(request.body()).canceled(false).id(batchId).createdAt(time) .modifiedAt(time).build(); String path = "/v1/" + spid + "/batches"; stubPostResponse(expected, path, 201); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchTextSmsResult actual = conn.createBatch(request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canCreateTextBatchWithSubstitutions() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.of(2016, 10, 2, 9, 34, 28, 542000000, ZoneOffset.UTC); MtBatchTextSmsCreate request = ClxApi.batchTextSms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("Hello, ${name}!") .putParameter("name", ClxApi.parameterValues().putSubstitution("123456789", "Jane").defaultValue("world").build()) .build(); MtBatchTextSmsResult expected = MtBatchTextSmsResult.builder().sender(request.sender()) .recipients(request.recipients()).body(request.body()).parameters(request.parameters()) .canceled(false).id(batchId).createdAt(time).modifiedAt(time).build(); String path = "/v1/" + spid + "/batches"; stubPostResponse(expected, path, 201); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchTextSmsResult actual = conn.createBatch(request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canHandleBatchCreateWithError() throws Exception { String spid = TestUtils.freshServicePlanId(); MtBatchTextSmsCreate request = ClxApi.batchTextSms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("Hello, world!").build(); ApiError apiError = ApiError.of("syntax_constraint_violation", "The syntax constraint was violated"); String path = "/v1/" + spid + "/batches"; stubPostResponse(apiError, path, 400); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { conn.createBatch(request); fail("Expected exception, got none"); } catch (ErrorResponseException e) { assertThat(e.getCode(), is(apiError.code())); assertThat(e.getText(), is(apiError.text())); } finally { conn.close(); } } @Test public void canHandleBatchCreateWithInvalidJson() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); MtBatchTextSmsCreate request = ClxApi.batchTextSms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("Hello, world!").build(); String response = String.join("\n", "{", " 'to': [", " '123456789',", " '987654321'", " ],", " 'body': 'Hello, world!',", " 'type' 'mt_text',", " 'canceled': false,", " 'id': '" + batchId + "',", " 'from': '12345',", " 'created_at': '2016-10-02T09:34:28.542Z',", " 'modified_at': '2016-10-02T09:34:28.542Z'", "}").replace('\'', '"'); String path = "/v1/" + spid + "/batches"; wm.stubFor(post(urlEqualTo(path)).willReturn(aResponse().withStatus(201) .withHeader("Content-Type", "application/json; charset=UTF-8").withBody(response))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { conn.createBatch(request); fail("Expected exception, got none"); } catch (ConcurrentException e) { assertThat(e.getCause(), is(instanceOf(JsonParseException.class))); } finally { conn.close(); } } @Test public void canHandleAsyncBatchCreateWithInvalidJson() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); MtBatchTextSmsCreate request = ClxApi.batchTextSms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("Hello, world!").build(); String response = String.join("\n", "{", " 'to': [", " '123456789',", " '987654321'", " ],", " 'body': 'Hello, world!',", " 'type' 'mt_text',", " 'canceled': false,", " 'id': '" + batchId + "',", " 'from': '12345',", " 'created_at': '2016-10-02T09:34:28.542Z',", " 'modified_at': '2016-10-02T09:34:28.542Z'", "}").replace('\'', '"'); String path = "/v1/" + spid + "/batches"; wm.stubFor(post(urlEqualTo(path)).willReturn(aResponse().withStatus(201) .withHeader("Content-Type", "application/json; charset=UTF-8").withBody(response))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); FutureCallback<MtBatchTextSmsResult> callback = new TestCallback<MtBatchTextSmsResult>() { @Override public void failed(Exception e) { assertThat(e, is(instanceOf(JsonParseException.class))); } }; try { conn.createBatchAsync(request, callback).get(); fail("Expected exception, got none"); } catch (ExecutionException e) { assertThat(e.getCause(), is(instanceOf(JsonParseException.class))); } finally { conn.close(); } } @Test(expected = UnauthorizedException.class) public void canHandleBatchCreateWithUnauthorized() throws Exception { String spid = TestUtils.freshServicePlanId(); MtBatchTextSmsCreate request = ClxApi.batchTextSms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("Hello, world!").build(); String path = "/v1/" + spid + "/batches"; stubPostResponse("", path, HttpStatus.SC_UNAUTHORIZED); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { conn.createBatch(request); fail("Expected exception, got none"); } finally { conn.close(); } } @Test public void canHandleAsyncBatchCreateWithUnauthorized() throws Exception { String spid = TestUtils.freshServicePlanId(); MtBatchTextSmsCreate request = ClxApi.batchTextSms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("Hello, world!").build(); String path = "/v1/" + spid + "/batches"; stubPostResponse("", path, HttpStatus.SC_UNAUTHORIZED); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); FutureCallback<MtBatchTextSmsResult> callback = new TestCallback<MtBatchTextSmsResult>() { @Override public void failed(Exception e) { assertThat(e, is(instanceOf(UnauthorizedException.class))); } }; try { conn.createBatchAsync(request, callback).get(); fail("Expected exception, got none"); } catch (ExecutionException e) { assertThat(e.getCause(), is(instanceOf(UnauthorizedException.class))); } finally { conn.close(); } } @Test public void canReplaceBinaryBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.now(Clock.systemUTC()); MtBatchBinarySmsCreate request = ClxApi.batchBinarySms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("body".getBytes(TestUtils.US_ASCII)) .udh("udh".getBytes(TestUtils.US_ASCII)).build(); MtBatchBinarySmsResult expected = MtBatchBinarySmsResult.builder().sender(request.sender()) .recipients(request.recipients()).body(request.body()).udh(request.udh()).canceled(false) .id(batchId).createdAt(time).modifiedAt(time).build(); String path = "/v1/" + spid + "/batches/" + batchId; stubPutResponse(expected, path, 201); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchBinarySmsResult actual = conn.replaceBatch(batchId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPutRequest(path, request); } @Test public void canReplaceTextBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.of(2016, 10, 2, 9, 34, 28, 542000000, ZoneOffset.UTC); MtBatchTextSmsCreate request = ClxApi.batchTextSms().sender("12345").addRecipient("123456789") .addRecipient("987654321").body("Hello, world!").build(); MtBatchTextSmsResult expected = MtBatchTextSmsResult.builder().sender(request.sender()) .recipients(request.recipients()).body(request.body()).canceled(false).id(batchId).createdAt(time) .modifiedAt(time).build(); String path = "/v1/" + spid + "/batches/" + batchId; stubPutResponse(expected, path, 201); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchTextSmsResult actual = conn.replaceBatch(batchId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPutRequest(path, request); } @Test public void canUpdateSimpleTextBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.of(2016, 10, 2, 9, 34, 28, 542000000, ZoneOffset.UTC); MtBatchTextSmsUpdate request = ClxApi.batchTextSmsUpdate().sender("12345").body("Hello, world!") .unsetDeliveryReport().unsetExpireAt().build(); MtBatchTextSmsResult expected = MtBatchTextSmsResult.builder().sender(request.sender()).addRecipient("123") .body(request.body()).canceled(false).id(batchId).createdAt(time).modifiedAt(time).build(); String path = "/v1/" + spid + "/batches/" + batchId; stubPostResponse(expected, path, 201); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchTextSmsResult actual = conn.updateBatch(batchId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canUpdateSimpleBinaryBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.of(2016, 10, 2, 9, 34, 28, 542000000, ZoneOffset.UTC); Set<String> tags = new TreeSet<String>(); tags.add("tag1"); tags.add("tag2"); MtBatchBinarySmsUpdate request = ClxApi.batchBinarySmsUpdate().sender("12345") .body("howdy".getBytes(TestUtils.US_ASCII)).unsetExpireAt().build(); MtBatchBinarySmsResult expected = MtBatchBinarySmsResult.builder().sender(request.sender()) .addRecipient("123").body(request.body()).udh((byte) 1, (byte) 0xff).canceled(false).id(batchId) .createdAt(time).modifiedAt(time).build(); String path = "/v1/" + spid + "/batches/" + batchId; stubPostResponse(expected, path, 201); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchBinarySmsResult actual = conn.updateBatch(batchId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canFetchTextBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.of(2016, 10, 2, 9, 34, 28, 542000000, ZoneOffset.UTC); String path = "/v1/" + spid + "/batches/" + batchId; final MtBatchSmsResult expected = MtBatchTextSmsResult.builder().sender("12345") .addRecipient("123456789", "987654321").body("Hello, world!").canceled(false).id(batchId) .createdAt(time).modifiedAt(time).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchSmsResult actual = conn.fetchBatch(batchId); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchTextBatchAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.of(2016, 10, 2, 9, 34, 28, 542000000, ZoneOffset.UTC); String path = "/v1/" + spid + "/batches/" + batchId; final MtBatchSmsResult expected = MtBatchTextSmsResult.builder().sender("12345") .addRecipient("123456789", "987654321").body("Hello, world!").canceled(false).id(batchId) .createdAt(time).modifiedAt(time).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<MtBatchSmsResult> testCallback = new TestCallback<MtBatchSmsResult>() { @Override public void completed(MtBatchSmsResult result) { assertThat(result, is(expected)); } }; MtBatchSmsResult actual = conn.fetchBatchAsync(batchId, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchBinaryBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.of(2016, 10, 2, 9, 34, 28, 542000000, ZoneOffset.UTC); String path = "/v1/" + spid + "/batches/" + batchId; final MtBatchSmsResult expected = MtBatchBinarySmsResult.builder().sender("12345") .addRecipient("123456789", "987654321").body((byte) 0xf0, (byte) 0x0f).udh((byte) 0x50, (byte) 0x05) .canceled(false).id(batchId).createdAt(time).modifiedAt(time).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<MtBatchSmsResult> testCallback = new TestCallback<MtBatchSmsResult>() { @Override public void completed(MtBatchSmsResult result) { assertThat(result, is(expected)); } }; MtBatchSmsResult actual = conn.fetchBatchAsync(batchId, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canHandle404WhenFetchingBatchSync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId; wm.stubFor(get(urlEqualTo(path)).willReturn(aResponse().withStatus(404) .withHeader("Content-Type", ContentType.TEXT_PLAIN.toString()).withBody("BAD"))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { conn.fetchBatch(batchId); fail("Expected exception, got none"); } catch (NotFoundException e) { assertThat(e.getPath(), is(path)); } finally { conn.close(); } } @Test public void canHandle404WhenFetchingBatchAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); final String path = "/v1/" + spid + "/batches/" + batchId; wm.stubFor(get(urlEqualTo(path)).willReturn(aResponse().withStatus(404) .withHeader("Content-Type", ContentType.TEXT_PLAIN.toString()).withBody("BAD"))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); FutureCallback<MtBatchSmsResult> callback = new TestCallback<MtBatchSmsResult>() { @Override public void failed(Exception e) { assertThat(e, is(instanceOf(NotFoundException.class))); NotFoundException nfe = (NotFoundException) e; assertThat(nfe.getPath(), is(path)); } }; try { conn.fetchBatchAsync(batchId, callback).get(); fail("Expected exception, got none"); } catch (ExecutionException e) { assertThat(e.getCause(), is(instanceOf(NotFoundException.class))); } finally { conn.close(); } } @Test public void canHandle500WhenFetchingBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId; wm.stubFor(get(urlEqualTo(path)).willReturn(aResponse().withStatus(500) .withHeader("Content-Type", ContentType.TEXT_PLAIN.toString()).withBody("BAD"))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); /* * The exception we'll receive in the callback. Need to store it to * verify that it is the same exception as received from #get(). */ final AtomicReference<Exception> failException = new AtomicReference<Exception>(); try { /* * Used to make sure callback and test thread are agreeing about the * failException variable. */ final CountDownLatch latch = new CountDownLatch(1); FutureCallback<MtBatchSmsResult> testCallback = new TestCallback<MtBatchSmsResult>() { @Override public void failed(Exception exception) { if (!failException.compareAndSet(null, exception)) { fail("failed called multiple times"); } latch.countDown(); } }; Future<MtBatchSmsResult> future = conn.fetchBatchAsync(batchId, testCallback); // Give plenty of time for the callback to be called. latch.await(); future.get(); fail("unexpected future get success"); } catch (ExecutionException ee) { /* * The exception cause should be the same as we received in the * callback. */ assertThat(failException.get(), is(theInstance(ee.getCause()))); assertThat(ee.getCause(), is(instanceOf(UnexpectedResponseException.class))); UnexpectedResponseException ure = (UnexpectedResponseException) ee.getCause(); HttpResponse response = ure.getResponse(); assertThat(response, notNullValue()); assertThat(response.getStatusLine().getStatusCode(), is(500)); assertThat(response.getEntity().getContentType().getValue(), is(ContentType.TEXT_PLAIN.toString())); byte[] buf = new byte[100]; int read; InputStream contentStream = null; try { contentStream = response.getEntity().getContent(); read = contentStream.read(buf); } catch (IOException ioe) { throw new AssertionError("unexpected exception: " + ioe.getMessage(), ioe); } finally { if (contentStream != null) { try { contentStream.close(); } catch (IOException ioe) { throw new AssertionError("unexpected exception: " + ioe.getMessage(), ioe); } } } assertThat(read, is(3)); assertThat(Arrays.copyOf(buf, 3), is(new byte[] { 'B', 'A', 'D' })); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canCancelBatch() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); OffsetDateTime time = OffsetDateTime.of(2016, 10, 2, 9, 34, 28, 542000000, ZoneOffset.UTC); String path = "/v1/" + spid + "/batches/" + batchId; MtBatchSmsResult expected = MtBatchTextSmsResult.builder().sender("12345") .addRecipient("123456789", "987654321").body("Hello, world!").canceled(true).id(batchId) .createdAt(time).modifiedAt(time).build(); stubDeleteResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchSmsResult result = conn.cancelBatch(batchId); assertThat(result, is(expected)); } finally { conn.close(); } verifyDeleteRequest(path); } /** * Verifies that the default HTTP client actually can handle multiple * simultaneous requests. * * @throws Exception * shouldn't happen */ @Test public void canCancelBatchConcurrently() throws Exception { String spid = TestUtils.freshServicePlanId(); // Set up the first request (the one that will be delayed). MtBatchSmsResult expected1 = MtBatchTextSmsResult.builder().sender("12345") .addRecipient("123456789", "987654321").body("Hello, world!").canceled(true) .id(TestUtils.freshBatchId()).createdAt(OffsetDateTime.now()).modifiedAt(OffsetDateTime.now()) .build(); String path1 = "/v1/" + spid + "/batches/" + expected1.id(); byte[] response1 = json.writeValueAsBytes(expected1); wm.stubFor(delete(urlEqualTo(path1)).willReturn(aResponse().withFixedDelay(500) // Delay for a while. .withStatus(200).withHeader("Content-Type", "application/json; charset=UTF-8") .withBody(response1))); // Set up the second request. MtBatchSmsResult expected2 = MtBatchBinarySmsResult.builder().sender("12345") .addRecipient("123456789", "987654321").body("Hello, world!".getBytes()).udh((byte) 1) .canceled(true).id(TestUtils.freshBatchId()).createdAt(OffsetDateTime.now()) .modifiedAt(OffsetDateTime.now()).build(); String path2 = "/v1/" + spid + "/batches/" + expected2.id(); stubDeleteResponse(expected2, path2); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { final Queue<MtBatchSmsResult> results = new ConcurrentArrayQueue<MtBatchSmsResult>(); final CountDownLatch latch = new CountDownLatch(2); FutureCallback<MtBatchSmsResult> callback = new TestCallback<MtBatchSmsResult>() { @Override public void completed(MtBatchSmsResult result) { results.add(result); latch.countDown(); } }; conn.cancelBatchAsync(expected1.id(), callback); Thread.sleep(100); conn.cancelBatchAsync(expected2.id(), callback); // Wait for callback to be called. latch.await(); // We expect the second message to be handled first. assertThat(results.size(), is(2)); assertThat(results.poll(), is(expected2)); assertThat(results.poll(), is(expected1)); } finally { conn.close(); } verifyDeleteRequest(path1); verifyDeleteRequest(path2); } @Test public void canListBatchesWithEmpty() throws Exception { String spid = TestUtils.freshServicePlanId(); String path = "/v1/" + spid + "/batches?page=0"; BatchFilter filter = ClxApi.batchFilter().build(); final Page<MtBatchSmsResult> expected = PagedBatchResult.builder().page(0).size(0).totalSize(0).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Page<MtBatchSmsResult>> testCallback = new TestCallback<Page<MtBatchSmsResult>>() { @Override public void completed(Page<MtBatchSmsResult> result) { assertThat(result, is(expected)); } }; PagedFetcher<MtBatchSmsResult> fetcher = conn.fetchBatches(filter); Page<MtBatchSmsResult> actual = fetcher.fetchAsync(0, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canListBatchesWithTwoPages() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchFilter filter = ClxApi.batchFilter().build(); // Prepare first page. String path1 = "/v1/" + spid + "/batches?page=0"; final Page<MtBatchSmsResult> expected1 = PagedBatchResult.builder().page(0).size(0).totalSize(2).build(); stubGetResponse(expected1, path1); // Prepare second page. String path2 = "/v1/" + spid + "/batches?page=1"; final Page<MtBatchSmsResult> expected2 = PagedBatchResult.builder().page(1).size(0).totalSize(2).build(); stubGetResponse(expected2, path2); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Page<MtBatchSmsResult>> testCallback = new TestCallback<Page<MtBatchSmsResult>>() { @Override public void completed(Page<MtBatchSmsResult> result) { switch (result.page()) { case 0: assertThat(result, is(expected1)); break; case 1: assertThat(result, is(expected2)); break; default: fail("unexpected page: " + result); } } }; PagedFetcher<MtBatchSmsResult> fetcher = conn.fetchBatches(filter); Page<MtBatchSmsResult> actual1 = fetcher.fetchAsync(0, testCallback).get(); assertThat(actual1, is(expected1)); Page<MtBatchSmsResult> actual2 = fetcher.fetchAsync(1, testCallback).get(); assertThat(actual2, is(expected2)); } finally { conn.close(); } verifyGetRequest(path1); verifyGetRequest(path2); } @Test public void canIterateOverPages() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchFilter filter = ClxApi.batchFilter().build(); // Prepare first page. String path1 = "/v1/" + spid + "/batches?page=0"; final Page<MtBatchSmsResult> expected1 = PagedBatchResult.builder().page(0).size(1).totalSize(2).addContent( MtBatchTextSmsResult.builder().id(TestUtils.freshBatchId()).body("body").canceled(false).build()) .build(); stubGetResponse(expected1, path1); // Prepare second page. String path2 = "/v1/" + spid + "/batches?page=1"; final Page<MtBatchSmsResult> expected2 = PagedBatchResult.builder().page(1).size(2).totalSize(2) .addContent(MtBatchBinarySmsResult.builder().id(TestUtils.freshBatchId()).body((byte) 0) .udh((byte) 1).canceled(false).build()) .addContent(MtBatchTextSmsResult.builder().id(TestUtils.freshBatchId()).body("body").canceled(false) .build()) .build(); stubGetResponse(expected2, path2); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { PagedFetcher<MtBatchSmsResult> fetcher = conn.fetchBatches(filter); List<Page<MtBatchSmsResult>> actuals = new ArrayList<Page<MtBatchSmsResult>>(); for (Page<MtBatchSmsResult> result : fetcher.pages()) { actuals.add(result); } List<Page<MtBatchSmsResult>> expecteds = new ArrayList<Page<MtBatchSmsResult>>(); expecteds.add(expected1); expecteds.add(expected2); assertThat(actuals, is(expecteds)); } finally { conn.close(); } verifyGetRequest(path1); verifyGetRequest(path2); } @Test public void canIterateOverBatchesWithTwoPages() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchFilter filter = ClxApi.batchFilter().build(); // Prepare first page. String path1 = "/v1/" + spid + "/batches?page=0"; final Page<MtBatchSmsResult> expected1 = PagedBatchResult.builder().page(0).size(1).totalSize(3).addContent( MtBatchTextSmsResult.builder().id(TestUtils.freshBatchId()).body("body").canceled(false).build()) .build(); stubGetResponse(expected1, path1); // Prepare second page. String path2 = "/v1/" + spid + "/batches?page=1"; final Page<MtBatchSmsResult> expected2 = PagedBatchResult.builder().page(1).size(2).totalSize(3) .addContent(MtBatchBinarySmsResult.builder().id(TestUtils.freshBatchId()).body((byte) 0) .udh((byte) 1).canceled(false).build()) .addContent(MtBatchTextSmsResult.builder().id(TestUtils.freshBatchId()).body("body").canceled(false) .build()) .build(); stubGetResponse(expected2, path2); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { PagedFetcher<MtBatchSmsResult> fetcher = conn.fetchBatches(filter); List<MtBatchSmsResult> actuals = new ArrayList<MtBatchSmsResult>(); for (MtBatchSmsResult result : fetcher.elements()) { actuals.add(result); } List<MtBatchSmsResult> expecteds = new ArrayList<MtBatchSmsResult>(); expecteds.addAll(expected1.content()); expecteds.addAll(expected2.content()); assertThat(actuals, is(expecteds)); } finally { conn.close(); } verifyGetRequest(path1); verifyGetRequest(path2); } @Test public void canFetchDeliveryReportSync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId + "/delivery_report" + "?type=summary&status=Aborted%2CDelivered&code=200%2C300"; final BatchDeliveryReport expected = BatchDeliveryReport.builder().batchId(batchId).totalMessageCount(1010) .addStatus(BatchDeliveryReport.Status.builder().code(200).status(DeliveryStatus.ABORTED).count(10) .addRecipient("rec1", "rec2").build()) .addStatus(BatchDeliveryReport.Status.builder().code(300).status(DeliveryStatus.DELIVERED).count(20) .addRecipient("rec3", "rec4", "rec5").build()) .build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); BatchDeliveryReportParams filter = ClxApi.batchDeliveryReportParams().summaryReport() .addStatus(DeliveryStatus.ABORTED, DeliveryStatus.DELIVERED).addCode(200, 300).build(); try { BatchDeliveryReport actual = conn.fetchDeliveryReport(batchId, filter); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchDeliveryReportAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId + "/delivery_report?type=full"; final BatchDeliveryReport expected = BatchDeliveryReport.builder().batchId(batchId).totalMessageCount(1010) .addStatus(BatchDeliveryReport.Status.builder().code(200).status(DeliveryStatus.ABORTED).count(10) .addRecipient("rec1", "rec2").build()) .addStatus(BatchDeliveryReport.Status.builder().code(300).status(DeliveryStatus.DELIVERED).count(20) .addRecipient("rec3", "rec4", "rec5").build()) .build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); BatchDeliveryReportParams filter = ClxApi.batchDeliveryReportParams().fullReport().build(); try { FutureCallback<BatchDeliveryReport> testCallback = new TestCallback<BatchDeliveryReport>() { @Override public void completed(BatchDeliveryReport result) { assertThat(result, is(expected)); } }; BatchDeliveryReport actual = conn.fetchDeliveryReportAsync(batchId, filter, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchRecipientDeliveryReportSync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String recipient = "987654321"; String path = "/v1/" + spid + "/batches/" + batchId + "/delivery_report/" + recipient; final RecipientDeliveryReport expected = RecipientDeliveryReport.builder().batchId(batchId) .recipient(recipient).code(200).status(DeliveryStatus.ABORTED).statusMessage("this is the status") .operator("10101").at(OffsetDateTime.now()).operatorStatusAt(OffsetDateTime.now(Clock.systemUTC())) .build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { RecipientDeliveryReport actual = conn.fetchDeliveryReport(batchId, recipient); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchRecipientDeliveryReportAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String recipient = "987654321"; String path = "/v1/" + spid + "/batches/" + batchId + "/delivery_report/" + recipient; final RecipientDeliveryReport expected = RecipientDeliveryReport.builder().batchId(batchId) .recipient(recipient).code(200).status(DeliveryStatus.ABORTED).statusMessage("this is the status") .operator("10101").at(OffsetDateTime.now()).operatorStatusAt(OffsetDateTime.now(Clock.systemUTC())) .build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<RecipientDeliveryReport> testCallback = new TestCallback<RecipientDeliveryReport>() { @Override public void completed(RecipientDeliveryReport result) { assertThat(result, is(expected)); } }; RecipientDeliveryReport actual = conn.fetchDeliveryReportAsync(batchId, recipient, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canUpdateBatchTagsSync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId + "/tags"; TagsUpdate request = ClxApi.tagsUpdate().addTagInsertion("aTag1", "2") .addTagRemoval("rTag1", "r2").build(); Tags expected = Tags.of("tag1", "2"); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { Tags actual = conn.updateTags(batchId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canUpdateBatchTagsAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId + "/tags"; TagsUpdate request = ClxApi.tagsUpdate().addTagInsertion("aTag1", "2") .addTagRemoval("rTag1", "r2").build(); final Tags expected = Tags.of("tag1", "2"); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Tags> testCallback = new TestCallback<Tags>() { @Override public void completed(Tags result) { assertThat(result, is(expected)); } }; Tags actual = conn.updateTagsAsync(batchId, request, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canReplaceBatchTagsSync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId + "/tags"; Tags request = Tags.of("rTag1", "rTag2"); Tags expected = Tags.of("tag1", "2"); stubPutResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { Tags actual = conn.replaceTags(batchId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPutRequest(path, request); } @Test public void canReplaceBatchTagsAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId + "/tags"; Tags request = Tags.of("rTag1", "rTag2"); final Tags expected = Tags.of("tag1", "2"); stubPutResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Tags> testCallback = new TestCallback<Tags>() { @Override public void completed(Tags result) { assertThat(result, is(expected)); } }; Tags actual = conn.replaceTagsAsync(batchId, request, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPutRequest(path, request); } @Test public void canFetchBatchTagsSync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId + "/tags"; Tags expected = Tags.of("tag1", "2"); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { Tags actual = conn.fetchTags(batchId); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchBatchTagsAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); BatchId batchId = TestUtils.freshBatchId(); String path = "/v1/" + spid + "/batches/" + batchId + "/tags"; final Tags expected = Tags.of("tag1", "2"); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Tags> testCallback = new TestCallback<Tags>() { @Override public void completed(Tags result) { assertThat(result, is(expected)); } }; Tags actual = conn.fetchTagsAsync(batchId, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canCreateBatchDryRunSync() throws Exception { String spid = TestUtils.freshServicePlanId(); String path = "/v1/" + spid + "/batches/dry_run"; MtBatchSmsCreate request = ClxApi.batchTextSms().sender("1234").addRecipient("987654321") .body("Hello, world!").build(); final MtBatchDryRunResult expected = MtBatchDryRunResult.builder().numberOfRecipients(20) .numberOfMessages(200).build(); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { MtBatchDryRunResult actual = conn.createBatchDryRun(request, null, null); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canCreateBatchDryRunAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); String path = "/v1/" + spid + "/batches/dry_run" + "?per_recipient=true&number_of_recipients=5"; MtBatchSmsCreate request = ClxApi.batchTextSms().sender("1234").addRecipient("987654321") .body("Hello, world!").build(); final MtBatchDryRunResult expected = MtBatchDryRunResult.builder().numberOfRecipients(20) .numberOfMessages(200).build(); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<MtBatchDryRunResult> testCallback = new TestCallback<MtBatchDryRunResult>() { @Override public void completed(MtBatchDryRunResult result) { assertThat(result, is(expected)); } }; MtBatchDryRunResult actual = conn.createBatchDryRunAsync(request, true, 5, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canCreateGroupSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups"; GroupCreate request = ClxApi.groupCreate().name("mygroup").addMember("123456789") .addMember("987654321", "4242424242").addTag("tag1", "2") .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .build(); GroupResult expected = GroupResult.builder().id(groupId).name("mygroup").size(72) .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .createdAt(OffsetDateTime.now()).modifiedAt(OffsetDateTime.now()).build(); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { GroupResult actual = conn.createGroup(request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canCreateGroupAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups"; GroupCreate request = ClxApi.groupCreate().name("mygroup").addMember("123456789") .addMember("987654321", "4242424242").addTag("tag1", "2") .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .build(); final GroupResult expected = GroupResult.builder().id(groupId).name("mygroup").size(72) .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .createdAt(OffsetDateTime.now()).modifiedAt(OffsetDateTime.now()).build(); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<GroupResult> testCallback = new TestCallback<GroupResult>() { @Override public void completed(GroupResult result) { assertThat(result, is(expected)); } }; GroupResult actual = conn.createGroupAsync(request, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canFetchGroupSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; GroupResult expected = GroupResult.builder().id(groupId).name("mygroup").size(72) .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .createdAt(OffsetDateTime.now()).modifiedAt(OffsetDateTime.now()).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { GroupResult actual = conn.fetchGroup(groupId); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchGroupAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; final GroupResult expected = GroupResult.builder().id(groupId).name("mygroup").size(72) .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .createdAt(OffsetDateTime.now()).modifiedAt(OffsetDateTime.now()).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<GroupResult> testCallback = new TestCallback<GroupResult>() { @Override public void completed(GroupResult result) { assertThat(result, is(expected)); } }; GroupResult actual = conn.fetchGroupAsync(groupId, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchGroupMembersSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId + "/members"; Set<String> expected = new HashSet<String>(Arrays.asList("mem1", "mem2", "mem3")); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { Set<String> actual = conn.fetchGroupMembers(groupId); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchGroupMembersAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId + "/members"; final Set<String> expected = new HashSet<String>(Arrays.asList("mem1", "mem2", "mem3")); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Set<String>> testCallback = new TestCallback<Set<String>>() { @Override public void completed(Set<String> result) { assertThat(result, is(expected)); } }; Set<String> actual = conn.fetchGroupMembersAsync(groupId, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canListGroupsWithEmpty() throws Exception { String spid = TestUtils.freshServicePlanId(); String path = "/v1/" + spid + "/groups?page=0"; GroupFilter filter = ClxApi.groupFilter().build(); final Page<GroupResult> expected = PagedGroupResult.builder().page(0).size(0).totalSize(0).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Page<GroupResult>> testCallback = new TestCallback<Page<GroupResult>>() { @Override public void completed(Page<GroupResult> result) { assertThat(result, is(expected)); } }; PagedFetcher<GroupResult> fetcher = conn.fetchGroups(filter); Page<GroupResult> actual = fetcher.fetchAsync(0, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canListGroupsWithTwoPages() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupFilter filter = ClxApi.groupFilter().addTag("tag1", "2").build(); // Prepare first page. String path1 = "/v1/" + spid + "/groups?page=0&tags=tag1%2C%D1%82%D0%B0%D0%B32"; final Page<GroupResult> expected1 = PagedGroupResult.builder().page(0).size(0).totalSize(2).build(); stubGetResponse(expected1, path1); // Prepare second page. String path2 = "/v1/" + spid + "/groups?page=1&tags=tag1%2C%D1%82%D0%B0%D0%B32"; final Page<GroupResult> expected2 = PagedGroupResult.builder().page(1).size(0).totalSize(2).build(); stubGetResponse(expected2, path2); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Page<GroupResult>> testCallback = new TestCallback<Page<GroupResult>>() { @Override public void completed(Page<GroupResult> result) { switch (result.page()) { case 0: assertThat(result, is(expected1)); break; case 1: assertThat(result, is(expected2)); break; default: fail("unexpected page: " + result); } } }; PagedFetcher<GroupResult> fetcher = conn.fetchGroups(filter); Page<GroupResult> actual1 = fetcher.fetchAsync(0, testCallback).get(); assertThat(actual1, is(expected1)); Page<GroupResult> actual2 = fetcher.fetchAsync(1, testCallback).get(); assertThat(actual2, is(expected2)); } finally { conn.close(); } verifyGetRequest(path1); verifyGetRequest(path2); } @Test public void canUpdateGroupSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; GroupUpdate request = ClxApi.groupUpdate().unsetName().addMemberInsertion("123456789") .addMemberRemoval("987654321").build(); GroupResult expected = GroupResult.builder().size(72).id(groupId) .createdAt(OffsetDateTime.now(Clock.systemUTC())).modifiedAt(OffsetDateTime.now(Clock.systemUTC())) .build(); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { GroupResult actual = conn.updateGroup(groupId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canUpdateGroupAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; GroupUpdate request = ClxApi.groupUpdate().unsetName().addMemberInsertion("123456789") .addMemberRemoval("987654321").build(); final GroupResult expected = GroupResult.builder().size(72).id(groupId) .createdAt(OffsetDateTime.now(Clock.systemUTC())).modifiedAt(OffsetDateTime.now(Clock.systemUTC())) .build(); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<GroupResult> testCallback = new TestCallback<GroupResult>() { @Override public void completed(GroupResult result) { assertThat(result, is(expected)); } }; GroupResult actual = conn.updateGroupAsync(groupId, request, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canReplaceGroupSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; GroupCreate request = ClxApi.groupCreate().name("mygroup").addMember("123456789") .addMember("987654321", "4242424242").addTag("tag1", "2") .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .build(); GroupResult expected = GroupResult.builder().id(groupId).name("mygroup").size(72) .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .createdAt(OffsetDateTime.now()).modifiedAt(OffsetDateTime.now()).build(); stubPutResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { GroupResult actual = conn.replaceGroup(groupId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPutRequest(path, request); } @Test public void canReplaceGroupAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; GroupCreate request = ClxApi.groupCreate().name("mygroup").addMember("123456789") .addMember("987654321", "4242424242").addTag("tag1", "2") .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .build(); final GroupResult expected = GroupResult.builder().id(groupId).name("mygroup").size(72) .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .createdAt(OffsetDateTime.now()).modifiedAt(OffsetDateTime.now()).build(); stubPutResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<GroupResult> testCallback = new TestCallback<GroupResult>() { @Override public void completed(GroupResult result) { assertThat(result, is(expected)); } }; GroupResult actual = conn.replaceGroupAsync(groupId, request, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPutRequest(path, request); } @Test public void canDeleteGroupSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; GroupResult expected = GroupResult.builder().id(groupId).name("mygroup").size(72) .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .createdAt(OffsetDateTime.now()).modifiedAt(OffsetDateTime.now()).build(); stubDeleteResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { conn.deleteGroup(groupId); } finally { conn.close(); } verifyDeleteRequest(path); } @Test public void canDeleteGroupAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; final GroupResult expected = GroupResult.builder().id(groupId).name("mygroup").size(72) .autoUpdate(ClxApi.autoUpdate().recipient("1111").add("kw0", "kw1").remove("kw2", "kw3").build()) .createdAt(OffsetDateTime.now()).modifiedAt(OffsetDateTime.now()).build(); stubDeleteResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Void> testCallback = new TestCallback<Void>() { @Override public void completed(Void result) { assertThat(result, is(nullValue())); } }; conn.deleteGroupAsync(groupId, testCallback).get(); } finally { conn.close(); } verifyDeleteRequest(path); } @Test public void canHandle400WhenDeletingGroupSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; ApiError apiError = ApiError.of("syntax_constraint_violation", "The syntax constraint was violated"); wm.stubFor(delete(urlEqualTo(path)).willReturn(aResponse().withStatus(400) .withHeader("Content-Type", "application/json").withBody(json.writeValueAsBytes(apiError)))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { conn.deleteGroup(groupId); fail("Expected exception, got none"); } catch (ErrorResponseException e) { assertThat(e.getCode(), is(apiError.code())); assertThat(e.getText(), is(apiError.text())); } finally { conn.close(); } verifyDeleteRequest(path); } @Test public void canHandle401WhenDeletingGroupSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; wm.stubFor(delete(urlEqualTo(path)).willReturn( aResponse().withStatus(401).withHeader("Content-Type", ContentType.TEXT_PLAIN.toString()))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { conn.deleteGroup(groupId); fail("Expected exception, got none"); } catch (UnauthorizedException e) { // This exception is expected. } finally { conn.close(); } verifyDeleteRequest(path); } @Test public void canHandle401WhenDeletingGroupAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); final String path = "/v1/" + spid + "/groups/" + groupId; wm.stubFor(delete(urlEqualTo(path)).willReturn( aResponse().withStatus(401).withHeader("Content-Type", ContentType.TEXT_PLAIN.toString()))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); FutureCallback<Void> callback = new TestCallback<Void>() { @Override public void failed(Exception e) { assertThat(e, is(instanceOf(UnauthorizedException.class))); } }; try { conn.deleteGroupAsync(groupId, callback).get(); fail("Expected exception, got none"); } catch (ExecutionException e) { assertThat(e.getCause(), is(instanceOf(UnauthorizedException.class))); } finally { conn.close(); } verifyDeleteRequest(path); } @Test public void canHandle404WhenDeletingGroupSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; wm.stubFor(delete(urlEqualTo(path)).willReturn( aResponse().withStatus(404).withHeader("Content-Type", ContentType.TEXT_PLAIN.toString()))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { conn.deleteGroup(groupId); fail("Expected exception, got none"); } catch (NotFoundException e) { assertThat(e.getPath(), is(path)); } finally { conn.close(); } verifyDeleteRequest(path); } @Test public void canHandle404WhenDeletingGroupAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); final String path = "/v1/" + spid + "/groups/" + groupId; wm.stubFor(delete(urlEqualTo(path)).willReturn( aResponse().withStatus(404).withHeader("Content-Type", ContentType.TEXT_PLAIN.toString()))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); FutureCallback<Void> callback = new TestCallback<Void>() { @Override public void failed(Exception e) { assertThat(e, is(instanceOf(NotFoundException.class))); NotFoundException nfe = (NotFoundException) e; assertThat(nfe.getPath(), is(path)); } }; try { conn.deleteGroupAsync(groupId, callback).get(); fail("Expected exception, got none"); } catch (ExecutionException e) { assertThat(e.getCause(), is(instanceOf(NotFoundException.class))); } finally { conn.close(); } verifyDeleteRequest(path); } @Test public void canHandle500WhenDeletingGroup() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId; wm.stubFor(delete(urlEqualTo(path)).willReturn(aResponse().withStatus(500) .withHeader("Content-Type", ContentType.TEXT_PLAIN.toString()).withBody("BAD"))); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); /* * The exception we'll receive in the callback. Need to store it to * verify that it is the same exception as received from #get(). */ final AtomicReference<Exception> failException = new AtomicReference<Exception>(); try { /* * Used to make sure callback and test thread are agreeing about the * failException variable. */ final CountDownLatch latch = new CountDownLatch(1); FutureCallback<Void> testCallback = new TestCallback<Void>() { @Override public void failed(Exception exception) { if (!failException.compareAndSet(null, exception)) { fail("failed called multiple times"); } latch.countDown(); } }; Future<Void> future = conn.deleteGroupAsync(groupId, testCallback); // Give plenty of time for the callback to be called. latch.await(); future.get(); fail("unexpected future get success"); } catch (ExecutionException ee) { /* * The exception cause should be the same as we received in the * callback. */ assertThat(failException.get(), is(theInstance(ee.getCause()))); assertThat(ee.getCause(), is(instanceOf(UnexpectedResponseException.class))); UnexpectedResponseException ure = (UnexpectedResponseException) ee.getCause(); HttpResponse response = ure.getResponse(); assertThat(response, notNullValue()); assertThat(response.getStatusLine().getStatusCode(), is(500)); assertThat(response.getEntity().getContentType().getValue(), is(ContentType.TEXT_PLAIN.toString())); byte[] buf = new byte[100]; int read; InputStream contentStream = null; try { contentStream = response.getEntity().getContent(); read = contentStream.read(buf); } catch (IOException ioe) { throw new AssertionError("unexpected exception: " + ioe.getMessage(), ioe); } finally { if (contentStream != null) { try { contentStream.close(); } catch (IOException ioe) { throw new AssertionError("unexpected exception: " + ioe.getMessage(), ioe); } } } assertThat(read, is(3)); assertThat(Arrays.copyOf(buf, 3), is(new byte[] { 'B', 'A', 'D' })); } finally { conn.close(); } verifyDeleteRequest(path); } @Test public void canUpdateGroupTagsSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId + "/tags"; TagsUpdate request = ClxApi.tagsUpdate().addTagInsertion("aTag1", "2") .addTagRemoval("rTag1", "r2").build(); Tags expected = Tags.of("tag1", "2"); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { Tags actual = conn.updateTags(groupId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canUpdateGroupTagsAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId + "/tags"; TagsUpdate request = ClxApi.tagsUpdate().addTagInsertion("aTag1", "2") .addTagRemoval("rTag1", "r2").build(); final Tags expected = Tags.of("tag1", "2"); stubPostResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Tags> testCallback = new TestCallback<Tags>() { @Override public void completed(Tags result) { assertThat(result, is(expected)); } }; Tags actual = conn.updateTagsAsync(groupId, request, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPostRequest(path, request); } @Test public void canReplaceGroupTagsSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId + "/tags"; Tags request = Tags.of("rTag1", "rTag2"); Tags expected = Tags.of("tag1", "2"); stubPutResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { Tags actual = conn.replaceTags(groupId, request); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPutRequest(path, request); } @Test public void canReplaceGroupTagsAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId + "/tags"; Tags request = Tags.of("rTag1", "rTag2"); final Tags expected = Tags.of("tag1", "2"); stubPutResponse(expected, path, 200); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("toktok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Tags> testCallback = new TestCallback<Tags>() { @Override public void completed(Tags result) { assertThat(result, is(expected)); } }; Tags actual = conn.replaceTagsAsync(groupId, request, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyPutRequest(path, request); } @Test public void canFetchGroupTagsSync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId + "/tags"; Tags expected = Tags.of("tag1", "2"); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { Tags actual = conn.fetchTags(groupId); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchGroupTagsAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); GroupId groupId = TestUtils.freshGroupId(); String path = "/v1/" + spid + "/groups/" + groupId + "/tags"; final Tags expected = Tags.of("tag1", "2"); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Tags> testCallback = new TestCallback<Tags>() { @Override public void completed(Tags result) { assertThat(result, is(expected)); } }; Tags actual = conn.fetchTagsAsync(groupId, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canListInboundsWithEmpty() throws Exception { String spid = TestUtils.freshServicePlanId(); String path = "/v1/" + spid + "/inbounds?page=0"; InboundsFilter filter = ClxApi.inboundsFilter().build(); final Page<MoSms> expected = PagedInboundsResult.builder().page(0).size(0).totalSize(0).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Page<MoSms>> testCallback = new TestCallback<Page<MoSms>>() { @Override public void completed(Page<MoSms> result) { assertThat(result, is(expected)); } }; PagedFetcher<MoSms> fetcher = conn.fetchInbounds(filter); Page<MoSms> actual = fetcher.fetchAsync(0, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canListInboundsWithTwoPages() throws Exception { String spid = TestUtils.freshServicePlanId(); InboundsFilter filter = ClxApi.inboundsFilter().addRecipient("10101").build(); String inboundsId1 = TestUtils.freshSmsId(); String inboundsId2 = TestUtils.freshSmsId(); OffsetDateTime time1 = OffsetDateTime.now(Clock.systemUTC()); OffsetDateTime time2 = OffsetDateTime.now(Clock.systemUTC()); // Prepare first page. String path1 = "/v1/" + spid + "/inbounds?page=0&to=10101"; final Page<MoSms> expected1 = PagedInboundsResult .builder().page(0).size(1).totalSize(2).addContent(MoTextSms.builder().sender("987654321") .recipient("54321").id(inboundsId1).receivedAt(time1).sentAt(time2).body("body1").build()) .build(); stubGetResponse(expected1, path1); // Prepare second page. String path2 = "/v1/" + spid + "/inbounds?page=1&to=10101"; final Page<MoSms> expected2 = PagedInboundsResult.builder().page(1).size(1).totalSize(2) .addContent(MoBinarySms.builder().sender("123456789").recipient("12345").id(inboundsId2) .receivedAt(time2).sentAt(time1).body("body2".getBytes(TestUtils.US_ASCII)) .udh("udh".getBytes(TestUtils.US_ASCII)).build()) .build(); stubGetResponse(expected2, path2); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<Page<MoSms>> testCallback = new TestCallback<Page<MoSms>>() { @Override public void completed(Page<MoSms> result) { switch (result.page()) { case 0: assertThat(result, is(expected1)); break; case 1: assertThat(result, is(expected2)); break; default: fail("unexpected page: " + result); } } }; PagedFetcher<MoSms> fetcher = conn.fetchInbounds(filter); Page<MoSms> actual1 = fetcher.fetchAsync(0, testCallback).get(); assertThat(actual1, is(expected1)); Page<MoSms> actual2 = fetcher.fetchAsync(1, testCallback).get(); assertThat(actual2, is(expected2)); } finally { conn.close(); } verifyGetRequest(path1); verifyGetRequest(path2); } @Test public void canFetchInboundSync() throws Exception { String spid = TestUtils.freshServicePlanId(); String smsId = TestUtils.freshSmsId(); String inboundsId = TestUtils.freshSmsId(); OffsetDateTime time1 = OffsetDateTime.now(Clock.systemUTC()); OffsetDateTime time2 = OffsetDateTime.now(Clock.systemUTC()); String path = "/v1/" + spid + "/inbounds/" + smsId; MoSms expected = MoBinarySms.builder().sender("123456789").recipient("12345").id(inboundsId) .receivedAt(time1).sentAt(time2).body("body2".getBytes(TestUtils.US_ASCII)) .udh("udh".getBytes(TestUtils.US_ASCII)).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { MoSms actual = conn.fetchInbound(smsId); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } @Test public void canFetchInboundAsync() throws Exception { String spid = TestUtils.freshServicePlanId(); String smsId = TestUtils.freshSmsId(); String inboundsId = TestUtils.freshSmsId(); OffsetDateTime time1 = OffsetDateTime.now(Clock.systemUTC()); OffsetDateTime time2 = OffsetDateTime.now(Clock.systemUTC()); String path = "/v1/" + spid + "/inbounds/" + smsId; final MoSms expected = MoBinarySms.builder().sender("123456789").recipient("12345").id(inboundsId) .receivedAt(time1).sentAt(time2).body("body2".getBytes(TestUtils.US_ASCII)) .udh("udh".getBytes(TestUtils.US_ASCII)).build(); stubGetResponse(expected, path); ApiConnection conn = ApiConnection.builder().servicePlanId(spid).token("tok") .endpoint("http://localhost:" + wm.port()).start(); try { FutureCallback<MoSms> testCallback = new TestCallback<MoSms>() { @Override public void completed(MoSms result) { assertThat(result, is(expected)); } }; MoSms actual = conn.fetchInboundAsync(smsId, testCallback).get(); assertThat(actual, is(expected)); } finally { conn.close(); } verifyGetRequest(path); } /** * Helper that sets up WireMock to respond to a GET using a JSON body. * * @param response * the response to give, serialized to JSON * @param path * the path on which to listen * @param status * the response HTTP status * @throws JsonProcessingException * if the given response object could not be serialized */ private void stubGetResponse(Object response, String path) throws JsonProcessingException { byte[] body = json.writeValueAsBytes(response); wm.stubFor(get(urlEqualTo(path)).willReturn( aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(body))); } /** * Helper that sets up WireMock to verify a GET request. * * @param path * the request path to match */ private void verifyGetRequest(String path) { wm.verify(getRequestedFor(urlEqualTo(path)).withHeader("Accept", equalTo("application/json; charset=UTF-8")) .withHeader("Authorization", equalTo("Bearer tok"))); } /** * Helper that sets up WireMock to respond to a DELETE using a JSON body. * * @param response * the response to give, serialized to JSON * @param path * the path on which to listen * @param status * the response HTTP status * @throws JsonProcessingException * if the given response object could not be serialized */ private void stubDeleteResponse(Object response, String path) throws JsonProcessingException { byte[] body = json.writeValueAsBytes(response); wm.stubFor(delete(urlEqualTo(path)).willReturn( aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(body))); } /** * Helper that sets up WireMock to verify a DELETE request. * * @param path * the request path to match */ private void verifyDeleteRequest(String path) { wm.verify(deleteRequestedFor(urlEqualTo(path)) .withHeader("Accept", equalTo("application/json; charset=UTF-8")) .withHeader("Authorization", equalTo("Bearer tok"))); } /** * Helper that sets up WireMock to respond to a POST using a JSON body. * * @param response * the response to give, serialized to JSON * @param path * the path on which to listen * @param status * the response HTTP status * @throws JsonProcessingException * if the given response object could not be serialized */ private void stubPostResponse(Object response, String path, int status) throws JsonProcessingException { byte[] body = json.writeValueAsBytes(response); wm.stubFor(post(urlEqualTo(path)).willReturn( aResponse().withStatus(status).withHeader("Content-Type", "application/json").withBody(body))); } /** * Helper that sets up WireMock to verify that a request matches a given * object in JSON format. * * @param path * the request path to match * @param request * the request object whose JSON serialization should match * @throws JsonProcessingException * if the given request object could not be serialized */ private void verifyPostRequest(String path, Object request) throws JsonProcessingException { String expectedRequest = json.writeValueAsString(request); wm.verify(postRequestedFor(urlEqualTo(path)).withRequestBody(equalToJson(expectedRequest)) .withHeader("Content-Type", matching("application/json; charset=UTF-8")) .withHeader("Accept", equalTo("application/json; charset=UTF-8")) .withHeader("Authorization", equalTo("Bearer toktok"))); } /** * Helper that sets up WireMock to respond to a POST using a JSON body. * * @param response * the response to give, serialized to JSON * @param path * the path on which to listen * @param status * the response HTTP status * @throws JsonProcessingException * if the given response object could not be serialized */ private void stubPutResponse(Object response, String path, int status) throws JsonProcessingException { byte[] body = json.writeValueAsBytes(response); wm.stubFor(put(urlEqualTo(path)).willReturn( aResponse().withStatus(status).withHeader("Content-Type", "application/json").withBody(body))); } /** * Helper that sets up WireMock to verify that a request matches a given * object in JSON format. * * @param path * the request path to match * @param request * the request object whose JSON serialization should match * @throws JsonProcessingException * if the given request object could not be serialized */ private void verifyPutRequest(String path, Object request) throws JsonProcessingException { String expectedRequest = json.writeValueAsString(request); wm.verify(putRequestedFor(urlEqualTo(path)).withRequestBody(equalToJson(expectedRequest)) .withHeader("Content-Type", matching("application/json; charset=UTF-8")) .withHeader("Accept", equalTo("application/json; charset=UTF-8")) .withHeader("Authorization", equalTo("Bearer toktok"))); } }