@next-k8s/tickets 1.0.24 → 1.0.27

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/coverage/clover.xml +158 -3
  3. package/coverage/coverage-final.json +12 -1
  4. package/coverage/lcov-report/index.html +101 -11
  5. package/coverage/lcov-report/src/app.ts.html +17 -17
  6. package/coverage/lcov-report/src/events/listeners/_queue-group-name.ts.html +88 -0
  7. package/coverage/lcov-report/src/events/listeners/index.html +131 -0
  8. package/coverage/lcov-report/src/events/listeners/order-created.ts.html +151 -0
  9. package/coverage/lcov-report/src/events/publishers/created.ts.html +106 -0
  10. package/coverage/lcov-report/src/events/publishers/index.html +131 -0
  11. package/coverage/lcov-report/src/events/publishers/updated.ts.html +106 -0
  12. package/coverage/lcov-report/src/index.html +1 -1
  13. package/coverage/lcov-report/src/models/index.html +5 -5
  14. package/coverage/lcov-report/src/models/ticket.ts.html +35 -11
  15. package/coverage/lcov-report/src/routes/create.ts.html +12 -12
  16. package/coverage/lcov-report/src/routes/find.ts.html +1 -1
  17. package/coverage/lcov-report/src/routes/get.ts.html +25 -10
  18. package/coverage/lcov-report/src/routes/index.html +8 -23
  19. package/coverage/lcov-report/src/routes/index.ts.html +8 -11
  20. package/coverage/lcov-report/src/routes/update.ts.html +13 -16
  21. package/coverage/lcov-report/src/test/index.html +1 -1
  22. package/coverage/lcov-report/src/test/utils.ts.html +3 -3
  23. package/coverage/lcov-report/ticket.ts.html +220 -0
  24. package/coverage/lcov.info +248 -0
  25. package/package.json +4 -3
  26. package/pnpm-lock.yaml +334 -18
  27. package/src/events/listeners/__test__/order-created.test.ts +44 -0
  28. package/src/events/listeners/_queue-group-name.ts +1 -0
  29. package/src/events/listeners/order-created.ts +22 -0
  30. package/src/models/__test__/ticket.test.ts +37 -0
  31. package/src/models/ticket.ts +11 -3
  32. package/src/routes/__test__/get.test.ts +15 -0
  33. package/src/routes/create.ts +2 -2
  34. package/src/routes/get.ts +5 -0
  35. package/src/routes/index.ts +1 -2
  36. package/src/routes/update.ts +2 -3
  37. package/src/routes/__test__/find.test.ts +0 -22
  38. package/src/routes/find.ts +0 -11
@@ -0,0 +1,44 @@
1
+ import mongoose from 'mongoose'
2
+ import { Message } from 'node-nats-streaming'
3
+ import { OrderCreatedEvent, OrderStatus } from '@next-k8s/common'
4
+
5
+ import Ticket from '../../../models/ticket'
6
+ import natsClient from '../../../nats-client'
7
+ import OrderCreatedListener from '../order-created'
8
+
9
+ const setup = async () => {
10
+ const listener = new OrderCreatedListener(natsClient.client)
11
+ const ticket = new Ticket({ title: 'Test Ticket', price: 21000, owner: new mongoose.Types.ObjectId().toHexString() })
12
+ await ticket.save()
13
+
14
+ const data: OrderCreatedEvent['data'] = {
15
+ id: new mongoose.Types.ObjectId().toHexString(),
16
+ version: 0,
17
+ status: OrderStatus.Created,
18
+ owner: new mongoose.Types.ObjectId().toHexString(),
19
+ expiresAt: new Date().toUTCString(),
20
+ ticket: {
21
+ id: ticket.id,
22
+ price: ticket.price
23
+ }
24
+ }
25
+
26
+ // @ts-ignore
27
+ const message: Message = { ack: jest.fn() }
28
+ return { data, listener, message, ticket }
29
+ }
30
+
31
+ describe('[Order Created] Listener', () => {
32
+ it('should set the ticket owner', async () => {
33
+ const { data, listener, message, ticket } = await setup()
34
+ await listener.onMessage(data, message)
35
+ const updatedTicket = await Ticket.findById(ticket.id)
36
+ expect(updatedTicket.orderId).toBe(data.id)
37
+ })
38
+
39
+ it('should ack the message', async () =>{
40
+ const { data, listener, message } = await setup()
41
+ await listener.onMessage(data, message)
42
+ expect(message.ack).toHaveBeenCalled()
43
+ })
44
+ })
@@ -0,0 +1 @@
1
+ export default 'tickets-service'
@@ -0,0 +1,22 @@
1
+ import { Listener, NotFoundError, OrderCreatedEvent, Subjects } from '@next-k8s/common'
2
+ import { Message } from 'node-nats-streaming'
3
+ import Ticket from '../../models/ticket'
4
+ import TicketUpdatedPublisher from '../publishers/updated'
5
+
6
+ import queueGroupName from './_queue-group-name'
7
+
8
+ export class OrderCreatedListener extends Listener<OrderCreatedEvent> {
9
+ readonly subject = Subjects.OrderCreated
10
+ queueGroupName = queueGroupName
11
+
12
+ async onMessage (data: OrderCreatedEvent['data'], msg: Message) {
13
+ const ticket = await Ticket.findById(data.ticket.id)
14
+ if (!ticket) throw new NotFoundError('Ticket not found')
15
+ ticket.set({ orderId: data.id })
16
+ await ticket.save()
17
+
18
+ msg.ack()
19
+ }
20
+ }
21
+
22
+ export default OrderCreatedListener
@@ -0,0 +1,37 @@
1
+ import mongoose from 'mongoose'
2
+ import Ticket from '../ticket'
3
+
4
+ describe('[Models] Ticket Model', () => {
5
+ it('implements optimistic concurrency control', async () => {
6
+ const ticket = new Ticket({ title: 'Test Ticket', price: 20000, owner: new mongoose.Types.ObjectId().toHexString() })
7
+ await ticket.save()
8
+
9
+ const copies = [
10
+ await Ticket.findById(ticket.id),
11
+ await Ticket.findById(ticket.id)
12
+ ]
13
+
14
+ copies[0].set({ price: 15000 })
15
+ copies[1].set({ price: 23000 })
16
+
17
+ await copies[0].save()
18
+
19
+ try {
20
+ await copies[1].save()
21
+ } catch {
22
+ return
23
+ }
24
+
25
+ throw new Error('Ticket was saved when it should not have been')
26
+ })
27
+
28
+ it('increments the version number on update', async () => {
29
+ const ticket = new Ticket({ title: 'Test Ticket', price: 20000, owner: new mongoose.Types.ObjectId().toHexString() })
30
+ await ticket.save()
31
+ expect(ticket.version).toEqual(0)
32
+ await ticket.save()
33
+ expect(ticket.version).toEqual(1)
34
+ await ticket.save()
35
+ expect(ticket.version).toEqual(2)
36
+ })
37
+ })
@@ -1,9 +1,11 @@
1
1
  import mongoose, { ObjectId } from 'mongoose'
2
+ import { updateIfCurrentPlugin } from 'mongoose-update-if-current'
2
3
 
3
4
  interface TicketAttributes {
4
- title: String;
5
- price: Number;
5
+ title: string;
6
+ price: number;
6
7
  owner: string;
8
+ orderId?: string;
7
9
  createdAt?: Date;
8
10
  updatedAt?: Date;
9
11
  }
@@ -22,10 +24,13 @@ const ticketSchema = new mongoose.Schema({
22
24
  owner: {
23
25
  type: mongoose.Schema.Types.ObjectId,
24
26
  required: true
27
+ },
28
+
29
+ orderId: {
30
+ type: String
25
31
  }
26
32
  }, {
27
33
  toJSON: {
28
- versionKey: false,
29
34
  transform (doc, ret) {
30
35
  ret.id = ret._id
31
36
  delete ret._id
@@ -34,6 +39,9 @@ const ticketSchema = new mongoose.Schema({
34
39
  }
35
40
  })
36
41
 
42
+ ticketSchema.set('versionKey', 'version')
43
+ ticketSchema.plugin(updateIfCurrentPlugin)
44
+
37
45
  export const TicketModel = mongoose.model('Ticket', ticketSchema)
38
46
  export default class Ticket extends TicketModel {
39
47
  constructor(attributes: TicketAttributes) {
@@ -5,6 +5,21 @@ import { getTokenCookie } from '@next-k8s/common'
5
5
  import app from '../../app'
6
6
  import { createTicket } from '../../test/utils'
7
7
 
8
+ describe('[List Tickets] Route: GET /api/tickets', () => {
9
+ it('should return a list of tickets', async () => {
10
+ const cookie = await getTokenCookie({ id: new mongoose.Types.ObjectId().toHexString() })
11
+ await createTicket(app, cookie)
12
+ await createTicket(app, cookie, 'Test Event 2', 40000)
13
+ const list = await request(app)
14
+ .get('/api/tickets')
15
+ .send()
16
+ .expect(200)
17
+
18
+ expect(list.body.tickets).toBeDefined()
19
+ expect(list.body.tickets.length).toEqual(2)
20
+ })
21
+ })
22
+
8
23
  describe('[Get Ticket] Route: GET /api/tickets/:id', () => {
9
24
  it('should throw a BadRequestError if ticket ID is invalid', async () => {
10
25
  await request(app)
@@ -3,7 +3,7 @@ import { body } from 'express-validator'
3
3
  import { requireAuth, validateRequest } from '@next-k8s/common'
4
4
 
5
5
  import Ticket from '../models/ticket'
6
- import { TicketCreatedPublisher } from '../events/publishers/tickets/created'
6
+ import TicketCreatedPublisher from '../events/publishers/created'
7
7
  import natsClient from '../nats-client'
8
8
 
9
9
  const router = express.Router()
@@ -17,7 +17,7 @@ router.post('/api/tickets', requireAuth, validateInput, validateRequest, async (
17
17
  const { title, price } = req.body
18
18
  const ticket = new Ticket({ title, price, owner: req.currentUser!.id })
19
19
  await ticket.save()
20
- new TicketCreatedPublisher(natsClient.client).publish({ id: ticket.id, title: ticket.title, price: ticket.price, owner: ticket.owner })
20
+ new TicketCreatedPublisher(natsClient.client).publish({ id: ticket.id, version: ticket.version, title: ticket.title, price: ticket.price, owner: ticket.owner })
21
21
  res.status(201).send({ ticket })
22
22
  })
23
23
 
package/src/routes/get.ts CHANGED
@@ -6,6 +6,11 @@ import Ticket from '../models/ticket'
6
6
 
7
7
  const router = express.Router()
8
8
 
9
+ router.get('/api/tickets', async (req: Request, res: Response) => {
10
+ const tickets = await Ticket.find({})
11
+ res.send({ tickets })
12
+ })
13
+
9
14
  router.get('/api/tickets/:id', async (req: Request, res: Response) => {
10
15
  if (!isValidObjectId(req.params.id)) throw new BadRequestError('Invalid Ticket ID')
11
16
  const ticket = await Ticket.findById(req.params.id)
@@ -1,6 +1,5 @@
1
1
  import getTicket from './get'
2
- import findTickets from './find'
3
2
  import createTicket from './create'
4
3
  import updateTicket from './update'
5
4
 
6
- export default [findTickets, getTicket, createTicket, updateTicket]
5
+ export default [getTicket, createTicket, updateTicket]
@@ -2,9 +2,8 @@ import { BadRequestError, NotFoundError, UnauthorizedError, validateRequest, req
2
2
  import express, { Request, Response } from 'express'
3
3
  import { body } from 'express-validator'
4
4
  import { isValidObjectId } from 'mongoose'
5
-
6
5
  import Ticket from '../models/ticket'
7
- import TicketUpdatedPublisher from '../events/publishers/tickets/updated'
6
+ import TicketUpdatedPublisher from '../events/publishers/updated'
8
7
  import natsClient from '../nats-client'
9
8
 
10
9
  const router = express.Router()
@@ -22,7 +21,7 @@ router.put('/api/tickets/:id', requireAuth, validateInput, validateRequest, asyn
22
21
  const { title, price } = req.body
23
22
  ticket.set({ title, price })
24
23
  await ticket.save()
25
- await new TicketUpdatedPublisher(natsClient.client).publish({ id: ticket.id, title: ticket.title, price: ticket.price, owner: ticket.owner })
24
+ await new TicketUpdatedPublisher(natsClient.client).publish({ id: ticket.id, version: ticket.version, title: ticket.title, price: ticket.price, owner: ticket.owner })
26
25
  res.json(ticket)
27
26
  })
28
27
 
@@ -1,22 +0,0 @@
1
-
2
- import mongoose from 'mongoose'
3
- import request from 'supertest'
4
- import { getTokenCookie } from '@next-k8s/common'
5
-
6
- import app from '../../app'
7
- import { createTicket } from '../../test/utils'
8
-
9
- describe('[List Tickets] Route: GET /api/tickets', () => {
10
- it('should return a list of tickets', async () => {
11
- const cookie = await getTokenCookie({ id: new mongoose.Types.ObjectId().toHexString() })
12
- await createTicket(app, cookie)
13
- await createTicket(app, cookie, 'Test Event 2', 40000)
14
- const list = await request(app)
15
- .get('/api/tickets')
16
- .send()
17
- .expect(200)
18
-
19
- expect(list.body.tickets).toBeDefined()
20
- expect(list.body.tickets.length).toEqual(2)
21
- })
22
- })
@@ -1,11 +0,0 @@
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