From 60d43d7b0980719beac21811da712f1cb4881308 Mon Sep 17 00:00:00 2001 From: Michael Lehenbauer Date: Fri, 13 Apr 2018 14:22:41 -0700 Subject: Array Transforms public API and parsing This includes the new FIRFieldValue sentinels, the FSTUserDataConverter code to parse them into internal ArrayTransform operations for use in an FSTTransformMutation, and some sanity testing in FSTMutationTests. I still need to implement FSTTransformMutation support for local application and serialization (and then integration tests). --- Firestore/Example/SwiftBuildTest/main.swift | 4 ++ Firestore/Example/Tests/Model/FSTMutationTests.mm | 62 ++++++++++++++++++++-- .../Example/Tests/Remote/FSTSerializerBetaTests.mm | 6 ++- Firestore/Example/Tests/Util/FSTHelpers.h | 8 ++- Firestore/Example/Tests/Util/FSTHelpers.mm | 24 +++++---- 5 files changed, 87 insertions(+), 17 deletions(-) (limited to 'Firestore/Example') diff --git a/Firestore/Example/SwiftBuildTest/main.swift b/Firestore/Example/SwiftBuildTest/main.swift index 3087085..691adf6 100644 --- a/Firestore/Example/SwiftBuildTest/main.swift +++ b/Firestore/Example/SwiftBuildTest/main.swift @@ -100,6 +100,10 @@ func writeDocument(at docRef: DocumentReference) { let updateData = [ "bar.baz": 42, FieldPath(["foobar"]): 42, + "server_timestamp": FieldValue.serverTimestamp(), + "array_union": FieldValue.arrayUnion(["a", "b"]), + "array_remove": FieldValue.arrayRemove(["a", "b"]), + "field_delete": FieldValue.delete(), ] as [AnyHashable: Any] docRef.setData(setData) diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.mm b/Firestore/Example/Tests/Model/FSTMutationTests.mm index 60cb7b8..56bf1c2 100644 --- a/Firestore/Example/Tests/Model/FSTMutationTests.mm +++ b/Firestore/Example/Tests/Model/FSTMutationTests.mm @@ -16,9 +16,12 @@ #import "Firestore/Source/Model/FSTMutation.h" +#import #import #import +#include + #import "Firestore/Source/Model/FSTDocument.h" #import "Firestore/Source/Model/FSTFieldValue.h" @@ -26,13 +29,19 @@ #include "Firestore/core/src/firebase/firestore/model/document_key.h" #include "Firestore/core/src/firebase/firestore/model/field_mask.h" +#include "Firestore/core/src/firebase/firestore/model/field_transform.h" #include "Firestore/core/src/firebase/firestore/model/precondition.h" +#include "Firestore/core/src/firebase/firestore/model/transform_operations.h" #include "Firestore/core/test/firebase/firestore/testutil/testutil.h" namespace testutil = firebase::firestore::testutil; +using firebase::firestore::model::ArrayTransform; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldMask; +using firebase::firestore::model::FieldPath; +using firebase::firestore::model::FieldTransform; using firebase::firestore::model::Precondition; +using firebase::firestore::model::TransformOperation; @interface FSTMutationTests : XCTestCase @end @@ -104,11 +113,12 @@ using firebase::firestore::model::Precondition; XCTAssertEqualObjects(patchedDoc, baseDoc); } -- (void)testAppliesLocalTransformsToDocuments { +- (void)testAppliesLocalServerTimestampTransformToDocuments { NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" }; FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); - FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @[ @"foo.bar" ]); + FSTMutation *transform = FSTTestTransformMutation( + @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]}); FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp]; @@ -130,11 +140,57 @@ using firebase::firestore::model::Precondition; XCTAssertEqualObjects(transformedDoc, expectedDoc); } +// NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have +// unit tests for it currently. We could consider removing this test once we have integration tests. +- (void)testCreateArrayUnionTransform { + FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{ + @"foo" : [FIRFieldValue fieldValueForArrayUnion:@[ @"tag" ]], + @"bar.baz" : [FIRFieldValue fieldValueForArrayUnion:@[ @YES, @[ @1, @2 ], @{@"a" : @"b"} ]] + }); + XCTAssertEqual(transform.fieldTransforms.size(), 2); + + const FieldTransform &first = transform.fieldTransforms[0]; + XCTAssertEqual(first.path(), FieldPath({"foo"})); + { + std::vector expectedElements{FSTTestFieldValue(@"tag")}; + ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements); + XCTAssertEqual(static_cast(first.transformation()), expected); + } + + const FieldTransform &second = transform.fieldTransforms[1]; + XCTAssertEqual(second.path(), FieldPath({"bar", "baz"})); + { + std::vector expectedElements { + FSTTestFieldValue(@YES), FSTTestFieldValue(@[ @1, @2 ]), FSTTestFieldValue(@{@"a" : @"b"}) + }; + ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements); + XCTAssertEqual(static_cast(second.transformation()), expected); + } +} + +// NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have +// unit tests for it currently. We could consider removing this test once we have integration tests. +- (void)testCreateArrayRemoveTransform { + FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{ + @"foo" : [FIRFieldValue fieldValueForArrayRemove:@[ @"tag" ]], + }); + XCTAssertEqual(transform.fieldTransforms.size(), 1); + + const FieldTransform &first = transform.fieldTransforms[0]; + XCTAssertEqual(first.path(), FieldPath({"foo"})); + { + std::vector expectedElements{FSTTestFieldValue(@"tag")}; + const ArrayTransform expected(TransformOperation::Type::ArrayRemove, expectedElements); + XCTAssertEqual(static_cast(first.transformation()), expected); + } +} + - (void)testAppliesServerAckedTransformsToDocuments { NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" }; FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO); - FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @[ @"foo.bar" ]); + FSTMutation *transform = FSTTestTransformMutation( + @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]}); FSTMutationResult *mutationResult = [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(1) diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm index 454b108..a648cd8 100644 --- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm +++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm @@ -17,6 +17,7 @@ #import "Firestore/Source/Remote/FSTSerializerBeta.h" #import +#import #import #import #import @@ -367,7 +368,10 @@ NS_ASSUME_NONNULL_BEGIN } - (void)testEncodesTransformMutation { - FSTTransformMutation *mutation = FSTTestTransformMutation(@"docs/1", @[ @"a", @"bar.baz" ]); + FSTTransformMutation *mutation = FSTTestTransformMutation(@"docs/1", @{ + @"a" : [FIRFieldValue fieldValueForServerTimestamp], + @"bar.baz" : [FIRFieldValue fieldValueForServerTimestamp] + }); GCFSWrite *proto = [GCFSWrite message]; proto.transform = [GCFSDocumentTransform message]; proto.transform.document = [self.serializer encodedDocumentKey:mutation.key]; diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h index d05c1f1..9b5f96a 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.h +++ b/Firestore/Example/Tests/Util/FSTHelpers.h @@ -245,8 +245,12 @@ FSTPatchMutation *FSTTestPatchMutation( NSDictionary *values, const std::vector &updateMask); -FSTTransformMutation *FSTTestTransformMutation(NSString *path, - NSArray *serverTimestampFields); +/** + * Creates a FSTTransformMutation by parsing any FIRFieldValue sentinels in the provided data. The + * data is expected to use dotted-notation for nested fields (i.e. + * @{ @"foo.bar": [FIRFieldValue ...] } and must not contain any non-sentinel data. + */ +FSTTransformMutation *FSTTestTransformMutation(NSString *path, NSDictionary *data); /** Creates a delete mutation for the document key at the given path. */ FSTDeleteMutation *FSTTestDeleteMutation(NSString *path); diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm index 78b41a4..e1ed18c 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.mm +++ b/Firestore/Example/Tests/Util/FSTHelpers.mm @@ -122,7 +122,7 @@ NSDateComponents *FSTTestDateComponents( return comps; } -FSTFieldValue *FSTTestFieldValue(id _Nullable value) { +FSTUserDataConverter *FSTTestUserDataConverter() { // This owns the DatabaseIds since we do not have FirestoreClient instance to own them. static DatabaseId database_id{"project", DatabaseId::kDefault}; FSTUserDataConverter *converter = @@ -130,6 +130,11 @@ FSTFieldValue *FSTTestFieldValue(id _Nullable value) { preConverter:^id _Nullable(id _Nullable input) { return input; }]; + return converter; +} + +FSTFieldValue *FSTTestFieldValue(id _Nullable value) { + FSTUserDataConverter *converter = FSTTestUserDataConverter(); // HACK: We use parsedQueryValue: since it accepts scalars as well as arrays / objects, and // our tests currently use FSTTestFieldValue() pretty generically so we don't know the intent. return [converter parsedQueryValue:value]; @@ -279,17 +284,14 @@ FSTPatchMutation *FSTTestPatchMutation(const absl::string_view path, precondition:Precondition::Exists(true)]; } -// For now this only creates TransformMutations with server timestamps. -FSTTransformMutation *FSTTestTransformMutation(NSString *path, - NSArray *serverTimestampFields) { +FSTTransformMutation *FSTTestTransformMutation(NSString *path, NSDictionary *data) { FSTDocumentKey *key = [FSTDocumentKey keyWithPath:testutil::Resource(util::MakeStringView(path))]; - std::vector fieldTransforms; - for (NSString *field in serverTimestampFields) { - FieldPath fieldPath = testutil::Field(util::MakeStringView(field)); - auto transformOp = absl::make_unique(ServerTimestampTransform::Get()); - fieldTransforms.emplace_back(std::move(fieldPath), std::move(transformOp)); - } - return [[FSTTransformMutation alloc] initWithKey:key fieldTransforms:std::move(fieldTransforms)]; + FSTUserDataConverter *converter = FSTTestUserDataConverter(); + FSTParsedUpdateData *result = [converter parsedUpdateData:data]; + FSTCAssert(result.data.value.count == 0, + @"FSTTestTransformMutation() only expects transforms; no other data"); + return [[FSTTransformMutation alloc] initWithKey:key + fieldTransforms:std::move(result.fieldTransforms)]; } FSTDeleteMutation *FSTTestDeleteMutation(NSString *path) { -- cgit v1.2.3