Repository navigation
Expand file tree
/
Copy pathcheckProviders.test.ts
More file actions
184 lines (169 loc) · 7.33 KB
/
Copy pathcheckProviders.test.ts
File metadata and controls
184 lines (169 loc) · 7.33 KB
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// SPDX-FileCopyrightText: 2026 Anton Wiklund and Solid Memo contributors
//
// SPDX-License-Identifier: MIT
import { describe, expect, it, vi } from "vitest";
import { POD_PROVIDERS } from "../packages/domain/src/podProvider.ts";
import { configurationUrl, main, providerProblems, run } from "./checkProviders.ts";
const PROVIDER = {
id: "example",
name: "Example Pods",
oidcIssuer: "https://login.example/",
signUpUrl: "https://example/sign-up",
};
const CONFIGURATION = "https://login.example/.well-known/openid-configuration";
/** A fetch that answers each URL from `answers`, in turn: a Response, or an error to throw. */
function fakeFetch(answers: Record<string, (Response | Error)[]>) {
return vi.fn(async (input: string | URL | Request) => {
const answer = answers[String(input)]?.shift();
if (answer === undefined) throw new Error(`nothing for ${String(input)}`);
if (answer instanceof Error) throw answer;
return answer;
}) as unknown as typeof globalThis.fetch & ReturnType<typeof vi.fn>;
}
const json = (body: unknown, status = 200) => new Response(JSON.stringify(body), { status });
describe("configurationUrl", () => {
it("puts the discovery document after the issuer, with or without its last slash", () => {
expect(configurationUrl("https://login.example/")).toBe(CONFIGURATION);
expect(configurationUrl("https://login.example")).toBe(CONFIGURATION);
});
});
describe("providerProblems", () => {
it("finds none when the issuer matches and the sign-up page answers", async () => {
const fetch = fakeFetch({
[CONFIGURATION]: [json({ issuer: "https://login.example/" })],
"https://example/sign-up": [new Response("<html>", { status: 200 })],
});
expect(await providerProblems(PROVIDER, fetch)).toEqual([]);
});
it("asks for the discovery document as JSON and for the sign-up page as a browser does", async () => {
const fetch = fakeFetch({
[CONFIGURATION]: [json({ issuer: "https://login.example/" })],
"https://example/sign-up": [new Response("<html>")],
});
await providerProblems(PROVIDER, fetch);
const accept = (url: string) =>
fetch.mock.calls.find(([input]) => String(input) === url)?.[1]?.headers.accept;
expect(accept(CONFIGURATION)).toBe("application/json");
expect(accept("https://example/sign-up")).toBe("text/html, */*;q=0.8");
});
it("reads no sign-up page for a provider that has none", async () => {
const fetch = fakeFetch({ [CONFIGURATION]: [json({ issuer: "https://login.example/" })] });
const { signUpUrl: _, ...withoutSignUp } = PROVIDER;
expect(await providerProblems(withoutSignUp, fetch)).toEqual([]);
expect(fetch).toHaveBeenCalledTimes(1);
});
it("names an issuer that differs, even by its last slash, or is missing", async () => {
const differs = fakeFetch({
[CONFIGURATION]: [json({ issuer: "https://login.example" })],
"https://example/sign-up": [new Response("")],
});
expect(await providerProblems(PROVIDER, differs)).toEqual([
'Example Pods: its issuer is "https://login.example", not "https://login.example/".',
]);
const missing = fakeFetch({
[CONFIGURATION]: [json(null)],
"https://example/sign-up": [new Response("")],
});
expect(await providerProblems(PROVIDER, missing)).toEqual([
'Example Pods: its issuer is null, not "https://login.example/".',
]);
});
it("says when the issuer answers no JSON, an error status, or not at all", async () => {
const html = fakeFetch({
[CONFIGURATION]: [new Response("<html>")],
"https://example/sign-up": [new Response("", { status: 404 })],
});
expect(await providerProblems(PROVIDER, html)).toEqual([
`Example Pods: ${CONFIGURATION} does not answer JSON.`,
"Example Pods: its sign-up page https://example/sign-up answers 404.",
]);
const gone = fakeFetch({
[CONFIGURATION]: [json({}, 410)],
"https://example/sign-up": [
new TypeError("a"),
new TypeError("b"),
new TypeError("fetch failed"),
],
});
expect(await providerProblems(PROVIDER, gone)).toEqual([
`Example Pods: ${CONFIGURATION} answers 410.`,
"Example Pods: its sign-up page https://example/sign-up does not answer (fetch failed).",
]);
});
it("tries a request that fails or answers 5xx again, three times in all", async () => {
const recovers = fakeFetch({
[CONFIGURATION]: [
new TypeError("fetch failed"),
json({}, 503),
json({ issuer: "https://login.example/" }),
],
"https://example/sign-up": [new Response("")],
});
expect(await providerProblems(PROVIDER, recovers)).toEqual([]);
const stays = fakeFetch({
[CONFIGURATION]: [json({}, 502), json({}, 502), json({}, 502)],
"https://example/sign-up": [new Response("")],
});
expect(await providerProblems(PROVIDER, stays)).toEqual([
`Example Pods: ${CONFIGURATION} answers 502.`,
]);
const down = fakeFetch({
[CONFIGURATION]: [new TypeError("a"), new TypeError("b"), new TypeError("fetch failed")],
"https://example/sign-up": [new Response("")],
});
expect(await providerProblems(PROVIDER, down)).toEqual([
`Example Pods: ${CONFIGURATION} does not answer (fetch failed).`,
]);
});
it("names a thrown value that is not an Error as it is", async () => {
const fetch = vi.fn(async () => {
throw "offline";
}) as unknown as typeof globalThis.fetch;
const { signUpUrl: _, ...withoutSignUp } = PROVIDER;
expect(await providerProblems(withoutSignUp, fetch)).toEqual([
`Example Pods: ${CONFIGURATION} does not answer (offline).`,
]);
});
});
describe("main", () => {
it("says every provider answers, and exits 0", async () => {
const logs: string[] = [];
const fetch = fakeFetch({
[CONFIGURATION]: [json({ issuer: "https://login.example/" })],
"https://example/sign-up": [new Response("")],
});
expect(await main({ providers: [PROVIDER], fetch, log: (m) => logs.push(m) })).toBe(0);
expect(logs).toEqual(["All 1 Pod providers answer with the issuer onboarding names."]);
});
it("logs each problem, and exits 1", async () => {
const logs: string[] = [];
const fetch = fakeFetch({
[CONFIGURATION]: [json({ issuer: "https://elsewhere/" })],
"https://example/sign-up": [new Response("")],
});
expect(await main({ providers: [PROVIDER], fetch, log: (m) => logs.push(m) })).toBe(1);
expect(logs).toEqual([
'Example Pods: its issuer is "https://elsewhere/", not "https://login.example/".',
]);
});
});
describe("run", () => {
it("checks the providers onboarding suggests, with the global fetch", async () => {
const issuers = new Map(
POD_PROVIDERS.map((p) => [configurationUrl(p.oidcIssuer), p.oidcIssuer]),
);
const fetch = vi.spyOn(globalThis, "fetch").mockImplementation(async (input) => {
const issuer = issuers.get(String(input));
return issuer === undefined ? new Response("") : json({ issuer });
});
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
const process: { exitCode?: number | string | null } = {};
await run(process);
expect(process.exitCode).toBe(0);
expect(fetch).toHaveBeenCalledTimes(
POD_PROVIDERS.length + POD_PROVIDERS.filter((p) => p.signUpUrl !== undefined).length,
);
fetch.mockRestore();
log.mockRestore();
});
});