blob: 3f47571800b26fe09f7795640b35076d56e4e8f2 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.i18n.addressinput.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.google.common.base.Preconditions;
import com.google.i18n.addressinput.common.AsyncRequestApi.AsyncCallback;
import com.google.i18n.addressinput.testing.AddressDataMapLoader;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.InOrder;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
@RunWith(JUnit4.class)
public class CacheDataTest {
private static final String DELIM = "~";
private static final String CANADA_KEY = "data/CA";
private static final String CALIFORNIA_KEY = "data/US/CA";
private static final String INVALID_KEY = "data/asIOSDxcowW";
private static final AsyncRequestApi EXPLODING_REQUEST_API = new AsyncRequestApi() {
@Override
public void requestObject(String url, AsyncCallback callback, int timeoutMillis) {
throw new AssertionError("unexpected use of AsyncRequestApi");
}
};
/**
* This is a simpler mock implementation distinct from the InMemoryAsyncReuqest which
* lets us test the asynchronous logic around data loading for a single expected value
* (unlike InMemoryAsyncReuqest, which tests fake data that loads immediately).
*/
private static class MockAsyncRequest implements AsyncRequestApi {
private final JsoMap jsoMap;
private AsyncCallback callback = null;
MockAsyncRequest(String jsonString) {
try {
this.jsoMap = JsoMap.buildJsoMap(jsonString);
} catch (JSONException e) {
throw new AssertionError("Invalid test JSON data: " + jsonString, e);
}
}
@Override
public void requestObject(String url, AsyncCallback callback, int timeoutMillis) {
Preconditions.checkState(this.callback == null, "requestObject was invoked twice");
this.callback = Preconditions.checkNotNull(callback);
}
void receiveResponse() {
Preconditions.checkState(callback != null, "requestObject was not invoked");
callback.onSuccess(jsoMap);
}
void timeoutWith() {
Preconditions.checkState(callback != null, "requestObject was not invoked");
callback.onFailure();
}
}
private static void assertListenerWasCalled(DataLoadListener mockListener) {
InOrder order = Mockito.inOrder(mockListener);
order.verify(mockListener).dataLoadingBegin();
order.verify(mockListener).dataLoadingEnd();
}
@Test public void testJsonConstructor() {
// Creating cache with content.
String id = "data/CA";
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(AddressDataMapLoader.TEST_COUNTRY_DATA.get(id));
} catch (JSONException jsonException) {
// If this throws an exception the test fails.
fail("Can't parse json object");
}
CacheData cache = new CacheData(EXPLODING_REQUEST_API);
cache.addToJsoMap(id, jsonObject);
String toBackup = cache.getJsonString();
// Creating cache from saved data.
cache = new CacheData(toBackup, EXPLODING_REQUEST_API);
assertTrue(cache.containsKey(id));
}
@Test public void testJsonConstructorTruncatedProperString() {
// Creating cache with content.
CacheData cache = new CacheData(EXPLODING_REQUEST_API);
String id = "data/CA";
try {
JSONObject jsonObject = new JSONObject(AddressDataMapLoader.TEST_COUNTRY_DATA.get(id));
String jsonString = jsonObject.toString();
jsonString = jsonString.substring(0, jsonString.length() / 2);
cache = new CacheData(jsonString, EXPLODING_REQUEST_API);
assertTrue(cache.toString(), cache.isEmpty());
} catch (JSONException jsonException) {
// If this throws an exception the test fails.
fail("Can't parse json object");
}
}
@Test public void testSimpleFetching() {
LookupKey key = new LookupKey.Builder(CANADA_KEY).build();
MockAsyncRequest fakeRequestApi = new MockAsyncRequest("{"
+ "\"id\":\"data/CA\","
+ "\"key\":\"CA\","
+ "\"name\":\"CANADA\","
+ "\"lang\":\"en\","
+ "\"languages\":\"en~fr\","
+ "\"fmt\":\"%N%n%O%n%A%n%C %S %Z\","
+ "\"require\":\"ACSZ\","
+ "\"upper\":\"ACNOSZ\","
+ "\"zip\":\"[A-Z]\\d[A-Z][ ]?\\d[A-Z]\\d\","
+ "\"zipex\":\"H3Z 2Y7,V8X 3X4\","
+ "\"posturl\":\"http://www.canadapost.ca\","
+ "\"sub_keys\":\"AB~BC~YT\","
+ "\"sub_names\":\"Alberta~British Columbia~Yukon\","
+ "\"sub_zips\":\"T~V~Y\""
+ "}");
CacheData cache = new CacheData(fakeRequestApi);
DataLoadListener mockListener = Mockito.mock(DataLoadListener.class);
cache.fetchDynamicData(key, null, mockListener);
// Force response to be received here...
fakeRequestApi.receiveResponse();
assertListenerWasCalled(mockListener);
JsoMap map = cache.getObj(CANADA_KEY);
assertTrue(map.containsKey("id"));
assertTrue(map.containsKey("lang"));
assertTrue(map.containsKey("zip"));
assertTrue(map.containsKey("fmt"));
assertTrue(map.containsKey("sub_keys"));
assertTrue(map.containsKey("sub_names"));
assertFalse(map.containsKey("sub_lnames"));
int namesSize = map.get("sub_names").split(DELIM).length;
int keysSize = map.get("sub_keys").split(DELIM).length;
assertEquals("Expect 3 states in dummy data for Canada.", 3, namesSize);
assertEquals(namesSize, keysSize);
}
@Test public void testFetchingOneKeyManyTimes() {
LookupKey key = new LookupKey.Builder(CALIFORNIA_KEY).build();
MockAsyncRequest fakeRequestApi = new MockAsyncRequest("{"
+ "\"id\":\"data/US/CA\","
+ "\"key\":\"CA\","
+ "\"name\":\"California\","
+ "\"lang\":\"en\","
+ "\"zip\":\"9[0-5]|96[01]\","
+ "\"zipex\":\"90000,96199\"}");
CacheData cache = new CacheData(fakeRequestApi);
List<DataLoadListener> mockListeners = new ArrayList<DataLoadListener>();
// Add some listeners before the response is in the cache.
for (int i = 0; i < 5; ++i) {
DataLoadListener mockListener = Mockito.mock(DataLoadListener.class);
mockListeners.add(mockListener);
cache.fetchDynamicData(key, null, mockListener);
// Anything added before the result is available should have begun loading.
Mockito.verify(mockListener).dataLoadingBegin();
Mockito.verify(mockListener, Mockito.never()).dataLoadingEnd();
}
// Check the cache state before and after the response arrives.
assertFalse("data for key '" + key + "' should not be cached",
cache.containsKey(key.toString()));
// Force response to be received here...
fakeRequestApi.receiveResponse();
assertTrue("data for key '" + key + "' should be cached", cache.containsKey(key.toString()));
// Add more listeners after the response is in the cache.
for (int i = 0; i < 5; ++i) {
DataLoadListener mockListener = Mockito.mock(DataLoadListener.class);
mockListeners.add(mockListener);
cache.fetchDynamicData(key, null, mockListener);
}
// Check that all our listener methods were called correctly.
for (DataLoadListener mockListener : mockListeners) {
assertListenerWasCalled(mockListener);
}
}
@Test public void testRecursiveFetchFromCallback() {
final LookupKey key = new LookupKey.Builder(CANADA_KEY).build();
final MockAsyncRequest fakeRequestApi = new MockAsyncRequest("{"
+ "\"id\":\"data/CA\","
+ "\"key\":\"CA\","
+ "\"name\":\"CANADA\","
+ "}");
final CacheData cache = new CacheData(fakeRequestApi);
final DataLoadListener mockListener = Mockito.mock(DataLoadListener.class);
cache.fetchDynamicData(key, null, new DataLoadListener() {
@Override
public void dataLoadingBegin() {
}
@Override
public void dataLoadingEnd() {
cache.fetchDynamicData(key, null, mockListener);
}
});
fakeRequestApi.receiveResponse();
assertListenerWasCalled(mockListener);
}
@Test public void testInvalidKey() {
LookupKey key = new LookupKey.Builder(INVALID_KEY).build();
MockAsyncRequest fakeRequestApi = new MockAsyncRequest("{}");
CacheData cache = new CacheData(fakeRequestApi);
DataLoadListener mockListener = Mockito.mock(DataLoadListener.class);
cache.fetchDynamicData(key, null, mockListener);
// Check the cache state before and after the response arrives.
assertFalse("data for key '" + key + "' should not be cached",
cache.containsKey(key.toString()));
// Force response to be received here...
fakeRequestApi.receiveResponse();
assertFalse("data for key '" + key + "' should not be cached",
cache.containsKey(key.toString()));
// Even without a response, the listener was called correctly.
assertListenerWasCalled(mockListener);
}
@Test public void testTimeoutKey() {
LookupKey key = new LookupKey.Builder(CANADA_KEY).build();
MockAsyncRequest fakeRequestApi = new MockAsyncRequest("{}");
CacheData cache = new CacheData(fakeRequestApi);
DataLoadListener mockListener = Mockito.mock(DataLoadListener.class);
cache.fetchDynamicData(key, null, mockListener);
// Check the cache state before and after the response arrives.
assertFalse("data for key '" + key + "' should not be cached",
cache.containsKey(key.toString()));
// Force timeout here...
fakeRequestApi.timeoutWith();
assertFalse("data for key '" + key + "' should not be cached",
cache.containsKey(key.toString()));
// Even without a response, the listener was called correctly.
assertListenerWasCalled(mockListener);
}
@Test public void testSetUrl() {
LookupKey key = new LookupKey.Builder(CANADA_KEY).build();
String url = "http://some-random-url";
AsyncRequestApi mockRequestApi = Mockito.mock(AsyncRequestApi.class);
CacheData cache = new CacheData(mockRequestApi);
cache.setUrl(url);
cache.fetchDynamicData(key, null, null);
Mockito.verify(mockRequestApi)
.requestObject(Mockito.startsWith(url), Mockito.<AsyncCallback>any(), Mockito.anyInt());
}
}