1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-10 07:55:21 +02:00

rework test to avoid seeding test user

This commit is contained in:
Tyler Myracle 2024-01-21 20:21:11 -06:00
parent 06b861aea7
commit 9ea0ec6b7a
12 changed files with 78 additions and 130 deletions

View file

@ -1,14 +0,0 @@
{
"presets": [
"@babel/preset-typescript",
"@babel/preset-env",
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View file

@ -1,18 +0,0 @@
const rootMain = require('../../../.storybook/main')
module.exports = {
...rootMain,
core: { ...rootMain.core, builder: 'webpack5' },
stories: ['../**/*.stories.@(js|jsx|ts|tsx)'],
addons: [...rootMain.addons, '@nrwl/react/plugins/storybook'],
webpackFinal: async (config, { configType }) => {
// apply any global webpack configs that might have been specified in .storybook/main.js
if (rootMain.webpackFinal) {
config = await rootMain.webpackFinal(config, { configType })
}
// add your own webpack tweaks if needed
return config
},
}

View file

@ -1,18 +0,0 @@
import type { Story, Meta } from '@storybook/react'
import Maintenance from './Maintenance.tsx'
import React from 'react'
export default {
title: 'components/Maintenance.tsx',
component: Maintenance,
} as Meta
const Template: Story = () => {
return (
<>
<Maintenance />
</>
)
}
export const Base = Template.bind({})

View file

@ -37,7 +37,7 @@ async function validateCredentials(credentials: any): Promise<z.infer<typeof aut
lastName: z.string().optional(), lastName: z.string().optional(),
email: z.string().email({ message: 'Invalid email address.' }), email: z.string().email({ message: 'Invalid email address.' }),
password: z.string().min(6), password: z.string().min(6),
isAdmin: z.boolean().default(false), role: z.string().default('user'),
}) })
const parsed = authSchema.safeParse(credentials) const parsed = authSchema.safeParse(credentials)
@ -53,15 +53,26 @@ async function createNewAuthUser(credentials: {
lastName: string lastName: string
email: string email: string
password: string password: string
isAdmin: boolean role: string
}): Promise<SharedType.AuthUser> { }): Promise<SharedType.AuthUser> {
const { firstName, lastName, email, password, isAdmin } = credentials const { firstName, lastName, email, password, role } = credentials
if (!firstName || !lastName) { if (!firstName || !lastName) {
throw new Error('Both first name and last name are required.') throw new Error('Both first name and last name are required.')
} }
const isDevelopment = process.env.NODE_ENV === 'development' const isDevelopment = process.env.NODE_ENV === 'development'
let userRole: AuthUserRole
if (role === AuthUserRole.admin && isDevelopment) {
userRole = AuthUserRole.admin
} else if (role === AuthUserRole.ci) {
userRole = AuthUserRole.ci
} else {
userRole = AuthUserRole.user
}
const hashedPassword = await bcrypt.hash(password, 10) const hashedPassword = await bcrypt.hash(password, 10)
return createAuthUser({ return createAuthUser({
firstName, firstName,
@ -69,7 +80,7 @@ async function createNewAuthUser(credentials: {
name: `${firstName} ${lastName}`, name: `${firstName} ${lastName}`,
email, email,
password: hashedPassword, password: hashedPassword,
role: isAdmin && isDevelopment ? AuthUserRole.admin : AuthUserRole.user, role: userRole,
}) })
} }
@ -99,14 +110,11 @@ export const authOptions = {
lastName: { label: 'Last name', type: 'text', placeholder: 'Last name' }, lastName: { label: 'Last name', type: 'text', placeholder: 'Last name' },
email: { label: 'Email', type: 'email', placeholder: 'hello@maybe.co' }, email: { label: 'Email', type: 'email', placeholder: 'hello@maybe.co' },
password: { label: 'Password', type: 'password' }, password: { label: 'Password', type: 'password' },
isAdmin: { label: 'Admin', type: 'checkbox' }, role: { label: 'Admin', type: 'text' },
}, },
async authorize(credentials) { async authorize(credentials) {
const { firstName, lastName, email, password, isAdmin } = await validateCredentials( const { firstName, lastName, email, password, role } = await validateCredentials(
{ credentials
...credentials,
isAdmin: Boolean(credentials?.isAdmin),
}
) )
const existingUser = await getAuthUserByEmail(email) const existingUser = await getAuthUserByEmail(email)
@ -123,7 +131,7 @@ export const authOptions = {
throw new Error('Invalid credentials provided.') throw new Error('Invalid credentials provided.')
} }
return createNewAuthUser({ firstName, lastName, email, password, isAdmin }) return createNewAuthUser({ firstName, lastName, email, password, role })
}, },
}), }),
], ],

View file

@ -15,7 +15,7 @@ export default function RegisterPage() {
const [isValid, setIsValid] = useState(false) const [isValid, setIsValid] = useState(false)
const [errorMessage, setErrorMessage] = useState<string | null>(null) const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [isAdmin, setIsAdmin] = useState(false) const [isAdmin, setIsAdmin] = useState<boolean>(false)
const { data: session } = useSession() const { data: session } = useSession()
const router = useRouter() const router = useRouter()
@ -39,7 +39,7 @@ export default function RegisterPage() {
password, password,
firstName, firstName,
lastName, lastName,
isAdmin, role: isAdmin ? 'admin' : 'user',
redirect: false, redirect: false,
}) })

View file

@ -27,14 +27,7 @@
"next-env.d.ts", "next-env.d.ts",
".next/types/**/*.ts" ".next/types/**/*.ts"
], ],
"exclude": [ "exclude": ["node_modules", "jest.config.ts"],
"node_modules",
"jest.config.ts",
"**/*.stories.ts",
"**/*.stories.js",
"**/*.stories.jsx",
"**/*.stories.tsx"
],
"references": [ "references": [
{ {
"path": "./.storybook/tsconfig.json" "path": "./.storybook/tsconfig.json"

View file

@ -4,6 +4,7 @@ declare namespace Cypress {
interface Chainable<Subject> { interface Chainable<Subject> {
login(): Chainable<any> login(): Chainable<any>
apiRequest(...params: Parameters<typeof cy.request>): Chainable<any> apiRequest(...params: Parameters<typeof cy.request>): Chainable<any>
nextApiRequest(...params: Parameters<typeof cy.request>): Chainable<any>
getByTestId(...parameters: Parameters<typeof cy.get>): Chainable<any> getByTestId(...parameters: Parameters<typeof cy.get>): Chainable<any>
selectDate(date: Date): Chainable<any> selectDate(date: Date): Chainable<any>
preserveAccessToken(): Chainable<any> preserveAccessToken(): Chainable<any>
@ -28,9 +29,22 @@ Cypress.Commands.add('apiRequest', ({ url, headers = {}, ...options }, ...rest)
) )
}) })
Cypress.Commands.add('nextApiRequest', ({ url, headers = {}, ...options }, ...rest) => {
return cy.request(
{
url: `${Cypress.env('NEXTAUTH_URL')}/${url}`,
headers: {
...headers,
},
...options,
},
...rest
)
})
Cypress.Commands.add('login', () => { Cypress.Commands.add('login', () => {
cy.visit('/login') cy.visit('/login')
cy.get('input[name="email"]').type('bond@007.com') cy.get('input[name="email"]').type('test@test.com')
cy.get('input[name="password"]').type('TestPassword123') cy.get('input[name="password"]').type('TestPassword123')
cy.get('button[type="submit"]').click() cy.get('button[type="submit"]').click()
//eslint-disable-next-line cypress/no-unnecessary-waiting //eslint-disable-next-line cypress/no-unnecessary-waiting

View file

@ -1,10 +1,36 @@
import './commands' import './commands'
beforeEach(() => { beforeEach(() => {
// Login cy.request({
cy.login() method: 'GET',
url: 'api/auth/csrf',
}).then((response) => {
let csrfCookies = response.headers['set-cookie']
if (Array.isArray(csrfCookies) && csrfCookies.length > 1) {
csrfCookies = csrfCookies.map((cookie) => cookie.split(';')[0]).join('; ')
}
const csrfToken = response.body.csrfToken.trim()
// Delete the current user to wipe all data before test cy.request({
method: 'POST',
form: true,
headers: {
Cookie: `${csrfCookies}`,
},
url: `api/auth/callback/credentials`,
body: {
email: 'test@test.com',
firstName: 'Test',
lastName: 'User',
password: 'TestPassword123',
role: 'ci',
csrfToken: csrfToken,
json: 'true',
},
}).then((response) => {
expect(response.status).to.equal(200)
})
})
cy.apiRequest({ cy.apiRequest({
method: 'POST', method: 'POST',
url: 'e2e/reset', url: 'e2e/reset',
@ -12,7 +38,5 @@ beforeEach(() => {
}).then((response) => { }).then((response) => {
expect(response.status).to.equal(200) expect(response.status).to.equal(200)
}) })
// Go back to dashboard
cy.visit('/') cy.visit('/')
}) })

View file

@ -1,4 +1,5 @@
import type { OnboardingState } from '@maybe-finance/server/features' import type { OnboardingState } from '@maybe-finance/server/features'
import { AuthUserRole } from '@prisma/client'
import { Router } from 'express' import { Router } from 'express'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
import { z } from 'zod' import { z } from 'zod'
@ -6,13 +7,13 @@ import endpoint from '../lib/endpoint'
const router = Router() const router = Router()
const testUserId = 'test_ec3ee8a4-fa01-4f11-8ac5-9c49dd7fbae4'
router.use((req, res, next) => { router.use((req, res, next) => {
if (req.user?.sub === testUserId) { const role = req.user?.role
if (role === AuthUserRole.admin || role === AuthUserRole.ci) {
next() next()
} else { } else {
res.status(401).send('Route only available to test users') res.status(401).send('Route only available to CIUser and Admin roles')
} }
}) })
@ -47,14 +48,15 @@ router.post(
trialLapsed: z.boolean().default(false), trialLapsed: z.boolean().default(false),
}), }),
resolve: async ({ ctx, input }) => { resolve: async ({ ctx, input }) => {
const user = ctx.user!
await ctx.prisma.$transaction([ await ctx.prisma.$transaction([
ctx.prisma.$executeRaw`DELETE FROM "user" WHERE auth_id=${testUserId};`, ctx.prisma.$executeRaw`DELETE FROM "user" WHERE auth_id=${user.authId};`,
ctx.prisma.user.create({ ctx.prisma.user.create({
data: { data: {
authId: testUserId, authId: user.authId,
email: 'bond@007.com', email: user.email,
firstName: 'James', firstName: user.firstName,
lastName: 'Bond', lastName: user.lastName,
dob: new Date('1990-01-01'), dob: new Date('1990-01-01'),
linkAccountDismissedAt: new Date(), // ensures our auto-account link doesn't trigger linkAccountDismissedAt: new Date(), // ensures our auto-account link doesn't trigger

View file

@ -687,7 +687,7 @@ export class InsightService implements IInsightService {
INNER JOIN security s ON s.id = h.security_id INNER JOIN security s ON s.id = h.security_id
LEFT JOIN LATERAL ( LEFT JOIN LATERAL (
SELECT SELECT
asset_class AS "category" s.asset_class AS "category"
) x ON TRUE ) x ON TRUE
WHERE WHERE
h.account_id IN ${accountIds} h.account_id IN ${accountIds}

View file

@ -35,23 +35,7 @@ async function main() {
}, },
] ]
const hashedPassword = await bcrypt.hash('TestPassword123', 10)
await prisma.$transaction([ 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 // create institution linked to provider institutions
...institutions.map(({ id, name, providers }) => ...institutions.map(({ id, name, providers }) =>
prisma.institution.upsert({ prisma.institution.upsert({

View file

@ -66,33 +66,6 @@
"options": { "options": {
"command": "node tools/scripts/triggerClientDeploy.js" "command": "node tools/scripts/triggerClientDeploy.js"
} }
},
"storybook": {
"executor": "@nrwl/storybook:storybook",
"options": {
"uiFramework": "@storybook/react",
"port": 4400,
"configDir": "apps/client/.storybook"
},
"configurations": {
"ci": {
"quiet": true
}
}
},
"build-storybook": {
"executor": "@nrwl/storybook:build",
"outputs": ["{options.outputDir}"],
"options": {
"uiFramework": "@storybook/react",
"outputDir": "dist/storybook/client",
"configDir": "apps/client/.storybook"
},
"configurations": {
"ci": {
"quiet": true
}
}
} }
}, },
"tags": ["scope:app"] "tags": ["scope:app"]