From d464a297d0389e1d1dce5154245f4e12d6da5d3f Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Thu, 8 Dec 2022 10:32:21 -0500 Subject: [PATCH 01/10] deps: update to latest prisma --- server/package.json | 4 ++-- server/yarn.lock | 36 ++++++++++++++++++------------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/server/package.json b/server/package.json index 9ace85b..dbb2156 100644 --- a/server/package.json +++ b/server/package.json @@ -34,7 +34,7 @@ "@nestjs/platform-express": "^8.0.0", "@nestjs/terminus": "^8.0.6", "@nestjs/throttler": "^3.1.0", - "@prisma/client": "^3.13.0", + "@prisma/client": "^4.7.1", "@supercharge/request-ip": "^1.2.0", "@types/passport": "^1.0.7", "ajv": "^8.11.0", @@ -67,7 +67,7 @@ "eslint-plugin-prettier": "^4.0.0", "jest": "^27.2.5", "prettier": "^2.3.2", - "prisma": "^3.13.0", + "prisma": "^4.7.1", "source-map-support": "^0.5.20", "supertest": "^6.1.3", "ts-jest": "^27.0.3", diff --git a/server/yarn.lock b/server/yarn.lock index 0ad82a3..80793a3 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -760,22 +760,22 @@ consola "^2.15.0" node-fetch "^2.6.1" -"@prisma/client@^3.13.0": - version "3.15.2" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.15.2.tgz#2181398147afc79bfe0d83c03a88dc45b49bd365" - integrity sha512-ErqtwhX12ubPhU4d++30uFY/rPcyvjk+mdifaZO5SeM21zS3t4jQrscy8+6IyB0GIYshl5ldTq6JSBo1d63i8w== +"@prisma/client@^4.7.1": + version "4.7.1" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.7.1.tgz#66fe84aca25de17cb3d9141dec003f34714914b9" + integrity sha512-/GbnOwIPtjiveZNUzGXOdp7RxTEkHL4DZP3vBaFNadfr6Sf0RshU5EULFzVaSi9i9PIK9PYd+1Rn7z2B2npb9w== dependencies: - "@prisma/engines-version" "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" + "@prisma/engines-version" "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c" -"@prisma/engines-version@3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e": - version "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz#bf5e2373ca68ce7556b967cb4965a7095e93fe53" - integrity sha512-e3k2Vd606efd1ZYy2NQKkT4C/pn31nehyLhVug6To/q8JT8FpiMrDy7zmm3KLF0L98NOQQcutaVtAPhzKhzn9w== +"@prisma/engines-version@4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c": + version "4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.7.1-1.272861e07ab64f234d3ffc4094e32bd61775599c.tgz#43ff7d85478e64a1d790e4d53e78768a2acfacfe" + integrity sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q== -"@prisma/engines@3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e": - version "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e.tgz#f691893df506b93e3cb1ccc15ec6e5ac64e8e570" - integrity sha512-NHlojO1DFTsSi3FtEleL9QWXeSF/UjhCW0fgpi7bumnNZ4wj/eQ+BJJ5n2pgoOliTOGv9nX2qXvmHap7rJMNmg== +"@prisma/engines@4.7.1": + version "4.7.1" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.7.1.tgz#d657d4d05724158140022fa00614e143643090c2" + integrity sha512-zWabHosTdLpXXlMefHmnouhXMoTB1+SCbUU3t4FCmdrtIOZcarPKU3Alto7gm/pZ9vHlGOXHCfVZ1G7OIrSbog== "@sinonjs/commons@^1.7.0": version "1.8.3" @@ -4425,12 +4425,12 @@ pretty-format@^27.0.0, pretty-format@^27.5.0: ansi-styles "^5.0.0" react-is "^17.0.1" -prisma@^3.13.0: - version "3.15.2" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.15.2.tgz#4ebe32fb284da3ac60c49fbc16c75e56ecf32067" - integrity sha512-nMNSMZvtwrvoEQ/mui8L/aiCLZRCj5t6L3yujKpcDhIPk7garp8tL4nMx2+oYsN0FWBacevJhazfXAbV1kfBzA== +prisma@^4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.7.1.tgz#0a1beac26abdc4421e496b75eb50413f3ee3b0ba" + integrity sha512-CCQP+m+1qZOGIZlvnL6T3ZwaU0LAleIHYFPN9tFSzjs/KL6vH9rlYbGOkTuG9Q1s6Ki5D0LJlYlW18Z9EBUpGg== dependencies: - "@prisma/engines" "3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e" + "@prisma/engines" "4.7.1" process-nextick-args@~2.0.0: version "2.0.1" From 5079405bdf9debc73bd2faa6a3e32cf42057b7d9 Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Sat, 17 Dec 2022 14:53:30 -0500 Subject: [PATCH 02/10] chore: use proxy instead of cross origin requests --- client/.env.development | 2 +- client/vite.config.ts | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/client/.env.development b/client/.env.development index da9382b..413f441 100644 --- a/client/.env.development +++ b/client/.env.development @@ -1,2 +1,2 @@ -VITE_SERVER_URL=http://localhost:3001 +VITE_SERVER_URL=/api VITE_SITE_TITLE=Pemberley Springs SPA EDEN \ No newline at end of file diff --git a/client/vite.config.ts b/client/vite.config.ts index 2edae5f..b9610c5 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,12 +1,20 @@ +import { defineConfig } from 'vite'; import { VitePWA } from 'vite-plugin-pwa'; // https://vitejs.dev/config/ /** @type {import('vite').UserConfig} */ -export default { - test: { - globals: true, - environment: 'jsdom', +export default defineConfig({ + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:3001', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + }, + }, }, + plugins: [ VitePWA({ injectRegister: 'auto', @@ -63,7 +71,4 @@ export default { }, }), ], - server: { - port: 3000, - }, -}; +}); From 571e35679df91c814d2886253a4b828335ebb76c Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Sat, 17 Dec 2022 15:09:47 -0500 Subject: [PATCH 03/10] fix: fix test config --- client/vite.config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/vite.config.ts b/client/vite.config.ts index b9610c5..816f561 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,9 +1,15 @@ +/// + import { defineConfig } from 'vite'; import { VitePWA } from 'vite-plugin-pwa'; // https://vitejs.dev/config/ /** @type {import('vite').UserConfig} */ export default defineConfig({ + test: { + globals: true, + environment: 'jsdom', + }, server: { port: 3000, proxy: { From 884bba7996e77732074025d38cac2b7f3cc11ed5 Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Sat, 17 Dec 2022 16:36:04 -0500 Subject: [PATCH 04/10] wip: added CRUD endpoint for visit resource --- server/package.json | 1 + server/prisma/schema.prisma | 21 ++ .../src/models/visit/dto/create-visit.dto.ts | 11 + .../src/models/visit/dto/filter-visit.dto.ts | 17 ++ .../src/models/visit/dto/update-visit.dto.ts | 11 + .../src/models/visit/entities/visit.entity.ts | 1 + .../src/models/visit/visit.controller.spec.ts | 20 ++ server/src/models/visit/visit.controller.ts | 61 ++++++ server/src/models/visit/visit.module.ts | 12 ++ server/src/models/visit/visit.service.spec.ts | 18 ++ server/src/models/visit/visit.service.ts | 188 ++++++++++++++++++ server/yarn.lock | 5 + 12 files changed, 366 insertions(+) create mode 100644 server/src/models/visit/dto/create-visit.dto.ts create mode 100644 server/src/models/visit/dto/filter-visit.dto.ts create mode 100644 server/src/models/visit/dto/update-visit.dto.ts create mode 100644 server/src/models/visit/entities/visit.entity.ts create mode 100644 server/src/models/visit/visit.controller.spec.ts create mode 100644 server/src/models/visit/visit.controller.ts create mode 100644 server/src/models/visit/visit.module.ts create mode 100644 server/src/models/visit/visit.service.spec.ts create mode 100644 server/src/models/visit/visit.service.ts diff --git a/server/package.json b/server/package.json index dbb2156..244c4c3 100644 --- a/server/package.json +++ b/server/package.json @@ -52,6 +52,7 @@ }, "devDependencies": { "@nestjs/cli": "^8.0.0", + "@nestjs/mapped-types": "^1.2.0", "@nestjs/schematics": "^8.0.0", "@nestjs/testing": "^8.4.4", "@types/bcrypt": "^5.0.0", diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index b4e9961..e7e1475 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -61,6 +61,7 @@ model Staff { activityEntries ActivityEntry[] Ledger Ledger[] Audit Audit[] + Visit Visit[] } model Variant { @@ -168,6 +169,7 @@ model Customer { updatedAt DateTime @updatedAt activityEntries ActivityEntry[] Ledger Ledger[] + Visit Visit[] } model ActivityEntry { @@ -187,6 +189,8 @@ model ActivityEntry { // The reason why the charge is 1 to many because we can't have more than one charge that is // not linked to an activity entry (have activityEntryId as null). charge Ledger[] + Visit Visit? @relation(fields: [visitId], references: [id]) + visitId String? @db.ObjectId } model Ledger { @@ -200,6 +204,8 @@ model Ledger { customerId String @db.ObjectId staffId String @db.ObjectId activityEntryId String? @db.ObjectId + Visit Visit? @relation(fields: [visitId], references: [id]) + visitId String? @db.ObjectId } model Audit { @@ -212,3 +218,18 @@ model Audit { createdBy Staff @relation(fields: [staffId], references: [id]) createdAt DateTime @default(now()) } + +model Visit { + id String @id @default(auto()) @map("_id") @db.ObjectId + visitDate DateTime @default(now()) + customer Customer @relation(fields: [customerId], references: [id]) + customerId String @db.ObjectId + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + createdBy Staff @relation(fields: [staffId], references: [id]) + staffId String @db.ObjectId + activityEntries ActivityEntry[] + charge Ledger[] + fees Int @default(0) + tip Int @default(0) +} diff --git a/server/src/models/visit/dto/create-visit.dto.ts b/server/src/models/visit/dto/create-visit.dto.ts new file mode 100644 index 0000000..05e0d56 --- /dev/null +++ b/server/src/models/visit/dto/create-visit.dto.ts @@ -0,0 +1,11 @@ +import { IsISO8601, IsMongoId, IsOptional, IsString } from 'class-validator'; + +export class CreateVisitDto { + @IsString() + @IsMongoId() + customerId: string; + + @IsISO8601() + @IsOptional() + visitDate?: string; +} diff --git a/server/src/models/visit/dto/filter-visit.dto.ts b/server/src/models/visit/dto/filter-visit.dto.ts new file mode 100644 index 0000000..4057487 --- /dev/null +++ b/server/src/models/visit/dto/filter-visit.dto.ts @@ -0,0 +1,17 @@ +import { IsEnum, IsISO8601, IsMongoId, IsOptional } from 'class-validator'; + +export type VisitStatus = 'open' | 'closed'; + +export class FilterVisitDto { + @IsOptional() + @IsMongoId() + customerId?: string; + + @IsISO8601() + @IsOptional() + visitDate?: string; + + @IsOptional() + @IsEnum(['open', 'closed']) + status?: VisitStatus; +} diff --git a/server/src/models/visit/dto/update-visit.dto.ts b/server/src/models/visit/dto/update-visit.dto.ts new file mode 100644 index 0000000..c420672 --- /dev/null +++ b/server/src/models/visit/dto/update-visit.dto.ts @@ -0,0 +1,11 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { IsArray, IsMongoId } from 'class-validator'; +import { CreateVisitDto } from './create-visit.dto'; + +export class UpdateVisitDto extends PartialType(CreateVisitDto) { + @IsArray() + @IsMongoId({ + each: true, + }) + activityEntryIds: string[]; +} diff --git a/server/src/models/visit/entities/visit.entity.ts b/server/src/models/visit/entities/visit.entity.ts new file mode 100644 index 0000000..1df756d --- /dev/null +++ b/server/src/models/visit/entities/visit.entity.ts @@ -0,0 +1 @@ +export class Visit {} diff --git a/server/src/models/visit/visit.controller.spec.ts b/server/src/models/visit/visit.controller.spec.ts new file mode 100644 index 0000000..5073c3f --- /dev/null +++ b/server/src/models/visit/visit.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VisitController } from './visit.controller'; +import { VisitService } from './visit.service'; + +describe('VisitController', () => { + let controller: VisitController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [VisitController], + providers: [VisitService], + }).compile(); + + controller = module.get(VisitController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server/src/models/visit/visit.controller.ts b/server/src/models/visit/visit.controller.ts new file mode 100644 index 0000000..1825450 --- /dev/null +++ b/server/src/models/visit/visit.controller.ts @@ -0,0 +1,61 @@ +import { + Controller, + Get, + Post, + Body, + Param, + Delete, + UseInterceptors, + Put, + Query, + ValidationPipe, +} from '@nestjs/common'; +import { VisitService } from './visit.service'; +import { CreateVisitDto } from './dto/create-visit.dto'; +import { UpdateVisitDto } from './dto/update-visit.dto'; +import { TransformInterceptor } from 'src/interceptors/transform.interceptor'; +import { Auth } from 'src/auth/role.decorator'; +import { Actions, Features } from 'src/auth/constants'; +import { GetUser } from 'src/auth/user.decorator'; +import { Staff } from '@prisma/client'; +import { Auditable } from 'src/auth/audit.decorator'; +import { VALIDATION_PIPE_OPTION } from 'src/utils/consts'; +import { FilterVisitDto } from './dto/filter-visit.dto'; + +@Controller('visit') +@UseInterceptors(TransformInterceptor) +export class VisitController { + constructor(private readonly visitService: VisitService) {} + + @Auth(Actions.WRITE, [Features.Entry]) + @Auditable() + @Post() + create(@Body() createVisitDto: CreateVisitDto, @GetUser() { id }: Staff) { + return this.visitService.create(createVisitDto, id); + } + + @Auth(Actions.READ, [Features.Entry]) + @Get() + findAll( + @Query(new ValidationPipe(VALIDATION_PIPE_OPTION)) params: FilterVisitDto, + ) { + return this.visitService.findAll(params); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.visitService.findOne(id); + } + + @Auth(Actions.WRITE, [Features.Entry]) + @Auditable() + @Put(':id') + update(@Param('id') id: string, @Body() updateVisitDto: UpdateVisitDto) { + return this.visitService.update(id, updateVisitDto); + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.visitService.remove(id); + } +} diff --git a/server/src/models/visit/visit.module.ts b/server/src/models/visit/visit.module.ts new file mode 100644 index 0000000..ec529a4 --- /dev/null +++ b/server/src/models/visit/visit.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { VisitService } from './visit.service'; +import { VisitController } from './visit.controller'; +import { PrismaService } from 'src/prisma.service'; +import { AuthModule } from 'src/auth/auth.module'; + +@Module({ + controllers: [VisitController], + providers: [VisitService, PrismaService], + imports: [AuthModule], +}) +export class VisitModule {} diff --git a/server/src/models/visit/visit.service.spec.ts b/server/src/models/visit/visit.service.spec.ts new file mode 100644 index 0000000..0d229b0 --- /dev/null +++ b/server/src/models/visit/visit.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VisitService } from './visit.service'; + +describe('VisitService', () => { + let service: VisitService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [VisitService], + }).compile(); + + service = module.get(VisitService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server/src/models/visit/visit.service.ts b/server/src/models/visit/visit.service.ts new file mode 100644 index 0000000..3534dbb --- /dev/null +++ b/server/src/models/visit/visit.service.ts @@ -0,0 +1,188 @@ +import { Prisma } from '@prisma/client'; +import { BadRequestException, Injectable } from '@nestjs/common'; +import { PrismaService } from 'src/prisma.service'; +import { CreateVisitDto } from './dto/create-visit.dto'; +import { FilterVisitDto } from './dto/filter-visit.dto'; +import { UpdateVisitDto } from './dto/update-visit.dto'; + +@Injectable() +export class VisitService { + constructor(private readonly prisma: PrismaService) {} + create({ customerId, visitDate }: CreateVisitDto, staffId: string) { + return this.prisma.visit.create({ + data: { + customer: { + connect: { + id: customerId, + }, + }, + visitDate: visitDate ?? new Date(), + createdBy: { + connect: { + id: staffId, + }, + }, + }, + }); + } + + findAll({ customerId, status, visitDate }: FilterVisitDto) { + const filterOptions: Prisma.VisitWhereInput = {}; + if (customerId) { + filterOptions.customer = { + id: customerId, + }; + } + if (visitDate) { + filterOptions.visitDate = { + equals: visitDate, + }; + } + if (status === 'closed') { + filterOptions.charge = { + some: { + amount: { + not: 0, + }, + }, + }; + } + if (status === 'open') { + filterOptions.charge = { + none: { + amount: { + not: 0, + }, + }, + }; + } + return this.prisma.visit.findMany({ + where: filterOptions, + select: { + id: true, + visitDate: true, + updatedAt: true, + createdAt: true, + charge: { + select: { + id: true, + amount: true, + description: true, + createdDt: true, + }, + }, + customer: { + select: { + id: true, + firstName: true, + lastName: true, + email: true, + }, + }, + }, + }); + } + + findOne(id: string) { + return this.prisma.visit.findFirst({ + where: { + id, + }, + select: { + id: true, + visitDate: true, + updatedAt: true, + createdAt: true, + activityEntries: { + select: { + id: true, + author: { + select: { + id: true, + firstName: true, + lastName: true, + email: true, + }, + }, + activity: { + select: { + id: true, + name: true, + price: true, + }, + }, + products: { + select: { + id: true, + price: true, + product: { + select: { + name: true, + brand: true, + }, + }, + }, + }, + }, + }, + charge: { + select: { + id: true, + amount: true, + description: true, + createdDt: true, + }, + }, + customer: { + select: { + id: true, + firstName: true, + lastName: true, + email: true, + }, + }, + }, + }); + } + + update(id: string, updateVisitDto: UpdateVisitDto) { + const { activityEntryIds, customerId, ...cleaned } = updateVisitDto; + return this.prisma.visit.update({ + where: { + id, + }, + data: { + ...cleaned, + activityEntries: { + set: activityEntryIds.map((i) => ({ id: i })), + }, + customer: { + connect: { + id: customerId, + }, + }, + }, + }); + } + + async remove(id: string) { + if (await this.isVisitCharged(id)) { + throw new BadRequestException( + 'Visit cannot be deleted as it has already been charged', + ); + } + return this.prisma.visit.delete({ + where: { id }, + }); + } + + private async isVisitCharged(id: string) { + const { charge } = await this.prisma.visit.findFirst({ + where: { id }, + include: { + charge: true, + }, + }); + return !!charge.length; + } +} diff --git a/server/yarn.lock b/server/yarn.lock index 80793a3..5bb6af8 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -681,6 +681,11 @@ "@types/jsonwebtoken" "8.5.4" jsonwebtoken "8.5.1" +"@nestjs/mapped-types@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-1.2.0.tgz#1bbdbb5c956f0adb3fd76add929137bc6ad3183f" + integrity sha512-NTFwPZkQWsArQH8QSyFWGZvJ08gR+R4TofglqZoihn/vU+ktHEJjMqsIsADwb7XD97DhiD+TVv5ac+jG33BHrg== + "@nestjs/passport@^8.2.1": version "8.2.1" resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-8.2.1.tgz#a2abff9f51b3857b3423f5380a00f475aa298fe7" From be932e2b384e20a20a3f3ee1def16cc112a2f60e Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Sat, 17 Dec 2022 17:01:24 -0500 Subject: [PATCH 05/10] feat: added the ability to create a visit on entry create --- .../activity-entry.controller.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/server/src/models/activity-entry/activity-entry.controller.ts b/server/src/models/activity-entry/activity-entry.controller.ts index e08336f..a0e6618 100644 --- a/server/src/models/activity-entry/activity-entry.controller.ts +++ b/server/src/models/activity-entry/activity-entry.controller.ts @@ -137,13 +137,44 @@ export class ActivityEntryController { @Auth(Actions.WRITE, [Features.Entry]) @Post() @Auditable() - createActivityEntry(@Body() body: ActivityEntryDto, @Request() { user }) { + async createActivityEntry( + @Body() body: ActivityEntryDto, + @Request() { user }, + ) { + const visitFromToday = await this.prisma.visit.findFirst({ + where: { + visitDate: { + equals: new Date(new Date().toDateString()), + }, + customer: { + id: body.customerId, + }, + }, + }); return this.service.createActivityEntry({ author: { connect: { id: user.id, }, }, + Visit: { + connectOrCreate: { + where: { + id: visitFromToday.id, + }, + create: { + customer: { + connect: { + id: body.customerId, + }, + }, + visitDate: new Date(new Date().toDateString()), + createdBy: { + connect: user.id, + }, + }, + }, + }, customer: { connect: { id: body.customerId, From 2a3d9c9ffb2a9aa71f00c280eb7fef8304f23ba6 Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Tue, 20 Dec 2022 14:55:24 -0500 Subject: [PATCH 06/10] wip: started working on tests for new endpoint --- server/src/app.module.ts | 2 + .../activity-entry.controller.ts | 2 +- server/src/models/visit/visit.controller.ts | 2 +- server/src/models/visit/visit.module.ts | 1 + server/src/models/visit/visit.service.ts | 17 +++ server/test/jest-e2e.json | 2 +- server/test/visits.e2e-spec.ts | 112 ++++++++++++++++++ 7 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 server/test/visits.e2e-spec.ts diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 6aacce1..eae8424 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -20,6 +20,7 @@ import { ThrottlerModule } from '@nestjs/throttler'; import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core'; import { ThrottlerBehindProxyGuard } from './auth/throttle.guard'; import { AuditInterceptor } from './interceptors/audit.interceptor'; +import { VisitModule } from './models/visit/visit.module'; @Module({ imports: [ @@ -28,6 +29,7 @@ import { AuditInterceptor } from './interceptors/audit.interceptor'; AnalyticsModule, AuditModule, AuthModule, + VisitModule, CustomerModule, FeatureModule, FormModule, diff --git a/server/src/models/activity-entry/activity-entry.controller.ts b/server/src/models/activity-entry/activity-entry.controller.ts index a0e6618..dd6b9e6 100644 --- a/server/src/models/activity-entry/activity-entry.controller.ts +++ b/server/src/models/activity-entry/activity-entry.controller.ts @@ -135,8 +135,8 @@ export class ActivityEntryController { } @Auth(Actions.WRITE, [Features.Entry]) - @Post() @Auditable() + @Post() async createActivityEntry( @Body() body: ActivityEntryDto, @Request() { user }, diff --git a/server/src/models/visit/visit.controller.ts b/server/src/models/visit/visit.controller.ts index 1825450..babf86f 100644 --- a/server/src/models/visit/visit.controller.ts +++ b/server/src/models/visit/visit.controller.ts @@ -22,7 +22,7 @@ import { Auditable } from 'src/auth/audit.decorator'; import { VALIDATION_PIPE_OPTION } from 'src/utils/consts'; import { FilterVisitDto } from './dto/filter-visit.dto'; -@Controller('visit') +@Controller('visits') @UseInterceptors(TransformInterceptor) export class VisitController { constructor(private readonly visitService: VisitService) {} diff --git a/server/src/models/visit/visit.module.ts b/server/src/models/visit/visit.module.ts index ec529a4..15b2df1 100644 --- a/server/src/models/visit/visit.module.ts +++ b/server/src/models/visit/visit.module.ts @@ -8,5 +8,6 @@ import { AuthModule } from 'src/auth/auth.module'; controllers: [VisitController], providers: [VisitService, PrismaService], imports: [AuthModule], + exports: [VisitController], }) export class VisitModule {} diff --git a/server/src/models/visit/visit.service.ts b/server/src/models/visit/visit.service.ts index 3534dbb..56abee2 100644 --- a/server/src/models/visit/visit.service.ts +++ b/server/src/models/visit/visit.service.ts @@ -79,6 +79,23 @@ export class VisitService { email: true, }, }, + activityEntries: { + select: { + author: { + select: { + firstName: true, + lastName: true, + id: true, + email: true, + }, + }, + activity: { + select: { + name: true, + }, + }, + }, + }, }, }); } diff --git a/server/test/jest-e2e.json b/server/test/jest-e2e.json index e9d912f..ee37286 100644 --- a/server/test/jest-e2e.json +++ b/server/test/jest-e2e.json @@ -2,7 +2,7 @@ "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", + "testRegex": "visits.e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } diff --git a/server/test/visits.e2e-spec.ts b/server/test/visits.e2e-spec.ts new file mode 100644 index 0000000..4c53071 --- /dev/null +++ b/server/test/visits.e2e-spec.ts @@ -0,0 +1,112 @@ +import { randomUUID } from 'crypto'; +import { testClient } from './testClient'; +import * as yup from 'yup'; + +describe('/visits', () => { + let createdCustomer = null; + let accessToken: string | null = null; + const findAllSchema = yup.object({ + customer: yup.object({ + firstName: yup.string(), + lastName: yup.string(), + email: yup.string().email(), + }), + visitDate: yup.string(), + createdAt: yup.string(), + updatedAt: yup.string(), + charge: yup.array( + yup.object({ + id: yup.string(), + amount: yup.number().integer(), + description: yup.string(), + createdDt: yup.string(), + }), + ), + activityEntries: yup.array( + yup.object({ + author: yup.object({ + firstName: yup.string(), + lastName: yup.string(), + }), + }), + ), + }); + + beforeEach(async () => { + const { + body: { accessToken: token }, + } = await testClient.post('/auth/login').send({ + email: 'test@konomi.ai', + password: 'test', + }); + accessToken = token; + + const res = await testClient + .post('/customers') + .set('Authorization', `Bearer ${accessToken}`) + .send({ + firstName: 'Test', + lastName: 'Wang', + email: `test.visit.${randomUUID()}@test.konomi.ai`, + phone: '+14169671111', + dateOfBirth: '1999-07-29', + gender: 'MALE', + }) + .expect(201); + createdCustomer = res.body.data; + await testClient + .post('/visits') + .set('Authorization', `Bearer ${accessToken}`) + .send({ + customerId: createdCustomer.id, + }) + .expect(201); + }); + + it('/ (GET)', async () => { + await testClient + .get('/visits') + .expect(200) + .set('Authorization', `Bearer ${accessToken}`) + .then((res) => { + const { body } = res; + expect(body.data.length).toBeGreaterThan(0); + }); + }); + + // it('/:id (GET)', async () => { + // await testClient + // .get(`/customers/${createdCustomer.id}`) + // .expect(200) + // .then((res) => { + // const { body } = res; + // expect(findAllSchema.isValidSync(body.data)).toEqual(true); + // }); + // }); + + // it('/:id (PUT)', async () => { + // await testClient + // .put(`/customers/${createdCustomer.id}`) + // .send({ + // firstName: 'Updated', + // lastName: 'Test', + // email: `test.${randomUUID()}@test.konomi.ai`, + // phone: '+14169671111', + // dateOfBirth: '1999-07-29', + // gender: 'MALE', + // }) + // .expect(200); + // await testClient + // .get(`/customers/${createdCustomer.id}`) + // .expect(200) + // .then((res) => { + // const { body } = res; + // expect(body.data.firstName).toEqual('Updated'); + // }); + // }); + + // it('/:id (DELETE)', async () => { + // await testClient.delete(`/customers/${createdCustomer.id}`).expect(200); + // await testClient.get(`/customers/${createdCustomer.id}`).expect(404); + // }); +}); From df5b1f4c8ec16a2086b0b4d00da34c2fdb6521c8 Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Tue, 20 Dec 2022 16:36:51 -0500 Subject: [PATCH 07/10] wip: testing and improving --- .../src/models/visit/dto/update-visit.dto.ts | 15 ++- .../src/models/visit/entities/visit.entity.ts | 57 ++++++++++- server/src/models/visit/visit.controller.ts | 1 + server/src/models/visit/visit.module.ts | 1 - server/src/models/visit/visit.service.ts | 96 +------------------ server/test/visits.e2e-spec.ts | 74 ++++++++------ 6 files changed, 114 insertions(+), 130 deletions(-) diff --git a/server/src/models/visit/dto/update-visit.dto.ts b/server/src/models/visit/dto/update-visit.dto.ts index c420672..db868a5 100644 --- a/server/src/models/visit/dto/update-visit.dto.ts +++ b/server/src/models/visit/dto/update-visit.dto.ts @@ -1,11 +1,18 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { IsArray, IsMongoId } from 'class-validator'; -import { CreateVisitDto } from './create-visit.dto'; +import { IsArray, IsISO8601, IsMongoId } from 'class-validator'; + +export class UpdateVisitDto { + @IsMongoId() + id: string; + + @IsMongoId() + customerId: string; -export class UpdateVisitDto extends PartialType(CreateVisitDto) { @IsArray() @IsMongoId({ each: true, }) activityEntryIds: string[]; + + @IsISO8601() + visitDate: string; } diff --git a/server/src/models/visit/entities/visit.entity.ts b/server/src/models/visit/entities/visit.entity.ts index 1df756d..1b29b81 100644 --- a/server/src/models/visit/entities/visit.entity.ts +++ b/server/src/models/visit/entities/visit.entity.ts @@ -1 +1,56 @@ -export class Visit {} +import { Prisma } from '@prisma/client'; + +export const VISIT_SELECT: Prisma.VisitSelect = { + id: true, + visitDate: true, + updatedAt: true, + createdAt: true, + activityEntries: { + select: { + id: true, + author: { + select: { + id: true, + firstName: true, + lastName: true, + email: true, + }, + }, + activity: { + select: { + id: true, + name: true, + price: true, + }, + }, + products: { + select: { + id: true, + price: true, + product: { + select: { + name: true, + brand: true, + }, + }, + }, + }, + }, + }, + charge: { + select: { + id: true, + amount: true, + description: true, + createdDt: true, + }, + }, + customer: { + select: { + id: true, + firstName: true, + lastName: true, + email: true, + }, + }, +} as const; diff --git a/server/src/models/visit/visit.controller.ts b/server/src/models/visit/visit.controller.ts index babf86f..a22aee0 100644 --- a/server/src/models/visit/visit.controller.ts +++ b/server/src/models/visit/visit.controller.ts @@ -42,6 +42,7 @@ export class VisitController { return this.visitService.findAll(params); } + @Auth(Actions.READ, [Features.Entry]) @Get(':id') findOne(@Param('id') id: string) { return this.visitService.findOne(id); diff --git a/server/src/models/visit/visit.module.ts b/server/src/models/visit/visit.module.ts index 15b2df1..ec529a4 100644 --- a/server/src/models/visit/visit.module.ts +++ b/server/src/models/visit/visit.module.ts @@ -8,6 +8,5 @@ import { AuthModule } from 'src/auth/auth.module'; controllers: [VisitController], providers: [VisitService, PrismaService], imports: [AuthModule], - exports: [VisitController], }) export class VisitModule {} diff --git a/server/src/models/visit/visit.service.ts b/server/src/models/visit/visit.service.ts index 56abee2..fd769ae 100644 --- a/server/src/models/visit/visit.service.ts +++ b/server/src/models/visit/visit.service.ts @@ -4,6 +4,7 @@ import { PrismaService } from 'src/prisma.service'; import { CreateVisitDto } from './dto/create-visit.dto'; import { FilterVisitDto } from './dto/filter-visit.dto'; import { UpdateVisitDto } from './dto/update-visit.dto'; +import { VISIT_SELECT } from './entities/visit.entity'; @Injectable() export class VisitService { @@ -58,45 +59,7 @@ export class VisitService { } return this.prisma.visit.findMany({ where: filterOptions, - select: { - id: true, - visitDate: true, - updatedAt: true, - createdAt: true, - charge: { - select: { - id: true, - amount: true, - description: true, - createdDt: true, - }, - }, - customer: { - select: { - id: true, - firstName: true, - lastName: true, - email: true, - }, - }, - activityEntries: { - select: { - author: { - select: { - firstName: true, - lastName: true, - id: true, - email: true, - }, - }, - activity: { - select: { - name: true, - }, - }, - }, - }, - }, + select: VISIT_SELECT, }); } @@ -105,60 +68,7 @@ export class VisitService { where: { id, }, - select: { - id: true, - visitDate: true, - updatedAt: true, - createdAt: true, - activityEntries: { - select: { - id: true, - author: { - select: { - id: true, - firstName: true, - lastName: true, - email: true, - }, - }, - activity: { - select: { - id: true, - name: true, - price: true, - }, - }, - products: { - select: { - id: true, - price: true, - product: { - select: { - name: true, - brand: true, - }, - }, - }, - }, - }, - }, - charge: { - select: { - id: true, - amount: true, - description: true, - createdDt: true, - }, - }, - customer: { - select: { - id: true, - firstName: true, - lastName: true, - email: true, - }, - }, - }, + select: VISIT_SELECT, }); } diff --git a/server/test/visits.e2e-spec.ts b/server/test/visits.e2e-spec.ts index 4c53071..877c44f 100644 --- a/server/test/visits.e2e-spec.ts +++ b/server/test/visits.e2e-spec.ts @@ -4,9 +4,11 @@ import * as yup from 'yup'; describe('/visits', () => { let createdCustomer = null; + let createdVisit = null; let accessToken: string | null = null; const findAllSchema = yup.object({ customer: yup.object({ + id: yup.string(), firstName: yup.string(), lastName: yup.string(), email: yup.string().email(), @@ -54,56 +56,66 @@ describe('/visits', () => { }) .expect(201); createdCustomer = res.body.data; - await testClient + const visitRes = await testClient .post('/visits') .set('Authorization', `Bearer ${accessToken}`) .send({ customerId: createdCustomer.id, }) .expect(201); + createdVisit = visitRes.body.data; }); it('/ (GET)', async () => { await testClient .get('/visits') - .expect(200) .set('Authorization', `Bearer ${accessToken}`) + .expect(200) .then((res) => { const { body } = res; expect(body.data.length).toBeGreaterThan(0); + expect(findAllSchema.isValidSync(body.data[0])); }); }); - // it('/:id (GET)', async () => { - // await testClient - // .get(`/customers/${createdCustomer.id}`) - // .expect(200) - // .then((res) => { - // const { body } = res; - // expect(findAllSchema.isValidSync(body.data)).toEqual(true); - // }); - // }); + it('/:id (GET)', async () => { + await testClient + .get(`/visits/${createdVisit.id}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(200) + .then((res) => { + const { body } = res; + expect(findAllSchema.isValidSync(body.data)).toEqual(true); + }); + }); - // it('/:id (PUT)', async () => { - // await testClient - // .put(`/customers/${createdCustomer.id}`) - // .send({ - // firstName: 'Updated', - // lastName: 'Test', - // email: `test.${randomUUID()}@test.konomi.ai`, - // phone: '+14169671111', - // dateOfBirth: '1999-07-29', - // gender: 'MALE', - // }) - // .expect(200); - // await testClient - // .get(`/customers/${createdCustomer.id}`) - // .expect(200) - // .then((res) => { - // const { body } = res; - // expect(body.data.firstName).toEqual('Updated'); - // }); - // }); + it('/:id (PUT)', async () => { + const { + body: { data: targetVisit }, + } = await testClient + .get(`/visits/${createdVisit.id}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(200); + + await testClient + .put(`/visits/${createdVisit.id}`) + .set('Authorization', `Bearer ${accessToken}`) + .send({ + id: createdVisit.id, + customerId: targetVisit.customer.id, + activityEntries: [], + visitDate: '2022-12-12', + }) + .expect(200); + await testClient + .get(`/visits/${createdVisit.id}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(200) + .then((res) => { + const { body } = res; + expect(body.data.visitDate).toEqual('2022-12-12'); + }); + }); // it('/:id (DELETE)', async () => { // await testClient.delete(`/customers/${createdCustomer.id}`).expect(200); From 433b160b79a977c693c474a64dc99b3e171ed425 Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Thu, 22 Dec 2022 12:25:20 -0500 Subject: [PATCH 08/10] fix: fix put test --- server/package.json | 1 + server/prisma/schema.prisma | 2 +- .../activity-entry/activity-entry.controller.ts | 5 +++-- server/src/models/visit/dto/update-visit.dto.ts | 3 --- server/src/models/visit/visit.service.ts | 3 ++- server/src/utils/dates.ts | 3 +++ server/test/visits.e2e-spec.ts | 15 +++++++++++++-- server/yarn.lock | 5 +++++ 8 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 server/src/utils/dates.ts diff --git a/server/package.json b/server/package.json index 244c4c3..5f60444 100644 --- a/server/package.json +++ b/server/package.json @@ -42,6 +42,7 @@ "cidr-matcher": "^2.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", + "date-fns": "^2.29.3", "jest-mock-extended": "2.0.4", "passport": "^0.5.2", "passport-jwt": "^4.0.0", diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index e7e1475..048e63c 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -221,7 +221,7 @@ model Audit { model Visit { id String @id @default(auto()) @map("_id") @db.ObjectId - visitDate DateTime @default(now()) + visitDate String customer Customer @relation(fields: [customerId], references: [id]) customerId String @db.ObjectId createdAt DateTime @default(now()) diff --git a/server/src/models/activity-entry/activity-entry.controller.ts b/server/src/models/activity-entry/activity-entry.controller.ts index dd6b9e6..89aecaa 100644 --- a/server/src/models/activity-entry/activity-entry.controller.ts +++ b/server/src/models/activity-entry/activity-entry.controller.ts @@ -18,6 +18,7 @@ import { Auth } from 'src/auth/role.decorator'; import { GetUser } from 'src/auth/user.decorator'; import { TransformInterceptor } from 'src/interceptors/transform.interceptor'; import { PrismaService } from 'src/prisma.service'; +import { getTodayDateString } from 'src/utils/dates'; import { LedgerService } from '../ledger/ledger.service'; import { ActivityEntryChargeDto, ActivityEntryDto } from './activity-entry.dto'; import { ActivityEntryService } from './activity-entry.service'; @@ -144,7 +145,7 @@ export class ActivityEntryController { const visitFromToday = await this.prisma.visit.findFirst({ where: { visitDate: { - equals: new Date(new Date().toDateString()), + equals: getTodayDateString(), }, customer: { id: body.customerId, @@ -168,7 +169,7 @@ export class ActivityEntryController { id: body.customerId, }, }, - visitDate: new Date(new Date().toDateString()), + visitDate: getTodayDateString(), createdBy: { connect: user.id, }, diff --git a/server/src/models/visit/dto/update-visit.dto.ts b/server/src/models/visit/dto/update-visit.dto.ts index db868a5..7c0f336 100644 --- a/server/src/models/visit/dto/update-visit.dto.ts +++ b/server/src/models/visit/dto/update-visit.dto.ts @@ -1,9 +1,6 @@ import { IsArray, IsISO8601, IsMongoId } from 'class-validator'; export class UpdateVisitDto { - @IsMongoId() - id: string; - @IsMongoId() customerId: string; diff --git a/server/src/models/visit/visit.service.ts b/server/src/models/visit/visit.service.ts index fd769ae..66079e7 100644 --- a/server/src/models/visit/visit.service.ts +++ b/server/src/models/visit/visit.service.ts @@ -5,6 +5,7 @@ import { CreateVisitDto } from './dto/create-visit.dto'; import { FilterVisitDto } from './dto/filter-visit.dto'; import { UpdateVisitDto } from './dto/update-visit.dto'; import { VISIT_SELECT } from './entities/visit.entity'; +import { getTodayDateString } from 'src/utils/dates'; @Injectable() export class VisitService { @@ -17,7 +18,7 @@ export class VisitService { id: customerId, }, }, - visitDate: visitDate ?? new Date(), + visitDate: visitDate ?? getTodayDateString(), createdBy: { connect: { id: staffId, diff --git a/server/src/utils/dates.ts b/server/src/utils/dates.ts new file mode 100644 index 0000000..17b04c9 --- /dev/null +++ b/server/src/utils/dates.ts @@ -0,0 +1,3 @@ +import { format } from 'date-fns'; + +export const getTodayDateString = () => format(new Date(), 'yyyy-MM-dd'); diff --git a/server/test/visits.e2e-spec.ts b/server/test/visits.e2e-spec.ts index 877c44f..51e4d0c 100644 --- a/server/test/visits.e2e-spec.ts +++ b/server/test/visits.e2e-spec.ts @@ -97,13 +97,24 @@ describe('/visits', () => { .set('Authorization', `Bearer ${accessToken}`) .expect(200); + console.log( + JSON.stringify( + { + id: createdVisit.id, + customerId: targetVisit.customer.id, + activityEntries: [], + visitDate: '2022-12-12', + }, + null, + 2, + ), + ); await testClient .put(`/visits/${createdVisit.id}`) .set('Authorization', `Bearer ${accessToken}`) .send({ - id: createdVisit.id, customerId: targetVisit.customer.id, - activityEntries: [], + activityEntryIds: [], visitDate: '2022-12-12', }) .expect(200); diff --git a/server/yarn.lock b/server/yarn.lock index 5bb6af8..d3c5914 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -2102,6 +2102,11 @@ date-fns@^2.16.1: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== +date-fns@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== + debug@2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" From 2a45ca69c65bb69c51a3015465a0018cd684b6f4 Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Thu, 22 Dec 2022 12:25:50 -0500 Subject: [PATCH 09/10] chore: revert test rejex --- server/test/jest-e2e.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/test/jest-e2e.json b/server/test/jest-e2e.json index ee37286..e9d912f 100644 --- a/server/test/jest-e2e.json +++ b/server/test/jest-e2e.json @@ -2,7 +2,7 @@ "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "testEnvironment": "node", - "testRegex": "visits.e2e-spec.ts$", + "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" } From eefc3ffa7de0fdd473ec166f4e91a235e0d9a55c Mon Sep 17 00:00:00 2001 From: Yong Lin Wang Date: Sat, 31 Dec 2022 16:31:27 -0500 Subject: [PATCH 10/10] test: added visit delete test --- server/test/visits.e2e-spec.ts | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/server/test/visits.e2e-spec.ts b/server/test/visits.e2e-spec.ts index 51e4d0c..3b8b8e2 100644 --- a/server/test/visits.e2e-spec.ts +++ b/server/test/visits.e2e-spec.ts @@ -97,18 +97,6 @@ describe('/visits', () => { .set('Authorization', `Bearer ${accessToken}`) .expect(200); - console.log( - JSON.stringify( - { - id: createdVisit.id, - customerId: targetVisit.customer.id, - activityEntries: [], - visitDate: '2022-12-12', - }, - null, - 2, - ), - ); await testClient .put(`/visits/${createdVisit.id}`) .set('Authorization', `Bearer ${accessToken}`) @@ -128,8 +116,16 @@ describe('/visits', () => { }); }); - // it('/:id (DELETE)', async () => { - // await testClient.delete(`/customers/${createdCustomer.id}`).expect(200); - // await testClient.get(`/customers/${createdCustomer.id}`).expect(404); - // }); + it('/:id (DELETE)', async () => { + const { + body: { data: targetVisit }, + } = await testClient + .get(`/visits/${createdVisit.id}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(200); + await testClient + .delete(`/visits/${targetVisit.id}`) + .set('Authorization', `Bearer ${accessToken}`) + .expect(200); + }); });