Skip to content

Commit 80a40bd

Browse files
committed
GetOptions for controlling offline behaviour.
Add option to allow the user to control where DocumentReference.getDocument() fetches from. By default, it fetches from the server (if possible) and falls back to the local cache. It's now possible to alternatively fetch from the local cache only, or to fetch from the server only (though in the server only case, latency compensation is still enabled). Still TODO: Also enable this for CollectionReferenece.getDocuments().
1 parent 4acdbdb commit 80a40bd

File tree

13 files changed

+499
-7
lines changed

13 files changed

+499
-7
lines changed

Firestore/Example/Firestore.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
6060
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
6161
6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
62+
61CC13FA2007D0C90021F5BF /* FIRGetOptionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 61CC13F82007D0C20021F5BF /* FIRGetOptionsTests.m */; };
6263
61E1D8B11FCF6C5700753285 /* StringViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 61E1D8AF1FCF6AF500753285 /* StringViewTests.mm */; };
6364
6ED54761B845349D43DB6B78 /* Pods_Firestore_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75A6FE51C1A02DF38F62FAAD /* Pods_Firestore_Example.framework */; };
6465
71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; };
@@ -235,6 +236,7 @@
235236
6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
236237
6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
237238
6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
239+
61CC13F82007D0C20021F5BF /* FIRGetOptionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRGetOptionsTests.m; sourceTree = "<group>"; };
238240
61E1D8AF1FCF6AF500753285 /* StringViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = StringViewTests.mm; sourceTree = "<group>"; };
239241
69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
240242
71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
@@ -723,6 +725,7 @@
723725
DE51B1BC1F0D48AC0013853F /* API */ = {
724726
isa = PBXGroup;
725727
children = (
728+
61CC13F82007D0C20021F5BF /* FIRGetOptionsTests.m */,
726729
DE51B1BD1F0D48AC0013853F /* FIRCursorTests.m */,
727730
DE51B1BE1F0D48AC0013853F /* FIRDatabaseTests.m */,
728731
DE51B1BF1F0D48AC0013853F /* FIRFieldsTests.m */,
@@ -1281,6 +1284,7 @@
12811284
54E928251F33953400C1953E /* FSTEventAccumulator.m in Sources */,
12821285
DE03B2ED1F214BA200A30B9C /* FSTSmokeTests.m in Sources */,
12831286
DE03B2F31F214BAA00A30B9C /* FIRQueryTests.m in Sources */,
1287+
61CC13FA2007D0C90021F5BF /* FIRGetOptionsTests.m in Sources */,
12841288
DE03B35E1F21586C00A30B9C /* FSTHelpers.m in Sources */,
12851289
DE03B2F51F214BAA00A30B9C /* FIRTypeTests.m in Sources */,
12861290
DE03B2EF1F214BAA00A30B9C /* FIRCursorTests.m in Sources */,

Firestore/Example/Tests/Core/FSTQueryListenerTests.m

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ - (void)testDoesNotRaiseEventsForMetadataChangesUnlessSpecified {
172172

173173
FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
174174
includeDocumentMetadataChanges:NO
175-
waitForSyncWhenOnline:NO];
175+
waitForSyncWhenOnline:NO
176+
getOptions:[FIRGetOptions defaultOptions]];
176177

177178
FSTQueryListener *filteredListener =
178179
[self listenToQuery:query accumulatingSnapshots:filteredAccum];
@@ -212,7 +213,8 @@ - (void)testRaisesDocumentMetadataEventsOnlyWhenSpecified {
212213

213214
FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO
214215
includeDocumentMetadataChanges:YES
215-
waitForSyncWhenOnline:NO];
216+
waitForSyncWhenOnline:NO
217+
getOptions:[FIRGetOptions defaultOptions]];
216218

217219
FSTQueryListener *filteredListener =
218220
[self listenToQuery:query accumulatingSnapshots:filteredAccum];
@@ -262,7 +264,9 @@ - (void)testRaisesQueryMetadataEventsOnlyWhenHasPendingWritesOnTheQueryChanges {
262264

263265
FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
264266
includeDocumentMetadataChanges:NO
265-
waitForSyncWhenOnline:NO];
267+
waitForSyncWhenOnline:NO
268+
getOptions:[FIRGetOptions defaultOptions]];
269+
266270
FSTQueryListener *fullListener =
267271
[self listenToQuery:query options:options accumulatingSnapshots:fullAccum];
268272

@@ -329,7 +333,9 @@ - (void)testWillWaitForSyncIfOnline {
329333
[self listenToQuery:query
330334
options:[[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO
331335
includeDocumentMetadataChanges:NO
332-
waitForSyncWhenOnline:YES]
336+
waitForSyncWhenOnline:YES
337+
getOptions:[FIRGetOptions defaultOptions]]
338+
333339
accumulatingSnapshots:events];
334340

335341
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
@@ -372,7 +378,9 @@ - (void)testWillRaiseInitialEventWhenGoingOffline {
372378
[self listenToQuery:query
373379
options:[[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:NO
374380
includeDocumentMetadataChanges:NO
375-
waitForSyncWhenOnline:YES]
381+
waitForSyncWhenOnline:YES
382+
getOptions:[FIRGetOptions defaultOptions]]
383+
376384
accumulatingSnapshots:events];
377385

378386
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/*
2+
* Copyright 2018 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
@import FirebaseFirestore;
18+
19+
#import <FirebaseFirestore/FIRFirestore.h>
20+
#import <XCTest/XCTest.h>
21+
22+
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
23+
#import "Firestore/Source/API/FIRFirestore+Internal.h"
24+
#import "Firestore/Source/Core/FSTFirestoreClient.h"
25+
26+
@interface FIRGetOptionsTests : FSTIntegrationTestCase
27+
@end
28+
29+
@implementation FIRGetOptionsTests
30+
31+
- (void)testGetDocumentWhileOnlineWithDefaultGetOptions {
32+
FIRDocumentReference *doc = [self documentRef];
33+
34+
// set document to a known value
35+
NSDictionary<NSString *, id> *initialData = @{@"key" : @"value"};
36+
[self writeDocumentRef:doc data:initialData];
37+
38+
// get doc and ensure that it exists, is *not* from the cache, and matches
39+
// the initialData.
40+
FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
41+
XCTAssertTrue(result.exists);
42+
XCTAssertFalse(result.metadata.fromCache);
43+
XCTAssertEqualObjects(result.data, initialData);
44+
}
45+
46+
- (void)testGetDocumentWhileOfflineWithDefaultGetOptions {
47+
FIRDocumentReference *doc = [self documentRef];
48+
49+
// set document to a known value
50+
NSDictionary<NSString *, id> *initialData = @{@"key1" : @"value1"};
51+
[self writeDocumentRef:doc data:initialData];
52+
53+
// go offline for the rest of this test
54+
[self disableNetwork];
55+
56+
// update the doc (though don't wait for a server response. We're offline; so
57+
// that ain't happening!). This allows us to further distinguished cached vs
58+
// server responses below.
59+
NSDictionary<NSString *, id> *newData = @{@"key2" : @"value2"};
60+
[doc setData:newData completion:^(NSError *_Nullable error) {
61+
XCTAssertTrue(false, "Because we're offline, this should never occur.");
62+
}];
63+
64+
// get doc and ensure it exists, *is* from the cache, and matches the
65+
// newData.
66+
FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
67+
XCTAssertTrue(result.exists);
68+
XCTAssertTrue(result.metadata.fromCache);
69+
XCTAssertEqualObjects(result.data, newData);
70+
}
71+
72+
- (void)testGetDocumentWhileOnlineCacheOnly {
73+
FIRDocumentReference *doc = [self documentRef];
74+
75+
// set document to a known value
76+
NSDictionary<NSString *, id> *initialData = @{@"key" : @"value"};
77+
[self writeDocumentRef:doc data:initialData];
78+
79+
// get doc and ensure that it exists, *is* from the cache, and matches
80+
// the initialData.
81+
FIRDocumentSnapshot *result = [self readDocumentForRef:doc getOptions:[[FIRGetOptions alloc] initWithSource:FIRCache]];
82+
XCTAssertTrue(result.exists);
83+
XCTAssertTrue(result.metadata.fromCache);
84+
XCTAssertEqualObjects(result.data, initialData);
85+
}
86+
87+
- (void)testGetDocumentWhileOfflineCacheOnly {
88+
FIRDocumentReference *doc = [self documentRef];
89+
90+
// set document to a known value
91+
NSDictionary<NSString *, id> *initialData = @{@"key1" : @"value1"};
92+
[self writeDocumentRef:doc data:initialData];
93+
94+
// go offline for the rest of this test
95+
[self disableNetwork];
96+
97+
// update the doc (though don't wait for a server response. We're offline; so
98+
// that ain't happening!). This allows us to further distinguished cached vs
99+
// server responses below.
100+
NSDictionary<NSString *, id> *newData = @{@"key2" : @"value2"};
101+
[doc setData:newData completion:^(NSError *_Nullable error) {
102+
XCTFail("Because we're offline, this should never occur.");
103+
}];
104+
105+
// get doc and ensure it exists, *is* from the cache, and matches the
106+
// newData.
107+
FIRDocumentSnapshot *result = [self readDocumentForRef:doc getOptions:[[FIRGetOptions alloc] initWithSource:FIRCache]];
108+
XCTAssertTrue(result.exists);
109+
XCTAssertTrue(result.metadata.fromCache);
110+
XCTAssertEqualObjects(result.data, newData);
111+
}
112+
113+
- (void)testGetDocumentWhileOnlineServerOnly {
114+
FIRDocumentReference *doc = [self documentRef];
115+
116+
// set document to a known value
117+
NSDictionary<NSString *, id> *initialData = @{@"key" : @"value"};
118+
[self writeDocumentRef:doc data:initialData];
119+
120+
// get doc and ensure that it exists, is *not* from the cache, and matches
121+
// the initialData.
122+
FIRDocumentSnapshot *result = [self readDocumentForRef:doc getOptions:[[FIRGetOptions alloc] initWithSource:FIRServer]];
123+
XCTAssertTrue(result.exists);
124+
XCTAssertFalse(result.metadata.fromCache);
125+
XCTAssertEqualObjects(result.data, initialData);
126+
}
127+
128+
- (void)testGetDocumentWhileOfflineServerOnly {
129+
FIRDocumentReference *doc = [self documentRef];
130+
131+
// set document to a known value
132+
NSDictionary<NSString *, id> *initialData = @{@"key1" : @"value1"};
133+
[self writeDocumentRef:doc data:initialData];
134+
135+
// go offline for the rest of this test
136+
[self disableNetwork];
137+
138+
// update the doc (though don't wait for a server response. We're offline; so
139+
// that ain't happening!). This allows us to further distinguished cached vs
140+
// server responses below.
141+
NSDictionary<NSString *, id> *newData = @{@"key2" : @"value2"};
142+
[doc setData:newData completion:^(NSError *_Nullable error) {
143+
XCTAssertTrue(false, "Because we're offline, this should never occur.");
144+
}];
145+
146+
// attempt to get doc and ensure it cannot be retreived
147+
XCTestExpectation *failedGetDocCompletion = [self expectationWithDescription:@"failedGetDoc"];
148+
[doc getDocumentWithOptions:[[FIRGetOptions alloc] initWithSource:FIRServer] completion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
149+
XCTAssertNotNil(error);
150+
XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
151+
XCTAssertEqual(error.code, FIRFirestoreErrorCodeUnavailable);
152+
[failedGetDocCompletion fulfill];
153+
}];
154+
[self awaitExpectations];
155+
}
156+
157+
- (void)testGetDocumentWhileOfflineWithDifferentGetOptions {
158+
FIRDocumentReference *doc = [self documentRef];
159+
160+
// set document to a known value
161+
NSDictionary<NSString *, id> *initialData = @{@"key1" : @"value1"};
162+
[self writeDocumentRef:doc data:initialData];
163+
164+
// go offline for the rest of this test
165+
[self disableNetwork];
166+
167+
// update the doc (though don't wait for a server response. We're offline; so
168+
// that ain't happening!). This allows us to further distinguished cached vs
169+
// server responses below.
170+
NSDictionary<NSString *, id> *newData = @{@"key2" : @"value2"};
171+
[doc setData:newData completion:^(NSError *_Nullable error) {
172+
XCTAssertTrue(false, "Because we're offline, this should never occur.");
173+
}];
174+
175+
// Create an initial listener for this query (to attempt to disrupt the gets below) and wait for the listener to be fully initialized before continuing.
176+
XCTestExpectation *listenerReady = [self expectationWithDescription:@"listenerReady"];
177+
[doc addSnapshotListener:^(FIRDocumentSnapshot *snapshot, NSError *error) {
178+
[listenerReady fulfill];
179+
}];
180+
[self awaitExpectations];
181+
182+
// get doc (from cache) and ensure it exists, *is* from the cache, and
183+
// matches the newData.
184+
FIRDocumentSnapshot *result = [self readDocumentForRef:doc getOptions:[[FIRGetOptions alloc] initWithSource:FIRCache]];
185+
XCTAssertTrue(result.exists);
186+
XCTAssertTrue(result.metadata.fromCache);
187+
XCTAssertEqualObjects(result.data, newData);
188+
189+
// attempt to get doc (with default get options)
190+
result = [self readDocumentForRef:doc getOptions:[[FIRGetOptions alloc] initWithSource:FIRDefault]];
191+
XCTAssertTrue(result.exists);
192+
XCTAssertTrue(result.metadata.fromCache);
193+
XCTAssertEqualObjects(result.data, newData);
194+
195+
// attempt to get doc (from the server) and ensure it cannot be retreived
196+
XCTestExpectation *failedGetDocCompletion = [self expectationWithDescription:@"failedGetDoc"];
197+
[doc getDocumentWithOptions:[[FIRGetOptions alloc] initWithSource:FIRServer] completion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
198+
XCTAssertNotNil(error);
199+
XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
200+
XCTAssertEqual(error.code, FIRFirestoreErrorCodeUnavailable);
201+
[failedGetDocCompletion fulfill];
202+
}];
203+
[self awaitExpectations];
204+
}
205+
206+
- (void)testGetNonExistingDocWhileOnlineWithDefaultGetOptions {
207+
FIRDocumentReference *doc = [self documentRef];
208+
209+
// get doc and ensure that it does not exist and is *not* from the cache.
210+
FIRDocumentSnapshot* snapshot = [self readDocumentForRef:doc];
211+
XCTAssertFalse(snapshot.exists);
212+
XCTAssertFalse(snapshot.metadata.fromCache);
213+
}
214+
215+
- (void)testGetNonExistingDocWhileOfflineWithDefaultGetOptions {
216+
FIRDocumentReference *doc = [self documentRef];
217+
218+
// go offline for the rest of this test
219+
[self disableNetwork];
220+
221+
// attempt to get doc. Currently, this is expected to fail. In the future, we
222+
// might consider adding support for negative cache hits so that we know
223+
// certain documents *don't* exist.
224+
XCTestExpectation *getNonExistingDocCompletion = [self expectationWithDescription:@"getNonExistingDoc"];
225+
[doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
226+
XCTAssertNotNil(error);
227+
XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
228+
XCTAssertEqual(error.code, FIRFirestoreErrorCodeUnavailable);
229+
[getNonExistingDocCompletion fulfill];
230+
}];
231+
[self awaitExpectations];
232+
}
233+
234+
- (void)testGetNonExistingDocWhileOnlineCacheOnly {
235+
FIRDocumentReference *doc = [self documentRef];
236+
237+
// attempt to get doc. Currently, this is expected to fail. In the future, we
238+
// might consider adding support for negative cache hits so that we know
239+
// certain documents *don't* exist.
240+
XCTestExpectation *getNonExistingDocCompletion = [self expectationWithDescription:@"getNonExistingDoc"];
241+
[doc getDocumentWithOptions:[[FIRGetOptions alloc] initWithSource:FIRCache] completion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
242+
XCTAssertNotNil(error);
243+
XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
244+
XCTAssertEqual(error.code, FIRFirestoreErrorCodeUnavailable);
245+
[getNonExistingDocCompletion fulfill];
246+
}];
247+
[self awaitExpectations];
248+
}
249+
250+
- (void)testGetNonExistingDocWhileOfflineCacheOnly {
251+
FIRDocumentReference *doc = [self documentRef];
252+
253+
// go offline for the rest of this test
254+
[self disableNetwork];
255+
256+
// attempt to get doc. Currently, this is expected to fail. In the future, we
257+
// might consider adding support for negative cache hits so that we know
258+
// certain documents *don't* exist.
259+
XCTestExpectation *getNonExistingDocCompletion = [self expectationWithDescription:@"getNonExistingDoc"];
260+
[doc getDocumentWithOptions:[[FIRGetOptions alloc] initWithSource:FIRCache] completion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
261+
XCTAssertNotNil(error);
262+
XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
263+
XCTAssertEqual(error.code, FIRFirestoreErrorCodeUnavailable);
264+
[getNonExistingDocCompletion fulfill];
265+
}];
266+
[self awaitExpectations];
267+
}
268+
269+
- (void)testGetNonExistingDocWhileOnlineServerOnly {
270+
FIRDocumentReference *doc = [self documentRef];
271+
272+
// get doc and ensure that it does not exist and is *not* from the cache.
273+
FIRDocumentSnapshot* snapshot = [self readDocumentForRef:doc getOptions:[[FIRGetOptions alloc] initWithSource:FIRServer]];
274+
XCTAssertFalse(snapshot.exists);
275+
XCTAssertFalse(snapshot.metadata.fromCache);
276+
}
277+
278+
- (void)testGetNonExistingDocWhileOfflineServerOnly {
279+
FIRDocumentReference *doc = [self documentRef];
280+
281+
// go offline for the rest of this test
282+
[self disableNetwork];
283+
284+
// attempt to get doc. Currently, this is expected to fail. In the future, we
285+
// might consider adding support for negative cache hits so that we know
286+
// certain documents *don't* exist.
287+
XCTestExpectation *getNonExistingDocCompletion = [self expectationWithDescription:@"getNonExistingDoc"];
288+
[doc getDocumentWithOptions:[[FIRGetOptions alloc] initWithSource:FIRServer] completion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
289+
XCTAssertNotNil(error);
290+
XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
291+
XCTAssertEqual(error.code, FIRFirestoreErrorCodeUnavailable);
292+
[getNonExistingDocCompletion fulfill];
293+
}];
294+
[self awaitExpectations];
295+
}
296+
297+
@end

Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.m

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,9 @@ - (FSTTargetID)addUserListenerWithQuery:(FSTQuery *)query {
243243
// TODO(dimond): Change spec tests to verify isFromCache on snapshots
244244
FSTListenOptions *options = [[FSTListenOptions alloc] initWithIncludeQueryMetadataChanges:YES
245245
includeDocumentMetadataChanges:YES
246-
waitForSyncWhenOnline:NO];
246+
waitForSyncWhenOnline:NO
247+
getOptions:[FIRGetOptions defaultOptions]];
248+
247249
FSTQueryListener *listener = [[FSTQueryListener alloc]
248250
initWithQuery:query
249251
options:options

0 commit comments

Comments
 (0)