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

feat: Migrate to undici MockClient #79

Closed

Conversation

oliversalzburg
Copy link

@oliversalzburg oliversalzburg commented Oct 27, 2023

Breaking change!

  • Internally uses undici's MockAgent, instead of nock to mock requests.
  • nock is still used for data-matching to retain backwards compatibility.
  • Tests now use fetch instead of axios.

Requires node>=18 and @octokit/rest>=20

Closes #77

Breaking change!

- Internally uses `undici`'s `MockAgent`, instead of `nock` to mock requests.
- `nock` is still used for data-matching to retain backwards
  compatibility.
- Tests now use `fetch` instead of `axios`.

Requires node>=18 and @octokit/rest>=20

Closes kiegroup#77
@shubhbapna
Copy link
Collaborator

Thank you for your PR! I will take a look at the changes over this week

@shubhbapna shubhbapna self-requested a review October 30, 2023 19:55
@oliversalzburg
Copy link
Author

@shubhbapna Please let me know if the chance to have this merged is low. I can also transition my project to my fork.

Copy link
Collaborator

@shubhbapna shubhbapna left a comment

Choose a reason for hiding this comment

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

So sorry for the delay! It slipped through the cracks for me.

@@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 20
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would like to support node 18 as well, so probably change it to matrix and test for both? Same for all the other workflow files

Copy link
Author

Choose a reason for hiding this comment

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

Sounds reasonable.

Copy link
Author

Choose a reason for hiding this comment

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

Wait, this is the publish job. The matrix is already in pull_request 😄 I'll add 18 there.

src/moctokit/moctokit.ts Outdated Show resolved Hide resolved
@@ -15,14 +15,14 @@ test("with default base url", async () => {
repo: "project",
});
expect(data1.status).toBe(200);
expect(data1.data).toStrictEqual({ full_name: "it definitely worked" });
expect(data1.data).toEqual({ full_name: "it definitely worked" });
Copy link
Collaborator

Choose a reason for hiding this comment

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

why was this change needed?

Copy link
Author

Choose a reason for hiding this comment

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

I guess it wasn't. It must have been an oversight when rewriting the tests.

@@ -42,21 +42,23 @@ async function generateEndpointRequest() {

newEndpointRequest[scope][
endpoint.id
] = `new MoctokitRequestMocker<"${path}", "${method}">(baseUrl, endpoints["${scope}"]["${endpoint.id}"], allowUnmocked).request`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

why was allowUnmocked removed?

Copy link
Author

Choose a reason for hiding this comment

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

This behavior is controlled globally through https://undici.nodejs.org/#/docs/api/MockAgent?id=mockagentdisablenetconnect in undici.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we keep such that the user can decide the behaviour? So if the user passes allowUnmocked as false then we call MockAgent.disableNetConnect() otherwise we do nothing

Copy link
Author

Choose a reason for hiding this comment

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

I'm doing that in EndpointMocker actually 😊

Comment on lines +19 to +30
if (process.env.JEST_WORKER_ID !== undefined) {
// @ts-expect-error This seems impossible to type correctly.
globalThis.fetch = (
input: NodeJS.fetch.RequestInfo,
init?: NodeJS.fetch.RequestInit | undefined
) =>
fetch(input, {
dispatcher: this.agent,
...(init as undiciRequestInit),
});
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you explain why we need this? I think this is more of a jest issue where the jest globals differ from node globals: jestjs/jest#2549. So we could leave the workaround for this user where, the user has to configure the globals correctly

Copy link
Author

Choose a reason for hiding this comment

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

It could probably be moved to the test suites themselves. Of course, all consumers of this package will have to do that too for all their test suites, if they use Jest. Could be painful. Or maybe I'm missing a better solution.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, lets keep it for now

path: requestPath => this.pathHandler(requestPath),
method: this.method.toUpperCase(),
body: requestBody => this.bodyHandler(requestBody),
query: this.query,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wasn't able to find the docs on this, but does undici match queries based on regex as well?

Copy link
Author

Choose a reason for hiding this comment

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

Queries are matched as part of the path. See further comments on this below.

@@ -1,18 +1,18 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios from "axios";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why was axios removed? I wouldn't change the existing test cases

Copy link
Author

Choose a reason for hiding this comment

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

axios doesn't use Node's fetch implementation and isn't affected by undici mocks.

Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm this PR would break backward compatibility altogether in the case. I think a better solution would be to have nock and as well as undici mocks. What do you think?

Copy link
Author

Choose a reason for hiding this comment

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

That would definitely be an option.

I've seen several other projects introduce a similar breaking change when they moved to Node18 and beyond. Many projects only support Node18 and newer now, at least in my part of the bubble :)

However, as I remember, the new Octokit tooling also has a new fetch-based, and the old interface still built in.

This also made me think about if both paths should be implemented in mock-github. I felt like users of old octokit versions could still use the existing version of mock-github; the path into the future will be entirely fetch-based, as that is what the latest implementations from GitHub use. If you are mocking Octokit, it is likely that you want to work with the latest supported version, which uses fetch by default in the latest version, and is highly likely to continue doing so moving ahead.

Given all that, I felt like it could make sense to just accept the breaking change and focus on a single implementation.

If you think it's worth the effort to maintain the two paths, I'm happy to work on that as well :)

Copy link
Author

@oliversalzburg oliversalzburg left a comment

Choose a reason for hiding this comment

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

I addressed the comments and I'll work on some changes.

@@ -15,14 +15,14 @@ test("with default base url", async () => {
repo: "project",
});
expect(data1.status).toBe(200);
expect(data1.data).toStrictEqual({ full_name: "it definitely worked" });
expect(data1.data).toEqual({ full_name: "it definitely worked" });
Copy link
Author

Choose a reason for hiding this comment

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

I guess it wasn't. It must have been an oversight when rewriting the tests.

@@ -1,18 +1,18 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios from "axios";
Copy link
Author

Choose a reason for hiding this comment

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

axios doesn't use Node's fetch implementation and isn't affected by undici mocks.

path: requestPath => this.pathHandler(requestPath),
method: this.method.toUpperCase(),
body: requestBody => this.bodyHandler(requestBody),
query: this.query,
Copy link
Author

Choose a reason for hiding this comment

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

Queries are matched as part of the path. See further comments on this below.

src/endpoint-mocker/response/abstract-response-mocker.ts Outdated Show resolved Hide resolved
Comment on lines +19 to +30
if (process.env.JEST_WORKER_ID !== undefined) {
// @ts-expect-error This seems impossible to type correctly.
globalThis.fetch = (
input: NodeJS.fetch.RequestInfo,
init?: NodeJS.fetch.RequestInit | undefined
) =>
fetch(input, {
dispatcher: this.agent,
...(init as undiciRequestInit),
});
}
}
Copy link
Author

Choose a reason for hiding this comment

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

It could probably be moved to the test suites themselves. Of course, all consumers of this package will have to do that too for all their test suites, if they use Jest. Could be painful. Or maybe I'm missing a better solution.

@@ -42,21 +42,23 @@ async function generateEndpointRequest() {

newEndpointRequest[scope][
endpoint.id
] = `new MoctokitRequestMocker<"${path}", "${method}">(baseUrl, endpoints["${scope}"]["${endpoint.id}"], allowUnmocked).request`;
Copy link
Author

Choose a reason for hiding this comment

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

This behavior is controlled globally through https://undici.nodejs.org/#/docs/api/MockAgent?id=mockagentdisablenetconnect in undici.

Copy link
Author

@oliversalzburg oliversalzburg left a comment

Choose a reason for hiding this comment

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

I hope I addressed everything :)

@@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
node-version: 20
Copy link
Author

Choose a reason for hiding this comment

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

Wait, this is the publish job. The matrix is already in pull_request 😄 I'll add 18 there.

@@ -42,21 +42,23 @@ async function generateEndpointRequest() {

newEndpointRequest[scope][
endpoint.id
] = `new MoctokitRequestMocker<"${path}", "${method}">(baseUrl, endpoints["${scope}"]["${endpoint.id}"], allowUnmocked).request`;
Copy link
Author

Choose a reason for hiding this comment

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

I'm doing that in EndpointMocker actually 😊

.reply(response.status, response.data as Record<string, unknown>, {
headers: {
...(response.headers as IncomingHttpHeaders),
"content-type": this.getContentTypeForResponseData(response.data),
Copy link
Author

Choose a reason for hiding this comment

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

I don't follow. In the headers hash, the content-type is the first entry, then we spread the user-defined response.headers (which could also include a content-type) into the hash, overriding any existing properties.

Shouldn't that already have the desired effect?

@shubhbapna
Copy link
Collaborator

Hi, sorry I was away for sometime. I was recently made aware of the ongoing effort in nock to support undici: nock/nock#2517

They estimate that this work will be done by January. I would rather wait for this and have the mocking entirely handled by nock which in the longer run will be better and will also reduce the maintenance load on this project. So for now I will mark this pull request as draft. Sorry for the inconvenience and thank you so much for the contribution!

@shubhbapna shubhbapna marked this pull request as draft December 27, 2023 07:26
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.

Node 18 and @octokit/core v5.0.0 incompatability
2 participants