FSTSerializerBetaTests.mm 33 KB

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