/* * Copyright (c) 2015-present, Parse, LLC. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.parse; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.util.Arrays; import java.util.Locale; import java.util.TimeZone; import bolts.Task; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) @Config(constants = BuildConfig.class, sdk = TestHelper.ROBOLECTRIC_SDK_VERSION) public class ParseInstallationTest extends ResetPluginsParseTest { private static final String KEY_INSTALLATION_ID = "installationId"; private static final String KEY_DEVICE_TYPE = "deviceType"; private static final String KEY_APP_NAME = "appName"; private static final String KEY_APP_IDENTIFIER = "appIdentifier"; private static final String KEY_TIME_ZONE = "timeZone"; private static final String KEY_LOCALE_IDENTIFIER = "localeIdentifier"; private static final String KEY_APP_VERSION = "appVersion"; private Locale defaultLocale; @Before public void setUp() throws Exception { super.setUp(); ParseObject.registerSubclass(ParseInstallation.class); defaultLocale = Locale.getDefault(); } @After public void tearDown() throws Exception { super.tearDown(); ParseObject.unregisterSubclass(ParseInstallation.class); Locale.setDefault(defaultLocale); } @Test public void testImmutableKeys() { String[] immutableKeys = { "installationId", "deviceType", "appName", "appIdentifier", "parseVersion", "deviceToken", "deviceTokenLastModified", "pushType", "timeZone", "localeIdentifier", "appVersion" }; ParseInstallation installation = new ParseInstallation(); installation.put("foo", "bar"); for (String immutableKey : immutableKeys) { try { installation.put(immutableKey, "blah"); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains("Cannot modify")); } try { installation.remove(immutableKey); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains("Cannot modify")); } try { installation.removeAll(immutableKey, Arrays.asList()); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains("Cannot modify")); } } } @Test (expected = RuntimeException.class) public void testInstallationObjectIdCannotBeChanged() throws Exception { boolean hasException = false; ParseInstallation installation = new ParseInstallation(); try { installation.put("objectId", "abc"); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains("Cannot modify")); hasException = true; } assertTrue(hasException); installation.setObjectId("abc"); } @Test public void testMissingRequiredFieldWhenSaveAsync() throws Exception { String sessionToken = "sessionToken"; Task<Void> toAwait = Task.forResult(null); ParseCurrentInstallationController controller = mockCurrentInstallationController(); ParseObjectController objController = mock(ParseObjectController.class); // mock return task when Installation was deleted on the server Task<ParseObject.State> taskError = Task.forError(new ParseException(ParseException.MISSING_REQUIRED_FIELD_ERROR, "")); // mock return task when Installation was re-saved to the server Task<ParseObject.State> task = Task.forResult(null); when(objController.saveAsync( any(ParseObject.State.class), any(ParseOperationSet.class), eq(sessionToken), any(ParseDecoder.class))) .thenReturn(taskError) .thenReturn(task); ParseCorePlugins.getInstance() .registerObjectController(objController); ParseInstallation installation = ParseInstallation.getCurrentInstallation(); assertNotNull(installation); installation.put("key", "value"); installation.saveAsync(sessionToken, toAwait); verify(controller).getAsync(); verify(objController, times(2)).saveAsync( any(ParseObject.State.class), any(ParseOperationSet.class), eq(sessionToken), any(ParseDecoder.class)); } @Test public void testObjectNotFoundWhenSaveAsync() throws Exception { OfflineStore lds = new OfflineStore(RuntimeEnvironment.application); Parse.setLocalDatastore(lds); String sessionToken = "sessionToken"; Task<Void> toAwait = Task.forResult(null); ParseCurrentInstallationController controller = mockCurrentInstallationController(); ParseObjectController objController = mock(ParseObjectController.class); // mock return task when Installation was deleted on the server Task<ParseObject.State> taskError = Task.forError(new ParseException(ParseException.OBJECT_NOT_FOUND, "")); // mock return task when Installation was re-saved to the server Task<ParseObject.State> task = Task.forResult(null); when(objController.saveAsync( any(ParseObject.State.class), any(ParseOperationSet.class), eq(sessionToken), any(ParseDecoder.class))) .thenReturn(taskError) .thenReturn(task); ParseCorePlugins.getInstance() .registerObjectController(objController); ParseObject.State state = new ParseObject.State.Builder("_Installation") .objectId("oldId") .put("deviceToken", "deviceToken") .build(); ParseInstallation installation = ParseInstallation.getCurrentInstallation(); assertNotNull(installation); installation.setState(state); installation.put("key", "value"); installation.saveAsync(sessionToken, toAwait); verify(controller).getAsync(); verify(objController, times(2)).saveAsync( any(ParseObject.State.class), any(ParseOperationSet.class), eq(sessionToken), any(ParseDecoder.class)); Parse.setLocalDatastore(null); } @Test public void testHandleSaveResultAsync() throws Exception { // Mock currentInstallationController to make setAsync work ParseCurrentInstallationController controller = mock(ParseCurrentInstallationController.class); when(controller.setAsync(any(ParseInstallation.class))).thenReturn(Task.<Void>forResult(null)); ParseCorePlugins.getInstance().registerCurrentInstallationController(controller); // Mock return state ParseInstallation.State state = new ParseInstallation.State.Builder("_Installation") .put("key", "value") .build(); ParseInstallation installation = new ParseInstallation(); installation.put("keyAgain", "valueAgain"); ParseOperationSet operationSet = installation.startSave(); ParseTaskUtils.wait(installation.handleSaveResultAsync(state, operationSet)); // Make sure the installation data is correct assertEquals("value", installation.get("key")); assertEquals("valueAgain", installation.get("keyAgain")); // Make sure we set the currentInstallation verify(controller, times(1)).setAsync(installation); } @Test public void testHandleFetchResultAsync() throws Exception { // Mock currentInstallationController to make setAsync work ParseCurrentInstallationController controller = mock(ParseCurrentInstallationController.class); when(controller.setAsync(any(ParseInstallation.class))).thenReturn(Task.<Void>forResult(null)); ParseCorePlugins.getInstance().registerCurrentInstallationController(controller); // Mock return state ParseInstallation.State state = new ParseInstallation.State.Builder("_Installation") .put("key", "value") .isComplete(true) .build(); ParseInstallation installation = new ParseInstallation(); ParseTaskUtils.wait(installation.handleFetchResultAsync(state)); // Make sure the installation data is correct assertEquals("value", installation.get("key")); // Make sure we set the currentInstallation verify(controller, times(1)).setAsync(installation); } @Test public void testUpdateBeforeSave() throws Exception { mocksForUpdateBeforeSave(); Locale.setDefault(new Locale("en", "US")); ParseInstallation installation = new ParseInstallation(); installation.updateBeforeSave(); // Make sure we update timezone String zone = TimeZone.getDefault().getID(); assertEquals(zone, installation.getString(KEY_TIME_ZONE)); // Make sure we update version info Context context = Parse.getApplicationContext(); String packageName = context.getPackageName(); PackageManager pm = context.getPackageManager(); PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0); String appVersion = pkgInfo.versionName; String appName = pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)).toString(); assertEquals(packageName, installation.getString(KEY_APP_IDENTIFIER)); assertEquals(appName, installation.getString(KEY_APP_NAME)); assertEquals(appVersion, installation.getString(KEY_APP_VERSION)); // Make sure we update device info assertEquals("android", installation.getString(KEY_DEVICE_TYPE)); assertEquals("installationId", installation.getString(KEY_INSTALLATION_ID)); // Make sure we update the locale identifier assertEquals("en-US", installation.getString(KEY_LOCALE_IDENTIFIER)); } // TODO(mengyan): Add other testUpdateBeforeSave cases to cover all branches @Test public void testPushType() throws Exception { ParseInstallation installation = new ParseInstallation(); installation.setPushType(PushType.GCM); assertEquals(PushType.GCM, installation.getPushType()); installation.removePushType(); assertNull(installation.getPushType()); // Make sure we add the pushType to operationSetQueue instead of serverData assertEquals(1, installation.operationSetQueue.getLast().size()); } @Test public void testPushTypeWithNullPushType() throws Exception { ParseInstallation installation = new ParseInstallation(); installation.setPushType(PushType.GCM); assertEquals(PushType.GCM, installation.getPushType()); installation.setPushType(null); assertEquals(PushType.GCM, installation.getPushType()); } @Test public void testDeviceToken() throws Exception { ParseInstallation installation = new ParseInstallation(); installation.setDeviceToken("deviceToken"); assertEquals("deviceToken", installation.getDeviceToken()); installation.removeDeviceToken(); assertNull(installation.getDeviceToken()); // Make sure we add the pushType to operationSetQueue instead of serverData assertEquals(1, installation.operationSetQueue.getLast().size()); } @Test public void testDeviceTokenWithNullDeviceToken() throws Exception { ParseInstallation installation = new ParseInstallation(); installation.setDeviceToken("deviceToken"); assertEquals("deviceToken", installation.getDeviceToken()); installation.setDeviceToken(null); assertEquals("deviceToken", installation.getDeviceToken()); } @Test public void testGetCurrentInstallation() throws Exception { // Mock currentInstallationController to make setAsync work ParseCurrentInstallationController controller = mock(ParseCurrentInstallationController.class); ParseInstallation currentInstallation = new ParseInstallation(); when(controller.getAsync()).thenReturn(Task.forResult(currentInstallation)); ParseCorePlugins.getInstance().registerCurrentInstallationController(controller); ParseInstallation installation = ParseInstallation.getCurrentInstallation(); assertEquals(currentInstallation, installation); verify(controller, times(1)).getAsync(); } @Test public void testLocaleIdentifierSpecialCases() throws Exception { mocksForUpdateBeforeSave(); ParseInstallation installation = new ParseInstallation(); // Deprecated two-letter codes (Java issue). Locale.setDefault(new Locale("iw", "US")); installation.updateBeforeSave(); assertEquals("he-US", installation.getString(KEY_LOCALE_IDENTIFIER)); Locale.setDefault(new Locale("in", "US")); installation.updateBeforeSave(); assertEquals("id-US", installation.getString(KEY_LOCALE_IDENTIFIER)); Locale.setDefault(new Locale("ji", "US")); installation.updateBeforeSave(); assertEquals("yi-US", installation.getString(KEY_LOCALE_IDENTIFIER)); // No country code. Locale.setDefault(new Locale("en")); installation.updateBeforeSave(); assertEquals("en", installation.getString(KEY_LOCALE_IDENTIFIER)); } // TODO(mengyan): Add testFetchAsync, right now we can not test super methods inside // testFetchAsync private static void mocksForUpdateBeforeSave() { // Mock currentInstallationController to make setAsync work ParseCurrentInstallationController controller = mock(ParseCurrentInstallationController.class); when(controller.isCurrent(any(ParseInstallation.class))).thenReturn(true); ParseCorePlugins.getInstance().registerCurrentInstallationController(controller); // Mock App Name RuntimeEnvironment.application.getApplicationInfo().name = "parseTest"; ParsePlugins.Android plugins = mock(ParsePlugins.Android.class); // Mock installationId InstallationId installationId = mock(InstallationId.class); when(installationId.get()).thenReturn("installationId"); when(plugins.installationId()).thenReturn(installationId); // Mock application context when(plugins.applicationContext()).thenReturn(RuntimeEnvironment.application); ParsePlugins.set(plugins); } private ParseCurrentInstallationController mockCurrentInstallationController() { ParseCurrentInstallationController controller = mock(ParseCurrentInstallationController.class); ParseInstallation currentInstallation = new ParseInstallation(); when(controller.getAsync()) .thenReturn(Task.forResult(currentInstallation)); ParseCorePlugins.getInstance() .registerCurrentInstallationController(controller); return controller; } }