mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
* Migrate FSTNullValue to FSTDelegateValue * Migrate FSTIntegerValue to FSTDelegateValue * Migrate FSTDoubleValue to FSTDelegateValue * Migrate FSTGeoPointValue to FSTDelegateValue
564 lines
26 KiB
Plaintext
564 lines
26 KiB
Plaintext
/*
|
|
* Copyright 2017 Google
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#import "Firestore/Source/Model/FSTMutation.h"
|
|
|
|
#import <FirebaseFirestore/FIRFieldValue.h>
|
|
#import <FirebaseFirestore/FIRTimestamp.h>
|
|
#import <XCTest/XCTest.h>
|
|
|
|
#include <vector>
|
|
|
|
#import "Firestore/Source/API/FIRFieldValue+Internal.h"
|
|
#import "Firestore/Source/Model/FSTDocument.h"
|
|
#import "Firestore/Source/Model/FSTFieldValue.h"
|
|
|
|
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
|
|
|
|
#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/field_value.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::FieldValue;
|
|
using firebase::firestore::model::Precondition;
|
|
using firebase::firestore::model::TransformOperation;
|
|
|
|
@interface FSTMutationTests : XCTestCase
|
|
@end
|
|
|
|
@implementation FSTMutationTests {
|
|
FIRTimestamp *_timestamp;
|
|
}
|
|
|
|
- (void)setUp {
|
|
_timestamp = [FIRTimestamp timestamp];
|
|
}
|
|
|
|
- (void)testAppliesSetsToDocuments {
|
|
NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"bar" : @"bar-value"});
|
|
FSTMaybeDocument *setDoc = [set applyToLocalDocument:baseDoc
|
|
baseDocument:baseDoc
|
|
localWriteTime:_timestamp];
|
|
|
|
NSDictionary *expectedData = @{@"bar" : @"bar-value"};
|
|
XCTAssertEqualObjects(
|
|
setDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations));
|
|
}
|
|
|
|
- (void)testAppliesPatchesToDocuments {
|
|
NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
|
|
FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc
|
|
baseDocument:baseDoc
|
|
localWriteTime:_timestamp];
|
|
|
|
NSDictionary *expectedData = @{@"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value"};
|
|
XCTAssertEqualObjects(
|
|
patchedDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations));
|
|
}
|
|
|
|
- (void)testDeletesValuesFromTheFieldMask {
|
|
NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value", @"baz" : @"baz-value"}};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
DocumentKey key = testutil::Key("collection/key");
|
|
FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:key
|
|
fieldMask:{testutil::Field("foo.bar")}
|
|
value:[FSTObjectValue objectValue]
|
|
precondition:Precondition::None()];
|
|
FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc
|
|
baseDocument:baseDoc
|
|
localWriteTime:_timestamp];
|
|
|
|
NSDictionary *expectedData = @{@"foo" : @{@"baz" : @"baz-value"}};
|
|
XCTAssertEqualObjects(
|
|
patchedDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations));
|
|
}
|
|
|
|
- (void)testPatchesPrimitiveValue {
|
|
NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
|
|
FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc
|
|
baseDocument:baseDoc
|
|
localWriteTime:_timestamp];
|
|
|
|
NSDictionary *expectedData = @{@"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value"};
|
|
XCTAssertEqualObjects(
|
|
patchedDoc, FSTTestDoc("collection/key", 0, expectedData, FSTDocumentStateLocalMutations));
|
|
}
|
|
|
|
- (void)testPatchingDeletedDocumentsDoesNothing {
|
|
FSTMaybeDocument *baseDoc = FSTTestDeletedDoc("collection/key", 0, NO);
|
|
FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"bar"}, {});
|
|
FSTMaybeDocument *patchedDoc = [patch applyToLocalDocument:baseDoc
|
|
baseDocument:baseDoc
|
|
localWriteTime:_timestamp];
|
|
XCTAssertEqualObjects(patchedDoc, baseDoc);
|
|
}
|
|
|
|
- (void)testAppliesLocalServerTimestampTransformToDocuments {
|
|
NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *transform = FSTTestTransformMutation(
|
|
@"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
|
|
FSTMaybeDocument *transformedDoc = [transform applyToLocalDocument:baseDoc
|
|
baseDocument:baseDoc
|
|
localWriteTime:_timestamp];
|
|
|
|
// Server timestamps aren't parsed, so we manually insert it.
|
|
FSTObjectValue *expectedData =
|
|
FSTTestObjectValue(@{@"foo" : @{@"bar" : @"<server-timestamp>"}, @"baz" : @"baz-value"});
|
|
expectedData =
|
|
[expectedData objectBySettingValue:[FSTServerTimestampValue
|
|
serverTimestampValueWithLocalWriteTime:_timestamp
|
|
previousValue:nil]
|
|
forPath:testutil::Field("foo.bar")];
|
|
|
|
FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData
|
|
key:FSTTestDocKey(@"collection/key")
|
|
version:testutil::Version(0)
|
|
state:FSTDocumentStateLocalMutations];
|
|
|
|
XCTAssertEqualObjects(transformedDoc, expectedDoc);
|
|
}
|
|
|
|
- (void)testAppliesIncrementTransformToDocument {
|
|
NSDictionary *baseDoc = @{
|
|
@"longPlusLong" : @1,
|
|
@"longPlusDouble" : @2,
|
|
@"doublePlusLong" : @3.3,
|
|
@"doublePlusDouble" : @4.0,
|
|
@"longPlusNan" : @5,
|
|
@"doublePlusNan" : @6.6,
|
|
@"longPlusInfinity" : @7,
|
|
@"doublePlusInfinity" : @8.8
|
|
};
|
|
NSDictionary *transform = @{
|
|
@"longPlusLong" : [FIRFieldValue fieldValueForIntegerIncrement:1],
|
|
@"longPlusDouble" : [FIRFieldValue fieldValueForDoubleIncrement:2.2],
|
|
@"doublePlusLong" : [FIRFieldValue fieldValueForIntegerIncrement:3],
|
|
@"doublePlusDouble" : [FIRFieldValue fieldValueForDoubleIncrement:4.4],
|
|
@"longPlusNan" : [FIRFieldValue fieldValueForDoubleIncrement:NAN],
|
|
@"doublePlusNan" : [FIRFieldValue fieldValueForDoubleIncrement:NAN],
|
|
@"longPlusInfinity" : [FIRFieldValue fieldValueForDoubleIncrement:INFINITY],
|
|
@"doublePlusInfinity" : [FIRFieldValue fieldValueForDoubleIncrement:INFINITY]
|
|
};
|
|
NSDictionary *expected = @{
|
|
@"longPlusLong" : @2L,
|
|
@"longPlusDouble" : @4.2,
|
|
@"doublePlusLong" : @6.3,
|
|
@"doublePlusDouble" : @8.4,
|
|
@"longPlusNan" : @(NAN),
|
|
@"doublePlusNan" : @(NAN),
|
|
@"longPlusInfinity" : @(INFINITY),
|
|
@"doublePlusInfinity" : @(INFINITY)
|
|
};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesIncrementTransformToUnexpectedType {
|
|
NSDictionary *baseDoc = @{@"string" : @"zero"};
|
|
NSDictionary *transform = @{@"string" : [FIRFieldValue fieldValueForIntegerIncrement:1]};
|
|
NSDictionary *expected = @{@"string" : @1};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesIncrementTransformToMissingField {
|
|
NSDictionary *baseDoc = @{};
|
|
NSDictionary *transform = @{@"missing" : [FIRFieldValue fieldValueForIntegerIncrement:1]};
|
|
NSDictionary *expected = @{@"missing" : @1};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesIncrementTransformsConsecutively {
|
|
NSDictionary *baseDoc = @{@"number" : @1};
|
|
NSDictionary *transform1 = @{@"number" : [FIRFieldValue fieldValueForIntegerIncrement:2]};
|
|
NSDictionary *transform2 = @{@"number" : [FIRFieldValue fieldValueForIntegerIncrement:3]};
|
|
NSDictionary *transform3 = @{@"number" : [FIRFieldValue fieldValueForIntegerIncrement:4]};
|
|
NSDictionary *expected = @{@"number" : @10};
|
|
[self transformBaseDoc:baseDoc
|
|
applyTransforms:@[ transform1, transform2, transform3 ]
|
|
expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesIncrementWithoutOverflow {
|
|
NSDictionary *baseDoc =
|
|
@{@"a" : @(LONG_MAX - 1), @"b" : @(LONG_MAX - 1), @"c" : @(LONG_MAX), @"d" : @(LONG_MAX)};
|
|
NSDictionary *transform = @{
|
|
@"a" : [FIRFieldValue fieldValueForIntegerIncrement:1],
|
|
@"b" : [FIRFieldValue fieldValueForIntegerIncrement:LONG_MAX],
|
|
@"c" : [FIRFieldValue fieldValueForIntegerIncrement:1],
|
|
@"d" : [FIRFieldValue fieldValueForIntegerIncrement:LONG_MAX]
|
|
};
|
|
NSDictionary *expected =
|
|
@{@"a" : @LONG_MAX, @"b" : @LONG_MAX, @"c" : @LONG_MAX, @"d" : @LONG_MAX};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesIncrementWithoutUnderflow {
|
|
NSDictionary *baseDoc =
|
|
@{@"a" : @(LONG_MIN + 1), @"b" : @(LONG_MIN + 1), @"c" : @(LONG_MIN), @"d" : @(LONG_MIN)};
|
|
NSDictionary *transform = @{
|
|
@"a" : [FIRFieldValue fieldValueForIntegerIncrement:-1],
|
|
@"b" : [FIRFieldValue fieldValueForIntegerIncrement:LONG_MIN],
|
|
@"c" : [FIRFieldValue fieldValueForIntegerIncrement:-1],
|
|
@"d" : [FIRFieldValue fieldValueForIntegerIncrement:LONG_MIN]
|
|
};
|
|
NSDictionary *expected =
|
|
@{@"a" : @(LONG_MIN), @"b" : @(LONG_MIN), @"c" : @(LONG_MIN), @"d" : @(LONG_MIN)};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting: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)testCreateArrayUnionTransform {
|
|
FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
|
|
@"foo" : [FIRFieldValue fieldValueForArrayUnion:@[ @"tag" ]],
|
|
@"bar.baz" :
|
|
[FIRFieldValue fieldValueForArrayUnion:@[ @YES, @{@"nested" : @{@"a" : @[ @1, @2 ]}} ]]
|
|
});
|
|
XCTAssertEqual(transform.fieldTransforms.size(), 2);
|
|
|
|
const FieldTransform &first = transform.fieldTransforms[0];
|
|
XCTAssertEqual(first.path(), FieldPath({"foo"}));
|
|
{
|
|
std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
|
|
ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
|
|
XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
|
|
}
|
|
|
|
const FieldTransform &second = transform.fieldTransforms[1];
|
|
XCTAssertEqual(second.path(), FieldPath({"bar", "baz"}));
|
|
{
|
|
std::vector<FSTFieldValue *> expectedElements {
|
|
FSTTestFieldValue(@YES), FSTTestFieldValue(@{@"nested" : @{@"a" : @[ @1, @2 ]}})
|
|
};
|
|
ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
|
|
XCTAssertEqual(static_cast<const ArrayTransform &>(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<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
|
|
const ArrayTransform expected(TransformOperation::Type::ArrayRemove, expectedElements);
|
|
XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
|
|
}
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayUnionTransformToMissingField {
|
|
auto baseDoc = @{};
|
|
auto transform = @{@"missing" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]};
|
|
auto expected = @{@"missing" : @[ @1, @2 ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayUnionTransformToNonArrayField {
|
|
auto baseDoc = @{@"non-array" : @42};
|
|
auto transform = @{@"non-array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]};
|
|
auto expected = @{@"non-array" : @[ @1, @2 ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayUnionTransformWithNonExistingElements {
|
|
auto baseDoc = @{@"array" : @[ @1, @3 ]};
|
|
auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @4 ]]};
|
|
auto expected = @{@"array" : @[ @1, @3, @2, @4 ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayUnionTransformWithExistingElements {
|
|
auto baseDoc = @{@"array" : @[ @1, @3 ]};
|
|
auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @3 ]]};
|
|
auto expected = @{@"array" : @[ @1, @3 ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayUnionTransformWithDuplicateExistingElements {
|
|
// Duplicate entries in your existing array should be preserved.
|
|
auto baseDoc = @{@"array" : @[ @1, @2, @2, @3 ]};
|
|
auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2 ]]};
|
|
auto expected = @{@"array" : @[ @1, @2, @2, @3 ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayUnionTransformWithDuplicateUnionElements {
|
|
// Duplicate entries in your union array should only be added once.
|
|
auto baseDoc = @{@"array" : @[ @1, @3 ]};
|
|
auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @2 ]]};
|
|
auto expected = @{@"array" : @[ @1, @3, @2 ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayUnionTransformWithNonPrimitiveElements {
|
|
// Union nested object values (one existing, one not).
|
|
auto baseDoc = @{@"array" : @[ @1, @{@"a" : @"b"} ]};
|
|
auto transform =
|
|
@{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]]};
|
|
auto expected = @{@"array" : @[ @1, @{@"a" : @"b"}, @{@"c" : @"d"} ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayUnionTransformWithPartiallyOverlappingElements {
|
|
// Union objects that partially overlap an existing object.
|
|
auto baseDoc = @{@"array" : @[ @1, @{@"a" : @"b", @"c" : @"d"} ]};
|
|
auto transform =
|
|
@{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]]};
|
|
auto expected =
|
|
@{@"array" : @[ @1, @{@"a" : @"b", @"c" : @"d"}, @{@"a" : @"b"}, @{@"c" : @"d"} ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayRemoveTransformToMissingField {
|
|
auto baseDoc = @{};
|
|
auto transform = @{@"missing" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @2 ]]};
|
|
auto expected = @{@"missing" : @[]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayRemoveTransformToNonArrayField {
|
|
auto baseDoc = @{@"non-array" : @42};
|
|
auto transform = @{@"non-array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @2 ]]};
|
|
auto expected = @{@"non-array" : @[]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayRemoveTransformWithNonExistingElements {
|
|
auto baseDoc = @{@"array" : @[ @1, @3 ]};
|
|
auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @2, @4 ]]};
|
|
auto expected = @{@"array" : @[ @1, @3 ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayRemoveTransformWithExistingElements {
|
|
auto baseDoc = @{@"array" : @[ @1, @2, @3, @4 ]};
|
|
auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @3 ]]};
|
|
auto expected = @{@"array" : @[ @2, @4 ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
- (void)testAppliesLocalArrayRemoveTransformWithNonPrimitiveElements {
|
|
// Remove nested object values (one existing, one not).
|
|
auto baseDoc = @{@"array" : @[ @1, @{@"a" : @"b"} ]};
|
|
auto transform =
|
|
@{@"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]]};
|
|
auto expected = @{@"array" : @[ @1 ]};
|
|
[self transformBaseDoc:baseDoc applyTransform:transform expecting:expected];
|
|
}
|
|
|
|
// Helper to test a particular transform scenario.
|
|
- (void)transformBaseDoc:(NSDictionary<NSString *, id> *)baseData
|
|
applyTransforms:(NSArray<NSDictionary<NSString *, id> *> *)transforms
|
|
expecting:(NSDictionary<NSString *, id> *)expectedData {
|
|
FSTMaybeDocument *currentDoc = FSTTestDoc("collection/key", 0, baseData, FSTDocumentStateSynced);
|
|
|
|
for (NSDictionary<NSString *, id> *transformData in transforms) {
|
|
FSTMutation *transform = FSTTestTransformMutation(@"collection/key", transformData);
|
|
currentDoc = [transform applyToLocalDocument:currentDoc
|
|
baseDocument:currentDoc
|
|
localWriteTime:_timestamp];
|
|
}
|
|
|
|
FSTDocument *expectedDoc = [FSTDocument documentWithData:FSTTestObjectValue(expectedData)
|
|
key:FSTTestDocKey(@"collection/key")
|
|
version:testutil::Version(0)
|
|
state:FSTDocumentStateLocalMutations];
|
|
|
|
XCTAssertEqualObjects(currentDoc, expectedDoc);
|
|
}
|
|
|
|
- (void)transformBaseDoc:(NSDictionary<NSString *, id> *)baseData
|
|
applyTransform:(NSDictionary<NSString *, id> *)transformData
|
|
expecting:(NSDictionary<NSString *, id> *)expectedData {
|
|
[self transformBaseDoc:baseData applyTransforms:@[ transformData ] expecting:expectedData];
|
|
}
|
|
|
|
- (void)testAppliesServerAckedIncrementTransformToDocuments {
|
|
NSDictionary *docData = @{@"sum" : @1};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *transform = FSTTestTransformMutation(
|
|
@"collection/key", @{@"sum" : [FIRFieldValue fieldValueForIntegerIncrement:2]});
|
|
|
|
FSTMutationResult *mutationResult =
|
|
[[FSTMutationResult alloc] initWithVersion:testutil::Version(1)
|
|
transformResults:@[ FieldValue::FromInteger(3).Wrap() ]];
|
|
|
|
FSTMaybeDocument *transformedDoc = [transform applyToRemoteDocument:baseDoc
|
|
mutationResult:mutationResult];
|
|
|
|
NSDictionary *expectedData = @{@"sum" : @3};
|
|
XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 1, expectedData,
|
|
FSTDocumentStateCommittedMutations));
|
|
}
|
|
|
|
- (void)testAppliesServerAckedServerTimestampTransformToDocuments {
|
|
NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *transform = FSTTestTransformMutation(
|
|
@"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
|
|
|
|
FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
|
|
initWithVersion:testutil::Version(1)
|
|
transformResults:@[ [FSTTimestampValue timestampValue:_timestamp] ]];
|
|
|
|
FSTMaybeDocument *transformedDoc = [transform applyToRemoteDocument:baseDoc
|
|
mutationResult:mutationResult];
|
|
|
|
NSDictionary *expectedData = @{@"foo" : @{@"bar" : _timestamp.dateValue}, @"baz" : @"baz-value"};
|
|
XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 1, expectedData,
|
|
FSTDocumentStateCommittedMutations));
|
|
}
|
|
|
|
- (void)testAppliesServerAckedArrayTransformsToDocuments {
|
|
NSDictionary *docData = @{@"array_1" : @[ @1, @2 ], @"array_2" : @[ @"a", @"b" ]};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @{
|
|
@"array_1" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @3 ]],
|
|
@"array_2" : [FIRFieldValue fieldValueForArrayRemove:@[ @"a", @"c" ]]
|
|
});
|
|
|
|
// Server just sends null transform results for array operations.
|
|
FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
|
|
initWithVersion:testutil::Version(1)
|
|
transformResults:@[ FieldValue::Null().Wrap(), FieldValue::Null().Wrap() ]];
|
|
|
|
FSTMaybeDocument *transformedDoc = [transform applyToRemoteDocument:baseDoc
|
|
mutationResult:mutationResult];
|
|
|
|
NSDictionary *expectedData = @{@"array_1" : @[ @1, @2, @3 ], @"array_2" : @[ @"b" ]};
|
|
XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 1, expectedData,
|
|
FSTDocumentStateCommittedMutations));
|
|
}
|
|
|
|
- (void)testDeleteDeletes {
|
|
NSDictionary *docData = @{@"foo" : @"bar"};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key");
|
|
FSTMaybeDocument *result = [mutation applyToLocalDocument:baseDoc
|
|
baseDocument:baseDoc
|
|
localWriteTime:_timestamp];
|
|
XCTAssertEqualObjects(result, FSTTestDeletedDoc("collection/key", 0, NO));
|
|
}
|
|
|
|
- (void)testSetWithMutationResult {
|
|
NSDictionary *docData = @{@"foo" : @"bar"};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"});
|
|
FSTMutationResult *mutationResult =
|
|
[[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
|
|
FSTMaybeDocument *setDoc = [set applyToRemoteDocument:baseDoc mutationResult:mutationResult];
|
|
|
|
NSDictionary *expectedData = @{@"foo" : @"new-bar"};
|
|
XCTAssertEqualObjects(
|
|
setDoc, FSTTestDoc("collection/key", 4, expectedData, FSTDocumentStateCommittedMutations));
|
|
}
|
|
|
|
- (void)testPatchWithMutationResult {
|
|
NSDictionary *docData = @{@"foo" : @"bar"};
|
|
FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, FSTDocumentStateSynced);
|
|
|
|
FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"new-bar"}, {});
|
|
FSTMutationResult *mutationResult =
|
|
[[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
|
|
FSTMaybeDocument *patchedDoc = [patch applyToRemoteDocument:baseDoc
|
|
mutationResult:mutationResult];
|
|
|
|
NSDictionary *expectedData = @{@"foo" : @"new-bar"};
|
|
XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 4, expectedData,
|
|
FSTDocumentStateCommittedMutations));
|
|
}
|
|
|
|
#define ASSERT_VERSION_TRANSITION(mutation, base, result, expected) \
|
|
do { \
|
|
FSTMaybeDocument *actual = [mutation applyToRemoteDocument:base mutationResult:result]; \
|
|
XCTAssertEqualObjects(actual, expected); \
|
|
} while (0);
|
|
|
|
/**
|
|
* Tests the transition table documented in FSTMutation.h.
|
|
*/
|
|
- (void)testTransitions {
|
|
FSTDocument *docV3 = FSTTestDoc("collection/key", 3, @{}, FSTDocumentStateSynced);
|
|
FSTDeletedDocument *deletedV3 = FSTTestDeletedDoc("collection/key", 3, NO);
|
|
|
|
FSTMutation *setMutation = FSTTestSetMutation(@"collection/key", @{});
|
|
FSTMutation *patchMutation = FSTTestPatchMutation("collection/key", @{}, {});
|
|
FSTMutation *transformMutation = FSTTestTransformMutation(@"collection/key", @{});
|
|
FSTMutation *deleteMutation = FSTTestDeleteMutation(@"collection/key");
|
|
|
|
FSTDeletedDocument *docV7Deleted = FSTTestDeletedDoc("collection/key", 7, YES);
|
|
FSTDocument *docV7Committed =
|
|
FSTTestDoc("collection/key", 7, @{}, FSTDocumentStateCommittedMutations);
|
|
FSTUnknownDocument *docV7Unknown = FSTTestUnknownDoc("collection/key", 7);
|
|
|
|
FSTMutationResult *mutationResult =
|
|
[[FSTMutationResult alloc] initWithVersion:testutil::Version(7) transformResults:nil];
|
|
FSTMutationResult *transformResult =
|
|
[[FSTMutationResult alloc] initWithVersion:testutil::Version(7) transformResults:@[]];
|
|
|
|
ASSERT_VERSION_TRANSITION(setMutation, docV3, mutationResult, docV7Committed);
|
|
ASSERT_VERSION_TRANSITION(setMutation, deletedV3, mutationResult, docV7Committed);
|
|
ASSERT_VERSION_TRANSITION(setMutation, nil, mutationResult, docV7Committed);
|
|
|
|
ASSERT_VERSION_TRANSITION(patchMutation, docV3, mutationResult, docV7Committed);
|
|
ASSERT_VERSION_TRANSITION(patchMutation, deletedV3, mutationResult, docV7Unknown);
|
|
ASSERT_VERSION_TRANSITION(patchMutation, nil, mutationResult, docV7Unknown);
|
|
|
|
ASSERT_VERSION_TRANSITION(transformMutation, docV3, transformResult, docV7Committed);
|
|
ASSERT_VERSION_TRANSITION(transformMutation, deletedV3, transformResult, docV7Unknown);
|
|
ASSERT_VERSION_TRANSITION(transformMutation, nil, transformResult, docV7Unknown);
|
|
|
|
ASSERT_VERSION_TRANSITION(deleteMutation, docV3, mutationResult, docV7Deleted);
|
|
ASSERT_VERSION_TRANSITION(deleteMutation, deletedV3, mutationResult, docV7Deleted);
|
|
ASSERT_VERSION_TRANSITION(deleteMutation, nil, mutationResult, docV7Deleted);
|
|
}
|
|
|
|
#undef ASSERT_TRANSITION
|
|
|
|
@end
|