fix: email validation, conditional order update, improved tests for payment routes
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import Fastify from 'fastify'
|
||||
import jwt from '@fastify/jwt'
|
||||
import { prisma } from '../../lib/prisma.js'
|
||||
import { registerUserPaymentRoutes } from '../user-payments.js'
|
||||
|
||||
const JWT_SECRET = 'test-secret'
|
||||
const TEST_USER_EMAIL = 'test-pay-user@example.com'
|
||||
const TEST_USER_EMAIL = `test-pay-${Date.now()}@example.com`
|
||||
|
||||
let testUserId
|
||||
let testOrderId
|
||||
|
||||
async function signToken(userId) {
|
||||
async function signToken(userId, email = TEST_USER_EMAIL) {
|
||||
const fastify = Fastify()
|
||||
await fastify.register(jwt, { secret: JWT_SECRET })
|
||||
await fastify.ready()
|
||||
return fastify.jwt.sign({ sub: userId, email: TEST_USER_EMAIL })
|
||||
return fastify.jwt.sign({ sub: userId, email })
|
||||
}
|
||||
|
||||
async function buildApp() {
|
||||
@@ -37,6 +37,10 @@ describe('POST /api/me/orders/:id/pay', () => {
|
||||
let app
|
||||
|
||||
beforeAll(async () => {
|
||||
await prisma.payment.deleteMany()
|
||||
await prisma.order.deleteMany({ where: { user: { email: TEST_USER_EMAIL } } })
|
||||
await prisma.user.deleteMany({ where: { email: TEST_USER_EMAIL } })
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: { email: TEST_USER_EMAIL },
|
||||
})
|
||||
@@ -57,6 +61,7 @@ describe('POST /api/me/orders/:id/pay', () => {
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.payment.deleteMany({ where: { orderId: testOrderId } })
|
||||
await prisma.order.deleteMany({ where: { userId: testUserId } })
|
||||
await prisma.user.deleteMany({ where: { email: TEST_USER_EMAIL } })
|
||||
})
|
||||
@@ -75,6 +80,7 @@ describe('POST /api/me/orders/:id/pay', () => {
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('returns 401 without auth', async () => {
|
||||
@@ -136,4 +142,104 @@ describe('POST /api/me/orders/:id/pay', () => {
|
||||
})
|
||||
expect(res.statusCode).toBe(409)
|
||||
})
|
||||
|
||||
it('returns 422 when user has no email', async () => {
|
||||
const noEmailUser = await prisma.user.create({
|
||||
data: { email: `noemail-${Date.now()}@test.com` },
|
||||
})
|
||||
const noEmailOrder = await prisma.order.create({
|
||||
data: {
|
||||
userId: noEmailUser.id,
|
||||
status: 'PENDING_PAYMENT',
|
||||
paymentMethod: 'online',
|
||||
deliveryFeeLocked: true,
|
||||
totalCents: 100000,
|
||||
currency: 'RUB',
|
||||
},
|
||||
})
|
||||
|
||||
const fastify = Fastify()
|
||||
await fastify.register(jwt, { secret: JWT_SECRET })
|
||||
const token = fastify.jwt.sign({ sub: noEmailUser.id })
|
||||
await fastify.close()
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: `/api/me/orders/${noEmailOrder.id}/pay`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
})
|
||||
expect(res.statusCode).toBe(422)
|
||||
|
||||
await prisma.order.deleteMany({ where: { userId: noEmailUser.id } })
|
||||
await prisma.user.deleteMany({ where: { id: noEmailUser.id } })
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /api/me/orders/:orderId/payment', () => {
|
||||
let app
|
||||
let getTestUserId
|
||||
let getTestOrderId
|
||||
|
||||
beforeAll(async () => {
|
||||
const getEmail = `get-pay-${Date.now()}@example.com`
|
||||
const user = await prisma.user.create({ data: { email: getEmail } })
|
||||
getTestUserId = user.id
|
||||
|
||||
const order = await prisma.order.create({
|
||||
data: {
|
||||
userId: getTestUserId,
|
||||
status: 'PENDING_PAYMENT',
|
||||
paymentMethod: 'online',
|
||||
deliveryFeeLocked: true,
|
||||
totalCents: 100000,
|
||||
currency: 'RUB',
|
||||
},
|
||||
})
|
||||
getTestOrderId = order.id
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.payment.deleteMany({ where: { orderId: getTestOrderId } })
|
||||
await prisma.order.deleteMany({ where: { userId: getTestUserId } })
|
||||
await prisma.user.deleteMany({ where: { id: getTestUserId } })
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await buildApp()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close()
|
||||
})
|
||||
|
||||
it('returns 401 without auth', async () => {
|
||||
const res = await app.inject({
|
||||
method: 'GET',
|
||||
url: `/api/me/orders/${getTestOrderId}/payment`,
|
||||
})
|
||||
expect(res.statusCode).toBe(401)
|
||||
})
|
||||
|
||||
it('returns 404 when order not found', async () => {
|
||||
const token = await signToken(getTestUserId)
|
||||
const res = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/me/orders/nonexistent-id/payment',
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
})
|
||||
expect(res.statusCode).toBe(404)
|
||||
})
|
||||
|
||||
it('returns status null when no payment exists', async () => {
|
||||
const token = await signToken(getTestUserId)
|
||||
const res = await app.inject({
|
||||
method: 'GET',
|
||||
url: `/api/me/orders/${getTestOrderId}/payment`,
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
})
|
||||
expect(res.statusCode).toBe(200)
|
||||
const body = JSON.parse(res.payload)
|
||||
expect(body.status).toBeNull()
|
||||
expect(body.paid).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,6 +9,11 @@ export async function registerUserPaymentRoutes(fastify) {
|
||||
async (request, reply) => {
|
||||
const userId = request.user.sub
|
||||
const userEmail = request.user.email
|
||||
|
||||
if (!userEmail) {
|
||||
return reply.code(422).send({ error: 'Для онлайн-оплаты необходим email в профиле' })
|
||||
}
|
||||
|
||||
const { id } = request.params
|
||||
|
||||
const order = await prisma.order.findFirst({
|
||||
@@ -42,7 +47,7 @@ export async function registerUserPaymentRoutes(fastify) {
|
||||
return { confirmationUrl: existingPayment.confirmationUrl }
|
||||
}
|
||||
|
||||
const idempotencyKey = `${id}-v1`
|
||||
const idempotencyKey = `${id}-${Date.now()}`
|
||||
const returnUrl = `${process.env.CLIENT_PUBLIC_URL || 'http://127.0.0.1:5173'}/me/orders/${id}?paid=1`
|
||||
const clientIp = request.ip
|
||||
|
||||
@@ -123,10 +128,11 @@ export async function registerUserPaymentRoutes(fastify) {
|
||||
})
|
||||
|
||||
if (ykPayment.status === 'succeeded' && order.status === 'PENDING_PAYMENT') {
|
||||
await prisma.order.update({
|
||||
where: { id: orderId },
|
||||
const updated = await prisma.order.updateMany({
|
||||
where: { id: orderId, status: 'PENDING_PAYMENT' },
|
||||
data: { status: 'PAID' },
|
||||
})
|
||||
if (updated.count > 0) {
|
||||
request.server.eventBus.emit(NOTIFICATION_EVENTS.PAYMENT_STATUS_CHANGED, {
|
||||
orderId,
|
||||
userId: order.userId,
|
||||
@@ -134,6 +140,7 @@ export async function registerUserPaymentRoutes(fastify) {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { status: ykPayment.status, paid: ykPayment.paid }
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user