import { readFileSync } from 'fs' import { join } from 'path' import { Test, TestingModule } from '@nestjs/testing' import { datatype, random } from 'faker' import { BioimpedanceService, BioimpedanceCreditService, BioimpedanceNotificationService, StorageService, } from '../../../services' import { FileNotFoundError, UnprocessableEntityError } from '../../../errors' import { AnthropometryRepository, BioimpedanceImportationRepository, PatientRepository, SmartfitUnitRepository, } from '../../../repositories' import { BioimpedanceImportation, CompressedBioimpedance, Patient, SmartfitUnit } from '../../../models' import { MockedModule } from '../../../../test/types' import { factory } from '../../../../test/factories' import { FROM_PATH_PREFIX, Importation, TO_PATH_PREFIX } from './importation' const DOCUMENT_NUMBERS_VALID_CSV = ['02226643478', '02496066406'] describe('Importation', () => { let subject: Importation let storageService: MockedModule let bioimpedanceImportationRepository: MockedModule let patientRepository: MockedModule let anthropometryRepository: MockedModule let bioimpedanceNotificationService: MockedModule let bioimpedanceCreditService: MockedModule let smartfitUnitRepository: MockedModule let bioimpedanceService: MockedModule beforeAll(async () => { storageService = { getFile: jest.fn(), moveFile: jest.fn(), } bioimpedanceImportationRepository = { findPending: jest.fn(), update: jest.fn(), create: jest.fn(), } bioimpedanceCreditService = { createTransaction: jest.fn(), getNextValidCreditToUse: jest.fn(), } patientRepository = { findByDocumentNumber: jest.fn(), findByLogin: jest.fn(), get: jest.fn(), updateSmartfitUnitAcronym: jest.fn(), } anthropometryRepository = { create: jest.fn(), findByPatientLogin: jest.fn(), } bioimpedanceNotificationService = { sendNotificatonForBioImports: jest.fn(), } smartfitUnitRepository = { get: jest.fn(), } bioimpedanceService = { createPatientAnthropometriesCache: jest.fn(), } const app: TestingModule = await Test.createTestingModule({ controllers: [], providers: [ { provide: StorageService, useValue: storageService, }, { provide: BioimpedanceImportationRepository, useValue: bioimpedanceImportationRepository, }, { provide: BioimpedanceCreditService, useValue: bioimpedanceCreditService, }, { provide: PatientRepository, useValue: patientRepository, }, { provide: AnthropometryRepository, useValue: anthropometryRepository, }, { provide: BioimpedanceNotificationService, useValue: bioimpedanceNotificationService, }, { provide: SmartfitUnitRepository, useValue: smartfitUnitRepository, }, { provide: BioimpedanceService, useValue: bioimpedanceService, }, Importation, ], }).compile() subject = app.get(Importation) }) beforeEach(() => { anthropometryRepository.create.mockResolvedValue({ id: datatype.uuid(), bioimpedance: datatype.uuid }) }) afterEach(() => { jest.clearAllMocks() }) afterAll(() => { jest.restoreAllMocks() }) describe('#importBioimpedances', () => { let file: Buffer const userId = datatype.uuid() const fileName = 'bioimpedances_valid.csv' const filePath = `bioimpedances/pending/${fileName}` let patient: Patient const users = [datatype.uuid()] const smartfitUnitId = datatype.uuid() beforeAll(async () => { file = readFileSync(join(__dirname, '..', '..', '..', '..', 'test', 'support', fileName)) patient = await factory.build('patient') }) describe('when file not exists', () => { beforeAll(() => { storageService.getFile.mockRejectedValue(null) }) it('throws an error', async () => { await expect(subject.importBioimpedances({ userId, filePath, users, smartfitUnitId })).rejects.toThrowError( FileNotFoundError, ) }) }) describe('when file exists', () => { describe('and hasn`t lines', () => { beforeAll(() => { storageService.getFile.mockResolvedValue(Buffer.from('')) }) it('throws an error', async () => { await expect(subject.importBioimpedances({ userId, filePath, users, smartfitUnitId })).rejects.toThrowError( UnprocessableEntityError, ) }) }) describe('and has valid lines', () => { describe('and header isn`t valid', () => { beforeAll(() => { storageService.getFile.mockResolvedValue(Buffer.concat([Buffer.from(`${random.word()}/n`), file])) }) it('throws an error', async () => { await expect(subject.importBioimpedances({ userId, filePath, users, smartfitUnitId })).rejects.toThrowError( UnprocessableEntityError, ) }) }) describe('and header is valid', () => { let smartfitUnit: SmartfitUnit beforeAll(async () => { smartfitUnit = await factory.build('smartfit_unit', { id: smartfitUnitId }) storageService.getFile.mockResolvedValue(file) patientRepository.findByDocumentNumber.mockResolvedValue(patient) smartfitUnitRepository.get.mockResolvedValue(smartfitUnit) bioimpedanceImportationRepository.create.mockResolvedValue({ rowsInserted: [{ document_number: 1 }], rowsWithError: null, }) }) it('creates the bioimpedance', async () => { await subject.importBioimpedances({ userId, filePath, users, smartfitUnitId }) expect(bioimpedanceImportationRepository.create).toHaveBeenCalledTimes(2) expect(bioimpedanceImportationRepository.create).toHaveBeenCalledWith( expect.objectContaining({ file_path: filePath.split(FROM_PATH_PREFIX)[1], created_by: userId, smartfit_unit_id: smartfitUnitId, users, }), ) }) it('moves the file', async () => { await subject.importBioimpedances({ userId, filePath, users, smartfitUnitId }) expect(storageService.moveFile).toHaveBeenCalledWith( `${FROM_PATH_PREFIX}${fileName}`, `${TO_PATH_PREFIX}${fileName}`, ) }) it('send notifications', async () => { await subject.importBioimpedances({ userId, filePath, users, smartfitUnitId }) expect(bioimpedanceNotificationService.sendNotificatonForBioImports).toHaveBeenCalled() }) describe('and occurs errors on creating a bioimpedance', () => { const error = { message: '' } beforeEach(() => { bioimpedanceImportationRepository.create.mockRejectedValue([error]) }) describe('and is an error of missing fields', () => { beforeAll(() => { error.message = 'Not-NULL violation' }) it('returns the failed bioimpedance`s document number with the error code', async () => { const response = await subject.importBioimpedances({ userId, filePath, users, smartfitUnitId }) expect(response.rowsWithError).toHaveProperty( DOCUMENT_NUMBERS_VALID_CSV[0], 'BIO_MISSING_REQUIRED_FIELDS', ) }) }) describe('and is an error of uniqueness', () => { beforeAll(() => { error.message = 'Uniqueness violation' }) it('returns the failed bioimpedance`s document number with the error code', async () => { const response = await subject.importBioimpedances({ userId, filePath, users, smartfitUnitId }) expect(response.rowsWithError).toHaveProperty(DOCUMENT_NUMBERS_VALID_CSV[0], 'BIO_ALREADY_IMPORTED') }) }) describe('and is an unknown error', () => { beforeAll(() => { error.message = random.words() }) it('returns the failed bioimpedance`s document number with the error code', async () => { const response = await subject.importBioimpedances({ userId, filePath, users, smartfitUnitId }) expect(response.rowsWithError).toHaveProperty(DOCUMENT_NUMBERS_VALID_CSV[0], 'BIO_UNKNOWN_ERROR') }) }) }) }) }) }) }) describe('#createAnthropometryIfNotExists', () => { let bioimpedanceImportation: BioimpedanceImportation let smartfitUnit: SmartfitUnit let patient: Patient beforeAll(async () => { smartfitUnit = await factory.build('smartfit_unit') patient = await factory.build('patient:subscriber', { anthropometries: [] }) }) beforeEach(async () => { const compressedBio: CompressedBioimpedance = await factory.build('compressed_bioimpedance') bioimpedanceImportation = compressedBio.toBioimpedanceImportation(patient, [datatype.uuid()], smartfitUnit.id) smartfitUnitRepository.get.mockResolvedValue(smartfitUnit) patientRepository.findByLogin.mockResolvedValue(patient) anthropometryRepository.findByPatientLogin.mockResolvedValue(patient) }) afterEach(() => { jest.resetAllMocks() }) it('writes cache by patient login', async () => { await subject.createAnthropometryIfNotExists(bioimpedanceImportation) expect(bioimpedanceService.createPatientAnthropometriesCache).toBeCalled() }) describe('when anthropometry has been created', () => { beforeEach(() => { bioimpedanceImportation.anthropometry_id = datatype.uuid() }) afterAll(() => { bioimpedanceImportation.anthropometry_id = null }) it('does not create anthropometry', async () => { await subject.createAnthropometryIfNotExists(bioimpedanceImportation) expect(anthropometryRepository.create).not.toBeCalled() }) it('does not debit from bioimpedance credit balance', async () => { await subject.createAnthropometryIfNotExists(bioimpedanceImportation) expect(bioimpedanceCreditService.createTransaction).not.toBeCalled() }) }) describe('when patient is a subscriber', () => { it('creates anthropometry', async () => { await subject.createAnthropometryIfNotExists(bioimpedanceImportation) expect(anthropometryRepository.create).toBeCalled() }) it('does not debit from bioimpedance credit balance', async () => { await subject.createAnthropometryIfNotExists(bioimpedanceImportation) expect(bioimpedanceCreditService.createTransaction).not.toBeCalled() }) }) describe('when patient is not a subscriber', () => { beforeAll(() => { patient.subscription = null }) it('creates anthropometry', async () => { await subject.createAnthropometryIfNotExists(bioimpedanceImportation) expect(anthropometryRepository.create).toBeCalled() }) describe('when patient has a different smartfit unit registered', () => { it('updates the smartfit unit of the patient', async () => { await subject.createAnthropometryIfNotExists(bioimpedanceImportation) expect(patientRepository.updateSmartfitUnitAcronym).toHaveBeenCalledWith(patient.id, smartfitUnit.acronym) }) }) }) }) describe('#createBioimpedanceIfPending', () => { let bioimpedanceImportation: BioimpedanceImportation let smartfitUnit: SmartfitUnit let patient: Patient beforeAll(async () => { const compressedBio: CompressedBioimpedance = await factory.build('compressed_bioimpedance') smartfitUnit = await factory.build('smartfit_unit') patient = await factory.build('patient:subscriber', { anthropometries: [] }) bioimpedanceImportation = compressedBio.toBioimpedanceImportation(patient, [datatype.uuid()], smartfitUnit.id) }) beforeEach(() => { patientRepository.get.mockResolvedValue(patient) anthropometryRepository.findByPatientLogin.mockResolvedValue(patient) }) it('writes cache by patient login', async () => { await subject.createBioimpedanceIfPending(patient) expect(bioimpedanceService.createPatientAnthropometriesCache).toBeCalled() }) describe('when does have not pending bioimpedance importation', () => { beforeEach(() => { bioimpedanceImportationRepository.findPending.mockResolvedValue([]) }) it('does not create anthropometry', async () => { await subject.createBioimpedanceIfPending(patient) expect(anthropometryRepository.create).not.toBeCalled() }) it('does not debit from bioimpedance credit balance', async () => { await subject.createBioimpedanceIfPending(patient) expect(bioimpedanceCreditService.createTransaction).not.toBeCalled() }) }) describe('when has pending bioimpedance importation', () => { beforeEach(() => { bioimpedanceImportationRepository.findPending.mockResolvedValue([bioimpedanceImportation]) }) describe('when patient is a subscriber', () => { it('creates anthropometry', async () => { await subject.createBioimpedanceIfPending(patient) expect(anthropometryRepository.create).toBeCalled() }) it('does not debit from bioimpedance credit balance', async () => { await subject.createBioimpedanceIfPending(patient) expect(bioimpedanceCreditService.createTransaction).not.toBeCalled() }) }) describe('when patient is not a subscriber', () => { beforeAll(() => { patient.subscription = null }) describe('when has bioimpedance credit', () => { let bioimpedanceImportations: BioimpedanceImportation[] beforeAll(() => { bioimpedanceImportations = [...Array(datatype.number({ min: 1, max: 10 }))].map(() => bioimpedanceImportation) }) beforeEach(() => { bioimpedanceImportationRepository.findPending.mockResolvedValue(bioimpedanceImportations) }) it('creates anthropometry', async () => { await subject.createBioimpedanceIfPending(patient) expect(anthropometryRepository.create).toBeCalled() }) it('debits from bioimpedance credit balance', async () => { await subject.createBioimpedanceIfPending(patient) expect(bioimpedanceCreditService.createTransaction).toBeCalledTimes(bioimpedanceImportations.length) }) }) }) }) }) })