fix: email validation, conditional order update, improved tests for payment routes

This commit is contained in:
Kirill
2026-05-20 19:12:46 +05:00
parent 7d0854a294
commit 317b910710
2 changed files with 125 additions and 12 deletions
@@ -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)
})
})
+10 -3
View File
@@ -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 {