FSTSerializerBetaTests.mm 33 KB

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