Skip to content
This repository was archived by the owner on Sep 24, 2022. It is now read-only.

Commit 2ca3b5f

Browse files
committed
feat: add hygen generator for module
1 parent ca7a15b commit 2ca3b5f

13 files changed

+329
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
to: src/app.module.ts
3+
inject: true
4+
after: "imports: \\["
5+
---
6+
<%= h.changeCase.pascalCase(name) %>Module,-%>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
to: src/app.module.ts
3+
inject: true
4+
prepend: true
5+
---
6+
import { <%= h.changeCase.pascalCase(name) %>Module } from './<%= h.changeCase.paramCase(name) %>/<%= h.changeCase.paramCase(name) %>.module'-%>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/<%= h.inflection.singularize(h.changeCase.paramCase(name)) %>.constraints.ts
3+
---
4+
export const <%= h.inflection.singularize(h.changeCase.constantCase(name)) %>_CONSTRAINTS = {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/<%= h.changeCase.paramCase(name) %>.controller.ts
3+
---
4+
import { Controller, Get, Post, Body, Param } from '@nestjs/common'
5+
import { <%= h.changeCase.pascalCase(name) %>Service } from './<%= h.changeCase.paramCase(name) %>.service'
6+
import { <%= h.changeCase.pascalCase(name) %>ControllerCreateRequestBodyDto, <%= h.changeCase.pascalCase(name) %>ControllerCreateResponseBodyDto } from './dto/<%= h.changeCase.paramCase(name) %>.create.dto'
7+
import { <%= h.changeCase.pascalCase(name) %>ControllerGetByIdRequestParamDto, <%= h.changeCase.pascalCase(name) %>ControllerGetByIdResponseBodyDto } from './dto/<%= h.changeCase.paramCase(name) %>.get-by-id.dto'
8+
9+
@Controller('<%= h.changeCase.paramCase(name) %>')
10+
export class <%= h.changeCase.pascalCase(name) %>Controller {
11+
constructor(private readonly <%= h.changeCase.pascalCase(name) %>Service: <%= h.changeCase.pascalCase(name) %>Service) {}
12+
13+
@Post('')
14+
create(@Body() body: <%= h.changeCase.pascalCase(name) %>ControllerCreateRequestBodyDto): Promise<<%= h.changeCase.pascalCase(name) %>ControllerCreateResponseBodyDto> {
15+
return this.<%= h.changeCase.pascalCase(name) %>Service.create({})
16+
}
17+
18+
@Get(':<%= h.inflection.singularize(h.changeCase.camelCase(name)) %>Id')
19+
getById(@Param() param: <%= h.changeCase.pascalCase(name) %>ControllerGetByIdRequestParamDto): Promise<<%= h.changeCase.pascalCase(name) %>ControllerGetByIdResponseBodyDto> {
20+
return this.<%= h.changeCase.pascalCase(name) %>Service.getById({ <%= h.inflection.singularize(h.changeCase.camelCase(name)) %>Id: param.<%= h.inflection.singularize(h.changeCase.camelCase(name)) %>Id })
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/<%= h.changeCase.paramCase(name) %>.controller.integration.spec.ts
3+
---
4+
import { Test, TestingModule } from '@nestjs/testing'
5+
import { <%= h.changeCase.pascalCase(name) %>Controller } from './<%= h.changeCase.paramCase(name) %>.controller'
6+
import { AppModule } from '../app.module'
7+
import { HttpStatus, INestApplication } from '@nestjs/common'
8+
import { Model } from 'mongoose'
9+
import supertest from 'supertest'
10+
import { <%= h.inflection.singularize(h.changeCase.pascalCase(name)) %> } from './<%= h.inflection.singularize(name) %>.schema'
11+
import { getModelToken } from '@nestjs/mongoose'
12+
import { <%= h.changeCase.pascalCase(name) %>ControllerCreateRequestBodyDto } from './dto/<%= h.changeCase.paramCase(name) %>.create.dto'
13+
import { createTest<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %> } from './<%= h.inflection.singularize(name) %>.mocks'
14+
15+
let nestApplication: INestApplication
16+
let <%= h.inflection.singularize(h.changeCase.camelCase(name)) %>Model: Model<<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>>
17+
18+
const first<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %> = createTest<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>()
19+
const second<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %> = createTest<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>()
20+
21+
beforeAll(async () => {
22+
const testingModule: TestingModule = await Test.createTestingModule({
23+
imports: [AppModule],
24+
}).compile()
25+
26+
nestApplication = testingModule.createNestApplication()
27+
await nestApplication.init()
28+
<%= h.inflection.singularize(h.changeCase.camelCase(name)) %>Model = nestApplication.get(getModelToken(<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>.name))
29+
})
30+
31+
beforeEach(async () => {
32+
await <%= h.inflection.singularize(h.changeCase.camelCase(name)) %>Model.create(first<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>)
33+
await <%= h.inflection.singularize(h.changeCase.camelCase(name)) %>Model.create(second<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>)
34+
})
35+
36+
afterEach(async () => {
37+
await <%= h.inflection.singularize(h.changeCase.camelCase(name)) %>Model.deleteMany({})
38+
})
39+
40+
afterAll(async () => {
41+
await nestApplication.close()
42+
})
43+
44+
describe('<%= h.changeCase.pascalCase(name) %>Controller', () => {
45+
describe('create <%= h.changeCase.lowerCase(h.changeCase.sentenceCase(h.inflection.singularize(name))) %>', () => {
46+
it('should create <%= h.changeCase.lowerCase(h.changeCase.sentenceCase(h.inflection.singularize(name))) %>', async () => {
47+
const body: <%= h.changeCase.pascalCase(name) %>ControllerCreateRequestBodyDto = {}
48+
49+
const response = await supertest(nestApplication.getHttpServer()).post('/<%= h.changeCase.paramCase(name) %>').send(body)
50+
51+
expect(response.body).toMatchSnapshot({
52+
_id: expect.any(String),
53+
createdAt: expect.any(String),
54+
updatedAt: expect.any(String),
55+
})
56+
expect(response.status).toBe(HttpStatus.CREATED)
57+
})
58+
})
59+
60+
describe('get <%= h.changeCase.lowerCase(h.changeCase.sentenceCase(h.inflection.singularize(name))) %> by id', () => {
61+
it('should throw error when <%= h.changeCase.lowerCase(h.changeCase.sentenceCase(h.inflection.singularize(name))) %> does not exist', async () => {
62+
const response = await supertest(nestApplication.getHttpServer())
63+
.get(`/<%= h.changeCase.paramCase(name) %>/62c3261b4bc318d862a90a71`)
64+
.send()
65+
66+
expect(response.body).toMatchSnapshot()
67+
expect(response.status).toBe(HttpStatus.NOT_FOUND)
68+
})
69+
70+
it('should get <%= h.changeCase.lowerCase(h.changeCase.sentenceCase(h.inflection.singularize(name))) %> by id', async () => {
71+
const response = await supertest(nestApplication.getHttpServer())
72+
.get(`/<%= h.changeCase.paramCase(name) %>/${first<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>._id}`)
73+
.send()
74+
75+
expect(response.body).toMatchSnapshot({
76+
_id: expect.any(String),
77+
createdAt: expect.any(String),
78+
updatedAt: expect.any(String),
79+
})
80+
expect(response.status).toBe(HttpStatus.OK)
81+
})
82+
})
83+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/dto/<%= h.changeCase.paramCase(name) %>.create.dto.ts
3+
---
4+
import {} from 'class-validator'
5+
import { <%= h.changeCase.constantCase(h.inflection.singularize(name)) %>_CONSTRAINTS } from '../<%= h.changeCase.paramCase(h.inflection.singularize(name)) %>.constraints'
6+
7+
export class <%= h.changeCase.pascalCase(name) %>ControllerCreateRequestBodyDto {}
8+
9+
export class <%= h.changeCase.pascalCase(name) %>ControllerCreateResponseBodyDto {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/dto/<%= h.changeCase.paramCase(name) %>.get-by-id.dto.ts
3+
---
4+
import { IsMongoId } from 'class-validator'
5+
6+
export class <%= h.changeCase.pascalCase(name) %>ControllerGetByIdRequestParamDto {
7+
@IsMongoId()
8+
public readonly <%= h.inflection.singularize(h.changeCase.camelCase(name)) %>Id: string
9+
}
10+
11+
export class <%= h.changeCase.pascalCase(name) %>ControllerGetByIdResponseBodyDto {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/<%= h.inflection.singularize(h.changeCase.paramCase(name)) %>.mocks.ts
3+
---
4+
import { <%= h.inflection.singularize(h.changeCase.pascalCase(name))%>DocumentFields } from './<%= h.inflection.singularize(h.changeCase.paramCase(name))%>.schema'
5+
import mongoose from 'mongoose'
6+
import { faker } from '@faker-js/faker'
7+
import { merge } from 'lodash'
8+
import { TestDocumentFactory, TestDocumentFields } from '@test-utils'
9+
10+
export const createTest<%= h.inflection.singularize(h.changeCase.pascalCase(name))%>: TestDocumentFactory<<%= h.inflection.singularize(h.changeCase.pascalCase(name))%>DocumentFields> = (fields) => {
11+
const default<%= h.inflection.singularize(h.changeCase.pascalCase(name))%>: Required<TestDocumentFields<<%= h.inflection.singularize(h.changeCase.pascalCase(name))%>DocumentFields>> = {
12+
_id: new mongoose.Types.ObjectId(faker.database.mongodbObjectId()),
13+
createdAt: new Date('2022-06-01T08:00:00.000Z'),
14+
updatedAt: new Date('2022-06-01T08:00:00.000Z'),
15+
}
16+
17+
return merge(default<%= h.inflection.singularize(h.changeCase.pascalCase(name))%>, fields)
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/<%= h.changeCase.paramCase(name) %>.module.ts
3+
---
4+
import { Module } from '@nestjs/common'
5+
import { <%= h.changeCase.pascalCase(name) %>Controller } from './<%= h.changeCase.paramCase(name) %>.controller'
6+
import { <%= h.changeCase.pascalCase(name) %>Service } from './<%= h.changeCase.paramCase(name) %>.service'
7+
import { MongooseModule } from '@nestjs/mongoose'
8+
import { <%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>, <%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>Schema } from './<%= h.inflection.singularize(h.changeCase.paramCase(name)) %>.schema'
9+
10+
@Module({
11+
imports: [MongooseModule.forFeature([{ name: <%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>.name, schema: <%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>Schema }])],
12+
controllers: [<%= h.changeCase.pascalCase(name) %>Controller],
13+
providers: [<%= h.changeCase.pascalCase(name) %>Service],
14+
})
15+
export class <%= h.changeCase.pascalCase(name) %>Module {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/<%= h.inflection.singularize(h.changeCase.paramCase(name)) %>.schema.ts
3+
---
4+
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
5+
import { Document } from 'mongoose'
6+
import { <%= h.inflection.singularize(h.changeCase.constantCase(name)) %>_CONSTRAINTS } from './<%= h.inflection.singularize(name) %>.constraints'
7+
8+
export type <%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>Document = <%= h.inflection.singularize(h.changeCase.pascalCase(name)) %> & Document
9+
10+
export type <%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>DocumentFields = {
11+
_id: string
12+
13+
createdAt: Date
14+
updatedAt: Date
15+
}
16+
17+
@Schema({ collection: '<%= h.changeCase.snakeCase(name) %>', versionKey: false })
18+
export class <%= h.inflection.singularize(h.changeCase.pascalCase(name)) %> implements Omit<<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>DocumentFields, '_id'> {
19+
@Prop({ required: true, type: Date })
20+
public createdAt: Date
21+
22+
@Prop({ required: true, type: Date })
23+
public updatedAt: Date
24+
}
25+
26+
export const <%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>Schema = SchemaFactory.createForClass(<%= h.inflection.singularize(h.changeCase.pascalCase(name)) %>)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/<%= h.changeCase.paramCase(name) %>.service.ts
3+
---
4+
import { Injectable, NotFoundException } from '@nestjs/common'
5+
import { <%= h.changeCase.pascalCase(h.inflection.singularize(name)) %> } from './<%= h.changeCase.paramCase(h.inflection.singularize(name)) %>.schema'
6+
import { InjectModel } from '@nestjs/mongoose'
7+
import { Model } from 'mongoose'
8+
9+
@Injectable()
10+
export class <%= h.changeCase.pascalCase(name) %>Service {
11+
constructor(@InjectModel(<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>.name) private readonly <%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model: Model<<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>>) {}
12+
13+
async create(args: {}) {
14+
const now = new Date().toISOString()
15+
16+
return this.<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model.create({
17+
...args,
18+
createdAt: now,
19+
updatedAt: now,
20+
})
21+
}
22+
23+
async getById(args: { <%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Id: string }) {
24+
const <%= h.changeCase.camelCase(h.inflection.singularize(name)) %> = await this.<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model.findOne({ _id: args.<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Id })
25+
26+
if (!<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>) {
27+
throw new NotFoundException(`<%= h.inflection.capitalize(h.changeCase.sentenceCase(h.inflection.singularize(name))) %> with id ${args.<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Id} not found.`)
28+
}
29+
30+
return <%= h.changeCase.camelCase(h.inflection.singularize(name)) %>
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
to: src/<%= h.changeCase.paramCase(name) %>/<%= h.changeCase.paramCase(name) %>.service.unit.spec.ts
3+
---
4+
import { Test, TestingModule } from '@nestjs/testing'
5+
import { getModelToken } from '@nestjs/mongoose'
6+
import { NotFoundException } from '@nestjs/common'
7+
import { createModelMock, execute, MockedModel } from '@test-utils'
8+
import { MethodArgs } from '@types'
9+
import { <%= h.changeCase.pascalCase(name) %>Service } from './<%= h.changeCase.paramCase(name) %>.service'
10+
import { <%= h.changeCase.pascalCase(h.inflection.singularize(name)) %> } from './<%= h.changeCase.paramCase(h.inflection.singularize(name)) %>.schema'
11+
import { createTest<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %> } from './<%= h.changeCase.paramCase(h.inflection.singularize(name)) %>.mocks'
12+
13+
let <%= h.changeCase.camelCase(name) %>Service: <%= h.changeCase.pascalCase(name) %>Service
14+
let <%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model: MockedModel
15+
16+
beforeEach(async () => {
17+
const testingModule: TestingModule = await Test.createTestingModule({
18+
providers: [
19+
<%= h.changeCase.pascalCase(name) %>Service,
20+
{
21+
provide: getModelToken(<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>.name),
22+
useValue: createModelMock(),
23+
},
24+
],
25+
}).compile()
26+
27+
<%= h.changeCase.camelCase(name) %>Service = testingModule.get<<%= h.changeCase.pascalCase(name) %>Service>(<%= h.changeCase.pascalCase(name) %>Service)
28+
<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model = testingModule.get(getModelToken(<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>.name))
29+
})
30+
31+
describe('<%= h.changeCase.pascalCase(name) %>Service', () => {
32+
describe('create', () => {
33+
const createArgs: MethodArgs<typeof <%= h.changeCase.pascalCase(name) %>Service, 'create'> = {}
34+
35+
it('should create <%= h.changeCase.lowerCase(h.changeCase.sentenceCase(h.inflection.singularize(name))) %>', async () => {
36+
const result = await execute(async () => {
37+
return <%= h.changeCase.camelCase(name) %>Service.create(createArgs)
38+
})
39+
40+
expect(result.error).toBeNull()
41+
expect(<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model.create).toHaveBeenCalledWith({
42+
createdAt: expect.any(String),
43+
updatedAt: expect.any(String),
44+
})
45+
})
46+
})
47+
48+
describe('getById', () => {
49+
it('should throw error when <%= h.changeCase.lowerCase(h.changeCase.sentenceCase(h.inflection.singularize(name))) %> is not found', async () => {
50+
<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model.findOne.mockResolvedValueOnce(null)
51+
52+
const result = await execute(async () => {
53+
return <%= h.changeCase.camelCase(name) %>Service.getById({ <%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Id: '62c0044efacef7bcb0ecea45' })
54+
})
55+
56+
expect(result.error).toBeInstanceOf(NotFoundException)
57+
expect(<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model.findOne).toHaveBeenCalledWith({ _id: '62c0044efacef7bcb0ecea45' })
58+
})
59+
60+
it('should return found <%= h.changeCase.lowerCase(h.changeCase.sentenceCase(h.inflection.singularize(name))) %>', async () => {
61+
const found<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %> = createTest<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>()
62+
<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model.findOne.mockResolvedValueOnce(found<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>)
63+
64+
const result = await execute(async () => {
65+
return <%= h.changeCase.camelCase(name) %>Service.getById({ <%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Id: found<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>._id.toHexString() })
66+
})
67+
68+
expect(result.error).toBeNull()
69+
expect(result.data).toEqual(found<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>)
70+
expect(<%= h.changeCase.camelCase(h.inflection.singularize(name)) %>Model.findOne).toHaveBeenCalledWith({
71+
_id: found<%= h.changeCase.pascalCase(h.inflection.singularize(name)) %>._id.toHexString(),
72+
})
73+
})
74+
})
75+
})

_templates/module/new/prompt.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { prompt } from 'enquirer'
2+
import { kebabCase } from 'lodash'
3+
4+
const prompts: Parameters<typeof prompt>[0][] = [
5+
{
6+
type: 'input',
7+
name: 'name',
8+
message: 'Module name (kebab-case)',
9+
validate: (value: string) => {
10+
const kebabCaseValue = kebabCase(value)
11+
12+
const isKebabCase = kebabCaseValue === value
13+
if (!isKebabCase) {
14+
return `Module name must be in kebab-case.`
15+
}
16+
17+
return true
18+
},
19+
},
20+
]
21+
22+
export default prompts

0 commit comments

Comments
 (0)