1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-08 15:05:22 +02:00

update e2e tests to work with new auth

This commit is contained in:
Tyler Myracle 2024-01-19 01:57:53 -06:00
parent 3bc4ac1687
commit 5375affd8e
10 changed files with 71 additions and 184 deletions

View file

@ -57,6 +57,7 @@ export default function LoginPage() {
<form className="space-y-4 w-full px-4" onSubmit={onSubmit}>
<Input
type="text"
name="email"
label="Email"
value={email}
onChange={(e) => setEmail(e.currentTarget.value)}
@ -64,6 +65,7 @@ export default function LoginPage() {
<InputPassword
autoComplete="password"
name="password"
label="Password"
value={password}
showPasswordRequirements={!isValid}

View file

@ -63,18 +63,21 @@ export default function RegisterPage() {
<form className="space-y-4 w-full px-4" onSubmit={onSubmit}>
<Input
type="text"
name="firstName"
label="First name"
value={firstName}
onChange={(e) => setFirstName(e.currentTarget.value)}
/>
<Input
type="text"
name="lastName"
label="Last name"
value={lastName}
onChange={(e) => setLastName(e.currentTarget.value)}
/>
<Input
type="text"
name="email"
label="Email"
value={email}
onChange={(e) => setEmail(e.currentTarget.value)}
@ -82,6 +85,7 @@ export default function RegisterPage() {
<InputPassword
autoComplete="password"
name="password"
label="Password"
value={password}
showPasswordRequirements={!isValid}

View file

@ -11,14 +11,10 @@ export default defineConfig({
baseUrl: 'http://localhost:4200',
env: {
API_URL: 'http://localhost:3333/v1',
AUTH0_ID: 'REPLACE_THIS',
AUTH0_CLIENT_ID: 'REPLACE_THIS',
AUTH0_NAME: 'Engineering CI',
AUTH0_EMAIL: 'REPLACE_THIS',
AUTH0_PASSWORD: 'REPLACE_THIS',
AUTH0_DOMAIN: 'REPLACE_THIS',
STRIPE_WEBHOOK_SECRET:
'REPLACE_THIS',
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
NEXT_PUBLIC_NEXTAUTH_URL: 'http://localhost:4200',
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
STRIPE_WEBHOOK_SECRET: 'REPLACE_THIS',
STRIPE_CUSTOMER_ID: 'REPLACE_THIS',
STRIPE_SUBSCRIPTION_ID: 'REPLACE_THIS',
},

View file

@ -19,81 +19,6 @@ function openEditAccountModal() {
}
describe('Accounts', () => {
it('should sync and edit a plaid connection', () => {
cy.apiRequest({
method: 'POST',
url: 'e2e/plaid/connect',
}).then((response) => {
expect(response.status).to.eql(200)
// The only time we need to manually send a Plaid webhook is in Github actions when testing a PR
if (Cypress.env('WEBHOOK_TYPE') === 'mock') {
const { plaidItemId } = response.body.data.json
cy.request({
method: 'POST',
url: `${Cypress.env('API_URL')}/plaid/webhook`,
body: {
webhook_type: 'TRANSACTIONS',
webhook_code: 'HISTORICAL_UPDATE',
item_id: plaidItemId,
},
})
.its('status')
.should('equal', 200)
}
})
// Check account sidebar names and balances
cy.visit('/accounts')
cy.getByTestId('account-group', { timeout: 20_000 })
assertSidebarAccounts([
['Assets', '$20,000'],
['Cash', '$20,000'],
['Sandbox Savings', '$15,000'],
['Sandbox Checking', '$5,000'],
['Debts', '$950'],
['Credit Cards', '$950'],
['Sandbox CC', '$950'],
])
// Check current net worth
cy.visit('/')
cy.getByTestId('current-data-value').should('contain.text', '$19,050.00')
// Visit each account page, edit details, re-validate amounts
cy.contains('a', 'Sandbox Checking').click()
cy.getByTestId('current-data-value').should('contain.text', '$5,000.00')
cy.contains('a', 'Sandbox Savings').click()
cy.getByTestId('current-data-value').should('contain.text', '$15,000.00')
cy.contains('a', 'Sandbox CC').click()
cy.getByTestId('current-data-value').should('contain.text', '$950.00')
openEditAccountModal()
cy.getByTestId('connected-account-form').within(() => {
cy.get('input[name="name"]')
.should('have.value', 'Sandbox CC')
.clear()
.type('Credit Credit')
cy.get('input[name="categoryUser"]').should('have.value', 'credit')
cy.get('input[name="startDate"]')
.should('have.value', '')
.type(DateTime.now().minus({ months: 1 }).toFormat('MMddyyyy'))
cy.root().submit()
})
// Should be able to submit empty start date on connected account
openEditAccountModal()
cy.getByTestId('connected-account-form').within(() => {
cy.get('input[name="startDate"]').clear()
cy.root().submit()
})
})
it('should interpolate and display manual vehicle account data', () => {
cy.getByTestId('add-account-button').click()
cy.contains('h4', 'Add account')
@ -149,7 +74,8 @@ describe('Accounts', () => {
cy.contains('h4', 'Add real estate')
// Details
cy.get('input[name="country"]').select('GB')
cy.contains('label', 'Country').click()
cy.contains('button', 'Uganda').click()
cy.get('input[name="line1"]').focus().type('123 Example St')
cy.get('input[name="city"]').type('New York')
cy.get('input[name="state"]').type('NY')
@ -188,7 +114,9 @@ describe('Accounts', () => {
openEditAccountModal()
cy.getByTestId('property-form').within(() => {
cy.get('input[name="country]').should('have.value', 'GB').clear().select('FR')
cy.get('input[name="country"]').should('have.value', 'UG')
cy.contains('label', 'Country').click()
cy.contains('button', 'United States').click()
cy.get('input[name="line1"]')
.should('have.value', '123 Example St')
.clear()

View file

@ -0,0 +1,9 @@
describe('Auth', () => {
beforeEach(() => cy.visit('/'))
describe('Logging in', () => {
it('should show the home page of an authenticated user', () => {
cy.contains('h5', 'Assets & Debts')
})
})
})

View file

@ -1,17 +1,13 @@
import jwtDecode from 'jwt-decode'
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(username: string, password: string): Chainable<any>
apiRequest(...params: Parameters<typeof cy.request>): Chainable<any>
getByTestId(...parameters: Parameters<typeof cy.get>): Chainable<any>
selectDate(date: Date): Chainable<any>
preserveAccessToken(): Chainable<any>
restoreAccessToken(): Chainable<any>
}
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(): Chainable<any>
apiRequest(...params: Parameters<typeof cy.request>): Chainable<any>
getByTestId(...parameters: Parameters<typeof cy.get>): Chainable<any>
selectDate(date: Date): Chainable<any>
preserveAccessToken(): Chainable<any>
restoreAccessToken(): Chainable<any>
}
}
@ -20,13 +16,10 @@ Cypress.Commands.add('getByTestId', (testId, ...rest) => {
})
Cypress.Commands.add('apiRequest', ({ url, headers = {}, ...options }, ...rest) => {
const accessToken = window.localStorage.getItem('token')
return cy.request(
{
url: `${Cypress.env('API_URL')}/${url}`,
headers: {
Authorization: `Bearer ${accessToken}`,
...headers,
},
...options,
@ -35,74 +28,11 @@ Cypress.Commands.add('apiRequest', ({ url, headers = {}, ...options }, ...rest)
)
})
/**
* Logs in with the engineering CI account
*/
Cypress.Commands.add('login', (username, password) => {
// Preserves login across tests
cy.session('login-session-key', login, {
validate() {
cy.apiRequest({ url: 'e2e' }).its('status').should('eq', 200)
},
})
function login() {
const client_id = Cypress.env('AUTH0_CLIENT_ID')
const audience = 'https://maybe-finance-api/v1'
const scope = 'openid profile email offline_access'
const accessTokenStorageKey = `@@auth0spajs@@::${client_id}::${audience}::${scope}`
const AUTH_DOMAIN = Cypress.env('AUTH0_DOMAIN')
cy.log(`Logging in as ${username}`)
/**
* Uses the official Cypress Auth0 strategy for testing with Auth0
* https://docs.cypress.io/guides/testing-strategies/auth0-authentication#Auth0-Application-Setup
*
* Relevant Auth0 endpoint
* https://auth0.com/docs/api/authentication?javascript#resource-owner-password
*/
cy.request({
method: 'POST',
url: `https://${AUTH_DOMAIN}/oauth/token`,
body: {
grant_type: 'password',
username,
password,
audience,
scope,
client_id,
},
}).then(({ body }) => {
const claims = jwtDecode<any>(body.id_token)
const { nickname, name, picture, updated_at, email, email_verified, sub, exp } = claims
const item = {
body: {
...body,
decodedToken: {
claims,
user: {
nickname,
name,
picture,
updated_at,
email,
email_verified,
sub,
},
audience,
client_id,
},
},
expiresAt: exp,
}
window.localStorage.setItem(accessTokenStorageKey, JSON.stringify(item))
window.localStorage.setItem('token', body.access_token)
cy.visit('/')
})
}
Cypress.Commands.add('login', () => {
cy.visit('/login')
cy.get('input[name="email"]').type('bond@007.com')
cy.get('input[name="password"]').type('TestPassword123')
cy.get('button[type="submit"]').click()
//eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000)
})

View file

@ -2,8 +2,7 @@ import './commands'
beforeEach(() => {
// Login
// Rate limit 30 / min - https://auth0.com/docs/troubleshoot/customer-support/operational-policies/rate-limit-policy#limits-for-non-production-tenants-of-paying-customers-and-all-tenants-of-free-customers
cy.login(Cypress.env('AUTH0_EMAIL'), Cypress.env('AUTH0_PASSWORD'))
cy.login()
// Delete the current user to wipe all data before test
cy.apiRequest({
@ -14,6 +13,6 @@ beforeEach(() => {
expect(response.status).to.equal(200)
})
// Re-login (JWT should still be valid)
// Go back to dashboard
cy.visit('/')
})

View file

@ -0,0 +1,2 @@
import './commands'
import './e2e'

View file

@ -6,13 +6,13 @@ import endpoint from '../lib/endpoint'
const router = Router()
router.use((req, res, next) => {
const roles = req.user?.['https://maybe.co/roles']
const testUserId = 'test_ec3ee8a4-fa01-4f11-8ac5-9c49dd7fbae4'
if (roles?.includes('CIUser') || roles?.includes('Admin')) {
router.use((req, res, next) => {
if (req.user?.sub === testUserId) {
next()
} else {
res.status(401).send('Route only available to CIUser and Admin roles')
res.status(401).send('Route only available to test users')
}
})
@ -47,14 +47,14 @@ router.post(
trialLapsed: z.boolean().default(false),
}),
resolve: async ({ ctx, input }) => {
ctx.logger.debug(`Resetting CI user ${ctx.user!.authId}`)
await ctx.prisma.$transaction([
ctx.prisma.$executeRaw`DELETE FROM "user" WHERE auth_id=${ctx.user!.authId};`,
ctx.prisma.$executeRaw`DELETE FROM "user" WHERE auth_id=${testUserId};`,
ctx.prisma.user.create({
data: {
authId: ctx.user!.authId,
email: 'REPLACE_THIS',
authId: testUserId,
email: 'bond@007.com',
firstName: 'James',
lastName: 'Bond',
dob: new Date('1990-01-01'),
linkAccountDismissedAt: new Date(), // ensures our auto-account link doesn't trigger

View file

@ -1,4 +1,5 @@
import { Institution, PrismaClient, Provider } from '@prisma/client'
import bcrypt from 'bcrypt'
const prisma = new PrismaClient()
@ -27,7 +28,23 @@ async function main() {
},
]
const hashedPassword = await bcrypt.hash('TestPassword123', 10)
await prisma.$transaction([
// create testing auth user
prisma.authUser.upsert({
where: {
id: 'test_ec3ee8a4-fa01-4f11-8ac5-9c49dd7fbae4',
},
create: {
id: 'test_ec3ee8a4-fa01-4f11-8ac5-9c49dd7fbae4',
firstName: 'James',
lastName: 'Bond',
email: 'bond@007.com',
password: hashedPassword,
},
update: {},
}),
// create institution linked to provider institutions
...institutions.map(({ id, name, providers }) =>
prisma.institution.upsert({