mirror of
https://github.com/hanzoai/firebase-js-sdk.git
synced 2026-09-24 16:59:10 +00:00
1201 lines
38 KiB
TypeScript
1201 lines
38 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 { isNode } from '@firebase/util';
|
|
import { expect } from 'chai';
|
|
|
|
import {
|
|
DocumentReference,
|
|
DocumentSnapshot,
|
|
documentSnapshotFromJSON,
|
|
QuerySnapshot,
|
|
querySnapshotFromJSON,
|
|
connectFirestoreEmulator,
|
|
loadBundle,
|
|
refEqual,
|
|
snapshotEqual,
|
|
queryEqual
|
|
} from '../../../src';
|
|
import { EmulatorAuthCredentialsProvider } from '../../../src/api/credentials';
|
|
import { User } from '../../../src/auth/user';
|
|
import {
|
|
collectionReference,
|
|
documentReference,
|
|
documentSnapshot,
|
|
firestore,
|
|
newTestFirestore,
|
|
query,
|
|
querySnapshot,
|
|
querySnapshotWithQuery
|
|
} from '../../util/api_helpers';
|
|
import { keys, query as internalQuery, orderBy } from '../../util/helpers';
|
|
|
|
describe('Bundle', () => {
|
|
it('loadBundle does not throw with an empty bundle string)', async () => {
|
|
const db = newTestFirestore();
|
|
expect(async () => {
|
|
await loadBundle(db, '');
|
|
}).to.not.throw;
|
|
});
|
|
});
|
|
|
|
describe('CollectionReference', () => {
|
|
it('support equality checking with isEqual()', () => {
|
|
expect(refEqual(collectionReference('foo'), collectionReference('foo'))).to
|
|
.be.true;
|
|
expect(refEqual(collectionReference('foo'), collectionReference('bar'))).to
|
|
.be.false;
|
|
});
|
|
|
|
it('JSON.stringify() does not throw', () => {
|
|
JSON.stringify(collectionReference('foo'));
|
|
});
|
|
});
|
|
|
|
describe('DocumentReference', () => {
|
|
it('support equality checking with isEqual()', () => {
|
|
expect(
|
|
refEqual(documentReference('rooms/foo'), documentReference('rooms/foo'))
|
|
).to.be.true;
|
|
expect(
|
|
refEqual(documentReference('rooms/foo'), documentReference('rooms/bar'))
|
|
).to.be.false;
|
|
});
|
|
|
|
it('JSON.stringify() does not throw', () => {
|
|
JSON.stringify(documentReference('foo/bar'));
|
|
});
|
|
|
|
it('toJSON() does not throw', () => {
|
|
expect(() => {
|
|
documentReference('foo/bar').toJSON();
|
|
}).to.not.throw;
|
|
});
|
|
|
|
it('toJSON() includes correct JSON fields', () => {
|
|
const docRef = documentReference('foo/bar');
|
|
const json = docRef.toJSON();
|
|
expect(json).to.deep.equal({
|
|
type: 'firestore/documentReference/1.0',
|
|
referencePath: 'foo/bar'
|
|
});
|
|
});
|
|
|
|
it('fromJSON() throws with invalid data', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
DocumentReference.fromJSON(db, {});
|
|
}).to.throw("JSON missing required field: 'type'");
|
|
});
|
|
|
|
it('fromJSON() throws with missing type data', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
documentSnapshotFromJSON(db, {
|
|
bundleSource: 'DocumentSnapshot',
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON missing required field: 'type'");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid type data', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
documentSnapshotFromJSON(db, {
|
|
type: 1,
|
|
bundleSource: 'DocumentSnapshot',
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON field 'type' must be a string");
|
|
});
|
|
|
|
it('fromJSON() throws with missing bundleSource', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
documentSnapshotFromJSON(db, {
|
|
type: DocumentSnapshot._jsonSchemaVersion,
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON missing required field: 'bundleSource'");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid bundleSource type', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
documentSnapshotFromJSON(db, {
|
|
type: DocumentSnapshot._jsonSchemaVersion,
|
|
bundleSource: 1,
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON field 'bundleSource' must be a string");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid bundleSource value', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
documentSnapshotFromJSON(db, {
|
|
type: DocumentSnapshot._jsonSchemaVersion,
|
|
bundleSource: 'QuerySnapshot',
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("Expected 'bundleSource' field to equal 'DocumentSnapshot'");
|
|
});
|
|
|
|
it('fromJSON() throws with missing bundleName', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
documentSnapshotFromJSON(db, {
|
|
type: DocumentSnapshot._jsonSchemaVersion,
|
|
bundleSource: 'DocumentSnapshot',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON missing required field: 'bundleName'");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid bundleName', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
documentSnapshotFromJSON(db, {
|
|
type: DocumentSnapshot._jsonSchemaVersion,
|
|
bundleSource: 'DocumentSnapshot',
|
|
bundleName: 1,
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON field 'bundleName' must be a string");
|
|
});
|
|
|
|
it('fromJSON() throws with missing bundle', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
documentSnapshotFromJSON(db, {
|
|
type: DocumentSnapshot._jsonSchemaVersion,
|
|
bundleSource: 'DocumentSnapshot',
|
|
bundleName: 'test name'
|
|
});
|
|
}).to.throw("JSON missing required field: 'bundle'");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid bundle', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
documentSnapshotFromJSON(db, {
|
|
type: DocumentSnapshot._jsonSchemaVersion,
|
|
bundleSource: 'DocumentSnapshot',
|
|
bundleName: 'test name',
|
|
bundle: 1
|
|
});
|
|
}).to.throw("JSON field 'bundle' must be a string");
|
|
});
|
|
|
|
it('fromJSON() does not throw', () => {
|
|
const db = newTestFirestore();
|
|
const docRef = documentReference('foo/bar');
|
|
const json = docRef.toJSON();
|
|
expect(() => {
|
|
DocumentReference.fromJSON(db, json);
|
|
}).to.not.throw;
|
|
});
|
|
|
|
it('fromJSON() equals original docRef', () => {
|
|
const db = newTestFirestore();
|
|
const docRef = documentReference('foo/bar');
|
|
const json = docRef.toJSON();
|
|
const deserializedDocRef = DocumentReference.fromJSON(db, json);
|
|
expect(docRef.id).to.equal(deserializedDocRef.id);
|
|
expect(docRef.path).to.equal(deserializedDocRef.path);
|
|
expect(docRef.toJSON()).to.deep.equal(deserializedDocRef.toJSON());
|
|
});
|
|
});
|
|
|
|
describe('DocumentSnapshot', () => {
|
|
it('support equality checking with isEqual()', () => {
|
|
expect(
|
|
snapshotEqual(
|
|
documentSnapshot('rooms/foo', { a: 1 }, true),
|
|
documentSnapshot('rooms/foo', { a: 1 }, true)
|
|
)
|
|
).to.be.true;
|
|
expect(
|
|
snapshotEqual(
|
|
documentSnapshot('rooms/foo', null, true),
|
|
documentSnapshot('rooms/foo', null, true)
|
|
)
|
|
).to.be.true;
|
|
// will do both !left.isEqual(right) and !right.isEqual(left).
|
|
expect(
|
|
snapshotEqual(
|
|
documentSnapshot('rooms/foo', { a: 1 }, true),
|
|
documentSnapshot('rooms/foo', null, true)
|
|
)
|
|
).to.be.false;
|
|
expect(
|
|
snapshotEqual(
|
|
documentSnapshot('rooms/foo', { a: 1 }, true),
|
|
documentSnapshot('rooms/bar', { a: 1 }, true)
|
|
)
|
|
).to.be.false;
|
|
expect(
|
|
snapshotEqual(
|
|
documentSnapshot('rooms/foo', { a: 1 }, true),
|
|
documentSnapshot('rooms/bar', { b: 1 }, true)
|
|
)
|
|
).to.be.false;
|
|
expect(
|
|
snapshotEqual(
|
|
documentSnapshot('rooms/foo', { a: 1 }, true),
|
|
documentSnapshot('rooms/bar', { a: 1 }, false)
|
|
)
|
|
).to.be.false;
|
|
});
|
|
|
|
it('JSON.stringify() does not throw', () => {
|
|
JSON.stringify(documentSnapshot('foo/bar', { a: 1 }, true));
|
|
});
|
|
|
|
it('toJSON returns a bundle', () => {
|
|
const snapshotJson = documentSnapshot(
|
|
'foo/bar',
|
|
{ a: 1 },
|
|
/*fromCache=*/ true
|
|
).toJSON();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const json = snapshotJson as any;
|
|
expect(json.bundle).to.exist;
|
|
expect(json.bundle.length).to.be.greaterThan(0);
|
|
});
|
|
|
|
it('toJSON returns a bundle containing NOT_SUPPORTED in non-node environments', () => {
|
|
if (!isNode()) {
|
|
const snapshotJson = documentSnapshot(
|
|
'foo/bar',
|
|
{ a: 1 },
|
|
/*fromCache=*/ true
|
|
).toJSON();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const json = snapshotJson as any;
|
|
expect(json.bundle).to.exist;
|
|
expect(json.bundle).to.equal('NOT SUPPORTED');
|
|
}
|
|
});
|
|
|
|
it('toJSON returns an empty bundle when there are no documents', () => {
|
|
if (isNode()) {
|
|
const snapshotJson = documentSnapshot(
|
|
'foo/bar',
|
|
/*data=*/ null,
|
|
/*fromCache=*/ true
|
|
).toJSON();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const json = snapshotJson as any;
|
|
expect(json.bundle).to.exist;
|
|
expect(json.bundle.length).to.equal(0);
|
|
}
|
|
});
|
|
|
|
it('toJSON throws when there are pending writes', () => {
|
|
expect(() => {
|
|
documentSnapshot(
|
|
'foo/bar',
|
|
{},
|
|
/*fromCache=*/ true,
|
|
/*hasPendingWrites=*/ true
|
|
).toJSON();
|
|
}).to.throw(
|
|
`DocumentSnapshot.toJSON() attempted to serialize a document with pending writes. ` +
|
|
`Await waitForPendingWrites() before invoking toJSON().`
|
|
);
|
|
});
|
|
|
|
it('fromJSON throws when parsing client-side toJSON result', () => {
|
|
if (!isNode()) {
|
|
const docSnap = documentSnapshot(
|
|
'foo/bar',
|
|
{ a: 1 },
|
|
/*fromCache=*/ true
|
|
);
|
|
expect(() => {
|
|
documentSnapshotFromJSON(docSnap._firestore, docSnap.toJSON());
|
|
}).to.throw;
|
|
}
|
|
});
|
|
|
|
it('fromJSON parses toJSON result', () => {
|
|
if (isNode()) {
|
|
const docSnap = documentSnapshot(
|
|
'foo/bar',
|
|
{ a: 1 },
|
|
/*fromCache=*/ true
|
|
);
|
|
expect(() => {
|
|
documentSnapshotFromJSON(docSnap._firestore, docSnap.toJSON());
|
|
}).to.not.throw;
|
|
}
|
|
});
|
|
|
|
it('fromJSON produces valid snapshot data.', () => {
|
|
if (isNode()) {
|
|
const docSnap = documentSnapshot(
|
|
'foo/bar',
|
|
{ a: 1 },
|
|
/*fromCache=*/ true
|
|
);
|
|
const db = firestore();
|
|
const docSnapFromJSON = documentSnapshotFromJSON(db, docSnap.toJSON());
|
|
expect(docSnapFromJSON).to.exist;
|
|
const data = docSnapFromJSON.data();
|
|
expect(docSnapFromJSON).to.not.be.undefined;
|
|
expect(docSnapFromJSON).to.not.be.null;
|
|
if (data) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
expect((data as any).a).to.exist;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
expect((data as any).a).to.equal(1);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Query', () => {
|
|
it('support equality checking with isEqual()', () => {
|
|
expect(queryEqual(query('foo'), query('foo'))).to.be.true;
|
|
expect(queryEqual(query('foo'), query('bar'))).to.be.false;
|
|
});
|
|
|
|
it('JSON.stringify() does not throw', () => {
|
|
JSON.stringify(query('foo'));
|
|
});
|
|
});
|
|
|
|
describe('QuerySnapshot', () => {
|
|
it('support equality checking with isEqual()', () => {
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys(), false, false),
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys(), false, false)
|
|
)
|
|
).to.be.true;
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys(), false, false),
|
|
querySnapshot('bar', {}, { a: { a: 1 } }, keys(), false, false)
|
|
)
|
|
).to.be.false;
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys(), false, false),
|
|
querySnapshot(
|
|
'foo',
|
|
{ b: { b: 1 } },
|
|
{ a: { a: 1 } },
|
|
keys(),
|
|
false,
|
|
false
|
|
)
|
|
)
|
|
).to.be.false;
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys(), false, false),
|
|
querySnapshot('foo', {}, { a: { b: 1 } }, keys(), false, false)
|
|
)
|
|
).to.be.false;
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys('foo/a'), false, false),
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys(), false, false)
|
|
)
|
|
).to.be.false;
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys('foo/a'), false, false),
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys('foo/b'), false, false)
|
|
)
|
|
).to.be.false;
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys('foo/a'), false, false),
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys('foo/a'), true, false)
|
|
)
|
|
).to.be.false;
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys('foo/a'), false, false),
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys('foo/a'), false, true)
|
|
)
|
|
).to.be.false;
|
|
// hasCachedResults should affect querySnapshot equality
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot(
|
|
'foo',
|
|
{},
|
|
{ a: { a: 1 } },
|
|
keys('foo/a'),
|
|
false,
|
|
false,
|
|
true
|
|
),
|
|
querySnapshot(
|
|
'foo',
|
|
{},
|
|
{ a: { a: 1 } },
|
|
keys('foo/a'),
|
|
false,
|
|
false,
|
|
true
|
|
)
|
|
)
|
|
).to.be.true;
|
|
expect(
|
|
snapshotEqual(
|
|
querySnapshot(
|
|
'foo',
|
|
{},
|
|
{ a: { a: 1 } },
|
|
keys('foo/a'),
|
|
false,
|
|
false,
|
|
true
|
|
),
|
|
querySnapshot(
|
|
'foo',
|
|
{},
|
|
{ a: { a: 1 } },
|
|
keys('foo/a'),
|
|
false,
|
|
false,
|
|
false
|
|
)
|
|
)
|
|
).to.be.false;
|
|
});
|
|
|
|
it('JSON.stringify() does not throw', () => {
|
|
JSON.stringify(
|
|
querySnapshot('foo', {}, { a: { a: 1 } }, keys(), false, false)
|
|
);
|
|
});
|
|
|
|
it('toJSON returns a bundle', () => {
|
|
const snapshotJson = querySnapshot(
|
|
'foo',
|
|
{},
|
|
{ a: { a: 1 } },
|
|
keys(), // An empty set of mutaded document keys signifies that there are no pending writes.
|
|
false,
|
|
false
|
|
).toJSON();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const json = snapshotJson as any;
|
|
expect(json.bundle).to.exist;
|
|
expect(json.bundle.length).to.be.greaterThan(0);
|
|
});
|
|
|
|
it('toJSON returns a bundle containing NOT_SUPPORTED in non-node environments', () => {
|
|
if (!isNode()) {
|
|
const snapshotJson = querySnapshot(
|
|
'foo',
|
|
{},
|
|
{ a: { a: 1 } },
|
|
keys(), // An empty set of mutaded document keys signifies that there are no pending writes.
|
|
false,
|
|
false
|
|
).toJSON();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const json = snapshotJson as any;
|
|
expect(json.bundle).to.exist;
|
|
expect(json.bundle).to.equal('NOT SUPPORTED');
|
|
}
|
|
});
|
|
|
|
it('toJSON returns a bundle when there are no documents', () => {
|
|
if (isNode()) {
|
|
const snapshotJson = querySnapshot(
|
|
'foo',
|
|
{},
|
|
{},
|
|
keys(),
|
|
false,
|
|
false
|
|
).toJSON();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const json = snapshotJson as any;
|
|
expect(json.bundle).to.exist;
|
|
expect(json.bundle.length).to.be.greaterThan(0);
|
|
}
|
|
});
|
|
|
|
it('toJSON throws when there are pending writes', () => {
|
|
expect(() =>
|
|
querySnapshot(
|
|
'foo',
|
|
{},
|
|
{ a: { a: 1 } },
|
|
keys('foo/a'), // A non empty set of mutated keys signifies pending writes.
|
|
false,
|
|
false
|
|
).toJSON()
|
|
).to.throw(
|
|
`QuerySnapshot.toJSON() attempted to serialize a document with pending writes. ` +
|
|
`Await waitForPendingWrites() before invoking toJSON().`
|
|
);
|
|
});
|
|
|
|
it('fromJSON() throws with invalid data', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {});
|
|
}).to.throw("JSON missing required field: 'type'");
|
|
});
|
|
|
|
it('fromJSON() throws with missing type data', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {
|
|
bundleSource: 'QuerySnapshot',
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON missing required field: 'type'");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid type data', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {
|
|
type: 1,
|
|
bundleSource: 'QuerySnapshot',
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON field 'type' must be a string");
|
|
});
|
|
|
|
it('fromJSON() throws with missing bundle source data', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {
|
|
type: QuerySnapshot._jsonSchemaVersion,
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON missing required field: 'bundleSource'");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid bundleSource type', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {
|
|
type: QuerySnapshot._jsonSchemaVersion,
|
|
bundleSource: 1,
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON field 'bundleSource' must be a string");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid bundleSource value', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {
|
|
type: QuerySnapshot._jsonSchemaVersion,
|
|
bundleSource: 'DocumentSnapshot',
|
|
bundleName: 'test name',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("Expected 'bundleSource' field to equal 'QuerySnapshot'");
|
|
});
|
|
|
|
it('fromJSON() throws with missing bundleName', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {
|
|
type: QuerySnapshot._jsonSchemaVersion,
|
|
bundleSource: 'QuerySnapshot',
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON missing required field: 'bundleName'");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid bundleName', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {
|
|
type: QuerySnapshot._jsonSchemaVersion,
|
|
bundleSource: 'QuerySnapshot',
|
|
bundleName: 1,
|
|
bundle: 'test bundle'
|
|
});
|
|
}).to.throw("JSON field 'bundleName' must be a string");
|
|
});
|
|
|
|
it('fromJSON() throws with missing bundle field', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {
|
|
type: QuerySnapshot._jsonSchemaVersion,
|
|
bundleSource: 'QuerySnapshot',
|
|
bundleName: 'test name'
|
|
});
|
|
}).to.throw("JSON missing required field: 'bundle'");
|
|
});
|
|
|
|
it('fromJSON() throws with invalid bundle field', () => {
|
|
const db = newTestFirestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, {
|
|
type: QuerySnapshot._jsonSchemaVersion,
|
|
bundleSource: 'QuerySnapshot',
|
|
bundleName: 'test name',
|
|
bundle: 1
|
|
});
|
|
}).to.throw("JSON field 'bundle' must be a string");
|
|
});
|
|
|
|
it('fromJSON does not throw', () => {
|
|
if (isNode()) {
|
|
const snapshot = querySnapshot(
|
|
'foo',
|
|
{},
|
|
{
|
|
a: { a: 1 },
|
|
b: { bar: 2 }
|
|
},
|
|
keys(), // An empty set of mutaded document keys signifies that there are no pending writes.
|
|
false,
|
|
false
|
|
);
|
|
const db = firestore();
|
|
expect(() => {
|
|
querySnapshotFromJSON(db, snapshot.toJSON());
|
|
}).to.not.throw;
|
|
}
|
|
});
|
|
|
|
it('fromJSON produces valid snapshot data', () => {
|
|
if (isNode()) {
|
|
const snapshot = querySnapshot(
|
|
'foo',
|
|
{},
|
|
{
|
|
a: { a: 1 },
|
|
b: { bar: 2 }
|
|
},
|
|
keys(), // An empty set of mutaded document keys signifies that there are no pending writes.
|
|
false,
|
|
false
|
|
);
|
|
const db = firestore();
|
|
const querySnap = querySnapshotFromJSON(db, snapshot.toJSON());
|
|
expect(querySnap).to.exist;
|
|
if (querySnap !== undefined) {
|
|
const docs = querySnap.docs;
|
|
expect(docs).to.not.be.undefined;
|
|
expect(docs).to.not.be.null;
|
|
if (docs) {
|
|
expect(docs.length).to.equal(2);
|
|
if (docs.length === 2) {
|
|
let docData = docs[0].data();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
let data = docData as any;
|
|
expect(data.a).to.exist;
|
|
expect(data.a).to.equal(1);
|
|
|
|
docData = docs[1].data();
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
data = docData as any;
|
|
expect(data.bar).to.exist;
|
|
expect(data.bar).to.equal(2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
it('fromJSON respects query sort order', () => {
|
|
if (isNode()) {
|
|
const q = internalQuery('foo', orderBy('title', 'asc'));
|
|
const snapshot = querySnapshotWithQuery(q, {
|
|
docA: { title: 'C' },
|
|
docB: { title: 'B' },
|
|
docC: { title: 'A' }
|
|
});
|
|
const db = firestore();
|
|
const querySnap = querySnapshotFromJSON(db, snapshot.toJSON());
|
|
expect(querySnap).to.exist;
|
|
const titles = querySnap.docs.map(d => d.data()['title']);
|
|
expect(titles).to.deep.equal(['A', 'B', 'C']);
|
|
}
|
|
});
|
|
|
|
it('fromJSON throws when parsing client-side toJSON result', () => {
|
|
if (!isNode()) {
|
|
const querySnap = querySnapshot(
|
|
'foo',
|
|
{},
|
|
{ a: { a: 1 } },
|
|
keys(), // An empty set of mutaded document keys signifies that there are no pending writes.
|
|
false,
|
|
false
|
|
);
|
|
const json = querySnap.toJSON();
|
|
expect(() => {
|
|
querySnapshotFromJSON(querySnap._firestore, json);
|
|
}).to.throw;
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('SnapshotMetadata', () => {
|
|
it('from DocumentSnapshot support equality checking with isEqual()', () => {
|
|
expect(documentSnapshot('rooms/foo', {}, true).metadata).to.deep.equal(
|
|
documentSnapshot('rooms/foo', {}, true).metadata
|
|
);
|
|
expect(documentSnapshot('rooms/foo', {}, true).metadata).to.not.deep.equal(
|
|
documentSnapshot('rooms/foo', {}, false).metadata
|
|
);
|
|
});
|
|
|
|
it('from QuerySnapshot support equality checking with isEqual()', () => {
|
|
expect(
|
|
querySnapshot('foo', {}, {}, keys('foo/a'), true, false).metadata
|
|
).to.deep.equal(
|
|
querySnapshot('foo', {}, {}, keys('foo/a'), true, false).metadata
|
|
);
|
|
expect(
|
|
querySnapshot('foo', {}, {}, keys('foo/a'), true, false).metadata
|
|
).to.not.deep.equal(
|
|
querySnapshot('foo', {}, {}, keys(), true, false).metadata
|
|
);
|
|
expect(
|
|
querySnapshot('foo', {}, {}, keys('foo/a'), true, false).metadata
|
|
).to.not.deep.equal(
|
|
querySnapshot('foo', {}, {}, keys('foo/a'), false, false).metadata
|
|
);
|
|
expect(
|
|
querySnapshot('foo', {}, {}, keys('foo/a'), true, false).metadata
|
|
).to.not.deep.equal(
|
|
querySnapshot('foo', {}, {}, keys(), false, false).metadata
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Settings', () => {
|
|
it('cannot use mutually exclusive settings together', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
experimentalForceLongPolling: true,
|
|
experimentalAutoDetectLongPolling: true
|
|
})
|
|
).to.throw(
|
|
`experimentalForceLongPolling and experimentalAutoDetectLongPolling cannot be used together.`
|
|
);
|
|
});
|
|
|
|
it('long polling should be in auto-detect mode by default', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.true;
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.false;
|
|
});
|
|
|
|
it('long polling should be in force mode if force=true', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
experimentalForceLongPolling: true
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.false;
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.true;
|
|
});
|
|
|
|
it('long polling should be in auto-detect mode if autoDetect=true', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
experimentalAutoDetectLongPolling: true
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.true;
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.false;
|
|
});
|
|
|
|
it('long polling should be in auto-detect mode if force=false', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
experimentalForceLongPolling: false
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.true;
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.false;
|
|
});
|
|
|
|
it('long polling should be disabled if autoDetect=false', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
experimentalAutoDetectLongPolling: false
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.false;
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.false;
|
|
});
|
|
|
|
it('long polling should be in auto-detect mode if autoDetect=true and force=false', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
experimentalAutoDetectLongPolling: true,
|
|
experimentalForceLongPolling: false
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.true;
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.false;
|
|
});
|
|
|
|
it('long polling should be in force mode if autoDetect=false and force=true', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
experimentalAutoDetectLongPolling: false,
|
|
experimentalForceLongPolling: true
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.false;
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.true;
|
|
});
|
|
|
|
it('long polling should be disabled if autoDetect=false and force=false', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
experimentalAutoDetectLongPolling: false,
|
|
experimentalForceLongPolling: false
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.false;
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.false;
|
|
});
|
|
|
|
it('timeoutSeconds is undefined by default', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(db._getSettings().experimentalLongPollingOptions.timeoutSeconds).to
|
|
.be.undefined;
|
|
});
|
|
|
|
it('timeoutSeconds minimum value is allowed', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({ experimentalLongPollingOptions: { timeoutSeconds: 5 } });
|
|
expect(
|
|
db._getSettings().experimentalLongPollingOptions.timeoutSeconds
|
|
).to.equal(5);
|
|
});
|
|
|
|
it('timeoutSeconds maximum value is allowed', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({ experimentalLongPollingOptions: { timeoutSeconds: 30 } });
|
|
expect(
|
|
db._getSettings().experimentalLongPollingOptions.timeoutSeconds
|
|
).to.equal(30);
|
|
});
|
|
|
|
it('timeoutSeconds typical value is allowed', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({ experimentalLongPollingOptions: { timeoutSeconds: 25 } });
|
|
expect(
|
|
db._getSettings().experimentalLongPollingOptions.timeoutSeconds
|
|
).to.equal(25);
|
|
});
|
|
|
|
it('timeoutSeconds floating point value is allowed', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
experimentalLongPollingOptions: { timeoutSeconds: 12.3456 }
|
|
});
|
|
expect(
|
|
db._getSettings().experimentalLongPollingOptions.timeoutSeconds
|
|
).to.equal(12.3456);
|
|
});
|
|
|
|
it('timeoutSeconds value one less than minimum throws', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({ experimentalLongPollingOptions: { timeoutSeconds: 4 } })
|
|
).to.throw(/invalid.*timeout.*4.*\(.*5.*\)/i);
|
|
});
|
|
|
|
it('timeoutSeconds value one more than maximum throws', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
experimentalLongPollingOptions: { timeoutSeconds: 31 }
|
|
})
|
|
).to.throw(/invalid.*timeout.*31.*\(.*30.*\)/i);
|
|
});
|
|
|
|
it('timeoutSeconds value of 0 throws', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({ experimentalLongPollingOptions: { timeoutSeconds: 0 } })
|
|
).to.throw(/invalid.*timeout.*0.*\(.*5.*\)/i);
|
|
});
|
|
|
|
it('timeoutSeconds value of -0 throws', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
experimentalLongPollingOptions: { timeoutSeconds: -0 }
|
|
})
|
|
).to.throw(/invalid.*timeout.*0.*\(.*5.*\)/i);
|
|
});
|
|
|
|
it('timeoutSeconds value of -1 throws', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
experimentalLongPollingOptions: { timeoutSeconds: -1 }
|
|
})
|
|
).to.throw(/invalid.*timeout.*-1.*\(.*5.*\)/i);
|
|
});
|
|
|
|
it('timeoutSeconds value of -infinity throws', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
experimentalLongPollingOptions: {
|
|
timeoutSeconds: Number.NEGATIVE_INFINITY
|
|
}
|
|
})
|
|
).to.throw(/invalid.*timeout.*-Infinity.*\(.*5.*\)/i);
|
|
});
|
|
|
|
it('timeoutSeconds value of +infinity throws', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
experimentalLongPollingOptions: {
|
|
timeoutSeconds: Number.POSITIVE_INFINITY
|
|
}
|
|
})
|
|
).to.throw(/invalid.*timeout.*Infinity.*\(.*30.*\)/i);
|
|
});
|
|
|
|
it('timeoutSeconds value of NaN throws', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
experimentalLongPollingOptions: { timeoutSeconds: Number.NaN }
|
|
})
|
|
).to.throw(/invalid.*timeout.*NaN/i);
|
|
});
|
|
|
|
it('long polling autoDetect=[something truthy] should be coerced to true', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
experimentalAutoDetectLongPolling: 1 as any
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.true;
|
|
});
|
|
|
|
it('long polling autoDetect=[something falsy] should be coerced to false', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
experimentalAutoDetectLongPolling: 0 as any
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.false;
|
|
});
|
|
|
|
it('long polling autoDetect=null should be coerced to false', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
experimentalAutoDetectLongPolling: null as any
|
|
});
|
|
expect(db._getSettings().experimentalAutoDetectLongPolling).to.be.false;
|
|
});
|
|
|
|
it('long polling force=[something truthy] should be coerced to true', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
experimentalForceLongPolling: 'I am truthy' as any
|
|
});
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.true;
|
|
});
|
|
|
|
it('long polling force=[something falsy] should be coerced to false', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
experimentalForceLongPolling: NaN as any
|
|
});
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.false;
|
|
});
|
|
|
|
it('long polling force=null should be coerced to false', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
experimentalForceLongPolling: null as any
|
|
});
|
|
expect(db._getSettings().experimentalForceLongPolling).to.be.false;
|
|
});
|
|
|
|
it('gets settings from useEmulator', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
connectFirestoreEmulator(db, '127.0.0.1', 9000);
|
|
|
|
expect(db._getSettings().host).to.equal('127.0.0.1:9000');
|
|
expect(db._getSettings().ssl).to.be.false;
|
|
});
|
|
|
|
it('gets privateSettings from useEmulator', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
const emulatorOptions = { mockUserToken: 'test' };
|
|
connectFirestoreEmulator(db, '127.0.0.1', 9000, emulatorOptions);
|
|
|
|
expect(db._getSettings().host).to.exist.and.to.equal('127.0.0.1:9000');
|
|
expect(db._getSettings().ssl).to.exist.and.to.be.false;
|
|
expect(db._getEmulatorOptions()).to.equal(emulatorOptions);
|
|
});
|
|
|
|
it('sets ssl to true if cloud workstation host', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
const emulatorOptions = { mockUserToken: 'test' };
|
|
const workstationHost = 'abc.cloudworkstations.dev';
|
|
connectFirestoreEmulator(db, workstationHost, 9000, emulatorOptions);
|
|
|
|
expect(db._getSettings().host).to.exist.and.to.equal(
|
|
`${workstationHost}:9000`
|
|
);
|
|
expect(db._getSettings().ssl).to.exist.and.to.be.true;
|
|
expect(db._getEmulatorOptions()).to.equal(emulatorOptions);
|
|
});
|
|
|
|
it('prefers host from useEmulator to host from settings', () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
db._setSettings({ host: 'other.host' });
|
|
connectFirestoreEmulator(db, '127.0.0.1', 9000);
|
|
|
|
expect(db._getSettings().host).to.equal('127.0.0.1:9000');
|
|
expect(db._getSettings().ssl).to.be.false;
|
|
});
|
|
|
|
it('sets credentials based on mockUserToken object', async () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
const mockUserToken = { sub: 'foobar' };
|
|
connectFirestoreEmulator(db, '127.0.0.1', 9000, { mockUserToken });
|
|
|
|
const credentials = db._authCredentials;
|
|
expect(credentials).to.be.instanceOf(EmulatorAuthCredentialsProvider);
|
|
const token = await credentials.getToken();
|
|
expect(token!.type).to.eql('OAuth');
|
|
expect(token!.user!.uid).to.eql(mockUserToken.sub);
|
|
});
|
|
|
|
it('sets credentials based on mockUserToken string', async () => {
|
|
// Use a new instance of Firestore in order to configure settings.
|
|
const db = newTestFirestore();
|
|
connectFirestoreEmulator(db, '127.0.0.1', 9000, {
|
|
mockUserToken: 'my-custom-mock-user-token'
|
|
});
|
|
|
|
const credentials = db._authCredentials;
|
|
expect(credentials).to.be.instanceOf(EmulatorAuthCredentialsProvider);
|
|
const token = await credentials.getToken();
|
|
expect(token!.type).to.eql('OAuth');
|
|
expect(token!.user).to.eql(User.MOCK_USER);
|
|
});
|
|
|
|
it('allows setting grpcFlowControlWindow to a positive integer', () => {
|
|
const db = newTestFirestore();
|
|
db._setSettings({
|
|
grpcFlowControlWindow: 512 * 1024
|
|
});
|
|
expect(db._getSettings().grpcFlowControlWindow).to.equal(512 * 1024);
|
|
});
|
|
|
|
it('throws when setting grpcFlowControlWindow to non-positive value', () => {
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
grpcFlowControlWindow: 0
|
|
})
|
|
).to.throw(/grpcFlowControlWindow must be a positive integer/);
|
|
|
|
expect(() =>
|
|
db._setSettings({
|
|
grpcFlowControlWindow: -50
|
|
})
|
|
).to.throw(/grpcFlowControlWindow must be a positive integer/);
|
|
});
|
|
|
|
it('throws when setting grpcFlowControlWindow to a non-integer', () => {
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
grpcFlowControlWindow: 12.5
|
|
})
|
|
).to.throw(/grpcFlowControlWindow must be a positive integer/);
|
|
|
|
expect(() =>
|
|
db._setSettings({
|
|
grpcFlowControlWindow: '100' as unknown as number
|
|
})
|
|
).to.throw(/grpcFlowControlWindow must be a positive integer/);
|
|
});
|
|
|
|
it('throws when setting grpcFlowControlWindow above 2147483647', () => {
|
|
const db = newTestFirestore();
|
|
expect(() =>
|
|
db._setSettings({
|
|
grpcFlowControlWindow: 2147483648
|
|
})
|
|
).to.throw(
|
|
/grpcFlowControlWindow must be a positive integer and cannot exceed 2147483647/
|
|
);
|
|
});
|
|
});
|