Skip to content

Commit 9a8be4d

Browse files
authored
test: add tests for account linking example (#749)
* test: add tests into account-linking example * test: clean up account linking example test * ci: auto install deps in frontend and backend dirs of examples before tests * ci: auto install deps in frontend and backend dirs of examples before tests * ci: auto install deps in frontend and backend dirs of examples before tests * ci: auto install deps in frontend and backend dirs of examples before tests * ci: auto install deps in frontend and backend dirs of examples before tests * ci: make updateExampleAppDeps support with-localstorage * chore: update lib dep versions in example
1 parent 379953b commit 9a8be4d

File tree

9 files changed

+242
-11
lines changed

9 files changed

+242
-11
lines changed

.github/workflows/test-examples.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ jobs:
2525
working-directory: ${{ matrix.examplePath }}
2626
steps:
2727
- uses: actions/checkout@v2
28-
- run: npm install git+https://github.com:supertokens/supertokens-auth-react.git#$GITHUB_SHA
29-
- run: npm install
28+
- run: bash ../../test/updateExampleAppDeps.sh .
3029
- run: npm install [email protected] [email protected] puppeteer@^11.0.0 isomorphic-fetch@^3.0.0
3130
- run: npm run build || true
3231
- run: |

examples/with-account-linking/backend/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export const SuperTokensConfig: TypeInput = {
9191
],
9292
}),
9393
Passwordless.init({
94-
contactMethod: "PHONE",
94+
contactMethod: "EMAIL_OR_PHONE",
9595
flowType: "USER_INPUT_CODE_AND_MAGIC_LINK",
9696
}),
9797
Session.init(),

examples/with-account-linking/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"helmet": "^5.1.0",
1414
"morgan": "^1.10.0",
1515
"npm-run-all": "^4.1.5",
16-
"supertokens-node": "github:supertokens/supertokens-node#account-linking",
16+
"supertokens-node": "latest",
1717
"ts-node-dev": "^2.0.0",
1818
"typescript": "^4.7.2"
1919
},

examples/with-account-linking/frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"react-dom": "^18.2.0",
1616
"react-router-dom": "^6.2.1",
1717
"react-scripts": "5.0.1",
18-
"supertokens-auth-react": "github:supertokens/supertokens-auth-react#feat/account-linking",
18+
"supertokens-auth-react": "latest",
19+
"supertokens-web-js": "latest",
1920
"typescript": "^4.8.2",
2021
"web-vitals": "^2.1.4"
2122
},

examples/with-account-linking/frontend/src/LinkingPage/index.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const LinkingPage: React.FC = () => {
4242
setSuccess("Successfully added password");
4343
setError(null);
4444
}
45-
}, [setError, setSuccess]);
45+
}, [setError, setSuccess, password]);
4646

4747
const addPhoneNumber = useCallback(async () => {
4848
const resp = await fetch(`${getApiDomain()}/addPhoneNumber`, {
@@ -62,7 +62,7 @@ export const LinkingPage: React.FC = () => {
6262
setSuccess("Successfully added password");
6363
}
6464
loadUserInfo();
65-
}, [setError, setSuccess, loadUserInfo]);
65+
}, [setError, setSuccess, loadUserInfo, phoneNumber]);
6666

6767
useEffect(() => {
6868
loadUserInfo();
@@ -87,7 +87,7 @@ export const LinkingPage: React.FC = () => {
8787
) : (
8888
<ul className="loginMethods">
8989
{passwordLoginMethods.map((lm: any) => (
90-
<div key={lm.recipeUserId} className="email-password login-method">
90+
<div key={lm.recipeUserId} className="emailpassword login-method">
9191
<span className="recipeId">{lm.recipeId}</span>
9292
<span className="userId">{lm.recipeUserId}</span>
9393
<span className="contactInfo"> Email: {lm.email}</span>
@@ -103,10 +103,11 @@ export const LinkingPage: React.FC = () => {
103103
</div>
104104
))}
105105
{phoneLoginMethod.map((lm: any) => (
106-
<div key={lm.recipeUserId} className="thirdparty login-method">
106+
<div key={lm.recipeUserId} className="passwordless login-method">
107107
<span className="recipeId">{lm.recipeId}</span>
108108
<span className="userId">{lm.recipeUserId}</span>
109-
<span className="contactInfo"> Phone number: {lm.phoneNumber}</span>
109+
{lm.phoneNumber && <span className="contactInfo"> Phone number: {lm.phoneNumber}</span>}
110+
{lm.email && <span className="contactInfo"> Email: {lm.email}</span>}
110111
</div>
111112
))}
112113
</ul>

examples/with-account-linking/frontend/src/config.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import ThirdPartyEmailPassword, { Google, Github, Apple } from "supertokens-auth-react/recipe/thirdpartyemailpassword";
22
import EmailVerification from "supertokens-auth-react/recipe/emailverification";
33
import { ThirdPartyEmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/thirdpartyemailpassword/prebuiltui";
4+
import { PasswordlessPreBuiltUI } from "supertokens-auth-react/recipe/passwordless/prebuiltui";
45
import { EmailVerificationPreBuiltUI } from "supertokens-auth-react/recipe/emailverification/prebuiltui";
56
import Session from "supertokens-auth-react/recipe/session";
7+
import Passwordless from "supertokens-auth-react/recipe/passwordless";
68

79
export function getApiDomain() {
810
const apiPort = process.env.REACT_APP_API_PORT || 3001;
@@ -50,6 +52,9 @@ export const SuperTokensConfig = {
5052
],
5153
},
5254
}),
55+
Passwordless.init({
56+
contactMethod: "EMAIL_OR_PHONE",
57+
}),
5358
Session.init(),
5459
],
5560
};
@@ -58,4 +63,4 @@ export const recipeDetails = {
5863
docsLink: "https://supertokens.com/docs/thirdpartyemailpassword/introduction",
5964
};
6065

61-
export const PreBuiltUIList = [ThirdPartyEmailPasswordPreBuiltUI, EmailVerificationPreBuiltUI];
66+
export const PreBuiltUIList = [ThirdPartyEmailPasswordPreBuiltUI, PasswordlessPreBuiltUI, EmailVerificationPreBuiltUI];
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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+
const assert = require("assert");
21+
const puppeteer = require("puppeteer");
22+
const {
23+
getTestEmail,
24+
getTestPhoneNumber,
25+
setInputValues,
26+
submitForm,
27+
toggleSignInSignUp,
28+
waitForSTElement,
29+
} = require("../../../test/exampleTestHelpers");
30+
31+
const SuperTokensNode = require("../backend/node_modules/supertokens-node");
32+
const Session = require("../backend/node_modules/supertokens-node/recipe/session");
33+
const EmailVerification = require("../backend/node_modules/supertokens-node/recipe/emailverification");
34+
const EmailPassword = require("../backend/node_modules/supertokens-node/recipe/emailpassword");
35+
const Passwordless = require("../backend/node_modules/supertokens-node/recipe/passwordless");
36+
37+
// Run the tests in a DOM environment.
38+
require("jsdom-global")();
39+
40+
const apiDomain = "http://localhost:3001";
41+
const websiteDomain = "http://localhost:3000";
42+
SuperTokensNode.init({
43+
supertokens: {
44+
// We are running these tests without running a local ST instance
45+
connectionURI: "https://try.supertokens.com",
46+
},
47+
appInfo: {
48+
// These largely shouldn't matter except for creating links which we can change anyway
49+
apiDomain: apiDomain,
50+
websiteDomain: websiteDomain,
51+
appName: "testNode",
52+
},
53+
recipeList: [
54+
EmailVerification.init({ mode: "OPTIONAL" }),
55+
EmailPassword.init(),
56+
Session.init(),
57+
Passwordless.init({
58+
contactMethod: "EMAIL_OR_PHONE",
59+
flowType: "USER_INPUT_CODE_AND_MAGIC_LINK",
60+
}),
61+
],
62+
});
63+
64+
describe("SuperTokens Example Basic tests", function () {
65+
let browser;
66+
let page;
67+
const email = getTestEmail();
68+
const phoneNumber = getTestPhoneNumber();
69+
const testOTP = "1234";
70+
const testPW = "Str0ngP@ssw0rd";
71+
72+
before(async function () {
73+
browser = await puppeteer.launch({
74+
args: ["--no-sandbox", "--disable-setuid-sandbox"],
75+
headless: true,
76+
});
77+
page = await browser.newPage();
78+
});
79+
80+
after(async function () {
81+
await browser.close();
82+
});
83+
84+
describe("Email Password test", function () {
85+
it("Successful signup with multiple credentials", async function () {
86+
await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]);
87+
88+
// redirected to /auth
89+
await toggleSignInSignUp(page);
90+
await setInputValues(page, [
91+
{ name: "email", value: email },
92+
{ name: "password", value: testPW },
93+
]);
94+
await submitForm(page);
95+
96+
// Redirected to email verification screen
97+
await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']");
98+
const userId = await page.evaluate(() => window.__supertokensSessionRecipe.getUserId());
99+
100+
// Attempt reloading Home
101+
await Promise.all([page.goto(websiteDomain), page.waitForNavigation({ waitUntil: "networkidle0" })]);
102+
await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']");
103+
104+
// Create a new token and use it (we don't have access to the originally sent one)
105+
const tokenInfo = await EmailVerification.createEmailVerificationToken(
106+
"public",
107+
SuperTokensNode.convertToRecipeUserId(userId),
108+
email
109+
);
110+
await page.goto(`${websiteDomain}/auth/verify-email?token=${tokenInfo.token}`);
111+
112+
await submitForm(page);
113+
114+
await page.waitForSelector(".sessionButton");
115+
116+
await page.goto(`${websiteDomain}/link`);
117+
118+
await page.waitForSelector(".emailpassword.login-method");
119+
await checkLoginMethods(page, [{ loginMethod: "emailpassword", email }]);
120+
121+
const input = await page.waitForSelector("[type=tel]");
122+
await input.type(phoneNumber);
123+
124+
await page.click("[type=tel]+button");
125+
await page.waitForSelector(".passwordless.login-method");
126+
127+
await checkLoginMethods(page, [
128+
{ loginMethod: "emailpassword", email },
129+
{ loginMethod: "passwordless", phoneNumber },
130+
]);
131+
132+
await page.evaluate(() => __supertokensSessionRecipe.signOut({}));
133+
await new Promise((res) => setTimeout(res, 200));
134+
135+
await page.goto(`${websiteDomain}/auth?rid=passwordless`);
136+
137+
await setInputValues(page, [{ name: "emailOrPhone", value: email }]);
138+
await submitForm(page);
139+
140+
await waitForSTElement(page, "[name=userInputCode]");
141+
142+
const loginAttemptInfo = JSON.parse(
143+
await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo"))
144+
);
145+
146+
await Passwordless.createNewCodeForDevice({
147+
tenantId: "public",
148+
deviceId: loginAttemptInfo.deviceId,
149+
userInputCode: testOTP,
150+
});
151+
152+
await setInputValues(page, [{ name: "userInputCode", value: testOTP }]);
153+
await submitForm(page);
154+
const callApiBtn = await page.waitForSelector(".sessionButton");
155+
let setAlertContent;
156+
let alertContent = new Promise((res) => (setAlertContent = res));
157+
page.on("dialog", async (dialog) => {
158+
setAlertContent(dialog.message());
159+
await dialog.dismiss();
160+
});
161+
await callApiBtn.click();
162+
163+
const alertText = await alertContent;
164+
assert(alertText.startsWith("Session Information:"));
165+
const sessionInfo = JSON.parse(alertText.replace(/^Session Information:/, ""));
166+
assert.strictEqual(sessionInfo.userId, userId);
167+
168+
await page.goto(`${websiteDomain}/link`);
169+
170+
await page.waitForSelector(".emailpassword.login-method");
171+
await checkLoginMethods(page, [
172+
{ loginMethod: "emailpassword", email },
173+
{ loginMethod: "passwordless", phoneNumber },
174+
{ loginMethod: "passwordless", email },
175+
]);
176+
});
177+
});
178+
});
179+
180+
async function checkLoginMethods(page, expectedLoginMethods) {
181+
assert.strictEqual(await page.url(), `${websiteDomain}/link`);
182+
const methodDivs = await page.$$(".login-method");
183+
184+
for (const div of methodDivs) {
185+
const classNameProp = await div.getProperty("className");
186+
const className = await classNameProp.jsonValue();
187+
const method = className.split(" ")[0];
188+
const contactInfo = (await (await div.$(".contactInfo")).evaluate((el) => el.textContent)).trim();
189+
190+
assert(
191+
expectedLoginMethods.some(
192+
(m) =>
193+
m.loginMethod === method &&
194+
(m.email === undefined || contactInfo === `Email: ${m.email}`) &&
195+
(m.phoneNumber === undefined || contactInfo === `Phone number: ${m.phoneNumber}`)
196+
)
197+
);
198+
}
199+
assert.strictEqual(methodDivs.length, expectedLoginMethods.length);
200+
}

test/exampleTestHelpers.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ function getTestEmail() {
120120
return `john.doe+${Date.now()}@supertokens.io`;
121121
}
122122

123+
function getTestPhoneNumber() {
124+
return `+3670${Date.now().toString().substring(6)}`;
125+
}
126+
123127
async function getSignInOrSignUpSwitchLink(page) {
124128
return waitForSTElement(
125129
page,
@@ -142,6 +146,7 @@ module.exports = {
142146
getFieldErrors,
143147
setInputValues,
144148
getTestEmail,
149+
getTestPhoneNumber,
145150
getSignInOrSignUpSwitchLink,
146151
toggleSignInSignUp,
147152
};

test/updateExampleAppDeps.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
3+
cd $1;
4+
npm i;
5+
npm install git+https://github.com:supertokens/supertokens-auth-react.git#$GITHUB_SHA;
6+
7+
if [ -d "frontend" ]; then
8+
pushd frontend;
9+
npm i;
10+
grep supertokens-auth-react package.json && npm install git+https://github.com:supertokens/supertokens-auth-react.git#$GITHUB_SHA;
11+
popd;
12+
else
13+
npm install git+https://github.com:supertokens/supertokens-auth-react.git#$GITHUB_SHA;
14+
fi
15+
16+
if [ -d "backend" ]; then
17+
pushd backend;
18+
npm i;
19+
popd;
20+
fi

0 commit comments

Comments
 (0)