Skip to content

Commit 7406edc

Browse files
Fix all tests to make them compatible with unified core setup
1 parent 3966e60 commit 7406edc

13 files changed

+627
-35
lines changed

test/end-to-end/generalerror.test.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
* Imports
1818
*/
1919
import assert from "assert";
20-
import puppeteer from "puppeteer";
21-
import fetch from "isomorphic-fetch";
2220
import {
2321
clearBrowserCookiesWithoutAffectingConsole,
2422
screenshotOnFailure,
Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved.
2+
*
3+
* This software is licensed under the Apache License, Version 2.0 (the
4+
* "License") as published by the Apache Software Foundation.
5+
*
6+
* You may not use this file except in compliance with the License. You may
7+
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12+
* License for the specific language governing permissions and limitations
13+
* under the License.
14+
*/
15+
16+
/*
17+
* Imports
18+
*/
19+
20+
import assert from "assert";
21+
import {
22+
clearBrowserCookiesWithoutAffectingConsole,
23+
waitForSTElement,
24+
screenshotOnFailure,
25+
getFactorChooserOptions,
26+
isMFASupported,
27+
expectErrorThrown,
28+
setupBrowser,
29+
backendHook,
30+
setupCoreApp,
31+
setupST,
32+
} from "../helpers";
33+
import { MFA_INFO_API } from "../constants";
34+
35+
import { TEST_CLIENT_BASE_URL } from "../constants";
36+
import {
37+
waitForDashboard,
38+
completeOTP,
39+
tryEmailPasswordSignIn,
40+
chooseFactor,
41+
completeTOTP,
42+
setupUserWithAllFactors,
43+
goToFactorChooser,
44+
waitForAccessDenied,
45+
} from "./mfa.helpers";
46+
import { randomUUID } from "crypto";
47+
48+
/*
49+
* Tests.
50+
*/
51+
describe("SuperTokens SignIn w/ MFA", function () {
52+
let browser;
53+
let page;
54+
let consoleLogs = [];
55+
56+
const appConfig = {
57+
accountLinkingConfig: {
58+
enabled: true,
59+
shouldAutoLink: {
60+
shouldAutomaticallyLink: true,
61+
shouldRequireVerification: false,
62+
},
63+
},
64+
mfaInfo: {
65+
requirements: [{ oneOf: ["otp-email", "otp-phone"] }],
66+
},
67+
};
68+
69+
before(async function () {
70+
if (!(await isMFASupported())) {
71+
this.skip();
72+
}
73+
74+
backendHook("before");
75+
const coreUrl = await setupCoreApp();
76+
appConfig.coreUrl = coreUrl;
77+
appConfig.accountLinkingConfig = {
78+
enabled: true,
79+
shouldAutoLink: {
80+
shouldAutomaticallyLink: true,
81+
shouldRequireVerification: false,
82+
},
83+
};
84+
await setupST(appConfig);
85+
86+
browser = await setupBrowser();
87+
});
88+
89+
beforeEach(async function () {
90+
backendHook("beforeEach");
91+
page = await browser.newPage();
92+
93+
page.on("console", (consoleObj) => {
94+
const log = consoleObj.text();
95+
// console.log(log);
96+
if (log.startsWith("ST_LOGS")) {
97+
consoleLogs.push(log);
98+
}
99+
});
100+
consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []);
101+
102+
await page.evaluate(() => window.localStorage.removeItem("supertokens-passwordless-loginAttemptInfo"));
103+
await page.evaluate(() => window.localStorage.removeItem("clientRecipeListForDynamicLogin"));
104+
await page.evaluate(() => window.localStorage.removeItem("mode"));
105+
await page.evaluate(() => window.localStorage.setItem("enableAllRecipes", "true"));
106+
});
107+
108+
afterEach(async function () {
109+
await screenshotOnFailure(this, browser);
110+
await page?.close();
111+
backendHook("afterEach");
112+
});
113+
114+
after(async function () {
115+
await browser?.close();
116+
backendHook("after");
117+
});
118+
119+
describe("chooser screen", () => {
120+
let email, phoneNumber;
121+
let totpSecret;
122+
123+
before(async () => {
124+
page = await browser.newPage();
125+
({ email, phoneNumber, totpSecret } = await setupUserWithAllFactors(page));
126+
await page.close();
127+
});
128+
129+
it("should redirect to the factor screen during sign in if only one factor is available (limited by FE recipe inits)", async () => {
130+
await page.evaluate(() => {
131+
window.localStorage.setItem("enableAllRecipes", "false");
132+
window.localStorage.setItem("clientRecipeListForDynamicLogin", JSON.stringify(["emailpassword"]));
133+
});
134+
135+
await setupST({
136+
...appConfig,
137+
mfaInfo: {
138+
requirements: [{ oneOf: ["otp-email", "otp-phone", "totp"] }],
139+
},
140+
});
141+
142+
await tryEmailPasswordSignIn(page, email);
143+
144+
await completeTOTP(page, totpSecret);
145+
await waitForDashboard(page);
146+
});
147+
148+
it("should redirect to the factor screen during sign in if only one factor is available (limited by alreadySetup/allowedToSetup)", async () => {
149+
await page.evaluate(() => {
150+
window.localStorage.setItem("enableAllRecipes", "false");
151+
window.localStorage.setItem("clientRecipeListForDynamicLogin", JSON.stringify(["emailpassword"]));
152+
});
153+
154+
await setupST({
155+
...appConfig,
156+
mfaInfo: {
157+
requirements: [{ oneOf: ["otp-email", "otp-phone", "totp"] }],
158+
159+
alreadySetup: ["totp"],
160+
allowedToSetup: [],
161+
},
162+
});
163+
164+
await tryEmailPasswordSignIn(page, email);
165+
166+
await completeTOTP(page, totpSecret);
167+
await waitForDashboard(page);
168+
});
169+
it("should redirect to the factor screen during sign in if only one factor is available (limited by next array)", async () => {
170+
await setupST({
171+
...appConfig,
172+
mfaInfo: {
173+
requirements: [{ oneOf: ["totp"] }],
174+
},
175+
});
176+
177+
await tryEmailPasswordSignIn(page, email);
178+
179+
await completeTOTP(page, totpSecret);
180+
await waitForDashboard(page);
181+
});
182+
183+
it("should show all factors the user can complete or set up in the next array", async () => {
184+
await setupST({
185+
...appConfig,
186+
mfaInfo: {
187+
requirements: [{ oneOf: ["totp", "otp-email"] }],
188+
},
189+
});
190+
191+
await tryEmailPasswordSignIn(page, email);
192+
193+
const options = await getFactorChooserOptions(page);
194+
assert.deepStrictEqual(new Set(options), new Set(["otp-email", "totp"]));
195+
});
196+
197+
it("should show all factors the user can complete or set up if the next array is empty", async () => {
198+
await setupST({
199+
...appConfig,
200+
mfaInfo: {
201+
requirements: [],
202+
alreadySetup: ["otp-phone", "otp-email"],
203+
allowedToSetup: ["totp"],
204+
},
205+
});
206+
207+
await tryEmailPasswordSignIn(page, email);
208+
await goToFactorChooser(page);
209+
210+
const optionsAfter2FA = await getFactorChooserOptions(page);
211+
assert.deepStrictEqual(new Set(optionsAfter2FA), new Set(["otp-phone", "otp-email", "totp"]));
212+
});
213+
214+
it("should throw error if there are no available options during sign in", async () => {
215+
await setupST({
216+
...appConfig,
217+
mfaInfo: {
218+
requirements: ["otp-phone"],
219+
alreadySetup: ["otp-email"],
220+
allowedToSetup: [],
221+
},
222+
});
223+
224+
await expectErrorThrown(page, () => tryEmailPasswordSignIn(page, email));
225+
await expectErrorThrown(page, () => page.goto(`${TEST_CLIENT_BASE_URL}/auth/mfa`));
226+
});
227+
228+
it("should show access denied if there are no available options after sign in", async () => {
229+
await setupST({
230+
...appConfig,
231+
mfaInfo: {
232+
requirements: [],
233+
alreadySetup: [],
234+
allowedToSetup: [],
235+
},
236+
});
237+
238+
await tryEmailPasswordSignIn(page, email);
239+
await goToFactorChooser(page, false);
240+
241+
await waitForAccessDenied(page);
242+
});
243+
244+
it("should show a back link only if visited after sign in", async () => {
245+
await setupST({
246+
...appConfig,
247+
mfaInfo: {
248+
requirements: [{ oneOf: ["otp-email", "otp-phone"] }],
249+
},
250+
});
251+
252+
await tryEmailPasswordSignIn(page, email);
253+
await waitForSTElement(page, "[data-supertokens~=factorChooserList]");
254+
255+
await waitForSTElement(page, "[data-supertokens~=backButton]", true);
256+
await chooseFactor(page, "otp-phone");
257+
await completeOTP(page);
258+
259+
await goToFactorChooser(page);
260+
261+
await waitForSTElement(page, "[data-supertokens~=backButton]");
262+
});
263+
264+
it("should show a logout link", async () => {
265+
await setupST({
266+
...appConfig,
267+
mfaInfo: {
268+
requirements: [{ oneOf: ["otp-email", "otp-phone"] }],
269+
},
270+
});
271+
272+
await tryEmailPasswordSignIn(page, email);
273+
await waitForSTElement(page, "[data-supertokens~=factorChooserList]");
274+
275+
await waitForSTElement(page, "[data-supertokens~=secondaryLinkWithLeftArrow]");
276+
await chooseFactor(page, "otp-phone");
277+
await completeOTP(page);
278+
279+
await goToFactorChooser(page);
280+
281+
await waitForSTElement(page, "[data-supertokens~=secondaryLinkWithLeftArrow]");
282+
});
283+
284+
it("should handle MFA info API failures gracefully", async () => {
285+
await setupST({
286+
...appConfig,
287+
mfaInfo: {
288+
requirements: [],
289+
alreadySetup: ["otp-phone", "otp-email"],
290+
allowedToSetup: [],
291+
},
292+
});
293+
294+
await page.setRequestInterception(true);
295+
const requestHandler = (request) => {
296+
if (request.url() === MFA_INFO_API && request.method() === "PUT") {
297+
return request.respond({
298+
status: 400,
299+
headers: {
300+
"access-control-allow-origin": TEST_CLIENT_BASE_URL,
301+
"access-control-allow-credentials": "true",
302+
},
303+
body: JSON.stringify({
304+
status: "BAD_INPUT",
305+
}),
306+
});
307+
}
308+
309+
return request.continue();
310+
};
311+
page.on("request", requestHandler);
312+
try {
313+
await tryEmailPasswordSignIn(page, email);
314+
await expectErrorThrown(page, () => goToFactorChooser(page, false));
315+
} finally {
316+
page.off("request", requestHandler);
317+
await page.setRequestInterception(false);
318+
}
319+
});
320+
});
321+
});

0 commit comments

Comments
 (0)