diff --git a/packages/firestore/test/integration/api/aggregation.test.ts b/packages/firestore/test/integration/api/aggregation.test.ts index 4a5804e0bed..9dedef43090 100644 --- a/packages/firestore/test/integration/api/aggregation.test.ts +++ b/packages/firestore/test/integration/api/aggregation.test.ts @@ -18,33 +18,112 @@ import { expect } from 'chai'; import { + collection, + collectionGroup, countQuery, + doc, disableNetwork, getAggregateFromServerDirect, - query + query, + terminate, + where, + writeBatch } from '../util/firebase_export'; -import { apiDescribe, withTestCollection } from '../util/helpers'; +import { + apiDescribe, + postConverter, + withEmptyTestCollection, + withTestCollection, + withTestDb +} from '../util/helpers'; apiDescribe('Aggregation query', (persistence: boolean) => { it('can run count query getAggregateFromServerDirect', () => { const testDocs = { - a: { k: 'a', sort: 1 }, - b: { k: 'b', sort: 2 }, - c: { k: 'c', sort: 2 } + a: { author: 'authorA', title: 'titleA' }, + b: { author: 'authorB', title: 'titleB' } }; return withTestCollection(persistence, testDocs, async coll => { + const countQuery_ = countQuery(coll); + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.getCount()).to.equal(2); + }); + }); + + it('aggregateQuery.query equals to original query', () => { + return withEmptyTestCollection(persistence, async coll => { const query_ = query(coll); + const aggregateQuery_ = countQuery(query_); + expect(aggregateQuery_.query).to.be.equal(query_); + }); + }); + + it('aggregateQuerySnapshot.query equals to aggregateQuery', () => { + return withEmptyTestCollection(persistence, async coll => { + const aggregateQuery_ = countQuery(coll); + const snapshot = await getAggregateFromServerDirect(aggregateQuery_); + expect(snapshot.query).to.be.equal(aggregateQuery_); + }); + }); + + it('aggregate query supports withConverter', () => { + const testDocs = { + a: { author: 'authorA', title: 'titleA' }, + b: { author: 'authorB', title: 'titleB' } + }; + return withTestCollection(persistence, testDocs, async coll => { + const query_ = query( + coll, + where('author', '==', 'authorA') + ).withConverter(postConverter); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); - expect(snapshot.getCount()).to.equal(3); + expect(snapshot.getCount()).to.equal(1); + }); + }); + + it('aggregate query supports collection groups', () => { + return withTestDb(persistence, async db => { + const collectionGroupId = doc(collection(db, 'aggregateQueryTest')).id; + const docPaths = [ + `${collectionGroupId}/cg-doc1`, + `abc/123/${collectionGroupId}/cg-doc2`, + `zzz${collectionGroupId}/cg-doc3`, + `abc/123/zzz${collectionGroupId}/cg-doc4`, + `abc/123/zzz/${collectionGroupId}` + ]; + const batch = writeBatch(db); + for (const docPath of docPaths) { + batch.set(doc(db, docPath), { x: 1 }); + } + await batch.commit(); + const countQuery_ = countQuery(collectionGroup(db, collectionGroupId)); + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.getCount()).to.equal(2); + }); + }); + + it('aggregate query fails if firestore is terminated', () => { + return withEmptyTestCollection(persistence, async (coll, firestore) => { + await terminate(firestore); + const countQuery_ = countQuery(coll); + expect(() => getAggregateFromServerDirect(countQuery_)).to.throw( + 'The client has already been terminated.' + ); + }); + }); + + it("terminate doesn't crash when there is aggregate query in flight", () => { + return withEmptyTestCollection(persistence, async (coll, firestore) => { + const countQuery_ = countQuery(coll); + void getAggregateFromServerDirect(countQuery_); + await terminate(firestore); }); }); it('getAggregateFromServerDirect fails if user is offline', () => { - const testDocs = {}; - return withTestCollection( + return withEmptyTestCollection( persistence, - testDocs, async (collection, firestore) => { await disableNetwork(firestore); const countQuery_ = countQuery(collection); diff --git a/packages/firestore/test/integration/util/helpers.ts b/packages/firestore/test/integration/util/helpers.ts index 0a8dd3a5108..6a653445945 100644 --- a/packages/firestore/test/integration/util/helpers.ts +++ b/packages/firestore/test/integration/util/helpers.ts @@ -31,7 +31,8 @@ import { setDoc, PrivateSettings, SnapshotListenOptions, - newTestFirestore + newTestFirestore, + QueryDocumentSnapshot } from './firebase_export'; import { ALT_PROJECT_ID, @@ -254,6 +255,13 @@ export function withTestCollection( return withTestCollectionSettings(persistence, DEFAULT_SETTINGS, docs, fn); } +export function withEmptyTestCollection( + persistence: boolean, + fn: (collection: CollectionReference, db: Firestore) => Promise +): Promise { + return withTestCollection(persistence, {}, fn); +} + // TODO(mikelehen): Once we wipe the database between tests, we can probably // return the same collection every time. export function withTestCollectionSettings( @@ -280,3 +288,24 @@ export function withTestCollectionSettings( } ); } + +export class Post { + constructor( + readonly title: string, + readonly author: string, + readonly ref: DocumentReference | null = null + ) {} + byline(): string { + return this.title + ', by ' + this.author; + } +} + +export const postConverter = { + toFirestore(post: Post): DocumentData { + return { title: post.title, author: post.author }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): Post { + const data = snapshot.data(); + return new Post(data.title, data.author, snapshot.ref); + } +}; diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index a439fe71e8d..8af92d36a19 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -2059,7 +2059,7 @@ describe('countQuery()', () => { it('empty test collection count', () => { return withTestCollection(async coll => { - const countQuery_ = countQuery(query(coll)); + const countQuery_ = countQuery(coll); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(0); }); @@ -2071,8 +2071,8 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const countQuery_ = countQuery(query(collection)); + return withTestCollectionAndInitialData(testDocs, async coll => { + const countQuery_ = countQuery(coll); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(3); }); @@ -2084,8 +2084,8 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, where('author', '==', 'authorA')); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query_ = query(coll, where('author', '==', 'authorA')); const countQuery_ = countQuery(query_); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(2); @@ -2098,12 +2098,8 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query( - collection, - where('author', '==', 'authorA'), - limit(1) - ); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query_ = query(coll, where('author', '==', 'authorA'), limit(1)); const countQuery_ = countQuery(query_); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(1); @@ -2116,12 +2112,8 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query( - collection, - where('author', '==', 'authorA'), - limit(3) - ); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query_ = query(coll, where('author', '==', 'authorA'), limit(3)); const countQuery_ = countQuery(query_); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(2); @@ -2135,8 +2127,8 @@ describe('countQuery()', () => { { author: 'authorB', title: null }, { author: 'authorB' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, orderBy('title')); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query_ = query(coll, orderBy('title')); const countQuery_ = countQuery(query_); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(3); @@ -2150,8 +2142,8 @@ describe('countQuery()', () => { { id: 2, author: 'authorB', title: 'titleC' }, { id: null, author: 'authorB', title: 'titleD' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, orderBy('id'), startAt(2)); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query_ = query(coll, orderBy('id'), startAt(2)); const countQuery_ = countQuery(query_); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(2); @@ -2165,8 +2157,8 @@ describe('countQuery()', () => { { id: 2, author: 'authorB', title: 'titleC' }, { id: null, author: 'authorB', title: 'titleD' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, orderBy('id'), startAfter(2)); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query_ = query(coll, orderBy('id'), startAfter(2)); const countQuery_ = countQuery(query_); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(1); @@ -2180,8 +2172,8 @@ describe('countQuery()', () => { { id: 2, author: 'authorB', title: 'titleC' }, { id: null, author: 'authorB', title: 'titleD' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, orderBy('id'), startAt(1), endAt(2)); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query_ = query(coll, orderBy('id'), startAt(1), endAt(2)); const countQuery_ = countQuery(query_); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(2); @@ -2195,8 +2187,8 @@ describe('countQuery()', () => { { id: 2, author: 'authorB', title: 'titleC' }, { id: null, author: 'authorB', title: 'titleD' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, orderBy('id'), startAt(1), endBefore(2)); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query_ = query(coll, orderBy('id'), startAt(1), endBefore(2)); const countQuery_ = countQuery(query_); const snapshot = await getAggregate(countQuery_); expect(snapshot.getCount()).to.equal(1); @@ -2209,9 +2201,9 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { + return withTestCollectionAndInitialData(testDocs, async coll => { const query_ = query( - collection, + coll, where('author', '==', 'authorA') ).withConverter(postConverter); const countQuery_ = countQuery(query_); @@ -2247,9 +2239,9 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query1 = query(collection, where('author', '==', 'authorA')); - const query2 = query(collection, where('author', '==', 'authorA')); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const query2 = query(coll, where('author', '==', 'authorA')); const countQuery1 = countQuery(query1); const countQuery2 = countQuery(query2); expect(aggregateQueryEqual(countQuery1, countQuery2)).to.be.true; @@ -2262,9 +2254,9 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query1 = query(collection, where('author', '==', 'authorA')); - const query2 = query(collection, where('author', '==', 'authorB')); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const query2 = query(coll, where('author', '==', 'authorB')); const countQuery1 = countQuery(query1); const countQuery2 = countQuery(query2); expect(aggregateQueryEqual(countQuery1, countQuery2)).to.be.false; @@ -2277,9 +2269,9 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query1 = query(collection, where('author', '==', 'authorA')); - const query2 = query(collection, where('author', '==', 'authorA')); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const query2 = query(coll, where('author', '==', 'authorA')); const countQuery1A = countQuery(query1); const countQuery1B = countQuery(query1); const countQuery2 = countQuery(query2); @@ -2297,9 +2289,9 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const query1 = query(collection, where('author', '==', 'authorA')); - const query2 = query(collection, where('author', '==', 'authorB')); + return withTestCollectionAndInitialData(testDocs, async coll => { + const query1 = query(coll, where('author', '==', 'authorA')); + const query2 = query(coll, where('author', '==', 'authorB')); const countQuery1 = countQuery(query1); const countQuery2 = countQuery(query2); const snapshot1 = await getAggregate(countQuery1); @@ -2309,9 +2301,9 @@ describe('countQuery()', () => { }); it('count query on a terminated Firestore', () => { - return withTestCollection(async collection => { - await terminate(collection.firestore); - const countQuery_ = countQuery(query(collection)); + return withTestCollection(async coll => { + await terminate(coll.firestore); + const countQuery_ = countQuery(coll); expect(() => getAggregate(countQuery_)).to.throw( 'The client has already been terminated.' ); @@ -2324,10 +2316,10 @@ describe('countQuery()', () => { { author: 'authorA', title: 'titleB' }, { author: 'authorB', title: 'titleC' } ]; - return withTestCollectionAndInitialData(testDocs, async collection => { - const countQuery_ = countQuery(query(collection)); + return withTestCollectionAndInitialData(testDocs, async coll => { + const countQuery_ = countQuery(coll); const promise = getAggregate(countQuery_); - await terminate(collection.firestore); + await terminate(coll.firestore); const snapshot = await promise; expect(snapshot.getCount()).to.equal(3); });