Files

208 lines
5.7 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 {
decodeResourcePath,
encodeResourcePath
} from '../../../src/local/encoded_resource_path';
import { PersistencePromise } from '../../../src/local/persistence_promise';
import {
SimpleDb,
SimpleDbSchemaConverter,
SimpleDbStore,
SimpleDbTransaction
} from '../../../src/local/simple_db';
import { ResourcePath } from '../../../src/model/path';
import { path } from '../../util/helpers';
let db: SimpleDb;
const sep = '\u0001\u0001';
class EncodedResourcePathSchemaConverter implements SimpleDbSchemaConverter {
createOrUpgrade(
db: IDBDatabase,
txn: IDBTransaction,
fromVersion: number,
toVersion: number
): PersistencePromise<void> {
db.createObjectStore('test');
return PersistencePromise.resolve();
}
}
describe('EncodedResourcePath', () => {
if (!SimpleDb.isAvailable()) {
console.warn('No IndexedDB. Skipping EncodedResourcePath tests.');
return;
}
const dbName = 'resource-path-tests';
beforeEach(async () => {
await SimpleDb.delete(dbName);
db = new SimpleDb(dbName, 1, new EncodedResourcePathSchemaConverter());
});
afterEach(() => {
db.close();
});
after(() => SimpleDb.delete(dbName));
it('encodes resource paths', async () => {
await assertEncoded(sep, ResourcePath.emptyPath());
await assertEncoded('\u0001\u0010' + sep, path('\0'));
await assertEncoded('\u0002' + sep, path('\u0002'));
await assertEncoded('foo\u0001\u0010' + sep, path('foo\0'));
await assertEncoded('\u0001\u0010foo' + sep, path('\0foo'));
// Server specials we don't care about here.
await assertEncoded('.' + sep, path('.'));
await assertEncoded('..' + sep, path('..'));
await assertEncoded('/' + sep, new ResourcePath(['/']));
await assertEncoded('a' + sep + 'b' + sep + 'c' + sep, path('a/b/c'));
await assertEncoded(
'a/b' +
sep +
'b.c' +
sep +
'c\u0001\u0010d' +
sep +
'd\u0001\u0011e' +
sep,
new ResourcePath(['a/b', 'b.c', 'c\0d', 'd\u0001e'])
);
});
it('orders resource paths', async () => {
await assertOrdered([
ResourcePath.emptyPath(),
path('\0'),
path('\u0001'),
path('\u0002'),
path('\t'),
path(' '),
path('%'),
path('.'),
new ResourcePath(['/']),
path('0'),
path('z'),
path('~')
]);
await assertOrdered([
ResourcePath.emptyPath(),
path('foo'),
new ResourcePath(['foo', '']),
new ResourcePath(['foo', 'bar']),
new ResourcePath(['foo/', 'bar']),
path('foob'),
path('foobar'),
path('food')
]);
});
});
function assertEncoded(expected: string, path: ResourcePath): Promise<void> {
const encoded = encodeResourcePath(path);
expect(encoded).to.deep.equal(expected);
const decoded = decodeResourcePath(encoded);
expect(decoded.toArray()).to.deep.equal(path.toArray());
let store: SimpleDbStore<string, boolean>;
return runTransaction(simpleStore => {
store = simpleStore;
return store
.put(encoded, true)
.next(() => {
return store.get(encoded);
})
.next(exists => {
expect(exists).to.equal(true);
return store.delete(encoded);
});
});
}
async function assertOrdered(paths: ResourcePath[]): Promise<void> {
// Compute the encoded forms of all the given paths
const encoded: string[] = [];
for (const path of paths) {
encoded.push(encodeResourcePath(path));
}
// Insert those all into a table, but backwards
paths.reverse();
await runTransaction(store => {
const promises: Array<PersistencePromise<void>> = [];
for (const path of encoded) {
promises.push(store.put(path, true));
}
return PersistencePromise.waitFor(promises);
});
paths.reverse();
const selected = await runTransaction(simpleStore => {
const allKeys: string[] = [];
return simpleStore
.iterate({ keysOnly: true }, key => {
allKeys.push(key);
})
.next(() => allKeys);
});
// Finally, verify all the orderings.
for (let i = 0; i < paths.length; i++) {
for (let j = 0; j < encoded.length; j++) {
if (i < j) {
expect(ResourcePath.comparator(paths[i], paths[j])).to.equal(-1);
expect(encoded[i] < encoded[j]).to.equal(true);
expect(selected[i] < selected[j]).to.equal(true);
} else if (i > j) {
expect(ResourcePath.comparator(paths[i], paths[j])).to.equal(1);
expect(encoded[i] > encoded[j]).to.equal(true);
expect(selected[i] > selected[j]).to.equal(true);
} else {
expect(ResourcePath.comparator(paths[i], paths[j])).to.equal(0);
expect(encoded[i]).to.deep.equal(encoded[j]);
expect(selected[i]).to.deep.equal(selected[j]);
}
}
}
}
// helper to reduce test boilerplate.
function runTransaction<T>(
fn: (
store: SimpleDbStore<string, boolean>,
transaction: SimpleDbTransaction
) => PersistencePromise<T>
): Promise<T> {
return db.runTransaction<T>(
'EncodedResourcePathTests',
'readwrite',
['test'],
txn => {
return fn(txn.store<string, boolean>('test'), txn);
}
);
}