FSTSerializerBetaTests.m 32 KB

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