Skip to content
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

132 criacao de endpoint de atualizacao de consultas #136

Merged
14 changes: 12 additions & 2 deletions apps/core-rest-api/http/appointment-client.http
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@authToken = {{$dotenv AUTH_TOKEN}}
@apiKey = {{$dotenv API_KEY}}

####
#### Create Appointment

# @name create_appointment
POST http://localhost:3333/core/appointment/create HTTP/1.1
Expand All @@ -19,8 +19,18 @@ Authorization: Bearer {{authToken}}
"date": "2024-01-19T08:30:54"
}

###
### Update Appointment

# @name update_appointment
PATCH http://localhost:3333/core/appointment/{{$dotenv APPOINTMENT_ID}}/update HTTP/1.1
Content-Type: application/json
Authorization: Bearer {{authToken}}

{
"confirmed": true
}

### Delete Appointment
# @name delete_appointment

DELETE http://localhost:3333/core/appointment/{{$dotenv APPOINTMENT_ID}}/delete HTTP/1.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { NestjsDeletePsychologistService } from './use-cases/psychologist/delete
import { NestjsUpdatePsychologistService } from './use-cases/psychologist/update-psychologist/nestjs-update-psychologist.service';

import { CreateAppointmentController } from './use-cases/appointment/create-appointment/create-appointment.controller';
import { NestjsUpdateAppointmentService } from './use-cases/appointment/update-appointment/nestjs-update-appointment.service';
import { UpdateAppointmentController } from './use-cases/appointment/update-appointment/update-appointment.controller';
import { CreateClinicController } from './use-cases/clinic/create-clinic/create-clinic.controller';
import { DeleteClinicController } from './use-cases/clinic/delete-clinic/delete-clinic.controller';
import { UpdateClinicController } from './use-cases/clinic/update-clinic/update-clinic.controller';
Expand Down Expand Up @@ -69,6 +71,7 @@ import { DeletePsychologistController } from './use-cases/psychologist/delete-ps
DeletePatientAppointmentRegistryController,
UpdatePatientAppointmentRegistryController,
UpdatePatientController,
UpdateAppointmentController,
],
providers: [
BcryptHasherService,
Expand All @@ -88,6 +91,7 @@ import { DeletePsychologistController } from './use-cases/psychologist/delete-ps
NestjsDeletePatientAppointmentRegistryService,
NestjsUpdatePatientAppointmentRegistryService,
NestjsUpdatePatientService,
NestjsUpdateAppointmentService
],
})
export class ApiModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { IsBoolean, IsDate, IsEnum, IsOptional, IsString } from 'class-validator';
import { PaymentMethod } from '../../../../../../core/shared/interfaces/payments';

export class UpdateAppointmentControllerParamsInputDto {
@IsString()
id!: string;
}

export class UpdateAppointmentControllerBodyInputDto {
@IsOptional()
@IsDate()
date?: Date;

@IsOptional()
@IsBoolean()
online?: boolean;

@IsOptional()
@IsBoolean()
confirmed?: boolean;

@IsOptional()
@IsDate()
confirmationDate?: Date | null;

@IsOptional()
@IsBoolean()
cancelled?: boolean;

@IsOptional()
@IsDate()
cancellationDate?: Date | null;

@IsOptional()
@IsBoolean()
done?: boolean | null;

@IsOptional()
@IsBoolean()
missed?: boolean | null;

@IsOptional()
@IsBoolean()
paid?: boolean;

@IsOptional()
@IsEnum(PaymentMethod)
paymentMethod?: PaymentMethod;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Injectable } from '@nestjs/common';
import { AppointmentDatabaseRepository } from '../../../../../../core/domains/appointment/repositories/database-repository';
import { UpdateAppointmentService } from '../../../../../../core/domains/appointment/use-cases/update-single-appointment/update-appointment.service';

@Injectable()
export class NestjsUpdateAppointmentService extends UpdateAppointmentService {
constructor(appointmentDatabaseRepository: AppointmentDatabaseRepository) {
super(appointmentDatabaseRepository);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IsString } from 'class-validator';

export class UpdateAppointmentControllerOutputDto {
@IsString()
message!: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { BadRequestException, Body, Controller, Param, Patch } from "@nestjs/common";
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
import { GlobalAppHttpException } from '../../../../../../shared/errors/globalAppHttpException';
import { UpdateAppointmentControllerBodyInputDto, UpdateAppointmentControllerParamsInputDto } from "./input.dto";
import { NestjsUpdateAppointmentService } from "./nestjs-update-appointment.service";
import { UpdateAppointmentControllerOutputDto } from "./output.dto";


@ApiTags('Appointment')
@ApiBearerAuth()
@Controller({
path: 'appointment',
})
export class UpdateAppointmentController {
constructor(private updateAppointmentService: NestjsUpdateAppointmentService) {}

@Patch(':id/update')
async execute(
@Param() { id }: UpdateAppointmentControllerParamsInputDto,
@Body() updateAppointmentDto: UpdateAppointmentControllerBodyInputDto)
: Promise<UpdateAppointmentControllerOutputDto>{
try {
const isReqBodyEmpty = Object.keys(updateAppointmentDto).length === 0;

if (isReqBodyEmpty) {
throw new BadRequestException('Must provide at least one field to update');
}

await this.updateAppointmentService.execute({...updateAppointmentDto, id});

return { message: 'Appointment updated successfully' };
} catch (error) {
throw new GlobalAppHttpException(error);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { faker } from '@faker-js/faker';
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { setupE2ETest } from '../../../../../../../../tests/utils/e2e-tests-initial-setup';
import { Replace } from '../../../../../../../app/shared/utils';
import { AppointmentEntity } from '../../../../../../core/domains/appointment/entities/appointment/entity';
import { UpdateAppointmentControllerBodyInputDto } from './input.dto';

describe('[E2E] - Update Appointment', () => {
let app: INestApplication;
let appointment: AppointmentEntity;
let access_token: string;
let invalid_access_token: string;

beforeAll(async () => {
const setup = await setupE2ETest();
app = setup.app;
appointment = setup.appointment
access_token = setup.access_token;
invalid_access_token = setup.invalid_access_token;
});

type IUpdateAppointmentProps = Replace<
UpdateAppointmentControllerBodyInputDto,
{ confirmed?: string | boolean; cancelled?: string | boolean }
>;

async function updateAppointmentWithoutAcessToken (appointmentId: string, updatedAppointmentInfos: IUpdateAppointmentProps) {
const response = await request(app.getHttpServer())
.patch(`/appointment/${appointmentId}/update`)
.send(updatedAppointmentInfos)

return response
}

async function updateAppointmentWithAcessToken (appointmentId: string, updatedAppointmentInfos: IUpdateAppointmentProps, accessToken?: string) {
const response = await request(app.getHttpServer())
.patch(`/appointment/${appointmentId}/update`)
.set('Authorization', `Bearer ${accessToken}`)
.send(updatedAppointmentInfos);

return response
}

it('[PATCH] - Should successfully update an appointment', async () => {
const updatedAppointmentInfos = {
confirmed: false,
cancelled: true,
};

const response = await updateAppointmentWithAcessToken(appointment.id, updatedAppointmentInfos, access_token)

expect(response.statusCode).toBe(200);
expect(response.body.message).toBe('Appointment updated successfully');
});

it('[PATCH] - Should return an error when trying to update an appointment without access_token', async () => {
const updatedAppointmentInfos = {
confirmed: false,
cancelled: true,
};

const response = await updateAppointmentWithoutAcessToken(appointment.id, updatedAppointmentInfos)

expect(response.statusCode).toBe(401);
expect(response.body.message).toBe('Invalid JWT token');
});

it('[PATCH] - Should return an error when trying to update an appointment with invalid access_token', async () => {
const updatedAppointmentInfos = {
confirmed: false,
};

const response = await updateAppointmentWithAcessToken(appointment.id, updatedAppointmentInfos, invalid_access_token)

expect(response.statusCode).toBe(401);
expect(response.body.message).toBe('Invalid JWT token');
});

it('[PATCH] - Should return an error when trying to update an appointment with invalid id', async () => {
const updatedAppointmentInfos = {
confirmed: false,
};
const response = await updateAppointmentWithAcessToken('invalid_id', updatedAppointmentInfos, access_token)

expect(response.statusCode).toBe(404);
expect(response.body.message).toEqual('appointment not found');
});

it('[PATCH] - Should return an error when trying to update an appointment with non existent id', async () => {
const nonExistentId = faker.string.uuid();

const updatedAppointmentInfos = {
confirmed: false,
};

const response = await updateAppointmentWithAcessToken(nonExistentId, updatedAppointmentInfos, access_token)

expect(response.statusCode).toBe(404);
expect(response.body.message).toBe('appointment not found');
});

it('[PATCH] - Should return an error when trying to update an appointment with empty request body', async () => {
const updatedAppointmentInfos = {};

const response = await updateAppointmentWithAcessToken(appointment.id, updatedAppointmentInfos, access_token)

expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Must provide at least one field to update');
});

it('[PATCH] - Should return an error when trying to update an appointment with an invalid body type params', async () => {
const updatedAppointmentInfos = {
confirmed: 'false',
cancelled: 'true'
};

const response = await updateAppointmentWithAcessToken(appointment.id, updatedAppointmentInfos, access_token)

expect(response.statusCode).toBe(400);
expect(response.body.message).toEqual([
'confirmed must be a boolean value',
'cancelled must be a boolean value',
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ export class PostgresqlPrismaOrmAppointmentRepository implements AppointmentData
await this.postgresqlPrismaOrmService['appointment'].update(toPrismaEntity);
}

async updateAppointment(newAppointmentInfo: UpdateAppointmentInfoDto): Promise<void> {
const oldAppointmentInfo = await this.findSingleAppointmentById(newAppointmentInfo.id);

if (!oldAppointmentInfo) {
throw new ConflictException(APPOINTMENT_ERROR_MESSAGES['APPOINTMENT_NOT_FOUND']);
}

const toPrismaEntity = PostgresqlPrismaAppointmentMapper.toPrismaUpdate({
...newAppointmentInfo,
});

await this.postgresqlPrismaOrmService['appointment'].update(toPrismaEntity);
}

async deleteSingleAppointment(appointmentId: string): Promise<void> {
const isAppointmentExists = await this.findSingleAppointmentById(appointmentId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AppointmentEntity } from '../entities/appointment/entity';
import { CreateSingleAppointmentDto } from '../use-cases/create-single-appointment/create-single-appointment-dto';
import { UpdatedAppointmentDateDto } from '../use-cases/update-appointment-date/update-appointment-date-dto';
import { UpdateAppointmentInfoDto } from '../use-cases/update-appointment-info/update-appointment-info-dto';
import { UpdateAppointmentDto } from '../use-cases/update-single-appointment/update-appointment-dto';
import { AppointmentDatabaseRepository } from './database-repository';

export class InMemoryAppointmentDatabaseRepository
Expand Down Expand Up @@ -94,6 +95,29 @@ export class InMemoryAppointmentDatabaseRepository
this.appointments[appointmentIndex] = updateAppointmentDate;
}

async updateAppointment(
newAppointmentInfo: UpdateAppointmentDto,
): Promise<void> {
const oldAppointmentInfo = await this.findSingleAppointmentById(
newAppointmentInfo.id,
);

if (!oldAppointmentInfo) {
throw new ConflictException(APPOINTMENT_ERROR_MESSAGES['APPOINTMENT_NOT_FOUND']);
}

const appointmentIndex = this.appointments.findIndex((appointment) => {
return appointment.id === newAppointmentInfo.id;
});

const updatedAppointment = Object.assign(oldAppointmentInfo, {
...newAppointmentInfo,
updatedAt: new Date(),
});

this.appointments[appointmentIndex] = updatedAppointment;
}

async deleteSingleAppointment(appointmentId: string): Promise<void> {
const isAppointmentExist = await this.findSingleAppointmentById(appointmentId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { AppointmentEntity } from '../entities/appointment/entity';
import { CreateSingleAppointmentDto } from '../use-cases/create-single-appointment/create-single-appointment-dto';
import { UpdatedAppointmentDateDto } from '../use-cases/update-appointment-date/update-appointment-date-dto';
import { UpdateAppointmentInfoDto } from '../use-cases/update-appointment-info/update-appointment-info-dto';
import { UpdateAppointmentDto } from '../use-cases/update-single-appointment/update-appointment-dto';

export abstract class AppointmentDatabaseRepository {
abstract createSingleAppointment(
Expand All @@ -20,5 +21,8 @@ export abstract class AppointmentDatabaseRepository {
abstract updateAppointmentDate(
newAppointmentInfo: UpdatedAppointmentDateDto
): Promise<void>;
abstract updateAppointment(
newAppointmentInfo: UpdateAppointmentDto
): Promise<void>;
abstract deleteSingleAppointment(appointmentId: string): Promise<void>;
}
Loading
Loading