@next-k8s/tickets 1.0.9
Sign up to get free protection for your applications and to get access to all the features.
- package/.dockerignore +1 -0
- package/CHANGELOG.md +72 -0
- package/Dockerfile +7 -0
- package/build/__mocks__/nats-client.js +12 -0
- package/build/app.js +31 -0
- package/build/events/publishers/tickets/created.js +12 -0
- package/build/events/publishers/tickets/updated.js +12 -0
- package/build/index.js +45 -0
- package/build/models/ticket.js +37 -0
- package/build/nats-client.js +42 -0
- package/build/routes/__test__/create.test.js +85 -0
- package/build/routes/__test__/find.test.js +30 -0
- package/build/routes/__test__/get.test.js +43 -0
- package/build/routes/__test__/update.test.js +95 -0
- package/build/routes/create.js +33 -0
- package/build/routes/find.js +22 -0
- package/build/routes/get.js +28 -0
- package/build/routes/index.js +10 -0
- package/build/routes/update.js +41 -0
- package/build/test/setup.js +34 -0
- package/build/test/utils.js +33 -0
- package/package.json +53 -0
- package/pnpm-lock.yaml +3641 -0
- package/src/__mocks__/nats-client.ts +10 -0
- package/src/app.ts +21 -0
- package/src/events/publishers/tickets/created.ts +7 -0
- package/src/events/publishers/tickets/updated.ts +7 -0
- package/src/index.ts +31 -0
- package/src/models/ticket.ts +42 -0
- package/src/nats-client.ts +30 -0
- package/src/routes/__test__/create.test.ts +83 -0
- package/src/routes/__test__/find.test.ts +18 -0
- package/src/routes/__test__/get.test.ts +34 -0
- package/src/routes/__test__/update.test.ts +101 -0
- package/src/routes/create.ts +24 -0
- package/src/routes/find.ts +11 -0
- package/src/routes/get.ts +16 -0
- package/src/routes/index.ts +8 -0
- package/src/routes/update.ts +29 -0
- package/src/test/setup.ts +24 -0
- package/src/test/utils.ts +18 -0
- package/tsconfig.json +101 -0
package/src/app.ts
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
import express from 'express'
|
2
|
+
import 'express-async-errors'
|
3
|
+
import { json } from 'body-parser'
|
4
|
+
import cookieSession from 'cookie-session'
|
5
|
+
|
6
|
+
import { currentUser, errorHandler, NotFoundError } from '@next-k8s/common'
|
7
|
+
import routes from './routes'
|
8
|
+
|
9
|
+
const app = express()
|
10
|
+
|
11
|
+
app.disable('x-powered-by')
|
12
|
+
app.set('trust proxy', true)
|
13
|
+
app.use(json())
|
14
|
+
app.use(cookieSession({ signed: false, secure: process.env.NODE_ENV !== 'test' }))
|
15
|
+
|
16
|
+
app.use(currentUser)
|
17
|
+
for (const route of routes) app.use(route)
|
18
|
+
app.use('*', async (req, res) => { throw new NotFoundError() })
|
19
|
+
app.use(errorHandler)
|
20
|
+
|
21
|
+
export default app
|
package/src/index.ts
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
import mongoose from 'mongoose'
|
2
|
+
|
3
|
+
import natsClient from './nats-client'
|
4
|
+
import app from './app'
|
5
|
+
|
6
|
+
const start = async () => {
|
7
|
+
if (!process.env.JWT_KEY) throw new Error('JWT_KEY is undefined')
|
8
|
+
if (!process.env.MONGO_URI) throw new Error('MONGO_URI is undefined')
|
9
|
+
if (!process.env.NATS_URL) throw new Error('NATS_URL is undefined')
|
10
|
+
if (!process.env.NATS_CLUSTER_ID) throw new Error('NATS_CLUSTER_ID is undefined')
|
11
|
+
if (!process.env.NATS_CLIENT_ID) throw new Error('NATS_CLIENT_ID is undefined')
|
12
|
+
|
13
|
+
await natsClient.connect(process.env.NATS_CLUSTER_ID, process.env.NATS_CLIENT_ID, process.env.NATS_URL)
|
14
|
+
|
15
|
+
natsClient.client.on('close', () => {
|
16
|
+
if (!process.env.NODE_ENV?.includes('test')) console.log('NATS connection closed')
|
17
|
+
process.exit()
|
18
|
+
})
|
19
|
+
|
20
|
+
process.on('SIGINT', () => natsClient.client.close())
|
21
|
+
process.on('SIGTERM', () => natsClient.client.close())
|
22
|
+
|
23
|
+
await mongoose.connect(process.env.MONGO_URI)
|
24
|
+
console.log('Database connected!')
|
25
|
+
const port = process.env.PORT || 3000
|
26
|
+
app.listen(port, () => {
|
27
|
+
console.log('Service running on port:', port)
|
28
|
+
})
|
29
|
+
}
|
30
|
+
|
31
|
+
start()
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import mongoose from 'mongoose'
|
2
|
+
|
3
|
+
interface TicketAttributes {
|
4
|
+
title: String;
|
5
|
+
price: Number;
|
6
|
+
owner: String;
|
7
|
+
createdAt?: Date;
|
8
|
+
updatedAt?: Date;
|
9
|
+
}
|
10
|
+
|
11
|
+
const ticketSchema = new mongoose.Schema({
|
12
|
+
title: {
|
13
|
+
type: String,
|
14
|
+
required: true
|
15
|
+
},
|
16
|
+
|
17
|
+
price: {
|
18
|
+
type: Number,
|
19
|
+
required: true
|
20
|
+
},
|
21
|
+
|
22
|
+
owner: {
|
23
|
+
type: String,
|
24
|
+
required: true
|
25
|
+
}
|
26
|
+
}, {
|
27
|
+
toJSON: {
|
28
|
+
versionKey: false,
|
29
|
+
transform (doc, ret) {
|
30
|
+
ret.id = ret._id
|
31
|
+
delete ret._id
|
32
|
+
delete ret.password
|
33
|
+
}
|
34
|
+
}
|
35
|
+
})
|
36
|
+
|
37
|
+
export const TicketModel = mongoose.model('Ticket', ticketSchema)
|
38
|
+
export default class Ticket extends TicketModel {
|
39
|
+
constructor(attributes: TicketAttributes) {
|
40
|
+
super(attributes)
|
41
|
+
}
|
42
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import type { Stan } from 'node-nats-streaming'
|
2
|
+
import nats from 'node-nats-streaming'
|
3
|
+
|
4
|
+
class NatsClient {
|
5
|
+
private _client?: Stan
|
6
|
+
|
7
|
+
get client () {
|
8
|
+
if (!this._client) throw new Error('Cannot access NATS client before connecting')
|
9
|
+
return this._client
|
10
|
+
}
|
11
|
+
|
12
|
+
async connect (clusterId: string, clientId: string, url: string) {
|
13
|
+
this._client = nats.connect(clusterId, clientId, { url })
|
14
|
+
|
15
|
+
return new Promise<void>((resolve, reject) => {
|
16
|
+
this.client.on('connect', () => {
|
17
|
+
if (!process.env.NODE_ENV?.includes('test')) console.log('Connected to NATS')
|
18
|
+
resolve()
|
19
|
+
})
|
20
|
+
|
21
|
+
this.client.on('error', (err: any) => {
|
22
|
+
console.error(err)
|
23
|
+
reject(err)
|
24
|
+
})
|
25
|
+
})
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
export const natsClient = new NatsClient()
|
30
|
+
export default natsClient
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import request from 'supertest'
|
2
|
+
import app from '../../app'
|
3
|
+
import Ticket from '../../models/ticket'
|
4
|
+
import natsClient from '../../nats-client'
|
5
|
+
import { createTicket, getTokenCookie } from '../../test/utils'
|
6
|
+
|
7
|
+
// const createTicket = (cookie?: string, title = 'Test Event', price = 20000, expectedStatusCode = 201) => {
|
8
|
+
// return request(app)
|
9
|
+
// .post('/api/tickets')
|
10
|
+
// .set('Cookie', cookie ? [cookie] : [])
|
11
|
+
// .send({ title, price })
|
12
|
+
// .expect(expectedStatusCode)
|
13
|
+
// }
|
14
|
+
|
15
|
+
describe('[Create New Ticket] Route: /api/tickets', () => {
|
16
|
+
it('should be a valid route', async () => {
|
17
|
+
const response = await request(app).post('/api/tickets').send({})
|
18
|
+
expect(response.status).not.toEqual(404)
|
19
|
+
})
|
20
|
+
|
21
|
+
it('should throw UnauthorizedError if unauthenticated', async () => {
|
22
|
+
const response = await request(app).post('/api/tickets').send({})
|
23
|
+
expect(response.status).toEqual(401)
|
24
|
+
})
|
25
|
+
|
26
|
+
it('should not get UnauthorizedError if authenticated', async () => {
|
27
|
+
const cookie = await getTokenCookie()
|
28
|
+
const response = await request(app)
|
29
|
+
.post('/api/tickets')
|
30
|
+
.set('Cookie', [cookie])
|
31
|
+
.send({})
|
32
|
+
|
33
|
+
expect(response.status).not.toEqual(401)
|
34
|
+
})
|
35
|
+
|
36
|
+
it('should throw error if new ticket has invalid title', async () => {
|
37
|
+
const cookie = await getTokenCookie()
|
38
|
+
await request(app)
|
39
|
+
.post('/api/tickets')
|
40
|
+
.set('Cookie', [cookie])
|
41
|
+
.send({ title: '', price: 10 })
|
42
|
+
.expect(400)
|
43
|
+
})
|
44
|
+
|
45
|
+
it('should throw error if new ticket has invalid price', async () => {
|
46
|
+
const cookie = await getTokenCookie()
|
47
|
+
await request(app)
|
48
|
+
.post('/api/tickets')
|
49
|
+
.set('Cookie', [cookie])
|
50
|
+
.send({ title: 'Test Event', price: -10 })
|
51
|
+
.expect(400)
|
52
|
+
|
53
|
+
await request(app)
|
54
|
+
.post('/api/tickets')
|
55
|
+
.set('Cookie', [cookie])
|
56
|
+
.send({ title: 'Test Event' })
|
57
|
+
.expect(400)
|
58
|
+
})
|
59
|
+
|
60
|
+
it('should create a new ticket', async () => {
|
61
|
+
let tickets = await Ticket.find({})
|
62
|
+
const cookie = await getTokenCookie()
|
63
|
+
|
64
|
+
await request(app)
|
65
|
+
.post('/api/tickets')
|
66
|
+
.set('Cookie', [cookie])
|
67
|
+
.send({
|
68
|
+
title: 'Test Event',
|
69
|
+
price: 20000
|
70
|
+
})
|
71
|
+
|
72
|
+
tickets = await Ticket.find({})
|
73
|
+
expect(tickets.length).toEqual(1)
|
74
|
+
expect(tickets[0].title).toEqual('Test Event')
|
75
|
+
expect(tickets[0].price).toEqual(20000)
|
76
|
+
})
|
77
|
+
|
78
|
+
it('should publish a ticket:created event', async () => {
|
79
|
+
const cookie = await getTokenCookie()
|
80
|
+
await createTicket(app, cookie)
|
81
|
+
expect(natsClient.client.publish).toHaveBeenCalled()
|
82
|
+
})
|
83
|
+
})
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import request from 'supertest'
|
2
|
+
import app from '../../app'
|
3
|
+
import { createTicket, getTokenCookie } from '../../test/utils'
|
4
|
+
|
5
|
+
describe('[List Tickets] Route: /api/tickets', () => {
|
6
|
+
it('should return a list of tickets', async () => {
|
7
|
+
const cookie = await getTokenCookie()
|
8
|
+
await createTicket(app, cookie)
|
9
|
+
await createTicket(app, cookie, 'Test Event 2', 40000)
|
10
|
+
const list = await request(app)
|
11
|
+
.get('/api/tickets')
|
12
|
+
.send()
|
13
|
+
.expect(200)
|
14
|
+
|
15
|
+
expect(list.body.tickets).toBeDefined()
|
16
|
+
expect(list.body.tickets.length).toEqual(2)
|
17
|
+
})
|
18
|
+
})
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import request from 'supertest'
|
2
|
+
import mongoose from 'mongoose'
|
3
|
+
import app from '../../app'
|
4
|
+
import { createTicket, getTokenCookie } from '../../test/utils'
|
5
|
+
|
6
|
+
describe('[Get Ticket] Route: /api/tickets/:id', () => {
|
7
|
+
it('should throw a BadRequestError if ticket ID is invalid', async () => {
|
8
|
+
await request(app)
|
9
|
+
.get('/api/tickets/notarealid')
|
10
|
+
.send()
|
11
|
+
.expect(400)
|
12
|
+
})
|
13
|
+
|
14
|
+
it('should throw a NotFoundError if ticket not found', async () => {
|
15
|
+
const id = new mongoose.Types.ObjectId().toHexString()
|
16
|
+
await request(app)
|
17
|
+
.get(`/api/tickets/${id}`)
|
18
|
+
.send()
|
19
|
+
.expect(404)
|
20
|
+
})
|
21
|
+
|
22
|
+
it('should return the fetched ticket', async () => {
|
23
|
+
const cookie = await getTokenCookie()
|
24
|
+
const response = await createTicket(app, cookie)
|
25
|
+
|
26
|
+
const ticket = await request(app)
|
27
|
+
.get(`/api/tickets/${response.body.ticket.id}`)
|
28
|
+
.send()
|
29
|
+
.expect(200)
|
30
|
+
|
31
|
+
expect(ticket.body.ticket.title).toEqual('Test Event')
|
32
|
+
expect(ticket.body.ticket.price).toEqual(20000)
|
33
|
+
})
|
34
|
+
})
|
@@ -0,0 +1,101 @@
|
|
1
|
+
import request from 'supertest'
|
2
|
+
import mongoose from 'mongoose'
|
3
|
+
|
4
|
+
import app from '../../app'
|
5
|
+
import { createTicket, getTokenCookie } from '../../test/utils'
|
6
|
+
import natsClient from '../../nats-client'
|
7
|
+
|
8
|
+
describe('[Update Ticket] Route: /api/tickets/:id', () => {
|
9
|
+
it('should throw a NotFoundError if the ticket does not exist', async () => {
|
10
|
+
const id = new mongoose.Types.ObjectId().toHexString()
|
11
|
+
const cookie = await getTokenCookie()
|
12
|
+
|
13
|
+
await request(app)
|
14
|
+
.put(`/api/tickets/${id}`)
|
15
|
+
.set('Cookie', [cookie])
|
16
|
+
.send({ title: 'Test Event', price: 23000 })
|
17
|
+
.expect(404)
|
18
|
+
})
|
19
|
+
|
20
|
+
it('should throw an UnauthorizedError if not authenticated', async () => {
|
21
|
+
const cookie = await getTokenCookie()
|
22
|
+
|
23
|
+
const response = await request(app)
|
24
|
+
.post(`/api/tickets`)
|
25
|
+
.set('Cookie', [cookie])
|
26
|
+
.send({ title: 'Test Event', price: 20000 })
|
27
|
+
.expect(201)
|
28
|
+
|
29
|
+
await request(app)
|
30
|
+
.put(`/api/tickets/${response.body.ticket.id}`)
|
31
|
+
.send({ title: 'Test Event', price: 23000 })
|
32
|
+
.expect(401)
|
33
|
+
})
|
34
|
+
|
35
|
+
it('should throw an UnauthorizedError if user does not own the ticket', async () => {
|
36
|
+
const cookie = await getTokenCookie()
|
37
|
+
|
38
|
+
const response = await request(app)
|
39
|
+
.post(`/api/tickets`)
|
40
|
+
.set('Cookie', [cookie])
|
41
|
+
.send({ title: 'Test Event', price: 20000 })
|
42
|
+
.expect(201)
|
43
|
+
|
44
|
+
const newCookie = await getTokenCookie()
|
45
|
+
|
46
|
+
await request(app)
|
47
|
+
.put(`/api/tickets/${response.body.ticket.id}`)
|
48
|
+
.set('Cookie', [newCookie])
|
49
|
+
.send({ title: 'Test Event', price: 23000 })
|
50
|
+
.expect(401)
|
51
|
+
})
|
52
|
+
|
53
|
+
it('should throw a BadRequestError if an invalid ticket is provided', async () => {
|
54
|
+
const cookie = await getTokenCookie()
|
55
|
+
|
56
|
+
await request(app)
|
57
|
+
.put(`/api/tickets/notarealid`)
|
58
|
+
.set('Cookie', [cookie])
|
59
|
+
.send({ title: 'Test Event', price: 20000 })
|
60
|
+
.expect(400)
|
61
|
+
})
|
62
|
+
|
63
|
+
it('should throw an error on invalid ticket data', async () => {
|
64
|
+
const cookie = await getTokenCookie()
|
65
|
+
const response = await createTicket(app, cookie)
|
66
|
+
|
67
|
+
await request(app)
|
68
|
+
.put(`/api/tickets/${response.body.ticket.id}`)
|
69
|
+
.set('Cookie', [cookie])
|
70
|
+
.send({ title: '', price: 23000 })
|
71
|
+
.expect(400)
|
72
|
+
|
73
|
+
await request(app)
|
74
|
+
.put(`/api/tickets/${response.body.ticket.id}`)
|
75
|
+
.set('Cookie', [cookie])
|
76
|
+
.send({ title: 'Test Event 2', price: -33 })
|
77
|
+
.expect(400)
|
78
|
+
})
|
79
|
+
|
80
|
+
it('should update a ticket', async () => {
|
81
|
+
const cookie = await getTokenCookie()
|
82
|
+
const response = await createTicket(app, cookie)
|
83
|
+
|
84
|
+
const updated = await request(app)
|
85
|
+
.put(`/api/tickets/${response.body.ticket.id}`)
|
86
|
+
.set('Cookie', [cookie])
|
87
|
+
.send({ title: 'New Event', price: 23000 })
|
88
|
+
.expect(200)
|
89
|
+
|
90
|
+
const ticket = await request(app).get(`/api/tickets/${response.body.ticket.id}`).send()
|
91
|
+
|
92
|
+
expect(ticket.body.ticket.title).toEqual('New Event')
|
93
|
+
expect(ticket.body.ticket.price).toEqual(23000)
|
94
|
+
})
|
95
|
+
|
96
|
+
it('should publish a ticket:updated event', async () => {
|
97
|
+
const cookie = await getTokenCookie()
|
98
|
+
await createTicket(app, cookie)
|
99
|
+
expect(natsClient.client.publish).toHaveBeenCalled()
|
100
|
+
})
|
101
|
+
})
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import express, { Request, Response } from 'express'
|
2
|
+
import { body } from 'express-validator'
|
3
|
+
import { requireAuth, validateRequest } from '@next-k8s/common'
|
4
|
+
|
5
|
+
import Ticket from '../models/ticket'
|
6
|
+
import { TicketCreatedPublisher } from '../events/publishers/tickets/created'
|
7
|
+
import natsClient from '../nats-client'
|
8
|
+
|
9
|
+
const router = express.Router()
|
10
|
+
|
11
|
+
const validateInput = [
|
12
|
+
body('title').not().isEmpty().withMessage('Title is required'),
|
13
|
+
body('price').isInt({ gt: -1, lt: Number.MAX_SAFE_INTEGER }).withMessage('Price must be an integer of cents or units')
|
14
|
+
]
|
15
|
+
|
16
|
+
router.post('/api/tickets', requireAuth, validateInput, validateRequest, async (req: Request, res: Response) => {
|
17
|
+
const { title, price } = req.body
|
18
|
+
const ticket = new Ticket({ title, price, owner: req.currentUser!.id })
|
19
|
+
await ticket.save()
|
20
|
+
new TicketCreatedPublisher(natsClient.client).publish({ id: ticket.id, title: ticket.title, price: ticket.price, owner: ticket.owner })
|
21
|
+
res.status(201).send({ ticket })
|
22
|
+
})
|
23
|
+
|
24
|
+
export default router
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import express, { Request, Response } from 'express'
|
2
|
+
import Ticket from '../models/ticket'
|
3
|
+
|
4
|
+
const router = express.Router()
|
5
|
+
|
6
|
+
router.get('/api/tickets', async (req: Request, res: Response) => {
|
7
|
+
const tickets = await Ticket.find({})
|
8
|
+
res.send({ tickets })
|
9
|
+
})
|
10
|
+
|
11
|
+
export default router
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { BadRequestError, NotFoundError } from '@next-k8s/common'
|
2
|
+
import express, { Request, Response } from 'express'
|
3
|
+
import { isValidObjectId } from 'mongoose'
|
4
|
+
|
5
|
+
import Ticket from '../models/ticket'
|
6
|
+
|
7
|
+
const router = express.Router()
|
8
|
+
|
9
|
+
router.get('/api/tickets/:id', async (req: Request, res: Response) => {
|
10
|
+
if (!isValidObjectId(req.params.id)) throw new BadRequestError('Invalid Ticket ID')
|
11
|
+
const ticket = await Ticket.findById(req.params.id)
|
12
|
+
if (!ticket) throw new NotFoundError()
|
13
|
+
res.json({ ticket })
|
14
|
+
})
|
15
|
+
|
16
|
+
export default router
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import express, { Request, Response } from 'express'
|
2
|
+
|
3
|
+
import getTicket from './get'
|
4
|
+
import findTickets from './find'
|
5
|
+
import createTicket from './create'
|
6
|
+
import updateTicket from './update'
|
7
|
+
|
8
|
+
export default [findTickets, getTicket, createTicket, updateTicket]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { BadRequestError, NotFoundError, UnauthorizedError, validateRequest, requireAuth } from '@next-k8s/common'
|
2
|
+
import express, { Request, Response } from 'express'
|
3
|
+
import { body } from 'express-validator'
|
4
|
+
import { isValidObjectId } from 'mongoose'
|
5
|
+
|
6
|
+
import Ticket from '../models/ticket'
|
7
|
+
import TicketUpdatedPublisher from '../events/publishers/tickets/updated'
|
8
|
+
import natsClient from '../nats-client'
|
9
|
+
|
10
|
+
const router = express.Router()
|
11
|
+
|
12
|
+
const validateInput = [
|
13
|
+
body('title').not().isEmpty().withMessage('Title is required'),
|
14
|
+
body('price').isInt({ gt: -1, lt: Number.MAX_SAFE_INTEGER }).withMessage('Price must be an integer of cents or units')
|
15
|
+
]
|
16
|
+
|
17
|
+
router.put('/api/tickets/:id', requireAuth, validateInput, validateRequest, async (req: Request, res: Response) => {
|
18
|
+
if (!isValidObjectId(req.params.id)) throw new BadRequestError('Invalid Ticket ID')
|
19
|
+
const ticket = await Ticket.findById(req.params.id)
|
20
|
+
if (!ticket) throw new NotFoundError()
|
21
|
+
if (ticket.owner !== req.currentUser!.id) throw new UnauthorizedError()
|
22
|
+
const { title, price } = req.body
|
23
|
+
ticket.set({ title, price })
|
24
|
+
await ticket.save()
|
25
|
+
await new TicketUpdatedPublisher(natsClient.client).publish({ id: ticket.id, title: ticket.title, price: ticket.price, owner: ticket.owner })
|
26
|
+
res.json(ticket)
|
27
|
+
})
|
28
|
+
|
29
|
+
export default router
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { MongoMemoryServer } from 'mongodb-memory-server'
|
2
|
+
import mongoose from 'mongoose'
|
3
|
+
|
4
|
+
jest.mock('../nats-client.ts')
|
5
|
+
|
6
|
+
let mongo: any
|
7
|
+
process.env.JWT_KEY = '!SuperSecretDevToken!'
|
8
|
+
|
9
|
+
beforeAll(async () => {
|
10
|
+
mongo = await MongoMemoryServer.create()
|
11
|
+
const uri = await mongo.getUri()
|
12
|
+
await mongoose.connect(uri)
|
13
|
+
})
|
14
|
+
|
15
|
+
beforeEach(async () => {
|
16
|
+
jest.clearAllMocks()
|
17
|
+
const collections = await mongoose.connection.db.collections()
|
18
|
+
for (const collection of collections) await collection.deleteMany({})
|
19
|
+
})
|
20
|
+
|
21
|
+
afterAll(async () => {
|
22
|
+
await mongoose.connection.close()
|
23
|
+
await mongo.stop()
|
24
|
+
})
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import jwt from 'jsonwebtoken'
|
2
|
+
import mongoose from 'mongoose'
|
3
|
+
import request from 'supertest'
|
4
|
+
|
5
|
+
export const getTokenCookie = async () => {
|
6
|
+
const payload = { id: new mongoose.Types.ObjectId().toHexString(), email: 'test@test.com' }
|
7
|
+
const token = jwt.sign(payload, process.env.JWT_KEY!)
|
8
|
+
const cookie = Buffer.from(JSON.stringify({ jwt: token })).toString('base64')
|
9
|
+
return `session=${cookie}`
|
10
|
+
}
|
11
|
+
|
12
|
+
export const createTicket = (app: Express.Application, cookie?: string, title = 'Test Event', price = 20000, expectedStatusCode = 201) => {
|
13
|
+
return request(app)
|
14
|
+
.post('/api/tickets')
|
15
|
+
.set('Cookie', cookie ? [cookie] : [])
|
16
|
+
.send({ title, price })
|
17
|
+
.expect(expectedStatusCode)
|
18
|
+
}
|
package/tsconfig.json
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
4
|
+
|
5
|
+
/* Projects */
|
6
|
+
// "incremental": true, /* Enable incremental compilation */
|
7
|
+
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
8
|
+
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
9
|
+
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
10
|
+
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
11
|
+
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
12
|
+
|
13
|
+
/* Language and Environment */
|
14
|
+
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
15
|
+
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
16
|
+
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
17
|
+
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
18
|
+
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
19
|
+
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
20
|
+
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
21
|
+
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
22
|
+
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
23
|
+
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
24
|
+
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
25
|
+
|
26
|
+
/* Modules */
|
27
|
+
"module": "commonjs", /* Specify what module code is generated. */
|
28
|
+
// "rootDir": "./", /* Specify the root folder within your source files. */
|
29
|
+
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
30
|
+
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
31
|
+
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
32
|
+
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
33
|
+
"typeRoots": ["./src/types", "./node_modules/@types"], /* Specify multiple folders that act like `./node_modules/@types`. */
|
34
|
+
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
35
|
+
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
36
|
+
// "resolveJsonModule": true, /* Enable importing .json files */
|
37
|
+
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
38
|
+
|
39
|
+
/* JavaScript Support */
|
40
|
+
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
41
|
+
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
42
|
+
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
43
|
+
|
44
|
+
/* Emit */
|
45
|
+
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
46
|
+
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
47
|
+
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
48
|
+
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
49
|
+
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
50
|
+
"outDir": "./build", /* Specify an output folder for all emitted files. */
|
51
|
+
// "removeComments": true, /* Disable emitting comments. */
|
52
|
+
// "noEmit": true, /* Disable emitting files from a compilation. */
|
53
|
+
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
54
|
+
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
55
|
+
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
56
|
+
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
57
|
+
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
58
|
+
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
59
|
+
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
60
|
+
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
61
|
+
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
62
|
+
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
63
|
+
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
64
|
+
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
65
|
+
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
66
|
+
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
67
|
+
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
68
|
+
|
69
|
+
/* Interop Constraints */
|
70
|
+
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
71
|
+
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
72
|
+
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
73
|
+
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
74
|
+
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
75
|
+
|
76
|
+
/* Type Checking */
|
77
|
+
"strict": true, /* Enable all strict type-checking options. */
|
78
|
+
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
|
79
|
+
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
|
80
|
+
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
81
|
+
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
82
|
+
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
83
|
+
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
|
84
|
+
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
|
85
|
+
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
86
|
+
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
|
87
|
+
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
|
88
|
+
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
89
|
+
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
90
|
+
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
91
|
+
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
92
|
+
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
93
|
+
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
94
|
+
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
95
|
+
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
96
|
+
|
97
|
+
/* Completeness */
|
98
|
+
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
99
|
+
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
100
|
+
}
|
101
|
+
}
|