From 76d1075e530eabb6085c4453ab9788d72278b9b3 Mon Sep 17 00:00:00 2001 From: Eric McGarry <46828798+mcgarrye@users.noreply.github.com> Date: Thu, 18 Apr 2024 15:41:24 -0400 Subject: [PATCH] Fix: Update Jurisdictional Admin E2E Tests and Correct Jurisdictional Admin Delete User (#4026) * fix: join jurisdictions on user for delete 4024 * fix: update tests 4024 * fix: make sure userRoles are present for deletion 4024 * fix: correct tests with userRoles for deletes --- api/src/enums/user/view-enum.ts | 4 ++ api/src/services/auth.service.ts | 8 ++- api/src/services/user.service.ts | 54 +++++++++++++------ ...n-as-juris-admin-correct-juris.e2e-spec.ts | 24 +++++---- api/test/unit/services/user.service.spec.ts | 40 ++++++++++++-- 5 files changed, 94 insertions(+), 36 deletions(-) create mode 100644 api/src/enums/user/view-enum.ts diff --git a/api/src/enums/user/view-enum.ts b/api/src/enums/user/view-enum.ts new file mode 100644 index 0000000000..e99380b046 --- /dev/null +++ b/api/src/enums/user/view-enum.ts @@ -0,0 +1,4 @@ +export enum UserViews { + base = 'base', + full = 'full', +} diff --git a/api/src/services/auth.service.ts b/api/src/services/auth.service.ts index 4e5a995ad7..d8ab5622f6 100644 --- a/api/src/services/auth.service.ts +++ b/api/src/services/auth.service.ts @@ -9,6 +9,7 @@ import { sign, verify } from 'jsonwebtoken'; import { Prisma } from '@prisma/client'; import { UpdatePassword } from '../dtos/auth/update-password.dto'; import { MfaType } from '../enums/mfa/mfa-type-enum'; +import { UserViews } from '../enums/user/view-enum'; import { isPasswordValid, passwordToHash } from '../utilities/password-helpers'; import { RequestMfaCodeResponse } from '../dtos/mfa/request-mfa-code-response.dto'; import { RequestMfaCode } from '../dtos/mfa/request-mfa-code.dto'; @@ -186,7 +187,7 @@ export class AuthService { async requestMfaCode(dto: RequestMfaCode): Promise { const user = await this.userService.findUserOrError( { email: dto.email }, - true, + UserViews.full, ); if (!user.mfaEnabled) { @@ -291,10 +292,7 @@ export class AuthService { async confirmUser(dto: Confirm, res?: Response): Promise { const token = verify(dto.token, process.env.APP_SECRET) as IdAndEmail; - let user = await this.userService.findUserOrError( - { userId: token.id }, - false, - ); + let user = await this.userService.findUserOrError({ userId: token.id }); if (user.confirmationToken !== dto.token) { throw new BadRequestException( diff --git a/api/src/services/user.service.ts b/api/src/services/user.service.ts index 734fe809c4..d1a307c578 100644 --- a/api/src/services/user.service.ts +++ b/api/src/services/user.service.ts @@ -36,6 +36,7 @@ import { UserCreate } from '../dtos/users/user-create.dto'; import { EmailService } from './email.service'; import { PermissionService } from './permission.service'; import { permissionActions } from '../enums/permissions/permission-actions-enum'; +import { UserViews } from '../enums/user/view-enum'; import { buildWhereClause } from '../utilities/build-user-where'; import { getPublicEmailURL } from '../utilities/get-public-email-url'; import { UserRole } from '../dtos/users/user-role.dto'; @@ -47,10 +48,16 @@ import { generateSingleUseCode } from '../utilities/generate-single-use-code'; it handles all the backend's business logic for reading/writing/deleting user data */ -const view: Prisma.UserAccountsInclude = { +const views: Partial> = { + base: { + jurisdictions: true, + userRoles: true, + }, +}; + +views.full = { + ...views.base, listings: true, - jurisdictions: true, - userRoles: true, }; type findByOptions = { @@ -97,7 +104,7 @@ export class UserService { ['firstName', 'lastName'], [OrderByEnum.ASC, OrderByEnum.ASC], ), - include: view, + include: views.full, where: whereClause, }); @@ -115,7 +122,10 @@ export class UserService { this will return 1 user or error */ async findOne(userId: string): Promise { - const rawUser = await this.findUserOrError({ userId: userId }, true); + const rawUser = await this.findUserOrError( + { userId: userId }, + UserViews.full, + ); return mapTo(User, rawUser); } @@ -127,7 +137,10 @@ export class UserService { requestingUser: User, jurisdictionName: string, ): Promise { - const storedUser = await this.findUserOrError({ userId: dto.id }, true); + const storedUser = await this.findUserOrError( + { userId: dto.id }, + UserViews.full, + ); if (dto.jurisdictions?.length) { // if the incoming dto has jurisdictions make sure the user has access to update users in that jurisdiction @@ -253,7 +266,7 @@ export class UserService { } const res = await this.prisma.userAccounts.update({ - include: view, + include: views.full, data: { email: dto.email, agreedToTermsOfService: dto.agreedToTermsOfService, @@ -291,7 +304,10 @@ export class UserService { this will delete a user or error if no user is found with the Id */ async delete(userId: string, requestingUser: User): Promise { - const targetUser = await this.findUserOrError({ userId: userId }, false); + const targetUser = await this.findUserOrError( + { userId: userId }, + UserViews.base, + ); this.authorizeAction( requestingUser, @@ -325,7 +341,10 @@ export class UserService { dto: EmailAndAppUrl, forPublic: boolean, ): Promise { - const storedUser = await this.findUserOrError({ email: dto.email }, true); + const storedUser = await this.findUserOrError( + { email: dto.email }, + UserViews.full, + ); if (!storedUser.confirmedAt) { const confirmationToken = this.createConfirmationToken( @@ -377,7 +396,10 @@ export class UserService { sets a reset token so a user can recover their account if they forgot the password */ async forgotPassword(dto: EmailAndAppUrl): Promise { - const storedUser = await this.findUserOrError({ email: dto.email }, true); + const storedUser = await this.findUserOrError( + { email: dto.email }, + UserViews.full, + ); const payload = { id: storedUser.id, @@ -505,7 +527,7 @@ export class UserService { ); } const existingUser = await this.prisma.userAccounts.findUnique({ - include: view, + include: views.full, where: { email: dto.email, }, @@ -516,7 +538,7 @@ export class UserService { if (!existingUser.userRoles && 'userRoles' in dto) { // existing user && public user && user will get roles -> trying to grant partner access to a public user const res = await this.prisma.userAccounts.update({ - include: view, + include: views.full, data: { userRoles: { create: { @@ -552,7 +574,7 @@ export class UserService { .concat(dto.listings); const res = await this.prisma.userAccounts.update({ - include: view, + include: views.full, data: { jurisdictions: { connect: jursidictions.map((juris) => ({ id: juris.id })), @@ -641,7 +663,7 @@ export class UserService { newUser.email, ); newUser = await this.prisma.userAccounts.update({ - include: view, + include: views.full, data: { confirmationToken: confirmationToken, }, @@ -747,7 +769,7 @@ export class UserService { this will return 1 user or error takes in a userId or email to find by, and a boolean to indicate if joins should be included */ - async findUserOrError(findBy: findByOptions, includeJoins: boolean) { + async findUserOrError(findBy: findByOptions, view?: UserViews) { const where: Prisma.UserAccountsWhereUniqueInput = { id: undefined, email: undefined, @@ -758,7 +780,7 @@ export class UserService { where.email = findBy.email; } const rawUser = await this.prisma.userAccounts.findUnique({ - include: includeJoins ? view : undefined, + include: view ? views[view] : undefined, where, }); diff --git a/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts b/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts index 178f3218e4..5e2ede1f75 100644 --- a/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts +++ b/api/test/integration/permission-tests/permission-as-juris-admin-correct-juris.e2e-spec.ts @@ -819,20 +819,20 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr .expect(200); }); - it('should error as forbidden for retrieve endpoint', async () => { + it('should succeed for retrieve endpoint', async () => { const userA = await prisma.userAccounts.create({ - data: await userFactory(), + data: await userFactory({ jurisdictionIds: [jurisId] }), }); await request(app.getHttpServer()) .get(`/user/${userA.id}`) .set('Cookie', cookies) - .expect(403); + .expect(200); }); - it('should error as forbidden for update endpoint', async () => { + it('should succeed for update endpoint', async () => { const userA = await prisma.userAccounts.create({ - data: await userFactory(), + data: await userFactory({ jurisdictionIds: [jurisId] }), }); await request(app.getHttpServer()) @@ -841,14 +841,15 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr id: userA.id, firstName: 'New User First Name', lastName: 'New User Last Name', + jurisdictions: [{ id: jurisId } as IdDTO], } as UserUpdate) .set('Cookie', cookies) - .expect(403); + .expect(200); }); - it('should error as forbidden for delete endpoint', async () => { + it('should succeed for delete endpoint', async () => { const userA = await prisma.userAccounts.create({ - data: await userFactory(), + data: await userFactory({ jurisdictionIds: [jurisId] }), }); await request(app.getHttpServer()) @@ -857,7 +858,7 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr id: userA.id, } as IdDTO) .set('Cookie', cookies) - .expect(403); + .expect(200); }); it('should succeed for public resend confirmation endpoint', async () => { @@ -950,7 +951,10 @@ describe('Testing Permissioning of endpoints as Jurisdictional Admin in the corr await request(app.getHttpServer()) .post(`/user/invite`) - .send(buildUserInviteMock(juris, 'partnerUser+jurisCorrect@email.com')) + .send( + // builds an invite for an admin + buildUserInviteMock(jurisId, 'partnerUser+jurisCorrect@email.com'), + ) .set('Cookie', cookies) .expect(403); }); diff --git a/api/test/unit/services/user.service.spec.ts b/api/test/unit/services/user.service.spec.ts index 6cfd6a259e..f575a110fe 100644 --- a/api/test/unit/services/user.service.spec.ts +++ b/api/test/unit/services/user.service.spec.ts @@ -17,6 +17,7 @@ import { User } from '../../../src/dtos/users/user.dto'; import { PermissionService } from '../../../src/services/permission.service'; import { permissionActions } from '../../../src/enums/permissions/permission-actions-enum'; import { OrderByEnum } from '../../../src/enums/shared/order-by-enum'; +import { UserViews } from '../../../src/enums/user/view-enum'; describe('Testing user service', () => { let service: UserService; @@ -370,7 +371,7 @@ describe('Testing user service', () => { prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({ id, }); - const res = await service.findUserOrError({ userId: id }, true); + const res = await service.findUserOrError({ userId: id }, UserViews.full); expect(res).toEqual({ id }); expect(prisma.userAccounts.findUnique).toHaveBeenCalledWith({ include: { @@ -384,12 +385,33 @@ describe('Testing user service', () => { }); }); + it('should find user by id and include only jurisdictions joins', async () => { + const id = randomUUID(); + prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({ + id, + }); + const res = await service.findUserOrError({ userId: id }, UserViews.base); + expect(res).toEqual({ id }); + expect(prisma.userAccounts.findUnique).toHaveBeenCalledWith({ + include: { + jurisdictions: true, + userRoles: true, + }, + where: { + id, + }, + }); + }); + it('should find user by email and include joins', async () => { const email = 'example@email.com'; prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({ email, }); - const res = await service.findUserOrError({ email: email }, true); + const res = await service.findUserOrError( + { email: email }, + UserViews.full, + ); expect(res).toEqual({ email }); expect(prisma.userAccounts.findUnique).toHaveBeenCalledWith({ include: { @@ -408,7 +430,7 @@ describe('Testing user service', () => { prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({ id, }); - const res = await service.findUserOrError({ userId: id }, false); + const res = await service.findUserOrError({ userId: id }); expect(res).toEqual({ id }); expect(prisma.userAccounts.findUnique).toHaveBeenCalledWith({ where: { @@ -422,7 +444,7 @@ describe('Testing user service', () => { prisma.userAccounts.findUnique = jest.fn().mockResolvedValue({ email, }); - const res = await service.findUserOrError({ email: email }, false); + const res = await service.findUserOrError({ email: email }); expect(res).toEqual({ email }); expect(prisma.userAccounts.findUnique).toHaveBeenCalledWith({ where: { @@ -435,7 +457,7 @@ describe('Testing user service', () => { const email = 'example@email.com'; prisma.userAccounts.findUnique = jest.fn().mockResolvedValue(null); await expect( - async () => await service.findUserOrError({ email: email }, false), + async () => await service.findUserOrError({ email: email }), ).rejects.toThrowError( 'user email: example@email.com was requested but not found', ); @@ -859,6 +881,10 @@ describe('Testing user service', () => { where: { id, }, + include: { + jurisdictions: true, + userRoles: true, + }, }); expect(prisma.userAccounts.delete).toHaveBeenCalledWith({ where: { @@ -902,6 +928,10 @@ describe('Testing user service', () => { where: { id, }, + include: { + jurisdictions: true, + userRoles: true, + }, }); expect(prisma.userAccounts.delete).not.toHaveBeenCalled();