FSTMutationTests.mm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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 "Firestore/Source/Model/FSTMutation.h"
  17. #import <FirebaseFirestore/FIRFieldValue.h>
  18. #import <FirebaseFirestore/FIRTimestamp.h>
  19. #import <XCTest/XCTest.h>
  20. #include <vector>
  21. #import "Firestore/Source/API/FIRFieldValue+Internal.h"
  22. #import "Firestore/Source/API/converters.h"
  23. #import "Firestore/Source/Model/FSTDocument.h"
  24. #import "Firestore/Source/Model/FSTFieldValue.h"
  25. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  26. #include "Firestore/core/include/firebase/firestore/timestamp.h"
  27. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  28. #include "Firestore/core/src/firebase/firestore/model/field_mask.h"
  29. #include "Firestore/core/src/firebase/firestore/model/field_transform.h"
  30. #include "Firestore/core/src/firebase/firestore/model/field_value.h"
  31. #include "Firestore/core/src/firebase/firestore/model/precondition.h"
  32. #include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
  33. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  34. namespace api = firebase::firestore::api;
  35. namespace testutil = firebase::firestore::testutil;
  36. using firebase::Timestamp;
  37. using firebase::firestore::model::ArrayTransform;
  38. using firebase::firestore::model::DocumentKey;
  39. using firebase::firestore::model::FieldMask;
  40. using firebase::firestore::model::FieldPath;
  41. using firebase::firestore::model::FieldTransform;
  42. using firebase::firestore::model::FieldValue;
  43. using firebase::firestore::model::Precondition;
  44. using firebase::firestore::model::TransformOperation;
  45. @interface FSTMutationTests : XCTestCase
  46. @end
  47. @implementation FSTMutationTests {
  48. Timestamp _timestamp;
  49. }
  50. - (void)setUp {
  51. _timestamp = Timestamp::Now();
  52. }
  53. - (void)testAppliesSetsToDocuments {
  54. NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
  55. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  56. FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"bar" : @"bar-value"});
  57. FSTMaybeDocument *setDoc = [set applyToLocalDocument:baseDoc
  58. baseDocument:baseDoc
  59. localWriteTime:_timestamp];
  60. NSDictionary *expectedData = @{@"bar" : @"bar-value"};
  61. XCTAssertEqualObjects(
  62. setDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations));
  63. }
  64. - (void)testAppliesPatchesToDocuments {
  65. NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"};
  66. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  67. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
  68. FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc
  69. baseDocument:baseDoc
  70. localWriteTime:_timestamp];
  71. NSDictionary *expectedData = @{@"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value"};
  72. XCTAssertEqualObjects(
  73. patchedDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations));
  74. }
  75. - (void)testDeletesValuesFromTheFieldMask {
  76. NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value", @"baz" : @"baz-value"}};
  77. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  78. DocumentKey key = testutil::Key("collection/key");
  79. FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:key
  80. fieldMask:{testutil::Field("foo.bar")}
  81. value:[FSTObjectValue objectValue]
  82. precondition:Precondition::None()];
  83. FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc
  84. baseDocument:baseDoc
  85. localWriteTime:_timestamp];
  86. NSDictionary *expectedData = @{@"foo" : @{@"baz" : @"baz-value"}};
  87. XCTAssertEqualObjects(
  88. patchedDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations));
  89. }
  90. - (void)testPatchesPrimitiveValue {
  91. NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
  92. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  93. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
  94. FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc
  95. baseDocument:baseDoc
  96. localWriteTime:_timestamp];
  97. NSDictionary *expectedData = @{@"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value"};
  98. XCTAssertEqualObjects(
  99. patchedDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations));
  100. }
  101. - (void)testPatchingDeletedDocumentsDoesNothing {
  102. FSTMaybeDocument *baseDoc = FSTTestDeletedDoc("collection/key", 0, NO);
  103. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"bar"}, {});
  104. FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc
  105. baseDocument:baseDoc
  106. localWriteTime:_timestamp];
  107. XCTAssertEqualObjects(patchedDoc, baseDoc);
  108. }
  109. - (void)testAppliesLocalServerTimestampTransformToDocuments {
  110. NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"};
  111. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  112. FSTMutation *transform = FSTTestTransformMutation(
  113. @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
  114. FSTMaybeDocument *transformedDoc = [transform applyToLocalDocument:baseDoc
  115. baseDocument:baseDoc
  116. localWriteTime:_timestamp];
  117. // Server timestamps aren't parsed, so we manually insert it.
  118. FSTObjectValue *expectedData =
  119. FSTTestObjectValue(@{@"foo" : @{@"bar" : @"<server-timestamp>"}, @"baz" : @"baz-value"});
  120. expectedData =
  121. [expectedData objectBySettingValue:[FSTServerTimestampValue
  122. serverTimestampValueWithLocalWriteTime:_timestamp
  123. previousValue:nil]
  124. forPath:testutil::Field("foo.bar")];
  125. FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData
  126. key:FSTTestDocKey(@"collection/key")
  127. version:testutil::Version(0)
  128. state:FSTDocumentStateLocalMutations];
  129. XCTAssertEqualObjects(transformedDoc, expectedDoc);
  130. }
  131. - (void)testAppliesIncrementTransformToDocument {
  132. NSDictionary *baseDoc = @{
  133. @"longPlusLong" : @1,
  134. @"longPlusDouble" : @2,
  135. @"doublePlusLong" : @3.3,
  136. @"doublePlusDouble" : @4.0,
  137. @"longPlusNan" : @5,
  138. @"doublePlusNan" : @6.6,
  139. @"longPlusInfinity" : @7,
  140. @"doublePlusInfinity" : @8.8
  141. };
  142. NSDictionary *transform = @{
  143. @"longPlusLong" : [FIRFieldValue fieldValueForIntegerIncrement:1],
  144. @"longPlusDouble" : [FIRFieldValue fieldValueForDoubleIncrement:2.2],
  145. @"doublePlusLong" : [FIRFieldValue fieldValueForIntegerIncrement:3],
  146. @"doublePlusDouble" : [FIRFieldValue fieldValueForDoubleIncrement:4.4],
  147. @"longPlusNan" : [FIRFieldValue fieldValueForDoubleIncrement:NAN],
  148. @"doublePlusNan" : [FIRFieldValue fieldValueForDoubleIncrement:NAN],
  149. @"longPlusInfinity" : [FIRFieldValue fieldValueForDoubleIncrement:INFINITY],
  150. @"doublePlusInfinity" : [FIRFieldValue fieldValueForDoubleIncrement:INFINITY]
  151. };
  152. NSDictionary *expected = @{
  153. @"longPlusLong" : @2L,
  154. @"longPlusDouble" : @4.2,
  155. @"doublePlusLong" : @6.3,
  156. @"doublePlusDouble" : @8.4,
  157. @"longPlusNan" : @(NAN),
  158. @"doublePlusNan" : @(NAN),
  159. @"longPlusInfinity" : @(INFINITY),
  160. @"doublePlusInfinity" : @(INFINITY)
  161. };
  162. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  163. }
  164. - (void)testAppliesIncrementTransformToUnexpectedType {
  165. NSDictionary *baseDoc = @{@"string" : @"zero"};
  166. NSDictionary *transform = @{@"string" : [FIRFieldValue fieldValueForIntegerIncrement:1]};
  167. NSDictionary *expected = @{@"string" : @1};
  168. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  169. }
  170. - (void)testAppliesIncrementTransformToMissingField {
  171. NSDictionary *baseDoc = @{};
  172. NSDictionary *transform = @{@"missing" : [FIRFieldValue fieldValueForIntegerIncrement:1]};
  173. NSDictionary *expected = @{@"missing" : @1};
  174. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  175. }
  176. - (void)testAppliesIncrementTransformsConsecutively {
  177. NSDictionary *baseDoc = @{@"number" : @1};
  178. NSDictionary *transform1 = @{@"number" : [FIRFieldValue fieldValueForIntegerIncrement:2]};
  179. NSDictionary *transform2 = @{@"number" : [FIRFieldValue fieldValueForIntegerIncrement:3]};
  180. NSDictionary *transform3 = @{@"number" : [FIRFieldValue fieldValueForIntegerIncrement:4]};
  181. NSDictionary *expected = @{@"number" : @10};
  182. [self transformBaseDoc:baseDoc
  183. applyTransforms:@[ transform1, transform2, transform3 ]
  184. expecting:expected];
  185. }
  186. - (void)testAppliesIncrementWithoutOverflow {
  187. NSDictionary *baseDoc =
  188. @{@"a" : @(LONG_MAX - 1), @"b" : @(LONG_MAX - 1), @"c" : @(LONG_MAX), @"d" : @(LONG_MAX)};
  189. NSDictionary *transform = @{
  190. @"a" : [FIRFieldValue fieldValueForIntegerIncrement:1],
  191. @"b" : [FIRFieldValue fieldValueForIntegerIncrement:LONG_MAX],
  192. @"c" : [FIRFieldValue fieldValueForIntegerIncrement:1],
  193. @"d" : [FIRFieldValue fieldValueForIntegerIncrement:LONG_MAX]
  194. };
  195. NSDictionary *expected =
  196. @{@"a" : @LONG_MAX, @"b" : @LONG_MAX, @"c" : @LONG_MAX, @"d" : @LONG_MAX};
  197. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  198. }
  199. - (void)testAppliesIncrementWithoutUnderflow {
  200. NSDictionary *baseDoc =
  201. @{@"a" : @(LONG_MIN + 1), @"b" : @(LONG_MIN + 1), @"c" : @(LONG_MIN), @"d" : @(LONG_MIN)};
  202. NSDictionary *transform = @{
  203. @"a" : [FIRFieldValue fieldValueForIntegerIncrement:-1],
  204. @"b" : [FIRFieldValue fieldValueForIntegerIncrement:LONG_MIN],
  205. @"c" : [FIRFieldValue fieldValueForIntegerIncrement:-1],
  206. @"d" : [FIRFieldValue fieldValueForIntegerIncrement:LONG_MIN]
  207. };
  208. NSDictionary *expected =
  209. @{@"a" : @(LONG_MIN), @"b" : @(LONG_MIN), @"c" : @(LONG_MIN), @"d" : @(LONG_MIN)};
  210. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  211. }
  212. // NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
  213. // unit tests for it currently. We could consider removing this test once we have integration tests.
  214. - (void)testCreateArrayUnionTransform {
  215. FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
  216. @"foo" : [FIRFieldValue fieldValueForArrayUnion:@[ @"tag" ]],
  217. @"bar.baz" :
  218. [FIRFieldValue fieldValueForArrayUnion:@[ @YES, @{@"nested" : @{@"a" : @[ @1, @2 ]}} ]]
  219. });
  220. XCTAssertEqual(transform.fieldTransforms.size(), 2);
  221. const FieldTransform &first = transform.fieldTransforms[0];
  222. XCTAssertEqual(first.path(), FieldPath({"foo"}));
  223. {
  224. std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
  225. ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
  226. XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
  227. }
  228. const FieldTransform &second = transform.fieldTransforms[1];
  229. XCTAssertEqual(second.path(), FieldPath({"bar", "baz"}));
  230. {
  231. std::vector<FSTFieldValue *> expectedElements {
  232. FSTTestFieldValue(@YES), FSTTestFieldValue(@{@"nested" : @{@"a" : @[ @1, @2 ]}})
  233. };
  234. ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
  235. XCTAssertEqual(static_cast<const ArrayTransform &>(second.transformation()), expected);
  236. }
  237. }
  238. // NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
  239. // unit tests for it currently. We could consider removing this test once we have integration tests.
  240. - (void)testCreateArrayRemoveTransform {
  241. FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
  242. @"foo" : [FIRFieldValue fieldValueForArrayRemove:@[ @"tag" ]],
  243. });
  244. XCTAssertEqual(transform.fieldTransforms.size(), 1);
  245. const FieldTransform &first = transform.fieldTransforms[0];
  246. XCTAssertEqual(first.path(), FieldPath({"foo"}));
  247. {
  248. std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
  249. const ArrayTransform expected(TransformOperation::Type::ArrayRemove, expectedElements);
  250. XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
  251. }
  252. }
  253. - (void)testAppliesLocalArrayUnionTransformToMissingField {
  254. auto baseDoc = @{};
  255. auto transform = @{@"missing" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]};
  256. auto expected = @{@"missing" : @[ @1, @2 ]};
  257. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  258. }
  259. - (void)testAppliesLocalArrayUnionTransformToNonArrayField {
  260. auto baseDoc = @{@"non-array" : @42};
  261. auto transform = @{@"non-array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]};
  262. auto expected = @{@"non-array" : @[ @1, @2 ]};
  263. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  264. }
  265. - (void)testAppliesLocalArrayUnionTransformWithNonExistingElements {
  266. auto baseDoc = @{@"array" : @[ @1, @3 ]};
  267. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @4 ]]};
  268. auto expected = @{@"array" : @[ @1, @3, @2, @4 ]};
  269. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  270. }
  271. - (void)testAppliesLocalArrayUnionTransformWithExistingElements {
  272. auto baseDoc = @{@"array" : @[ @1, @3 ]};
  273. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @3 ]]};
  274. auto expected = @{@"array" : @[ @1, @3 ]};
  275. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  276. }
  277. - (void)testAppliesLocalArrayUnionTransformWithDuplicateExistingElements {
  278. // Duplicate entries in your existing array should be preserved.
  279. auto baseDoc = @{@"array" : @[ @1, @2, @2, @3 ]};
  280. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2 ]]};
  281. auto expected = @{@"array" : @[ @1, @2, @2, @3 ]};
  282. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  283. }
  284. - (void)testAppliesLocalArrayUnionTransformWithDuplicateUnionElements {
  285. // Duplicate entries in your union array should only be added once.
  286. auto baseDoc = @{@"array" : @[ @1, @3 ]};
  287. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @2 ]]};
  288. auto expected = @{@"array" : @[ @1, @3, @2 ]};
  289. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  290. }
  291. - (void)testAppliesLocalArrayUnionTransformWithNonPrimitiveElements {
  292. // Union nested object values (one existing, one not).
  293. auto baseDoc = @{@"array" : @[ @1, @{@"a" : @"b"} ]};
  294. auto transform =
  295. @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]]};
  296. auto expected = @{@"array" : @[ @1, @{@"a" : @"b"}, @{@"c" : @"d"} ]};
  297. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  298. }
  299. - (void)testAppliesLocalArrayUnionTransformWithPartiallyOverlappingElements {
  300. // Union objects that partially overlap an existing object.
  301. auto baseDoc = @{@"array" : @[ @1, @{@"a" : @"b", @"c" : @"d"} ]};
  302. auto transform =
  303. @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]]};
  304. auto expected =
  305. @{@"array" : @[ @1, @{@"a" : @"b", @"c" : @"d"}, @{@"a" : @"b"}, @{@"c" : @"d"} ]};
  306. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  307. }
  308. - (void)testAppliesLocalArrayRemoveTransformToMissingField {
  309. auto baseDoc = @{};
  310. auto transform = @{@"missing" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @2 ]]};
  311. auto expected = @{@"missing" : @[]};
  312. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  313. }
  314. - (void)testAppliesLocalArrayRemoveTransformToNonArrayField {
  315. auto baseDoc = @{@"non-array" : @42};
  316. auto transform = @{@"non-array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @2 ]]};
  317. auto expected = @{@"non-array" : @[]};
  318. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  319. }
  320. - (void)testAppliesLocalArrayRemoveTransformWithNonExistingElements {
  321. auto baseDoc = @{@"array" : @[ @1, @3 ]};
  322. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @2, @4 ]]};
  323. auto expected = @{@"array" : @[ @1, @3 ]};
  324. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  325. }
  326. - (void)testAppliesLocalArrayRemoveTransformWithExistingElements {
  327. auto baseDoc = @{@"array" : @[ @1, @2, @3, @4 ]};
  328. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @3 ]]};
  329. auto expected = @{@"array" : @[ @2, @4 ]};
  330. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  331. }
  332. - (void)testAppliesLocalArrayRemoveTransformWithNonPrimitiveElements {
  333. // Remove nested object values (one existing, one not).
  334. auto baseDoc = @{@"array" : @[ @1, @{@"a" : @"b"} ]};
  335. auto transform =
  336. @{@"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]]};
  337. auto expected = @{@"array" : @[ @1 ]};
  338. [self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
  339. }
  340. // Helper to test a particular transform scenario.
  341. - (void)transformBaseDoc:(NSDictionary<NSString *, id> *)baseData
  342. applyTransforms:(NSArray<NSDictionary<NSString *, id> *> *)transforms
  343. expecting:(NSDictionary<NSString *, id> *)expectedData {
  344. FSTMaybeDocument *currentDoc = FSTTestDoc("collection/key", 0, baseData, FSTDocumentStateSynced);
  345. for (NSDictionary<NSString *, id> *transformData in transforms) {
  346. FSTMutation *transform = FSTTestTransformMutation(@"collection/key", transformData);
  347. currentDoc = [transform applyToLocalDocument:currentDoc
  348. baseDocument:currentDoc
  349. localWriteTime:_timestamp];
  350. }
  351. FSTDocument *expectedDoc = [FSTDocument documentWithData:FSTTestObjectValue(expectedData)
  352. key:FSTTestDocKey(@"collection/key")
  353. version:testutil::Version(0)
  354. state:FSTDocumentStateLocalMutations];
  355. XCTAssertEqualObjects(currentDoc, expectedDoc);
  356. }
  357. - (void)transformBaseDoc:(NSDictionary<NSString *, id> *)baseData
  358. applyTransform:(NSDictionary<NSString *, id> *)transformData
  359. expecting:(NSDictionary<NSString *, id> *)expectedData {
  360. [self transformBaseDoc:baseData applyTransforms:@[ transformData ] expecting:expectedData];
  361. }
  362. - (void)testAppliesServerAckedIncrementTransformToDocuments {
  363. NSDictionary *docData = @{@"sum" : @1};
  364. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  365. FSTMutation *transform = FSTTestTransformMutation(
  366. @"collection/key", @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:2]});
  367. FSTMutationResult *mutationResult =
  368. [[FSTMutationResult alloc] initWithVersion:testutil::Version(1)
  369. transformResults:@[ FieldValue::FromInteger(3).Wrap() ]];
  370. FSTMaybeDocument *transformedDoc = [transform applyToRemoteDocument:baseDoc
  371. mutationResult:mutationResult];
  372. NSDictionary *expectedData = @{@"sum" : @3};
  373. XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 1, expectedData,
  374. FSTDocumentStateCommittedMutations));
  375. }
  376. - (void)testAppliesServerAckedServerTimestampTransformToDocuments {
  377. NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"};
  378. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  379. FSTMutation *transform = FSTTestTransformMutation(
  380. @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
  381. FSTMutationResult *mutationResult =
  382. [[FSTMutationResult alloc] initWithVersion:testutil::Version(1)
  383. transformResults:@[ FieldValue::FromTimestamp(_timestamp).Wrap() ]];
  384. FIRTimestamp *publicTimestamp = api::MakeFIRTimestamp(_timestamp);
  385. FSTMaybeDocument *transformedDoc = [transform applyToRemoteDocument:baseDoc
  386. mutationResult:mutationResult];
  387. NSDictionary *expectedData =
  388. @{@"foo" : @{@"bar" : publicTimestamp.dateValue}, @"baz" : @"baz-value"};
  389. FSTDocument *expectedDoc =
  390. FSTTestDoc("collection/key", 1, expectedData, FSTDocumentStateCommittedMutations);
  391. XCTAssertEqualObjects(transformedDoc, expectedDoc);
  392. }
  393. - (void)testAppliesServerAckedArrayTransformsToDocuments {
  394. NSDictionary *docData = @{@"array_1" : @[ @1, @2 ], @"array_2" : @[ @"a", @"b" ]};
  395. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  396. FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @{
  397. @"array_1" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @3 ]],
  398. @"array_2" : [FIRFieldValue fieldValueForArrayRemove:@[ @"a", @"c" ]]
  399. });
  400. // Server just sends null transform results for array operations.
  401. FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
  402. initWithVersion:testutil::Version(1)
  403. transformResults:@[ FieldValue::Null().Wrap(), FieldValue::Null().Wrap() ]];
  404. FSTMaybeDocument *transformedDoc = [transform applyToRemoteDocument:baseDoc
  405. mutationResult:mutationResult];
  406. NSDictionary *expectedData = @{@"array_1" : @[ @1, @2, @3 ], @"array_2" : @[ @"b" ]};
  407. XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 1, expectedData,
  408. FSTDocumentStateCommittedMutations));
  409. }
  410. - (void)testDeleteDeletes {
  411. NSDictionary *docData = @{@"foo" : @"bar"};
  412. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  413. FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key");
  414. FSTMaybeDocument *result = [mutation applyToLocalDocument:baseDoc
  415. baseDocument:baseDoc
  416. localWriteTime:_timestamp];
  417. XCTAssertEqualObjects(result, FSTTestDeletedDoc("collection/key", 0, NO));
  418. }
  419. - (void)testSetWithMutationResult {
  420. NSDictionary *docData = @{@"foo" : @"bar"};
  421. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  422. FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"});
  423. FSTMutationResult *mutationResult =
  424. [[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
  425. FSTMaybeDocument *setDoc = [set applyToRemoteDocument:baseDoc mutationResult:mutationResult];
  426. NSDictionary *expectedData = @{@"foo" : @"new-bar"};
  427. XCTAssertEqualObjects(
  428. setDoc, FSTTestDoc("collection/key", 4, expectedData, FSTDocumentStateCommittedMutations));
  429. }
  430. - (void)testPatchWithMutationResult {
  431. NSDictionary *docData = @{@"foo" : @"bar"};
  432. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
  433. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"new-bar"}, {});
  434. FSTMutationResult *mutationResult =
  435. [[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
  436. FSTMaybeDocument *patchedDoc = [patch applyToRemoteDocument:baseDoc
  437. mutationResult:mutationResult];
  438. NSDictionary *expectedData = @{@"foo" : @"new-bar"};
  439. XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 4, expectedData,
  440. FSTDocumentStateCommittedMutations));
  441. }
  442. #define ASSERT_VERSION_TRANSITION(mutation, base, result, expected) \
  443. do { \
  444. FSTMaybeDocument *actual = [mutation applyToRemoteDocument:base mutationResult:result]; \
  445. XCTAssertEqualObjects(actual, expected); \
  446. } while (0);
  447. /**
  448. * Tests the transition table documented in FSTMutation.h.
  449. */
  450. - (void)testTransitions {
  451. FSTDocument *docV3 = FSTTestDoc("collection/key", 3, @{}, FSTDocumentStateSynced);
  452. FSTDeletedDocument *deletedV3 = FSTTestDeletedDoc("collection/key", 3, NO);
  453. FSTMutation *setMutation = FSTTestSetMutation(@"collection/key", @{});
  454. FSTMutation *patchMutation = FSTTestPatchMutation("collection/key", @{}, {});
  455. FSTMutation *transformMutation = FSTTestTransformMutation(@"collection/key", @{});
  456. FSTMutation *deleteMutation = FSTTestDeleteMutation(@"collection/key");
  457. FSTDeletedDocument *docV7Deleted = FSTTestDeletedDoc("collection/key", 7, YES);
  458. FSTDocument *docV7Committed =
  459. FSTTestDoc("collection/key", 7, @{}, FSTDocumentStateCommittedMutations);
  460. FSTUnknownDocument *docV7Unknown = FSTTestUnknownDoc("collection/key", 7);
  461. FSTMutationResult *mutationResult =
  462. [[FSTMutationResult alloc] initWithVersion:testutil::Version(7) transformResults:nil];
  463. FSTMutationResult *transformResult =
  464. [[FSTMutationResult alloc] initWithVersion:testutil::Version(7) transformResults:@[]];
  465. ASSERT_VERSION_TRANSITION(setMutation, docV3, mutationResult, docV7Committed);
  466. ASSERT_VERSION_TRANSITION(setMutation, deletedV3, mutationResult, docV7Committed);
  467. ASSERT_VERSION_TRANSITION(setMutation, nil, mutationResult, docV7Committed);
  468. ASSERT_VERSION_TRANSITION(patchMutation, docV3, mutationResult, docV7Committed);
  469. ASSERT_VERSION_TRANSITION(patchMutation, deletedV3, mutationResult, docV7Unknown);
  470. ASSERT_VERSION_TRANSITION(patchMutation, nil, mutationResult, docV7Unknown);
  471. ASSERT_VERSION_TRANSITION(transformMutation, docV3, transformResult, docV7Committed);
  472. ASSERT_VERSION_TRANSITION(transformMutation, deletedV3, transformResult, docV7Unknown);
  473. ASSERT_VERSION_TRANSITION(transformMutation, nil, transformResult, docV7Unknown);
  474. ASSERT_VERSION_TRANSITION(deleteMutation, docV3, mutationResult, docV7Deleted);
  475. ASSERT_VERSION_TRANSITION(deleteMutation, deletedV3, mutationResult, docV7Deleted);
  476. ASSERT_VERSION_TRANSITION(deleteMutation, nil, mutationResult, docV7Deleted);
  477. }
  478. #undef ASSERT_TRANSITION
  479. @end