/* * 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 org.json.JSONArray; import org.json.JSONObject; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import bolts.Task; import static com.parse.ParseMatchers.hasParseErrorCode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; /** * Unit tests for OfflineQueryLogic. */ public class OfflineQueryLogicTest { @Rule public ExpectedException thrown = ExpectedException.none(); @After public void tearDown() { ParseCorePlugins.getInstance().reset(); } //region hasReadAccess @Test public void testHasReadAccessWithSameObject() { ParseUser user = mock(ParseUser.class); assertTrue(OfflineQueryLogic.hasReadAccess(user, user)); verify(user, never()).getACL(); } @Test public void testHasReadAccessWithNoACL() { ParseObject object = mock(ParseObject.class); when(object.getACL()).thenReturn(null); assertTrue(OfflineQueryLogic.hasReadAccess(null, object)); } @Test public void testHasReadAccessWithPublicReadAccess() { ParseACL acl = mock(ParseACL.class); when(acl.getPublicReadAccess()).thenReturn(true); ParseObject object = mock(ParseObject.class); when(object.getACL()).thenReturn(acl); assertTrue(OfflineQueryLogic.hasReadAccess(null, object)); } @Test public void testHasReadAccessWithReadAccess() { ParseUser user = mock(ParseUser.class); when(user.getObjectId()).thenReturn("test"); ParseACL acl = mock(ParseACL.class); when(acl.getReadAccess(user)).thenReturn(true); ParseObject object = mock(ParseObject.class); when(object.getACL()).thenReturn(acl); assertTrue(OfflineQueryLogic.hasReadAccess(user, object)); } @Test public void testHasReadAccessWithNoReadAccess() { ParseACL acl = mock(ParseACL.class); when(acl.getPublicReadAccess()).thenReturn(false); when(acl.getReadAccess(any(ParseUser.class))).thenReturn(false); ParseObject object = mock(ParseObject.class); when(object.getACL()).thenReturn(acl); assertFalse(OfflineQueryLogic.hasReadAccess(null, object)); } //endregion //region hasWriteAccess @Test public void testHasWriteAccessWithSameObject() { ParseUser user = mock(ParseUser.class); assertTrue(OfflineQueryLogic.hasWriteAccess(user, user)); verify(user, never()).getACL(); } @Test public void testHasWriteAccessWithNoACL() { ParseObject object = mock(ParseObject.class); when(object.getACL()).thenReturn(null); assertTrue(OfflineQueryLogic.hasWriteAccess(null, object)); } @Test public void testHasWriteAccessWithPublicWriteAccess() { ParseACL acl = mock(ParseACL.class); when(acl.getPublicWriteAccess()).thenReturn(true); ParseObject object = mock(ParseObject.class); when(object.getACL()).thenReturn(acl); assertTrue(OfflineQueryLogic.hasWriteAccess(null, object)); } @Test public void testHasWriteAccessWithWriteAccess() { ParseUser user = mock(ParseUser.class); ParseACL acl = mock(ParseACL.class); when(acl.getWriteAccess(user)).thenReturn(true); ParseObject object = mock(ParseObject.class); when(object.getACL()).thenReturn(acl); assertTrue(OfflineQueryLogic.hasWriteAccess(user, object)); } @Test public void testHasWriteAccessWithNoWriteAccess() { ParseACL acl = mock(ParseACL.class); when(acl.getPublicReadAccess()).thenReturn(false); ParseObject object = mock(ParseObject.class); when(object.getACL()).thenReturn(acl); assertFalse(OfflineQueryLogic.hasWriteAccess(null, object)); } //endregion //region createMatcher @Test public void testMatcherWithNoReadAccess() throws ParseException { OfflineQueryLogic logic = new OfflineQueryLogic(null); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .build(); ParseACL acl = new ParseACL(); acl.setPublicReadAccess(false); ParseObject object = new ParseObject("TestObject"); object.setACL(acl); ParseUser user = mock(ParseUser.class); when(user.getObjectId()).thenReturn("test"); assertFalse(matches(logic, query, object, user)); } @Test public void testSimpleMatcher() throws ParseException { OfflineQueryLogic logic = new OfflineQueryLogic(null); ParseObject objectA = new ParseObject("TestObject"); objectA.put("value", "A"); objectA.put("foo", "bar"); ParseObject objectB = new ParseObject("TestObject"); objectB.put("value", "B"); objectB.put("foo", "bar"); ParseQuery.State<ParseObject> queryA = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", "A") .whereEqualTo("foo", "bar") .build(); assertTrue(matches(logic, queryA, objectA)); assertFalse(matches(logic, queryA, objectB)); } @Test public void testOrMatcher() throws Exception { ParseObject objectA = new ParseObject("TestObject"); objectA.put("value", "A"); ParseObject objectB = new ParseObject("TestObject"); objectB.put("value", "B"); ParseQuery.State<ParseObject> query = ParseQuery.State.Builder.or(Arrays.asList( new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", "A"), new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", "B") )).build(); OfflineQueryLogic logic = new OfflineQueryLogic(null); assertTrue(matches(logic, query, objectA)); assertTrue(matches(logic, query, objectB)); } @Test public void testAndMatcher() throws Exception { ParseObject objectA = new ParseObject("TestObject"); objectA.put("foo", "bar"); ParseObject objectB = new ParseObject("TestObject"); objectB.put("baz", "qux"); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$ne", "bar") .addCondition("baz", "$ne", "qux") .build(); OfflineQueryLogic logic = new OfflineQueryLogic(null); assertFalse(matches(logic, query, objectA)); assertFalse(matches(logic, query, objectB)); } // TODO(grantland): testRelationMatcher() //endregion //region matchesEquals @Test public void testMatchesEqualsWithGeoPoint() throws Exception { ParseGeoPoint point = new ParseGeoPoint(37.774929f, -122.419416f); // SF ParseObject object = new ParseObject("TestObject"); object.put("point", point); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("point", point) .build(); assertTrue(matches(logic, query, object)); // Test lat query = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("point", new ParseGeoPoint(37.774929f, -74.005941f)) .build(); assertFalse(matches(logic, query, object)); // Test lng query = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("point", new ParseGeoPoint(40.712784f, -122.419416f)) .build(); assertFalse(matches(logic, query, object)); // Not GeoPoint object = new ParseObject("TestObject"); object.put("point", "A"); assertFalse(matches(logic, query, object)); } @Test public void testMatchesEqualsWithNumbers() throws ParseException { OfflineQueryLogic logic = new OfflineQueryLogic(null); ParseQuery.State<ParseObject> iQuery = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", /* (int) */ 5) .build(); ParseObject iObject = new ParseObject("TestObject"); iObject.put("value", /* (int) */ 5); assertTrue(matches(logic, iQuery, iObject)); ParseObject object = new ParseObject("TestObject"); object.put("value", "string"); assertFalse(matches(logic, iQuery, object)); ParseObject noMatch = new ParseObject("TestObject"); noMatch.put("value", 6); assertFalse(matches(logic, iQuery, noMatch)); } @Test public void testMatchesEqualsNull() throws ParseException { OfflineQueryLogic logic = new OfflineQueryLogic(null); ParseObject object = new ParseObject("TestObject"); object.put("value", "test"); ParseObject nullObject = new ParseObject("TestObject"); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", "test") .build(); ParseQuery.State<ParseObject> nullQuery = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", null) .build(); assertTrue(matches(logic, query, object)); assertFalse(matches(logic, query, nullObject)); assertFalse(matches(logic, nullQuery, object)); assertTrue(matches(logic, nullQuery, nullObject)); } //endregion @Test public void testMatchesIn() throws ParseException { ParseObject object = new ParseObject("TestObject"); object.put("foo", "bar"); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$in", Arrays.asList("bar", "baz")) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$in", Collections.singletonList("qux")) .build(); assertFalse(matches(logic, query, object)); // Non-existant key object = new ParseObject("TestObject"); assertFalse(matches(logic, query, object)); object.put("foo", JSONObject.NULL); assertFalse(matches(logic, query, object)); } @Test public void testMatchesAll() throws Exception { ParseObject object = new ParseObject("TestObject"); object.put("foo", Arrays.asList("foo", "bar")); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$all", Arrays.asList("foo", "bar")) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$all", Arrays.asList("foo", "bar", "qux")) .build(); assertFalse(matches(logic, query, object)); // Non-existant key object = new ParseObject("TestObject"); assertFalse(matches(logic, query, object)); object.put("foo", JSONObject.NULL); assertFalse(matches(logic, query, object)); thrown.expect(IllegalArgumentException.class); object.put("foo", "bar"); assertFalse(matches(logic, query, object)); } @Test public void testMatchesNearSphere() throws Exception { ParseGeoPoint fb = new ParseGeoPoint(37.481689f, -122.154949f); ParseGeoPoint sf = new ParseGeoPoint(37.774929f, -122.419416f); ParseObject object = new ParseObject("TestObject"); object.put("point", fb); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("TestObject") .whereNear("point", fb) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("TestObject") .whereNear("point", sf) .maxDistance("point", 0.00628) .build(); assertFalse(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("TestObject") .whereNear("point", sf) .maxDistance("point", 0.00629) .build(); assertTrue(matches(logic, query, object)); // Non-existant key object = new ParseObject("TestObject"); assertFalse(matches(logic, query, object)); } //region matchesWithin @Test public void testMatchesWithinFailureInternationalDateLine() throws ParseException { ParseGeoPoint fb = new ParseGeoPoint(37.481689f, -122.154949f); ParseGeoPoint sf = new ParseGeoPoint(37.774929f, -122.419416f); ParseGeoPoint sj = new ParseGeoPoint(37.338208f, -121.886329f); ParseObject object = new ParseObject("TestObject"); object.put("point", fb); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); thrown.expect(ParseException.class); thrown.expect(hasParseErrorCode(ParseException.INVALID_QUERY)); thrown.expectMessage("whereWithinGeoBox queries cannot cross the International Date Line."); query = new ParseQuery.State.Builder<>("TestObject") .whereWithin("point", sj, sf) .build(); matches(logic, query, object); } @Test public void testMatchesWithinFailureSwapped() throws Exception { ParseGeoPoint fb = new ParseGeoPoint(37.481689f, -122.154949f); ParseGeoPoint sf = new ParseGeoPoint(37.774929f, -122.419416f); ParseGeoPoint sj = new ParseGeoPoint(37.338208f, -121.886329f); ParseObject object = new ParseObject("TestObject"); object.put("point", fb); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); thrown.expect(ParseException.class); thrown.expect(hasParseErrorCode(ParseException.INVALID_QUERY)); thrown.expectMessage( "The southwest corner of a geo box must be south of the northeast corner."); query = new ParseQuery.State.Builder<>("TestObject") .whereWithin("point", sf, sj) .build(); matches(logic, query, object); } @Test public void testMatchesWithinFailure180() throws Exception { ParseGeoPoint fb = new ParseGeoPoint(37.481689f, -122.154949f); ParseGeoPoint sf = new ParseGeoPoint(37.774929f, -122.419416f); ParseGeoPoint beijing = new ParseGeoPoint(39.904211f, 116.407395f); ParseObject object = new ParseObject("TestObject"); object.put("point", fb); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); thrown.expect(ParseException.class); thrown.expect(hasParseErrorCode(ParseException.INVALID_QUERY)); thrown.expectMessage("Geo box queries larger than 180 degrees in longitude are not supported. " + "Please check point order."); query = new ParseQuery.State.Builder<>("TestObject") .whereWithin("point", sf, beijing) .build(); matches(logic, query, object); } @Test public void testMatchesWithin() throws ParseException { ParseGeoPoint fb = new ParseGeoPoint(37.481689f, -122.154949f); ParseGeoPoint sunset = new ParseGeoPoint(37.746731f, -122.486349f); ParseGeoPoint soma = new ParseGeoPoint(37.778519f, -122.40564f); ParseGeoPoint twinPeaks = new ParseGeoPoint(37.754407f, -122.447684f); ParseObject object = new ParseObject("TestObject"); object.put("point", fb); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); object.put("point", twinPeaks); query = new ParseQuery.State.Builder<>("TestObject") .whereWithin("point", sunset, soma) .build(); assertTrue(matches(logic, query, object)); object.put("point", fb); assertFalse(matches(logic, query, object)); // Non-existant key object = new ParseObject("TestObject"); assertFalse(matches(logic, query, object)); } //endregion //region compare @Test public void testCompareList() throws Exception { ParseObject object = new ParseObject("SomeObject"); List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); object.put("list", list); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("SomeObject") .whereEqualTo("list", 2) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("SomeObject") .whereEqualTo("list", 4) .build(); assertFalse(matches(logic, query, object)); } @Test public void testCompareJSONArray() throws Exception { ParseObject object = new ParseObject("SomeObject"); JSONArray array = new JSONArray(); array.put(1); array.put(2); array.put(3); object.put("array", array); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("SomeObject") .whereEqualTo("array", 2) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("SomeObject") .whereEqualTo("array", 4) .build(); assertFalse(matches(logic, query, object)); } //endregion //region compareTo @Test public void testCompareToNumber() throws Exception { ParseObject object = new ParseObject("TestObject"); object.put("value", 5); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", /* (int) */ 5) .build(); assertTrue(matches(logic, query, object)); object.put("value", 6); assertFalse(matches(logic, query, object)); object.put("value", 5); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("value", "$lt", 6) .build(); assertTrue(matches(logic, query, object)); object.put("value", 6); assertFalse(matches(logic, query, object)); object.put("value", 5); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("value", "$gt", 4) .build(); assertTrue(matches(logic, query, object)); object.put("value", 4); assertFalse(matches(logic, query, object)); object.put("value", 5); // TODO(grantland): Move below to NumbersTest ParseObject iObject = new ParseObject("TestObject"); iObject.put("value", /* (int) */ 5); ParseObject dObject = new ParseObject("TestObject"); dObject.put("value", /* (double) */ 5.0); ParseObject fObject = new ParseObject("TestObject"); fObject.put("value", /* (float) */ 5.0f); ParseObject lObject = new ParseObject("TestObject"); lObject.put("value", (long) 5); ParseQuery.State<ParseObject> iQuery = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", /* (int) */ 5) .build(); ParseQuery.State<ParseObject> dQuery = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", /* (double) */ 5.0) .build(); ParseQuery.State<ParseObject> fQuery = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", /* (float) */ 5.0f) .build(); ParseQuery.State<ParseObject> lQuery = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("value", (long) 5) .build(); assertTrue(matches(logic, iQuery, iObject)); assertTrue(matches(logic, iQuery, dObject)); assertTrue(matches(logic, iQuery, fObject)); assertTrue(matches(logic, iQuery, lObject)); assertTrue(matches(logic, dQuery, iObject)); assertTrue(matches(logic, dQuery, dObject)); assertTrue(matches(logic, dQuery, fObject)); assertTrue(matches(logic, dQuery, lObject)); assertTrue(matches(logic, fQuery, iObject)); assertTrue(matches(logic, fQuery, dObject)); assertTrue(matches(logic, fQuery, fObject)); assertTrue(matches(logic, fQuery, lObject)); assertTrue(matches(logic, lQuery, iObject)); assertTrue(matches(logic, lQuery, dObject)); assertTrue(matches(logic, lQuery, fObject)); assertTrue(matches(logic, lQuery, lObject)); } @Test public void testCompareToDate() throws Exception { Date date = new Date(); Date before = new Date(date.getTime() - 20); Date after = new Date(date.getTime() + 20); ParseObject object = new ParseObject("TestObject"); object.put("date", date); ParseQuery.State<ParseObject> query; OfflineQueryLogic logic = new OfflineQueryLogic(null); query = new ParseQuery.State.Builder<>("TestObject") .whereEqualTo("date", date) .build(); assertTrue(matches(logic, query, object)); object.put("date", after); assertFalse(matches(logic, query, object)); object.put("date", date); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("date", "$lt", after) .build(); assertTrue(matches(logic, query, object)); object.put("date", after); assertFalse(matches(logic, query, object)); object.put("date", date); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("date", "$gt", before) .build(); assertTrue(matches(logic, query, object)); object.put("date", before); assertFalse(matches(logic, query, object)); object.put("date", after); } //endregion //region Sort @Test public void testSortInvalidKey() throws ParseException { ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .addAscendingOrder("_test") .build(); thrown.expect(ParseException.class); OfflineQueryLogic.sort(null, query); } @Test public void testSortWithNoOrder() throws ParseException { ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .build(); OfflineQueryLogic.sort(null, query); } @Test public void testSortWithGeoQuery() throws ParseException { ParseGeoPoint fb = new ParseGeoPoint(37.481689f, -122.154949f); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .whereNear("point", fb) .build(); List<ParseObject> objects = new ArrayList<>(); ParseObject object; object = new ParseObject("TestObject"); object.put("name", "sf"); object.put("point", new ParseGeoPoint(37.774929f, -122.419416f)); objects.add(object); object = new ParseObject("TestObject"); object.put("name", "ny"); object.put("point", new ParseGeoPoint(40.712784f, -74.005941f)); objects.add(object); object = new ParseObject("TestObject"); object.put("name", "mpk"); object.put("point", new ParseGeoPoint(37.452960f, -122.181725f)); objects.add(object); OfflineQueryLogic.sort(objects, query); assertEquals("mpk", objects.get(0).getString("name")); assertEquals("sf", objects.get(1).getString("name")); assertEquals("ny", objects.get(2).getString("name")); } @Test public void testSortDescending() throws ParseException { ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .addDescendingOrder("name") .build(); List<ParseObject> objects = new ArrayList<>(); ParseObject object; object = new ParseObject("TestObject"); object.put("name", "grantland"); objects.add(object); object = new ParseObject("TestObject"); object.put("name", "nikita"); objects.add(object); object = new ParseObject("TestObject"); object.put("name", "listiarso"); objects.add(object); OfflineQueryLogic.sort(objects, query); assertEquals("nikita", objects.get(0).getString("name")); assertEquals("listiarso", objects.get(1).getString("name")); assertEquals("grantland", objects.get(2).getString("name")); } @Test public void testQuerySortNumber() throws ParseException { ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .addAscendingOrder("key") .build(); List<ParseObject> results = generateParseObjects("key", new Object[]{ /* (int) */ 8, /* (double) */ 7.0, /* (float) */ 6.0f, (long) 5 }); OfflineQueryLogic.sort(results, query); int last = 0; for (ParseObject result : results) { int current = result.getInt("key"); assertTrue(current > last); last = current; } } @Test public void testQuerySortNull() throws ParseException { ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .addAscendingOrder("key") .build(); List<ParseObject> results = generateParseObjects("key", new Object[]{ null, "value", null }); OfflineQueryLogic.sort(results, query); assertEquals(0, results.get(0).getInt("id")); assertEquals(2, results.get(1).getInt("id")); assertEquals(1, results.get(2).getInt("id")); } @Test public void testQuerySortDifferentTypes() throws ParseException { ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .addAscendingOrder("key") .build(); List<ParseObject> results = generateParseObjects("key", new Object[]{ "string", 5 }); thrown.expect(IllegalArgumentException.class); OfflineQueryLogic.sort(results, query); } //endregion //region fetchIncludes @Test public void testFetchIncludesParseObject() throws ParseException { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseSQLiteDatabase db = mock(ParseSQLiteDatabase.class); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo") .build(); ParseObject object = new ParseObject("TestObject"); ParseObject unfetchedObject = new ParseObject("TestObject"); object.put("foo", unfetchedObject); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, db)); verify(store).fetchLocallyAsync(object, db); verify(store).fetchLocallyAsync(unfetchedObject, db); verifyNoMoreInteractions(store); } @Test public void testFetchIncludesCollection() throws ParseException { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseSQLiteDatabase db = mock(ParseSQLiteDatabase.class); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo") .build(); ParseObject object = mock(ParseObject.class); ParseObject unfetchedObject = mock(ParseObject.class); Collection<ParseObject> objects = new ArrayList<>(); objects.add(unfetchedObject); when(object.get("foo")).thenReturn(objects); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, db)); verify(store).fetchLocallyAsync(object, db); verify(store).fetchLocallyAsync(unfetchedObject, db); verifyNoMoreInteractions(store); } @Test public void testFetchIncludesJSONArray() throws ParseException { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseSQLiteDatabase db = mock(ParseSQLiteDatabase.class); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo") .build(); ParseObject object = mock(ParseObject.class); ParseObject unfetchedObject = mock(ParseObject.class); JSONArray objects = new JSONArray(); objects.put(unfetchedObject); when(object.get("foo")).thenReturn(objects); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, db)); verify(store).fetchLocallyAsync(object, db); verify(store).fetchLocallyAsync(unfetchedObject, db); verifyNoMoreInteractions(store); } @Test public void testFetchIncludesMap() throws ParseException { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseSQLiteDatabase db = mock(ParseSQLiteDatabase.class); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo.bar") .build(); ParseObject object = mock(ParseObject.class); ParseObject unfetchedObject = mock(ParseObject.class); Map<String, ParseObject> objects = new HashMap<>(); objects.put("bar", unfetchedObject); when(object.get("foo")).thenReturn(objects); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, db)); verify(store).fetchLocallyAsync(object, db); verify(store).fetchLocallyAsync(unfetchedObject, db); verifyNoMoreInteractions(store); } @Test public void testFetchIncludesJSONObject() throws Exception { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseSQLiteDatabase db = mock(ParseSQLiteDatabase.class); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo.bar") .build(); ParseObject object = mock(ParseObject.class); ParseObject unfetchedObject = mock(ParseObject.class); JSONObject objects = new JSONObject(); objects.put("bar", unfetchedObject); when(object.get("foo")).thenReturn(objects); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, db)); verify(store).fetchLocallyAsync(object, db); verify(store).fetchLocallyAsync(unfetchedObject, db); verifyNoMoreInteractions(store); } @Test public void testFetchIncludesNull() throws ParseException { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo") .build(); ParseObject object = new ParseObject("TestObject"); object.put("foo", JSONObject.NULL); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, null)); // only itself verify(store, times(1)) .fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class)); } @Test public void testFetchIncludesNonParseObject() throws ParseException { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo") .build(); ParseObject object = new ParseObject("TestObject"); object.put("foo", 1); thrown.expect(ParseException.class); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, null)); // only itself verify(store, times(1)) .fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class)); } @Test public void testFetchIncludesDoesNotExist() throws ParseException { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo") .build(); ParseObject object = new ParseObject("TestObject"); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, null)); // only itself verify(store, times(1)) .fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class)); } @Test public void testFetchIncludesNestedNull() throws Exception { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo.bar") .build(); ParseObject object = new ParseObject("TestObject"); object.put("foo", JSONObject.NULL); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, null)); // only itself verify(store, times(1)) .fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class)); } @Test public void testFetchIncludesNestedNonParseObject() throws Exception { OfflineStore store = mock(OfflineStore.class); when(store.fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class))) .thenReturn(Task.<ParseObject>forResult(null)); ParseQuery.State<ParseObject> query = new ParseQuery.State.Builder<>("TestObject") .include("foo.bar") .build(); ParseObject object = new ParseObject("TestObject"); object.put("foo", 1); thrown.expect(IllegalStateException.class); ParseTaskUtils.wait(OfflineQueryLogic.fetchIncludesAsync(store, object, query, null)); // only itself verify(store, times(1)) .fetchLocallyAsync(any(ParseObject.class), any(ParseSQLiteDatabase.class)); } //endregion private static <T extends ParseObject> boolean matches( OfflineQueryLogic logic, ParseQuery.State<T> query, T object) throws ParseException { return matches(logic, query, object, null); } private static <T extends ParseObject> boolean matches( OfflineQueryLogic logic, ParseQuery.State<T> query, T object, ParseUser user) throws ParseException { Task<Boolean> task = logic.createMatcher(query, user).matchesAsync(object, null); return ParseTaskUtils.wait(task); } private static List<ParseObject> generateParseObjects( String key, Object[] values) { List<ParseObject> objects = new ArrayList<>(); int i = 0; for (Object value : values) { ParseObject object = ParseObject.create("TestObject"); object.put("id", i); if (value != null) { object.put(key, value); } objects.add(object); i++; } return objects; } }