FSTSerializerBetaTests.mm 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  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/Remote/FSTSerializerBeta.h"
  17. #import <FirebaseFirestore/FIRFieldPath.h>
  18. #import <FirebaseFirestore/FIRFieldValue.h>
  19. #import <FirebaseFirestore/FIRFirestoreErrors.h>
  20. #import <FirebaseFirestore/FIRGeoPoint.h>
  21. #import <FirebaseFirestore/FIRTimestamp.h>
  22. #import <XCTest/XCTest.h>
  23. #include <memory>
  24. #include <vector>
  25. #import "Firestore/Protos/objc/firestore/local/MaybeDocument.pbobjc.h"
  26. #import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h"
  27. #import "Firestore/Protos/objc/google/firestore/v1/Common.pbobjc.h"
  28. #import "Firestore/Protos/objc/google/firestore/v1/Document.pbobjc.h"
  29. #import "Firestore/Protos/objc/google/firestore/v1/Firestore.pbobjc.h"
  30. #import "Firestore/Protos/objc/google/firestore/v1/Query.pbobjc.h"
  31. #import "Firestore/Protos/objc/google/firestore/v1/Write.pbobjc.h"
  32. #import "Firestore/Protos/objc/google/rpc/Status.pbobjc.h"
  33. #import "Firestore/Protos/objc/google/type/Latlng.pbobjc.h"
  34. #import "Firestore/Source/API/FIRFieldValue+Internal.h"
  35. #import "Firestore/Source/Local/FSTQueryData.h"
  36. #import "Firestore/Source/Model/FSTDocument.h"
  37. #import "Firestore/Source/Model/FSTMutation.h"
  38. #import "Firestore/Source/Model/FSTMutationBatch.h"
  39. #import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
  40. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  41. #include "Firestore/core/include/firebase/firestore/firestore_errors.h"
  42. #include "Firestore/core/src/firebase/firestore/core/direction.h"
  43. #include "Firestore/core/src/firebase/firestore/core/field_filter.h"
  44. #include "Firestore/core/src/firebase/firestore/core/filter.h"
  45. #include "Firestore/core/src/firebase/firestore/core/order_by.h"
  46. #include "Firestore/core/src/firebase/firestore/model/database_id.h"
  47. #include "Firestore/core/src/firebase/firestore/model/field_mask.h"
  48. #include "Firestore/core/src/firebase/firestore/model/field_path.h"
  49. #include "Firestore/core/src/firebase/firestore/model/field_transform.h"
  50. #include "Firestore/core/src/firebase/firestore/model/field_value.h"
  51. #include "Firestore/core/src/firebase/firestore/model/precondition.h"
  52. #include "Firestore/core/src/firebase/firestore/remote/watch_change.h"
  53. #include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
  54. #include "Firestore/core/src/firebase/firestore/util/status.h"
  55. #include "Firestore/core/src/firebase/firestore/util/string_apple.h"
  56. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  57. namespace testutil = firebase::firestore::testutil;
  58. namespace util = firebase::firestore::util;
  59. using firebase::Timestamp;
  60. using firebase::firestore::Error;
  61. using firebase::firestore::core::Direction;
  62. using firebase::firestore::core::FieldFilter;
  63. using firebase::firestore::core::OrderBy;
  64. using firebase::firestore::model::DatabaseId;
  65. using firebase::firestore::model::DocumentKey;
  66. using firebase::firestore::model::DocumentState;
  67. using firebase::firestore::model::FieldMask;
  68. using firebase::firestore::model::FieldPath;
  69. using firebase::firestore::model::FieldTransform;
  70. using firebase::firestore::model::FieldValue;
  71. using firebase::firestore::model::ObjectValue;
  72. using firebase::firestore::model::Precondition;
  73. using firebase::firestore::model::SnapshotVersion;
  74. using firebase::firestore::remote::DocumentWatchChange;
  75. using firebase::firestore::remote::ExistenceFilterWatchChange;
  76. using firebase::firestore::remote::WatchChange;
  77. using firebase::firestore::remote::WatchTargetChange;
  78. using firebase::firestore::remote::WatchTargetChangeState;
  79. using firebase::firestore::testutil::Array;
  80. using firebase::firestore::util::Status;
  81. using testutil::Filter;
  82. using testutil::OrderBy;
  83. using testutil::Query;
  84. using testutil::Ref;
  85. using testutil::Value;
  86. namespace {
  87. template <typename T>
  88. bool Equals(const WatchChange &lhs, const WatchChange &rhs) {
  89. return static_cast<const T &>(lhs) == static_cast<const T &>(rhs);
  90. }
  91. // Compares two `WatchChange`s taking into account their actual derived type.
  92. bool IsWatchChangeEqual(const WatchChange &lhs, const WatchChange &rhs) {
  93. if (lhs.type() != rhs.type()) {
  94. return false;
  95. }
  96. switch (lhs.type()) {
  97. case WatchChange::Type::Document:
  98. return Equals<DocumentWatchChange>(lhs, rhs);
  99. case WatchChange::Type::ExistenceFilter:
  100. return Equals<ExistenceFilterWatchChange>(lhs, rhs);
  101. case WatchChange::Type::TargetChange:
  102. return Equals<WatchTargetChange>(lhs, rhs);
  103. }
  104. UNREACHABLE();
  105. }
  106. NSString *const kDocumentKeyPath =
  107. [[NSString alloc] initWithUTF8String:FieldPath::kDocumentKeyPath];
  108. } // namespace
  109. NS_ASSUME_NONNULL_BEGIN
  110. @interface GCFSStructuredQuery_Order (Test)
  111. + (instancetype)messageWithProperty:(NSString *)property ascending:(BOOL)ascending;
  112. @end
  113. @implementation GCFSStructuredQuery_Order (Test)
  114. + (instancetype)messageWithProperty:(NSString *)property ascending:(BOOL)ascending {
  115. GCFSStructuredQuery_Order *order = [GCFSStructuredQuery_Order message];
  116. order.field.fieldPath = property;
  117. order.direction = ascending ? GCFSStructuredQuery_Direction_Ascending
  118. : GCFSStructuredQuery_Direction_Descending;
  119. return order;
  120. }
  121. @end
  122. @interface FSTSerializerBetaTests : XCTestCase
  123. @property(nonatomic, strong) FSTSerializerBeta *serializer;
  124. @end
  125. @implementation FSTSerializerBetaTests
  126. - (void)setUp {
  127. self.serializer = [[FSTSerializerBeta alloc] initWithDatabaseID:DatabaseId("p", "d")];
  128. }
  129. - (void)testEncodesNull {
  130. FieldValue model = FieldValue::Null();
  131. GCFSValue *proto = [GCFSValue message];
  132. proto.nullValue = GPBNullValue_NullValue;
  133. [self assertRoundTripForModel:model proto:proto type:GCFSValue_ValueType_OneOfCase_NullValue];
  134. }
  135. - (void)testEncodesBool {
  136. NSArray<NSNumber *> *examples = @[ @YES, @NO ];
  137. for (NSNumber *example in examples) {
  138. FieldValue model = FSTTestFieldValue(example);
  139. GCFSValue *proto = [GCFSValue message];
  140. proto.booleanValue = [example boolValue];
  141. [self assertRoundTripForModel:model
  142. proto:proto
  143. type:GCFSValue_ValueType_OneOfCase_BooleanValue];
  144. }
  145. }
  146. - (void)testEncodesIntegers {
  147. NSArray<NSNumber *> *examples = @[ @(LLONG_MIN), @(-100), @(-1), @0, @1, @100, @(LLONG_MAX) ];
  148. for (NSNumber *example in examples) {
  149. FieldValue model = FSTTestFieldValue(example);
  150. GCFSValue *proto = [GCFSValue message];
  151. proto.integerValue = [example longLongValue];
  152. [self assertRoundTripForModel:model
  153. proto:proto
  154. type:GCFSValue_ValueType_OneOfCase_IntegerValue];
  155. }
  156. }
  157. - (void)testEncodesDoubles {
  158. NSArray<NSNumber *> *examples = @[
  159. // normal negative numbers.
  160. @(-INFINITY), @(-DBL_MAX), @(LLONG_MIN * 1.0 - 1.0), @(-2.0), @(-1.1), @(-1.0), @(-DBL_MIN),
  161. // negative smallest subnormal, zeroes, positive smallest subnormal
  162. @(-0x1.0p-1074), @(-0.0), @(0.0), @(0x1.0p-1074),
  163. // and the rest
  164. @(DBL_MIN), @0.1, @1.1, @(LLONG_MAX * 1.0), @(DBL_MAX), @(INFINITY),
  165. // NaN.
  166. @(0.0 / 0.0)
  167. ];
  168. for (NSNumber *example in examples) {
  169. FieldValue model = FSTTestFieldValue(example);
  170. GCFSValue *proto = [GCFSValue message];
  171. proto.doubleValue = [example doubleValue];
  172. [self assertRoundTripForModel:model proto:proto type:GCFSValue_ValueType_OneOfCase_DoubleValue];
  173. }
  174. }
  175. - (void)testEncodesStrings {
  176. NSArray<NSString *> *examples = @[
  177. @"",
  178. @"a",
  179. @"abc def",
  180. @"æ",
  181. @"\0\ud7ff\ue000\uffff",
  182. @"(╯°□°)╯︵ ┻━┻",
  183. ];
  184. for (NSString *example in examples) {
  185. FieldValue model = FSTTestFieldValue(example);
  186. GCFSValue *proto = [GCFSValue message];
  187. proto.stringValue = example;
  188. [self assertRoundTripForModel:model proto:proto type:GCFSValue_ValueType_OneOfCase_StringValue];
  189. }
  190. }
  191. - (void)testEncodesDates {
  192. NSDateComponents *dateWithNanos = FSTTestDateComponents(2016, 1, 2, 10, 20, 50);
  193. dateWithNanos.nanosecond = 500000000;
  194. NSArray<NSDate *> *examples = @[
  195. [[NSCalendar currentCalendar] dateFromComponents:dateWithNanos],
  196. FSTTestDate(2016, 6, 17, 10, 50, 15)
  197. ];
  198. GCFSValue *timestamp1 = [GCFSValue message];
  199. timestamp1.timestampValue.seconds = 1451730050;
  200. timestamp1.timestampValue.nanos = 500000000;
  201. GCFSValue *timestamp2 = [GCFSValue message];
  202. timestamp2.timestampValue.seconds = 1466160615;
  203. timestamp2.timestampValue.nanos = 0;
  204. NSArray<GCFSValue *> *expectedTimestamps = @[ timestamp1, timestamp2 ];
  205. for (NSUInteger i = 0; i < [examples count]; i++) {
  206. [self assertRoundTripForModel:FSTTestFieldValue(examples[i])
  207. proto:expectedTimestamps[i]
  208. type:GCFSValue_ValueType_OneOfCase_TimestampValue];
  209. }
  210. }
  211. - (void)testEncodesGeoPoints {
  212. NSArray<FIRGeoPoint *> *examples =
  213. @[ FSTTestGeoPoint(0, 0), FSTTestGeoPoint(1.24, 4.56), FSTTestGeoPoint(-90, 180) ];
  214. for (FIRGeoPoint *example in examples) {
  215. FieldValue model = FSTTestFieldValue(example);
  216. GCFSValue *proto = [GCFSValue message];
  217. proto.geoPointValue = [GTPLatLng message];
  218. proto.geoPointValue.latitude = example.latitude;
  219. proto.geoPointValue.longitude = example.longitude;
  220. [self assertRoundTripForModel:model
  221. proto:proto
  222. type:GCFSValue_ValueType_OneOfCase_GeoPointValue];
  223. }
  224. }
  225. - (void)testEncodesBlobs {
  226. NSArray<NSData *> *examples = @[
  227. FSTTestData(-1),
  228. FSTTestData(0, -1),
  229. FSTTestData(0, 1, 2, -1),
  230. FSTTestData(255, -1),
  231. FSTTestData(0, 1, 255, -1),
  232. ];
  233. for (NSData *example in examples) {
  234. FieldValue model = FSTTestFieldValue(example);
  235. GCFSValue *proto = [GCFSValue message];
  236. proto.bytesValue = example;
  237. [self assertRoundTripForModel:model proto:proto type:GCFSValue_ValueType_OneOfCase_BytesValue];
  238. }
  239. }
  240. - (void)testEncodesResourceNames {
  241. self.serializer = [[FSTSerializerBeta alloc] initWithDatabaseID:DatabaseId("project")];
  242. FSTDocumentKeyReference *reference = FSTTestRef("project", DatabaseId::kDefault, @"foo/bar");
  243. GCFSValue *proto = [GCFSValue message];
  244. proto.referenceValue = @"projects/project/databases/(default)/documents/foo/bar";
  245. [self assertRoundTripForModel:FSTTestFieldValue(reference)
  246. proto:proto
  247. type:GCFSValue_ValueType_OneOfCase_ReferenceValue];
  248. }
  249. - (void)testEncodesArrays {
  250. FieldValue model = FSTTestFieldValue(@[ @YES, @"foo" ]);
  251. GCFSValue *proto = [GCFSValue message];
  252. [proto.arrayValue.valuesArray addObjectsFromArray:@[
  253. [self.serializer encodedBool:true], [self.serializer encodedString:"foo"]
  254. ]];
  255. [self assertRoundTripForModel:model proto:proto type:GCFSValue_ValueType_OneOfCase_ArrayValue];
  256. }
  257. - (void)testEncodesEmptyMap {
  258. FieldValue model = ObjectValue::Empty();
  259. GCFSValue *proto = [GCFSValue message];
  260. proto.mapValue = [GCFSMapValue message];
  261. [self assertRoundTripForModel:model proto:proto type:GCFSValue_ValueType_OneOfCase_MapValue];
  262. }
  263. - (void)testEncodesNestedObjects {
  264. FieldValue model = FSTTestFieldValue(@{
  265. @"b" : @YES,
  266. @"d" : @(DBL_MAX),
  267. @"i" : @1,
  268. @"n" : [NSNull null],
  269. @"s" : @"foo",
  270. @"a" : @[ @2, @"bar", @{@"b" : @NO} ],
  271. @"o" : @{
  272. @"d" : @100,
  273. @"nested" : @{@"e" : @(LLONG_MIN)},
  274. },
  275. });
  276. GCFSValue *innerObject = [GCFSValue message];
  277. innerObject.mapValue.fields[@"b"] = [self.serializer encodedBool:false];
  278. GCFSValue *middleArray = [GCFSValue message];
  279. [middleArray.arrayValue.valuesArray addObjectsFromArray:@[
  280. [self.serializer encodedInteger:2], [self.serializer encodedString:"bar"], innerObject
  281. ]];
  282. innerObject = [GCFSValue message];
  283. innerObject.mapValue.fields[@"e"] = [self.serializer encodedInteger:LLONG_MIN];
  284. GCFSValue *middleObject = [GCFSValue message];
  285. [middleObject.mapValue.fields addEntriesFromDictionary:@{
  286. @"d" : [self.serializer encodedInteger:100],
  287. @"nested" : innerObject
  288. }];
  289. GCFSValue *proto = [GCFSValue message];
  290. [proto.mapValue.fields addEntriesFromDictionary:@{
  291. @"b" : [self.serializer encodedBool:true],
  292. @"d" : [self.serializer encodedDouble:DBL_MAX],
  293. @"i" : [self.serializer encodedInteger:1],
  294. @"n" : [self.serializer encodedNull],
  295. @"s" : [self.serializer encodedString:"foo"],
  296. @"a" : middleArray,
  297. @"o" : middleObject
  298. }];
  299. [self assertRoundTripForModel:model proto:proto type:GCFSValue_ValueType_OneOfCase_MapValue];
  300. }
  301. - (void)assertRoundTripForModel:(const FieldValue &)model
  302. proto:(GCFSValue *)value
  303. type:(GCFSValue_ValueType_OneOfCase)type {
  304. GCFSValue *actualProto = [self.serializer encodedFieldValue:model];
  305. XCTAssertEqual(actualProto.valueTypeOneOfCase, type);
  306. XCTAssertEqualObjects(actualProto, value);
  307. FieldValue actualModel = [self.serializer decodedFieldValue:value];
  308. XCTAssertEqual(actualModel, model);
  309. }
  310. - (void)testEncodesSetMutation {
  311. FSTSetMutation *mutation = FSTTestSetMutation(@"docs/1", @{@"a" : @"b", @"num" : @1});
  312. GCFSWrite *proto = [GCFSWrite message];
  313. proto.update = [self.serializer encodedDocumentWithFields:mutation.value key:mutation.key];
  314. [self assertRoundTripForMutation:mutation proto:proto];
  315. }
  316. - (void)testEncodesPatchMutation {
  317. FSTPatchMutation *mutation = FSTTestPatchMutation(
  318. "docs/1", @{@"a" : @"b", @"num" : @1, @"some.de\\\\ep.th\\ing'" : @2}, {});
  319. GCFSWrite *proto = [GCFSWrite message];
  320. proto.update = [self.serializer encodedDocumentWithFields:mutation.value key:mutation.key];
  321. proto.updateMask = [self.serializer encodedFieldMask:*(mutation.fieldMask)];
  322. proto.currentDocument.exists = YES;
  323. [self assertRoundTripForMutation:mutation proto:proto];
  324. }
  325. - (void)testEncodesDeleteMutation {
  326. FSTDeleteMutation *mutation = FSTTestDeleteMutation(@"docs/1");
  327. GCFSWrite *proto = [GCFSWrite message];
  328. proto.delete_p = @"projects/p/databases/d/documents/docs/1";
  329. [self assertRoundTripForMutation:mutation proto:proto];
  330. }
  331. - (void)testEncodesServerTimestampTransformMutation {
  332. FSTTransformMutation *mutation = FSTTestTransformMutation(@"docs/1", @{
  333. @"a" : [FIRFieldValue fieldValueForServerTimestamp],
  334. @"bar.baz" : [FIRFieldValue fieldValueForServerTimestamp]
  335. });
  336. GCFSWrite *proto = [GCFSWrite message];
  337. proto.transform = [GCFSDocumentTransform message];
  338. proto.transform.document = [self.serializer encodedDocumentKey:mutation.key];
  339. proto.transform.fieldTransformsArray =
  340. [self.serializer encodedFieldTransforms:mutation.fieldTransforms];
  341. proto.currentDocument.exists = YES;
  342. [self assertRoundTripForMutation:mutation proto:proto];
  343. }
  344. - (void)testEncodesArrayTransformMutations {
  345. FSTTransformMutation *mutation = FSTTestTransformMutation(@"docs/1", @{
  346. @"a" : [FIRFieldValue fieldValueForArrayUnion:@[ @"a", @2 ]],
  347. @"bar.baz" : [FIRFieldValue fieldValueForArrayRemove:@[ @{@"x" : @1} ]]
  348. });
  349. GCFSWrite *proto = [GCFSWrite message];
  350. proto.transform = [GCFSDocumentTransform message];
  351. proto.transform.document = [self.serializer encodedDocumentKey:mutation.key];
  352. GCFSDocumentTransform_FieldTransform *arrayUnion = [GCFSDocumentTransform_FieldTransform message];
  353. arrayUnion.fieldPath = @"a";
  354. arrayUnion.appendMissingElements = [GCFSArrayValue message];
  355. NSMutableArray *unionElements = arrayUnion.appendMissingElements.valuesArray;
  356. [unionElements addObject:[self.serializer encodedFieldValue:FSTTestFieldValue(@"a")]];
  357. [unionElements addObject:[self.serializer encodedFieldValue:FSTTestFieldValue(@2)]];
  358. [proto.transform.fieldTransformsArray addObject:arrayUnion];
  359. GCFSDocumentTransform_FieldTransform *arrayRemove =
  360. [GCFSDocumentTransform_FieldTransform message];
  361. arrayRemove.fieldPath = @"bar.baz";
  362. arrayRemove.removeAllFromArray_p = [GCFSArrayValue message];
  363. NSMutableArray *removeElements = arrayRemove.removeAllFromArray_p.valuesArray;
  364. [removeElements addObject:[self.serializer encodedFieldValue:FSTTestFieldValue(@{@"x" : @1})]];
  365. [proto.transform.fieldTransformsArray addObject:arrayRemove];
  366. proto.currentDocument.exists = YES;
  367. [self assertRoundTripForMutation:mutation proto:proto];
  368. }
  369. - (void)testEncodesSetMutationWithPrecondition {
  370. FSTSetMutation *mutation =
  371. [[FSTSetMutation alloc] initWithKey:FSTTestDocKey(@"foo/bar")
  372. value:FSTTestObjectValue(@{@"a" : @"b", @"num" : @1})
  373. precondition:Precondition::UpdateTime(testutil::Version(4))];
  374. GCFSWrite *proto = [GCFSWrite message];
  375. proto.update = [self.serializer encodedDocumentWithFields:mutation.value key:mutation.key];
  376. proto.currentDocument.updateTime = [self.serializer encodedTimestamp:Timestamp{0, 4000}];
  377. [self assertRoundTripForMutation:mutation proto:proto];
  378. }
  379. - (void)assertRoundTripForMutation:(FSTMutation *)mutation proto:(GCFSWrite *)proto {
  380. GCFSWrite *actualProto = [self.serializer encodedMutation:mutation];
  381. XCTAssertEqualObjects(actualProto, proto);
  382. FSTMutation *actualMutation = [self.serializer decodedMutation:proto];
  383. XCTAssertEqualObjects(actualMutation, mutation);
  384. }
  385. - (void)testDecodesMutationResult {
  386. SnapshotVersion commitVersion = testutil::Version(3000);
  387. SnapshotVersion updateVersion = testutil::Version(4000);
  388. GCFSWriteResult *proto = [GCFSWriteResult message];
  389. proto.updateTime = [self.serializer encodedTimestamp:updateVersion.timestamp()];
  390. [proto.transformResultsArray addObject:[self.serializer encodedString:"result"]];
  391. FSTMutationResult *result = [self.serializer decodedMutationResult:proto
  392. commitVersion:commitVersion];
  393. XCTAssertEqual(result.version, updateVersion);
  394. XCTAssertTrue(result.transformResults.has_value());
  395. XCTAssertEqual(*result.transformResults, Array("result").array_value());
  396. }
  397. - (void)testDecodesDeleteMutationResult {
  398. GCFSWriteResult *proto = [GCFSWriteResult message];
  399. SnapshotVersion commitVersion = testutil::Version(4000);
  400. FSTMutationResult *result = [self.serializer decodedMutationResult:proto
  401. commitVersion:commitVersion];
  402. XCTAssertEqual(result.version, commitVersion);
  403. XCTAssertFalse(result.transformResults.has_value());
  404. }
  405. - (void)testRoundTripSpecialFieldNames {
  406. FSTMutation *set = FSTTestSetMutation(@"collection/key", @{
  407. @"field" : [NSString stringWithFormat:@"field %d", 1],
  408. @"field.dot" : @2,
  409. @"field\\slash" : @3
  410. });
  411. GCFSWrite *encoded = [self.serializer encodedMutation:set];
  412. FSTMutation *decoded = [self.serializer decodedMutation:encoded];
  413. XCTAssertEqualObjects(set, decoded);
  414. }
  415. - (void)testEncodesListenRequestLabels {
  416. core::Query query = Query("collection/key");
  417. FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
  418. targetID:2
  419. listenSequenceNumber:3
  420. purpose:FSTQueryPurposeListen];
  421. NSDictionary<NSString *, NSString *> *result =
  422. [self.serializer encodedListenRequestLabelsForQueryData:queryData];
  423. XCTAssertNil(result);
  424. queryData = [[FSTQueryData alloc] initWithQuery:query
  425. targetID:2
  426. listenSequenceNumber:3
  427. purpose:FSTQueryPurposeLimboResolution];
  428. result = [self.serializer encodedListenRequestLabelsForQueryData:queryData];
  429. XCTAssertEqualObjects(result, @{@"goog-listen-tags" : @"limbo-document"});
  430. queryData = [[FSTQueryData alloc] initWithQuery:query
  431. targetID:2
  432. listenSequenceNumber:3
  433. purpose:FSTQueryPurposeExistenceFilterMismatch];
  434. result = [self.serializer encodedListenRequestLabelsForQueryData:queryData];
  435. XCTAssertEqualObjects(result, @{@"goog-listen-tags" : @"existence-filter-mismatch"});
  436. }
  437. - (void)testEncodesUnaryFilter {
  438. auto input = Filter("item", "==", nullptr);
  439. GCFSStructuredQuery_Filter *actual = [self.serializer encodedUnaryOrFieldFilter:*input];
  440. GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message];
  441. GCFSStructuredQuery_UnaryFilter *prop = expected.unaryFilter;
  442. prop.field.fieldPath = @"item";
  443. prop.op = GCFSStructuredQuery_UnaryFilter_Operator_IsNull;
  444. XCTAssertEqualObjects(actual, expected);
  445. auto roundTripped = [self.serializer decodedUnaryFilter:prop];
  446. XCTAssertEqual(*input, *roundTripped);
  447. }
  448. - (void)testEncodesFieldFilter {
  449. auto input = Filter("item.part.top", "==", "food");
  450. GCFSStructuredQuery_Filter *actual = [self.serializer encodedUnaryOrFieldFilter:*input];
  451. GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message];
  452. GCFSStructuredQuery_FieldFilter *prop = expected.fieldFilter;
  453. prop.field.fieldPath = @"item.part.top";
  454. prop.op = GCFSStructuredQuery_FieldFilter_Operator_Equal;
  455. prop.value.stringValue = @"food";
  456. XCTAssertEqualObjects(actual, expected);
  457. auto roundTripped = [self.serializer decodedFieldFilter:prop];
  458. XCTAssertEqual(*input, *roundTripped);
  459. }
  460. - (void)testEncodesArrayContainsFilter {
  461. auto input = Filter("item.tags", "array_contains", "food");
  462. GCFSStructuredQuery_Filter *actual = [self.serializer encodedUnaryOrFieldFilter:*input];
  463. GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message];
  464. GCFSStructuredQuery_FieldFilter *prop = expected.fieldFilter;
  465. prop.field.fieldPath = @"item.tags";
  466. prop.op = GCFSStructuredQuery_FieldFilter_Operator_ArrayContains;
  467. prop.value.stringValue = @"food";
  468. XCTAssertEqualObjects(actual, expected);
  469. auto roundTripped = [self.serializer decodedFieldFilter:prop];
  470. XCTAssertEqual(*input, *roundTripped);
  471. }
  472. - (void)testEncodesArrayContainsAnyFilter {
  473. auto input = Filter("item.tags", "array-contains-any", Array("food"));
  474. GCFSStructuredQuery_Filter *actual = [self.serializer encodedUnaryOrFieldFilter:*input];
  475. GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message];
  476. GCFSStructuredQuery_FieldFilter *prop = expected.fieldFilter;
  477. prop.field.fieldPath = @"item.tags";
  478. prop.op = GCFSStructuredQuery_FieldFilter_Operator_ArrayContainsAny;
  479. [prop.value.arrayValue.valuesArray addObject:[self.serializer encodedString:"food"]];
  480. XCTAssertEqualObjects(actual, expected);
  481. auto roundTripped = [self.serializer decodedFieldFilter:prop];
  482. XCTAssertEqual(*input, *roundTripped);
  483. }
  484. - (void)testEncodesInFilter {
  485. auto input = Filter("item.tags", "in", Array("food"));
  486. GCFSStructuredQuery_Filter *actual = [self.serializer encodedUnaryOrFieldFilter:*input];
  487. GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message];
  488. GCFSStructuredQuery_FieldFilter *prop = expected.fieldFilter;
  489. prop.field.fieldPath = @"item.tags";
  490. prop.op = GCFSStructuredQuery_FieldFilter_Operator_In;
  491. [prop.value.arrayValue.valuesArray addObject:[self.serializer encodedString:"food"]];
  492. XCTAssertEqualObjects(actual, expected);
  493. auto roundTripped = [self.serializer decodedFieldFilter:prop];
  494. XCTAssertEqual(*input, *roundTripped);
  495. }
  496. - (void)testEncodesKeyFieldFilter {
  497. auto input = Filter("__name__", "==", Ref("p/d", "coll/doc"));
  498. GCFSStructuredQuery_Filter *actual = [self.serializer encodedUnaryOrFieldFilter:*input];
  499. GCFSStructuredQuery_Filter *expected = [GCFSStructuredQuery_Filter message];
  500. GCFSStructuredQuery_FieldFilter *prop = expected.fieldFilter;
  501. prop.field.fieldPath = @"__name__";
  502. prop.op = GCFSStructuredQuery_FieldFilter_Operator_Equal;
  503. prop.value.referenceValue = @"projects/p/databases/d/documents/coll/doc";
  504. XCTAssertEqualObjects(actual, expected);
  505. auto roundTripped = [self.serializer decodedFieldFilter:prop];
  506. XCTAssertEqual(*input, *roundTripped);
  507. }
  508. #pragma mark - encodedQuery
  509. - (void)testEncodesFirstLevelKeyQueries {
  510. core::Query query = Query("docs/1");
  511. FSTQueryData *model = [self queryDataForQuery:std::move(query)];
  512. GCFSTarget *expected = [GCFSTarget message];
  513. [expected.documents.documentsArray addObject:@"projects/p/databases/d/documents/docs/1"];
  514. expected.targetId = 1;
  515. [self assertRoundTripForQueryData:model proto:expected];
  516. }
  517. - (void)testEncodesFirstLevelAncestorQueries {
  518. core::Query q = Query("messages");
  519. FSTQueryData *model = [self queryDataForQuery:std::move(q)];
  520. GCFSTarget *expected = [GCFSTarget message];
  521. expected.query.parent = @"projects/p/databases/d/documents";
  522. GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
  523. from.collectionId = @"messages";
  524. [expected.query.structuredQuery.fromArray addObject:from];
  525. [expected.query.structuredQuery.orderByArray
  526. addObject:[GCFSStructuredQuery_Order messageWithProperty:kDocumentKeyPath ascending:YES]];
  527. expected.targetId = 1;
  528. [self assertRoundTripForQueryData:model proto:expected];
  529. }
  530. - (void)testEncodesNestedAncestorQueries {
  531. core::Query q = Query("rooms/1/messages/10/attachments");
  532. FSTQueryData *model = [self queryDataForQuery:std::move(q)];
  533. GCFSTarget *expected = [GCFSTarget message];
  534. expected.query.parent = @"projects/p/databases/d/documents/rooms/1/messages/10";
  535. GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
  536. from.collectionId = @"attachments";
  537. [expected.query.structuredQuery.fromArray addObject:from];
  538. [expected.query.structuredQuery.orderByArray
  539. addObject:[GCFSStructuredQuery_Order messageWithProperty:kDocumentKeyPath ascending:YES]];
  540. expected.targetId = 1;
  541. [self assertRoundTripForQueryData:model proto:expected];
  542. }
  543. - (void)testEncodesSingleFiltersAtFirstLevelCollections {
  544. core::Query q = Query("docs").AddingFilter(Filter("prop", "<", 42));
  545. FSTQueryData *model = [self queryDataForQuery:std::move(q)];
  546. GCFSTarget *expected = [GCFSTarget message];
  547. expected.query.parent = @"projects/p/databases/d/documents";
  548. GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
  549. from.collectionId = @"docs";
  550. [expected.query.structuredQuery.fromArray addObject:from];
  551. [expected.query.structuredQuery.orderByArray
  552. addObject:[GCFSStructuredQuery_Order messageWithProperty:@"prop" ascending:YES]];
  553. [expected.query.structuredQuery.orderByArray
  554. addObject:[GCFSStructuredQuery_Order messageWithProperty:kDocumentKeyPath ascending:YES]];
  555. GCFSStructuredQuery_FieldFilter *filter = expected.query.structuredQuery.where.fieldFilter;
  556. filter.field.fieldPath = @"prop";
  557. filter.op = GCFSStructuredQuery_FieldFilter_Operator_LessThan;
  558. filter.value.integerValue = 42;
  559. expected.targetId = 1;
  560. [self assertRoundTripForQueryData:model proto:expected];
  561. }
  562. - (void)testEncodesMultipleFiltersOnDeeperCollections {
  563. core::Query q = Query("rooms/1/messages/10/attachments")
  564. .AddingFilter(Filter("prop", ">=", 42))
  565. .AddingFilter(Filter("author", "==", "dimond"))
  566. .AddingFilter(Filter("tags", "array_contains", "pending"));
  567. FSTQueryData *model = [self queryDataForQuery:std::move(q)];
  568. GCFSTarget *expected = [GCFSTarget message];
  569. expected.query.parent = @"projects/p/databases/d/documents/rooms/1/messages/10";
  570. GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
  571. from.collectionId = @"attachments";
  572. [expected.query.structuredQuery.fromArray addObject:from];
  573. GCFSStructuredQuery_Filter *filter1 = [GCFSStructuredQuery_Filter message];
  574. GCFSStructuredQuery_FieldFilter *field1 = filter1.fieldFilter;
  575. field1.field.fieldPath = @"prop";
  576. field1.op = GCFSStructuredQuery_FieldFilter_Operator_GreaterThanOrEqual;
  577. field1.value.integerValue = 42;
  578. GCFSStructuredQuery_Filter *filter2 = [GCFSStructuredQuery_Filter message];
  579. GCFSStructuredQuery_FieldFilter *field2 = filter2.fieldFilter;
  580. field2.field.fieldPath = @"author";
  581. field2.op = GCFSStructuredQuery_FieldFilter_Operator_Equal;
  582. field2.value.stringValue = @"dimond";
  583. GCFSStructuredQuery_Filter *filter3 = [GCFSStructuredQuery_Filter message];
  584. GCFSStructuredQuery_FieldFilter *field3 = filter3.fieldFilter;
  585. field3.field.fieldPath = @"tags";
  586. field3.op = GCFSStructuredQuery_FieldFilter_Operator_ArrayContains;
  587. field3.value.stringValue = @"pending";
  588. GCFSStructuredQuery_CompositeFilter *composite =
  589. expected.query.structuredQuery.where.compositeFilter;
  590. composite.op = GCFSStructuredQuery_CompositeFilter_Operator_And;
  591. [composite.filtersArray addObject:filter1];
  592. [composite.filtersArray addObject:filter2];
  593. [composite.filtersArray addObject:filter3];
  594. [expected.query.structuredQuery.orderByArray
  595. addObject:[GCFSStructuredQuery_Order messageWithProperty:@"prop" ascending:YES]];
  596. [expected.query.structuredQuery.orderByArray
  597. addObject:[GCFSStructuredQuery_Order messageWithProperty:kDocumentKeyPath ascending:YES]];
  598. expected.targetId = 1;
  599. [self assertRoundTripForQueryData:model proto:expected];
  600. }
  601. - (void)testEncodesNullFilter {
  602. [self unaryFilterTestWithValue:Value(nullptr)
  603. expectedUnaryOperator:GCFSStructuredQuery_UnaryFilter_Operator_IsNull];
  604. }
  605. - (void)testEncodesNanFilter {
  606. [self unaryFilterTestWithValue:Value(NAN)
  607. expectedUnaryOperator:GCFSStructuredQuery_UnaryFilter_Operator_IsNan];
  608. }
  609. - (void)unaryFilterTestWithValue:(FieldValue)value
  610. expectedUnaryOperator:(GCFSStructuredQuery_UnaryFilter_Operator)op {
  611. core::Query q = Query("docs").AddingFilter(Filter("prop", "==", value));
  612. FSTQueryData *model = [self queryDataForQuery:std::move(q)];
  613. GCFSTarget *expected = [GCFSTarget message];
  614. expected.query.parent = @"projects/p/databases/d/documents";
  615. GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
  616. from.collectionId = @"docs";
  617. [expected.query.structuredQuery.fromArray addObject:from];
  618. [expected.query.structuredQuery.orderByArray
  619. addObject:[GCFSStructuredQuery_Order messageWithProperty:kDocumentKeyPath ascending:YES]];
  620. GCFSStructuredQuery_UnaryFilter *filter = expected.query.structuredQuery.where.unaryFilter;
  621. filter.field.fieldPath = @"prop";
  622. filter.op = op;
  623. expected.targetId = 1;
  624. [self assertRoundTripForQueryData:model proto:expected];
  625. }
  626. - (void)testEncodesSortOrders {
  627. core::Query query = Query("docs").AddingOrderBy(OrderBy("prop", "asc"));
  628. FSTQueryData *model = [self queryDataForQuery:std::move(query)];
  629. GCFSTarget *expected = [GCFSTarget message];
  630. expected.query.parent = @"projects/p/databases/d/documents";
  631. GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
  632. from.collectionId = @"docs";
  633. [expected.query.structuredQuery.fromArray addObject:from];
  634. [expected.query.structuredQuery.orderByArray
  635. addObject:[GCFSStructuredQuery_Order messageWithProperty:@"prop" ascending:YES]];
  636. [expected.query.structuredQuery.orderByArray
  637. addObject:[GCFSStructuredQuery_Order messageWithProperty:kDocumentKeyPath ascending:YES]];
  638. expected.targetId = 1;
  639. [self assertRoundTripForQueryData:model proto:expected];
  640. }
  641. - (void)testEncodesSortOrdersDescending {
  642. core::Query query =
  643. Query("rooms/1/messages/10/attachments").AddingOrderBy(OrderBy("prop", "desc"));
  644. FSTQueryData *model = [self queryDataForQuery:std::move(query)];
  645. GCFSTarget *expected = [GCFSTarget message];
  646. expected.query.parent = @"projects/p/databases/d/documents/rooms/1/messages/10";
  647. GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
  648. from.collectionId = @"attachments";
  649. [expected.query.structuredQuery.fromArray addObject:from];
  650. [expected.query.structuredQuery.orderByArray
  651. addObject:[GCFSStructuredQuery_Order messageWithProperty:@"prop" ascending:NO]];
  652. [expected.query.structuredQuery.orderByArray
  653. addObject:[GCFSStructuredQuery_Order messageWithProperty:kDocumentKeyPath ascending:NO]];
  654. expected.targetId = 1;
  655. [self assertRoundTripForQueryData:model proto:expected];
  656. }
  657. - (void)testEncodesLimits {
  658. core::Query query = Query("docs").WithLimit(26);
  659. FSTQueryData *model = [self queryDataForQuery:std::move(query)];
  660. GCFSTarget *expected = [GCFSTarget message];
  661. expected.query.parent = @"projects/p/databases/d/documents";
  662. GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
  663. from.collectionId = @"docs";
  664. [expected.query.structuredQuery.fromArray addObject:from];
  665. [expected.query.structuredQuery.orderByArray
  666. addObject:[GCFSStructuredQuery_Order messageWithProperty:kDocumentKeyPath ascending:YES]];
  667. expected.query.structuredQuery.limit.value = 26;
  668. expected.targetId = 1;
  669. [self assertRoundTripForQueryData:model proto:expected];
  670. }
  671. - (void)testEncodesResumeTokens {
  672. core::Query q = Query("docs");
  673. FSTQueryData *model = [[FSTQueryData alloc] initWithQuery:std::move(q)
  674. targetID:1
  675. listenSequenceNumber:0
  676. purpose:FSTQueryPurposeListen
  677. snapshotVersion:SnapshotVersion::None()
  678. resumeToken:FSTTestData(1, 2, 3, -1)];
  679. GCFSTarget *expected = [GCFSTarget message];
  680. expected.query.parent = @"projects/p/databases/d/documents";
  681. GCFSStructuredQuery_CollectionSelector *from = [GCFSStructuredQuery_CollectionSelector message];
  682. from.collectionId = @"docs";
  683. [expected.query.structuredQuery.fromArray addObject:from];
  684. [expected.query.structuredQuery.orderByArray
  685. addObject:[GCFSStructuredQuery_Order messageWithProperty:kDocumentKeyPath ascending:YES]];
  686. expected.targetId = 1;
  687. expected.resumeToken = FSTTestData(1, 2, 3, -1);
  688. [self assertRoundTripForQueryData:model proto:expected];
  689. }
  690. - (FSTQueryData *)queryDataForQuery:(core::Query)query {
  691. return [[FSTQueryData alloc] initWithQuery:std::move(query)
  692. targetID:1
  693. listenSequenceNumber:0
  694. purpose:FSTQueryPurposeListen
  695. snapshotVersion:SnapshotVersion::None()
  696. resumeToken:[NSData data]];
  697. }
  698. - (void)assertRoundTripForQueryData:(FSTQueryData *)queryData proto:(GCFSTarget *)proto {
  699. // Verify that the encoded FSTQueryData matches the target.
  700. GCFSTarget *actualProto = [self.serializer encodedTarget:queryData];
  701. XCTAssertEqualObjects(actualProto, proto);
  702. // We don't have deserialization logic for full targets since they're not used for RPC
  703. // interaction, but the query deserialization only *is* used for the local store.
  704. core::Query actualModel;
  705. if (proto.targetTypeOneOfCase == GCFSTarget_TargetType_OneOfCase_Query) {
  706. actualModel = [self.serializer decodedQueryFromQueryTarget:proto.query];
  707. } else {
  708. actualModel = [self.serializer decodedQueryFromDocumentsTarget:proto.documents];
  709. }
  710. XCTAssertEqual(actualModel, queryData.query);
  711. }
  712. - (void)testConvertsTargetChangeWithAdded {
  713. WatchTargetChange expected{WatchTargetChangeState::Added, {1, 4}};
  714. GCFSListenResponse *listenResponse = [GCFSListenResponse message];
  715. listenResponse.targetChange.targetChangeType = GCFSTargetChange_TargetChangeType_Add;
  716. [listenResponse.targetChange.targetIdsArray addValue:1];
  717. [listenResponse.targetChange.targetIdsArray addValue:4];
  718. std::unique_ptr<WatchChange> actual = [self.serializer decodedWatchChange:listenResponse];
  719. XCTAssertTrue(IsWatchChangeEqual(*actual, expected));
  720. }
  721. - (void)testConvertsTargetChangeWithRemoved {
  722. WatchTargetChange expected{WatchTargetChangeState::Removed,
  723. {1, 4},
  724. FSTTestData(0, 1, 2, -1),
  725. Status{Error::PermissionDenied, "Error message"}};
  726. GCFSListenResponse *listenResponse = [GCFSListenResponse message];
  727. listenResponse.targetChange.targetChangeType = GCFSTargetChange_TargetChangeType_Remove;
  728. listenResponse.targetChange.cause.code = FIRFirestoreErrorCodePermissionDenied;
  729. listenResponse.targetChange.cause.message = @"Error message";
  730. listenResponse.targetChange.resumeToken = FSTTestData(0, 1, 2, -1);
  731. [listenResponse.targetChange.targetIdsArray addValue:1];
  732. [listenResponse.targetChange.targetIdsArray addValue:4];
  733. std::unique_ptr<WatchChange> actual = [self.serializer decodedWatchChange:listenResponse];
  734. XCTAssertTrue(IsWatchChangeEqual(*actual, expected));
  735. }
  736. - (void)testConvertsTargetChangeWithNoChange {
  737. WatchTargetChange expected{WatchTargetChangeState::NoChange, {1, 4}};
  738. GCFSListenResponse *listenResponse = [GCFSListenResponse message];
  739. listenResponse.targetChange.targetChangeType = GCFSTargetChange_TargetChangeType_NoChange;
  740. [listenResponse.targetChange.targetIdsArray addValue:1];
  741. [listenResponse.targetChange.targetIdsArray addValue:4];
  742. std::unique_ptr<WatchChange> actual = [self.serializer decodedWatchChange:listenResponse];
  743. XCTAssertTrue(IsWatchChangeEqual(*actual, expected));
  744. }
  745. - (void)testConvertsDocumentChangeWithTargetIds {
  746. DocumentWatchChange expected {
  747. {1, 2}, {}, FSTTestDocKey(@"coll/1"),
  748. FSTTestDoc("coll/1", 5, @{@"foo" : @"bar"}, DocumentState::kSynced)
  749. };
  750. GCFSListenResponse *listenResponse = [GCFSListenResponse message];
  751. listenResponse.documentChange.document.name = @"projects/p/databases/d/documents/coll/1";
  752. listenResponse.documentChange.document.updateTime.nanos = 5000;
  753. GCFSValue *fooValue = [GCFSValue message];
  754. fooValue.stringValue = @"bar";
  755. [listenResponse.documentChange.document.fields setObject:fooValue forKey:@"foo"];
  756. [listenResponse.documentChange.targetIdsArray addValue:1];
  757. [listenResponse.documentChange.targetIdsArray addValue:2];
  758. std::unique_ptr<WatchChange> actual = [self.serializer decodedWatchChange:listenResponse];
  759. XCTAssertTrue(IsWatchChangeEqual(*actual, expected));
  760. }
  761. - (void)testConvertsDocumentChangeWithRemovedTargetIds {
  762. DocumentWatchChange expected {
  763. {2}, {1}, FSTTestDocKey(@"coll/1"),
  764. FSTTestDoc("coll/1", 5, @{@"foo" : @"bar"}, DocumentState::kSynced)
  765. };
  766. GCFSListenResponse *listenResponse = [GCFSListenResponse message];
  767. listenResponse.documentChange.document.name = @"projects/p/databases/d/documents/coll/1";
  768. listenResponse.documentChange.document.updateTime.nanos = 5000;
  769. GCFSValue *fooValue = [GCFSValue message];
  770. fooValue.stringValue = @"bar";
  771. [listenResponse.documentChange.document.fields setObject:fooValue forKey:@"foo"];
  772. [listenResponse.documentChange.removedTargetIdsArray addValue:1];
  773. [listenResponse.documentChange.targetIdsArray addValue:2];
  774. std::unique_ptr<WatchChange> actual = [self.serializer decodedWatchChange:listenResponse];
  775. XCTAssertTrue(IsWatchChangeEqual(*actual, expected));
  776. }
  777. - (void)testConvertsDocumentChangeWithDeletions {
  778. DocumentWatchChange expected{
  779. {}, {1, 2}, FSTTestDocKey(@"coll/1"), FSTTestDeletedDoc("coll/1", 5, NO)};
  780. GCFSListenResponse *listenResponse = [GCFSListenResponse message];
  781. listenResponse.documentDelete.document = @"projects/p/databases/d/documents/coll/1";
  782. listenResponse.documentDelete.readTime.nanos = 5000;
  783. [listenResponse.documentDelete.removedTargetIdsArray addValue:1];
  784. [listenResponse.documentDelete.removedTargetIdsArray addValue:2];
  785. std::unique_ptr<WatchChange> actual = [self.serializer decodedWatchChange:listenResponse];
  786. XCTAssertTrue(IsWatchChangeEqual(*actual, expected));
  787. }
  788. - (void)testConvertsDocumentChangeWithRemoves {
  789. DocumentWatchChange expected{{}, {1, 2}, FSTTestDocKey(@"coll/1"), nil};
  790. GCFSListenResponse *listenResponse = [GCFSListenResponse message];
  791. listenResponse.documentRemove.document = @"projects/p/databases/d/documents/coll/1";
  792. [listenResponse.documentRemove.removedTargetIdsArray addValue:1];
  793. [listenResponse.documentRemove.removedTargetIdsArray addValue:2];
  794. std::unique_ptr<WatchChange> actual = [self.serializer decodedWatchChange:listenResponse];
  795. XCTAssertTrue(IsWatchChangeEqual(*actual, expected));
  796. }
  797. @end
  798. NS_ASSUME_NONNULL_END