-
Notifications
You must be signed in to change notification settings - Fork 944
Creating Stream Tests for the Web Client #260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
a37c4bd
to
a30f63b
Compare
* Fails the test if the expected callback does not match the name of the | ||
* actual callback function. | ||
*/ | ||
awaitCallback(expectedCallback: StreamStatusCallback): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd normally put the public surface area of the class (awaitCallback / verifyNoPendingCallbacks) at the top, but feel free to leave as-is for symmetry with iOS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to top.
import { getDefaultDatabaseInfo } from '../util/helpers'; | ||
import FirestoreError = firestore.FirestoreError; | ||
|
||
type StreamStatusCallback = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps StreamCallbackType
or StreameEvent
? (not all of these are "status" events, and I'd expect an actual "callback" type to be a function, not a string)
I think the slightly strange thing here is that we're including 'on' in the names, which makes them refer to the method you call to register an event callback rather than the event itself. So it's also tempting to drop the 'on's ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, is it worth adding a comment calling out that this is a merging of the watch stream and write stream callback types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed it to StreameEventType, which indeed suggests to remove the 'on' from the type definition.
| 'onClose'; | ||
|
||
class StreamStatusListener implements WatchStreamListener, WriteStreamListener { | ||
private pendingStates: StreamStatusCallback[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'state' seems out-of-place. pendingCallbacks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
private pendingStates: StreamStatusCallback[] = []; | ||
private pendingPromises: Deferred<StreamStatusCallback>[] = []; | ||
|
||
private resolvePending(actualCallback: StreamStatusCallback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you declare the return type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
private pendingPromises: Deferred<StreamStatusCallback>[] = []; | ||
|
||
private resolvePending(actualCallback: StreamStatusCallback) { | ||
let pendingPromise = this.pendingPromises.shift(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Here you're calling shift() and checking for undefined. Below (for pendingStates) you check the length > 0 before calling shift. I prefer the latter, but regardless I'd try to be consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with the length check, but left the intermediate variable since it makes the control flow easier to follow.
return deferred.promise.then(actualCallback => { | ||
expect(actualCallback).to.equal(expectedCallback); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is a bit wonky in that on failure it might throw an exception or might return a failed promise, depending on if we have pendingStates (and that the expectedCallback expectation is repeated). Perhaps do something like:
let promise: Promise<StreamStatusCallback>;
if (this.pendingStates.length > 0) {
promise = Promise.resolve(this.pendingStates.shift());
} else {
const deferred = new Deferred<StreamStatusCallback>();
this.pendingPromises.push(deferred);
promise = deferred.promise;
}
return promise.then(actualCallback => {
expect(actualCallback).to.equal(expectedCallback);
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the code to always return a Promise, and to reject it if the callback type doesn't match.
new EmptyCredentialsProvider(), | ||
serializer | ||
); | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to factor out an initializeDatastore() method to avoid 90% of this repeated code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
}) | ||
.then(() => { | ||
writeStream.stop(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does the web test not verify that attempting to write before handshake throws but works after? If we're going to the effort of porting the tests, we should make sure they're at parity (or at least add a comment for why they're not). :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I totally forgot to port this part :(
}); | ||
} | ||
|
||
asyncIt('can be stopped before handshake', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment as above about not testing we don't get extra onClose event after stop().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above.
}); | ||
} | ||
|
||
asyncIt('can be stopped before handshake', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The iOS test has a comment:
/** Verifies that the watch stream does not issue an onClose callback after a call to stop(). */
and does indeed trigger a GRPC close event (to make sure that our stream doesn't trigger a close event in response). Why aren't we testing that on web?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went back and forth on this just now, but decided to ultimately leave it as is. The handleStreamClose callback on the Web only ever gets called when the stream is still open and asserts that this is the case. To bring it closer to the other platforms, I would have to break this invariant.
If you prefer, I can also assert here that 'handleStreamClose' throws an error at this point. Just making it public for that doesn't seem worth it - especially since it invites other consumers to trip over this assertion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. To make web a more direct port, I think we'd refactor
this.stream.onClose((error: FirestoreError) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Nice withTestDatastore() cleanup.
return Promise.reject( | ||
`Unexpected callback encountered. Expected '${expectedCallback}', but got '${actualCallback}'.` | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have any strong preference, but FYI I think using expect(...).to.equal(...) here was probably fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
watchStream.start(); | ||
|
||
return streamListener.awaitCallback('open').then(() => { | ||
// Stop must not call onClose because the full implementation of the delegate could |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delegate => callback ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// Don't start the handshake. | ||
|
||
// Stop must not call onClose because the full implementation of the delegate could | ||
// attempt to restart the stream in the event it had pending watches. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delagate => callback and while you're here maybe fix "pending watches" to "pending writes" (even though Obj-C is wrong too :-))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
This is a port of firebase/firebase-ios-sdk#391