-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/permanent delete person #579
Merged
slavcho
merged 18 commits into
podkrepi-bg:master
from
PetarDimitrov91:feature/permanent-delete-person
Nov 23, 2023
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
321d13d
FEAT: created permanent delete methods in account.service.ts & auth.s…
PetarDimitrov91 3027d0c
CHORE: DB entities
PetarDimitrov91 1c3ae8c
Merge branch 'master' into feature/permanent-delete-person
PetarDimitrov91 2c48164
FEAT: refactor permanentDeleteUser. Add exception handling.
PetarDimitrov91 4649ce3
FEAT: change permanentDeleteUser in auth.service.ts logic so when the…
PetarDimitrov91 2a94f5d
FEAT: improved the permanentDeleteUser promise chain and write some t…
PetarDimitrov91 9b63bef
FEAT: added safeguard to ensure that the deleted person is not corpor…
PetarDimitrov91 c9a866c
FEAT: fix if to check the array length and adjust tests.
PetarDimitrov91 7ca574d
FIX: wrong .env commit
PetarDimitrov91 3e5848c
FIX: wrong .env commit #2
PetarDimitrov91 68fb84b
CHORE: reformat.
PetarDimitrov91 150104c
Run yarn format
sashko9807 101c401
Merge pull request #1 from sashko9807/feature/permanent-delete-person
PetarDimitrov91 3b443fb
FIX: check for corporate profile to use companyId.
PetarDimitrov91 3d9782b
FIX: return organizer in the if statement & adjust tests
PetarDimitrov91 53b89df
CHORE: update comment
PetarDimitrov91 4f9b604
Merge remote-tracking branch 'upstream/master' into feature/permanent…
PetarDimitrov91 562ed30
FIX: made if in deleteUser() more readable
PetarDimitrov91 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
import { Person } from '@prisma/client' | ||
import { Beneficiary, Person } from '@prisma/client' | ||
import { mockDeep } from 'jest-mock-extended' | ||
import { ConfigService } from '@nestjs/config' | ||
import { HttpService } from '@nestjs/axios' | ||
import { plainToClass } from 'class-transformer' | ||
import { Test, TestingModule } from '@nestjs/testing' | ||
import { Logger, UnauthorizedException } from '@nestjs/common' | ||
import { InternalServerErrorException, Logger, UnauthorizedException } from '@nestjs/common' | ||
import KeycloakConnect, { Grant } from 'keycloak-connect' | ||
import { KEYCLOAK_INSTANCE } from 'nest-keycloak-connect' | ||
import KeycloakAdminClient from '@keycloak/keycloak-admin-client' | ||
|
@@ -23,6 +23,7 @@ | |
import { SendGridNotificationsProvider } from '../notifications/providers/notifications.sendgrid.provider' | ||
import { NotificationsProviderInterface } from '../notifications/providers/notifications.interface.providers' | ||
import { MarketingNotificationsModule } from '../notifications/notifications.module' | ||
import { PersonService } from '../person/person.service' | ||
|
||
jest.mock('@keycloak/keycloak-admin-client') | ||
|
||
|
@@ -32,6 +33,7 @@ | |
let admin: KeycloakAdminClient | ||
let keycloak: KeycloakConnect.Keycloak | ||
let marketing: NotificationsProviderInterface<unknown> | ||
let personService: PersonService | ||
|
||
const person: Person = { | ||
id: 'e43348aa-be33-4c12-80bf-2adfbf8736cd', | ||
|
@@ -116,6 +118,7 @@ | |
admin = module.get<KeycloakAdminClient>(KeycloakAdminClient) | ||
marketing = module.get<NotificationsProviderInterface<never>>(NotificationsProviderInterface) | ||
keycloak = module.get<KeycloakConnect.Keycloak>(KEYCLOAK_INSTANCE) | ||
personService = module.get<PersonService>(PersonService) | ||
}) | ||
|
||
it('should be defined', () => { | ||
|
@@ -501,4 +504,155 @@ | |
expect(marketingSpy).not.toHaveBeenCalled() | ||
}) | ||
}) | ||
|
||
describe('deleteUser', () => { | ||
const corporatePerson: any = { | ||
id: 'e43348aa-be33-4c12-80bf-2adfbf8736cd', | ||
firstName: 'Admin', | ||
lastName: 'Dev', | ||
companyId: null, | ||
keycloakId: '123', | ||
email: '[email protected]', | ||
emailConfirmed: false, | ||
phone: null, | ||
picture: null, | ||
createdAt: new Date('2021-10-07T13:38:11.097Z'), | ||
updatedAt: new Date('2021-10-07T13:38:11.097Z'), | ||
newsletter: false, | ||
address: null, | ||
birthday: null, | ||
personalNumber: null, | ||
stripeCustomerId: null, | ||
profileEnabled: false, | ||
beneficiaries: [], | ||
organizer: null, | ||
} | ||
|
||
it('should delete user successfully', async () => { | ||
const keycloakId = '123' | ||
|
||
const personSpy = jest | ||
.spyOn(personService, 'findOneByKeycloakId') | ||
.mockResolvedValue(corporatePerson) | ||
|
||
const authenticateAdminSpy = jest | ||
.spyOn(service as any, 'authenticateAdmin') | ||
.mockResolvedValueOnce('') | ||
|
||
const adminDeleteSpy = jest.spyOn(admin.users, 'del').mockResolvedValueOnce() | ||
const prismaDeleteSpy = jest.spyOn(prismaMock.person, 'delete').mockResolvedValueOnce(person) | ||
const loggerLogSpy = jest.spyOn(Logger, 'log') | ||
|
||
await expect(service.deleteUser(keycloakId)).resolves.not.toThrow() | ||
|
||
expect(personSpy).toHaveBeenCalledOnce() | ||
expect(authenticateAdminSpy).toHaveBeenCalledTimes(1) | ||
expect(adminDeleteSpy).toHaveBeenCalledWith({ id: keycloakId }) | ||
expect(prismaDeleteSpy).toHaveBeenCalledWith({ where: { keycloakId } }) | ||
expect(loggerLogSpy).toHaveBeenCalledWith( | ||
`User with keycloak id ${keycloakId} was successfully deleted!`, | ||
) | ||
}) | ||
|
||
it('should handle admin client rejection', async () => { | ||
const keycloakId = '123' | ||
|
||
const personSpy = jest | ||
.spyOn(personService, 'findOneByKeycloakId') | ||
.mockResolvedValue(corporatePerson) | ||
|
||
const authenticateAdminSpy = jest | ||
.spyOn(service as any, 'authenticateAdmin') | ||
.mockResolvedValueOnce('') | ||
|
||
const adminDeleteSpy = jest | ||
.spyOn(admin.users, 'del') | ||
.mockRejectedValueOnce(new Error('Admin Client Rejection!')) | ||
|
||
const loggerLogSpy = jest.spyOn(Logger, 'error') | ||
|
||
await expect(service.deleteUser(keycloakId)).rejects.toThrow(InternalServerErrorException) | ||
|
||
expect(personSpy).toHaveBeenCalledOnce() | ||
expect(authenticateAdminSpy).toHaveBeenCalledTimes(1) | ||
expect(adminDeleteSpy).toHaveBeenCalledWith({ id: keycloakId }) | ||
expect(loggerLogSpy).toHaveBeenCalledWith( | ||
`Deleting user fails with reason: Admin Client Rejection!`, | ||
) | ||
}) | ||
|
||
it('should handle Prisma rejection', async () => { | ||
const keycloakId = '123' | ||
const personSpy = jest | ||
.spyOn(personService, 'findOneByKeycloakId') | ||
.mockResolvedValue(corporatePerson) | ||
|
||
const authenticateAdminSpy = jest | ||
.spyOn(service as any, 'authenticateAdmin') | ||
.mockResolvedValueOnce('') | ||
|
||
const adminDeleteSpy = jest.spyOn(admin.users, 'del').mockResolvedValueOnce() | ||
|
||
const prismaDeleteSpy = jest | ||
.spyOn(prismaMock.person, 'delete') | ||
.mockRejectedValueOnce(new Error('Prisma Rejection!')) | ||
|
||
const loggerLogSpy = jest.spyOn(Logger, 'error') | ||
|
||
await expect(service.deleteUser(keycloakId)).rejects.toThrow(InternalServerErrorException) | ||
|
||
expect(personSpy).toHaveBeenCalledOnce() | ||
expect(authenticateAdminSpy).toHaveBeenCalledTimes(1) | ||
expect(adminDeleteSpy).toHaveBeenCalledWith({ id: keycloakId }) | ||
expect(prismaDeleteSpy).toHaveBeenCalledWith({ where: { keycloakId } }) | ||
expect(loggerLogSpy).toHaveBeenCalledWith( | ||
`Deleting user fails with reason: Prisma Rejection!`, | ||
) | ||
}) | ||
|
||
it('should throw when corporate user has beneficiaries', async () => { | ||
corporatePerson.beneficiaries = [{ id: '123' } as Beneficiary] | ||
|
||
const personSpy = jest | ||
.spyOn(personService, 'findOneByKeycloakId') | ||
.mockResolvedValue(corporatePerson) | ||
|
||
await expect(service.deleteUser('123')).rejects.toThrow(InternalServerErrorException) | ||
expect(personSpy).toHaveBeenCalledOnce() | ||
}) | ||
|
||
it('should throw when user has company id', async () => { | ||
corporatePerson.companyId = '123' | ||
|
||
const personSpy = jest | ||
.spyOn(personService, 'findOneByKeycloakId') | ||
.mockResolvedValue(corporatePerson) | ||
|
||
await expect(service.deleteUser('123')).rejects.toThrow(InternalServerErrorException) | ||
expect(personSpy).toHaveBeenCalledOnce() | ||
}) | ||
|
||
it('should throw when user is organizer', async () => { | ||
corporatePerson.organizer = { id: '123' } | ||
const personSpy = jest | ||
.spyOn(personService, 'findOneByKeycloakId') | ||
.mockResolvedValue(corporatePerson) | ||
|
||
await expect(service.deleteUser('123')).rejects.toThrow(InternalServerErrorException) | ||
expect(personSpy).toHaveBeenCalledOnce() | ||
}) | ||
|
||
it('should throw when corporate user has companyId & beneficiaries & is organizer', async () => { | ||
corporatePerson.companyId = '123' | ||
corporatePerson.beneficiaries = [{ id: '123' } as Beneficiary] | ||
corporatePerson.organizer = { id: '123' } | ||
|
||
const personSpy = jest | ||
.spyOn(personService, 'findOneByKeycloakId') | ||
.mockResolvedValue(corporatePerson) | ||
|
||
await expect(service.deleteUser('123')).rejects.toThrow(InternalServerErrorException) | ||
expect(personSpy).toHaveBeenCalledOnce() | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I am fine with this .then format.
But I think so far the common pattern is to use await.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made this decision because if, for some reason, the promise from Keycloak does not resolve successfully, I want to interrupt the chain and refrain from removing the user in the API database. I am unsure if this is possible when I use await for every promise.