diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/APIHelper.java b/Habitica/src/main/java/com/habitrpg/android/habitica/APIHelper.java index 938f61c7d..9cf6d748d 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/APIHelper.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/APIHelper.java @@ -1,18 +1,66 @@ package com.habitrpg.android.habitica; +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + import com.amplitude.api.Amplitude; import com.crashlytics.android.Crashlytics; +import com.habitrpg.android.habitica.database.CheckListItemExcludeStrategy; import com.magicmicky.habitrpgwrapper.lib.api.ApiService; import com.magicmicky.habitrpgwrapper.lib.api.Server; +import com.magicmicky.habitrpgwrapper.lib.models.ChatMessage; +import com.magicmicky.habitrpgwrapper.lib.models.ContentResult; +import com.magicmicky.habitrpgwrapper.lib.models.Customization; +import com.magicmicky.habitrpgwrapper.lib.models.FAQArticle; +import com.magicmicky.habitrpgwrapper.lib.models.Group; import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser; import com.magicmicky.habitrpgwrapper.lib.models.PurchaseValidationRequest; import com.magicmicky.habitrpgwrapper.lib.models.PurchaseValidationResult; +import com.magicmicky.habitrpgwrapper.lib.models.Purchases; +import com.magicmicky.habitrpgwrapper.lib.models.Skill; +import com.magicmicky.habitrpgwrapper.lib.models.TutorialStep; import com.magicmicky.habitrpgwrapper.lib.models.UserAuth; import com.magicmicky.habitrpgwrapper.lib.models.UserAuthResponse; import com.magicmicky.habitrpgwrapper.lib.models.UserAuthSocial; import com.magicmicky.habitrpgwrapper.lib.models.UserAuthSocialTokens; +import com.magicmicky.habitrpgwrapper.lib.models.inventory.Egg; +import com.magicmicky.habitrpgwrapper.lib.models.inventory.Food; +import com.magicmicky.habitrpgwrapper.lib.models.inventory.HatchingPotion; +import com.magicmicky.habitrpgwrapper.lib.models.inventory.Mount; +import com.magicmicky.habitrpgwrapper.lib.models.inventory.Pet; +import com.magicmicky.habitrpgwrapper.lib.models.inventory.QuestContent; +import com.magicmicky.habitrpgwrapper.lib.models.responses.FeedResponse; +import com.magicmicky.habitrpgwrapper.lib.models.tasks.ChecklistItem; +import com.magicmicky.habitrpgwrapper.lib.models.tasks.ItemData; import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task; import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskList; +import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskTag; +import com.magicmicky.habitrpgwrapper.lib.utils.BooleanAsIntAdapter; +import com.magicmicky.habitrpgwrapper.lib.utils.ChatMessageDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.ChecklistItemSerializer; +import com.magicmicky.habitrpgwrapper.lib.utils.ContentDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.CustomizationDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.DateDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.EggListDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.FAQArticleListDeserilializer; +import com.magicmicky.habitrpgwrapper.lib.utils.FeedResponseDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.FoodListDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.GroupSerialization; +import com.magicmicky.habitrpgwrapper.lib.utils.HatchingPotionListDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.ItemDataListDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.MountListDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.PetListDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.PurchasedDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.QuestListDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.SkillDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.TaskListDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.TaskSerializer; +import com.magicmicky.habitrpgwrapper.lib.utils.TaskTagDeserializer; +import com.magicmicky.habitrpgwrapper.lib.utils.TutorialStepListDeserializer; +import com.raizlabs.android.dbflow.structure.ModelAdapter; import org.json.JSONException; import org.json.JSONObject; @@ -22,10 +70,13 @@ import android.support.v7.app.AlertDialog; import java.io.IOException; import java.lang.annotation.Annotation; +import java.lang.reflect.Type; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -133,6 +184,62 @@ public class APIHelper implements Action1 { this.apiService = retrofitAdapter.create(ApiService.class); } + public static GsonConverterFactory createGsonFactory() { + Type taskTagClassListType = new TypeToken>() {}.getType(); + Type skillListType = new TypeToken>() {}.getType(); + Type customizationListType = new TypeToken>() {}.getType(); + Type tutorialStepListType = new TypeToken>() {}.getType(); + Type faqArticleListType = new TypeToken>() {}.getType(); + Type itemDataListType = new TypeToken>() {}.getType(); + Type eggListType = new TypeToken>() {}.getType(); + Type foodListType = new TypeToken>() {}.getType(); + Type hatchingPotionListType = new TypeToken>() {}.getType(); + Type questContentListType = new TypeToken>() {}.getType(); + Type petListType = new TypeToken>() {}.getType(); + Type mountListType = new TypeToken>() {}.getType(); + + //Exclusion strategy needed for DBFlow https://github.com/Raizlabs/DBFlow/issues/121 + Gson gson = new GsonBuilder() + .setExclusionStrategies(new CheckListItemExcludeStrategy()) + .setExclusionStrategies(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes f) { + return f.getDeclaredClass().equals(ModelAdapter.class); + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } + }) + .registerTypeAdapter(taskTagClassListType, new TaskTagDeserializer()) + .registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter()) + .registerTypeAdapter(boolean.class, new BooleanAsIntAdapter()) + .registerTypeAdapter(skillListType, new SkillDeserializer()) + .registerTypeAdapter(ChecklistItem.class, new ChecklistItemSerializer()) + .registerTypeAdapter(TaskList.class, new TaskListDeserializer()) + .registerTypeAdapter(Purchases.class, new PurchasedDeserializer()) + .registerTypeAdapter(customizationListType, new CustomizationDeserializer()) + .registerTypeAdapter(tutorialStepListType, new TutorialStepListDeserializer()) + .registerTypeAdapter(faqArticleListType, new FAQArticleListDeserilializer()) + .registerTypeAdapter(Group.class, new GroupSerialization()) + .registerTypeAdapter(Date.class, new DateDeserializer()) + .registerTypeAdapter(itemDataListType, new ItemDataListDeserializer()) + .registerTypeAdapter(eggListType, new EggListDeserializer()) + .registerTypeAdapter(foodListType, new FoodListDeserializer()) + .registerTypeAdapter(hatchingPotionListType, new HatchingPotionListDeserializer()) + .registerTypeAdapter(questContentListType, new QuestListDeserializer()) + .registerTypeAdapter(petListType, new PetListDeserializer()) + .registerTypeAdapter(mountListType, new MountListDeserializer()) + .registerTypeAdapter(ChatMessage.class, new ChatMessageDeserializer()) + .registerTypeAdapter(Task.class, new TaskSerializer()) + .registerTypeAdapter(ContentResult.class, new ContentDeserializer()) + .registerTypeAdapter(FeedResponse.class, new FeedResponseDeserializer()) + .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") + .create(); + return GsonConverterFactory.create(gson); + } + public Observable registerUser(String username, String email, String password, String confirmPassword) { UserAuth auth = new UserAuth(); auth.setUsername(username); @@ -218,7 +325,7 @@ public class APIHelper implements Action1 { return habitRPGUser; }); } - return userObservable.compose(configureApiCallObserver()); + return userObservable; } private List sortTasks(Map taskMap, List taskOrder){ diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java index 618c639ad..d3c41dae3 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/modules/ApiModule.java @@ -81,59 +81,7 @@ public class ApiModule { @Provides public GsonConverterFactory providesGsonConverterFactory() { - Type taskTagClassListType = new TypeToken>() {}.getType(); - Type skillListType = new TypeToken>() {}.getType(); - Type customizationListType = new TypeToken>() {}.getType(); - Type tutorialStepListType = new TypeToken>() {}.getType(); - Type faqArticleListType = new TypeToken>() {}.getType(); - Type itemDataListType = new TypeToken>() {}.getType(); - Type eggListType = new TypeToken>() {}.getType(); - Type foodListType = new TypeToken>() {}.getType(); - Type hatchingPotionListType = new TypeToken>() {}.getType(); - Type questContentListType = new TypeToken>() {}.getType(); - Type petListType = new TypeToken>() {}.getType(); - Type mountListType = new TypeToken>() {}.getType(); - - //Exclusion strategy needed for DBFlow https://github.com/Raizlabs/DBFlow/issues/121 - Gson gson = new GsonBuilder() - .setExclusionStrategies(new CheckListItemExcludeStrategy()) - .setExclusionStrategies(new ExclusionStrategy() { - @Override - public boolean shouldSkipField(FieldAttributes f) { - return f.getDeclaredClass().equals(ModelAdapter.class); - } - - @Override - public boolean shouldSkipClass(Class clazz) { - return false; - } - }) - .registerTypeAdapter(taskTagClassListType, new TaskTagDeserializer()) - .registerTypeAdapter(Boolean.class, new BooleanAsIntAdapter()) - .registerTypeAdapter(boolean.class, new BooleanAsIntAdapter()) - .registerTypeAdapter(skillListType, new SkillDeserializer()) - .registerTypeAdapter(ChecklistItem.class, new ChecklistItemSerializer()) - .registerTypeAdapter(TaskList.class, new TaskListDeserializer()) - .registerTypeAdapter(Purchases.class, new PurchasedDeserializer()) - .registerTypeAdapter(customizationListType, new CustomizationDeserializer()) - .registerTypeAdapter(tutorialStepListType, new TutorialStepListDeserializer()) - .registerTypeAdapter(faqArticleListType, new FAQArticleListDeserilializer()) - .registerTypeAdapter(Group.class, new GroupSerialization()) - .registerTypeAdapter(Date.class, new DateDeserializer()) - .registerTypeAdapter(itemDataListType, new ItemDataListDeserializer()) - .registerTypeAdapter(eggListType, new EggListDeserializer()) - .registerTypeAdapter(foodListType, new FoodListDeserializer()) - .registerTypeAdapter(hatchingPotionListType, new HatchingPotionListDeserializer()) - .registerTypeAdapter(questContentListType, new QuestListDeserializer()) - .registerTypeAdapter(petListType, new PetListDeserializer()) - .registerTypeAdapter(mountListType, new MountListDeserializer()) - .registerTypeAdapter(ChatMessage.class, new ChatMessageDeserializer()) - .registerTypeAdapter(Task.class, new TaskSerializer()) - .registerTypeAdapter(ContentResult.class, new ContentDeserializer()) - .registerTypeAdapter(FeedResponse.class, new FeedResponseDeserializer()) - .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") - .create(); - return GsonConverterFactory.create(gson); + return APIHelper.createGsonFactory(); } @Provides diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java index a04e9444b..322ffcca7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.java @@ -232,6 +232,7 @@ public class MainActivity extends BaseActivity implements Action1, Ha if (this.lastSync == null || (new Date().getTime() - this.lastSync.getTime()) > 180000) { if (this.apiHelper != null) { this.apiHelper.retrieveUser(true) + .compose(apiHelper.configureApiCallObserver()) .subscribe(new HabitRPGUserCallback(this), throwable -> {}); this.checkMaintenance(); } @@ -672,6 +673,7 @@ public class MainActivity extends BaseActivity implements Action1, Ha if (resultCode == SELECT_CLASS_RESULT) { if (this.apiHelper != null) { this.apiHelper.retrieveUser(true) + .compose(apiHelper.configureApiCallObserver()) .subscribe(new HabitRPGUserCallback(this), throwable -> {}); } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java index 1be5a16a9..21f7a0278 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/tasks/TasksFragment.java @@ -196,6 +196,7 @@ public class TasksFragment extends BaseMainFragment implements OnCheckedChangeLi if (apiHelper != null) { apiHelper.retrieveUser(true) + .compose(apiHelper.configureApiCallObserver()) .subscribe( new HabitRPGUserCallback(activity), throwable -> stopAnimatingRefreshItem() diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/PurchasedDeserializer.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/PurchasedDeserializer.java index 047c61492..d34711747 100644 --- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/PurchasedDeserializer.java +++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/PurchasedDeserializer.java @@ -30,8 +30,13 @@ public class PurchasedDeserializer implements JsonDeserializer { List customizations = new ArrayList(); Purchases purchases = new Purchases(); - List existingCustomizations = new Select().from(Customization.class).queryList(); - + List existingCustomizations; + try { + existingCustomizations = new Select().from(Customization.class).queryList(); + } catch (RuntimeException e) { + //Tests don't have a database + existingCustomizations = new ArrayList<>(); + } for (Customization customization : existingCustomizations) { if(object.has(customization.getType())) { JsonObject nestedObject = object.get(customization.getType()).getAsJsonObject(); diff --git a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/TaskTagDeserializer.java b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/TaskTagDeserializer.java index 4f4078de1..d8419adde 100644 --- a/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/TaskTagDeserializer.java +++ b/Habitica/src/main/java/com/magicmicky/habitrpgwrapper/lib/utils/TaskTagDeserializer.java @@ -17,9 +17,15 @@ public class TaskTagDeserializer implements JsonDeserializer> { @Override public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { List taskTags = new ArrayList<>(); - List allTags = new Select() - .from(Tag.class) - .queryList(); + List allTags; + try { + allTags = new Select() + .from(Tag.class) + .queryList(); + } catch (RuntimeException e) { + //Tests don't have a database + allTags = new ArrayList<>(); + } for (JsonElement tagElement : json.getAsJsonArray()) { String tagId = tagElement.getAsString(); diff --git a/Habitica/src/test/java/com/habitrpg/android/habitica/APIHelperTests.java b/Habitica/src/test/java/com/habitrpg/android/habitica/APIHelperTests.java new file mode 100644 index 000000000..4bc63614f --- /dev/null +++ b/Habitica/src/test/java/com/habitrpg/android/habitica/APIHelperTests.java @@ -0,0 +1,60 @@ +package com.habitrpg.android.habitica; + + +import com.magicmicky.habitrpgwrapper.lib.models.HabitRPGUser; +import com.magicmicky.habitrpgwrapper.lib.models.tasks.Task; +import com.magicmicky.habitrpgwrapper.lib.models.tasks.TaskList; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricGradleTestRunner; +import org.robolectric.annotation.Config; + +import android.os.Build; + +import java.util.List; + +import rx.observers.TestSubscriber; + +@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP) +@RunWith(RobolectricGradleTestRunner.class) +public class APIHelperTests { + + private APIHelper apiHelper; + + @Before + public void setUp() { + HostConfig hostConfig = new HostConfig(BuildConfig.BASE_URL, + BuildConfig.PORT, + BuildConfig.TEST_USER_KEY, + BuildConfig.TEST_USER_ID); + apiHelper = new APIHelper(APIHelper.createGsonFactory(), hostConfig); + } + + @Test + public void shouldLoadUserFromServer() { + TestSubscriber testSubscriber = new TestSubscriber<>(); + apiHelper.apiService.getUser().subscribe(testSubscriber); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); + testSubscriber.assertValueCount(1); + } + + @Test + public void shouldLoadTasksFromServer() { + TestSubscriber testSubscriber = new TestSubscriber<>(); + apiHelper.apiService.getTasks().subscribe(testSubscriber); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); + } + + @Test + public void shouldLoadCompleteUserFromServer() { + TestSubscriber testSubscriber = new TestSubscriber<>(); + apiHelper.retrieveUser(true).subscribe(testSubscriber); + testSubscriber.assertNoErrors(); + testSubscriber.assertCompleted(); + testSubscriber.assertValueCount(1); + } +}