Skip to content

Commit 45688b0

Browse files
committed
Testing for pipeline options
1 parent d66ddb0 commit 45688b0

File tree

5 files changed

+232
-36
lines changed

5 files changed

+232
-36
lines changed

packages/firestore/src/api/pipeline_impl.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,27 @@
1717

1818
import { Pipeline } from '../api/pipeline';
1919
import { firestoreClientExecutePipeline } from '../core/firestore_client';
20+
import {
21+
StructuredPipeline,
22+
StructuredPipelineOptions
23+
} from '../core/structured_pipeline';
2024
import { Pipeline as LitePipeline } from '../lite-api/pipeline';
2125
import { PipelineResult, PipelineSnapshot } from '../lite-api/pipeline-result';
2226
import { PipelineSource } from '../lite-api/pipeline-source';
27+
import { PipelineOptions } from '../lite-api/pipeline_settings';
2328
import { Stage } from '../lite-api/stage';
2429
import {
2530
newUserDataReader,
2631
parseData,
2732
UserDataReader,
2833
UserDataSource
2934
} from '../lite-api/user_data_reader';
35+
import { ApiClientObjectMap, Value } from '../protos/firestore_proto_api';
3036
import { cast } from '../util/input_validation';
3137

3238
import { ensureFirestoreConfigured, Firestore } from './database';
3339
import { DocumentReference } from './reference';
3440
import { ExpUserDataWriter } from './user_data_writer';
35-
import { PipelineOptions } from '../lite-api/pipeline_settings';
36-
import {
37-
StructuredPipeline,
38-
StructuredPipelineOptions
39-
} from '../core/structured_pipeline';
40-
import { ApiClientObjectMap, Value } from '../protos/firestore_proto_api';
4141

4242
declare module './database' {
4343
interface Firestore {
@@ -84,16 +84,16 @@ export function execute(options: PipelineOptions): Promise<PipelineSnapshot>;
8484
export function execute(
8585
pipelineOrOptions: LitePipeline | PipelineOptions
8686
): Promise<PipelineSnapshot> {
87-
let pipeline: LitePipeline =
87+
const pipeline: LitePipeline =
8888
pipelineOrOptions instanceof LitePipeline
8989
? pipelineOrOptions
9090
: pipelineOrOptions.pipeline;
91-
let options: StructuredPipelineOptions = !(
91+
const options: StructuredPipelineOptions = !(
9292
pipelineOrOptions instanceof LitePipeline
9393
)
9494
? pipelineOrOptions
9595
: {};
96-
let genericOptions: { [name: string]: unknown } =
96+
const genericOptions: { [name: string]: unknown } =
9797
(pipelineOrOptions as PipelineOptions).genericOptions ?? {};
9898

9999
const firestore = cast(pipeline._db, Firestore);
@@ -107,7 +107,7 @@ export function execute(
107107
const optionsOverride: ApiClientObjectMap<Value> =
108108
parseData(genericOptions, context)?.mapValue?.fields ?? {};
109109

110-
let structuredPipeline: StructuredPipeline = new StructuredPipeline(
110+
const structuredPipeline: StructuredPipeline = new StructuredPipeline(
111111
pipeline,
112112
options,
113113
optionsOverride

packages/firestore/src/core/firestore_client.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
CredentialsProvider
2424
} from '../api/credentials';
2525
import { User } from '../auth/user';
26-
import { Pipeline } from '../lite-api/pipeline';
2726
import { LocalStore } from '../local/local_store';
2827
import {
2928
localStoreConfigureFieldIndexes,
@@ -87,6 +86,7 @@ import {
8786
removeSnapshotsInSyncListener
8887
} from './event_manager';
8988
import { newQueryForPath, Query } from './query';
89+
import { StructuredPipeline } from './structured_pipeline';
9090
import { SyncEngine } from './sync_engine';
9191
import {
9292
syncEngineListen,
@@ -102,7 +102,6 @@ import { TransactionOptions } from './transaction_options';
102102
import { TransactionRunner } from './transaction_runner';
103103
import { View } from './view';
104104
import { ViewSnapshot } from './view_snapshot';
105-
import { StructuredPipeline } from './structured_pipeline';
106105

107106
const LOG_TAG = 'FirestoreClient';
108107
export const MAX_CONCURRENT_LIMBO_RESOLUTIONS = 100;

packages/firestore/src/core/structured_pipeline.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,15 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { ObjectValue } from '../model/object_value';
19+
import { FieldPath } from '../model/path';
1820
import {
1921
StructuredPipeline as StructuredPipelineProto,
2022
Pipeline as PipelineProto,
2123
ApiClientObjectMap,
2224
Value
2325
} from '../protos/firestore_proto_api';
24-
2526
import { JsonProtoSerializer, ProtoSerializable } from '../remote/serializer';
26-
import { ObjectValue } from '../model/object_value';
27-
import { FieldPath } from '../model/path';
2827
import { mapToArray } from '../util/obj';
2928

3029
export interface StructuredPipelineOptions {
@@ -47,7 +46,7 @@ export class StructuredPipeline
4746
_getKnownOptions(): ObjectValue {
4847
const options: ObjectValue = ObjectValue.empty();
4948

50-
/** SERIALIZE KNOWN OPTIONS **/
49+
// SERIALIZE KNOWN OPTIONS
5150
if (typeof this.options.indexMode === 'string') {
5251
options.set(FieldPath.fromServerFormat('index_mode'), {
5352
stringValue: this.options.indexMode
@@ -60,7 +59,7 @@ export class StructuredPipeline
6059
private getOptionsProto(): ApiClientObjectMap<Value> {
6160
const options: ObjectValue = this._getKnownOptions();
6261

63-
/** APPLY OPTIONS OVERRIDES **/
62+
// APPLY OPTIONS OVERRIDES
6463
const optionsMap = new Map(
6564
mapToArray(this.optionsOverride, (value, key) => [
6665
FieldPath.fromServerFormat(key),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { expect } from 'chai';
19+
import * as sinon from 'sinon';
20+
21+
import { Timestamp } from '../../../src';
22+
import { Firestore } from '../../../src/api/database';
23+
import { execute } from '../../../src/api/pipeline_impl';
24+
import {
25+
MemoryOfflineComponentProvider,
26+
OnlineComponentProvider
27+
} from '../../../src/core/component_provider';
28+
import {
29+
ExecutePipelineRequest as ProtoExecutePipelineRequest,
30+
ExecutePipelineResponse as ProtoExecutePipelineResponse
31+
} from '../../../src/protos/firestore_proto_api';
32+
import { newTestFirestore } from '../../util/api_helpers';
33+
34+
const FIRST_CALL = 0;
35+
const EXECUTE_PIPELINE_REQUEST = 3;
36+
37+
function fakePipelineResponse(
38+
firestore: Firestore,
39+
response?: ProtoExecutePipelineResponse[]
40+
): sinon.SinonSpy {
41+
response = response ?? [
42+
{
43+
executionTime: Timestamp.now().toDate().toISOString(),
44+
results: []
45+
}
46+
];
47+
const fake = sinon.fake.resolves(response);
48+
49+
firestore._componentsProvider = {
50+
_offline: {
51+
build: () => new MemoryOfflineComponentProvider()
52+
},
53+
_online: {
54+
build: () => {
55+
const provider = new OnlineComponentProvider();
56+
const ogCreateDatastore = provider.createDatastore.bind(provider);
57+
provider.createDatastore = config => {
58+
const datastore = ogCreateDatastore(config);
59+
// @ts-ignore
60+
datastore.invokeStreamingRPC = fake;
61+
return datastore;
62+
};
63+
return provider;
64+
}
65+
}
66+
};
67+
68+
return fake;
69+
}
70+
71+
describe('execute(Pipeline|PipelineOptions)', () => {
72+
it('returns execution time with empty results', async () => {
73+
const firestore = newTestFirestore();
74+
75+
const executeTime = Timestamp.now();
76+
const spy = fakePipelineResponse(firestore, [
77+
{
78+
executionTime: executeTime.toDate().toISOString(),
79+
results: []
80+
}
81+
]);
82+
83+
const pipelineSnapshot = await execute(
84+
firestore.pipeline().collection('foo')
85+
);
86+
87+
expect(pipelineSnapshot.results.length).to.equal(0);
88+
expect(spy.calledOnce);
89+
90+
expect(pipelineSnapshot.executionTime.toJSON()).to.deep.equal(
91+
executeTime.toJSON()
92+
);
93+
});
94+
95+
it('serializes the pipeline', async () => {
96+
const firestore = newTestFirestore();
97+
const spy = fakePipelineResponse(firestore);
98+
99+
await execute({
100+
pipeline: firestore.pipeline().collection('foo')
101+
});
102+
103+
const executePipelineRequest: ProtoExecutePipelineRequest = {
104+
database: 'projects/new-project/databases/(default)',
105+
structuredPipeline: {
106+
'options': {},
107+
'pipeline': {
108+
'stages': [
109+
{
110+
'args': [
111+
{
112+
'referenceValue': '/foo'
113+
}
114+
],
115+
'name': 'collection'
116+
}
117+
]
118+
}
119+
}
120+
};
121+
expect(spy.args[FIRST_CALL][EXECUTE_PIPELINE_REQUEST]).to.deep.equal(
122+
executePipelineRequest
123+
);
124+
});
125+
126+
it('serializes the pipeline options', async () => {
127+
const firestore = newTestFirestore();
128+
const spy = fakePipelineResponse(firestore);
129+
130+
await execute({
131+
pipeline: firestore.pipeline().collection('foo'),
132+
indexMode: 'recommended'
133+
});
134+
135+
const executePipelineRequest: ProtoExecutePipelineRequest = {
136+
database: 'projects/new-project/databases/(default)',
137+
structuredPipeline: {
138+
'options': {
139+
'index_mode': {
140+
'stringValue': 'recommended'
141+
}
142+
},
143+
'pipeline': {
144+
'stages': [
145+
{
146+
'args': [
147+
{
148+
'referenceValue': '/foo'
149+
}
150+
],
151+
'name': 'collection'
152+
}
153+
]
154+
}
155+
}
156+
};
157+
expect(spy.args[FIRST_CALL][EXECUTE_PIPELINE_REQUEST]).to.deep.equal(
158+
executePipelineRequest
159+
);
160+
});
161+
162+
it('serializes the pipeline generic options', async () => {
163+
const firestore = newTestFirestore();
164+
const spy = fakePipelineResponse(firestore);
165+
166+
await execute({
167+
pipeline: firestore.pipeline().collection('foo'),
168+
genericOptions: {
169+
'foo': 'bar'
170+
}
171+
});
172+
173+
const executePipelineRequest: ProtoExecutePipelineRequest = {
174+
database: 'projects/new-project/databases/(default)',
175+
structuredPipeline: {
176+
'options': {
177+
'foo': {
178+
'stringValue': 'bar'
179+
}
180+
},
181+
'pipeline': {
182+
'stages': [
183+
{
184+
'args': [
185+
{
186+
'referenceValue': '/foo'
187+
}
188+
],
189+
'name': 'collection'
190+
}
191+
]
192+
}
193+
}
194+
};
195+
expect(spy.args[FIRST_CALL][EXECUTE_PIPELINE_REQUEST]).to.deep.equal(
196+
executePipelineRequest
197+
);
198+
});
199+
});

0 commit comments

Comments
 (0)