Files

279 lines
8.3 KiB
TypeScript

/**
* @license
* Copyright 2020 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, use } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { toByteStreamReader } from '../../../src/platform/byte_stream_reader';
import { newTextEncoder } from '../../../src/platform/text_serializer';
import {
BundleReader,
SizedBundleElement
} from '../../../src/util/bundle_reader';
import { newBundleReader } from '../../../src/util/bundle_reader_impl';
import { JSON_SERIALIZER } from '../local/persistence_test_helpers';
import {
doc1String,
doc1MetaString,
doc1Meta,
noDocMetaString,
noDocMeta,
doc2MetaString,
doc2Meta,
limitQueryString,
limitQuery,
limitToLastQuery,
limitToLastQueryString,
meta,
metaString,
doc2String,
doc1,
doc2
} from './bundle_data';
use(chaiAsPromised);
const encoder = newTextEncoder();
/**
* Create a `ReadableStream` from a string.
*
* @param content - Bundle in string.
* @param bytesPerRead - How many bytes to read from the underlying buffer from
* each read through the stream.
*/
export function byteStreamReaderFromString(
content: string,
bytesPerRead: number
): ReadableStreamDefaultReader<Uint8Array> {
const data = encoder.encode(content);
return toByteStreamReader(data, bytesPerRead);
}
// Testing readableStreamFromString() is working as expected.
describe('byteStreamReaderFromString()', () => {
it('returns a reader stepping readable stream', async () => {
const r = byteStreamReaderFromString('0123456789', 4);
let result = await r.read();
expect(result.value).to.deep.equal(encoder.encode('0123'));
expect(result.done).to.be.false;
result = await r.read();
expect(result.value).to.deep.equal(encoder.encode('4567'));
expect(result.done).to.be.false;
result = await r.read();
expect(result.value).to.deep.equal(encoder.encode('89'));
expect(result.done).to.be.false;
result = await r.read();
expect(result.value).to.be.undefined;
expect(result.done).to.be.true;
});
});
describe('Bundle ', () => {
genericBundleReadingTests(1);
genericBundleReadingTests(4);
genericBundleReadingTests(64);
genericBundleReadingTests(1024);
});
function genericBundleReadingTests(bytesPerRead: number): void {
function bundleFromString(s: string): BundleReader {
return newBundleReader(
byteStreamReaderFromString(s, bytesPerRead),
JSON_SERIALIZER
);
}
async function getAllElements(
bundle: BundleReader
): Promise<SizedBundleElement[]> {
const result: SizedBundleElement[] = [];
while (true) {
const sizedElement = await bundle.nextElement();
if (sizedElement === null) {
break;
}
if (!sizedElement.isBundleMetadata()) {
result.push(sizedElement);
}
}
return Promise.resolve(result);
}
function verifySizedElement(
element: SizedBundleElement,
payload: unknown,
payloadString: string
): void {
expect(element.payload).to.deep.equal(payload);
expect(element.byteLength).to.equal(
encoder.encode(payloadString).byteLength
);
}
async function generateBundleAndParse(
bundleString: string,
bytesPerRead: number,
validMeta = false
): Promise<void> {
const bundle = bundleFromString(bundleString);
if (!validMeta) {
await expect(await bundle.getMetadata()).should.be.rejected;
} else {
expect(await bundle.getMetadata()).to.deep.equal(meta.metadata);
}
await getAllElements(bundle);
}
it('reads with query and doc with bytesPerRead ' + bytesPerRead, async () => {
const bundle = bundleFromString(
metaString +
limitQueryString +
limitToLastQueryString +
doc1MetaString +
doc1String
);
expect(await bundle.getMetadata()).to.deep.equal(meta.metadata);
const actual = await getAllElements(bundle);
expect(actual.length).to.equal(4);
verifySizedElement(actual[0], limitQuery, limitQueryString);
verifySizedElement(actual[1], limitToLastQuery, limitToLastQueryString);
verifySizedElement(actual[2], doc1Meta, doc1MetaString);
verifySizedElement(actual[3], doc1, doc1String);
});
it(
'reads with unexpected orders with bytesPerRead ' + bytesPerRead,
async () => {
const bundle = bundleFromString(
metaString +
doc1MetaString +
doc1String +
limitQueryString +
doc2MetaString +
doc2String
);
const actual = await getAllElements(bundle);
expect(actual.length).to.equal(5);
verifySizedElement(actual[0], doc1Meta, doc1MetaString);
verifySizedElement(actual[1], doc1, doc1String);
verifySizedElement(actual[2], limitQuery, limitQueryString);
verifySizedElement(actual[3], doc2Meta, doc2MetaString);
verifySizedElement(actual[4], doc2, doc2String);
// Reading metadata after other elements should also work.
expect(await bundle.getMetadata()).to.deep.equal(meta.metadata);
}
);
it(
'reads without named query with bytesPerRead ' + bytesPerRead,
async () => {
const bundle = bundleFromString(metaString + doc1MetaString + doc1String);
expect(await bundle.getMetadata()).to.deep.equal(meta.metadata);
const actual = await getAllElements(bundle);
expect(actual.length).to.equal(2);
verifySizedElement(actual[0], doc1Meta, doc1MetaString);
verifySizedElement(actual[1], doc1, doc1String);
}
);
it('reads with deleted doc with bytesPerRead ' + bytesPerRead, async () => {
const bundle = bundleFromString(
metaString + noDocMetaString + doc1MetaString + doc1String
);
expect(await bundle.getMetadata()).to.deep.equal(meta.metadata);
const actual = await getAllElements(bundle);
expect(actual.length).to.equal(3);
verifySizedElement(actual[0], noDocMeta, noDocMetaString);
verifySizedElement(actual[1], doc1Meta, doc1MetaString);
verifySizedElement(actual[2], doc1, doc1String);
});
it(
'reads without documents or query with bytesPerRead ' + bytesPerRead,
async () => {
const bundle = bundleFromString(metaString);
expect(await bundle.getMetadata()).to.deep.equal(meta.metadata);
const actual = await getAllElements(bundle);
expect(actual.length).to.equal(0);
}
);
it(
'throws with ill-formatted bundle with bytesPerRead ' + bytesPerRead,
async () => {
await expect(
generateBundleAndParse('metadata: "no length prefix"', bytesPerRead)
).to.be.rejectedWith(
'Reached the end of bundle when a length string is expected.'
);
// The multiple "rejectedWith" checks below are an attempt to make the
// test robust in the presence of various permutations of the error
// message, which is produced by the JavaScript runtime.
// Chrome produces: Unexpected end of JSON input
// Webkit produces: JSON Parse error: Unexpected EOF
const noLengthPrefixPromise = generateBundleAndParse(
'{metadata: "no length prefix"}',
bytesPerRead
);
await expect(noLengthPrefixPromise).to.be.rejectedWith(
/(\b|^)unexpected ((end of)|(eof))(\b|$)/gi
);
await expect(noLengthPrefixPromise).to.be.rejectedWith(
/(\b|^)JSON(\b|$)/g
);
await expect(
generateBundleAndParse(
metaString + 'invalid-string',
bytesPerRead,
true
)
).to.be.rejectedWith(
'Reached the end of bundle when a length string is expected.'
);
await expect(
generateBundleAndParse('1' + metaString, bytesPerRead)
).to.be.rejectedWith('Reached the end of bundle when more is expected.');
// First element is not BundleMetadata.
await expect(
generateBundleAndParse(doc1MetaString + doc1String, bytesPerRead)
).to.be.rejectedWith('The first element of the bundle is not a metadata');
}
);
}