FSTTransactionTests.mm 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  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. #include <atomic>
  19. #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
  20. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  21. using firebase::firestore::util::TimerId;
  22. @interface FSTTransactionTests : FSTIntegrationTestCase
  23. - (void)runFailedPreconditionTransactionWithOptions:(FIRTransactionOptions *_Nullable)options
  24. expectNumAttempts:(int)expectedNumAttempts;
  25. @end
  26. /**
  27. * This category is to handle the use of assertions in `FSTTransactionTester`, since XCTest
  28. * assertions do not work in classes that don't extend XCTestCase.
  29. */
  30. @interface FSTTransactionTests (Assertions)
  31. - (void)assertExistsWithSnapshot:(FIRDocumentSnapshot *)snapshot error:(NSError *)error;
  32. - (void)assertDoesNotExistWithSnapshot:(FIRDocumentSnapshot *)snapshot error:(NSError *)error;
  33. - (void)assertNilError:(NSError *)error message:(NSString *)message;
  34. - (void)assertError:(NSError *)error message:(NSString *)message code:(NSInteger)code;
  35. - (void)assertSnapshot:(FIRDocumentSnapshot *)snapshot
  36. equalsObject:(NSObject *)expected
  37. error:(NSError *)error;
  38. @end
  39. @implementation FSTTransactionTests (Assertions)
  40. - (void)assertExistsWithSnapshot:(FIRDocumentSnapshot *)snapshot error:(NSError *)error {
  41. XCTAssertNil(error);
  42. XCTAssertTrue(snapshot.exists);
  43. }
  44. - (void)assertDoesNotExistWithSnapshot:(FIRDocumentSnapshot *)snapshot error:(NSError *)error {
  45. XCTAssertNil(error);
  46. XCTAssertFalse(snapshot.exists);
  47. }
  48. - (void)assertNilError:(NSError *)error message:(NSString *)message {
  49. XCTAssertNil(error, @"%@", message);
  50. }
  51. - (void)assertError:(NSError *)error message:(NSString *)message code:(NSInteger)code {
  52. XCTAssertNotNil(error, @"%@", message);
  53. XCTAssertEqual(error.code, code, @"%@", message);
  54. }
  55. - (void)assertSnapshot:(FIRDocumentSnapshot *)snapshot
  56. equalsObject:(NSObject *)expected
  57. error:(NSError *)error {
  58. XCTAssertNil(error);
  59. XCTAssertTrue(snapshot.exists);
  60. XCTAssertEqualObjects(expected, snapshot.data);
  61. }
  62. @end
  63. typedef void (^TransactionStage)(FIRTransaction *, FIRDocumentReference *);
  64. /**
  65. * The transaction stages that follow are postfixed by numbers to indicate the calling order. For
  66. * example, calling `set1` followed by `set2` should result in the document being set to the value
  67. * specified by `set2`.
  68. */
  69. TransactionStage delete1 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  70. [transaction deleteDocument:doc];
  71. };
  72. TransactionStage update1 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  73. [transaction updateData:@{@"foo" : @"bar1"} forDocument:doc];
  74. };
  75. TransactionStage update2 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  76. [transaction updateData:@{@"foo" : @"bar2"} forDocument:doc];
  77. };
  78. TransactionStage set1 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  79. [transaction setData:@{@"foo" : @"bar1"} forDocument:doc];
  80. };
  81. TransactionStage set2 = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  82. [transaction setData:@{@"foo" : @"bar2"} forDocument:doc];
  83. };
  84. TransactionStage get = ^(FIRTransaction *transaction, FIRDocumentReference *doc) {
  85. NSError *error = nil;
  86. [transaction getDocument:doc error:&error];
  87. };
  88. typedef NS_ENUM(NSUInteger, FIRFromDocumentType) {
  89. // The operation will be performed on a document that exists.
  90. FIRFromDocumentTypeExisting,
  91. // The operation will be performed on a document that has never existed.
  92. FIRFromDocumentTypeNonExistent,
  93. // The operation will be performed on a document that existed, but was deleted.
  94. FIRFromDocumentTypeDeleted,
  95. };
  96. /**
  97. * Used for testing that all possible combinations of executing transactions result in the desired
  98. * document value or error.
  99. *
  100. * `runWithStages`, `withExistingDoc`, and `withNonexistentDoc` don't actually do anything except
  101. * assign variables into `FSTTransactionTester`.
  102. *
  103. * `expectDoc`, `expectNoDoc`, and `expectError` will trigger the transaction to run and assert
  104. * that the end result matches the input.
  105. */
  106. @interface FSTTransactionTester : NSObject
  107. - (FSTTransactionTester *)withExistingDoc;
  108. - (FSTTransactionTester *)withNonexistentDoc;
  109. - (FSTTransactionTester *)withDeletedDoc;
  110. - (FSTTransactionTester *)runWithStages:(NSArray<TransactionStage> *)stages;
  111. - (void)expectDoc:(NSObject *)expected;
  112. - (void)expectNoDoc;
  113. - (void)expectError:(FIRFirestoreErrorCode)expected;
  114. @property(atomic, strong, readwrite) NSArray<TransactionStage> *stages;
  115. @property(atomic, strong, readwrite) FIRDocumentReference *docRef;
  116. @property(atomic, assign, readwrite) FIRFromDocumentType fromDocumentType;
  117. @end
  118. @implementation FSTTransactionTester {
  119. FIRFirestore *_db;
  120. FSTTransactionTests *_testCase;
  121. NSMutableArray<XCTestExpectation *> *_testExpectations;
  122. }
  123. - (instancetype)initWithDb:(FIRFirestore *)db testCase:(FSTTransactionTests *)testCase {
  124. self = [super init];
  125. if (self) {
  126. _db = db;
  127. _stages = [NSArray array];
  128. _fromDocumentType = FIRFromDocumentTypeNonExistent;
  129. _testCase = testCase;
  130. _testExpectations = [NSMutableArray array];
  131. }
  132. return self;
  133. }
  134. - (FSTTransactionTester *)withExistingDoc {
  135. self.fromDocumentType = FIRFromDocumentTypeExisting;
  136. return self;
  137. }
  138. - (FSTTransactionTester *)withNonexistentDoc {
  139. self.fromDocumentType = FIRFromDocumentTypeNonExistent;
  140. return self;
  141. }
  142. - (FSTTransactionTester *)withDeletedDoc {
  143. self.fromDocumentType = FIRFromDocumentTypeDeleted;
  144. return self;
  145. }
  146. - (FSTTransactionTester *)runWithStages:(NSArray<TransactionStage> *)stages {
  147. self.stages = stages;
  148. return self;
  149. }
  150. - (void)expectDoc:(NSObject *)expected {
  151. [self prepareDoc];
  152. [self runSuccessfulTransaction];
  153. XCTestExpectation *expectation = [_testCase expectationWithDescription:@"expectDoc"];
  154. [self.docRef getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  155. [self->_testCase assertSnapshot:snapshot equalsObject:expected error:error];
  156. [expectation fulfill];
  157. }];
  158. [_testCase awaitExpectations];
  159. [self cleanupTester];
  160. }
  161. - (void)expectNoDoc {
  162. [self prepareDoc];
  163. [self runSuccessfulTransaction];
  164. XCTestExpectation *expectation = [_testCase expectationWithDescription:@"expectNoDoc"];
  165. [self.docRef getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  166. [self->_testCase assertDoesNotExistWithSnapshot:snapshot error:error];
  167. [expectation fulfill];
  168. }];
  169. [_testCase awaitExpectations];
  170. [self cleanupTester];
  171. }
  172. - (void)expectError:(FIRFirestoreErrorCode)expected {
  173. [self prepareDoc];
  174. [self runFailingTransactionWithError:expected];
  175. [self cleanupTester];
  176. }
  177. - (void)prepareDoc {
  178. self.docRef = [[_db collectionWithPath:@"nonexistent"] documentWithAutoID];
  179. switch (_fromDocumentType) {
  180. case FIRFromDocumentTypeExisting: {
  181. NSError *setError = [self writeDocumentRef:self.docRef data:@{@"foo" : @"bar"}];
  182. NSString *message = [NSString stringWithFormat:@"Failed set at %@", [self stageNames]];
  183. [_testCase assertNilError:setError message:message];
  184. break;
  185. }
  186. case FIRFromDocumentTypeNonExistent: {
  187. // Nothing to do; document does not exist.
  188. break;
  189. }
  190. case FIRFromDocumentTypeDeleted: {
  191. {
  192. NSError *setError = [self writeDocumentRef:self.docRef data:@{@"foo" : @"bar"}];
  193. NSString *message = [NSString stringWithFormat:@"Failed set at %@", [self stageNames]];
  194. [_testCase assertNilError:setError message:message];
  195. }
  196. {
  197. NSError *deleteError = [self deleteDocumentRef:self.docRef];
  198. NSString *message = [NSString stringWithFormat:@"Failed delete at %@", [self stageNames]];
  199. [_testCase assertNilError:deleteError message:message];
  200. }
  201. break;
  202. }
  203. }
  204. }
  205. - (NSError *)writeDocumentRef:(FIRDocumentReference *)ref
  206. data:(NSDictionary<NSString *, id> *)data {
  207. __block NSError *errorResult;
  208. XCTestExpectation *expectation = [_testCase expectationWithDescription:@"prepareDoc:set"];
  209. [ref setData:data
  210. completion:^(NSError *error) {
  211. errorResult = error;
  212. [expectation fulfill];
  213. }];
  214. [_testCase awaitExpectations];
  215. return errorResult;
  216. }
  217. - (NSError *)deleteDocumentRef:(FIRDocumentReference *)ref {
  218. __block NSError *errorResult;
  219. XCTestExpectation *expectation = [_testCase expectationWithDescription:@"prepareDoc:delete"];
  220. [ref deleteDocumentWithCompletion:^(NSError *error) {
  221. errorResult = error;
  222. [expectation fulfill];
  223. }];
  224. [_testCase awaitExpectations];
  225. return errorResult;
  226. }
  227. - (void)runSuccessfulTransaction {
  228. XCTestExpectation *expectation =
  229. [_testCase expectationWithDescription:@"runSuccessfulTransaction"];
  230. [_db
  231. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **) {
  232. for (TransactionStage stage in self.stages) {
  233. stage(transaction, self.docRef);
  234. }
  235. return @YES;
  236. }
  237. completion:^(id, NSError *error) {
  238. [expectation fulfill];
  239. NSString *message =
  240. [NSString stringWithFormat:@"Expected the sequence %@, to succeed, but got %d.",
  241. [self stageNames], (int)[error code]];
  242. [self->_testCase assertNilError:error message:message];
  243. }];
  244. [_testCase awaitExpectations];
  245. }
  246. - (void)runFailingTransactionWithError:(FIRFirestoreErrorCode)expected {
  247. (void)expected;
  248. XCTestExpectation *expectation =
  249. [_testCase expectationWithDescription:@"runFailingTransactionWithError"];
  250. [_db
  251. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **) {
  252. for (TransactionStage stage in self.stages) {
  253. stage(transaction, self.docRef);
  254. }
  255. return @YES;
  256. }
  257. completion:^(id, NSError *_Nullable error) {
  258. [expectation fulfill];
  259. NSString *message =
  260. [NSString stringWithFormat:@"Expected the sequence (%@), to fail, but it didn't.",
  261. [self stageNames]];
  262. [self->_testCase assertError:error message:message code:expected];
  263. }];
  264. [_testCase awaitExpectations];
  265. }
  266. - (void)cleanupTester {
  267. self.stages = [NSArray array];
  268. // Set the docRef to something else to lose the original reference.
  269. self.docRef = [[self->_db collectionWithPath:@"reset"] documentWithAutoID];
  270. }
  271. - (NSString *)stageNames {
  272. NSMutableArray<NSString *> *seqList = [NSMutableArray array];
  273. for (TransactionStage stage in self.stages) {
  274. if (stage == delete1) {
  275. [seqList addObject:@"delete"];
  276. } else if (stage == update1 || stage == update2) {
  277. [seqList addObject:@"update"];
  278. } else if (stage == set1 || stage == set2) {
  279. [seqList addObject:@"set"];
  280. } else if (stage == get) {
  281. [seqList addObject:@"get"];
  282. }
  283. }
  284. return [seqList description];
  285. }
  286. @end
  287. @implementation FSTTransactionTests
  288. - (void)testRunsTransactionsAfterGettingExistingDoc {
  289. FIRFirestore *firestore = [self firestore];
  290. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  291. [[[tt withExistingDoc] runWithStages:@[ get, delete1, delete1 ]] expectNoDoc];
  292. [[[tt withExistingDoc] runWithStages:@[ get, delete1, update2 ]]
  293. expectError:FIRFirestoreErrorCodeInvalidArgument];
  294. [[[tt withExistingDoc] runWithStages:@[ get, delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  295. [[[tt withExistingDoc] runWithStages:@[ get, update1, delete1 ]] expectNoDoc];
  296. [[[tt withExistingDoc] runWithStages:@[ get, update1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  297. [[[tt withExistingDoc] runWithStages:@[ get, update1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  298. [[[tt withExistingDoc] runWithStages:@[ get, set1, delete1 ]] expectNoDoc];
  299. [[[tt withExistingDoc] runWithStages:@[ get, set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  300. [[[tt withExistingDoc] runWithStages:@[ get, set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  301. }
  302. - (void)testRunsTransactionsAfterGettingNonexistentDoc {
  303. FIRFirestore *firestore = [self firestore];
  304. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  305. [[[tt withNonexistentDoc] runWithStages:@[ get, delete1, delete1 ]] expectNoDoc];
  306. [[[tt withNonexistentDoc] runWithStages:@[ get, delete1, update2 ]]
  307. expectError:FIRFirestoreErrorCodeInvalidArgument];
  308. [[[tt withNonexistentDoc] runWithStages:@[ get, delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  309. [[[tt withNonexistentDoc] runWithStages:@[ get, update1, delete1 ]]
  310. expectError:FIRFirestoreErrorCodeInvalidArgument];
  311. [[[tt withNonexistentDoc] runWithStages:@[ get, update1, update2 ]]
  312. expectError:FIRFirestoreErrorCodeInvalidArgument];
  313. [[[tt withNonexistentDoc] runWithStages:@[ get, update1, set2 ]]
  314. expectError:FIRFirestoreErrorCodeInvalidArgument];
  315. [[[tt withNonexistentDoc] runWithStages:@[ get, set1, delete1 ]] expectNoDoc];
  316. [[[tt withNonexistentDoc] runWithStages:@[ get, set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  317. [[[tt withNonexistentDoc] runWithStages:@[ get, set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  318. }
  319. // This test is identical to the test above, except that withNonexistentDoc() is replaced by
  320. // withDeletedDoc(), to guard against regression of
  321. // https://github.com/firebase/firebase-js-sdk/issues/5871, where transactions would incorrectly
  322. // fail with FAILED_PRECONDITION when operations were performed on a deleted document (rather than
  323. // a non-existent document).
  324. - (void)testRunsTransactionsAfterGettingDeletedDoc {
  325. FIRFirestore *firestore = [self firestore];
  326. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  327. [[[tt withDeletedDoc] runWithStages:@[ get, delete1, delete1 ]] expectNoDoc];
  328. [[[tt withDeletedDoc] runWithStages:@[ get, delete1, update2 ]]
  329. expectError:FIRFirestoreErrorCodeInvalidArgument];
  330. [[[tt withDeletedDoc] runWithStages:@[ get, delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  331. [[[tt withDeletedDoc] runWithStages:@[ get, update1, delete1 ]]
  332. expectError:FIRFirestoreErrorCodeInvalidArgument];
  333. [[[tt withDeletedDoc] runWithStages:@[ get, update1, update2 ]]
  334. expectError:FIRFirestoreErrorCodeInvalidArgument];
  335. [[[tt withDeletedDoc] runWithStages:@[ get, update1, set2 ]]
  336. expectError:FIRFirestoreErrorCodeInvalidArgument];
  337. [[[tt withDeletedDoc] runWithStages:@[ get, set1, delete1 ]] expectNoDoc];
  338. [[[tt withDeletedDoc] runWithStages:@[ get, set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  339. [[[tt withDeletedDoc] runWithStages:@[ get, set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  340. }
  341. - (void)testRunsTransactionOnExistingDoc {
  342. FIRFirestore *firestore = [self firestore];
  343. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  344. [[[tt withExistingDoc] runWithStages:@[ delete1, delete1 ]] expectNoDoc];
  345. [[[tt withExistingDoc] runWithStages:@[ delete1, update2 ]]
  346. expectError:FIRFirestoreErrorCodeInvalidArgument];
  347. [[[tt withExistingDoc] runWithStages:@[ delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  348. [[[tt withExistingDoc] runWithStages:@[ update1, delete1 ]] expectNoDoc];
  349. [[[tt withExistingDoc] runWithStages:@[ update1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  350. [[[tt withExistingDoc] runWithStages:@[ update1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  351. [[[tt withExistingDoc] runWithStages:@[ set1, delete1 ]] expectNoDoc];
  352. [[[tt withExistingDoc] runWithStages:@[ set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  353. [[[tt withExistingDoc] runWithStages:@[ set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  354. }
  355. - (void)testRunsTransactionsOnNonexistentDoc {
  356. FIRFirestore *firestore = [self firestore];
  357. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  358. [[[tt withNonexistentDoc] runWithStages:@[ delete1, delete1 ]] expectNoDoc];
  359. [[[tt withNonexistentDoc] runWithStages:@[ delete1, update2 ]]
  360. expectError:FIRFirestoreErrorCodeInvalidArgument];
  361. [[[tt withNonexistentDoc] runWithStages:@[ delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  362. [[[tt withNonexistentDoc] runWithStages:@[ update1, delete1 ]]
  363. expectError:FIRFirestoreErrorCodeNotFound];
  364. [[[tt withNonexistentDoc] runWithStages:@[ update1, update2 ]]
  365. expectError:FIRFirestoreErrorCodeNotFound];
  366. [[[tt withNonexistentDoc] runWithStages:@[ update1, set2 ]]
  367. expectError:FIRFirestoreErrorCodeNotFound];
  368. [[[tt withNonexistentDoc] runWithStages:@[ set1, delete1 ]] expectNoDoc];
  369. [[[tt withNonexistentDoc] runWithStages:@[ set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  370. [[[tt withNonexistentDoc] runWithStages:@[ set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  371. }
  372. - (void)testRunsTransactionsOnDeletedDoc {
  373. FIRFirestore *firestore = [self firestore];
  374. FSTTransactionTester *tt = [[FSTTransactionTester alloc] initWithDb:firestore testCase:self];
  375. [[[tt withDeletedDoc] runWithStages:@[ delete1, delete1 ]] expectNoDoc];
  376. [[[tt withDeletedDoc] runWithStages:@[ delete1, update2 ]]
  377. expectError:FIRFirestoreErrorCodeInvalidArgument];
  378. [[[tt withDeletedDoc] runWithStages:@[ delete1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  379. [[[tt withDeletedDoc] runWithStages:@[ update1, delete1 ]]
  380. expectError:FIRFirestoreErrorCodeNotFound];
  381. [[[tt withDeletedDoc] runWithStages:@[ update1, update2 ]]
  382. expectError:FIRFirestoreErrorCodeNotFound];
  383. [[[tt withDeletedDoc] runWithStages:@[ update1, set2 ]]
  384. expectError:FIRFirestoreErrorCodeNotFound];
  385. [[[tt withDeletedDoc] runWithStages:@[ set1, delete1 ]] expectNoDoc];
  386. [[[tt withDeletedDoc] runWithStages:@[ set1, update2 ]] expectDoc:@{@"foo" : @"bar2"}];
  387. [[[tt withDeletedDoc] runWithStages:@[ set1, set2 ]] expectDoc:@{@"foo" : @"bar2"}];
  388. }
  389. - (void)testSetDocumentWithMerge {
  390. FIRFirestore *firestore = [self firestore];
  391. FIRDocumentReference *doc = [[firestore collectionWithPath:@"towns"] documentWithAutoID];
  392. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  393. [firestore
  394. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **) {
  395. [transaction setData:@{@"a" : @"b", @"nested" : @{@"a" : @"b"}} forDocument:doc];
  396. [transaction setData:@{@"c" : @"d", @"nested" : @{@"c" : @"d"}} forDocument:doc merge:YES];
  397. return @YES;
  398. }
  399. completion:^(id _Nullable result, NSError *_Nullable error) {
  400. XCTAssertEqualObjects(result, @YES);
  401. XCTAssertNil(error);
  402. [expectation fulfill];
  403. }];
  404. [self awaitExpectations];
  405. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
  406. XCTAssertEqualObjects(snapshot.data,
  407. (@{@"a" : @"b", @"c" : @"d", @"nested" : @{@"a" : @"b", @"c" : @"d"}}));
  408. }
  409. - (void)testIncrementTransactionally {
  410. // A barrier to make sure every transaction reaches the same spot.
  411. dispatch_semaphore_t writeBarrier = dispatch_semaphore_create(0);
  412. auto counter = std::make_shared<std::atomic_int>(0);
  413. FIRFirestore *firestore = [self firestore];
  414. FIRDocumentReference *doc = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  415. [self writeDocumentRef:doc data:@{@"count" : @(5.0)}];
  416. // Skip backoff delays.
  417. [firestore workerQueue]->SkipDelaysForTimerId(TimerId::RetryTransaction);
  418. // Make 3 transactions that will all increment.
  419. int total = 3;
  420. for (int i = 0; i < total; i++) {
  421. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  422. [firestore
  423. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  424. FIRDocumentSnapshot *snapshot = [transaction getDocument:doc error:error];
  425. XCTAssertNil(*error);
  426. (*counter)++;
  427. // Once all of the transactions have read, allow the first write.
  428. if (*counter == total) {
  429. dispatch_semaphore_signal(writeBarrier);
  430. }
  431. dispatch_semaphore_wait(writeBarrier, DISPATCH_TIME_FOREVER);
  432. // Refill the barrier so that the other transactions and retries succeed.
  433. dispatch_semaphore_signal(writeBarrier);
  434. double newCount = ((NSNumber *)snapshot[@"count"]).doubleValue + 1.0;
  435. [transaction setData:@{@"count" : @(newCount)} forDocument:doc];
  436. return @YES;
  437. }
  438. completion:^(id, NSError *) {
  439. [expectation fulfill];
  440. }];
  441. }
  442. [self awaitExpectations];
  443. // Now all transaction should be completed, so check the result.
  444. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
  445. XCTAssertEqualObjects(snapshot[@"count"], @(5.0 + total));
  446. }
  447. - (void)testUpdateTransactionally {
  448. // A barrier to make sure every transaction reaches the same spot.
  449. dispatch_semaphore_t writeBarrier = dispatch_semaphore_create(0);
  450. auto counter = std::make_shared<std::atomic_int>(0);
  451. FIRFirestore *firestore = [self firestore];
  452. FIRDocumentReference *doc = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  453. [self writeDocumentRef:doc data:@{@"count" : @(5.0), @"other" : @"yes"}];
  454. // Skip backoff delays.
  455. [firestore workerQueue]->SkipDelaysForTimerId(TimerId::RetryTransaction);
  456. // Make 3 transactions that will all increment.
  457. int total = 3;
  458. for (int i = 0; i < total; i++) {
  459. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  460. [firestore
  461. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  462. int32_t nowStarted = ++(*counter);
  463. FIRDocumentSnapshot *snapshot = [transaction getDocument:doc error:error];
  464. XCTAssertNil(*error);
  465. // Once all of the transactions have read, allow the first write. There should be 3
  466. // initial transaction runs.
  467. if (nowStarted == total) {
  468. XCTAssertEqual(counter->load(), 3);
  469. dispatch_semaphore_signal(writeBarrier);
  470. }
  471. dispatch_semaphore_wait(writeBarrier, DISPATCH_TIME_FOREVER);
  472. // Refill the barrier so that the other transactions and retries succeed.
  473. dispatch_semaphore_signal(writeBarrier);
  474. double newCount = ((NSNumber *)snapshot[@"count"]).doubleValue + 1.0;
  475. [transaction updateData:@{@"count" : @(newCount)} forDocument:doc];
  476. return @YES;
  477. }
  478. completion:^(id, NSError *) {
  479. [expectation fulfill];
  480. }];
  481. }
  482. [self awaitExpectations];
  483. // There should be a maximum of 3 retries: once for the 2nd update, and twice for the 3rd update.
  484. XCTAssertLessThanOrEqual(counter->load(), 6);
  485. // Now all transaction should be completed, so check the result.
  486. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
  487. XCTAssertEqualObjects(snapshot[@"count"], @(5.0 + total));
  488. XCTAssertEqualObjects(@"yes", snapshot[@"other"]);
  489. }
  490. - (void)testRetriesWhenDocumentThatWasReadWithoutBeingWrittenChanges {
  491. FIRFirestore *firestore = [self firestore];
  492. FIRDocumentReference *doc1 = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  493. FIRDocumentReference *doc2 = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  494. auto counter = std::make_shared<std::atomic_int>(0);
  495. [self writeDocumentRef:doc1 data:@{@"count" : @(15.0)}];
  496. // Skip backoff delays.
  497. [firestore workerQueue]->SkipDelaysForTimerId(TimerId::RetryTransaction);
  498. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  499. [firestore
  500. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  501. ++(*counter);
  502. // Get the first doc.
  503. [transaction getDocument:doc1 error:error];
  504. XCTAssertNil(*error);
  505. // Do a write outside of the transaction. The first time the
  506. // transaction is tried, this will bump the version, which
  507. // will cause the write to doc2 to fail. The second time, it
  508. // will be a no-op and not bump the version.
  509. dispatch_semaphore_t writeSemaphore = dispatch_semaphore_create(0);
  510. [doc1 setData:@{
  511. @"count" : @(1234)
  512. }
  513. completion:^(NSError *) {
  514. dispatch_semaphore_signal(writeSemaphore);
  515. }];
  516. // We can block on it, because transactions run on a background queue.
  517. dispatch_semaphore_wait(writeSemaphore, DISPATCH_TIME_FOREVER);
  518. // Now try to update the other doc from within the transaction.
  519. // This should fail once, because we read 15 earlier.
  520. [transaction setData:@{@"count" : @(16)} forDocument:doc2];
  521. return nil;
  522. }
  523. completion:^(id, NSError *_Nullable error) {
  524. XCTAssertNil(error);
  525. XCTAssertEqual(counter->load(), 2);
  526. [expectation fulfill];
  527. }];
  528. [self awaitExpectations];
  529. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc1];
  530. XCTAssertEqualObjects(snapshot[@"count"], @(1234));
  531. }
  532. - (void)testReadingADocTwiceWithDifferentVersions {
  533. FIRFirestore *firestore = [self firestore];
  534. FIRDocumentReference *doc = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  535. auto counter = std::make_shared<std::atomic_int>(0);
  536. [self writeDocumentRef:doc data:@{@"count" : @(15.0)}];
  537. // Skip backoff delays.
  538. [firestore workerQueue]->SkipDelaysForTimerId(TimerId::RetryTransaction);
  539. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  540. [firestore
  541. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  542. ++(*counter);
  543. // Get the doc once.
  544. FIRDocumentSnapshot *snapshot = [transaction getDocument:doc error:error];
  545. XCTAssertNotNil(snapshot);
  546. XCTAssertNil(*error);
  547. // Do a write outside of the transaction. Because the transaction will retry, set the
  548. // document to a different value each time.
  549. dispatch_semaphore_t writeSemaphore = dispatch_semaphore_create(0);
  550. [doc setData:@{
  551. @"count" : @(1234 + (int)(*counter))
  552. }
  553. completion:^(NSError *) {
  554. dispatch_semaphore_signal(writeSemaphore);
  555. }];
  556. // We can block on it, because transactions run on a background queue.
  557. dispatch_semaphore_wait(writeSemaphore, DISPATCH_TIME_FOREVER);
  558. // Get the doc again in the transaction with the new version.
  559. snapshot = [transaction getDocument:doc error:error];
  560. // The get itself will fail, because we already read an earlier version of this document.
  561. // TODO(klimt): Perhaps we shouldn't fail reads for this, but should wait and fail the
  562. // whole transaction? It's an edge-case anyway, as developers shouldn't be reading the same
  563. // doc multiple times. But they need to handle read errors anyway.
  564. XCTAssertNil(snapshot);
  565. XCTAssertNotNil(*error);
  566. return nil;
  567. }
  568. completion:^(id, NSError *_Nullable error) {
  569. [expectation fulfill];
  570. XCTAssertNotNil(error);
  571. XCTAssertEqual(error.code, FIRFirestoreErrorCodeAborted);
  572. }];
  573. [self awaitExpectations];
  574. }
  575. - (void)testReadAndUpdateNonExistentDocumentWithExternalWrite {
  576. FIRFirestore *firestore = [self firestore];
  577. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  578. [firestore
  579. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  580. // Get and update a document that doesn't exist so that the transaction fails.
  581. FIRDocumentReference *doc =
  582. [[firestore collectionWithPath:@"nonexistent"] documentWithAutoID];
  583. [transaction getDocument:doc error:error];
  584. XCTAssertNil(*error);
  585. // Do a write outside of the transaction.
  586. dispatch_semaphore_t writeSemaphore = dispatch_semaphore_create(0);
  587. [doc setData:@{
  588. @"count" : @(1234)
  589. }
  590. completion:^(NSError *) {
  591. dispatch_semaphore_signal(writeSemaphore);
  592. }];
  593. // We can block on it, because transactions run on a background queue.
  594. dispatch_semaphore_wait(writeSemaphore, DISPATCH_TIME_FOREVER);
  595. // Now try to update the other doc from within the transaction.
  596. // This should fail, because the document didn't exist at the
  597. // start of the transaction.
  598. [transaction updateData:@{@"count" : @(16)} forDocument:doc];
  599. return nil;
  600. }
  601. completion:^(id, NSError *_Nullable error) {
  602. [expectation fulfill];
  603. XCTAssertNotNil(error);
  604. XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument);
  605. }];
  606. [self awaitExpectations];
  607. }
  608. - (void)testCanHaveGetsWithoutMutations {
  609. FIRFirestore *firestore = [self firestore];
  610. FIRDocumentReference *doc = [[firestore collectionWithPath:@"foo"] documentWithAutoID];
  611. FIRDocumentReference *doc2 = [[firestore collectionWithPath:@"foo"] documentWithAutoID];
  612. [self writeDocumentRef:doc data:@{@"foo" : @"bar"}];
  613. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  614. [firestore
  615. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  616. [transaction getDocument:doc2 error:error];
  617. [transaction getDocument:doc error:error];
  618. return nil;
  619. }
  620. completion:^(id, NSError *_Nullable error) {
  621. XCTAssertNil(error);
  622. [expectation fulfill];
  623. }];
  624. [self awaitExpectations];
  625. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
  626. XCTAssertEqualObjects(snapshot[@"foo"], @"bar");
  627. }
  628. - (void)testDoesNotRetryOnPermanentError {
  629. FIRFirestore *firestore = [self firestore];
  630. auto counter = std::make_shared<std::atomic_int>(0);
  631. // Make a transaction that should fail with a permanent error
  632. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  633. [firestore
  634. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  635. ++(*counter);
  636. // Get and update a document that doesn't exist so that the transaction fails.
  637. FIRDocumentReference *doc =
  638. [[firestore collectionWithPath:@"nonexistent"] documentWithAutoID];
  639. [transaction getDocument:doc error:error];
  640. [transaction updateData:@{@"count" : @(16)} forDocument:doc];
  641. return nil;
  642. }
  643. completion:^(id, NSError *_Nullable error) {
  644. [expectation fulfill];
  645. XCTAssertNotNil(error);
  646. XCTAssertEqual(error.code, FIRFirestoreErrorCodeInvalidArgument);
  647. XCTAssertEqual(counter->load(), 1);
  648. }];
  649. [self awaitExpectations];
  650. }
  651. - (void)testRetryOnAlreadyExistsError {
  652. FIRFirestore *firestore = [self firestore];
  653. FIRDocumentReference *doc1 = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  654. auto transactionCallbackCallCount = std::make_shared<std::atomic_int>(0);
  655. // Skip backoff delays.
  656. [firestore workerQueue]->SkipDelaysForTimerId(TimerId::RetryTransaction);
  657. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  658. [firestore
  659. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  660. int callbackNum = ++(*transactionCallbackCallCount);
  661. FIRDocumentSnapshot *snapshot = [transaction getDocument:doc1 error:error];
  662. XCTAssertNil(*error);
  663. if (callbackNum == 1) {
  664. XCTAssertFalse(snapshot.exists);
  665. // Create the document outside of the transaction to cause the commit to fail with
  666. // ALREADY_EXISTS.
  667. dispatch_semaphore_t writeSemaphore = dispatch_semaphore_create(0);
  668. [doc1 setData:@{@"foo1" : @"bar1"}
  669. completion:^(NSError *) {
  670. dispatch_semaphore_signal(writeSemaphore);
  671. }];
  672. // We can block on it, because transactions run on a background queue.
  673. dispatch_semaphore_wait(writeSemaphore, DISPATCH_TIME_FOREVER);
  674. } else if (callbackNum == 2) {
  675. XCTAssertTrue(snapshot.exists);
  676. } else {
  677. XCTFail(@"unexpected callbackNum: %@", @(callbackNum));
  678. }
  679. [transaction setData:@{@"foo2" : @"bar2"} forDocument:doc1];
  680. return nil;
  681. }
  682. completion:^(id, NSError *_Nullable error) {
  683. [expectation fulfill];
  684. XCTAssertNil(error);
  685. }];
  686. [self awaitExpectations];
  687. XCTAssertEqual(transactionCallbackCallCount->load(), 2);
  688. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc1];
  689. XCTAssertNotNil(snapshot);
  690. XCTAssertTrue(snapshot.exists);
  691. XCTAssertEqualObjects(snapshot.data, (@{@"foo2" : @"bar2"}));
  692. }
  693. - (void)testMakesDefaultMaxAttempts {
  694. FIRFirestore *firestore = [self firestore];
  695. FIRDocumentReference *doc1 = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  696. auto counter = std::make_shared<std::atomic_int>(0);
  697. [self writeDocumentRef:doc1 data:@{@"count" : @(15.0)}];
  698. // Skip backoff delays.
  699. [firestore workerQueue]->SkipDelaysForTimerId(TimerId::RetryTransaction);
  700. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  701. [firestore
  702. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  703. ++(*counter);
  704. // Get the first doc.
  705. [transaction getDocument:doc1 error:error];
  706. XCTAssertNil(*error);
  707. // Do a write outside of the transaction to cause the transaction to fail.
  708. dispatch_semaphore_t writeSemaphore = dispatch_semaphore_create(0);
  709. int newValue = 1234 + counter->load();
  710. [doc1 setData:@{
  711. @"count" : @(newValue)
  712. }
  713. completion:^(NSError *) {
  714. dispatch_semaphore_signal(writeSemaphore);
  715. }];
  716. // We can block on it, because transactions run on a background queue.
  717. dispatch_semaphore_wait(writeSemaphore, DISPATCH_TIME_FOREVER);
  718. return nil;
  719. }
  720. completion:^(id, NSError *_Nullable error) {
  721. [expectation fulfill];
  722. XCTAssertNotNil(error);
  723. XCTAssertEqual(error.code, FIRFirestoreErrorCodeFailedPrecondition);
  724. XCTAssertEqual(counter->load(), 5);
  725. }];
  726. [self awaitExpectations];
  727. }
  728. - (void)testSuccessWithNoTransactionOperations {
  729. FIRFirestore *firestore = [self firestore];
  730. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  731. [firestore
  732. runTransactionWithBlock:^id _Nullable(FIRTransaction *, NSError **) {
  733. return @"yes";
  734. }
  735. completion:^(id _Nullable result, NSError *_Nullable error) {
  736. XCTAssertEqualObjects(result, @"yes");
  737. XCTAssertNil(error);
  738. [expectation fulfill];
  739. }];
  740. [self awaitExpectations];
  741. }
  742. - (void)testCancellationOnError {
  743. FIRFirestore *firestore = [self firestore];
  744. FIRDocumentReference *doc = [[firestore collectionWithPath:@"towns"] documentWithAutoID];
  745. auto counter = std::make_shared<std::atomic_int>(0);
  746. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  747. [firestore
  748. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  749. ++(*counter);
  750. [transaction setData:@{@"foo" : @"bar"} forDocument:doc];
  751. if (error) {
  752. *error = [NSError errorWithDomain:NSCocoaErrorDomain code:35 userInfo:@{}];
  753. }
  754. return nil;
  755. }
  756. completion:^(id _Nullable result, NSError *_Nullable error) {
  757. XCTAssertNil(result);
  758. XCTAssertNotNil(error);
  759. XCTAssertEqual(error.code, 35);
  760. [expectation fulfill];
  761. }];
  762. [self awaitExpectations];
  763. XCTAssertEqual(counter->load(), 1);
  764. FIRDocumentSnapshot *snapshot = [self readDocumentForRef:doc];
  765. XCTAssertFalse(snapshot.exists);
  766. }
  767. - (void)testUpdateFieldsWithDotsTransactionally {
  768. FIRDocumentReference *doc = [self documentRef];
  769. XCTestExpectation *expectation =
  770. [self expectationWithDescription:@"testUpdateFieldsWithDotsTransactionally"];
  771. [doc.firestore
  772. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  773. XCTAssertNil(*error);
  774. [transaction setData:@{@"a.b" : @"old", @"c.d" : @"old"} forDocument:doc];
  775. [transaction updateData:@{
  776. [[FIRFieldPath alloc] initWithFields:@[ @"a.b" ]] : @"new"
  777. }
  778. forDocument:doc];
  779. return nil;
  780. }
  781. completion:^(id, NSError *error) {
  782. XCTAssertNil(error);
  783. [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  784. XCTAssertNil(error);
  785. XCTAssertEqualObjects(snapshot.data, (@{@"a.b" : @"new", @"c.d" : @"old"}));
  786. }];
  787. [expectation fulfill];
  788. }];
  789. [self awaitExpectations];
  790. }
  791. - (void)testUpdateNestedFieldsTransactionally {
  792. FIRDocumentReference *doc = [self documentRef];
  793. XCTestExpectation *expectation =
  794. [self expectationWithDescription:@"testUpdateNestedFieldsTransactionally"];
  795. [doc.firestore
  796. runTransactionWithBlock:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  797. XCTAssertNil(*error);
  798. [transaction setData:@{
  799. @"a" : @{@"b" : @"old"},
  800. @"c" : @{@"d" : @"old"},
  801. @"e" : @{@"f" : @"old"}
  802. }
  803. forDocument:doc];
  804. [transaction updateData:@{
  805. @"a.b" : @"new",
  806. [[FIRFieldPath alloc] initWithFields:@[ @"c", @"d" ]] : @"new"
  807. }
  808. forDocument:doc];
  809. return nil;
  810. }
  811. completion:^(id, NSError *error) {
  812. XCTAssertNil(error);
  813. [doc getDocumentWithCompletion:^(FIRDocumentSnapshot *snapshot, NSError *error) {
  814. XCTAssertNil(error);
  815. XCTAssertEqualObjects(snapshot.data, (@{
  816. @"a" : @{@"b" : @"new"},
  817. @"c" : @{@"d" : @"new"},
  818. @"e" : @{@"f" : @"old"}
  819. }));
  820. }];
  821. [expectation fulfill];
  822. }];
  823. [self awaitExpectations];
  824. }
  825. - (void)runFailedPreconditionTransactionWithOptions:(FIRTransactionOptions *_Nullable)options
  826. expectNumAttempts:(int)expectedNumAttempts {
  827. // Note: The logic below to force retries is heavily based on
  828. // testRetriesWhenDocumentThatWasReadWithoutBeingWrittenChanges.
  829. FIRFirestore *firestore = [self firestore];
  830. FIRDocumentReference *doc = [[firestore collectionWithPath:@"counters"] documentWithAutoID];
  831. auto attemptCount = std::make_shared<std::atomic_int>(0);
  832. attemptCount->store(0);
  833. [self writeDocumentRef:doc data:@{@"count" : @"initial value"}];
  834. // Skip backoff delays.
  835. [firestore workerQueue]->SkipDelaysForTimerId(TimerId::RetryTransaction);
  836. XCTestExpectation *expectation = [self expectationWithDescription:@"transaction"];
  837. [firestore runTransactionWithOptions:options
  838. block:^id _Nullable(FIRTransaction *transaction, NSError **error) {
  839. ++(*attemptCount);
  840. [transaction getDocument:doc error:error];
  841. XCTAssertNil(*error);
  842. // Do a write outside of the transaction. This will force the transaction to be retried.
  843. dispatch_semaphore_t writeSemaphore = dispatch_semaphore_create(0);
  844. [doc setData:@{
  845. @"count" : @(attemptCount->load())
  846. }
  847. completion:^(NSError *) {
  848. dispatch_semaphore_signal(writeSemaphore);
  849. }];
  850. dispatch_semaphore_wait(writeSemaphore, DISPATCH_TIME_FOREVER);
  851. // Now try to update the doc from within the transaction.
  852. // This will fail since the document was modified outside of the transaction.
  853. [transaction setData:@{@"count" : @"this write should fail"} forDocument:doc];
  854. return nil;
  855. }
  856. completion:^(id, NSError *_Nullable error) {
  857. [self assertError:error
  858. message:@"the transaction should fail due to retries exhausted"
  859. code:FIRFirestoreErrorCodeFailedPrecondition];
  860. XCTAssertEqual(attemptCount->load(), expectedNumAttempts);
  861. [expectation fulfill];
  862. }];
  863. [self awaitExpectations];
  864. }
  865. - (void)testTransactionOptionsNil {
  866. [self runFailedPreconditionTransactionWithOptions:nil expectNumAttempts:5];
  867. }
  868. - (void)testTransactionOptionsMaxAttempts {
  869. FIRTransactionOptions *options = [[FIRTransactionOptions alloc] init];
  870. options.maxAttempts = 7;
  871. [self runFailedPreconditionTransactionWithOptions:options expectNumAttempts:7];
  872. }
  873. @end