Skip to content
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

Fix mocking of gRPC-web and Connect GET requests #100

Merged
merged 10 commits into from
Jan 24, 2024
Merged

Conversation

smaye81
Copy link
Member

@smaye81 smaye81 commented Jan 16, 2024

The GrpcWebTransport expects all request bodies to be an AsyncIterable, but Playwright's APIs for accessing the request body only return JSON or a Buffer. So, when creating a mock router, the handler should look at the content type and if it's gRPC-web, convert the body to an AsyncIterable.

In addition, this fixes mocking GET requests via the Connect protocol.

Finally, this adds some tests to verify the mocks are correctly created for Connect and gRPC-web in both JSON and binary formats and with / without GET support for the Connect protocol.

Copy link
Member

@timostamm timostamm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great in general, but converting the body isn't quite right right.

packages/connect-playwright-example/src/App.tsx Outdated Show resolved Hide resolved
Comment on lines 201 to 204
// gRPC-web expects the body to be an AsyncIterable
if (contentType.startsWith("application/grpc-web")) {
body = createAsyncIterable([body]);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The body property of the type UniversalServerRequest is AsyncIterable<Uint8Array> | JsonValue. We're still passing in other types here. We do not need to special case the gRPC-web content type, only application/json.

Please update let body: any to not use any - this is how this bug slipped in in the first place. I suggest we use let body: UniversalServerRequest["body"].

@@ -184,18 +191,18 @@ async function universalHandlerToRouteResponse({
}) {
const headers = await request.allHeaders();
const abortSignal = new AbortController().signal;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- The Serializable type isn't exposed by Playwright
let body: any;
let body: UniversalServerRequest["body"] = [];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure of the best default value for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[] is not a type error because the body can be pre-parsed JSON, and [] is a valid JSON value.

You are providing the request body [] to the call, if the original request had an empty body. That's no good. To provide an empty request body, create an empty byte stream with createAsyncIterable<Uint8Array>([]).

Let's also add a test for Connect GET, because I'm pretty sure we would crash on it.

@smaye81
Copy link
Member Author

smaye81 commented Jan 22, 2024

@timostamm lmk what you think now. Was able to remove a lot of the eslint ignores also.

@smaye81
Copy link
Member Author

smaye81 commented Jan 23, 2024

@timostamm Round 3. Lmk what you think.

Copy link
Member

@timostamm timostamm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment on lines 206 to 209
// Default body to an empty byte stream
let body: UniversalServerRequest["body"] = createAsyncIterable<Uint8Array>(
[],
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Not crucial here, but you can simply declare the variable without assigning anything, as long as all code paths assign before use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the only issue was there was a code path that didn't assign it (if buffer was null), but I fixed that so that I didn't initialize it. And now the AsyncIterable is only created just-in-time if needed.

@@ -55,6 +62,17 @@ interface Options {
binaryOptions?: Partial<BinaryReadOptions & BinaryWriteOptions>;
}

// Builds a regular expression for matching paths by appending the suffix onto
// base and escaping forward slashes and periods
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +48 to +54
baseURL,
baseURL + "?transport=connect",
baseURL + "?transport=connect&useHttpGet=true",
baseURL + "?transport=connect&format=binary",
baseURL + "?transport=connect&format=binary&useHttpGet=true",
baseURL + "?transport=grpcweb",
baseURL + "?transport=grpcweb&format=json",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice to have coverage against regressions 🎉

@smaye81 smaye81 merged commit 963f530 into main Jan 24, 2024
3 checks passed
@smaye81 smaye81 deleted the sayers/grpcweb branch January 24, 2024 15:56
@smaye81 smaye81 mentioned this pull request Jan 24, 2024
@smaye81 smaye81 changed the title Handle mocking gRPC-web Fix mocking of gRPC-web and Connect GET requests Jan 24, 2024
smaye81 added a commit that referenced this pull request Jan 24, 2024
## What's Fixed
* Fix playwright exports and update to current Connect deps (#101) by
@smaye81
* Fix mocking of gRPC-web and Connect GET requests (#100) by @smaye81

**Full Changelog**:
v0.3.1...v0.3.2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants