diff options
Diffstat (limited to 'Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm')
-rw-r--r-- | Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm b/Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm new file mode 100644 index 0000000..3374fcf --- /dev/null +++ b/Firestore/Example/Tests/Local/FSTLevelDBKeyTests.mm @@ -0,0 +1,361 @@ +/* + * 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 "Local/FSTLevelDBKey.h" + +#import <XCTest/XCTest.h> + +#import "Model/FSTPath.h" + +#import "FSTHelpers.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FSTLevelDBKeyTests : XCTestCase +@end + +// I can't believe I have to write this... +bool StartsWith(const std::string &value, const std::string &prefix) { + return prefix.size() <= value.size() && std::equal(prefix.begin(), prefix.end(), value.begin()); +} + +static std::string RemoteDocKey(NSString *pathString) { + return [FSTLevelDBRemoteDocumentKey keyWithDocumentKey:FSTTestDocKey(pathString)]; +} + +static std::string RemoteDocKeyPrefix(NSString *pathString) { + return [FSTLevelDBRemoteDocumentKey keyPrefixWithResourcePath:FSTTestPath(pathString)]; +} + +static std::string DocMutationKey(NSString *userID, NSString *key, FSTBatchID batchID) { + return [FSTLevelDBDocumentMutationKey keyWithUserID:userID + documentKey:FSTTestDocKey(key) + batchID:batchID]; +} + +static std::string TargetDocKey(FSTTargetID targetID, NSString *key) { + return [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:FSTTestDocKey(key)]; +} + +static std::string DocTargetKey(NSString *key, FSTTargetID targetID) { + return [FSTLevelDBDocumentTargetKey keyWithDocumentKey:FSTTestDocKey(key) targetID:targetID]; +} + +/** + * Asserts that the description for given key is equal to the expected description. + * + * @param key A StringView of a textual key + * @param key An NSString that [FSTLevelDBKey descriptionForKey:] is expected to produce. + */ +#define FSTAssertExpectedKeyDescription(key, expectedDescription) \ + XCTAssertEqualObjects([FSTLevelDBKey descriptionForKey:(key)], (expectedDescription)) + +#define FSTAssertKeyLessThan(left, right) \ + do { \ + std::string leftKey = (left); \ + std::string rightKey = (right); \ + XCTAssertLessThan(leftKey.compare(right), 0, @"Expected %@ to be less than %@", \ + [FSTLevelDBKey descriptionForKey:leftKey], \ + [FSTLevelDBKey descriptionForKey:rightKey]); \ + } while (0) + +@implementation FSTLevelDBKeyTests + +- (void)testMutationKeyPrefixing { + auto tableKey = [FSTLevelDBMutationKey keyPrefix]; + auto emptyUserKey = [FSTLevelDBMutationKey keyPrefixWithUserID:""]; + auto fooUserKey = [FSTLevelDBMutationKey keyPrefixWithUserID:"foo"]; + + auto foo2Key = [FSTLevelDBMutationKey keyWithUserID:"foo" batchID:2]; + + XCTAssertTrue(StartsWith(emptyUserKey, tableKey)); + + // This is critical: prefixes of the a value don't convert into prefixes of the key. + XCTAssertTrue(StartsWith(fooUserKey, tableKey)); + XCTAssertFalse(StartsWith(fooUserKey, emptyUserKey)); + + // However whole segments in common are prefixes. + XCTAssertTrue(StartsWith(foo2Key, tableKey)); + XCTAssertTrue(StartsWith(foo2Key, fooUserKey)); +} + +- (void)testMutationKeyEncodeDecodeCycle { + FSTLevelDBMutationKey *key = [[FSTLevelDBMutationKey alloc] init]; + std::string user("foo"); + + NSArray<NSNumber *> *batchIds = @[ @0, @1, @100, @(INT_MAX - 1), @(INT_MAX) ]; + for (NSNumber *batchIDNumber in batchIds) { + FSTBatchID batchID = [batchIDNumber intValue]; + auto encoded = [FSTLevelDBMutationKey keyWithUserID:user batchID:batchID]; + + BOOL ok = [key decodeKey:encoded]; + XCTAssertTrue(ok); + XCTAssertEqual(key.userID, user); + XCTAssertEqual(key.batchID, batchID); + } +} + +- (void)testMutationKeyDescription { + FSTAssertExpectedKeyDescription([FSTLevelDBMutationKey keyPrefix], @"[mutation: incomplete key]"); + + FSTAssertExpectedKeyDescription([FSTLevelDBMutationKey keyPrefixWithUserID:@"user1"], + @"[mutation: userID=user1 incomplete key]"); + + auto key = [FSTLevelDBMutationKey keyWithUserID:@"user1" batchID:42]; + FSTAssertExpectedKeyDescription(key, @"[mutation: userID=user1 batchID=42]"); + + FSTAssertExpectedKeyDescription(key + " extra", + @"[mutation: userID=user1 batchID=42 invalid " + @"key=<hW11dGF0aW9uAAGNdXNlcjEAAYqqgCBleHRyYQ==>]"); + + // Truncate the key so that it's missing its terminator. + key.resize(key.size() - 1); + FSTAssertExpectedKeyDescription(key, @"[mutation: userID=user1 batchID=42 incomplete key]"); +} + +- (void)testDocumentMutationKeyPrefixing { + auto tableKey = [FSTLevelDBDocumentMutationKey keyPrefix]; + auto emptyUserKey = [FSTLevelDBDocumentMutationKey keyPrefixWithUserID:""]; + auto fooUserKey = [FSTLevelDBDocumentMutationKey keyPrefixWithUserID:"foo"]; + + FSTDocumentKey *documentKey = FSTTestDocKey(@"foo/bar"); + auto foo2Key = + [FSTLevelDBDocumentMutationKey keyWithUserID:"foo" documentKey:documentKey batchID:2]; + + XCTAssertTrue(StartsWith(emptyUserKey, tableKey)); + + // While we want a key with whole segments in common be considered a prefix it's vital that + // partial segments in common not be prefixes. + XCTAssertTrue(StartsWith(fooUserKey, tableKey)); + + // Here even though "" is a prefix of "foo" that prefix is within a segment so keys derived from + // those segments cannot be prefixes of each other. + XCTAssertFalse(StartsWith(fooUserKey, emptyUserKey)); + XCTAssertFalse(StartsWith(emptyUserKey, fooUserKey)); + + // However whole segments in common are prefixes. + XCTAssertTrue(StartsWith(foo2Key, tableKey)); + XCTAssertTrue(StartsWith(foo2Key, fooUserKey)); +} + +- (void)testDocumentMutationKeyEncodeDecodeCycle { + FSTLevelDBDocumentMutationKey *key = [[FSTLevelDBDocumentMutationKey alloc] init]; + std::string user("foo"); + + NSArray<FSTDocumentKey *> *documentKeys = @[ FSTTestDocKey(@"a/b"), FSTTestDocKey(@"a/b/c/d") ]; + + NSArray<NSNumber *> *batchIds = @[ @0, @1, @100, @(INT_MAX - 1), @(INT_MAX) ]; + for (NSNumber *batchIDNumber in batchIds) { + for (FSTDocumentKey *documentKey in documentKeys) { + FSTBatchID batchID = [batchIDNumber intValue]; + auto encoded = [FSTLevelDBDocumentMutationKey keyWithUserID:user + documentKey:documentKey + batchID:batchID]; + + BOOL ok = [key decodeKey:encoded]; + XCTAssertTrue(ok); + XCTAssertEqual(key.userID, user); + XCTAssertEqualObjects(key.documentKey, documentKey); + XCTAssertEqual(key.batchID, batchID); + } + } +} + +- (void)testDocumentMutationKeyOrdering { + // Different user: + FSTAssertKeyLessThan(DocMutationKey(@"1", @"foo/bar", 0), DocMutationKey(@"10", @"foo/bar", 0)); + FSTAssertKeyLessThan(DocMutationKey(@"1", @"foo/bar", 0), DocMutationKey(@"2", @"foo/bar", 0)); + + // Different paths: + FSTAssertKeyLessThan(DocMutationKey(@"1", @"foo/bar", 0), DocMutationKey(@"1", @"foo/baz", 0)); + FSTAssertKeyLessThan(DocMutationKey(@"1", @"foo/bar", 0), DocMutationKey(@"1", @"foo/bar2", 0)); + FSTAssertKeyLessThan(DocMutationKey(@"1", @"foo/bar", 0), + DocMutationKey(@"1", @"foo/bar/suffix/key", 0)); + FSTAssertKeyLessThan(DocMutationKey(@"1", @"foo/bar/suffix/key", 0), + DocMutationKey(@"1", @"foo/bar2", 0)); + + // Different batchID: + FSTAssertKeyLessThan(DocMutationKey(@"1", @"foo/bar", 0), DocMutationKey(@"1", @"foo/bar", 1)); +} + +- (void)testDocumentMutationKeyDescription { + FSTAssertExpectedKeyDescription([FSTLevelDBDocumentMutationKey keyPrefix], + @"[document_mutation: incomplete key]"); + + FSTAssertExpectedKeyDescription([FSTLevelDBDocumentMutationKey keyPrefixWithUserID:@"user1"], + @"[document_mutation: userID=user1 incomplete key]"); + + auto key = [FSTLevelDBDocumentMutationKey keyPrefixWithUserID:@"user1" + resourcePath:FSTTestPath(@"foo/bar")]; + FSTAssertExpectedKeyDescription(key, + @"[document_mutation: userID=user1 key=foo/bar incomplete key]"); + + key = [FSTLevelDBDocumentMutationKey keyWithUserID:@"user1" + documentKey:FSTTestDocKey(@"foo/bar") + batchID:42]; + FSTAssertExpectedKeyDescription(key, @"[document_mutation: userID=user1 key=foo/bar batchID=42]"); +} + +- (void)testTargetGlobalKeyEncodeDecodeCycle { + FSTLevelDBTargetGlobalKey *key = [[FSTLevelDBTargetGlobalKey alloc] init]; + + auto encoded = [FSTLevelDBTargetGlobalKey key]; + BOOL ok = [key decodeKey:encoded]; + XCTAssertTrue(ok); +} + +- (void)testTargetGlobalKeyDescription { + FSTAssertExpectedKeyDescription([FSTLevelDBTargetGlobalKey key], @"[target_global:]"); +} + +- (void)testTargetKeyEncodeDecodeCycle { + FSTLevelDBTargetKey *key = [[FSTLevelDBTargetKey alloc] init]; + FSTTargetID targetID = 42; + + auto encoded = [FSTLevelDBTargetKey keyWithTargetID:42]; + BOOL ok = [key decodeKey:encoded]; + XCTAssertTrue(ok); + XCTAssertEqual(key.targetID, targetID); +} + +- (void)testTargetKeyDescription { + FSTAssertExpectedKeyDescription([FSTLevelDBTargetKey keyWithTargetID:42], + @"[target: targetID=42]"); +} + +- (void)testQueryTargetKeyEncodeDecodeCycle { + FSTLevelDBQueryTargetKey *key = [[FSTLevelDBQueryTargetKey alloc] init]; + std::string canonicalID("foo"); + FSTTargetID targetID = 42; + + auto encoded = [FSTLevelDBQueryTargetKey keyWithCanonicalID:canonicalID targetID:42]; + BOOL ok = [key decodeKey:encoded]; + XCTAssertTrue(ok); + XCTAssertEqual(key.canonicalID, canonicalID); + XCTAssertEqual(key.targetID, targetID); +} + +- (void)testQueryKeyDescription { + FSTAssertExpectedKeyDescription([FSTLevelDBQueryTargetKey keyWithCanonicalID:"foo" targetID:42], + @"[query_target: canonicalID=foo targetID=42]"); +} + +- (void)testTargetDocumentKeyEncodeDecodeCycle { + FSTLevelDBTargetDocumentKey *key = [[FSTLevelDBTargetDocumentKey alloc] init]; + + auto encoded = + [FSTLevelDBTargetDocumentKey keyWithTargetID:42 documentKey:FSTTestDocKey(@"foo/bar")]; + BOOL ok = [key decodeKey:encoded]; + XCTAssertTrue(ok); + XCTAssertEqual(key.targetID, 42); + XCTAssertEqualObjects(key.documentKey, FSTTestDocKey(@"foo/bar")); +} + +- (void)testTargetDocumentKeyOrdering { + // Different targetID: + FSTAssertKeyLessThan(TargetDocKey(1, @"foo/bar"), TargetDocKey(2, @"foo/bar")); + FSTAssertKeyLessThan(TargetDocKey(2, @"foo/bar"), TargetDocKey(10, @"foo/bar")); + FSTAssertKeyLessThan(TargetDocKey(10, @"foo/bar"), TargetDocKey(100, @"foo/bar")); + FSTAssertKeyLessThan(TargetDocKey(42, @"foo/bar"), TargetDocKey(100, @"foo/bar")); + + // Different paths: + FSTAssertKeyLessThan(TargetDocKey(1, @"foo/bar"), TargetDocKey(1, @"foo/baz")); + FSTAssertKeyLessThan(TargetDocKey(1, @"foo/bar"), TargetDocKey(1, @"foo/bar2")); + FSTAssertKeyLessThan(TargetDocKey(1, @"foo/bar"), TargetDocKey(1, @"foo/bar/suffix/key")); + FSTAssertKeyLessThan(TargetDocKey(1, @"foo/bar/suffix/key"), TargetDocKey(1, @"foo/bar2")); +} + +- (void)testTargetDocumentKeyDescription { + auto key = [FSTLevelDBTargetDocumentKey keyWithTargetID:42 documentKey:FSTTestDocKey(@"foo/bar")]; + XCTAssertEqualObjects([FSTLevelDBKey descriptionForKey:key], + @"[target_document: targetID=42 key=foo/bar]"); +} + +- (void)testDocumentTargetKeyEncodeDecodeCycle { + FSTLevelDBDocumentTargetKey *key = [[FSTLevelDBDocumentTargetKey alloc] init]; + + auto encoded = + [FSTLevelDBDocumentTargetKey keyWithDocumentKey:FSTTestDocKey(@"foo/bar") targetID:42]; + BOOL ok = [key decodeKey:encoded]; + XCTAssertTrue(ok); + XCTAssertEqualObjects(key.documentKey, FSTTestDocKey(@"foo/bar")); + XCTAssertEqual(key.targetID, 42); +} + +- (void)testDocumentTargetKeyDescription { + auto key = [FSTLevelDBDocumentTargetKey keyWithDocumentKey:FSTTestDocKey(@"foo/bar") targetID:42]; + XCTAssertEqualObjects([FSTLevelDBKey descriptionForKey:key], + @"[document_target: key=foo/bar targetID=42]"); +} + +- (void)testDocumentTargetKeyOrdering { + // Different paths: + FSTAssertKeyLessThan(DocTargetKey(@"foo/bar", 1), DocTargetKey(@"foo/baz", 1)); + FSTAssertKeyLessThan(DocTargetKey(@"foo/bar", 1), DocTargetKey(@"foo/bar2", 1)); + FSTAssertKeyLessThan(DocTargetKey(@"foo/bar", 1), DocTargetKey(@"foo/bar/suffix/key", 1)); + FSTAssertKeyLessThan(DocTargetKey(@"foo/bar/suffix/key", 1), DocTargetKey(@"foo/bar2", 1)); + + // Different targetID: + FSTAssertKeyLessThan(DocTargetKey(@"foo/bar", 1), DocTargetKey(@"foo/bar", 2)); + FSTAssertKeyLessThan(DocTargetKey(@"foo/bar", 2), DocTargetKey(@"foo/bar", 10)); + FSTAssertKeyLessThan(DocTargetKey(@"foo/bar", 10), DocTargetKey(@"foo/bar", 100)); + FSTAssertKeyLessThan(DocTargetKey(@"foo/bar", 42), DocTargetKey(@"foo/bar", 100)); +} + +- (void)testRemoteDocumentKeyPrefixing { + auto tableKey = [FSTLevelDBRemoteDocumentKey keyPrefix]; + + XCTAssertTrue(StartsWith(RemoteDocKey(@"foo/bar"), tableKey)); + + // This is critical: foo/bar2 should not contain foo/bar. + XCTAssertFalse(StartsWith(RemoteDocKey(@"foo/bar2"), RemoteDocKey(@"foo/bar"))); + + // Prefixes must be encoded specially + XCTAssertFalse(StartsWith(RemoteDocKey(@"foo/bar/baz/quu"), RemoteDocKey(@"foo/bar"))); + XCTAssertTrue(StartsWith(RemoteDocKey(@"foo/bar/baz/quu"), RemoteDocKeyPrefix(@"foo/bar"))); + XCTAssertTrue(StartsWith(RemoteDocKeyPrefix(@"foo/bar/baz/quu"), RemoteDocKeyPrefix(@"foo/bar"))); + XCTAssertTrue(StartsWith(RemoteDocKeyPrefix(@"foo/bar/baz"), RemoteDocKeyPrefix(@"foo/bar"))); + XCTAssertTrue(StartsWith(RemoteDocKeyPrefix(@"foo/bar"), RemoteDocKeyPrefix(@"foo"))); +} + +- (void)testRemoteDocumentKeyOrdering { + FSTAssertKeyLessThan(RemoteDocKey(@"foo/bar"), RemoteDocKey(@"foo/bar2")); + FSTAssertKeyLessThan(RemoteDocKey(@"foo/bar"), RemoteDocKey(@"foo/bar/suffix/key")); +} + +- (void)testRemoteDocumentKeyEncodeDecodeCycle { + FSTLevelDBRemoteDocumentKey *key = [[FSTLevelDBRemoteDocumentKey alloc] init]; + + NSArray<NSString *> *paths = @[ @"foo/bar", @"foo/bar2", @"foo/bar/baz/quux" ]; + for (NSString *path in paths) { + auto encoded = RemoteDocKey(path); + BOOL ok = [key decodeKey:encoded]; + XCTAssertTrue(ok); + XCTAssertEqualObjects(key.documentKey, FSTTestDocKey(path)); + } +} + +- (void)testRemoteDocumentKeyDescription { + FSTAssertExpectedKeyDescription( + [FSTLevelDBRemoteDocumentKey keyWithDocumentKey:FSTTestDocKey(@"foo/bar/baz/quux")], + @"[remote_document: key=foo/bar/baz/quux]"); +} + +@end + +#undef FSTAssertExpectedKeyDescription + +NS_ASSUME_NONNULL_END |