FSTMutationTests.mm 26 KB

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