import { internet, datatype, random } from 'faker' import MockDate from 'mockdate' import { factory } from '../../../../test/factories' import { Patient, User } from '../../../models' import { BadAuthenticationError, UnprocessableEntityError } from '../../../errors' import { UserRepository } from '../../../repositories' import { Accounts, MY_NUTRI_TENANT } from './accounts' describe('Accounts', () => { let subject: Accounts beforeAll(() => { subject = new Accounts(null, null, null, null, null, null, null, null) }) afterEach(() => { jest.resetAllMocks() }) afterAll(() => { jest.restoreAllMocks() }) describe('#createAuthenticationIfNotExists()', () => { let user: User let userAuth beforeAll(() => { userAuth = { id: datatype.uuid() } }) beforeEach(async () => { Object.defineProperty(subject, 'authentication', { value: { getUser: jest.fn().mockImplementation(() => Promise.resolve(userAuth)), createUser: jest.fn().mockImplementation(() => Promise.resolve(userAuth)), }, }) await subject['createAuthenticationIfNotExists'](user) }) describe('when user is null', () => { it('no search the user', () => { expect(subject['authentication'].getUser).not.toBeCalled() }) it('create a new user', () => { expect(subject['authentication'].createUser).toBeCalled() }) }) describe('when id user is empty', () => { beforeAll(async () => { user = await factory.build('user', { id: null }) }) it('no search the user', () => { expect(subject['authentication'].getUser).not.toBeCalled() }) it('create a new user', () => { expect(subject['authentication'].createUser).toBeCalled() }) }) describe('when id user is give', () => { beforeAll(async () => { user = await factory.build('user') }) it('search the user', () => { expect(subject['authentication'].getUser).toBeCalled() }) describe('when user is found', () => { it('no create a new user', () => { expect(subject['authentication'].createUser).not.toBeCalled() }) }) describe('when user is not found', () => { beforeAll(() => { userAuth = undefined }) it('create a new user', () => { expect(subject['authentication'].createUser).toBeCalled() }) }) }) }) describe('#createNutritionist()', () => { let userAuth: any let user: User let token: string let promiseCreateUser: Promise let promiseCreateAuth: Promise let userRepository: UserRepository let hasuraFetched: any let createUserSpy: jest.SpyInstance beforeAll(async () => { user = await factory.build('user') userRepository = new UserRepository(null) userAuth = { uid: datatype.uuid() } hasuraFetched = { data: { user: { id: datatype.uuid() } } } promiseCreateUser = Promise.resolve(hasuraFetched) promiseCreateAuth = Promise.resolve(userAuth) }) beforeEach(async () => { userRepository['fetchGraphQL'] = jest.fn().mockImplementation(() => promiseCreateUser) createUserSpy = jest.spyOn(userRepository, 'create') Object.defineProperty(subject, 'authentication', { value: { getAccessToken: jest.fn().mockImplementation(() => Promise.resolve(token)), delete: jest.fn().mockImplementation(() => Promise.resolve()), }, }) Object.defineProperty(subject, 'userRepository', { value: userRepository, }) subject['createAuthenticationIfNotExists'] = jest.fn().mockImplementation(() => promiseCreateAuth) subject['notifyNewUserByEmail'] = jest.fn().mockImplementation(() => Promise.resolve()) try { await subject.createNutritionist(user) } catch (error) {} }) afterEach(() => { createUserSpy.mockRestore() }) it('get or create auth user', () => { expect(subject['createAuthenticationIfNotExists']).toBeCalled() }) describe('when already exists email in FirebaseAuth', () => { beforeAll(() => { promiseCreateAuth = Promise.reject({ code: 'auth/email-already-exists' }) }) afterAll(() => { promiseCreateAuth = Promise.resolve(userAuth) }) it('throw UnprocessableEntityError', async () => { await expect(subject.createNutritionist(user)).rejects.toThrowError( new UnprocessableEntityError('E-mail já cadastrado.'), ) }) }) describe('when create a new user', () => { it('create a user', () => { expect(createUserSpy).toBeCalled() }) describe('when create a user with success', () => { it('no delete user auth', () => { expect(subject['authentication'].delete).not.toBeCalled() }) it('generate access token', () => { expect(subject['authentication'].getAccessToken).toBeCalled() }) }) describe('when throws error', () => { describe("when has constraint-violation 'users_document_number_key'", () => { beforeAll(() => { hasuraFetched = { errors: [ { extensions: { code: 'constraint-violation' }, message: 'anything with users_document_number_key' }, ], } promiseCreateUser = Promise.resolve(hasuraFetched) }) it('delete user auth', () => { expect(subject['authentication'].delete).toBeCalled() }) it('does not generate access token', () => { expect(subject['authentication'].getAccessToken).not.toBeCalled() }) it('throw UnprocessableEntityError', async () => { await expect(subject.createNutritionist(user)).rejects.toThrowError( new UnprocessableEntityError('CPF já cadastrado.'), ) }) }) describe("when has constraint-violation 'users_email_key'", () => { beforeAll(() => { hasuraFetched = { errors: [{ extensions: { code: 'constraint-violation' }, message: 'anything with users_email_key' }], } promiseCreateUser = Promise.resolve(hasuraFetched) }) it('delete user auth', () => { expect(subject['authentication'].delete).toBeCalled() }) it('does not generate access token', () => { expect(subject['authentication'].getAccessToken).not.toBeCalled() }) it('throw UnprocessableEntityError', async () => { await expect(subject.createNutritionist(user)).rejects.toThrowError( new UnprocessableEntityError('E-mail já cadastrado.'), ) }) }) }) }) }) describe('#authenticateNutritionist()', () => { let userCredential: any let promiseSignIn: Promise let token: string let userRepository: any let user: User let result: any let error: Error let promiseToken: Promise beforeAll(() => { userCredential = { user: { uid: datatype.uuid() } } promiseSignIn = Promise.resolve(userCredential) userRepository = { get: jest.fn(), } }) beforeEach(async () => { userRepository.get.mockImplementationOnce(async (id: string) => Promise.resolve(user ? { ...user, id } : user)) Object.defineProperty(subject, 'authentication', { value: { getAccessToken: jest.fn().mockImplementation(() => promiseToken), signIn: jest.fn().mockImplementation(() => promiseSignIn), }, }) Object.defineProperty(subject, 'userRepository', { value: userRepository, }) try { result = await subject.authenticateNutritionist(internet.email(), random.word()) } catch (err) { error = err } }) afterEach(() => { error = undefined }) it('sign in using the credentials given', () => { expect(subject['authentication'].signIn).toBeCalled() }) describe('when are invalid credentials', () => { beforeAll(() => { promiseSignIn = Promise.reject({ code: 'auth/wrong-password' }) }) it('throws UnprocessableEntityError', () => { expect(error).toBeInstanceOf(BadAuthenticationError) }) it('does not generate access token', () => { expect(subject['authentication'].getAccessToken).not.toBeCalled() }) }) describe('when are valid credentials', () => { beforeAll(() => { promiseSignIn = Promise.resolve(userCredential) }) describe('when nutritionist is not found', () => { it('throws UnprocessableEntityError', () => { expect(error).toBeInstanceOf(UnprocessableEntityError) }) }) describe('when nutritionist is found', () => { beforeAll(async () => { user = await factory.build('user') token = datatype.uuid() promiseToken = Promise.resolve(token) }) it('generate access token', () => { expect(subject['authentication'].getAccessToken).toBeCalledWith( userCredential.user.uid, MY_NUTRI_TENANT, user.role, user.country_code, ) }) it('returns token', () => { expect(result).toEqual({ token }) }) describe('when getAccessToken throws error', () => { beforeAll(() => { promiseToken = Promise.reject({ message: 'error' }) }) it('throws error', () => { expect(error).toEqual({ message: 'error' }) }) }) }) }) }) describe('#sendResetPasswordEmail()', () => { let email: string beforeAll(() => { email = internet.email() }) beforeEach(() => { Object.defineProperty(subject, 'authentication', { value: { sendResetPasswordEmail: jest.fn(), }, }) }) it('call send reset password email from authentication service', async () => { await subject.sendResetPasswordEmail(email) expect(subject['authentication'].sendResetPasswordEmail).toBeCalledWith(email, MY_NUTRI_TENANT) }) }) describe('#removeAuthUser()', () => { let user: User let userAuth beforeAll(async () => { user = await factory.build('user') }) beforeEach(async () => { Object.defineProperty(subject, 'authentication', { value: { getUser: jest.fn().mockImplementation(() => Promise.resolve(userAuth)), delete: jest.fn().mockImplementation(() => Promise.resolve()), }, }) }) describe('when user auth is not found', () => { beforeAll(() => { userAuth = null }) it('throws error', async () => { await expect(subject.removeAuthUser(user)).rejects.toThrowError(UnprocessableEntityError) }) it('does not delete auth user', async () => { try { await subject.removeAuthUser(user) } catch (err) {} expect(subject['authentication'].delete).not.toBeCalled() }) }) describe('when user auth is found', () => { beforeAll(() => { userAuth = { uid: datatype.uuid() } }) it('delete auth user', async () => { await subject.removeAuthUser(user) expect(subject['authentication'].delete).toBeCalledWith(userAuth.uid, MY_NUTRI_TENANT) }) }) }) describe('#inactiveNutritionist(', () => { const userId = datatype.uuid() let user: User const now = new Date() const users: User[] = [] const similarUsers: User[] = [] const patients: Patient[] = [] beforeAll(async () => { MockDate.set(now) user = await factory.build('user', { id: userId }) }) beforeEach(async () => { Object.defineProperty(subject, 'messageRepository', { value: { transferPatientPendingMessages: jest.fn(), }, }) Object.defineProperty(subject, 'patientRepository', { value: { findByUser: jest.fn().mockImplementation(() => patients), save: jest.fn(), }, }) Object.defineProperty(subject, 'userRepository', { value: { get: jest.fn().mockImplementation(() => user), findByRoleAndCountryCode: jest.fn().mockImplementation(() => users), findUsersByGoalAndRestrictions: jest.fn().mockImplementation(() => similarUsers), save: jest.fn(), }, }) Object.defineProperty(subject, 'availabilityTimeRepository', { value: { blockAllAvailabilityTimes: jest.fn(), }, }) }) it('tries to find a admin nutri to default option', async () => { await subject.inactiveNutritionist(userId) expect(subject['userRepository'].findByRoleAndCountryCode).toBeCalled() }) describe('when found a user to tranfer', () => { beforeAll(async () => { users.push(await factory.build('user')) }) it("save user's inactivated_at data", async () => { await subject.inactiveNutritionist(userId) expect(subject['userRepository'].save).toBeCalledWith({ id: userId, inactivated_at: now }, ['inactivated_at']) }) it("blocks all user's availability times", async () => { await subject.inactiveNutritionist(userId) expect(subject['availabilityTimeRepository'].blockAllAvailabilityTimes).toBeCalled() }) describe("when not found user's patients", () => { it('does not save any patient', async () => { await subject.inactiveNutritionist(userId) expect(subject['patientRepository'].save).not.toBeCalled() }) it('does not transfer any pending message', async () => { await subject.inactiveNutritionist(userId) expect(subject['messageRepository'].transferPatientPendingMessages).not.toBeCalled() }) }) describe("when found user's patients", () => { beforeAll(async () => { await Promise.all( Array.from(Array(datatype.number({ min: 1, max: 10 })).keys()).map(async () => patients.push(await factory.build('patient')), ), ) }) it('saves all patient', async () => { await subject.inactiveNutritionist(userId) expect(subject['patientRepository'].save).toBeCalledTimes(patients.length) }) it("transfer all patient's pending message", async () => { await subject.inactiveNutritionist(userId) expect(subject['messageRepository'].transferPatientPendingMessages).toBeCalledTimes(patients.length) }) describe('when did not find another user for patient', () => { it('saves the patient with default user', async () => { await subject.inactiveNutritionist(userId) expect(subject['patientRepository'].save).toHaveBeenNthCalledWith( 1, expect.objectContaining({ user_id: users[0].id }), ['user_id'], ) }) }) describe('when found another user for patient', () => { let similarUser: User beforeAll(async () => { similarUser = await factory.build('user') similarUsers.push(similarUser) }) it('saves the patient with found user', async () => { await subject.inactiveNutritionist(userId) expect(subject['patientRepository'].save).toHaveBeenNthCalledWith( 1, expect.objectContaining({ user_id: similarUser.id }), ['user_id'], ) }) }) }) }) }) })