FIRServerTimestampTests.mm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /*
  2. * Copyright 2017 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. #import <FirebaseFirestore/FirebaseFirestore.h>
  17. #import <XCTest/XCTest.h>
  18. #import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
  19. #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
  20. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  21. #import "Firestore/Source/Core/FSTFirestoreClient.h"
  22. @interface FIRServerTimestampTests : FSTIntegrationTestCase
  23. @end
  24. @implementation FIRServerTimestampTests {
  25. // Data written in tests via set.
  26. NSDictionary *_setData;
  27. // Base and update data used for update tests.
  28. NSDictionary *_initialData;
  29. NSDictionary *_updateData;
  30. // A document reference to read and write to.
  31. FIRDocumentReference *_docRef;
  32. // Accumulator used to capture events during the test.
  33. FSTEventAccumulator *_accumulator;
  34. // Listener registration for a listener maintained during the course of the test.
  35. id<FIRListenerRegistration> _listenerRegistration;
  36. }
  37. - (void)setUp {
  38. [super setUp];
  39. // Data written in tests via set.
  40. _setData = @{
  41. @"a" : @42,
  42. @"when" : [FIRFieldValue fieldValueForServerTimestamp],
  43. @"deep" : @{@"when" : [FIRFieldValue fieldValueForServerTimestamp]}
  44. };
  45. // Base and update data used for update tests.
  46. _initialData = @{ @"a" : @42 };
  47. _updateData = @{
  48. @"when" : [FIRFieldValue fieldValueForServerTimestamp],
  49. @"deep" : @{@"when" : [FIRFieldValue fieldValueForServerTimestamp]}
  50. };
  51. _docRef = [self documentRef];
  52. _accumulator = [FSTEventAccumulator accumulatorForTest:self];
  53. _listenerRegistration = [_docRef addSnapshotListener:_accumulator.valueEventHandler];
  54. // Wait for initial nil snapshot to avoid potential races.
  55. FIRDocumentSnapshot *initialSnapshot = [_accumulator awaitEventWithName:@"initial event"];
  56. XCTAssertFalse(initialSnapshot.exists);
  57. }
  58. - (void)tearDown {
  59. [_listenerRegistration remove];
  60. [super tearDown];
  61. }
  62. #pragma mark - Test Helpers
  63. /** Returns the expected data, with the specified timestamp substituted in. */
  64. - (NSDictionary *)expectedDataWithTimestamp:(nullable id)timestamp {
  65. return @{ @"a" : @42, @"when" : timestamp, @"deep" : @{@"when" : timestamp} };
  66. }
  67. /** Writes _initialData and waits for the corresponding snapshot. */
  68. - (void)writeInitialData {
  69. [self writeDocumentRef:_docRef data:_initialData];
  70. FIRDocumentSnapshot *initialDataSnap = [_accumulator awaitEventWithName:@"Initial data event."];
  71. XCTAssertEqualObjects(initialDataSnap.data, _initialData);
  72. }
  73. /** Waits for a snapshot with local writes. */
  74. - (FIRDocumentSnapshot *)waitForLocalEvent {
  75. FIRDocumentSnapshot *snapshot;
  76. do {
  77. snapshot = [_accumulator awaitEventWithName:@"Local event."];
  78. } while (!snapshot.metadata.hasPendingWrites);
  79. return snapshot;
  80. }
  81. /** Waits for a snapshot that has no pending writes */
  82. - (FIRDocumentSnapshot *)waitForRemoteEvent {
  83. FIRDocumentSnapshot *snapshot;
  84. do {
  85. snapshot = [_accumulator awaitEventWithName:@"Remote event."];
  86. } while (snapshot.metadata.hasPendingWrites);
  87. return snapshot;
  88. }
  89. /** Verifies a snapshot containing _setData but with NSNull for the timestamps. */
  90. - (void)verifyTimestampsAreNullInSnapshot:(FIRDocumentSnapshot *)snapshot {
  91. XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:[NSNull null]]);
  92. }
  93. /** Verifies a snapshot containing _setData but with a local estimate for the timestamps. */
  94. - (void)verifyTimestampsAreEstimatedInSnapshot:(FIRDocumentSnapshot *)snapshot {
  95. id timestamp =
  96. [snapshot valueForField:@"when" serverTimestampBehavior:FIRServerTimestampBehaviorEstimate];
  97. XCTAssertTrue([timestamp isKindOfClass:[FIRTimestamp class]]);
  98. XCTAssertEqualObjects(
  99. [snapshot dataWithServerTimestampBehavior:FIRServerTimestampBehaviorEstimate],
  100. [self expectedDataWithTimestamp:timestamp]);
  101. }
  102. /**
  103. * Verifies a snapshot containing _setData but using the previous field value for server
  104. * timestamps.
  105. */
  106. - (void)verifyTimestampsInSnapshot:(FIRDocumentSnapshot *)snapshot
  107. fromPreviousSnapshot:(nullable FIRDocumentSnapshot *)previousSnapshot {
  108. if (previousSnapshot == nil) {
  109. XCTAssertEqualObjects(
  110. [snapshot dataWithServerTimestampBehavior:FIRServerTimestampBehaviorPrevious],
  111. [self expectedDataWithTimestamp:[NSNull null]]);
  112. } else {
  113. XCTAssertEqualObjects(
  114. [snapshot dataWithServerTimestampBehavior:FIRServerTimestampBehaviorPrevious],
  115. [self expectedDataWithTimestamp:previousSnapshot[@"when"]]);
  116. }
  117. }
  118. /** Verifies a snapshot containing _setData but with resolved server timestamps. */
  119. - (void)verifySnapshotWithResolvedTimestamps:(FIRDocumentSnapshot *)snapshot {
  120. XCTAssertTrue(snapshot.exists);
  121. FIRTimestamp *when = snapshot[@"when"];
  122. XCTAssertTrue([when isKindOfClass:[FIRTimestamp class]]);
  123. // Tolerate up to 10 seconds of clock skew between client and server.
  124. XCTAssertEqualWithAccuracy(when.seconds, [FIRTimestamp timestamp].seconds, 10);
  125. // Validate the rest of the document.
  126. XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:when]);
  127. }
  128. /** Runs a transaction block. */
  129. - (void)runTransactionBlock:(void (^)(FIRTransaction *transaction))transactionBlock {
  130. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"];
  131. [_docRef.firestore runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) {
  132. transactionBlock(transaction);
  133. return nil;
  134. }
  135. completion:^(id result, NSError *error) {
  136. XCTAssertNil(error);
  137. [expectation fulfill];
  138. }];
  139. [self awaitExpectations];
  140. }
  141. #pragma mark - Test Cases
  142. - (void)testServerTimestampsWorkViaSet {
  143. [self writeDocumentRef:_docRef data:_setData];
  144. [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]];
  145. [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
  146. }
  147. - (void)testServerTimestampsWorkViaUpdate {
  148. [self writeInitialData];
  149. [self updateDocumentRef:_docRef data:_updateData];
  150. [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]];
  151. [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
  152. }
  153. - (void)testServerTimestampsWithEstimatedValue {
  154. [self writeDocumentRef:_docRef data:_setData];
  155. [self verifyTimestampsAreEstimatedInSnapshot:[self waitForLocalEvent]];
  156. [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
  157. }
  158. - (void)testServerTimestampsWithPreviousValue {
  159. [self writeDocumentRef:_docRef data:_setData];
  160. [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
  161. FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
  162. [_docRef updateData:_updateData];
  163. [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:remoteSnapshot];
  164. [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
  165. }
  166. - (void)testServerTimestampsWithPreviousValueOfDifferentType {
  167. [self writeDocumentRef:_docRef data:_setData];
  168. [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
  169. [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
  170. [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
  171. FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
  172. XCTAssertEqualObjects([localSnapshot valueForField:@"a"], [NSNull null]);
  173. XCTAssertEqualObjects(
  174. [localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
  175. @42);
  176. XCTAssertTrue(
  177. [[localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorEstimate]
  178. isKindOfClass:[FIRTimestamp class]]);
  179. FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
  180. XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[FIRTimestamp class]]);
  181. XCTAssertTrue([
  182. [remoteSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious]
  183. isKindOfClass:[FIRTimestamp class]]);
  184. XCTAssertTrue([
  185. [remoteSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorEstimate]
  186. isKindOfClass:[FIRTimestamp class]]);
  187. }
  188. - (void)testServerTimestampsWithConsecutiveUpdates {
  189. [self writeDocumentRef:_docRef data:_setData];
  190. [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
  191. [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
  192. [self disableNetwork];
  193. [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
  194. FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
  195. XCTAssertEqualObjects(
  196. [localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
  197. @42);
  198. [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
  199. localSnapshot = [self waitForLocalEvent];
  200. XCTAssertEqualObjects(
  201. [localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
  202. @42);
  203. [self enableNetwork];
  204. FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
  205. XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[FIRTimestamp class]]);
  206. }
  207. - (void)testServerTimestampsPreviousValueFromLocalMutation {
  208. [self writeDocumentRef:_docRef data:_setData];
  209. [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
  210. [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
  211. [self disableNetwork];
  212. [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
  213. FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
  214. XCTAssertEqualObjects(
  215. [localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
  216. @42);
  217. [_docRef updateData:@{ @"a" : @1337 }];
  218. localSnapshot = [self waitForLocalEvent];
  219. XCTAssertEqualObjects([localSnapshot valueForField:@"a"], @1337);
  220. [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
  221. localSnapshot = [self waitForLocalEvent];
  222. XCTAssertEqualObjects(
  223. [localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
  224. @1337);
  225. [self enableNetwork];
  226. FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
  227. XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[FIRTimestamp class]]);
  228. }
  229. - (void)testServerTimestampsWorkViaTransactionSet {
  230. [self runTransactionBlock:^(FIRTransaction *transaction) {
  231. [transaction setData:_setData forDocument:_docRef];
  232. }];
  233. [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
  234. }
  235. - (void)testServerTimestampsWorkViaTransactionUpdate {
  236. [self writeInitialData];
  237. [self runTransactionBlock:^(FIRTransaction *transaction) {
  238. [transaction updateData:_updateData forDocument:_docRef];
  239. }];
  240. [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
  241. }
  242. - (void)testServerTimestampsFailViaUpdateOnNonexistentDocument {
  243. XCTestExpectation *expectation = [self expectationWithDescription:@"update complete"];
  244. [_docRef updateData:_updateData
  245. completion:^(NSError *error) {
  246. XCTAssertNotNil(error);
  247. XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
  248. XCTAssertEqual(error.code, FIRFirestoreErrorCodeNotFound);
  249. [expectation fulfill];
  250. }];
  251. [self awaitExpectations];
  252. }
  253. - (void)testServerTimestampsFailViaTransactionUpdateOnNonexistentDocument {
  254. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"];
  255. [_docRef.firestore runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) {
  256. [transaction updateData:_updateData forDocument:_docRef];
  257. return nil;
  258. }
  259. completion:^(id result, NSError *error) {
  260. XCTAssertNotNil(error);
  261. XCTAssertEqualObjects(error.domain, FIRFirestoreErrorDomain);
  262. // TODO(b/35201829): This should be NotFound, but right now we retry transactions on any
  263. // error and so this turns into Aborted instead.
  264. // TODO(mikelehen): Actually it's FailedPrecondition, unlike Android. What do we want???
  265. XCTAssertEqual(error.code, FIRFirestoreErrorCodeFailedPrecondition);
  266. [expectation fulfill];
  267. }];
  268. [self awaitExpectations];
  269. }
  270. @end