Skip to content

Commit e71e202

Browse files
Add spec test support for target-scoped resume tokens
1 parent d134c39 commit e71e202

File tree

3 files changed

+78
-28
lines changed

3 files changed

+78
-28
lines changed

packages/firestore/test/unit/specs/listen_spec.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,4 +406,24 @@ describeSpec('Listens:', [], () => {
406406
);
407407
}
408408
);
409+
410+
specTest('Persists resume token sent with target', [], () => {
411+
const query = Query.atPath(path('collection'));
412+
const docA = doc('collection/a', 2000, { key: 'a' });
413+
return spec()
414+
.withGCEnabled(false)
415+
.userListens(query)
416+
.watchAcksFull(query, 1000)
417+
.expectEvents(query, {})
418+
.watchSends({ affects: [query] }, docA)
419+
.watchSnapshots(2000, [query], 'resume-token-2000')
420+
.watchSnapshots(2000)
421+
.expectEvents(query, { added: [docA] })
422+
.userUnlistens(query)
423+
.watchRemoves(query)
424+
.userListens(query, 'resume-token-2000')
425+
.expectEvents(query, { added: [docA], fromCache: true })
426+
.watchAcksFull(query, 3000)
427+
.expectEvents(query, {});
428+
});
409429
});

packages/firestore/test/unit/specs/spec_builder.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,9 +481,20 @@ export class SpecBuilder {
481481
return this;
482482
}
483483

484-
watchSnapshots(version: TestSnapshotVersion): SpecBuilder {
484+
watchSnapshots(
485+
version: TestSnapshotVersion,
486+
targets?: Query[],
487+
resumeToken?: string
488+
): SpecBuilder {
485489
this.assertStep('Watch snapshot requires previous watch step');
486-
this.currentStep!.watchSnapshot = version;
490+
491+
if (this.currentStep!.watchSnapshot !== undefined) {
492+
this.nextStep();
493+
this.currentStep = {};
494+
}
495+
496+
const targetIds = targets && targets.map(query => this.getTargetId(query));
497+
this.currentStep!.watchSnapshot = { version, targetIds, resumeToken };
487498
return this;
488499
}
489500

packages/firestore/test/unit/specs/spec_test_runner.ts

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,9 @@ abstract class TestRunner {
482482
return this.doRestart();
483483
} else if ('changeUser' in step) {
484484
return this.doChangeUser(step.changeUser!);
485+
} else if ('watchSnapshot' in step) {
486+
// Handle a single `watchSnapshot` event without any other watch change
487+
return this.doWatchSnapshot(step.watchSnapshot!);
485488
} else {
486489
return fail('Unknown step: ' + JSON.stringify(step));
487490
}
@@ -556,7 +559,7 @@ abstract class TestRunner {
556559

557560
private doWatchAck(
558561
ackedTargets: SpecWatchAck,
559-
watchSnapshot?: SpecSnapshotVersion
562+
watchSnapshot?: SpecWatchSnapshot
560563
): Promise<void> {
561564
const change = new WatchTargetChange(
562565
WatchTargetChangeState.Added,
@@ -567,7 +570,7 @@ abstract class TestRunner {
567570

568571
private doWatchCurrent(
569572
currentTargets: SpecWatchCurrent,
570-
watchSnapshot?: SpecSnapshotVersion
573+
watchSnapshot?: SpecWatchSnapshot
571574
): Promise<void> {
572575
const targets = currentTargets[0];
573576
const resumeToken = currentTargets[1] as ProtoByteString;
@@ -581,7 +584,7 @@ abstract class TestRunner {
581584

582585
private doWatchReset(
583586
targetIds: SpecWatchReset,
584-
watchSnapshot?: SpecSnapshotVersion
587+
watchSnapshot?: SpecWatchSnapshot
585588
): Promise<void> {
586589
const change = new WatchTargetChange(
587590
WatchTargetChangeState.Reset,
@@ -592,7 +595,7 @@ abstract class TestRunner {
592595

593596
private doWatchRemove(
594597
removed: SpecWatchRemove,
595-
watchSnapshot?: SpecSnapshotVersion
598+
watchSnapshot?: SpecWatchSnapshot
596599
): Promise<void> {
597600
const cause =
598601
removed.cause &&
@@ -624,7 +627,7 @@ abstract class TestRunner {
624627

625628
private doWatchEntity(
626629
watchEntity: SpecWatchEntity,
627-
watchSnapshot?: SpecSnapshotVersion
630+
watchSnapshot?: SpecWatchSnapshot
628631
): Promise<void> {
629632
if (watchEntity.docs) {
630633
assert(
@@ -672,7 +675,7 @@ abstract class TestRunner {
672675

673676
private doWatchFilter(
674677
watchFilter: SpecWatchFilter,
675-
watchSnapshot?: SpecSnapshotVersion
678+
watchSnapshot?: SpecWatchSnapshot
676679
): Promise<void> {
677680
const targetIds: TargetId[] = watchFilter[0];
678681
assert(
@@ -685,27 +688,39 @@ abstract class TestRunner {
685688
return this.doWatchEvent(change, watchSnapshot);
686689
}
687690

688-
private doWatchEvent(
689-
watchChange: WatchChange,
690-
watchSnapshot?: SpecSnapshotVersion
691-
): Promise<void> {
692-
let protoJSON = this.serializer.toTestWatchChange(watchChange);
693-
this.connection.watchStream!.callOnMessage(protoJSON);
694-
691+
private doWatchSnapshot(watchSnapshot: SpecWatchSnapshot): Promise<void> {
695692
// The client will only respond to watchSnapshots if they are on a target
696693
// change with an empty set of target IDs. So we should be sure to send a
697694
// separate event. TODO(klimt): We should make the spec tests behave more
698695
// like the backend. Alternatively, we could hook in to the system at a
699696
// level higher than the stream.
700-
const snapshot =
701-
watchSnapshot !== undefined ? version(watchSnapshot) : undefined;
702-
if (snapshot) {
703-
protoJSON = {
704-
targetChange: {
705-
readTime: this.serializer.toVersion(snapshot)
706-
}
707-
};
708-
this.connection.watchStream!.callOnMessage(protoJSON);
697+
const protoJSON: api.ListenResponse = {
698+
targetChange: {
699+
readTime: this.serializer.toVersion(version(watchSnapshot.version))
700+
}
701+
};
702+
if (watchSnapshot.resumeToken) {
703+
protoJSON.targetChange.resumeToken = watchSnapshot.resumeToken;
704+
}
705+
if (watchSnapshot.targetIds) {
706+
protoJSON.targetChange.targetIds = watchSnapshot.targetIds;
707+
}
708+
this.connection.watchStream!.callOnMessage(protoJSON);
709+
710+
// Put a no-op in the queue so that we know when any outstanding RemoteStore
711+
// writes on the network are complete.
712+
return this.queue.enqueue(async () => {});
713+
}
714+
715+
private async doWatchEvent(
716+
watchChange: WatchChange,
717+
watchSnapshot?: SpecWatchSnapshot
718+
): Promise<void> {
719+
const protoJSON = this.serializer.toTestWatchChange(watchChange);
720+
this.connection.watchStream!.callOnMessage(protoJSON);
721+
722+
if (watchSnapshot) {
723+
await this.doWatchSnapshot(watchSnapshot);
709724
}
710725
// Put a no-op in the queue so that we know when any outstanding RemoteStore
711726
// writes on the network are complete.
@@ -1109,7 +1124,7 @@ export interface SpecStep {
11091124
* Optional snapshot version that can be additionally specified on any other
11101125
* watch event
11111126
*/
1112-
watchSnapshot?: SpecSnapshotVersion;
1127+
watchSnapshot?: SpecWatchSnapshot;
11131128
/** A step that the watch stream restarts. */
11141129
watchStreamClose?: SpecWatchStreamClose;
11151130

@@ -1183,7 +1198,11 @@ export type SpecWatchRemove = {
11831198
cause?: SpecError;
11841199
};
11851200

1186-
export type SpecSnapshotVersion = TestSnapshotVersion;
1201+
export type SpecWatchSnapshot = {
1202+
version: TestSnapshotVersion;
1203+
targetIds: TargetId[];
1204+
resumeToken?: string;
1205+
};
11871206

11881207
export type SpecWatchStreamClose = {
11891208
error: SpecError;
@@ -1192,7 +1211,7 @@ export type SpecWatchStreamClose = {
11921211

11931212
export type SpecWriteAck = {
11941213
/** The version the backend uses to ack the write. */
1195-
version: SpecSnapshotVersion;
1214+
version: TestSnapshotVersion;
11961215
/** Whether the ack is expected to generate a user callback. */
11971216
expectUserCallback: boolean;
11981217
};
@@ -1257,7 +1276,7 @@ export interface SpecQuery {
12571276
*/
12581277
export type SpecDocument = [
12591278
string,
1260-
SpecSnapshotVersion,
1279+
TestSnapshotVersion,
12611280
JsonObject<AnyJs> | null
12621281
];
12631282

0 commit comments

Comments
 (0)