/** * Copyright (c) 2015-present, Facebook, Inc. * 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.facebook.react.modules.storage; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import android.app.Activity; import android.content.Context; import android.content.ContextWrapper; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactTestHelper; import com.facebook.react.bridge.JavaOnlyArray; import com.facebook.react.bridge.JavaOnlyMap; import com.facebook.react.modules.storage.AsyncStorageModule; import com.facebook.react.modules.storage.ReactDatabaseSupplier; import org.json.JSONArray; import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.RuntimeEnvironment; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import static org.mockito.Mockito.mock; import static org.fest.assertions.api.Assertions.assertThat; /** * Tests for {@link AsyncStorageModule}. */ @PrepareForTest({Arguments.class}) @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "org.json.*"}) @RunWith(RobolectricTestRunner.class) public class AsyncStorageModuleTest { private AsyncStorageModule mStorage; private JavaOnlyArray mEmptyArray; @Rule public PowerMockRule rule = new PowerMockRule(); @Before public void prepareModules() { PowerMockito.mockStatic(Arguments.class); Mockito.when(Arguments.createArray()).thenAnswer( new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return new JavaOnlyArray(); } }); Mockito.when(Arguments.createMap()).thenAnswer( new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return new JavaOnlyMap(); } }); // don't use Robolectric before initializing mocks mStorage = new AsyncStorageModule(ReactTestHelper.createCatalystContextForTest()); mEmptyArray = new JavaOnlyArray(); } @After public void cleanUp() { RuntimeEnvironment.application.deleteDatabase(ReactDatabaseSupplier.DATABASE_NAME); ReactDatabaseSupplier.deleteInstance(); } @Test public void testMultiSetMultiGet() { final String key1 = "foo1"; final String key2 = "foo2"; final String fakeKey = "fakeKey"; final String value1 = "bar1"; final String value2 = "bar2"; JavaOnlyArray keyValues = new JavaOnlyArray(); keyValues.pushArray(getArray(key1, value1)); keyValues.pushArray(getArray(key2, value2)); Callback setCallback = mock(Callback.class); mStorage.multiSet(keyValues, setCallback); Mockito.verify(setCallback, Mockito.times(1)).invoke(); JavaOnlyArray keys = new JavaOnlyArray(); keys.pushString(key1); keys.pushString(key2); Callback getCallback = mock(Callback.class); mStorage.multiGet(keys, getCallback); Mockito.verify(getCallback, Mockito.times(1)).invoke(null, keyValues); keys.pushString(fakeKey); JavaOnlyArray row3 = new JavaOnlyArray(); row3.pushString(fakeKey); row3.pushString(null); keyValues.pushArray(row3); Callback getCallback2 = mock(Callback.class); mStorage.multiGet(keys, getCallback2); Mockito.verify(getCallback2, Mockito.times(1)).invoke(null, keyValues); } @Test public void testMultiRemove() { final String key1 = "foo1"; final String key2 = "foo2"; final String value1 = "bar1"; final String value2 = "bar2"; JavaOnlyArray keyValues = new JavaOnlyArray(); keyValues.pushArray(getArray(key1, value1)); keyValues.pushArray(getArray(key2, value2)); mStorage.multiSet(keyValues, mock(Callback.class)); JavaOnlyArray keys = new JavaOnlyArray(); keys.pushString(key1); keys.pushString(key2); Callback getCallback = mock(Callback.class); mStorage.multiRemove(keys, getCallback); Mockito.verify(getCallback, Mockito.times(1)).invoke(); Callback getAllCallback = mock(Callback.class); mStorage.getAllKeys(getAllCallback); Mockito.verify(getAllCallback, Mockito.times(1)).invoke(null, mEmptyArray); mStorage.multiSet(keyValues, mock(Callback.class)); keys.pushString("fakeKey"); Callback getCallback2 = mock(Callback.class); mStorage.multiRemove(keys, getCallback2); Mockito.verify(getCallback2, Mockito.times(1)).invoke(); Callback getAllCallback2 = mock(Callback.class); mStorage.getAllKeys(getAllCallback2); Mockito.verify(getAllCallback2, Mockito.times(1)).invoke(null, mEmptyArray); } @Test public void testMultiMerge() throws Exception { final String mergeKey = "mergeTest"; JSONObject value = new JSONObject(); value.put("foo1", "bar1"); value.put("foo2", createJSONArray("val1", "val2", 3)); value.put("foo3", 1001); value.put("foo4", createJSONObject("key1", "randomValueThatWillNeverBeUsed")); mStorage.multiSet(JavaOnlyArray.of(getArray(mergeKey, value.toString())), mock(Callback.class)); { Callback callback = mock(Callback.class); mStorage.multiGet(getArray(mergeKey), callback); Mockito.verify(callback, Mockito.times(1)) .invoke(null, JavaOnlyArray.of(getArray(mergeKey, value.toString()))); } value.put("foo1", 1001); value.put("foo2", createJSONObject("key1", "val1")); value.put("foo3", "bar1"); value.put("foo4", createJSONArray("val1", "val2", 3)); JSONObject newValue = new JSONObject(); newValue.put("foo2", createJSONObject("key2", "val2")); JSONObject newValue2 = new JSONObject(); newValue2.put("foo2", createJSONObject("key1", "val3")); mStorage.multiMerge( JavaOnlyArray.of( JavaOnlyArray.of(mergeKey, value.toString()), JavaOnlyArray.of(mergeKey, newValue.toString()), JavaOnlyArray.of(mergeKey, newValue2.toString())), mock(Callback.class)); value.put("foo2", createJSONObject("key1", "val3", "key2", "val2")); Callback callback = mock(Callback.class); mStorage.multiGet(getArray(mergeKey), callback); Mockito.verify(callback, Mockito.times(1)) .invoke(null, JavaOnlyArray.of(getArray(mergeKey, value.toString()))); } @Test public void testGetAllKeys() { final String[] keys = {"foo", "foo2"}; final String[] values = {"bar", "bar2"}; JavaOnlyArray keyValues = new JavaOnlyArray(); keyValues.pushArray(getArray(keys[0], values[0])); keyValues.pushArray(getArray(keys[1], values[1])); mStorage.multiSet(keyValues, mock(Callback.class)); JavaOnlyArray storedKeys = new JavaOnlyArray(); storedKeys.pushString(keys[0]); storedKeys.pushString(keys[1]); Callback getAllCallback = mock(Callback.class); mStorage.getAllKeys(getAllCallback); Mockito.verify(getAllCallback, Mockito.times(1)).invoke(null, storedKeys); Callback getAllCallback2 = mock(Callback.class); mStorage.multiRemove(getArray(keys[0]), mock(Callback.class)); mStorage.getAllKeys(getAllCallback2); Mockito.verify(getAllCallback2, Mockito.times(1)).invoke(null, getArray(keys[1])); mStorage.multiRemove(getArray(keys[1]), mock(Callback.class)); Callback getAllCallback3 = mock(Callback.class); mStorage.getAllKeys(getAllCallback3); Mockito.verify(getAllCallback3, Mockito.times(1)).invoke(null, mEmptyArray); } @Test public void testClear() { JavaOnlyArray keyValues = new JavaOnlyArray(); keyValues.pushArray(getArray("foo", "foo2")); keyValues.pushArray(getArray("bar", "bar2")); mStorage.multiSet(keyValues, mock(Callback.class)); Callback clearCallback2 = mock(Callback.class); mStorage.clear(clearCallback2); Mockito.verify(clearCallback2, Mockito.times(1)).invoke(); Callback getAllCallback2 = mock(Callback.class); mStorage.getAllKeys(getAllCallback2); Mockito.verify(getAllCallback2, Mockito.times(1)).invoke(null, mEmptyArray); } @Test public void testHugeMultiGetMultiGet() { // Test with many keys, so that it's above the 999 limit per batch imposed by SQLite. final int keyCount = 1001; // don't set keys that divide by this magical number, so that we can check that multiGet works, // and returns null for missing keys final int magicalNumber = 343; JavaOnlyArray keyValues = new JavaOnlyArray(); for (int i = 0; i < keyCount; i++) { if (i % magicalNumber > 0) { keyValues.pushArray(getArray("key" + i, "value" + i)); } } mStorage.multiSet(keyValues, mock(Callback.class)); JavaOnlyArray keys = new JavaOnlyArray(); for (int i = 0; i < keyCount; i++) { keys.pushString("key" + i); } mStorage.multiGet( keys, new Callback() { @Override public void invoke(Object... args) { assertThat(args.length).isEqualTo(2); JavaOnlyArray resultArray = (JavaOnlyArray) args[1]; assertThat(resultArray.size()).isEqualTo(keyCount); boolean keyReceived[] = new boolean[keyCount]; for (int i = 0; i < keyCount; i++) { String key = resultArray.getArray(i).getString(0).substring(3); int idx = Integer.parseInt(key); assertThat(keyReceived[idx]).isFalse(); keyReceived[idx] = true; if (idx % magicalNumber > 0) { String value = resultArray.getArray(i).getString(1).substring(5); assertThat(key).isEqualTo(value); } else { assertThat(resultArray.getArray(i).isNull(1)); } } } }); // Test removal in same test, since it's costly to set up the test again. // Remove only odd keys JavaOnlyArray keyRemoves = new JavaOnlyArray(); for (int i = 0; i < keyCount; i++) { if (i % 2 > 0) { keyRemoves.pushString("key" + i); } } mStorage.multiRemove(keyRemoves, mock(Callback.class)); mStorage.getAllKeys( new Callback() { @Override public void invoke(Object... args) { JavaOnlyArray resultArray = (JavaOnlyArray) args[1]; assertThat(resultArray.size()).isEqualTo(499); for (int i = 0; i < resultArray.size(); i++) { String key = resultArray.getString(i).substring(3); int idx = Integer.parseInt(key); assertThat(idx % 2).isEqualTo(0); } } }); } private static JSONArray createJSONArray(Object... objects) { return new JSONArray(Arrays.asList(objects)); } private static JSONObject createJSONObject(Object... keysAndValues) { if (keysAndValues.length % 2 != 0) { throw new IllegalArgumentException("You must provide the same number of keys and values"); } Map map = new HashMap(); for (int i = 0; i < keysAndValues.length; i += 2) { map.put(keysAndValues[i], keysAndValues[i + 1]); } return new JSONObject(map); } private JavaOnlyArray getArray(String... values) { JavaOnlyArray array = new JavaOnlyArray(); for (String value : values) { array.pushString(value); } return array; } }