Files
Josh SorefandGitHub b4e60981a5 Spelling (#8280)
Numerous fixes to spelling across the SDK.
2024-07-18 16:53:00 -04:00

397 lines
13 KiB
TypeScript

/**
* @license
* Copyright 2017 Google LLC
*
* 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 { expect } from 'chai';
import { Timestamp } from '../../../src/api/timestamp';
import { queryToTarget } from '../../../src/core/query';
import { SnapshotVersion } from '../../../src/core/snapshot_version';
import {
canonifyTarget,
Target,
targetEquals,
TargetImpl
} from '../../../src/core/target';
import { TargetId } from '../../../src/core/types';
import { IndexedDbPersistence } from '../../../src/local/indexeddb_persistence';
import { Persistence } from '../../../src/local/persistence';
import { TargetData, TargetPurpose } from '../../../src/local/target_data';
import { addEqualityMatcher } from '../../util/equality_matcher';
import {
filter,
key,
query,
resumeTokenForSnapshot,
version
} from '../../util/helpers';
import * as persistenceHelpers from './persistence_test_helpers';
import { TestTargetCache } from './test_target_cache';
describe('MemoryTargetCache', () => {
genericTargetCacheTests(persistenceHelpers.testMemoryEagerPersistence);
});
describe('IndexedDbTargetCache', () => {
if (!IndexedDbPersistence.isAvailable()) {
console.warn('No IndexedDB. Skipping IndexedDbTargetCache tests.');
return;
}
let persistencePromise: Promise<Persistence>;
beforeEach(async () => {
persistencePromise = persistenceHelpers.testIndexedDbPersistence();
});
genericTargetCacheTests(() => persistencePromise);
it('persists metadata across restarts', async () => {
const db1 = await persistencePromise;
const targetCache1 = new TestTargetCache(db1, db1.getTargetCache());
expect(await targetCache1.getHighestSequenceNumber()).to.equal(0);
const originalSequenceNumber = 1234;
const targetId = 5;
const snapshotVersion = SnapshotVersion.fromTimestamp(new Timestamp(1, 2));
const lastLimboFreeSnapshotVersion = SnapshotVersion.fromTimestamp(
new Timestamp(3, 4)
);
const target = queryToTarget(query('rooms'));
const targetData = new TargetData(
target,
targetId,
TargetPurpose.Listen,
originalSequenceNumber,
snapshotVersion,
lastLimboFreeSnapshotVersion
);
await targetCache1.addTargetData(targetData);
// Snapshot version needs to be set separately
await targetCache1.setTargetsMetadata(
originalSequenceNumber,
snapshotVersion
);
await db1.shutdown();
const db2 = await persistenceHelpers.testIndexedDbPersistence({
dontPurgeData: true
});
const targetCache2 = new TestTargetCache(db2, db2.getTargetCache());
expect(await targetCache2.getHighestSequenceNumber()).to.equal(
originalSequenceNumber
);
const actualTargetData = await targetCache2.getTargetData(target);
expect(targetData).to.deep.equal(actualTargetData);
const actualSnapshotVersion =
await targetCache2.getLastRemoteSnapshotVersion();
expect(snapshotVersion.isEqual(actualSnapshotVersion)).to.be.true;
await db2.shutdown();
await persistenceHelpers.clearTestPersistence();
});
});
/**
* Defines the set of tests to run against both target cache implementations.
*/
function genericTargetCacheTests(
persistencePromise: () => Promise<Persistence>
): void {
addEqualityMatcher({ equalsFn: targetEquals, forType: TargetImpl });
let cache: TestTargetCache;
const QUERY_ROOMS = queryToTarget(query('rooms'));
const QUERY_HALLS = queryToTarget(query('halls'));
const QUERY_GARAGES = queryToTarget(query('garages'));
/**
* Creates a new TargetData object from the given parameters, synthesizing
* a resume token from the snapshot version.
*/
let previousSequenceNumber = 0;
function testTargetData(
target: Target,
targetId: TargetId,
testVersion?: number
): TargetData {
if (testVersion === undefined) {
testVersion = 0;
}
const snapshotVersion = version(testVersion);
const resumeToken = resumeTokenForSnapshot(snapshotVersion);
return new TargetData(
target,
targetId,
TargetPurpose.Listen,
++previousSequenceNumber,
snapshotVersion,
/* lastLimboFreeSnapshotVersion= */ SnapshotVersion.min(),
resumeToken
);
}
let persistence: Persistence;
beforeEach(async () => {
persistence = await persistencePromise();
cache = new TestTargetCache(persistence, persistence.getTargetCache());
});
afterEach(async () => {
if (persistence.started) {
await persistence.shutdown();
await persistenceHelpers.clearTestPersistence();
}
});
it('returns null for target not in cache', () => {
return cache.getTargetData(QUERY_ROOMS).then(targetData => {
expect(targetData).to.equal(null);
});
});
it('can set and read a target', async () => {
const targetData = testTargetData(QUERY_ROOMS, 1, 1);
await cache.addTargetData(targetData);
const read = await cache.getTargetData(targetData.target);
expect(read).to.deep.equal(targetData);
});
it('handles canonical ID collisions', async () => {
// Type information is currently lost in our canonicalID implementations so
// this currently an easy way to force colliding canonicalIDs
const q1 = queryToTarget(query('a', filter('foo', '==', 1)));
const q2 = queryToTarget(query('a', filter('foo', '==', '1')));
expect(canonifyTarget(q1)).to.equal(canonifyTarget(q2));
const data1 = testTargetData(q1, 1, 1);
await cache.addTargetData(data1);
// Using the other query should not return the query cache entry despite
// equal canonicalIDs.
expect(await cache.getTargetData(q2)).to.equal(null);
expect(await cache.getTargetData(q1)).to.deep.equal(data1);
expect(await cache.getTargetCount()).to.equal(1);
const data2 = testTargetData(q2, 2, 1);
await cache.addTargetData(data2);
expect(await cache.getTargetCount()).to.equal(2);
expect(await cache.getTargetData(q1)).to.deep.equal(data1);
expect(await cache.getTargetData(q2)).to.deep.equal(data2);
await cache.removeTargetData(data1);
expect(await cache.getTargetData(q1)).to.equal(null);
expect(await cache.getTargetData(q2)).to.deep.equal(data2);
expect(await cache.getTargetCount()).to.equal(1);
await cache.removeTargetData(data2);
expect(await cache.getTargetData(q1)).to.equal(null);
expect(await cache.getTargetData(q2)).to.equal(null);
expect(await cache.getTargetCount()).to.equal(0);
});
it('can set target to new value', async () => {
await cache.addTargetData(testTargetData(QUERY_ROOMS, 1, 1));
const updated = testTargetData(QUERY_ROOMS, 1, 2);
await cache.updateTargetData(updated);
const retrieved = await cache.getTargetData(updated.target);
expect(retrieved).to.deep.equal(updated);
});
it('can remove a target', async () => {
const targetData = testTargetData(QUERY_ROOMS, 1, 1);
await cache.addTargetData(targetData);
await cache.removeTargetData(targetData);
const read = await cache.getTargetData(QUERY_ROOMS);
expect(read).to.equal(null);
});
it('can remove matching keys when a target is removed', async () => {
const rooms = testTargetData(QUERY_ROOMS, 1, 1);
await cache.addTargetData(rooms);
const key1 = key('rooms/foo');
const key2 = key('rooms/bar');
expect(await cache.containsKey(key1)).to.equal(false);
await cache.addMatchingKeys([key1], rooms.targetId);
await cache.addMatchingKeys([key2], rooms.targetId);
expect(await cache.containsKey(key1)).to.equal(true);
expect(await cache.containsKey(key2)).to.equal(true);
await cache.removeTargetData(rooms);
expect(await cache.containsKey(key1)).to.equal(false);
expect(await cache.containsKey(key2)).to.equal(false);
});
it('adds or removes matching keys', async () => {
const k = key('foo/bar');
expect(await cache.containsKey(k)).to.equal(false);
await cache.addMatchingKeys([k], 1);
expect(await cache.containsKey(k)).to.equal(true);
await cache.addMatchingKeys([k], 2);
expect(await cache.containsKey(k)).to.equal(true);
await cache.removeMatchingKeys([k], 1);
expect(await cache.containsKey(k)).to.equal(true);
await cache.removeMatchingKeys([k], 2);
expect(await cache.containsKey(k)).to.equal(false);
});
it('can remove matching keys for a targetId', async () => {
const key1 = key('foo/bar');
const key2 = key('foo/baz');
const key3 = key('foo/blah');
await cache.addMatchingKeys([key1, key2], 1);
await cache.addMatchingKeys([key3], 2);
expect(await cache.containsKey(key1)).to.equal(true);
expect(await cache.containsKey(key2)).to.equal(true);
expect(await cache.containsKey(key3)).to.equal(true);
await cache.removeMatchingKeysForTargetId(1);
expect(await cache.containsKey(key1)).to.equal(false);
expect(await cache.containsKey(key2)).to.equal(false);
expect(await cache.containsKey(key3)).to.equal(true);
await cache.removeMatchingKeysForTargetId(2);
expect(await cache.containsKey(key1)).to.equal(false);
expect(await cache.containsKey(key2)).to.equal(false);
expect(await cache.containsKey(key3)).to.equal(false);
});
it('can get matching keys for targetId', async () => {
const key1 = key('foo/bar');
const key2 = key('foo/baz');
const key3 = key('foo/blah');
await cache.addMatchingKeys([key1, key2], 1);
await cache.addMatchingKeys([key3], 2);
expect(await cache.getMatchingKeysForTargetId(1)).to.deep.equal([
key1,
key2
]);
expect(await cache.getMatchingKeysForTargetId(2)).to.deep.equal([key3]);
await cache.addMatchingKeys([key1], 2);
expect(await cache.getMatchingKeysForTargetId(1)).to.deep.equal([
key1,
key2
]);
expect(await cache.getMatchingKeysForTargetId(2)).to.deep.equal([
key1,
key3
]);
});
it('can allocate target ID', async () => {
expect(await cache.allocateTargetId()).to.deep.equal(2);
const targetData1 = testTargetData(QUERY_ROOMS, 2);
await cache.addTargetData(targetData1);
const key1 = key('rooms/bar');
const key2 = key('rooms/foo');
await cache.addMatchingKeys([key1, key2], 2);
expect(await cache.allocateTargetId()).to.deep.equal(4);
const targetData2 = testTargetData(QUERY_HALLS, 4);
await cache.addTargetData(targetData2);
const key3 = key('halls/foo');
await cache.addMatchingKeys([key3], 4);
expect(await cache.allocateTargetId()).to.deep.equal(6);
await cache.removeTargetData(targetData2);
// Target IDs never come down.
expect(await cache.allocateTargetId()).to.deep.equal(8);
// A target with an empty result set still counts.
const targetData3 = testTargetData(QUERY_GARAGES, 42);
await cache.addTargetData(targetData3);
expect(await cache.allocateTargetId()).to.deep.equal(44);
await cache.removeTargetData(targetData1);
expect(await cache.allocateTargetId()).to.deep.equal(46);
await cache.removeTargetData(targetData3);
expect(await cache.allocateTargetId()).to.deep.equal(48);
// Verify that the highestTargetId persists restarts.
const otherCache = new TestTargetCache(
persistence,
persistence.getTargetCache()
);
expect(await otherCache.allocateTargetId()).to.deep.equal(50);
});
it('can get / set targets metadata', async () => {
expect(await cache.getLastRemoteSnapshotVersion()).to.deep.equal(
SnapshotVersion.min()
);
// Can set the snapshot version.
return cache
.setTargetsMetadata(/* highestListenSequenceNumber= */ 0, version(42))
.then(async () => {
expect(await cache.getLastRemoteSnapshotVersion()).to.deep.equal(
version(42)
);
})
.then(async () => {
// Verify snapshot version persists restarts.
const otherCache = new TestTargetCache(
persistence,
persistence.getTargetCache()
);
expect(await otherCache.getLastRemoteSnapshotVersion()).to.deep.equal(
version(42)
);
});
});
it('sets the highest sequence number', async () => {
const target1 = new TargetData(QUERY_ROOMS, 1, TargetPurpose.Listen, 10);
await cache.addTargetData(target1);
const target2 = new TargetData(QUERY_HALLS, 2, TargetPurpose.Listen, 20);
await cache.addTargetData(target2);
expect(await cache.getHighestSequenceNumber()).to.equal(20);
// Sequence numbers can never come down
await cache.removeTargetData(target2);
expect(await cache.getHighestSequenceNumber()).to.equal(20);
const target3 = new TargetData(QUERY_GARAGES, 3, TargetPurpose.Listen, 100);
await cache.addTargetData(target3);
expect(await cache.getHighestSequenceNumber()).to.equal(100);
await cache.removeTargetData(target1);
expect(await cache.getHighestSequenceNumber()).to.equal(100);
await cache.removeTargetData(target3);
expect(await cache.getHighestSequenceNumber()).to.equal(100);
});
}