FSTSerializerBetaTests.mm 37 KB

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