Skip to content

Commit af8ef43

Browse files
authored
test: use native fetch with mock server (#702)
1 parent eaceec7 commit af8ef43

10 files changed

+3294
-1884
lines changed

Diff for: package-lock.json

+1,412-1,458
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"lint": "prettier --check '{src,test}/**/*' README.md package.json",
1313
"lint:fix": "prettier --write '{src,test}/**/*' README.md package.json vite.config.js",
1414
"pretest": "npm run -s lint",
15-
"test": "vitest run --coverage"
15+
"test": "vitest run --coverage",
16+
"test:watch": "vitest --coverage"
1617
},
1718
"repository": "github:octokit/request.js",
1819
"keywords": [

Diff for: src/defaults.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { getUserAgent } from "universal-user-agent";
2+
import { VERSION } from "./version.js";
3+
4+
export default {
5+
headers: {
6+
"user-agent": `octokit-request.js/${VERSION} ${getUserAgent()}`,
7+
},
8+
};

Diff for: src/index.ts

+2-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
import { endpoint } from "@octokit/endpoint";
2-
import { getUserAgent } from "universal-user-agent";
3-
4-
import { VERSION } from "./version.js";
2+
import defaults from "./defaults.js";
53
import withDefaults from "./with-defaults.js";
64

7-
export const request = withDefaults(endpoint, {
8-
headers: {
9-
"user-agent": `octokit-request.js/${VERSION} ${getUserAgent()}`,
10-
},
11-
});
5+
export const request = withDefaults(endpoint, defaults);

Diff for: test/body-parser.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { IncomingMessage } from "http";
2+
3+
export default function bodyParser(request: IncomingMessage) {
4+
return new Promise((resolve, reject) => {
5+
let body = "";
6+
request.on("error", reject);
7+
request.on("data", (chunk: string) => (body += chunk));
8+
request.on("end", () => resolve(body));
9+
});
10+
}

Diff for: test/mock-request-http-server.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { createServer, RequestListener } from "node:http";
2+
import { type AddressInfo } from "node:net";
3+
import { once } from "node:stream";
4+
5+
import { endpoint } from "@octokit/endpoint";
6+
import type { RequestInterface } from "@octokit/types";
7+
8+
import withDefaults from "../src/with-defaults.ts";
9+
import defaults from "../src/defaults.ts";
10+
11+
export default async function mockRequestHttpServer(
12+
requestListener: RequestListener,
13+
): Promise<
14+
RequestInterface<object> & {
15+
closeMockServer: () => void;
16+
baseUrlMockServer: string;
17+
}
18+
> {
19+
const server = createServer(requestListener);
20+
server.listen(0);
21+
await once(server, "listening");
22+
23+
const baseUrl = `http://localhost:${(server.address() as AddressInfo).port}`;
24+
25+
const request = withDefaults(endpoint, {
26+
...defaults,
27+
baseUrl,
28+
}) as RequestInterface<object> & {
29+
closeMockServer: () => void;
30+
baseUrlMockServer: string;
31+
};
32+
33+
request.baseUrlMockServer = baseUrl;
34+
request.closeMockServer = server.close.bind(server);
35+
36+
return request;
37+
}

Diff for: test/request-common.test.ts

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { describe, it, expect } from "vitest";
2+
import fetchMock from "fetch-mock";
3+
4+
import { request } from "../src/index.ts";
5+
6+
describe("request()", () => {
7+
it("is a function", () => {
8+
expect(request).toBeInstanceOf(Function);
9+
});
10+
11+
it("Request error", async () => {
12+
expect.assertions(1);
13+
14+
// port: 8 // officially unassigned port. See https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
15+
await expect(request("GET https://127.0.0.1:8/")).rejects.toHaveProperty(
16+
"status",
17+
500,
18+
);
19+
});
20+
21+
it("Resolves with url", async () => {
22+
expect.assertions(1);
23+
24+
// this test cannot be mocked with `fetch-mock`. I don’t like to rely on
25+
// external websites to run tests, but in this case I’ll make an exception.
26+
// The alternative would be to start a local server we then send a request to,
27+
// this would only work in Node, so we would need to adapt the test setup, too.
28+
// We also can’t test the GitHub API, because on Travis unauthenticated
29+
// GitHub API requests are usually blocked due to IP rate limiting
30+
const response = await request(
31+
"https://www.githubstatus.com/api/v2/status.json",
32+
);
33+
expect(response.url).toEqual(
34+
"https://www.githubstatus.com/api/v2/status.json",
35+
);
36+
});
37+
38+
it("request should pass the `redirect` option to fetch", () => {
39+
expect.assertions(1);
40+
41+
const customFetch = async (url: string, options: RequestInit) => {
42+
expect(options.redirect).toEqual("manual");
43+
return await fetch(url, options);
44+
};
45+
46+
return request("/", {
47+
request: {
48+
redirect: "manual",
49+
fetch: customFetch,
50+
},
51+
});
52+
});
53+
54+
it("options.request.fetch", async () => {
55+
expect.assertions(1);
56+
57+
const response = await request("/", {
58+
request: {
59+
fetch: () =>
60+
Promise.resolve({
61+
status: 200,
62+
headers: new Headers({
63+
"Content-Type": "application/json; charset=utf-8",
64+
}),
65+
url: "http://api.github.com/",
66+
json() {
67+
return Promise.resolve("funk");
68+
},
69+
}),
70+
},
71+
});
72+
expect(response.data).toEqual("funk");
73+
});
74+
75+
it("Request TypeError error with an Error cause", async () => {
76+
expect.assertions(2);
77+
78+
try {
79+
// port: 8 // officially unassigned port. See https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
80+
await request("GET https://127.0.0.1:8/", {
81+
request: {
82+
fetch: () =>
83+
Promise.reject(
84+
Object.assign(new TypeError("fetch failed"), {
85+
cause: new Error("bad"),
86+
}),
87+
),
88+
},
89+
});
90+
throw new Error("should not resolve");
91+
} catch (error) {
92+
expect(error.status).toEqual(500);
93+
expect(error.message).toEqual("bad");
94+
}
95+
});
96+
97+
it("Request TypeError error with a string cause", async () => {
98+
expect.assertions(2);
99+
100+
const mock = fetchMock.sandbox().get("https://127.0.0.1:8/", {
101+
throws: Object.assign(new TypeError("fetch failed"), { cause: "bad" }),
102+
});
103+
104+
try {
105+
// port: 8 // officially unassigned port. See https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
106+
await request("GET https://127.0.0.1:8/", {
107+
request: {
108+
fetch: () =>
109+
Promise.reject(
110+
Object.assign(new TypeError("fetch failed"), {
111+
cause: "bad",
112+
}),
113+
),
114+
},
115+
});
116+
throw new Error("should not resolve");
117+
} catch (error) {
118+
expect(error.status).toEqual(500);
119+
expect(error.message).toEqual("bad");
120+
}
121+
});
122+
});

0 commit comments

Comments
 (0)