From 4fe628be38b83f5684e2ccbad557751b7343bea9 Mon Sep 17 00:00:00 2001 From: Jay McDoniel Date: Sun, 17 Nov 2019 22:42:54 -0800 Subject: [PATCH] merge(mongo-sample): merge mongo-sample branch to master * fix(actions): update GitHub actions Actions are now 1-to-1 mapped for each application to run its own tests * feat(mongo): add test suite for mongo This suite covers a basic CRUD application for mongodb and NestJS Related to #3 --- .github/workflows/{nodejs.yml => complex.yml} | 9 +- .github/workflows/mongo.yml | 29 +++ .github/workflows/simple.yml | 29 +++ .github/workflows/typeorm-graphql.yml | 29 +++ .github/workflows/typeorm.yml | 29 +++ Contributing.md | 6 - README.md | 2 +- apps/mongo-sample/README.md | 11 + apps/mongo-sample/src/app.module.ts | 4 +- .../src/cat/cat.controller.spec.ts | 168 +++++++++++++++ apps/mongo-sample/src/cat/cat.controller.ts | 48 +++++ apps/mongo-sample/src/cat/cat.dto.ts | 6 + apps/mongo-sample/src/cat/cat.module.ts | 12 ++ apps/mongo-sample/src/cat/cat.service.spec.ts | 194 ++++++++++++++++++ apps/mongo-sample/src/cat/cat.service.ts | 78 +++++++ .../cat/interfaces/cat-document.interface.ts | 7 + .../src/cat/interfaces/cat.interface.ts | 6 + .../src/cat/schemas/cat.schema.ts | 7 + apps/mongo-sample/testCoverage.png | Bin 0 -> 8328 bytes apps/typeorm-sample/README.md | 4 + package-lock.json | 173 +++++++++++++++- package.json | 4 +- 22 files changed, 837 insertions(+), 18 deletions(-) rename .github/workflows/{nodejs.yml => complex.yml} (70%) create mode 100644 .github/workflows/mongo.yml create mode 100644 .github/workflows/simple.yml create mode 100644 .github/workflows/typeorm-graphql.yml create mode 100644 .github/workflows/typeorm.yml create mode 100644 apps/mongo-sample/README.md create mode 100644 apps/mongo-sample/src/cat/cat.controller.spec.ts create mode 100644 apps/mongo-sample/src/cat/cat.controller.ts create mode 100644 apps/mongo-sample/src/cat/cat.dto.ts create mode 100644 apps/mongo-sample/src/cat/cat.module.ts create mode 100644 apps/mongo-sample/src/cat/cat.service.spec.ts create mode 100644 apps/mongo-sample/src/cat/cat.service.ts create mode 100644 apps/mongo-sample/src/cat/interfaces/cat-document.interface.ts create mode 100644 apps/mongo-sample/src/cat/interfaces/cat.interface.ts create mode 100644 apps/mongo-sample/src/cat/schemas/cat.schema.ts create mode 100644 apps/mongo-sample/testCoverage.png diff --git a/.github/workflows/nodejs.yml b/.github/workflows/complex.yml similarity index 70% rename from .github/workflows/nodejs.yml rename to .github/workflows/complex.yml index 1b7f17ba..2b183af1 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/complex.yml @@ -1,8 +1,8 @@ -name: Node CI +name: Complex Sample -on: +on: pull_request: - branches: + branches: - 'master' push: @@ -14,7 +14,6 @@ jobs: strategy: matrix: node-version: [10.x, 12.x] - sample-name: [complex-sample, simple-sample, typeorm-sample, typeorm-graphql-sample] steps: - uses: actions/checkout@v1 @@ -25,6 +24,6 @@ jobs: - name: npm install run: npm ci - name: npm test - run: npm test -- ${{ matrix.sample-name }} + run: npm test -- complex-sample env: CI: true diff --git a/.github/workflows/mongo.yml b/.github/workflows/mongo.yml new file mode 100644 index 00000000..470505f0 --- /dev/null +++ b/.github/workflows/mongo.yml @@ -0,0 +1,29 @@ +name: Mongo Sample + +on: + pull_request: + branches: + - 'master' + push: + +jobs: + test: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: npm install + run: npm ci + - name: npm test + run: npm test -- mongo-sample + env: + CI: true diff --git a/.github/workflows/simple.yml b/.github/workflows/simple.yml new file mode 100644 index 00000000..9826a397 --- /dev/null +++ b/.github/workflows/simple.yml @@ -0,0 +1,29 @@ +name: Simple Sample + +on: + pull_request: + branches: + - 'master' + push: + +jobs: + test: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: npm install + run: npm ci + - name: npm test + run: npm test -- simple-sample + env: + CI: true diff --git a/.github/workflows/typeorm-graphql.yml b/.github/workflows/typeorm-graphql.yml new file mode 100644 index 00000000..08ba384e --- /dev/null +++ b/.github/workflows/typeorm-graphql.yml @@ -0,0 +1,29 @@ +name: TypeORM GraphQL Sample + +on: + pull_request: + branches: + - 'master' + push: + +jobs: + test: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: npm install + run: npm ci + - name: npm test + run: npm test -- typeorm-graphql-sample + env: + CI: true diff --git a/.github/workflows/typeorm.yml b/.github/workflows/typeorm.yml new file mode 100644 index 00000000..adfafa3f --- /dev/null +++ b/.github/workflows/typeorm.yml @@ -0,0 +1,29 @@ +name: TypeORM Sample + +on: + pull_request: + branches: + - 'master' + push: + +jobs: + test: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [10.x, 12.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: npm install + run: npm ci + - name: npm test + run: npm test -- typeorm-sample + env: + CI: true diff --git a/Contributing.md b/Contributing.md index b892ca4a..37176c05 100644 --- a/Contributing.md +++ b/Contributing.md @@ -27,12 +27,6 @@ Or run tests in watch mode: npm run test:watch ``` -To update Jest snapshots: - -```bash -npm run test:jest -- -u -``` - **Don’t forget to add tests and update documentation for your changes.** **Please update npm lock file (`package-lock.json`) if you add or update dependencies.** diff --git a/README.md b/README.md index 1ac6f0f9..aaad0472 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ This is not necessarily the Nest canonical way to test an application, nor is it 1. `git clone https://github.com/jmcdo29/testing-nestjs.git` 2. `cd testing-nestjs/` 3. `npm install` OR `yarn add` -4. `npm run test` OR `npm run test:e2e` OR `yarn test` OR `yarn test:e2e` +4. `npm run test` OR `yarn test` ## Contributing diff --git a/apps/mongo-sample/README.md b/apps/mongo-sample/README.md new file mode 100644 index 00000000..7978067d --- /dev/null +++ b/apps/mongo-sample/README.md @@ -0,0 +1,11 @@ +

+ +

+ +# Mongo Sample + +Welcome to the example of using MongoDB with Nest and running tests! My _second favorite_ topic! I decided to go with a very simple CRUD application for a single database object, but if there is enough of a demand I will extend this out to a larger repository with more objects and options. Not much else to say other than I hope this is found as helpful to the community! + +## Side Note + +For this application, I have added a side package called `@golevelup/nestjs-testing` (name subject to change) to help with mocking Repository objects without needing to create the entire mock on your own. I did run into a few issues with the library this time around, namely matching mocks to mocks, so that's something I'll need to work on updating in the future. In the meantime, enjoy the tests! diff --git a/apps/mongo-sample/src/app.module.ts b/apps/mongo-sample/src/app.module.ts index 86628031..d31b6e43 100644 --- a/apps/mongo-sample/src/app.module.ts +++ b/apps/mongo-sample/src/app.module.ts @@ -1,9 +1,11 @@ import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { CatModule } from './cat/cat.module'; @Module({ - imports: [], + imports: [MongooseModule.forRoot(process.env.MONGODB_URI), CatModule], controllers: [AppController], providers: [AppService], }) diff --git a/apps/mongo-sample/src/cat/cat.controller.spec.ts b/apps/mongo-sample/src/cat/cat.controller.spec.ts new file mode 100644 index 00000000..7d9b9c7e --- /dev/null +++ b/apps/mongo-sample/src/cat/cat.controller.spec.ts @@ -0,0 +1,168 @@ +import { createMock, DeepMocked } from '@golevelup/nestjs-testing'; +import { Test, TestingModule } from '@nestjs/testing'; +import { CatController } from './cat.controller'; +import { CatDTO } from './cat.dto'; +import { CatService } from './cat.service'; +import { Cat } from './interfaces/cat.interface'; + +describe('Cat Controller', () => { + let controller: CatController; + let service: CatService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [CatController], + // If you've looked at the complex sample you'll notice that these functions + // are a little bit more in depth using mock implementation + // to give us a little bit more control and flexibility in our tests + // this is not necessary, but can sometimes be helpful in a test scenario + providers: [ + { + provide: CatService, + useValue: { + getAll: jest + .fn() + .mockResolvedValue([ + { name: 'Test Cat 1', breed: 'Test Breed 1', age: 4 }, + { name: 'Test Cat 2', breed: 'Test Breed 2', age: 3 }, + { name: 'Test Cat 3', breed: 'Test Breed 3', age: 2 }, + ]), + getOne: jest.fn().mockImplementation((id: string) => + Promise.resolve({ + name: 'Test Cat 1', + breed: 'Test Breed 1', + age: 4, + _id: id, + }), + ), + getOneByName: jest + .fn() + .mockImplementation((name: string) => + Promise.resolve({ name, breed: 'Test Breed 1', age: 4 }), + ), + insertOne: jest + .fn() + .mockImplementation((cat: CatDTO) => + Promise.resolve({ _id: 'a uuid', ...cat }), + ), + updateOne: jest + .fn() + .mockImplementation((cat: CatDTO) => + Promise.resolve({ _id: 'a uuid', ...cat }), + ), + deleteOne: jest.fn().mockResolvedValue({ deleted: true }), + }, + }, + ], + }).compile(); + + controller = module.get(CatController); + service = module.get(CatService); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('getCats', () => { + it('should get an array of cats', () => { + expect(controller.getCats()).resolves.toEqual([ + { + name: 'Test Cat 1', + breed: 'Test Breed 1', + age: 4, + }, + { + name: 'Test Cat 2', + breed: 'Test Breed 2', + age: 3, + }, + { + name: 'Test Cat 3', + breed: 'Test Breed 3', + age: 2, + }, + ]); + }); + }); + describe('getById', () => { + it('should get a single cat', () => { + expect(controller.getById('a strange id')).resolves.toEqual({ + name: 'Test Cat 1', + breed: 'Test Breed 1', + age: 4, + _id: 'a strange id', + }); + expect(controller.getById('a different id')).resolves.toEqual({ + name: 'Test Cat 1', + breed: 'Test Breed 1', + age: 4, + _id: 'a different id', + }); + }); + }); + describe('getByName', () => { + it('should get a cat back', async () => { + expect(controller.getByName('Ventus')).resolves.toEqual({ + name: 'Ventus', + breed: 'Test Breed 1', + age: 4, + }); + // using the really cool @golevelup/nestjs-testing module's utility function here + // otherwise we need to pass `as any` or we need to mock all 54+ attributes of Document + const aquaMock = createMock({ + name: 'Aqua', + breed: 'Maine Coon', + age: 5, + }); + const getByNameSpy = jest + .spyOn(service, 'getOneByName') + .mockResolvedValueOnce(aquaMock); + const getResponse = await controller.getByName('Aqua'); + expect(getResponse).toEqual(aquaMock); + expect(getByNameSpy).toBeCalledWith('Aqua'); + }); + }); + describe('newCat', () => { + it('should create a new cat', () => { + const newCatDTO: CatDTO = { + name: 'New Cat 1', + breed: 'New Breed 1', + age: 4, + }; + expect(controller.newCat(newCatDTO)).resolves.toEqual({ + _id: 'a uuid', + ...newCatDTO, + }); + }); + }); + describe('updateCat', () => { + it('should update a new cat', () => { + const newCatDTO: CatDTO = { + name: 'New Cat 1', + breed: 'New Breed 1', + age: 4, + }; + expect(controller.updateCat(newCatDTO)).resolves.toEqual({ + _id: 'a uuid', + ...newCatDTO, + }); + }); + }); + describe('deleteCat', () => { + it('should return that it deleted a cat', () => { + expect(controller.deleteCat('a uuid that exists')).resolves.toEqual({ + deleted: true, + }); + }); + it('should return that it did not delete a cat', () => { + const deleteSpy = jest + .spyOn(service, 'deleteOne') + .mockResolvedValueOnce({ deleted: false }); + expect( + controller.deleteCat('a uuid that does not exist'), + ).resolves.toEqual({ deleted: false }); + expect(deleteSpy).toBeCalledWith('a uuid that does not exist'); + }); + }); +}); diff --git a/apps/mongo-sample/src/cat/cat.controller.ts b/apps/mongo-sample/src/cat/cat.controller.ts new file mode 100644 index 00000000..f65c57b5 --- /dev/null +++ b/apps/mongo-sample/src/cat/cat.controller.ts @@ -0,0 +1,48 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Query, +} from '@nestjs/common'; +import { CatDTO } from './cat.dto'; +import { Cat } from './interfaces/cat.interface'; +import { CatService } from './cat.service'; + +@Controller('cat') +export class CatController { + constructor(private readonly catService: CatService) {} + + @Get() + async getCats(): Promise { + return this.catService.getAll(); + } + + @Get('/:id') + async getById(@Param('id') id: string): Promise { + return this.catService.getOne(id); + } + + @Get('/name') + async getByName(@Query('name') name: string): Promise { + return this.catService.getOneByName(name); + } + + @Post('/new') + async newCat(@Body() cat: CatDTO): Promise { + return this.catService.insertOne(cat); + } + + @Patch('/update') + async updateCat(@Body() cat: CatDTO): Promise { + return this.catService.updateOne(cat); + } + + @Delete('/delete/:id') + async deleteCat(@Param('id') id: string): Promise<{ deleted: boolean }> { + return this.catService.deleteOne(id); + } +} diff --git a/apps/mongo-sample/src/cat/cat.dto.ts b/apps/mongo-sample/src/cat/cat.dto.ts new file mode 100644 index 00000000..492fa047 --- /dev/null +++ b/apps/mongo-sample/src/cat/cat.dto.ts @@ -0,0 +1,6 @@ +export interface CatDTO { + _id?: string; + name?: string; + breed?: string; + age?: number; +} diff --git a/apps/mongo-sample/src/cat/cat.module.ts b/apps/mongo-sample/src/cat/cat.module.ts new file mode 100644 index 00000000..ad973a8d --- /dev/null +++ b/apps/mongo-sample/src/cat/cat.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { CatController } from './cat.controller'; +import { CatService } from './cat.service'; +import { MongooseModule } from '@nestjs/mongoose'; +import { CatSchema } from './schemas/cat.schema'; + +@Module({ + imports: [MongooseModule.forFeature([{ name: 'Cat', schema: CatSchema }])], + controllers: [CatController], + providers: [CatService], +}) +export class CatModule {} diff --git a/apps/mongo-sample/src/cat/cat.service.spec.ts b/apps/mongo-sample/src/cat/cat.service.spec.ts new file mode 100644 index 00000000..08bd6de1 --- /dev/null +++ b/apps/mongo-sample/src/cat/cat.service.spec.ts @@ -0,0 +1,194 @@ +/** + * You'll note that in a lot of this test class we use `to any` + * rather liberally. Normally I'd be against this, but I don't + * really want to mock all 59 fields **and** the ones we have + * defined for our model, so instead we add an `as any` and + * make those errors magically go away. In all seriousness + * you may want to use some sort of base file elsewhere that + * contains all the basic mock fields so you can take that + * and add your fields on top. Seriously, 59 plus fields is a lot. + */ + +import { Test, TestingModule } from '@nestjs/testing'; +import { CatService } from './cat.service'; +import { getModelToken } from '@nestjs/mongoose'; +import { Cat } from './interfaces/cat.interface'; +import { createMock } from '@golevelup/nestjs-testing'; +import { Model, DocumentQuery } from 'mongoose'; +import { CatDoc } from './interfaces/cat-document.interface'; + +// I'm lazy and like to have functions that can be re-used to deal with a lot of my initialization/creation logic +const mockCat: ( + name?: string, + id?: string, + age?: number, + breed?: string, +) => Cat = ( + name: string = 'Ventus', + id: string = 'a uuid', + age: number = 4, + breed: string = 'Russian Blue', +) => { + return { + name, + id, + age, + breed, + }; +}; + +// still lazy, but this time using an object instead of multiple parameters +const mockCatDoc: (mock?: { + name?: string; + id?: string; + breed?: string; + age?: number; +}) => Partial = (mock?: { + name: string; + id: string; + age: number; + breed: string; +}) => { + return { + name: (mock && mock.name) || 'Ventus', + _id: (mock && mock.id) || 'a uuid', + age: (mock && mock.age) || 4, + breed: (mock && mock.breed) || 'Russian Blue', + }; +}; + +const catArray: Cat[] = [ + mockCat(), + mockCat('Vitani', 'a new uuid', 2, 'Tabby'), + mockCat('Simba', 'the king', 14, 'Lion'), +]; + +const catDocArray = [ + mockCatDoc(), + mockCatDoc({ name: 'Vitani', id: 'a new uuid', age: 2, breed: 'Tabby' }), + mockCatDoc({ name: 'Simba', age: 14, id: 'the king', breed: 'Lion' }), +]; + +describe('CatService', () => { + let service: CatService; + let model: Model; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + CatService, + { + provide: getModelToken('Cat'), + // notice that only the functions we call from the model are mocked + useValue: { + new: jest.fn().mockResolvedValue(mockCat()), + constructor: jest.fn().mockResolvedValue(mockCat()), + find: jest.fn(), + findOne: jest.fn(), + update: jest.fn(), + create: jest.fn(), + remove: jest.fn(), + exec: jest.fn(), + }, + }, + ], + }).compile(); + + service = module.get(CatService); + model = module.get>(getModelToken('Cat')); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + // In all the spy methods/mock methods we need to make sure to + // add in the property function exec and tell it what to return + // this way all of our mongo functions can and will be called + // properly allowing for us to successfully test them. + it('should return all cats', () => { + jest.spyOn(model, 'find').mockReturnValue({ + exec: jest.fn().mockResolvedValueOnce(catDocArray), + } as any); + expect(service.getAll()).resolves.toEqual(catArray); + }); + it('should getOne by id', async () => { + jest.spyOn(model, 'findOne').mockReturnValueOnce( + createMock>({ + exec: jest + .fn() + .mockResolvedValueOnce(mockCatDoc({ name: 'Ventus', id: 'an id' })), + }), + ); + const findMockCat = mockCat('Ventus', 'an id'); + const foundCat = await service.getOne('an id'); + expect(foundCat).toEqual(findMockCat); + }); + it('should getOne by name', async () => { + jest.spyOn(model, 'findOne').mockReturnValueOnce( + createMock>({ + exec: jest + .fn() + .mockResolvedValueOnce( + mockCatDoc({ name: 'Mufasa', id: 'the dead king' }), + ), + }), + ); + const findMockCat = mockCat('Mufasa', 'the dead king'); + const foundCat = await service.getOneByName('Mufasa'); + expect(foundCat).toEqual(findMockCat); + }); + it('should insert a new cat', async () => { + jest.spyOn(model, 'create').mockResolvedValueOnce({ + _id: 'some id', + name: 'Oliver', + age: 1, + breed: 'Tabby', + } as any); // dreaded as any, but it can't be helped + const newCat = await service.insertOne({ + name: 'Oliver', + age: 1, + breed: 'Tabby', + }); + expect(newCat).toEqual(mockCat('Oliver', 'some id', 1, 'Tabby')); + }); + it('should update a cat successfully', async () => { + jest.spyOn(model, 'update').mockResolvedValueOnce(true); + jest.spyOn(model, 'findOne').mockReturnValueOnce( + createMock>({ + exec: jest.fn().mockResolvedValueOnce({ + _id: 'lasagna lover', + name: 'Garfield', + breed: 'Tabby', + age: 42, + }), + }), + ); + const updatedCat = await service.updateOne({ + _id: 'lasagna lover', + name: 'Garfield', + breed: 'Tabby', + age: 42, + }); + expect(updatedCat).toEqual( + mockCat('Garfield', 'lasagna lover', 42, 'Tabby'), + ); + }); + it('should delete a cat successfully', async () => { + // really just returning a truthy value here as we aren't doing any logic with the return + jest.spyOn(model, 'remove').mockResolvedValueOnce(true as any); + expect(await service.deleteOne('a bad id')).toEqual({ deleted: true }); + }); + it('should not delete a cat', async () => { + // really just returning a falsy value here as we aren't doing any logic with the return + jest.spyOn(model, 'remove').mockRejectedValueOnce(new Error('Bad delete')); + expect(await service.deleteOne('a bad id')).toEqual({ + deleted: false, + message: 'Bad delete', + }); + }); +}); diff --git a/apps/mongo-sample/src/cat/cat.service.ts b/apps/mongo-sample/src/cat/cat.service.ts new file mode 100644 index 00000000..24d0f6aa --- /dev/null +++ b/apps/mongo-sample/src/cat/cat.service.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; +import { CatDTO } from './cat.dto'; +import { Cat } from './interfaces/cat.interface'; +import { CatDoc } from './interfaces/cat-document.interface'; + +@Injectable() +export class CatService { + constructor(@InjectModel('Cat') private readonly catModel: Model) {} + + async getAll(): Promise { + const catDocs = await this.catModel.find().exec(); + return catDocs.map((doc) => ({ + id: doc._id, + age: doc.age, + name: doc.name, + breed: doc.breed, + })); + } + + async getOne(id: string): Promise { + const cat = await this.catModel.findOne({ _id: id }).exec(); + return { + id: cat._id, + age: cat.age, + breed: cat.breed, + name: cat.name, + }; + } + + async getOneByName(name: string): Promise { + const cat = await this.catModel.findOne({ name }).exec(); + return { + id: cat._id, + age: cat.age, + breed: cat.breed, + name: cat.name, + }; + } + + /** + * I would suggest against using something like `new this.catModel()` + * because it becomes really _really_ hard to mock. + * Instead, you can use the class method `create` to acheive + * the same effect. + */ + async insertOne(cat: CatDTO): Promise { + const retCat = await this.catModel.create(cat); + return { + id: retCat._id, + age: retCat.age, + name: retCat.name, + breed: retCat.breed, + }; + } + + async updateOne(cat: CatDTO): Promise { + const { _id } = cat; + await this.catModel.update({ _id }, cat); + const foundCat = await this.catModel.findOne({ _id }).exec(); + return { + id: foundCat._id, + age: foundCat.age, + breed: foundCat.breed, + name: foundCat.name, + }; + } + + async deleteOne(id: string): Promise<{ deleted: boolean; message?: string }> { + try { + await this.catModel.remove({ id }); + return { deleted: true }; + } catch (err) { + return { deleted: false, message: err.message }; + } + } +} diff --git a/apps/mongo-sample/src/cat/interfaces/cat-document.interface.ts b/apps/mongo-sample/src/cat/interfaces/cat-document.interface.ts new file mode 100644 index 00000000..51da464e --- /dev/null +++ b/apps/mongo-sample/src/cat/interfaces/cat-document.interface.ts @@ -0,0 +1,7 @@ +import { Document } from 'mongoose'; + +export interface CatDoc extends Document { + age: number; + name: string; + breed: string; +} diff --git a/apps/mongo-sample/src/cat/interfaces/cat.interface.ts b/apps/mongo-sample/src/cat/interfaces/cat.interface.ts new file mode 100644 index 00000000..0d22dc8f --- /dev/null +++ b/apps/mongo-sample/src/cat/interfaces/cat.interface.ts @@ -0,0 +1,6 @@ +export interface Cat { + age: number; + name: string; + breed: string; + id: string; +} diff --git a/apps/mongo-sample/src/cat/schemas/cat.schema.ts b/apps/mongo-sample/src/cat/schemas/cat.schema.ts new file mode 100644 index 00000000..02ecd8ec --- /dev/null +++ b/apps/mongo-sample/src/cat/schemas/cat.schema.ts @@ -0,0 +1,7 @@ +import { Schema } from 'mongoose'; + +export const CatSchema = new Schema({ + name: String, + age: Number, + breed: String, +}); diff --git a/apps/mongo-sample/testCoverage.png b/apps/mongo-sample/testCoverage.png new file mode 100644 index 0000000000000000000000000000000000000000..6100317a80ab6d954e08438975a8001bfa3cfecd GIT binary patch literal 8328 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 z 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 ZZ}4~}zph&t5J0!YP>@jtmP);N^B