Skip to content

Commit

Permalink
Merge pull request #12 from sailthru/LT-1142-m-particle-outgoing-api-…
Browse files Browse the repository at this point in the history
…url-should-be-stored-alongside-the-key-and-secret

[LT-1142] Use API URL if provided.
  • Loading branch information
jc-sailthru authored Jul 22, 2024
2 parents 8eb3ca6 + 7e5a49c commit 82cffe1
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 12 deletions.
14 changes: 14 additions & 0 deletions src/main/java/com/sailthru/sqs/ApiFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sailthru.sqs;

import com.mparticle.ApiClient;
import com.mparticle.client.EventsApi;

public class ApiFactory {
public EventsApi create(final String apiKey, final String apiSecret, final String apiURL) {
final ApiClient apiClient = new ApiClient(apiKey, apiSecret);

apiClient.getAdapterBuilder().baseUrl(apiURL);

return apiClient.createService(EventsApi.class);
}
}
25 changes: 17 additions & 8 deletions src/main/java/com/sailthru/sqs/MParticleClient.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.sailthru.sqs;

import com.mparticle.ApiClient;
import com.mparticle.client.EventsApi;
import com.mparticle.model.Batch;
import com.mparticle.model.CustomEvent;
Expand All @@ -16,20 +15,24 @@
import java.io.IOException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.util.Optional;

import static java.lang.String.format;
import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
import static java.util.Optional.ofNullable;
import static java.util.function.Predicate.not;

public class MParticleClient {
private static final Logger LOGGER = LoggerFactory.getLogger(MessageProcessor.class);
private static final String BASE_URL = "https://inbound.mparticle.com/s2s/v2/";
static final String DEFAULT_BASE_URL = "https://inbound.mparticle.com/s2s/v2/";

private ApiFactory apiFactory;

public void submit(final MParticleOutgoingMessage message) throws RetryLaterException {

final Batch batch = prepareBatch(message);

final EventsApi eventsApi = getEventsApi(message.getAuthenticationKey(), message.getAuthenticationSecret());
final EventsApi eventsApi = getEventsApi(message);

LOGGER.debug("Attempting to send batch: {} for message: {}", batch, message);

Expand All @@ -50,12 +53,14 @@ public void submit(final MParticleOutgoingMessage message) throws RetryLaterExce
}
}

private EventsApi getEventsApi(final String apiKey, final String apiSecret) {
final ApiClient apiClient = new ApiClient(apiKey, apiSecret);

apiClient.getAdapterBuilder().baseUrl(BASE_URL);
private EventsApi getEventsApi(final MParticleOutgoingMessage message) {
final String apiKey = message.getAuthenticationKey();
final String apiSecret = message.getAuthenticationSecret();
final String apiURL = Optional.ofNullable(message.getApiURL())
.filter(not(String::isEmpty))
.orElse(DEFAULT_BASE_URL);

return apiClient.createService(EventsApi.class);
return apiFactory.create(apiKey, apiSecret, apiURL);
}

private Batch prepareBatch(final MParticleOutgoingMessage message) {
Expand Down Expand Up @@ -92,4 +97,8 @@ private Long parseTimestamp(String timestamp) {
}
return null;
}

public void setApiFactory(final ApiFactory apiFactory) {
this.apiFactory = apiFactory;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class MParticleOutgoingMessage {
private String profileEmail;
private List<Event> events;
private String timestamp;
private String apiURL;

public String getAuthenticationKey() {
return authenticationKey;
Expand Down Expand Up @@ -53,6 +54,14 @@ public String getTimestamp() {
return timestamp;
}

public String getApiURL() {
return apiURL;
}

public void setApiURL(String apiURL) {
this.apiURL = apiURL;
}

@JsonIgnoreProperties(ignoreUnknown = true)
public static class Event {
private MParticleEventName eventName;
Expand Down
162 changes: 162 additions & 0 deletions src/test/java/com/sailthru/sqs/MParticleClientTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package com.sailthru.sqs;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mparticle.client.EventsApi;
import com.mparticle.model.Batch;
import com.mparticle.model.CustomEvent;
import com.mparticle.model.CustomEventData;
import com.mparticle.model.UserIdentities;
import com.sailthru.sqs.exception.RetryLaterException;
import com.sailthru.sqs.message.MParticleEventName;
import com.sailthru.sqs.message.MParticleOutgoingMessage;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import retrofit2.Call;
import retrofit2.Response;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;

import static com.mparticle.model.CustomEvent.EventTypeEnum.CUSTOM_EVENT;
import static com.sailthru.sqs.MParticleClient.DEFAULT_BASE_URL;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class MParticleClientTest {
@InjectMocks
private MParticleClient testInstance;

@Mock
private ApiFactory mockApiFactory;

@Mock
private EventsApi mockEventsApi;

@Captor
private ArgumentCaptor<Batch> batchCaptor;

@Mock
private Call<Void> mockCall;

@Mock
private Response<Void> mockResponse;

@BeforeEach
void setUp() throws Exception {
when(mockApiFactory.create(anyString(), anyString(), anyString())).thenReturn(mockEventsApi);
when(mockEventsApi.uploadEvents(any(Batch.class))).thenReturn(mockCall);
when(mockCall.execute()).thenReturn(mockResponse);
}

@Test
void givenUnsuccessfulResponseThenRetryLaterExceptionIsThrown() throws Exception {
final MParticleOutgoingMessage validMessageWithURL = givenValidMessage("/messages/valid.json");

when(mockResponse.isSuccessful()).thenReturn(false);

assertThrows(RetryLaterException.class, () -> testInstance.submit(validMessageWithURL));
}

@Test
void givenIOExceptionThenRetryLaterExceptionIsThrown() throws Exception {
final MParticleOutgoingMessage validMessageWithURL = givenValidMessage("/messages/valid.json");

when(mockCall.execute()).thenThrow(IOException.class);

assertThrows(RetryLaterException.class, () -> testInstance.submit(validMessageWithURL));
}

@Test
void givenValidMessageTheCorrectBatchIsSent() throws Exception {
final MParticleOutgoingMessage validMessageWithURL = givenValidMessage("/messages/valid.json");

testInstance.submit(validMessageWithURL);

verify(mockEventsApi).uploadEvents(batchCaptor.capture());

final Batch result = batchCaptor.getValue();
final UserIdentities userIdentities = result.getUserIdentities();
assertThat(userIdentities.getEmail(), is(equalTo("[email protected]")));
assertThat(result.getEvents().size(), is(2));

final CustomEvent event1 = (CustomEvent) result.getEvents().get(0);
assertThat(event1.getEventType(), is(CUSTOM_EVENT));

final CustomEventData event1Data = event1.getData();
assertThat(event1Data.getEventName(), is(equalTo(MParticleEventName.EMAIL_SUBSCRIBE.name())));
assertThat(event1Data.getCustomEventType(), is(equalTo(CustomEventData.CustomEventType.OTHER)));

final Map<String, String> customAttributes1 = event1Data.getCustomAttributes();
assertThat(customAttributes1, hasEntry("client_id", "3386"));
assertThat(customAttributes1, hasEntry("profile_id", "6634e1bd31a2a0e8af0b0dff"));
assertThat(customAttributes1, hasEntry("list_id", "5609b2641aa312d6318b456b"));

final CustomEvent event2 = (CustomEvent) result.getEvents().get(1);
assertThat(event2.getEventType(), is(CUSTOM_EVENT));

final CustomEventData event2Data = event2.getData();
assertThat(event2Data.getEventName(), is(equalTo(MParticleEventName.EMAIL_UNSUBSCRIBE.name())));
assertThat(event2Data.getCustomEventType(), is(equalTo(CustomEventData.CustomEventType.OTHER)));

final Map<String, String> customAttributes2 = event2Data.getCustomAttributes();
assertThat(customAttributes2, hasEntry("client_id", "3386"));
assertThat(customAttributes2, hasEntry("profile_id", "7634e1bd31a2a0e8af0b0dfg"));
assertThat(customAttributes2, hasEntry("list_id", "6609b2641aa312d6318b456d"));
}

@Test
void givenApiURLIsProvidedInMessageThenItIsUsed() throws Exception {
final MParticleOutgoingMessage validMessageWithURL = givenValidMessage("/messages/valid.json");

testInstance.submit(validMessageWithURL);

verify(mockApiFactory).create("test_key", "test_secret", "https://test_url.com");
}

@Test
void givenApiURLIsNullInMessageThenDefaultIsUsed() throws Exception {
final MParticleOutgoingMessage validMessage = givenValidMessage("/messages/validWithoutURL.json");

testInstance.submit(validMessage);

verify(mockApiFactory).create("test_key", "test_secret", DEFAULT_BASE_URL);
}

@Test
void givenApiURLIsMissingThenDefaultIsUsed() throws Exception {
final MParticleOutgoingMessage validMessage = givenValidMessage("/messages/validWithoutURL2.json");

testInstance.submit(validMessage);

verify(mockApiFactory).create("test_key", "test_secret", DEFAULT_BASE_URL);
}

private MParticleOutgoingMessage givenValidMessage(final String filePath) throws Exception {
lenient().when(mockResponse.isSuccessful()).thenReturn(true);

final String json = loadResourceFileContent(filePath);
return new ObjectMapper().readValue(json, MParticleOutgoingMessage.class);
}

private String loadResourceFileContent(final String path) throws IOException, URISyntaxException {
return Files.readString(Paths.get(getClass().getResource(path).toURI()));
}
}
2 changes: 1 addition & 1 deletion src/test/java/com/sailthru/sqs/MessageProcessorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ void givenValidPayloadProvidedThenMessageSubmitted() throws Exception {
assertThat(event1.getEventType(), is(equalTo(MParticleEventType.OTHER)));

final MParticleOutgoingMessage.Event event2 = message.getEvents().get(1);
assertThat(event2.getEventName(), is(MParticleEventName.EMAIL_SUBSCRIBE));
assertThat(event2.getEventName(), is(MParticleEventName.EMAIL_UNSUBSCRIBE));
assertThat(event2.getEventType(), is(equalTo(MParticleEventType.OTHER)));
}

Expand Down
1 change: 1 addition & 0 deletions src/test/resources/messages/invalid1.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"authenticationKey": "",
"authenticationSecret": "test_secret",
"apiURL": null,
"profileEmail": "[email protected]",
"events": [
{
Expand Down
1 change: 1 addition & 0 deletions src/test/resources/messages/invalid2.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"authenticationKey": "test_key",
"authenticationSecret": "",
"apiURL": "https://test_url.com",
"profileEmail": "[email protected]",
"events": [
{
Expand Down
7 changes: 4 additions & 3 deletions src/test/resources/messages/valid.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"authenticationKey": "test_key",
"authenticationSecret": "test_secret",
"apiURL": "https://test_url.com",
"profileEmail": "[email protected]",
"events": [
{
Expand All @@ -13,12 +14,12 @@
}
},
{
"eventName": "EMAIL_SUBSCRIBE",
"eventName": "EMAIL_UNSUBSCRIBE",
"eventType": "OTHER",
"additionalData": {
"client_id": "3386",
"profile_id": "6634e1bd31a2a0e8af0b0dff",
"list_id": "5609b2641aa312d6318b456c"
"profile_id": "7634e1bd31a2a0e8af0b0dfg",
"list_id": "6609b2641aa312d6318b456d"
}
}
],
Expand Down
27 changes: 27 additions & 0 deletions src/test/resources/messages/validWithoutURL.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"authenticationKey": "test_key",
"authenticationSecret": "test_secret",
"apiURL": null,
"profileEmail": "[email protected]",
"events": [
{
"eventName": "EMAIL_SUBSCRIBE",
"eventType": "OTHER",
"additionalData": {
"client_id": "3386",
"profile_id": "6634e1bd31a2a0e8af0b0dff",
"list_id": "5609b2641aa312d6318b456b"
}
},
{
"eventName": "EMAIL_SUBSCRIBE",
"eventType": "OTHER",
"additionalData": {
"client_id": "3386",
"profile_id": "6634e1bd31a2a0e8af0b0dff",
"list_id": "5609b2641aa312d6318b456c"
}
}
],
"timestamp": "2024-05-03T13:11:17Z[UTC]"
}
26 changes: 26 additions & 0 deletions src/test/resources/messages/validWithoutURL2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"authenticationKey": "test_key",
"authenticationSecret": "test_secret",
"profileEmail": "[email protected]",
"events": [
{
"eventName": "EMAIL_SUBSCRIBE",
"eventType": "OTHER",
"additionalData": {
"client_id": "3386",
"profile_id": "6634e1bd31a2a0e8af0b0dff",
"list_id": "5609b2641aa312d6318b456b"
}
},
{
"eventName": "EMAIL_SUBSCRIBE",
"eventType": "OTHER",
"additionalData": {
"client_id": "3386",
"profile_id": "6634e1bd31a2a0e8af0b0dff",
"list_id": "5609b2641aa312d6318b456c"
}
}
],
"timestamp": "2024-05-03T13:11:17Z[UTC]"
}

0 comments on commit 82cffe1

Please sign in to comment.