Skip to content

Commit

Permalink
Fix: Update Jurisdictional Admin E2E Tests and Correct Jurisdictional…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
mcgarrye authored Apr 18, 2024
1 parent c2419da commit 76d1075
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 36 deletions.
4 changes: 4 additions & 0 deletions api/src/enums/user/view-enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum UserViews {
base = 'base',
full = 'full',
}
8 changes: 3 additions & 5 deletions api/src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -186,7 +187,7 @@ export class AuthService {
async requestMfaCode(dto: RequestMfaCode): Promise<RequestMfaCodeResponse> {
const user = await this.userService.findUserOrError(
{ email: dto.email },
true,
UserViews.full,
);

if (!user.mfaEnabled) {
Expand Down Expand Up @@ -291,10 +292,7 @@ export class AuthService {
async confirmUser(dto: Confirm, res?: Response): Promise<SuccessDTO> {
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(
Expand Down
54 changes: 38 additions & 16 deletions api/src/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<Record<UserViews, Prisma.UserAccountsInclude>> = {
base: {
jurisdictions: true,
userRoles: true,
},
};

views.full = {
...views.base,
listings: true,
jurisdictions: true,
userRoles: true,
};

type findByOptions = {
Expand Down Expand Up @@ -97,7 +104,7 @@ export class UserService {
['firstName', 'lastName'],
[OrderByEnum.ASC, OrderByEnum.ASC],
),
include: view,
include: views.full,
where: whereClause,
});

Expand All @@ -115,7 +122,10 @@ export class UserService {
this will return 1 user or error
*/
async findOne(userId: string): Promise<User> {
const rawUser = await this.findUserOrError({ userId: userId }, true);
const rawUser = await this.findUserOrError(
{ userId: userId },
UserViews.full,
);
return mapTo(User, rawUser);
}

Expand All @@ -127,7 +137,10 @@ export class UserService {
requestingUser: User,
jurisdictionName: string,
): Promise<User> {
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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<SuccessDTO> {
const targetUser = await this.findUserOrError({ userId: userId }, false);
const targetUser = await this.findUserOrError(
{ userId: userId },
UserViews.base,
);

this.authorizeAction(
requestingUser,
Expand Down Expand Up @@ -325,7 +341,10 @@ export class UserService {
dto: EmailAndAppUrl,
forPublic: boolean,
): Promise<SuccessDTO> {
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(
Expand Down Expand Up @@ -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<SuccessDTO> {
const storedUser = await this.findUserOrError({ email: dto.email }, true);
const storedUser = await this.findUserOrError(
{ email: dto.email },
UserViews.full,
);

const payload = {
id: storedUser.id,
Expand Down Expand Up @@ -505,7 +527,7 @@ export class UserService {
);
}
const existingUser = await this.prisma.userAccounts.findUnique({
include: view,
include: views.full,
where: {
email: dto.email,
},
Expand All @@ -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: {
Expand Down Expand Up @@ -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 })),
Expand Down Expand Up @@ -641,7 +663,7 @@ export class UserService {
newUser.email,
);
newUser = await this.prisma.userAccounts.update({
include: view,
include: views.full,
data: {
confirmationToken: confirmationToken,
},
Expand Down Expand Up @@ -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,
Expand All @@ -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,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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())
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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, '[email protected]'))
.send(
// builds an invite for an admin
buildUserInviteMock(jurisId, '[email protected]'),
)
.set('Cookie', cookies)
.expect(403);
});
Expand Down
40 changes: 35 additions & 5 deletions api/test/unit/services/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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: {
Expand All @@ -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 = '[email protected]';
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: {
Expand All @@ -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: {
Expand All @@ -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: {
Expand All @@ -435,7 +457,7 @@ describe('Testing user service', () => {
const email = '[email protected]';
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: [email protected] was requested but not found',
);
Expand Down Expand Up @@ -859,6 +881,10 @@ describe('Testing user service', () => {
where: {
id,
},
include: {
jurisdictions: true,
userRoles: true,
},
});
expect(prisma.userAccounts.delete).toHaveBeenCalledWith({
where: {
Expand Down Expand Up @@ -902,6 +928,10 @@ describe('Testing user service', () => {
where: {
id,
},
include: {
jurisdictions: true,
userRoles: true,
},
});

expect(prisma.userAccounts.delete).not.toHaveBeenCalled();
Expand Down

0 comments on commit 76d1075

Please sign in to comment.