diff --git a/src/app.module.ts b/src/app.module.ts index 7d1afe2..d6415cb 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -9,7 +9,9 @@ import { DbModule } from './config/db/db.module'; import { DbService } from './config/db/db.service'; import { CronModule } from './cron/cron.module'; import { IdosoModule } from './idoso/idoso.module'; +import { MetricaModule } from './metrica/metrica.module'; import { RotinaModule } from './rotina/rotina.module'; +import { ValorMetricaModule } from './valorMetrica/valorMetrica.module'; const ENV = process.env.NODE_ENV; @@ -41,6 +43,8 @@ const ENV = process.env.NODE_ENV; DbModule, IdosoModule, RotinaModule, + MetricaModule, + ValorMetricaModule, CronModule, ], controllers: [], diff --git a/src/metrica/classes/tipo-metrica.enum.ts b/src/metrica/classes/tipo-metrica.enum.ts new file mode 100644 index 0000000..acc57b9 --- /dev/null +++ b/src/metrica/classes/tipo-metrica.enum.ts @@ -0,0 +1,11 @@ +export enum ECategoriaMetrica { + FREQUENCIA_CARDIACA = 'Frequência Cardíaca', + PRESSAO_SANGUINEA = 'Pressão', + TEMPERATURA = 'Temperatura', + PESO = 'Peso', + GLICEMIA = 'Glicemia', + SATURACAO = 'Saturação', + HORAS_DORMIDAS = 'Horas Dormidas', + ALTURA = 'Altura', + IMC = 'IMC', +} diff --git a/src/metrica/dto/create-metrica-dto.ts b/src/metrica/dto/create-metrica-dto.ts new file mode 100644 index 0000000..2a6ada8 --- /dev/null +++ b/src/metrica/dto/create-metrica-dto.ts @@ -0,0 +1,12 @@ +import { IsEnum, IsNotEmpty, IsNumber } from "class-validator"; +import { ECategoriaMetrica } from "../classes/tipo-metrica.enum"; + +export class CreateMetricaDto { + @IsNotEmpty() + @IsNumber() + idIdoso!: number; + + @IsNotEmpty() + @IsEnum(ECategoriaMetrica) + categoria?: ECategoriaMetrica; +} \ No newline at end of file diff --git a/src/metrica/dto/update-metrica-dto.ts b/src/metrica/dto/update-metrica-dto.ts new file mode 100644 index 0000000..aaa8487 --- /dev/null +++ b/src/metrica/dto/update-metrica-dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from "@nestjs/mapped-types"; +import { CreateMetricaDto } from "./create-metrica-dto"; + +export class UpdateMetricaDto extends PartialType(CreateMetricaDto) { } \ No newline at end of file diff --git a/src/metrica/entities/metrica.entity.ts b/src/metrica/entities/metrica.entity.ts new file mode 100644 index 0000000..ef646a7 --- /dev/null +++ b/src/metrica/entities/metrica.entity.ts @@ -0,0 +1,22 @@ +import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { Idoso } from "../../idoso/entities/idoso.entity"; +import { ECategoriaMetrica } from "../classes/tipo-metrica.enum"; +import { CreateMetricaDto } from "../dto/create-metrica-dto"; +import { UpdateMetricaDto } from "../dto/update-metrica-dto"; + +@Entity({ name: 'metrica' }) +export class Metrica { + @PrimaryGeneratedColumn() + id!: number; + + @ManyToOne(() => Idoso) + @JoinColumn({ name: 'idIdoso' }) + idIdoso!: number; + + @Column('enum', { enum: ECategoriaMetrica }) + categoria!: ECategoriaMetrica; + + constructor(createMetricaDto: CreateMetricaDto | UpdateMetricaDto) { + Object.assign(this, createMetricaDto); + } +} \ No newline at end of file diff --git a/src/metrica/interfaces/metrica-filter.interface.ts b/src/metrica/interfaces/metrica-filter.interface.ts new file mode 100644 index 0000000..e54b987 --- /dev/null +++ b/src/metrica/interfaces/metrica-filter.interface.ts @@ -0,0 +1,3 @@ +export interface IMetricaFilter { + idIdoso?: number; +} diff --git a/src/metrica/metrica.controller.spec.ts b/src/metrica/metrica.controller.spec.ts new file mode 100644 index 0000000..09f0dcf --- /dev/null +++ b/src/metrica/metrica.controller.spec.ts @@ -0,0 +1,132 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Filtering } from '../shared/decorators/filtrate.decorator'; +import { OrderParams, Ordering } from '../shared/decorators/ordenate.decorator'; +import { + Pagination, + PaginationParams, +} from '../shared/decorators/paginate.decorator'; +import { ECategoriaMetrica } from './classes/tipo-metrica.enum'; +import { Metrica } from './entities/metrica.entity'; +import { IMetricaFilter } from './interfaces/metrica-filter.interface'; +import { MetricaController } from './metrica.controller'; +import { MetricaService } from './metrica.service'; + +describe('MetricaController', () => { + let controller: MetricaController; + let service: MetricaService; + + const metricaDto = { + idIdoso: 1, + categoria: ECategoriaMetrica.FREQUENCIA_CARDIACA, + }; + + const metrica = { + ...metricaDto, + id: 1, + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [], + controllers: [MetricaController], + providers: [ + { + provide: MetricaService, + useValue: { + create: jest.fn(), + findOne: jest.fn(), + remove: jest.fn(), + update: jest.fn(), + findAll: jest.fn(), + }, + }, + { + provide: getRepositoryToken(Metrica), + useValue: {}, + }, + ], + }).compile(); + + controller = module.get(MetricaController); + service = module.get(MetricaService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should create IMetrica', async () => { + jest + .spyOn(service, 'create') + .mockReturnValue(Promise.resolve(metrica as Metrica)); + + const response = await controller.create(metricaDto); + expect(response.data).toEqual(metrica); + expect(response.message).toEqual('Salvo com sucesso!'); + }); + + it('should find IMetrica', async () => { + jest + .spyOn(service, 'findOne') + .mockReturnValue(Promise.resolve(metrica as Metrica)); + + const response = await controller.findOne({ id: 1 }); + expect(response).toEqual(metrica); + }); + + it('should remove IMetrica', async () => { + jest + .spyOn(service, 'remove') + .mockReturnValue(Promise.resolve(metrica as Metrica)); + + const response = await controller.remove({ id: 1 }); + expect(response.data).toEqual(metrica); + expect(response.message).toEqual('Excluído com sucesso!'); + }); + + it('should update IMetrica', async () => { + jest + .spyOn(service, 'update') + .mockReturnValue(Promise.resolve(metrica as Metrica)); + + const response = await controller.update({ id: 1 }, { idIdoso: 2 }); + expect(response.data).toEqual(metrica); + expect(response.message).toEqual('Atualizado com sucesso!'); + }); + + describe('findAll', () => { + const filter: IMetricaFilter = { + idIdoso: 1, + }; + const filtering = new Filtering(JSON.stringify(filter)); + + const order: OrderParams = { + column: 'id', + dir: 'ASC', + }; + const ordering: Ordering = new Ordering(JSON.stringify(order)); + + const paginate: PaginationParams = { + limit: 10, + offset: 0, + }; + const pagination: Pagination = new Pagination(paginate); + + it('should findAll Metrica', async () => { + const expected = { data: [metrica], count: 1, pageSize: 1 }; + + jest.spyOn(service, 'findAll').mockReturnValue(Promise.resolve(expected)); + + const { data, count, pageSize } = await controller.findAll( + filtering as any, + pagination, + ordering, + ); + + expect(count).toEqual(1); + expect(pageSize).toEqual(1); + expect(data).toEqual([metrica]); + }); + }); +}); diff --git a/src/metrica/metrica.controller.ts b/src/metrica/metrica.controller.ts new file mode 100644 index 0000000..09c842e --- /dev/null +++ b/src/metrica/metrica.controller.ts @@ -0,0 +1,60 @@ +import { + Body, + Controller, + Get, + Param, + Patch, + Post, + Delete, +} from '@nestjs/common'; +import { MetricaService } from './metrica.service'; +import { Response } from '../shared/interceptors/data-transform.interceptor'; +import { Paginate, Pagination } from '../shared/decorators/paginate.decorator'; +import { Ordenate, Ordering } from '../shared/decorators/ordenate.decorator'; +import { ResponsePaginate } from '../shared/interfaces/response-paginate.interface'; +import { Metrica } from './entities/metrica.entity'; +import { IdValidator } from '../shared/validators/id.validator'; +import { UpdateMetricaDto } from './dto/update-metrica-dto'; +import { HttpResponse } from '../shared/classes/http-response'; +import { CreateMetricaDto } from './dto/create-metrica-dto'; +import { PublicRoute } from '../shared/decorators/public-route.decorator'; +import { Filtering, Filtrate } from '../shared/decorators/filtrate.decorator'; +import { IMetricaFilter } from './interfaces/metrica-filter.interface'; + +@Controller('metrica') +export class MetricaController { + constructor(private readonly _service: MetricaService) {} + + @Get() + @PublicRoute() + async findAll( + @Filtrate() queryParam: Filtering, + @Paginate() pagination: Pagination, + @Ordenate() ordering: Ordering, + ): Promise> { + return this._service.findAll(queryParam.filter, ordering, pagination); + } + @Get(':id') + async findOne(@Param() param: IdValidator): Promise { + return this._service.findOne(param.id); + } + @Patch(':id') + async update( + @Param() param: IdValidator, + @Body() body: UpdateMetricaDto, + ): Promise> { + const updated = await this._service.update(param.id, body); + return new HttpResponse(updated).onUpdated(); + } + @Post() + async create(@Body() body: CreateMetricaDto) { + const created = await this._service.create(body); + return new HttpResponse(created).onCreated(); + } + + @Delete(':id') + async remove(@Param() param: IdValidator): Promise> { + const deleted = await this._service.remove(param.id); + return new HttpResponse(deleted).onDeleted(); + } +} diff --git a/src/metrica/metrica.module.ts b/src/metrica/metrica.module.ts new file mode 100644 index 0000000..1c48d79 --- /dev/null +++ b/src/metrica/metrica.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Metrica } from './entities/metrica.entity'; +import { MetricaController } from './metrica.controller'; +import { MetricaService } from './metrica.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Metrica])], + controllers: [MetricaController], + providers: [MetricaService, Repository], + exports: [MetricaService], +}) +export class MetricaModule { } diff --git a/src/metrica/metrica.service.spec.ts b/src/metrica/metrica.service.spec.ts new file mode 100644 index 0000000..5e949b6 --- /dev/null +++ b/src/metrica/metrica.service.spec.ts @@ -0,0 +1,171 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { MetricaService } from './metrica.service'; +import { Metrica } from './entities/metrica.entity'; +import { CreateMetricaDto } from './dto/create-metrica-dto'; +import { UpdateMetricaDto } from './dto/update-metrica-dto'; +import { ECategoriaMetrica } from './classes/tipo-metrica.enum'; +import { OrderParams, Ordering } from '../shared/decorators/ordenate.decorator'; +import { Pagination, PaginationParams } from '../shared/decorators/paginate.decorator'; +import { IMetricaFilter } from './interfaces/metrica-filter.interface'; +import { Filtering } from '../shared/decorators/filtrate.decorator'; + + +describe('MetricaService', () => { + let service: MetricaService; + let repository: Repository; + + const metricatest: CreateMetricaDto = { + idIdoso: 2, + categoria: ECategoriaMetrica.TEMPERATURA, + }; + + const mockRepository = { + save: jest.fn(), + findOneOrFail: jest.fn(), + remove: jest.fn(), + findOne: jest.fn(), + createQueryBuilder: jest.fn(() => ({ + where: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + offset: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + getManyAndCount: jest.fn(), + })), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: getRepositoryToken(Metrica), + useValue: mockRepository, + }, + MetricaService, + ], + }).compile(); + + service = module.get(MetricaService); + repository = module.get>(getRepositoryToken(Metrica)); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('create', () => { + it('criacao metrica', async () => { + const createMetricaDto: CreateMetricaDto = metricatest; + const metrica = new Metrica(createMetricaDto); + + jest.spyOn(repository, 'save').mockResolvedValue(metrica); + + const result = await service.create(createMetricaDto); + + expect(result).toEqual(metrica); + expect(repository.save).toHaveBeenCalledWith(metrica); + }); + + }); + +describe('findOne', () => { + it('should find a metrica by id', async () => { + const id = 1; + const metrica = new Metrica(metricatest); + + jest.spyOn(repository, 'findOneOrFail').mockResolvedValue(metrica); + + const result = await service.findOne(id); + + expect(result).toEqual(metrica); + expect(repository.findOneOrFail).toHaveBeenCalledWith({ where: { id } }); + }); + + }); + + describe('update', () => { + it('should update a metrica by id', async () => { + const id = 1; + const updateMetricaDto: UpdateMetricaDto = { + idIdoso: 3, + categoria: ECategoriaMetrica.PRESSAO_SANGUINEA, + }; + const metrica = new Metrica(metricatest); + + jest.spyOn(service, 'findOne').mockResolvedValue(metrica); + jest.spyOn(repository, 'save').mockResolvedValue(metrica); + + const result = await service.update(id, updateMetricaDto); + + expect(result).toEqual(metrica); + expect(service.findOne).toHaveBeenCalledWith(id); + expect(repository.save).toHaveBeenCalledWith(expect.objectContaining(updateMetricaDto)); + }); + + }); + + it('should remove Metrica', async () => { + jest.spyOn(repository, 'findOneOrFail').mockReturnValue({ id: 1 } as any); + jest.spyOn(repository, 'remove').mockReturnValue({ id: 1 } as any); + + const removed = await service.remove(1); + expect(removed.id).toEqual(1); +}); + +describe('findAll', () => { + const Metrica = { + id: 1, + idMetrica: 1, + }; + + const order: OrderParams = { + column: 'id', + dir: 'ASC', + }; + + const ordering: Ordering = new Ordering(JSON.stringify(order)); + + const paginate: PaginationParams = { + limit: 10, + offset: 0, + }; + + const pagination: Pagination = new Pagination(paginate); + + const filter: IMetricaFilter = { + idIdoso: 1, + }; + + const filtering = new Filtering(JSON.stringify(filter)); + + it('should findAll Metrica', async () => { + jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({ + where: () => ({ + limit: () => ({ + offset: () => ({ + orderBy: () => ({ + getManyAndCount: jest.fn().mockResolvedValueOnce([[Metrica], 1]), + }), + }), + }), + }), + } as any); + + const { data, count } = await service.findAll( + filtering as any, + ordering, + pagination, + ); + expect(count).toEqual(1); + expect((data as Metrica[])[0]).toEqual(Metrica); + + const res = await service.findAll({}, ordering, pagination); + expect(res.count).toEqual(1); + expect((res.data as Metrica[])[0]).toEqual(Metrica); + }); + +}); + + +}); \ No newline at end of file diff --git a/src/metrica/metrica.service.ts b/src/metrica/metrica.service.ts new file mode 100644 index 0000000..871912d --- /dev/null +++ b/src/metrica/metrica.service.ts @@ -0,0 +1,77 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Ordering } from '../shared/decorators/ordenate.decorator'; +import { Pagination } from '../shared/decorators/paginate.decorator'; +import { getWhereClauseNumber } from '../shared/helpers/sql-query-helper'; +import { ResponsePaginate } from '../shared/interfaces/response-paginate.interface'; +import { CreateMetricaDto } from './dto/create-metrica-dto'; +import { Metrica } from './entities/metrica.entity'; +import { UpdateMetricaDto } from './dto/update-metrica-dto'; +import { IMetricaFilter } from './interfaces/metrica-filter.interface'; + +@Injectable() +export class MetricaService { + constructor( + @InjectRepository(Metrica) + private readonly _repository: Repository, + ) {} + + async create(body: CreateMetricaDto): Promise { + const metrica = new Metrica(body); + return this._repository.save(metrica); + } + + async findOne(id: number) { + const metrica = await this._repository.findOneOrFail({ where: { id } }); + return metrica; + } + + async update(id: number, body: UpdateMetricaDto): Promise { + const found = await this.findOne(id); + const merged = Object.assign(found, body); + + const updated = await this._repository.save(merged); + + return updated; + } + + async findAll( + filter: IMetricaFilter, + ordering: Ordering, + paging: Pagination, + ): Promise> { + const limit = paging.limit; + const offset = paging.offset; + const sort = ordering.column; + const order = ordering.dir.toUpperCase() as 'ASC' | 'DESC'; + const where = this.buildWhereClause(filter); + + const [result, total] = await this._repository + .createQueryBuilder('metrica') + .where(`${where}`) + .limit(limit) + .offset(offset) + .orderBy(`"${sort}"`, order) + .getManyAndCount(); + + return { + data: result, + count: +total, + pageSize: +total, + }; + } + + private buildWhereClause(filter: IMetricaFilter): string { + let whereClause = '1 = 1 '; + + whereClause += getWhereClauseNumber(filter.idIdoso, '"idIdoso"'); + + return whereClause; + } + + async remove(id: number) { + const found = await this._repository.findOneOrFail({ where: { id } }); + return this._repository.remove(found); + } +} diff --git a/src/migration/1701549606356-CreateTableMetrica.ts b/src/migration/1701549606356-CreateTableMetrica.ts new file mode 100644 index 0000000..d9988ae --- /dev/null +++ b/src/migration/1701549606356-CreateTableMetrica.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateTableMetrica1701549606356 implements MigrationInterface { + name = 'CreateTableMetrica1701549606356' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TYPE "public"."metrica_categoria_enum" AS ENUM('Frequência Cardíaca', 'Pressão', 'Temperatura', 'Peso', 'Glicemia', 'Saturação')`); + await queryRunner.query(`CREATE TABLE "metrica" ("id" SERIAL NOT NULL, "categoria" "public"."metrica_categoria_enum" NOT NULL, "idIdoso" integer, CONSTRAINT "PK_37eda6d5162b9305738916e1712" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "metrica" ADD CONSTRAINT "FK_574d03daab9657eaa6cc7d5d726" FOREIGN KEY ("idIdoso") REFERENCES "idoso"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "metrica" DROP CONSTRAINT "FK_574d03daab9657eaa6cc7d5d726"`); + await queryRunner.query(`DROP TABLE "metrica"`); + await queryRunner.query(`DROP TYPE "public"."metrica_categoria_enum"`); + } + +} diff --git a/src/migration/1701559083238-CreateTableValorMetrica.ts b/src/migration/1701559083238-CreateTableValorMetrica.ts new file mode 100644 index 0000000..4f4d935 --- /dev/null +++ b/src/migration/1701559083238-CreateTableValorMetrica.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateTableValorMetrica1701559083238 implements MigrationInterface { + name = 'CreateTableValorMetrica1701559083238' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "valorMetrica" ("id" SERIAL NOT NULL, "valor" double precision NOT NULL, "dataHora" TIMESTAMP NOT NULL, "idMetrica" integer, CONSTRAINT "PK_9e8ca5a7cfc9169850aa35d4fe2" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "valorMetrica" ADD CONSTRAINT "FK_80f274382900aafd3a667034d46" FOREIGN KEY ("idMetrica") REFERENCES "metrica"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "valorMetrica" DROP CONSTRAINT "FK_80f274382900aafd3a667034d46"`); + await queryRunner.query(`DROP TABLE "valorMetrica"`); + } + +} diff --git a/src/migration/1701996752336-AlterTableValorMetrica.ts b/src/migration/1701996752336-AlterTableValorMetrica.ts new file mode 100644 index 0000000..9f4205e --- /dev/null +++ b/src/migration/1701996752336-AlterTableValorMetrica.ts @@ -0,0 +1,24 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AlterTableValorMetrica1701996752336 implements MigrationInterface { + name = 'AlterTableValorMetrica1701996752336' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TYPE "public"."metrica_categoria_enum" RENAME TO "metrica_categoria_enum_old"`); + await queryRunner.query(`CREATE TYPE "public"."metrica_categoria_enum" AS ENUM('Frequência Cardíaca', 'Pressão', 'Temperatura', 'Peso', 'Glicemia', 'Saturação', 'Horas Dormidas', 'Altura', 'IMC')`); + await queryRunner.query(`ALTER TABLE "metrica" ALTER COLUMN "categoria" TYPE "public"."metrica_categoria_enum" USING "categoria"::"text"::"public"."metrica_categoria_enum"`); + await queryRunner.query(`DROP TYPE "public"."metrica_categoria_enum_old"`); + await queryRunner.query(`ALTER TABLE "valorMetrica" DROP COLUMN "valor"`); + await queryRunner.query(`ALTER TABLE "valorMetrica" ADD "valor" character varying(10) NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "valorMetrica" DROP COLUMN "valor"`); + await queryRunner.query(`ALTER TABLE "valorMetrica" ADD "valor" double precision NOT NULL`); + await queryRunner.query(`CREATE TYPE "public"."metrica_categoria_enum_old" AS ENUM('Frequência Cardíaca', 'Pressão', 'Temperatura', 'Peso', 'Glicemia', 'Saturação')`); + await queryRunner.query(`ALTER TABLE "metrica" ALTER COLUMN "categoria" TYPE "public"."metrica_categoria_enum_old" USING "categoria"::"text"::"public"."metrica_categoria_enum_old"`); + await queryRunner.query(`DROP TYPE "public"."metrica_categoria_enum"`); + await queryRunner.query(`ALTER TYPE "public"."metrica_categoria_enum_old" RENAME TO "metrica_categoria_enum"`); + } + +} diff --git a/src/valorMetrica/dto/create-valorMetrica-dto.ts b/src/valorMetrica/dto/create-valorMetrica-dto.ts new file mode 100644 index 0000000..f49189d --- /dev/null +++ b/src/valorMetrica/dto/create-valorMetrica-dto.ts @@ -0,0 +1,17 @@ +import { IsDateString, IsNotEmpty, IsNumber, IsString } from "class-validator"; + +export class CreateValorMetricaDto { + + @IsNotEmpty() + @IsNumber() + idMetrica!: number; + + @IsNotEmpty() + @IsString() + valor!: string; + + @IsDateString() + @IsNotEmpty() + dataHora!: Date; + +} \ No newline at end of file diff --git a/src/valorMetrica/dto/update-valorMetrica-dto.ts b/src/valorMetrica/dto/update-valorMetrica-dto.ts new file mode 100644 index 0000000..8604a57 --- /dev/null +++ b/src/valorMetrica/dto/update-valorMetrica-dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from "@nestjs/mapped-types"; +import { CreateValorMetricaDto } from "./create-valorMetrica-dto"; + +export class UpdateValorMetricaDto extends PartialType(CreateValorMetricaDto) { } \ No newline at end of file diff --git a/src/valorMetrica/entities/valorMetrica.entity.ts b/src/valorMetrica/entities/valorMetrica.entity.ts new file mode 100644 index 0000000..ef10908 --- /dev/null +++ b/src/valorMetrica/entities/valorMetrica.entity.ts @@ -0,0 +1,24 @@ +import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { Metrica } from "../../metrica/entities/metrica.entity"; +import { CreateValorMetricaDto } from "../dto/create-valorMetrica-dto"; +import { UpdateValorMetricaDto } from "../dto/update-valorMetrica-dto"; + +@Entity({ name: 'valorMetrica' }) +export class ValorMetrica { + @PrimaryGeneratedColumn() + id!: number; + + @ManyToOne(() => Metrica) + @JoinColumn({ name: 'idMetrica' }) + idMetrica!: number; + + @Column('varchar', {length: 10}) + valor!: string; + + @Column('timestamp') + dataHora!: Date; + + constructor(createValorMetricaDto: CreateValorMetricaDto | UpdateValorMetricaDto) { + Object.assign(this, createValorMetricaDto); + } +} \ No newline at end of file diff --git a/src/valorMetrica/interfaces/valorMetrica-filter.interface.ts b/src/valorMetrica/interfaces/valorMetrica-filter.interface.ts new file mode 100644 index 0000000..048bf42 --- /dev/null +++ b/src/valorMetrica/interfaces/valorMetrica-filter.interface.ts @@ -0,0 +1,3 @@ +export interface IValorMetricaFilter { + idMetrica?: number; +} diff --git a/src/valorMetrica/valorMetrica.controller.spec.ts b/src/valorMetrica/valorMetrica.controller.spec.ts new file mode 100644 index 0000000..b3f4a49 --- /dev/null +++ b/src/valorMetrica/valorMetrica.controller.spec.ts @@ -0,0 +1,119 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Filtering } from '../shared/decorators/filtrate.decorator'; +import { OrderParams, Ordering } from '../shared/decorators/ordenate.decorator'; +import { Pagination, PaginationParams } from '../shared/decorators/paginate.decorator'; +import { ValorMetrica } from './entities/valorMetrica.entity'; +import { IValorMetricaFilter } from './interfaces/valorMetrica-filter.interface'; +import { ValorMetricaController } from './valorMetrica.controller'; +import { ValorMetricaService } from './valorMetrica.service'; + +describe("ValorMetricaController", () => { + let controller: ValorMetricaController; + let service: ValorMetricaService; + + const valorMetricaDto = { + idMetrica: 1, + valor: "10", + dataHora: new Date().toISOString() as any, + } + + const valorMetrica = { + ...valorMetricaDto, + id: 1 + } + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [], + controllers: [ValorMetricaController], + providers: [ + { + provide: ValorMetricaService, + useValue: { + create: jest.fn(), + findOne: jest.fn(), + remove: jest.fn(), + update: jest.fn(), + findAll: jest.fn(), + }, + }, + { + provide: getRepositoryToken(ValorMetrica), + useValue: {}, + }, + ], + }).compile(); + + controller = module.get(ValorMetricaController); + service = module.get(ValorMetricaService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + it('should create ValorMetrica', async () => { + jest + .spyOn(service, 'create') + .mockReturnValue(Promise.resolve(valorMetrica as ValorMetrica)); + + const response = await controller.create(valorMetricaDto); + expect(response.data).toEqual(valorMetrica); + expect(response.message).toEqual('Salvo com sucesso!'); + }); + + it('should find ValorMetrica', async () => { + jest + .spyOn(service, 'findOne') + .mockReturnValue(Promise.resolve(valorMetrica as ValorMetrica)); + + const response = await controller.findOne({ id: 1 }); + expect(response).toEqual(valorMetrica); + }); + + it('should remove ValorMetrica', async () => { + jest + .spyOn(service, 'remove') + .mockReturnValue(Promise.resolve(valorMetrica as ValorMetrica)); + + const response = await controller.remove({ id: 1 }); + expect(response.data).toEqual(valorMetrica); + expect(response.message).toEqual('Excluído com sucesso!'); + }); + + describe('findAll', () => { + const filter: IValorMetricaFilter = { + idMetrica: 1, + }; + const filtering = new Filtering(JSON.stringify(filter)); + + const order: OrderParams = { + column: 'id', + dir: 'ASC', + }; + const ordering: Ordering = new Ordering(JSON.stringify(order)); + + const paginate: PaginationParams = { + limit: 10, + offset: 0, + }; + const pagination: Pagination = new Pagination(paginate); + + it('should findAll Idoso', async () => { + const expected = { data: [valorMetrica], count: 1, pageSize: 1 }; + + jest.spyOn(service, 'findAll').mockReturnValue(Promise.resolve(expected)); + + const { data, count, pageSize } = await controller.findAll( + filtering, + pagination, + ordering, + ); + + expect(count).toEqual(1); + expect(pageSize).toEqual(1); + expect(data).toEqual([valorMetrica]); + }); + }); +}) \ No newline at end of file diff --git a/src/valorMetrica/valorMetrica.controller.ts b/src/valorMetrica/valorMetrica.controller.ts new file mode 100644 index 0000000..b28ff9c --- /dev/null +++ b/src/valorMetrica/valorMetrica.controller.ts @@ -0,0 +1,43 @@ +import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; +import { HttpResponse } from '../shared/classes/http-response'; +import { Filtering, Filtrate } from '../shared/decorators/filtrate.decorator'; +import { Ordenate, Ordering } from '../shared/decorators/ordenate.decorator'; +import { Paginate, Pagination } from '../shared/decorators/paginate.decorator'; +import { PublicRoute } from '../shared/decorators/public-route.decorator'; +import { Response } from '../shared/interceptors/data-transform.interceptor'; +import { ResponsePaginate } from '../shared/interfaces/response-paginate.interface'; +import { IdValidator } from '../shared/validators/id.validator'; +import { CreateValorMetricaDto } from './dto/create-valorMetrica-dto'; +import { ValorMetrica } from './entities/valorMetrica.entity'; +import { IValorMetricaFilter } from './interfaces/valorMetrica-filter.interface'; +import { ValorMetricaService } from './valorMetrica.service'; + +@Controller('valorMetrica') +export class ValorMetricaController { + constructor(private readonly _service: ValorMetricaService) { } + + @Get() + @PublicRoute() + async findAll( + @Filtrate() queryParam: Filtering, + @Paginate() pagination: Pagination, + @Ordenate() ordering: Ordering, + ): Promise> { + return this._service.findAll(queryParam.filter, ordering, pagination); + } + @Get(':id') + async findOne(@Param() param: IdValidator): Promise { + return this._service.findOne(param.id); + } + @Post() + async create(@Body() body: CreateValorMetricaDto) { + const created = await this._service.create(body); + return new HttpResponse(created).onCreated(); + } + + @Delete(':id') + async remove(@Param() param: IdValidator): Promise> { + const deleted = await this._service.remove(param.id); + return new HttpResponse(deleted).onDeleted(); + } +} diff --git a/src/valorMetrica/valorMetrica.module.ts b/src/valorMetrica/valorMetrica.module.ts new file mode 100644 index 0000000..b1c5dcb --- /dev/null +++ b/src/valorMetrica/valorMetrica.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { ValorMetrica } from './entities/valorMetrica.entity'; +import { ValorMetricaController } from './valorMetrica.controller'; +import { ValorMetricaService } from './valorMetrica.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([ValorMetrica])], + controllers: [ValorMetricaController], + providers: [ValorMetricaService, Repository], + exports: [ValorMetricaService], +}) +export class ValorMetricaModule { } \ No newline at end of file diff --git a/src/valorMetrica/valorMetrica.service.spec.ts b/src/valorMetrica/valorMetrica.service.spec.ts new file mode 100644 index 0000000..8340f72 --- /dev/null +++ b/src/valorMetrica/valorMetrica.service.spec.ts @@ -0,0 +1,132 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Filtering } from '../shared/decorators/filtrate.decorator'; +import { OrderParams, Ordering } from '../shared/decorators/ordenate.decorator'; +import { + Pagination, + PaginationParams, +} from '../shared/decorators/paginate.decorator'; +import { ValorMetrica } from './entities/valorMetrica.entity'; +import { IValorMetricaFilter } from './interfaces/valorMetrica-filter.interface'; +import { ValorMetricaService } from './valorMetrica.service'; + +describe('ValorMetricaService', () => { + let service: ValorMetricaService; + let repository: Repository; + + const mockRepository = { + save: jest.fn(), + findOneOrFail: jest.fn(), + remove: jest.fn(), + findOne: jest.fn(), + createQueryBuilder: jest.fn(() => ({ + where: jest.fn().mockReturnThis(), + limit: jest.fn().mockReturnThis(), + offset: jest.fn().mockReturnThis(), + orderBy: jest.fn().mockReturnThis(), + getManyAndCount: jest.fn(), + })), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: getRepositoryToken(ValorMetrica), + useValue: mockRepository, + }, + ValorMetricaService, + ], + }).compile(); + + service = module.get(ValorMetricaService); + repository = module.get>(getRepositoryToken(ValorMetrica)); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should create ValorMetrica', async () => { + const valorMetrica = { idMetrica: 1 } as any; + jest.spyOn(repository, 'save').mockReturnValue({ id: 1 } as any); + const created = await service.create(valorMetrica); + expect(created.id).toEqual(1); + }); + + it('should find ValorMetrica', async () => { + jest.spyOn(repository, 'findOneOrFail').mockReturnValue({ id: 1 } as any); + + const found = await service.findOne(1); + expect(found.id).toEqual(1); + }); + + it('should remove ValorMetrica', async () => { + jest.spyOn(repository, 'findOneOrFail').mockReturnValue({ id: 1 } as any); + jest.spyOn(repository, 'remove').mockReturnValue({ id: 1 } as any); + + const removed = await service.remove(1); + expect(removed.id).toEqual(1); + }); + + // it('should update ValorMetrica', async () => { + // jest.spyOn(repository, 'findOneOrFail').mockReturnValue({ id: 1 } as any); + // jest + // .spyOn(repository, 'save') + // .mockReturnValue({ id: 1, idMetrica: 1} as any); + + // const found = await service.update(1, { idMetrica: 2 }); + // expect(found).toEqual({ id: 1, idMetrica : 2 }); + // }); + + describe('findAll', () => { + const valorMetrica = { + id: 1, + idMetrica: 1, + }; + + const order: OrderParams = { + column: 'id', + dir: 'ASC', + }; + const ordering: Ordering = new Ordering(JSON.stringify(order)); + + const paginate: PaginationParams = { + limit: 10, + offset: 0, + }; + const pagination: Pagination = new Pagination(paginate); + + const filter: IValorMetricaFilter = { + idMetrica: 1, + }; + const filtering = new Filtering(JSON.stringify(filter)); + + it('should findAll ValorMetrica', async () => { + jest.spyOn(repository, 'createQueryBuilder').mockReturnValue({ + where: () => ({ + limit: () => ({ + offset: () => ({ + orderBy: () => ({ + getManyAndCount: jest.fn().mockResolvedValueOnce([[valorMetrica], 1]), + }), + }), + }), + }), + } as any); + + const { data, count } = await service.findAll( + filtering as any, + ordering, + pagination, + ); + expect(count).toEqual(1); + expect((data as ValorMetrica[])[0]).toEqual(valorMetrica); + + const res = await service.findAll({}, ordering, pagination); + expect(res.count).toEqual(1); + expect((res.data as ValorMetrica[])[0]).toEqual(valorMetrica); + }); + }); +}); diff --git a/src/valorMetrica/valorMetrica.service.ts b/src/valorMetrica/valorMetrica.service.ts new file mode 100644 index 0000000..640e377 --- /dev/null +++ b/src/valorMetrica/valorMetrica.service.ts @@ -0,0 +1,67 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Ordering } from '../shared/decorators/ordenate.decorator'; +import { Pagination } from '../shared/decorators/paginate.decorator'; +import { getWhereClauseNumber } from '../shared/helpers/sql-query-helper'; +import { ResponsePaginate } from '../shared/interfaces/response-paginate.interface'; +import { CreateValorMetricaDto } from './dto/create-valorMetrica-dto'; +import { ValorMetrica } from './entities/valorMetrica.entity'; +import { IValorMetricaFilter } from './interfaces/valorMetrica-filter.interface'; + +@Injectable() +export class ValorMetricaService { + constructor( + @InjectRepository(ValorMetrica) + private readonly _repository: Repository, + ) { } + + async create(body: CreateValorMetricaDto): Promise { + const valorMetrica = new ValorMetrica(body); + return this._repository.save(valorMetrica); + } + + async findOne(id: number) { + const metrica = await this._repository.findOneOrFail({ where: { id } }); + return metrica; + } + + async findAll( + filter: IValorMetricaFilter, + ordering: Ordering, + paging: Pagination, + ): Promise> { + const limit = paging.limit; + const offset = paging.offset; + const sort = ordering.column; + const order = ordering.dir.toUpperCase() as 'ASC' | 'DESC'; + const where = this.buildWhereClause(filter); + + const [result, total] = await this._repository + .createQueryBuilder('valorMetrica') + .where(`${where}`) + .limit(limit) + .offset(offset) + .orderBy(`"${sort}"`, order) + .getManyAndCount(); + + return { + data: result, + count: +total, + pageSize: +total, + }; + } + + private buildWhereClause(filter: IValorMetricaFilter): string { + let whereClause = '1 = 1 '; + + whereClause += getWhereClauseNumber(filter.idMetrica, '"idMetrica"'); + + return whereClause; + } + + async remove(id: number) { + const found = await this._repository.findOneOrFail({ where: { id } }); + return this._repository.remove(found); + } +}