archetecture security fix

This commit is contained in:
root
2026-06-11 03:22:12 -04:00
parent 6def9993da
commit 9483750161
3126 changed files with 177194 additions and 37211 deletions
+112
View File
@@ -0,0 +1,112 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
const originalEnv = { ...process.env }
async function importService(env: Record<string, string | undefined> = {}) {
vi.resetModules()
process.env = { ...originalEnv, ...env }
return import('./paypalService')
}
describe('paypalService', () => {
afterEach(() => {
process.env = { ...originalEnv }
vi.unstubAllGlobals()
vi.resetModules()
})
it('reports unconfigured when client credentials are placeholders or missing', async () => {
const service = await importService({ PAYPAL_CLIENT_ID: 'your-paypal-client-id', PAYPAL_CLIENT_SECRET: 'secret' })
expect(service.isConfigured()).toBe(false)
})
it('creates an order after obtaining and caching an access token', async () => {
const fetchMock = vi.fn()
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({ access_token: 'access_1', expires_in: 3600 }),
})
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({
id: 'paypal_order_1',
status: 'CREATED',
links: [{ rel: 'approve', href: 'https://paypal.test/approve' }],
}),
})
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({ id: 'paypal_order_2', status: 'CREATED', links: [] }),
})
vi.stubGlobal('fetch', fetchMock)
const service = await importService({
PAYPAL_BASE_URL: 'https://paypal.test',
PAYPAL_CLIENT_ID: 'client_1',
PAYPAL_CLIENT_SECRET: 'secret_1',
})
await expect(service.createOrder({
amount: 12345,
currency: 'MAD',
orderId: 'reservation_1',
description: 'Reservation deposit',
returnUrl: 'https://app.test/return',
cancelUrl: 'https://app.test/cancel',
})).resolves.toEqual({
orderId: 'paypal_order_1',
approveUrl: 'https://paypal.test/approve',
status: 'CREATED',
})
await service.createOrder({
amount: 500,
currency: 'MAD',
orderId: 'reservation_2',
description: 'Second payment',
returnUrl: 'https://app.test/return',
cancelUrl: 'https://app.test/cancel',
})
expect(fetchMock).toHaveBeenCalledTimes(3)
expect(fetchMock).toHaveBeenNthCalledWith(1, 'https://paypal.test/v1/oauth2/token', expect.objectContaining({
method: 'POST',
body: 'grant_type=client_credentials',
headers: expect.objectContaining({ Authorization: `Basic ${Buffer.from('client_1:secret_1').toString('base64')}` }),
}))
expect(fetchMock).toHaveBeenNthCalledWith(2, 'https://paypal.test/v2/checkout/orders', expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({ Authorization: 'Bearer access_1' }),
body: expect.stringContaining('"value":"123.45"'),
}))
})
it('captures and refunds through authenticated PayPal endpoints with cent-to-decimal formatting', async () => {
const fetchMock = vi.fn()
.mockResolvedValueOnce({ ok: true, json: vi.fn().mockResolvedValue({ access_token: 'access_1', expires_in: 3600 }) })
.mockResolvedValueOnce({ ok: true, json: vi.fn().mockResolvedValue({ id: 'capture_result' }) })
.mockResolvedValueOnce({ ok: true, json: vi.fn().mockResolvedValue({ id: 'refund_result' }) })
vi.stubGlobal('fetch', fetchMock)
const service = await importService({ PAYPAL_BASE_URL: 'https://paypal.test', PAYPAL_CLIENT_ID: 'client_1', PAYPAL_CLIENT_SECRET: 'secret_1' })
await expect(service.captureOrder('order_1')).resolves.toEqual({ id: 'capture_result' })
await expect(service.refundCapture('capture_1', 2500, 'MAD', 'Customer cancellation')).resolves.toEqual({ id: 'refund_result' })
expect(fetchMock).toHaveBeenNthCalledWith(2, 'https://paypal.test/v2/checkout/orders/order_1/capture', expect.objectContaining({
method: 'POST',
body: '{}',
}))
expect(fetchMock).toHaveBeenNthCalledWith(3, 'https://paypal.test/v2/payments/captures/capture_1/refund', expect.objectContaining({
method: 'POST',
body: JSON.stringify({ amount: { currency_code: 'MAD', value: '25.00' }, note_to_payer: 'Customer cancellation' }),
}))
})
it('returns false instead of throwing when webhook verification cannot be completed', async () => {
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network down')))
const service = await importService({ PAYPAL_CLIENT_ID: 'client_1', PAYPAL_CLIENT_SECRET: 'secret_1' })
await expect(service.verifyWebhookEvent({}, '{"id":"event_1"}')).resolves.toBe(false)
})
})