initial: server + shared
This commit is contained in:
+133
@@ -0,0 +1,133 @@
|
||||
import Fastify from 'fastify'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { NOTIFICATION_EVENTS } from '../../../../shared/constants/notification-events.js'
|
||||
|
||||
const { mockPrisma } = vi.hoisted(() => ({
|
||||
mockPrisma: {
|
||||
payment: { findFirst: vi.fn(), update: vi.fn() },
|
||||
order: { findFirst: vi.fn(), updateMany: vi.fn() },
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('../../lib/prisma.js', () => ({
|
||||
prisma: mockPrisma,
|
||||
}))
|
||||
|
||||
vi.mock('../../lib/yookassa.js', () => ({
|
||||
validateWebhook: vi.fn(),
|
||||
}))
|
||||
|
||||
import { validateWebhook } from '../../lib/yookassa.js'
|
||||
import { registerYookassaWebhookRoute } from '../webhook-yookassa.js'
|
||||
|
||||
function buildApp(eventBusMock) {
|
||||
const app = Fastify({ logger: false })
|
||||
app.decorate('eventBus', eventBusMock || { emit: () => {} })
|
||||
return app
|
||||
}
|
||||
|
||||
describe('POST /api/webhooks/yookassa', () => {
|
||||
let app
|
||||
let eventBus
|
||||
|
||||
beforeEach(async () => {
|
||||
eventBus = { emit: vi.fn() }
|
||||
validateWebhook.mockImplementation((_ip, body) => {
|
||||
if (!body || typeof body !== 'object') throw new Error('Invalid webhook body')
|
||||
if (body.type !== 'notification') throw new Error('Expected notification type in webhook body')
|
||||
if (!body.event || !body.object) throw new Error('Missing event or object in webhook body')
|
||||
return { event: body.event, paymentObject: body.object }
|
||||
})
|
||||
app = buildApp(eventBus)
|
||||
await registerYookassaWebhookRoute(app)
|
||||
await app.ready()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('returns 400 for invalid body', async () => {
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/webhooks/yookassa',
|
||||
payload: { not: 'valid' },
|
||||
})
|
||||
expect(res.statusCode).toBe(400)
|
||||
})
|
||||
|
||||
it('returns 404 when payment not found', async () => {
|
||||
mockPrisma.payment.findFirst.mockResolvedValue(null)
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/webhooks/yookassa',
|
||||
payload: {
|
||||
type: 'notification',
|
||||
event: 'payment.succeeded',
|
||||
object: { id: 'unknown-id', status: 'succeeded', paid: true },
|
||||
},
|
||||
})
|
||||
expect(res.statusCode).toBe(404)
|
||||
})
|
||||
|
||||
it('updates payment and order on payment.succeeded', async () => {
|
||||
mockPrisma.payment.findFirst.mockResolvedValue({
|
||||
id: 'payment-1',
|
||||
yookassaPaymentId: 'yk-id',
|
||||
status: 'pending',
|
||||
orderId: 'order-1',
|
||||
})
|
||||
mockPrisma.payment.update.mockResolvedValue({})
|
||||
mockPrisma.order.findFirst.mockResolvedValue({
|
||||
id: 'order-1',
|
||||
status: 'PENDING_PAYMENT',
|
||||
userId: 'user-1',
|
||||
})
|
||||
mockPrisma.order.updateMany.mockResolvedValue({ count: 1 })
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/webhooks/yookassa',
|
||||
payload: {
|
||||
type: 'notification',
|
||||
event: 'payment.succeeded',
|
||||
object: { id: 'yk-id', status: 'succeeded', paid: true },
|
||||
},
|
||||
})
|
||||
expect(res.statusCode).toBe(200)
|
||||
|
||||
const updateData = mockPrisma.payment.update.mock.calls[0][0].data
|
||||
expect(updateData.status).toBe('succeeded')
|
||||
|
||||
const orderUpdateData = mockPrisma.order.updateMany.mock.calls[0][0].data
|
||||
expect(orderUpdateData.status).toBe('PAID')
|
||||
expect(eventBus.emit).toHaveBeenCalledWith(NOTIFICATION_EVENTS.PAYMENT_STATUS_CHANGED, {
|
||||
orderId: 'order-1',
|
||||
userId: 'user-1',
|
||||
paymentStatus: 'paid',
|
||||
})
|
||||
})
|
||||
|
||||
it('updates payment on payment.canceled without changing order', async () => {
|
||||
mockPrisma.payment.findFirst.mockResolvedValue({
|
||||
id: 'payment-1',
|
||||
yookassaPaymentId: 'yk-id',
|
||||
status: 'pending',
|
||||
orderId: 'order-1',
|
||||
})
|
||||
mockPrisma.payment.update.mockResolvedValue({})
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/webhooks/yookassa',
|
||||
payload: {
|
||||
type: 'notification',
|
||||
event: 'payment.canceled',
|
||||
object: { id: 'yk-id', status: 'canceled', paid: false },
|
||||
},
|
||||
})
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(mockPrisma.order.findFirst).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user