FSTMutationTests.mm 30 KB

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