/
npm-info.spec.ts
107 lines (89 loc) · 3.07 KB
/
npm-info.spec.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { execa } from "execa";
import { type ExecaError, type NpmInfoError, npmInfo, isNpmInfoError } from "./npm-info";
jest.mock("execa");
jest.mock("./persistent-cache");
const mockExeca = execa as unknown as jest.Mock;
function createExecaError(message: string, stdout: string | Record<string, unknown>): Error {
const error = new Error(message) as Error & ExecaError;
error.stdout = typeof stdout === "string" ? stdout : JSON.stringify(stdout);
return error;
}
mockExeca.mockImplementation((_, args: string[]) => {
const [name, version] = args.at(-1)!.split("@");
switch (name) {
case "exception":
throw new Error("Unrelated exception");
case "unpublished":
throw createExecaError("mock error", {
error: {
code: "E404",
summary: "Mock summary",
detail: "Mock details",
},
});
case "unpublished-garbage":
throw createExecaError("mock error", "garbage non-json response");
default:
return {
stdout: JSON.stringify({ name, version }),
};
}
});
beforeEach(() => {
jest.clearAllMocks();
});
it("should fetch package info", async () => {
expect.assertions(2);
const pkgData = await npmInfo("my-package@1.2.3");
expect(mockExeca).toHaveBeenCalledWith("npm", ["info", "--json", "my-package@1.2.3"]);
expect(pkgData).toEqual({
name: "my-package",
version: "1.2.3",
});
});
it("should return null when using ignoreUnpublished", async () => {
expect.assertions(1);
const pkgData = await npmInfo("unpublished@1.2.3", { ignoreUnpublished: true });
expect(pkgData).toBeNull();
});
it("should cache results", async () => {
expect.assertions(1);
await npmInfo("my-package@1");
await npmInfo("my-package@2");
await npmInfo("my-package@1");
expect(mockExeca).toHaveBeenCalledTimes(2);
});
it("should cache ignoreUnpublished", async () => {
expect.assertions(1);
await npmInfo("unpublished@1", { ignoreUnpublished: true });
await npmInfo("unpublished@2", { ignoreUnpublished: true });
await npmInfo("unpublished@1", { ignoreUnpublished: true });
expect(mockExeca).toHaveBeenCalledTimes(2);
});
it("should throw custom error if an error is returned by npm info", async () => {
expect.assertions(1);
await expect(() => npmInfo("unpublished@1.2.3")).rejects.toThrow("Mock summary");
});
it("should handle garbade data from registry", async () => {
expect.assertions(1);
await expect(() => npmInfo("unpublished-garbage@1.2.3")).rejects.toThrow("mock error");
});
it("should throw original error for other exceptions", async () => {
expect.assertions(1);
await expect(() => npmInfo("exception")).rejects.toThrow("Unrelated exception");
});
describe("isNpmInfoError()", () => {
it("should return true if error contains npm info error data", () => {
expect.assertions(1);
const error = new Error("mock error") as Error & NpmInfoError;
error.code = "E404";
error.summary = "summary";
error.detail = "detail";
expect(isNpmInfoError(error)).toBeTruthy();
});
it("should return false for errors without npm info error data", () => {
expect.assertions(1);
const error = new Error("mock error");
expect(isNpmInfoError(error)).toBeFalsy();
});
});