package com.mixpanel.android.mpmetrics; import android.graphics.Color; import android.os.Bundle; import android.test.AndroidTestCase; import com.mixpanel.android.util.RemoteService; import com.mixpanel.android.util.HttpService; import com.mixpanel.android.viewcrawler.UpdatesFromMixpanel; import org.json.JSONArray; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.net.ssl.SSLSocketFactory; public class DecideCheckerTest extends AndroidTestCase { @Override public void setUp() { mConfig = new MockConfig(new Bundle()); mDecideChecker = new DecideChecker(getContext(), mConfig, new SystemInformation(getContext())); mPoster = new MockPoster(); mEventBinder = new MockUpdatesFromMixpanel(); mEventBinder.startUpdates(); mDecideMessages1 = new DecideMessages("TOKEN 1", null, mEventBinder); mDecideMessages1.setDistinctId("DISTINCT ID 1"); mDecideMessages2 = new DecideMessages("TOKEN 2", null, mEventBinder); mDecideMessages2.setDistinctId("DISTINCT ID 2"); mDecideMessages3 = new DecideMessages("TOKEN 3", null, mEventBinder); mDecideMessages3.setDistinctId("DISTINCT ID 3"); } public void testReadEmptyLists() throws RemoteService.ServiceUnavailableException { mDecideChecker.addDecideCheck(mDecideMessages1); mPoster.response = bytes("{}"); mDecideChecker.runDecideChecks(mPoster); assertNull(mDecideMessages1.getNotification(false)); assertUpdatesSeen(new JSONArray[] { new JSONArray() }); mEventBinder.bindingsSeen.clear(); mPoster.response = bytes("{\"notifications\":[]}"); mDecideChecker.runDecideChecks(mPoster); assertNull(mDecideMessages1.getNotification(false)); assertUpdatesSeen(new JSONArray[] { new JSONArray() }); } public void testBadDecideResponses() throws RemoteService.ServiceUnavailableException { mDecideChecker.addDecideCheck(mDecideMessages1); // Corrupted or crazy responses. mPoster.response = bytes("{ WONT PARSE"); mDecideChecker.runDecideChecks(mPoster); assertNull(mDecideMessages1.getNotification(false)); assertUpdatesSeen(new JSONArray[] {}); // No updates at all on parsing failure mEventBinder.bindingsSeen.clear(); // Just pure (but legal) JSON craziness mPoster.response = bytes("null"); mDecideChecker.runDecideChecks(mPoster); assertNull(mDecideMessages1.getNotification(false)); assertUpdatesSeen(new JSONArray[]{}); mEventBinder.bindingsSeen.clear(); // Valid JSON that isn't relevant mPoster.response = bytes("{\"Ziggy Startdust and the Spiders from Mars\":\"The Best Ever Number One\"}"); mDecideChecker.runDecideChecks(mPoster); assertNull(mDecideMessages1.getNotification(false)); assertUpdatesSeen(new JSONArray[]{ new JSONArray() }); mEventBinder.bindingsSeen.clear(); } public void testDecideHonorsFallbackEnabled() throws RemoteService.ServiceUnavailableException { mConfig.fallbackDisabled = false; mPoster.requestedUrls.clear(); mPoster.response = null; mPoster.exception = new IOException("Bang!"); mDecideChecker.addDecideCheck(mDecideMessages1); mDecideChecker.runDecideChecks(mPoster); assertEquals(2, mPoster.requestedUrls.size()); } public void testDecideHonorsFallbackDisabled() throws RemoteService.ServiceUnavailableException { mConfig.fallbackDisabled = true; mPoster.requestedUrls.clear(); mPoster.response = null; mPoster.exception = new IOException("Bang!"); mDecideChecker.addDecideCheck(mDecideMessages1); mDecideChecker.runDecideChecks(mPoster); assertEquals(1, mPoster.requestedUrls.size()); } public void testDecideResponses() throws DecideChecker.UnintelligibleMessageException { { final String nonsense = "I AM NONSENSE"; try { final DecideChecker.Result parseNonsense = DecideChecker.parseDecideResponse(nonsense); fail("Should have thrown exception on parse"); } catch (DecideChecker.UnintelligibleMessageException e) { ; // OK } } { final String allNull = "null"; try { final DecideChecker.Result parseAllNull = DecideChecker.parseDecideResponse(allNull); fail("Should have thrown exception on decide response that isn't surrounded by {}"); } catch (DecideChecker.UnintelligibleMessageException e) { ; // OK } } { final String elementsNull = "{\"notifications\": null}"; final DecideChecker.Result parseElementsNull = DecideChecker.parseDecideResponse(elementsNull); assertTrue(parseElementsNull.notifications.isEmpty()); } { final String elementsEmpty = "{\"notifications\": []}"; final DecideChecker.Result parseElementsEmpty = DecideChecker.parseDecideResponse(elementsEmpty); assertTrue(parseElementsEmpty.notifications.isEmpty()); } { final String notificationOnly = "{\"notifications\":[{\"id\": 1234, \"message_id\": 4321, \"type\": \"takeover\", \"body\": \"Hook me up, yo!\", \"body_color\": 4294901760, \"title\": null, \"title_color\": 4278255360, \"image_url\": \"http://mixpanel.com/Balok.jpg\", \"bg_color\": 3909091328, \"close_color\": 4294967295, \"extras\": {\"image_fade\": true},\"buttons\": [{\"text\": \"Button!\", \"text_color\": 4278190335, \"bg_color\": 4294967040, \"border_color\": 4278255615, \"cta_url\": \"hellomixpanel://deeplink/howareyou\"}, {\"text\": \"Button 2!\", \"text_color\": 4278190335, \"bg_color\": 4294967040, \"border_color\": 4278255615, \"cta_url\": \"hellomixpanel://deeplink/howareyou\"}]}]}"; final DecideChecker.Result parseNotificationOnly = DecideChecker.parseDecideResponse(notificationOnly); assertEquals(parseNotificationOnly.notifications.size(), 1); final TakeoverInAppNotification parsed = (TakeoverInAppNotification) parseNotificationOnly.notifications.get(0); assertEquals(parsed.getId(), 1234); assertEquals(parsed.getMessageId(), 4321); assertEquals(parsed.getBody(), "Hook me up, yo!"); assertNull(parsed.getTitle()); assertEquals(parsed.hasTitle(), false); assertEquals(parsed.hasBody(), true); assertEquals(parsed.getBackgroundColor(), Color.argb(233, 0, 0, 0)); assertEquals(parsed.getBodyColor(), Color.parseColor("#FFFF0000")); assertEquals(parsed.getTitleColor(), Color.parseColor("#FF00FF00")); assertEquals(parsed.getImageUrl(), "http://mixpanel.com/Balok.jpg"); assertEquals(parsed.getCloseColor(), Color.WHITE); assertEquals(parsed.setShouldShowShadow(), true); assertEquals(parsed.getButton(0).getText(), "Button!"); assertEquals(parsed.getButton(0).getTextColor(), Color.BLUE); assertEquals(parsed.getButton(0).getCtaUrl(), "hellomixpanel://deeplink/howareyou"); assertEquals(parsed.getButton(0).getBorderColor(), Color.parseColor("#FF00FFFF")); assertEquals(parsed.getButton(0).getBackgroundColor(), Color.parseColor("#FFFFFF00")); assertEquals(parsed.getButton(1).getText(), "Button 2!"); assertEquals(parsed.getType(), InAppNotification.Type.TAKEOVER); } { final String both = "{\"notifications\":[{\"body\":\"A\",\"image_tint_color\":4294967295,\"border_color\":4294967295,\"message_id\":85151,\"bg_color\":3858759680,\"extras\":{},\"image_url\":\"https://cdn.mxpnl.com/site_media/images/engage/inapp_messages/mini/icon_megaphone.png\",\"cta_url\":null,\"type\":\"mini\",\"id\":1191793,\"body_color\":4294967295}]}"; final DecideChecker.Result parseBoth = DecideChecker.parseDecideResponse(both); final MiniInAppNotification parsedNotification = (MiniInAppNotification) parseBoth.notifications.get(0); assertEquals(parsedNotification.getBody(), "A"); assertEquals(parsedNotification.getBodyColor(), Color.WHITE); assertEquals(parsedNotification.getImageTintColor(), Color.WHITE); assertEquals(parsedNotification.getBorderColor(), Color.WHITE); assertEquals(parsedNotification.getBackgroundColor(), Color.parseColor("#E6000000")); assertEquals(parsedNotification.getExtras().length(), 0); assertEquals(parsedNotification.getMessageId(), 85151); assertEquals(parsedNotification.getImageUrl(), "https://cdn.mxpnl.com/site_media/images/engage/inapp_messages/mini/icon_megaphone.png"); assertEquals(parsedNotification.getCtaUrl(), null); assertEquals(parsedNotification.getId(), 1191793); assertEquals(parsedNotification.getType(), InAppNotification.Type.MINI); } } private void assertUpdatesSeen(JSONArray[] expected) { assertEquals(expected.length, mEventBinder.bindingsSeen.size()); for (int bindingCallIx = 0; bindingCallIx < expected.length; bindingCallIx++) { final JSONArray expectedArray = expected[bindingCallIx]; final JSONArray seen = mEventBinder.bindingsSeen.get(bindingCallIx); assertEquals(expectedArray.toString(), seen.toString()); } } private byte[] bytes(String s) { try { return s.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("This is not an android device, or a compatible java. WHO ARE YOU?"); } } private static class MockPoster extends HttpService { @Override public byte[] performRequest(String url, Map<String, Object> params, SSLSocketFactory socketFactory) throws IOException { assertNull(params); requestedUrls.add(url); if (null != exception) { throw exception; } return response; } public List<String> requestedUrls = new ArrayList<String>(); public byte[] response = null; public IOException exception = null; } private static class MockUpdatesFromMixpanel implements UpdatesFromMixpanel { @Override public void startUpdates() { mStarted = true; } @Override public void setEventBindings(JSONArray bindings) { assertTrue(mStarted); bindingsSeen.add(bindings); } @Override public void setVariants(JSONArray variants) { assertTrue(mStarted); variantsSeen.add(variants); } @Override public Tweaks getTweaks() { assertTrue(mStarted); return null; } @Override public void addOnMixpanelTweaksUpdatedListener(OnMixpanelTweaksUpdatedListener listener) { } @Override public void removeOnMixpanelTweaksUpdatedListener(OnMixpanelTweaksUpdatedListener listener) { } public List<JSONArray> bindingsSeen = new ArrayList<JSONArray>(); public List<JSONArray> variantsSeen = new ArrayList<JSONArray>(); private volatile boolean mStarted = false; } private static class MockConfig extends MPConfig { MockConfig(Bundle metaData) { super(metaData, null); } @Override public boolean getDisableFallback() { return fallbackDisabled; } public boolean fallbackDisabled = false; } private DecideChecker mDecideChecker; private MockPoster mPoster; private MockConfig mConfig; private MockUpdatesFromMixpanel mEventBinder; private DecideMessages mDecideMessages1, mDecideMessages2, mDecideMessages3; }