package com.rapidftr.service;
import android.graphics.BitmapFactory;
import com.google.common.io.CharStreams;
import com.rapidftr.CustomTestRunner;
import com.rapidftr.RapidFtrApplication;
import com.rapidftr.database.Database;
import com.rapidftr.model.Child;
import com.rapidftr.model.History;
import com.rapidftr.model.User;
import com.rapidftr.repository.ChildRepository;
import com.rapidftr.utils.AudioCaptureHelper;
import com.rapidftr.utils.PhotoCaptureHelper;
import com.rapidftr.utils.http.FluentRequest;
import org.apache.http.HttpException;
import org.json.JSONArray;
import org.json.JSONException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.robolectric.Robolectric;
import org.robolectric.tester.org.apache.http.FakeHttpLayer;
import org.robolectric.tester.org.apache.http.TestHttpResponse;
import javax.xml.ws.http.HTTPException;
import java.io.*;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static com.rapidftr.RapidFtrApplication.SERVER_URL_PREF;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.doThrow;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.robolectric.Robolectric.getFakeHttpLayer;
@RunWith(CustomTestRunner.class)
public class GenericSyncServiceTest {
@Mock
private ChildRepository repository;
@Mock
private User currentUser;
private RapidFtrApplication application;
FluentRequest fluentRequest;
public static final String RESPONSE = "{\"unique_identifier\":\"adf7c0c9-0137-4cae-beea-b7d282344829\",\"created_at\":\"2013-02-08 12:18:37\",\"created_by_full_name\":\"RapidFTR\",\"couchrest-type\":\"Child\",\"short_id\":\"2344829\",\"_id\":\"b7f89b978870da823e0af6491c3e295b\",\"_rev\":\"2-bc72af384e177fcaa8e9e8d181bfe05b\",\"name\":\"\",\"last_updated_at\":\"2013-02-08 11:37:33\",\"current_photo_key\":\"photo--1475374810-2013-02-08T175138\",\"created_by\":\"rapidftr\",\"photo_keys\":[\"photo--1475374810-2013-02-08T175138\"],\"created_organisation\":\"N/A\",\"posted_at\":\"2013-02-08 12:16:55UTC\",\"last_updated_by_full_name\":\"RapidFTR\"}";
EntityHttpDao<Child> childHttpDao;
MediaSyncHelper mediaSyncHelper;
GenericSyncService<Child> genericSyncService;
@Before
public void setUp() throws Exception {
initMocks(this);
application = (RapidFtrApplication) Robolectric.getShadowApplication().getApplicationContext();
User user = new User("userName", "password", true, "http://1.2.3.4");
application.setCurrentUser(user);
childHttpDao = EntityHttpDaoFactory.createChildHttpDao(application,
"http://whatever",
ChildSyncService.CHILDREN_API_PATH,
ChildSyncService.CHILDREN_API_PARAMETER);
given(currentUser.isVerified()).willReturn(true);
fluentRequest = new FluentRequest();
mediaSyncHelper = new MediaSyncHelper(childHttpDao, mockContext());
genericSyncService = new GenericSyncService<Child>(mediaSyncHelper, childHttpDao, repository);
}
@Test
public void shouldMarkChildAsSyncedWhenSyncing() throws IOException, JSONException, GeneralSecurityException {
getFakeHttpLayer().setDefaultHttpResponse(201, "{}");
Child child = genericSyncService.sync(new Child(), "");
assertThat(child.isSynced(), is(true));
}
@Test
public void shouldCreateOrUpdateChildWithoutHistoryWhenSyncing() throws IOException, JSONException, GeneralSecurityException {
getFakeHttpLayer().setDefaultHttpResponse(201, "{}");
Child child = genericSyncService.sync(new Child(), "");
verify(repository).createOrUpdateWithoutHistory(child);
}
@Test(expected = SyncFailedException.class)
public void shouldThrowSyncFailedExceptionWhenSyncFailure() throws Exception {
Child child = new Child();
getFakeHttpLayer().setDefaultHttpResponse(503, "error");
genericSyncService.sync(child, "");
}
@Test
public void shouldSyncUsingTheProvidedPath() throws Exception {
Child child = new Child("id1", "user1", "{ 'test1' : 'value1' }");
child.put(Database.ChildTableColumn.internal_id.getColumnName(), "xyz");
getFakeHttpLayer().addHttpResponseRule("http://whatever/api/children/xyz", "{}");
genericSyncService.sync(child, "/api/children/xyz");
}
@Test
public void shouldCreateNewChildIfThereIsNoID() throws Exception {
Child child = new Child("id1", "user1", "{ 'test1' : 'value1' }");
getFakeHttpLayer().addHttpResponseRule("http://whatever/api/children", "{ 'test1' : 'value2', '_id' : 'abcd1234'}");
child = genericSyncService.sync(child, "/api/children");
verify(repository).createOrUpdateWithoutHistory(child);
}
@Test
public void shouldAddPhotoKeysToParam() throws JSONException, IOException, GeneralSecurityException, HttpException {
getFakeHttpLayer().setDefaultHttpResponse(201, RESPONSE);
List<String> photoKeysList = Arrays.asList("photo-998877", "photo-998547", "abcd123", "1234ABC");
Child child = new Child("id1", "user1", String.format("{ '_id' : 'abcdef', 'name' : 'child1', 'test2' : 0, 'current_photo_key' : '1234ABC', 'photo_keys' : %s}", new JSONArray(photoKeysList).toString()));
EntityHttpDao<Child> httpDao = new EntityHttpDao<Child>(application, "", "", "child");
EntityHttpDao<Child> spyHttpDao = spy(httpDao);
doReturn(child).when(spyHttpDao).update(any(Child.class), any(String.class), any(Map.class));
new GenericSyncService<Child>(mediaSyncHelper, spyHttpDao, repository).sync(child, "");
verify(spyHttpDao).update(any(Child.class), any(String.class), argThat(new ContainsPhotoKeysParams("abcd123", "1234ABC")));
}
@Test
public void shouldNotAddPhotoParamIfThePhotoNameIsPresentInPhotoKeys() throws JSONException, IOException, GeneralSecurityException {
RapidFtrApplication context = mockContext();
String photoKeys = new JSONArray(Arrays.asList("photo-998877", "photo-998547", "1234ABC")).toString();
String childDetails = String.format("{ 'name' : 'child1', 'test2' : 0, 'current_photo_key' : '1234ABC', 'photo_keys' : %s}", photoKeys);
Child child = new Child("id1", "user1", childDetails);
AudioCaptureHelper audioCaptureHelper = new AudioCaptureHelper(context);
audioCaptureHelper.saveAudio(child, new ByteArrayInputStream("OK".getBytes()));
byte[] content = "Testing".getBytes();
PhotoCaptureHelper photoCaptureHelper = new PhotoCaptureHelper(context);
photoCaptureHelper.savePhoto(BitmapFactory.decodeByteArray(content, 0, content.length), 10, "photo-998877");
photoCaptureHelper.savePhoto(BitmapFactory.decodeByteArray(content, 0, content.length), 10, "photo-998547");
photoCaptureHelper.savePhoto(BitmapFactory.decodeByteArray(content, 0, content.length), 10, "1234ABC");
getFakeHttpLayer().addHttpResponseRule("http://whatever/api/children", "{}");
getFakeHttpLayer().hasRequestMatchingRule(new FakeHttpLayer.RequestMatcherBuilder().param("current_photo_key", "1234ABC"));
genericSyncService.sync(child, "api/children");
}
@Test
public void shouldAddAudioRecordedIfCreatedOnTheMobile() throws JSONException, IOException {
RapidFtrApplication context = mockContext();
String response = "{'_id': 'testing', 'name' : 'child1', 'recorded_audio' : '123456'}";
Child child = new Child("id", "user", "{'name' : 'child1', 'recorded_audio' : '123455'}");
getFakeHttpLayer().addHttpResponseRule("http://whatever/api/children", response);
getFakeHttpLayer().addHttpResponseRule("http://whatever/child/testing/audio", "RECEIVED");
AudioCaptureHelper audioCaptureHelper = new AudioCaptureHelper(context);
audioCaptureHelper.saveAudio(child, new ByteArrayInputStream("OK".getBytes()));
genericSyncService.sync(child, "api/children");
File file = audioCaptureHelper.getFile("123456", "");
String fileContent = CharStreams.toString(new InputStreamReader(new FileInputStream(file)));
assertEquals("RECEIVED", fileContent);
}
@Test
public void shouldNotAddAudioRecordedToTheRequestIfItsAlreadyPresentInServer() throws JSONException, IOException, HttpException {
getFakeHttpLayer().setDefaultHttpResponse(201, RESPONSE);
Child child = new Child("id", "user", "{'_id' : 'abcd123488', 'name' : 'child1', 'recorded_audio' : '123455', 'audio_attachments' : {'original' : '123455', 'amr':'123455'}}");
EntityHttpDao<Child> httpDao = new EntityHttpDao<Child>(application, "", "", "child");
EntityHttpDao<Child> spyHttpDao = spy(httpDao);
doReturn(child).when(spyHttpDao).update(any(Child.class), any(String.class), any(Map.class));
new GenericSyncService<Child>(mediaSyncHelper, spyHttpDao, repository).sync(child, "api/children");
verify(spyHttpDao).update(any(Child.class), any(String.class), argThat(new DoesntContainAudioKeyParams()));
}
@Test
public void shouldRemoveUnusedParametersBeforeSync() throws JSONException, IOException, GeneralSecurityException {
RapidFtrApplication context = mockContext();
String photoKeys = new JSONArray(Arrays.asList("photo-998877", "photo-998547", "1234ABC")).toString();
String childDetails = String.format("{ 'name' : 'child1', 'test2' : 0, 'current_photo_key' : '1234ABC', 'recorded_audio' : '123455', 'audio_attachments' : {'original' : '123455', 'amr':'123455'}, 'photo_keys' : %s}", photoKeys);
Child child = new Child("id", "user", childDetails);
AudioCaptureHelper audioCaptureHelper = new AudioCaptureHelper(context);
audioCaptureHelper.saveAudio(child, new ByteArrayInputStream("OK".getBytes()));
byte[] content = "Testing".getBytes();
PhotoCaptureHelper photoCaptureHelper = new PhotoCaptureHelper(context);
photoCaptureHelper.savePhoto(BitmapFactory.decodeByteArray(content, 0, content.length), 10, "photo-998877");
photoCaptureHelper.savePhoto(BitmapFactory.decodeByteArray(content, 0, content.length), 10, "photo-998547");
photoCaptureHelper.savePhoto(BitmapFactory.decodeByteArray(content, 0, content.length), 10, "1234ABC");
getFakeHttpLayer().addHttpResponseRule("http://whatever/api/children", "{}");
genericSyncService.sync(child, "api/children");
assertThat(child.optString("photo_keys"), is(""));
assertThat(child.optString("audio_attachments"), is(""));
assertThat(child.optString("synced"), is(""));
}
@Test
public void shouldUpdateChildAttributesAfterSync() throws IOException, JSONException {
RapidFtrApplication context = mockContext();
String response = "{\"separation_place\":\"\",\"wishes_address_3\":\"\",\"care_arrangments_name\":\"\",\"other_family\":\"\",\"care_arrangements_knowsfamily\":\"\",\"created_at\":\"2012-12-14 10:57:39UTC\",\"wishes_contacted_details\":\"\",\"posted_from\":\"Browser\",\"care_arrangements_relationship\":\"\",\"interviewer\":\"\",\"birthplace\":\"\",\"father_death_details\":\"\",\"mothers_name\":\"\",\"name\":\"kavitha working\",\"other_child_2_relationship\":\"\",\"other_child_1\":\"\",\"other_child_1_dob\":\"\",\"other_child_2_telephone\":\"\",\"caregivers_name\":\"\",\"other_child_3_dob\":\"\",\"concerns_medical_case\":\"\",\"names_origin\":\"\",\"gender\":\"\",\"unique_identifier\":\"8a126c33-d2e3-4802-8698-19c06f52d5d1\",\"is_caregiver_alive\":\"\",\"wishes_contacted\":\"\",\"other_child_3_address\":\"\",\"evacuation_from\":\"\",\"photo_keys\":[],\"address\":\"\",\"disclosure_other_orgs\":\"\",\"concerns_other\":\"\",\"histories\":[],\"wishes_wants_contact\":\"\",\"wishes_telephone_1\":\"\",\"posted_at\":\"2012-12-14 10:57:39UTC\",\"other_child_1_address\":\"\",\"other_child_3_birthplace\":\"\",\"other_child_3_relationship\":\"\",\"languages\":\"\",\"concerns_followup_details\":\"\",\"other_org_interview_status\":\"\",\"concerns_further_info\":\"\",\"concerns_needs_followup\":\"\",\"disclosure_public_photo\":\"\",\"wishes_name_1\":\"\",\"created_by\":\"rapidftr\",\"other_org_date\":\"\",\"wishes_address_1\":\"\",\"is_mother_alive\":\"\",\"other_child_1_relationship\":\"\",\"other_child_1_telephone\":\"\",\"interview_place\":\"\",\"evacuation_date\":\"\",\"evacuation_status\":\"\",\"other_child_2\":\"\",\"c206ec4e\":\"\",\"other_child_2_dob\":\"\",\"interviewers_org\":\"\",\"dob_or_age\":\"\",\"id_document\":\"\",\"care_arrangements_arrival_date\":\"\",\"rc_id_no\":\"\",\"care_arrangements_came_from\":\"\",\"protection_status\":\"\",\"other_org_place\":\"\",\"separation_date\":\"\",\"created_organisation\":\"N/A\",\"mother_death_details\":\"\",\"concerns_girl_mother\":\"\",\"e96c289e\":\"\",\"orther_org_reference_no\":\"\",\"_rev\":\"1-ec347c93b262e7db0e306b77f22c2e19\",\"evacuation_to\":\"\",\"disclosure_authorities\":\"\",\"c9fc0344\":\"\",\"wishes_telephone_2\":\"\",\"interview_date\":\"\",\"telephone\":\"\",\"evacuation_agent\":\"\",\"additional_tracing_info\":\"\",\"couchrest-type\":\"Child\",\"care_arrangements\":\"\",\"other_child_2_birthplace\":\"\",\"disclosure_public_relatives\":\"\",\"other_child_2_address\":\"\",\"wishes_name_2\":\"\",\"current_photo_key\":\"\",\"disclosure_public_name\":\"\",\"separation_details\":\"\",\"interview_subject_details\":\"\",\"wishes_address_2\":\"\",\"concerns_abuse_situation\":\"\",\"063c3784\":\"\",\"concerns_street_child\":\"\",\"other_child_3\":\"\",\"interview_subject\":\"\",\"care_arrangements_address\":\"\",\"documents\":\"\",\"other_child_1_birthplace\":\"\",\"fef83a5e\":\"\",\"is_father_alive\":\"\",\"created_by_full_name\":\"RapidFTR\",\"characteristics\":\"\",\"care_arrangements_familyinfo\":\"\",\"disclosure_deny_details\":\"\",\"other_org_name\":\"\",\"nationality\":\"\",\"short_id\":\"f52d5d1\",\"concerns_chh\":\"\",\"concerns_vulnerable_person\":\"\",\"wishes_telephone_3\":\"\",\"concerns_disabled\":\"\",\"fathers_name\":\"\",\"_id\":\"0369c92c8e2245e680dc9a580202e285\",\"other_org_country\":\"\",\"ethnicity_or_tribe\":\"\",\"care_arrangements_other\":\"\",\"wishes_name_3\":\"\"}";
getFakeHttpLayer().setDefaultHttpResponse(200, response);
Child child = new Child("id", "user", "{ 'name' : 'child1'}");
Child syncedChild = genericSyncService.sync(child, "");
assertThat(syncedChild.isSynced(), is(true));
assertThat(syncedChild.getString("last_synced_at"), not(is(nullValue())));
assertThat(syncedChild.getString("_attachments"), is(nullValue()));
}
@Test
public void shouldSetMediaIfNotAlreadyExistingOnTheMobile() throws JSONException, IOException {
RapidFtrApplication context = mockContext();
String response = "{\"recorded_audio\":\"audio-12321\",\"photo_keys\": [\"photo-998\",\"photo-888\", \"photo-777\"],\"_id\":\"abcd\",\"current_photo_key\": \"photo-888\",\"separation_place\":\"\",\"wishes_address_3\":\"\",\"care_arrangments_name\":\"\",\"other_family\":\"\",\"care_arrangements_knowsfamily\":\"\",\"created_at\":\"2012-12-14 10:57:39UTC\",\"wishes_contacted_details\":\"\",\"posted_from\":\"Browser\"}";
MediaSyncHelper spyMediaHelper = spy(new MediaSyncHelper(childHttpDao, context));
GenericSyncService syncService = new GenericSyncService<Child>(spyMediaHelper, childHttpDao, repository);
getFakeHttpLayer().setDefaultHttpResponse(200, response);
Child child = new Child("id", "user", "{ 'name' : 'child1'}");
doNothing().when(spyMediaHelper).getPhotoFromServer(Matchers.any(Child.class), Matchers.any(PhotoCaptureHelper.class), eq("photo-998"));
doNothing().when(spyMediaHelper).getPhotoFromServer(Matchers.any(Child.class), Matchers.any(PhotoCaptureHelper.class), eq("photo-888"));
doNothing().when(spyMediaHelper).getPhotoFromServer(Matchers.any(Child.class), Matchers.any(PhotoCaptureHelper.class), eq("photo-777"));
syncService.sync(child, "");
verify(spyMediaHelper).getPhotoFromServer(Matchers.any(Child.class), Matchers.any(PhotoCaptureHelper.class), eq("photo-888"));
verify(spyMediaHelper).getPhotoFromServer(Matchers.any(Child.class), Matchers.any(PhotoCaptureHelper.class), eq("photo-998"));
verify(spyMediaHelper).getPhotoFromServer(Matchers.any(Child.class), Matchers.any(PhotoCaptureHelper.class), eq("photo-777"));
}
@Test
public void shouldMarkUnverifiedChildAsSyncedOnceSuccessfullySynced() throws Exception {
getFakeHttpLayer().addHttpResponseRule("http://whatever/api/children/unverified", new TestHttpResponse(200, "{}"));
Child child = new Child();
given(currentUser.isVerified()).willReturn(false);
child = genericSyncService.sync(child, "api/children/unverified");
assertThat(child.isSynced(), is(true));
}
@Test
public void shouldRemoveHistoriesAfterSuccessfulSync() throws Exception {
Child childSpy = spy(new Child("{\"_id\" : \"couch_id\", \"child_name\":\"subhas\",\"unique_identifier\":\"78223s4h1e468f5200edc\"}"));
doReturn(false).when(childSpy).isNew();
childHttpDao = mock(EntityHttpDao.class);
doReturn(childSpy).when(childHttpDao).update(eq(childSpy), anyString(), any(Map.class));
GenericSyncService<Child> syncService = new GenericSyncService<Child>(mediaSyncHelper, childHttpDao, repository);
syncService.sync(childSpy, "");
verify(childSpy).remove(History.HISTORIES);
}
@Test(expected = SyncFailedException.class)
public void shouldNotRemoveHistoriesAfterFailedSync() throws Exception {
Child childSpy = spy(new Child("{\"_id\" : \"couch_id\", \"child_name\":\"subhas\",\"unique_identifier\":\"78223s4h1e468f5200edc\"}"));
doReturn(false).when(childSpy).isNew();
childHttpDao = mock(EntityHttpDao.class);
doThrow(new HTTPException(404)).when(childHttpDao).update(eq(childSpy), anyString(), any(Map.class));
new GenericSyncService<Child>(mediaSyncHelper, childHttpDao, repository).sync(childSpy, "");
verify(childSpy, never()).remove(History.HISTORIES);
}
private RapidFtrApplication mockContext() {
RapidFtrApplication context = RapidFtrApplication.getApplicationInstance();
context.getSharedPreferences().edit().putString(SERVER_URL_PREF, "whatever").commit();
context.getCurrentUser().setDbKey("tu2347893023u2");
return context;
}
class ContainsPhotoKeysParams extends ArgumentMatcher<Map> {
private String[] photoKeys;
public ContainsPhotoKeysParams(String... photoKeys) {
this.photoKeys = photoKeys;
}
public boolean matches(Object params) {
String actualPhotoKeys = ((Map<String, String>) params).get("photo_keys");
for (int i = 0; i < photoKeys.length; i++) {
if (!actualPhotoKeys.matches(".*" + photoKeys[i] + ".*"))
return false;
}
return true;
}
}
class DoesntContainAudioKeyParams extends ArgumentMatcher<Map> {
public boolean matches(Object params) {
return !((Map<String, String>) params).containsKey("recorded_audio");
}
}
}