Skip to content

Commit

Permalink
Merge pull request #8 from button/merchant-library-traffic-shaping-up…
Browse files Browse the repository at this point in the history
…date

Add Application ID to all API requests
  • Loading branch information
madebymozart committed Mar 29, 2019
2 parents c1e5247 + 1eb8e67 commit b0ac0fd
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 26 deletions.
Expand Up @@ -62,7 +62,7 @@ final class ButtonApiImpl implements ButtonApi {
private final String userAgent;

@VisibleForTesting
String baseUrl = "https://api.usebutton.com";
String baseUrl = "https://%s.mobileapi.usebutton.com";

private static ButtonApi buttonApi;

Expand Down Expand Up @@ -96,7 +96,7 @@ public PostInstallLink getPendingLink(String applicationId, String ifa,
requestBody.put("signals", new JSONObject(signalsMap));

// setup url connection
final URL url = new URL(baseUrl + "/v1/web/deferred-deeplink");
final URL url = new URL(getBaseUrl(applicationId) + "/v1/web/deferred-deeplink");
urlConnection = (HttpURLConnection) url.openConnection();
initializeUrlConnection(urlConnection);

Expand Down Expand Up @@ -193,7 +193,7 @@ public Void postActivity(String applicationId, String sourceToken, String timest
requestBody.put("source", "merchant-library");

// setup url connection
final URL url = new URL(baseUrl + "/v1/activity/order");
final URL url = new URL(getBaseUrl(applicationId) + "/v1/activity/order");
urlConnection = (HttpURLConnection) url.openConnection();
initializeUrlConnection(urlConnection);

Expand Down Expand Up @@ -251,6 +251,10 @@ public Void postActivity(String applicationId, String sourceToken, String timest
return null;
}

String getBaseUrl(String applicationId) {
return String.format(baseUrl, applicationId);
}

private String getUserAgent() {
return userAgent;
}
Expand Down
Expand Up @@ -30,6 +30,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;

import com.usebutton.merchant.exception.ApplicationIdNotFoundException;

Expand All @@ -41,9 +42,10 @@
* The methods in the ButtonInternalImpl class should never be public because it will only be used
* by {@link ButtonMerchant}.
*/

final class ButtonInternalImpl implements ButtonInternal {

private static final String TAG = ButtonInternal.class.getSimpleName();

/**
* A list of {@link ButtonMerchant.AttributionTokenListener}. All listeners will be notified of
* a token change via the
Expand All @@ -66,6 +68,15 @@ final class ButtonInternalImpl implements ButtonInternal {
}

public void configure(ButtonRepository buttonRepository, String applicationId) {
final boolean isApplicationIdValid = ButtonUtil.isApplicationIdValid(applicationId);
if (!isApplicationIdValid) {
final String errorMessage = String.format(
"Button App ID '%s' is not valid. You can find your App ID in the dashboard by"
+ " logging in at https://app.usebutton.com/", applicationId);
Log.e(TAG, errorMessage);
return;
}

buttonRepository.setApplicationId(applicationId);
}

Expand Down
@@ -0,0 +1,47 @@
/*
* ButtonUtil.java
*
* Copyright (c) 2019 Button, Inc. (https://usebutton.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

package com.usebutton.merchant;

/**
* Utility Class
*/
class ButtonUtil {
/**
* Valid App Id Regex
*/
private static final String APP_ID_REGEX = "^app-[0-9a-zA-Z-]+$";

/**
* Validates the string against the {@link ButtonUtil#APP_ID_REGEX} to verify if it is a valid
* Application ID
*
* @param appId String to be validated.
* @return True if string is not empty and is a valid application id, otherwise false.
*/
static boolean isApplicationIdValid(String appId) {
return appId.matches(APP_ID_REGEX);
}
}
Expand Up @@ -40,6 +40,7 @@

import java.util.Collections;

import static com.usebutton.merchant.TestHelper.APPLICATION_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

Expand Down Expand Up @@ -81,7 +82,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
});

PostInstallLink postInstallLink =
buttonApi.getPendingLink("valid_application_id", "valid_ifa",
buttonApi.getPendingLink(APPLICATION_ID, "valid_ifa",
true, Collections.<String, String>emptyMap());

assertNotNull(postInstallLink);
Expand All @@ -98,7 +99,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
public void getPendingLink_validateRequest() throws Exception {
server.enqueue(new MockResponse().setResponseCode(200).setBody("{}"));

buttonApi.getPendingLink("valid_application_id", "valid_ifa",
buttonApi.getPendingLink(APPLICATION_ID, "valid_ifa",
true, Collections.singletonMap("key", "value"));

RecordedRequest recordedRequest = server.takeRequest();
Expand All @@ -116,7 +117,7 @@ public void getPendingLink_validateRequest() throws Exception {
assertEquals("application/json", headers.get("Content-Type"));

// request body
assertEquals("valid_application_id", bodyJson.getString("application_id"));
assertEquals(APPLICATION_ID, bodyJson.getString("application_id"));
assertEquals("valid_ifa", bodyJson.getString("ifa"));
assertEquals(true, bodyJson.getBoolean("ifa_limited"));
assertEquals("value", signalsJson.getString("key"));
Expand All @@ -132,7 +133,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
}
});

buttonApi.getPendingLink("valid_application_id", "valid_ifa", true,
buttonApi.getPendingLink(APPLICATION_ID, "valid_ifa", true,
Collections.<String, String>emptyMap());
}

Expand All @@ -151,15 +152,15 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
}
});

buttonApi.getPendingLink("valid_application_id", "valid_ifa", true,
buttonApi.getPendingLink(APPLICATION_ID, "valid_ifa", true,
Collections.<String, String>emptyMap());
}

@Test(expected = ButtonNetworkException.class)
public void getPendingLink_invalidUrl_catchException() throws Exception {
buttonApi.baseUrl = "invalid_url";

buttonApi.getPendingLink("valid_application_id", "valid_ifa", true,
buttonApi.getPendingLink(APPLICATION_ID, "valid_ifa", true,
Collections.<String, String>emptyMap());
}

Expand All @@ -174,7 +175,7 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
});

Order order = new Order.Builder("123").setCurrencyCode("AUG").build();
buttonApi.postActivity("valid_application_id", "valid_aid", "valid_ts", order);
buttonApi.postActivity(APPLICATION_ID, "valid_aid", "valid_ts", order);
}

@Test(expected = ButtonNetworkException.class)
Expand All @@ -194,15 +195,15 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
});

Order order = new Order.Builder("123").setCurrencyCode("AUG").build();
buttonApi.postActivity("valid_application_id", "valid_aid", "valid_ts", order);
buttonApi.postActivity(APPLICATION_ID, "valid_aid", "valid_ts", order);
}

@Test(expected = ButtonNetworkException.class)
public void postUserActivity_invalidUrl_catchException() throws Exception {
buttonApi.baseUrl = "invalid_url";

Order order = new Order.Builder("123").setCurrencyCode("AUG").build();
buttonApi.postActivity("valid_application_id", "valid_aid", "valid_ts", order);
buttonApi.postActivity(APPLICATION_ID, "valid_aid", "valid_ts", order);
}

@Test
Expand All @@ -211,7 +212,7 @@ public void postUserActivity_validateRequest() throws Exception {
.setBody("{\"meta\":{\"status\":\"ok\"}}\n"));

Order order = new Order.Builder("123").setAmount(999).setCurrencyCode("AUG").build();
buttonApi.postActivity("valid_application_id", "valid_aid", "valid_ts", order);
buttonApi.postActivity(APPLICATION_ID, "valid_aid", "valid_ts", order);

RecordedRequest recordedRequest = server.takeRequest();
Headers headers = recordedRequest.getHeaders();
Expand All @@ -226,7 +227,7 @@ public void postUserActivity_validateRequest() throws Exception {
assertEquals("application/json", headers.get("Content-Type"));

// request body
assertEquals("valid_application_id", requestBodyJson.getString("app_id"));
assertEquals(APPLICATION_ID, requestBodyJson.getString("app_id"));
assertEquals("valid_ts", requestBodyJson.getString("user_local_time"));
assertEquals("valid_aid", requestBodyJson.getString("btn_ref"));
assertEquals("123", requestBodyJson.getString("order_id"));
Expand All @@ -251,6 +252,17 @@ public MockResponse dispatch(RecordedRequest request) throws InterruptedExceptio
});

Order order = new Order.Builder("123").setCurrencyCode("AUG").build();
buttonApi.postActivity("valid_application_id", "valid_aid", "valid_ts", order);
buttonApi.postActivity(APPLICATION_ID, "valid_aid", "valid_ts", order);
}

@Test()
public void getBaseUrl_validateApplicationIdInUrl() {
// Reinitialize the buttonApi variable to reset the baseUrl back to the original.
buttonApi = new ButtonApiImpl(userAgent);

final String expectedUrl = "https://" + APPLICATION_ID + ".mobileapi.usebutton.com";
final String actualUrl = buttonApi.getBaseUrl(APPLICATION_ID);

assertEquals(expectedUrl, actualUrl);
}
}
Expand Up @@ -40,6 +40,7 @@

import java.util.concurrent.Executor;

import static com.usebutton.merchant.TestHelper.APPLICATION_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -69,17 +70,25 @@ public void setUp() {
public void configure_saveApplicationIdInMemory() {
ButtonRepository buttonRepository = mock(ButtonRepository.class);

buttonInternal.configure(buttonRepository, "valid_application_id");
verify(buttonRepository).setApplicationId("valid_application_id");
buttonInternal.configure(buttonRepository, APPLICATION_ID);
verify(buttonRepository).setApplicationId(APPLICATION_ID);
}

@Test
public void configure_shouldNotSaveApplicationIdInMemory_InvalidAppId() {
ButtonRepository buttonRepository = mock(ButtonRepository.class);

buttonInternal.configure(buttonRepository, "invalid_application_id");
verify(buttonRepository, never()).setApplicationId(APPLICATION_ID);
}

@Test
public void getApplicationId_retrieveFromRepository() {
ButtonRepository buttonRepository = mock(ButtonRepository.class);
when(buttonRepository.getApplicationId()).thenReturn("valid_application_id");
when(buttonRepository.getApplicationId()).thenReturn(APPLICATION_ID);

String applicationId = buttonInternal.getApplicationId(buttonRepository);
assertEquals("valid_application_id", applicationId);
assertEquals(APPLICATION_ID, applicationId);
}

@Test
Expand Down Expand Up @@ -283,7 +292,7 @@ public void handlePostInstallIntent_returnValidPostInstallLink_setSourceToken()
PostInstallIntentListener postInstallIntentListener = mock(PostInstallIntentListener.class);
DeviceManager deviceManager = mock(DeviceManager.class);

when(buttonRepository.getApplicationId()).thenReturn("valid_application_id");
when(buttonRepository.getApplicationId()).thenReturn(APPLICATION_ID);

buttonInternal.handlePostInstallIntent(buttonRepository, postInstallIntentListener,
"com.usebutton.merchant",
Expand Down Expand Up @@ -312,7 +321,7 @@ public void handlePostInstallIntent_returnInvalidPostInstallLink_doNotSetSourceT
PostInstallIntentListener postInstallIntentListener = mock(PostInstallIntentListener.class);
DeviceManager deviceManager = mock(DeviceManager.class);

when(buttonRepository.getApplicationId()).thenReturn("valid_application_id");
when(buttonRepository.getApplicationId()).thenReturn(APPLICATION_ID);

buttonInternal.handlePostInstallIntent(buttonRepository, postInstallIntentListener,
"com.usebutton.merchant",
Expand Down Expand Up @@ -357,7 +366,7 @@ public void handlePostInstallIntent_throwButtonNetworkException() {
PostInstallIntentListener postInstallIntentListener = mock(PostInstallIntentListener.class);
DeviceManager deviceManager = mock(DeviceManager.class);

when(buttonRepository.getApplicationId()).thenReturn("valid_application_id");
when(buttonRepository.getApplicationId()).thenReturn(APPLICATION_ID);

buttonInternal.handlePostInstallIntent(buttonRepository, postInstallIntentListener,
"com.usebutton.merchant",
Expand All @@ -380,7 +389,7 @@ public void handlePostInstallIntent_newInstallationAndDidNotCheckDeferredDeepLin
PostInstallIntentListener postInstallIntentListener = mock(PostInstallIntentListener.class);
DeviceManager deviceManager = mock(DeviceManager.class);

when(buttonRepository.getApplicationId()).thenReturn("valid_application_id");
when(buttonRepository.getApplicationId()).thenReturn(APPLICATION_ID);
when(deviceManager.isOldInstallation()).thenReturn(false);
when(buttonRepository.checkedDeferredDeepLink()).thenReturn(false);

Expand All @@ -398,7 +407,7 @@ public void handlePostInstallIntent_oldInstallation_doNotUpdateCheckDeferredDeep
PostInstallIntentListener postInstallIntentListener = mock(PostInstallIntentListener.class);
DeviceManager deviceManager = mock(DeviceManager.class);

when(buttonRepository.getApplicationId()).thenReturn("valid_application_id");
when(buttonRepository.getApplicationId()).thenReturn(APPLICATION_ID);
when(deviceManager.isOldInstallation()).thenReturn(true);
when(buttonRepository.checkedDeferredDeepLink()).thenReturn(false);

Expand All @@ -416,7 +425,7 @@ public void handlePostInstallIntent_checkedDeferredDeepLink_doNotUpdateCheckDefe
PostInstallIntentListener postInstallIntentListener = mock(PostInstallIntentListener.class);
DeviceManager deviceManager = mock(DeviceManager.class);

when(buttonRepository.getApplicationId()).thenReturn("valid_application_id");
when(buttonRepository.getApplicationId()).thenReturn(APPLICATION_ID);
when(deviceManager.isOldInstallation()).thenReturn(false);
when(buttonRepository.checkedDeferredDeepLink()).thenReturn(true);

Expand Down
@@ -0,0 +1,54 @@
/*
* ButtonUtilTest.java
*
* Copyright (c) 2019 Button, Inc. (https://usebutton.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/

package com.usebutton.merchant;

import org.junit.Test;

import static com.usebutton.merchant.TestHelper.APPLICATION_ID;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class ButtonUtilTest {
@Test
public void isApplicationIdValid_ShouldReturnTrueWithValidAppId() {
assertTrue(ButtonUtil.isApplicationIdValid(APPLICATION_ID));
}

@Test
public void isApplicationIdValid_ShouldReturnFalseWithInvalidAppId_EmptyString() {
assertFalse(ButtonUtil.isApplicationIdValid(""));
}

@Test
public void isApplicationIdValid_ShouldReturnFalseWithInvalidAppId_NoAppPrefix() {
assertFalse(ButtonUtil.isApplicationIdValid("-1111111111111111"));
}

@Test
public void isApplicationIdValid_ShouldReturnFalseWithInvalidAppId_InvalidCharacter() {
assertFalse(ButtonUtil.isApplicationIdValid("app-992&&&&3 "));
}
}

0 comments on commit b0ac0fd

Please sign in to comment.