FSTSerializerBetaTests.mm 39 KB

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