2017-05-15 12:27:07 -07:00
|
|
|
/*
|
|
|
|
|
* 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 <XCTest/XCTest.h>
|
|
|
|
|
|
|
|
|
|
#import "FLevelDBStorageEngine.h"
|
|
|
|
|
#import "FSnapshotUtilities.h"
|
|
|
|
|
#import "FQueryParams.h"
|
|
|
|
|
#import "FPathIndex.h"
|
|
|
|
|
#import "FTrackedQuery.h"
|
|
|
|
|
#import "FWriteRecord.h"
|
|
|
|
|
#import "FTestHelpers.h"
|
|
|
|
|
#import "FEmptyNode.h"
|
|
|
|
|
|
|
|
|
|
@interface FLevelDBStorageEngineTests : XCTestCase
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation FLevelDBStorageEngineTests
|
|
|
|
|
|
|
|
|
|
- (FLevelDBStorageEngine *)cleanStorageEngine {
|
|
|
|
|
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test-db"];
|
|
|
|
|
FLevelDBStorageEngine *db = [[FLevelDBStorageEngine alloc] initWithPath:path];
|
|
|
|
|
[db purgeEverything];
|
|
|
|
|
return db;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define SAMPLE_NODE ([FSnapshotUtilities nodeFrom:@{ @"foo": @{ @"bar": @YES, @"baz": @"string" }, @"qux": @2, @"quu": @1.2 }])
|
|
|
|
|
|
|
|
|
|
#define ONE_MEG_NODE ([FTestHelpers leafNodeOfSize:1024*1024])
|
|
|
|
|
#define FIVE_MEG_NODE ([FTestHelpers leafNodeOfSize:5*1024*1024])
|
|
|
|
|
#define TEN_MEG_NODE ([FTestHelpers leafNodeOfSize:10*1024*1024])
|
|
|
|
|
#define TEN_MEG_MINUS_ONE_NODE ([FTestHelpers leafNodeOfSize:10*1024*1024 - 1])
|
|
|
|
|
|
|
|
|
|
#define SAMPLE_PARAMS \
|
|
|
|
|
([[[[[FQueryParams defaultInstance] orderBy:[[FPathIndex alloc] initWithPath:PATH(@"child")]] \
|
|
|
|
|
startAt:[FSnapshotUtilities nodeFrom:@"startVal"] childKey:@"startKey"] \
|
|
|
|
|
endAt:[FSnapshotUtilities nodeFrom:@"endVal"] childKey:@"endKey"] \
|
|
|
|
|
limitToLast:5])
|
|
|
|
|
|
|
|
|
|
#define SAMPLE_QUERY \
|
|
|
|
|
([[FQuerySpec alloc] initWithPath:[FPath pathWithString:@"foo"] params:SAMPLE_PARAMS])
|
|
|
|
|
|
|
|
|
|
#define DEFAULT_FOO_QUERY \
|
|
|
|
|
([[FQuerySpec alloc] initWithPath:[FPath pathWithString:@"foo"] params:[FQueryParams defaultInstance]])
|
|
|
|
|
|
|
|
|
|
#define SAMPLE_TRACKED_QUERY \
|
|
|
|
|
([[FTrackedQuery alloc] initWithId:1 \
|
|
|
|
|
query:SAMPLE_QUERY \
|
|
|
|
|
isPinned:NO \
|
|
|
|
|
lastUse:100 \
|
|
|
|
|
Active:NO \
|
|
|
|
|
isComplete:NO])
|
|
|
|
|
#define OVERWRITE_RECORD(__path, __node, __writeId) \
|
|
|
|
|
([[FWriteRecord alloc] initWithPath:[FPath pathWithString:__path] overwrite:__node writeId:__writeId visible:YES])
|
|
|
|
|
|
|
|
|
|
#define MERGE_RECORD(__path, __merge, __writeId) \
|
|
|
|
|
([[FWriteRecord alloc] initWithPath:[FPath pathWithString:__path] merge:__merge writeId:__writeId])
|
|
|
|
|
|
2017-07-26 13:50:34 -07:00
|
|
|
- (void)testRecocversFromBadCache {
|
|
|
|
|
NSString *dbPath = @"corrupted-db";
|
|
|
|
|
NSString *serverData = [[FLevelDBStorageEngine firebaseDir] stringByAppendingPathComponent:@"corrupted-db/server_data/CURRENT"];
|
|
|
|
|
[@"Corrupted" writeToFile:serverData atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
|
|
|
|
NSString *userData = [[FLevelDBStorageEngine firebaseDir] stringByAppendingPathComponent:@"corrupted-db/writes/CURRENT"];
|
|
|
|
|
[@"Corrupted" writeToFile:userData atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
|
|
|
|
FLevelDBStorageEngine *db = [[FLevelDBStorageEngine alloc] initWithPath:dbPath];
|
|
|
|
|
XCTAssertNotNil(db);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-15 12:27:07 -07:00
|
|
|
- (void)testUserWriteIsPersisted {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine saveUserOverwrite:SAMPLE_NODE atPath:[FPath pathWithString:@"foo/bar"] writeId:1];
|
|
|
|
|
XCTAssertEqualObjects(engine.userWrites, @[OVERWRITE_RECORD(@"foo/bar", SAMPLE_NODE, 1)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testUserMergeIsPersisted {
|
|
|
|
|
FCompoundWrite *merge = [FCompoundWrite compoundWriteWithValueDictionary:@{@"foo": @{@"bar": @1, @"baz": @"string"}, @"quu": @YES}];
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine saveUserMerge:merge atPath:PATH(@"foo/bar") writeId:1];
|
|
|
|
|
XCTAssertEqualObjects(engine.userWrites, @[MERGE_RECORD(@"foo/bar", merge, 1)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testDeepUserMergeIsPersisted {
|
|
|
|
|
FCompoundWrite *merge = [FCompoundWrite compoundWriteWithValueDictionary:@{@"foo/bar": @1, @"foo/baz": @"string", @"quu/qux": @YES, @"shallow": @2}];
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine saveUserMerge:merge atPath:PATH(@"foo/bar") writeId:1];
|
|
|
|
|
XCTAssertEqualObjects(engine.userWrites, @[MERGE_RECORD(@"foo/bar", merge, 1)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testSameWriteIdOverwritesOldWrite {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine saveUserOverwrite:NODE(@"first") atPath:PATH(@"foo/bar") writeId:1];
|
|
|
|
|
[engine saveUserOverwrite:NODE(@"second") atPath:PATH(@"other/path") writeId:1];
|
|
|
|
|
XCTAssertEqualObjects(engine.userWrites, @[OVERWRITE_RECORD(@"other/path", NODE(@"second"), 1)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testHugeWriteWorks {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/bar") writeId:1];
|
|
|
|
|
FCompoundWrite *merge = [[FCompoundWrite emptyWrite] addWrite:TEN_MEG_NODE atKey:@"update"];
|
|
|
|
|
[engine saveUserMerge:merge atPath:PATH(@"foo/bar") writeId:2];
|
|
|
|
|
NSArray *expected = @[OVERWRITE_RECORD(@"foo/bar", TEN_MEG_NODE, 1), MERGE_RECORD(@"foo/bar", merge, 2)];
|
|
|
|
|
XCTAssertEqualObjects(engine.userWrites, expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testHugeWritesCanBeDeleted {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/bar") writeId:1];
|
|
|
|
|
[engine removeUserWrite:1];
|
|
|
|
|
XCTAssertTrue(engine.userWrites.count == 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testHugeWritesCanBeInterleavedWithSmallWrites {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
[engine saveUserOverwrite:NODE(@"node-1") atPath:PATH(@"foo/1") writeId:1];
|
|
|
|
|
[engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/2") writeId:2];
|
|
|
|
|
[engine saveUserOverwrite:NODE(@"node-3") atPath:PATH(@"foo/3") writeId:3];
|
|
|
|
|
[engine saveUserOverwrite:FIVE_MEG_NODE atPath:PATH(@"foo/4") writeId:4];
|
|
|
|
|
|
|
|
|
|
NSArray *expected = @[OVERWRITE_RECORD(@"foo/1", NODE(@"node-1"), 1),
|
|
|
|
|
OVERWRITE_RECORD(@"foo/2", TEN_MEG_NODE, 2),
|
|
|
|
|
OVERWRITE_RECORD(@"foo/3", NODE(@"node-3"), 3),
|
|
|
|
|
OVERWRITE_RECORD(@"foo/4", FIVE_MEG_NODE, 4)];
|
|
|
|
|
XCTAssertEqualObjects(engine.userWrites, expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This is ported from the Android client and doesn't really make sense since we don't have multi part writes, but
|
|
|
|
|
// It's always good to have tests, so what the heck...
|
|
|
|
|
- (void)testSameWriteIdOverwritesOldMultiPartWrite {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
[engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/bar") writeId:1];
|
|
|
|
|
[engine saveUserOverwrite:NODE(@"second") atPath:PATH(@"other/path") writeId:1];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects(engine.userWrites, @[OVERWRITE_RECORD(@"other/path", NODE(@"second"), 1)]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testWritesAreReturnedInOrder {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
NSUInteger count = 20;
|
|
|
|
|
for (NSUInteger i = count - 1; i > 0; i--) {
|
|
|
|
|
NSString *path = [NSString stringWithFormat:@"foo/%lu", (unsigned long)i];
|
|
|
|
|
[engine saveUserOverwrite:NODE(@(i)) atPath:PATH(path) writeId:i];
|
|
|
|
|
}
|
|
|
|
|
NSString *path = [NSString stringWithFormat:@"foo/%lu", (unsigned long)count];
|
|
|
|
|
[engine saveUserOverwrite:NODE(@(count)) atPath:PATH(path) writeId:count];
|
|
|
|
|
NSArray *userWrites = engine.userWrites;
|
|
|
|
|
XCTAssertEqual(userWrites.count, count);
|
|
|
|
|
for (NSUInteger i = 1; i <= count; i++) {
|
|
|
|
|
NSString *path = [NSString stringWithFormat:@"foo/%lu", (unsigned long)i];
|
|
|
|
|
XCTAssertEqualObjects(userWrites[i-1], OVERWRITE_RECORD(path, NODE(@(i)), i));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testRemoveAllUserWrites {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
[engine saveUserOverwrite:NODE(@"node-1") atPath:PATH(@"foo/1") writeId:1];
|
|
|
|
|
[engine saveUserOverwrite:TEN_MEG_NODE atPath:PATH(@"foo/2") writeId:2];
|
|
|
|
|
FCompoundWrite *merge = [[FCompoundWrite emptyWrite] addWrite:TEN_MEG_NODE atKey:@"update"];
|
|
|
|
|
[engine saveUserMerge:merge atPath:PATH(@"foo/bar") writeId:3];
|
|
|
|
|
[engine removeAllUserWrites];
|
|
|
|
|
XCTAssertEqualObjects(engine.userWrites, @[]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- (void)testCacheSavedIsReturned {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:SAMPLE_NODE atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], SAMPLE_NODE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testCacheSavedIsReturnedAtRoot {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:SAMPLE_NODE atPath:PATH(@"") merge:NO];
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"")], SAMPLE_NODE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testLaterCacheWritesOverwriteOlderWrites {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:SAMPLE_NODE atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"later-bar") atPath:PATH(@"foo/bar") merge:NO];
|
|
|
|
|
// this does not affect the node
|
|
|
|
|
[engine updateServerCache:NODE(@"unaffected") atPath:PATH(@"unaffected") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"later-qux") atPath:PATH(@"foo/later-qux") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"latest-bar") atPath:PATH(@"foo/bar") merge:NO];
|
|
|
|
|
|
|
|
|
|
id<FNode> expected = [[SAMPLE_NODE updateImmediateChild:@"bar" withNewChild:NODE(@"latest-bar")]
|
|
|
|
|
updateImmediateChild:@"later-qux" withNewChild:NODE(@"later-qux")];
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testLaterCacheWritesOverwriteOlderDeeperWrites {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:SAMPLE_NODE atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"later-bar") atPath:PATH(@"foo/bar") merge:NO];
|
|
|
|
|
// this does not affect the node
|
|
|
|
|
[engine updateServerCache:NODE(@"unaffected") atPath:PATH(@"unaffected") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"later-qux") atPath:PATH(@"foo/later-qux") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"latest-bar") atPath:PATH(@"foo/bar") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"latest-foo") atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(@"latest-foo"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testLaterCacheWritesDontAffectEarlierWritesAtUnaffectedPath {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:SAMPLE_NODE atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
// this does not affect the node
|
|
|
|
|
[engine updateServerCache:NODE(@"unaffected") atPath:PATH(@"unaffected") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"latest-foo") atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"unaffected")], NODE(@"unaffected"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testMergeOnEmptyCacheGivesResults {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
NSDictionary *mergeData = @{@"foo": @"foo-value", @"bar": @"bar-value"};
|
|
|
|
|
FCompoundWrite *merge = [FCompoundWrite compoundWriteWithValueDictionary:mergeData];
|
|
|
|
|
[engine updateServerCacheWithMerge:merge atPath:PATH(@"foo")];
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(mergeData));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testMergePartlyOverwritingPreviousWrite {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
id<FNode> existingNode = NODE((@{@"foo": @"foo-value", @"bar": @"bar-value"}));
|
|
|
|
|
[engine updateServerCache:existingNode atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
FCompoundWrite *merge = [FCompoundWrite compoundWriteWithValueDictionary:@{@"foo": @"new-foo-value", @"baz": @"baz-value"}];
|
|
|
|
|
[engine updateServerCacheWithMerge:merge atPath:PATH(@"foo")];
|
|
|
|
|
|
|
|
|
|
id<FNode> expected = NODE((@{@"foo": @"new-foo-value", @"bar": @"bar-value", @"baz": @"baz-value"}));
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testDeepMergePartlyOverwritingPreviousWrite {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
id<FNode> existingNode = NODE((@{@"foo": @{ @"bar": @"bar-value", @"baz": @"baz-value"}, @"qux": @"qux-value"}));
|
|
|
|
|
[engine updateServerCache:existingNode atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
FCompoundWrite *merge = [FCompoundWrite compoundWriteWithValueDictionary:@{@"foo/bar": @"new-bar-value", @"quu": @"quu-value"}];
|
|
|
|
|
[engine updateServerCacheWithMerge:merge atPath:PATH(@"foo")];
|
|
|
|
|
|
|
|
|
|
id<FNode> expected = NODE((@{@"foo": @{ @"bar": @"new-bar-value", @"baz": @"baz-value"}, @"qux": @"qux-value", @"quu": @"quu-value"}));
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testMergePartlyOverwritingPreviousMerge {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
FCompoundWrite *merge1 = [FCompoundWrite compoundWriteWithValueDictionary:@{@"foo": @"foo-value", @"bar": @"bar-value"}];
|
|
|
|
|
[engine updateServerCacheWithMerge:merge1 atPath:PATH(@"foo")];
|
|
|
|
|
|
|
|
|
|
FCompoundWrite *merge2 = [FCompoundWrite compoundWriteWithValueDictionary:@{@"foo": @"new-foo-value", @"baz": @"baz-value"}];
|
|
|
|
|
[engine updateServerCacheWithMerge:merge2 atPath:PATH(@"foo")];
|
|
|
|
|
|
|
|
|
|
id<FNode> expected = NODE((@{@"foo": @"new-foo-value", @"bar": @"bar-value", @"baz": @"baz-value"}));
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testOverwriteRemovesPreviousMerge {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
id<FNode> initial = NODE((@{@"foo": @"foo-value", @"bar": @"bar-value"}));
|
|
|
|
|
[engine updateServerCache:initial atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
FCompoundWrite *merge2 = [FCompoundWrite compoundWriteWithValueDictionary:@{@"foo": @"new-foo-value", @"baz": @"baz-value"}];
|
|
|
|
|
[engine updateServerCacheWithMerge:merge2 atPath:PATH(@"foo")];
|
|
|
|
|
|
|
|
|
|
id<FNode> replacingNode = NODE((@{@"qux": @"qux-value", @"quu": @"quu-value"}));
|
|
|
|
|
[engine updateServerCache:replacingNode atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], replacingNode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testEmptyOverwriteDeletesNodeFromHigherWrite {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
id<FNode> initial = NODE((@{@"foo": @"foo-value", @"bar": @"bar-value"}));
|
|
|
|
|
[engine updateServerCache:initial atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
// delete bar
|
|
|
|
|
[engine updateServerCache:NODE(nil) atPath:PATH(@"foo/bar") merge:NO];
|
|
|
|
|
|
|
|
|
|
id<FNode> expected = NODE((@{@"foo": @"foo-value"}));
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testDeeperReadFromHigherSet {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
id<FNode> initial = NODE((@{@"foo": @"foo-value", @"bar": @"bar-value"}));
|
|
|
|
|
[engine updateServerCache:initial atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/bar")], NODE(@"bar-value"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testDeeperLeafNodeSetRemovesHigherLeafNodes {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:NODE(@"level-0") atPath:PATH(@"") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"level-1") atPath:PATH(@"lvl1") merge:NO];
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"")], NODE((@{@"lvl1": @"level-1"})));
|
|
|
|
|
|
|
|
|
|
[engine updateServerCache:NODE(@"level-2") atPath:PATH(@"lvl1/lvl2") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"lvl1")], NODE((@{@"lvl2": @"level-2"})));
|
|
|
|
|
|
|
|
|
|
[engine updateServerCache:NODE(@"level-4") atPath:PATH(@"lvl1/lvl2/lvl3/lvl4") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"lvl1")], NODE((@{@"lvl2": @{@"lvl3": @{@"lvl4": @"level-4"}}})));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// This test causes a split on Android so it doesn't really make sense here, but why not test anyways...
|
|
|
|
|
- (void)testHugeNodeWithSplit {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
id<FNode> outer = [FEmptyNode emptyNode];
|
|
|
|
|
// This structure ensures splits at various depths
|
|
|
|
|
for (NSUInteger i = 0; i < 100; i++) { // Outer
|
|
|
|
|
id<FNode> inner = [FEmptyNode emptyNode];
|
|
|
|
|
for (NSUInteger j = 0; j < i; j++) { // Inner
|
|
|
|
|
id<FNode> innerMost = [FEmptyNode emptyNode];
|
|
|
|
|
for (NSUInteger k = 0; k < j; k++) {
|
|
|
|
|
NSString *key = [NSString stringWithFormat:@"key-%lu", (unsigned long)k];
|
|
|
|
|
id<FNode> node = NODE(([NSString stringWithFormat:@"leaf-%lu", (unsigned long)k]));
|
|
|
|
|
innerMost = [innerMost updateImmediateChild:key withNewChild:node];
|
|
|
|
|
}
|
|
|
|
|
NSString *innerKey = [NSString stringWithFormat:@"key-%lu", (unsigned long)j];
|
|
|
|
|
inner = [inner updateImmediateChild:innerKey withNewChild:innerMost];
|
|
|
|
|
}
|
|
|
|
|
NSString *outerKey = [NSString stringWithFormat:@"key-%lu", (unsigned long)i];
|
|
|
|
|
outer = [outer updateImmediateChild:outerKey withNewChild:inner];
|
|
|
|
|
}
|
|
|
|
|
[engine updateServerCache:outer atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], outer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testManyLargeLeafNodes {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
id<FNode> outer = [FEmptyNode emptyNode];
|
|
|
|
|
for (NSUInteger i = 0; i < 30; i++) {
|
|
|
|
|
NSString *outerKey = [NSString stringWithFormat:@"key-%lu", (unsigned long)i];
|
|
|
|
|
outer = [outer updateImmediateChild:outerKey withNewChild:ONE_MEG_NODE];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[engine updateServerCache:outer atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], outer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testPriorityWorks {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
[engine updateServerCache:NODE(@"bar-value") atPath:PATH(@"foo/bar") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"prio-value") atPath:PATH(@"foo/.priority") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE((@{ @".priority": @"prio-value", @"bar": @"bar-value"})));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testSimilarSiblingsAreNotLoaded {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
[engine updateServerCache:NODE(@"value") atPath:PATH(@"foo/123") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"sibling-value") atPath:PATH(@"foo/1230") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/123")], NODE(@"value"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: this test fails, but it is a rare edge case around priorities which would require a bunch of code
|
|
|
|
|
// Fix whenever we have too much time on our hands
|
|
|
|
|
- (void)priorityIsCleared {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
[engine updateServerCache:NODE((@{@"bar": @"bar-value"})) atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"prio-value") atPath:PATH(@"foo/.priority") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(nil) atPath:PATH(@"foo/bar") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"baz-value") atPath:PATH(@"foo/baz") merge:NO];
|
|
|
|
|
|
|
|
|
|
// Priority should have been cleaned out
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(@{@"baz": @"baz-value"}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testHugeLeafNode {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], TEN_MEG_NODE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testHugeLeafNodeSiblings {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo/one") merge:NO];
|
|
|
|
|
[engine updateServerCache:TEN_MEG_MINUS_ONE_NODE atPath:PATH(@"foo/two") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/one")], TEN_MEG_NODE);
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/two")], TEN_MEG_MINUS_ONE_NODE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testHugeLeafNodeThenTinyLeafNode {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"tiny") atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(@"tiny"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testHugeLeafNodeThenSmallerLeafNode {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
[engine updateServerCache:FIVE_MEG_NODE atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], FIVE_MEG_NODE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testHugeLeafNodeThenDeeperSet {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:TEN_MEG_NODE atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
[engine updateServerCache:NODE(@"deep-value") atPath:PATH(@"foo/deep") merge:NO];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE((@{@"deep": @"deep-value"})));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Well this is awkward, but NSJSONSerialization fails to deserialize JSON with tiny/huge doubles
|
|
|
|
|
// It is kind of bad we raise "invalid" data, but at least we don't crash *trollface*
|
|
|
|
|
- (void)testExtremeDoublesAsServerCache {
|
2017-07-20 14:20:07 -07:00
|
|
|
#ifdef TARGET_OS_IOS
|
|
|
|
|
if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion == 11) {
|
|
|
|
|
// NSJSONSerialization on iOS 11 correctly serializes small and large doubles.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-05-15 12:27:07 -07:00
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:NODE((@{@"works": @"value", @"fails": @(2.225073858507201e-308)})) atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
// Will drop the tiny double
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo")], NODE(@{@"works": @"value"}));
|
|
|
|
|
XCTAssertEqualObjects([engine serverCacheAtPath:PATH(@"foo/fails")], [FEmptyNode emptyNode]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testExtremeDoublesAsTrackedQuery {
|
2017-07-20 14:20:07 -07:00
|
|
|
#ifdef TARGET_OS_IOS
|
|
|
|
|
if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion == 11) {
|
|
|
|
|
// NSJSONSerialization on iOS 11 correctly serializes small and large doubles.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2017-05-15 12:27:07 -07:00
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
id<FNode> tinyDouble = NODE(@(2.225073858507201e-308));
|
|
|
|
|
|
|
|
|
|
FQueryParams *params = [[[FQueryParams defaultInstance] startAt:tinyDouble] endAt:tinyDouble];
|
|
|
|
|
FTrackedQuery *doesNotWork = [[FTrackedQuery alloc] initWithId:0
|
|
|
|
|
query:[[FQuerySpec alloc] initWithPath:PATH(@"foo") params:params]
|
|
|
|
|
lastUse:0
|
|
|
|
|
isActive:NO];
|
|
|
|
|
FTrackedQuery *doesWork = [[FTrackedQuery alloc] initWithId:1
|
|
|
|
|
query:[FQuerySpec defaultQueryAtPath:PATH(@"bar")]
|
|
|
|
|
lastUse:0
|
|
|
|
|
isActive:NO];
|
|
|
|
|
[engine saveTrackedQuery:doesNotWork];
|
|
|
|
|
[engine saveTrackedQuery:doesWork];
|
|
|
|
|
// One will be dropped, the other should still be there
|
|
|
|
|
XCTAssertEqualObjects([engine loadTrackedQueries], @[doesWork]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testExtremeDoublesAsUserWrites {
|
2017-07-26 14:32:28 -07:00
|
|
|
#ifdef TARGET_OS_IOS
|
|
|
|
|
if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion == 11) {
|
|
|
|
|
// NSJSONSerialization on iOS 11 correctly serializes small and large doubles.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2017-05-15 12:27:07 -07:00
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
id<FNode> tinyDouble = NODE(@(2.225073858507201e-308));
|
|
|
|
|
|
|
|
|
|
[engine saveUserOverwrite:tinyDouble atPath:PATH(@"foo") writeId:1];
|
|
|
|
|
[engine saveUserMerge:[[FCompoundWrite emptyWrite] addWrite:tinyDouble atPath:PATH(@"bar")] atPath:PATH(@"foo") writeId:2];
|
|
|
|
|
[engine saveUserOverwrite:NODE(@"should-work") atPath:PATH(@"other") writeId:3];
|
|
|
|
|
|
|
|
|
|
// The other two should be dropped and only the valid should remain
|
|
|
|
|
XCTAssertEqualObjects([engine userWrites], @[[[FWriteRecord alloc] initWithPath:PATH(@"other")
|
|
|
|
|
overwrite:NODE(@"should-work")
|
|
|
|
|
writeId:3
|
|
|
|
|
visible:YES]]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testLongValuesDontLosePrecision {
|
|
|
|
|
id longValue = @1542405709418655810;
|
|
|
|
|
id floatValue = @2.47;
|
|
|
|
|
id<FNode> expectedData = NODE((@{@"long": longValue, @"float": floatValue}));
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:expectedData atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
id<FNode> actualData = [engine serverCacheAtPath:PATH(@"foo")];
|
|
|
|
|
NSDictionary* value = [actualData val];
|
|
|
|
|
XCTAssertEqualObjects([value[@"long"] stringValue], [longValue stringValue]);
|
|
|
|
|
XCTAssertEqualObjects([value[@"float"] stringValue], [floatValue stringValue]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NSJSONSerialization has a bug in which it rounds doubles wrongly so hashes end up not matching on the server for
|
|
|
|
|
// some doubles (including 2.47). Make sure LevelDB has the correct hash for that
|
|
|
|
|
- (void)testDoublesAreRoundedProperly {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:NODE(@(2.47)) atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
|
|
|
|
|
// Expected hash for 2.47 parsed correctly
|
|
|
|
|
NSString *hashFor247 = @"EsibHXKcBp2/b/bn/a0C5WffcUU=";
|
|
|
|
|
XCTAssertEqualObjects([[engine serverCacheAtPath:PATH(@"foo")] dataHash], hashFor247);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-27 10:04:47 -07:00
|
|
|
- (void)testIntegersAreReturnedsAsIntegers {
|
|
|
|
|
id intValue = @247;
|
|
|
|
|
id longValue = @1542405709418655810;
|
|
|
|
|
id doubleValue = @0xFFFFFFFFFFFFFFFFUL; // This number can't be represented as a signed long.
|
2017-06-30 10:47:37 -07:00
|
|
|
|
2017-06-27 10:04:47 -07:00
|
|
|
id<FNode> expectedData = NODE((@{@"int": @247, @"long": longValue, @"double": doubleValue}));
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine updateServerCache:expectedData atPath:PATH(@"foo") merge:NO];
|
|
|
|
|
id<FNode> actualData = [engine serverCacheAtPath:PATH(@"foo")];
|
|
|
|
|
NSNumber* actualInt = [actualData val][@"int"];
|
|
|
|
|
NSNumber* actualLong = [actualData val][@"long"];
|
|
|
|
|
NSNumber* actualDouble = [actualData val][@"double"];
|
2017-06-30 10:47:37 -07:00
|
|
|
|
2017-06-27 10:04:47 -07:00
|
|
|
XCTAssertEqualObjects([actualInt stringValue], [intValue stringValue]);
|
|
|
|
|
XCTAssertEqual(CFNumberGetType((CFNumberRef)actualInt), kCFNumberSInt64Type);
|
|
|
|
|
XCTAssertEqualObjects([actualLong stringValue ], [longValue stringValue]);
|
|
|
|
|
XCTAssertEqual(CFNumberGetType((CFNumberRef)actualLong), kCFNumberSInt64Type);
|
|
|
|
|
XCTAssertEqual(CFNumberGetType((CFNumberRef)actualDouble), kCFNumberFloat64Type);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-15 12:27:07 -07:00
|
|
|
// TODO[offline]: Somehow test estimated server size?
|
|
|
|
|
// TODO[offline]: Test pruning!
|
|
|
|
|
|
|
|
|
|
- (void)testSaveAndLoadTrackedQueries {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
NSArray *queries = @[[[FTrackedQuery alloc] initWithId:1 query:SAMPLE_QUERY lastUse:100 isActive:NO isComplete:NO],
|
|
|
|
|
[[FTrackedQuery alloc] initWithId:2 query:[FQuerySpec defaultQueryAtPath:PATH(@"a")] lastUse:200 isActive:NO isComplete:NO],
|
|
|
|
|
[[FTrackedQuery alloc] initWithId:3 query:[FQuerySpec defaultQueryAtPath:PATH(@"b")] lastUse:300 isActive:YES isComplete:NO],
|
|
|
|
|
[[FTrackedQuery alloc] initWithId:4 query:[FQuerySpec defaultQueryAtPath:PATH(@"c")] lastUse:400 isActive:NO isComplete:YES],
|
|
|
|
|
[[FTrackedQuery alloc] initWithId:5 query:[FQuerySpec defaultQueryAtPath:PATH(@"foo")] lastUse:500 isActive:NO isComplete:NO]];
|
|
|
|
|
|
|
|
|
|
[queries enumerateObjectsUsingBlock:^(FTrackedQuery *query, NSUInteger idx, BOOL *stop) {
|
|
|
|
|
[engine saveTrackedQuery:query];
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine loadTrackedQueries], queries);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testOverwriteTrackedQueryById {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
|
|
|
|
|
FTrackedQuery *first = [[FTrackedQuery alloc] initWithId:1 query:SAMPLE_QUERY lastUse:100 isActive:NO isComplete:NO];
|
|
|
|
|
FTrackedQuery *second = [[FTrackedQuery alloc] initWithId:1 query:DEFAULT_FOO_QUERY lastUse:200 isActive:YES isComplete:YES];
|
|
|
|
|
[engine saveTrackedQuery:first];
|
|
|
|
|
[engine saveTrackedQuery:second];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine loadTrackedQueries], @[second]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testDeleteTrackedQuery {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
FTrackedQuery *query1 = [[FTrackedQuery alloc] initWithId:1 query:[FQuerySpec defaultQueryAtPath:PATH(@"a")] lastUse:100 isActive:NO isComplete:NO];
|
|
|
|
|
FTrackedQuery *query2 = [[FTrackedQuery alloc] initWithId:2 query:[FQuerySpec defaultQueryAtPath:PATH(@"b")] lastUse:200 isActive:YES isComplete:NO];
|
|
|
|
|
FTrackedQuery *query3 = [[FTrackedQuery alloc] initWithId:3 query:[FQuerySpec defaultQueryAtPath:PATH(@"c")] lastUse:300 isActive:NO isComplete:YES];
|
|
|
|
|
[engine saveTrackedQuery:query1];
|
|
|
|
|
[engine saveTrackedQuery:query2];
|
|
|
|
|
[engine saveTrackedQuery:query3];
|
|
|
|
|
|
|
|
|
|
[engine removeTrackedQuery:2];
|
|
|
|
|
XCTAssertEqualObjects([engine loadTrackedQueries], (@[query1, query3]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testSaveAndLoadTrackedQueryKeys {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
NSSet *keys = [NSSet setWithArray:@[@"foo", @"☁", @"10", @"٩(͡๏̯͡๏)۶"]];
|
|
|
|
|
[engine setTrackedQueryKeys:keys forQueryId:1];
|
|
|
|
|
[engine setTrackedQueryKeys:[NSSet setWithArray:@[@"not", @"included"]] forQueryId:2];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1], keys);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testSaveOverwritesTrackedQueryKeys {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine setTrackedQueryKeys:[NSSet setWithArray:@[@"a", @"b", @"c"]] forQueryId:1];
|
|
|
|
|
[engine setTrackedQueryKeys:[NSSet setWithArray:@[@"c", @"d", @"e"]] forQueryId:1];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1], ([NSSet setWithArray:@[@"c", @"d", @"e"]]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testUpdateTrackedQueryKeys {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
[engine setTrackedQueryKeys:[NSSet setWithArray:@[@"a", @"b", @"c"]] forQueryId:1];
|
|
|
|
|
[engine updateTrackedQueryKeysWithAddedKeys:[NSSet setWithArray:@[@"c", @"d", @"e"]]
|
|
|
|
|
removedKeys:[NSSet setWithArray:@[@"a", @"b"]]
|
|
|
|
|
forQueryId:1];
|
|
|
|
|
XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1], ([NSSet setWithArray:@[@"c", @"d", @"e"]]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testRemoveTrackedQueryRemovesTrackedQueryKeys {
|
|
|
|
|
FLevelDBStorageEngine *engine = [self cleanStorageEngine];
|
|
|
|
|
FTrackedQuery *query1 = [[FTrackedQuery alloc] initWithId:1 query:[FQuerySpec defaultQueryAtPath:PATH(@"a")] lastUse:100 isActive:NO isComplete:NO];
|
|
|
|
|
FTrackedQuery *query2 = [[FTrackedQuery alloc] initWithId:2 query:[FQuerySpec defaultQueryAtPath:PATH(@"b")] lastUse:200 isActive:NO isComplete:NO];
|
|
|
|
|
[engine saveTrackedQuery:query1];
|
|
|
|
|
[engine saveTrackedQuery:query2];
|
|
|
|
|
[engine setTrackedQueryKeys:[NSSet setWithArray:@[@"a", @"b"]] forQueryId:1];
|
|
|
|
|
[engine setTrackedQueryKeys:[NSSet setWithArray:@[@"b", @"c"]] forQueryId:2];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine loadTrackedQueries], (@[query1, query2]));
|
|
|
|
|
XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1], ([NSSet setWithArray:@[@"a", @"b"]]));
|
|
|
|
|
XCTAssertEqualObjects([engine trackedQueryKeysForQuery:2], ([NSSet setWithArray:@[@"b", @"c"]]));
|
|
|
|
|
|
|
|
|
|
[engine removeTrackedQuery:1];
|
|
|
|
|
|
|
|
|
|
XCTAssertEqualObjects([engine loadTrackedQueries], (@[query2]));
|
|
|
|
|
XCTAssertEqualObjects([engine trackedQueryKeysForQuery:1], [NSSet set]);
|
|
|
|
|
XCTAssertEqualObjects([engine trackedQueryKeysForQuery:2], ([NSSet setWithArray:@[@"b", @"c"]]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|