FSTSerializerBetaTests.mm 37 KB

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