Files
wehub-resource-sync 3a28426bf4
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s
chore: import upstream snapshot with attribution
2026-07-13 12:23:40 +08:00

101 lines
4.1 KiB
TypeScript

import crypto from 'crypto';
import { describe, expect, it, vi } from 'vitest';
import {
RazorpayProvider,
maskRazorpayKey,
validateRazorpayKey,
} from '../../src/providers/payments/razorpay.provider';
const TEST_RAZORPAY_KEY_ID = 'rzp_test_fixture';
const TEST_RAZORPAY_KEY_SECRET = 'test_secret';
function sign(payload: string, secret: string = TEST_RAZORPAY_KEY_SECRET): string {
return crypto.createHmac('sha256', secret).update(payload).digest('hex');
}
describe('RazorpayProvider', () => {
it('rejects keys with the wrong environment prefix', () => {
expect(() => validateRazorpayKey('test', 'rzp_live_wrong')).toThrow(
/must start with "rzp_test_"/i
);
});
it('masks configured keys for logs and API responses', () => {
expect(maskRazorpayKey('rzp_test_abcdefghijklmnopqrstuvwxyz')).toBe('rzp_test_****wxyz');
});
it('strictly validates webhook signatures as 64-character hex digests', () => {
const provider = new RazorpayProvider(TEST_RAZORPAY_KEY_ID, TEST_RAZORPAY_KEY_SECRET, 'test');
const rawBody = Buffer.from('{"event":"payment.captured"}');
const signature = sign(rawBody.toString('utf8'), 'webhook_secret');
expect(provider.verifyWebhookSignature(rawBody, signature, 'webhook_secret')).toBe(true);
expect(provider.verifyWebhookSignature(rawBody, `${signature}zz`, 'webhook_secret')).toBe(
false
);
expect(provider.verifyWebhookSignature(rawBody, `${signature}g`, 'webhook_secret')).toBe(false);
});
it('strictly validates order and subscription checkout signatures as hex digests', () => {
const provider = new RazorpayProvider(TEST_RAZORPAY_KEY_ID, TEST_RAZORPAY_KEY_SECRET, 'test');
const orderSignature = sign('order_123|pay_123');
const subscriptionSignature = sign('pay_123|sub_123');
expect(provider.verifyOrderPaymentSignature('order_123', 'pay_123', orderSignature)).toBe(true);
expect(
provider.verifyOrderPaymentSignature('order_123', 'pay_123', `${orderSignature}zz`)
).toBe(false);
expect(
provider.verifySubscriptionPaymentSignature('sub_123', 'pay_123', subscriptionSignature)
).toBe(true);
expect(
provider.verifySubscriptionPaymentSignature(
'sub_123',
'pay_123',
`${subscriptionSignature}zz`
)
).toBe(false);
});
// Stub the SDK client's list endpoint so the real fetchAllPaginated loop runs.
function stubPlansAll(provider: RazorpayProvider, all: ReturnType<typeof vi.fn>) {
(provider as unknown as { client: { plans: { all: typeof all } } }).client.plans.all = all;
}
it('walks every page until a short page and concatenates the results', async () => {
const provider = new RazorpayProvider(TEST_RAZORPAY_KEY_ID, TEST_RAZORPAY_KEY_SECRET, 'test');
const firstPage = Array.from({ length: 100 }, (_, i) => ({ id: `plan_${i}` }));
const secondPage = [{ id: 'plan_100' }, { id: 'plan_101' }];
const all = vi
.fn()
.mockResolvedValueOnce({ items: firstPage })
.mockResolvedValueOnce({ items: secondPage });
stubPlansAll(provider, all);
const result = await provider.listPlans();
expect(result).toHaveLength(102);
expect(result[0]).toEqual({ id: 'plan_0' });
expect(result[101]).toEqual({ id: 'plan_101' });
// A full page triggers another request; the short page stops the loop.
expect(all).toHaveBeenCalledTimes(2);
expect(all).toHaveBeenNthCalledWith(1, { count: 100, skip: 0 });
expect(all).toHaveBeenNthCalledWith(2, { count: 100, skip: 100 });
});
it('stops after a single short page and tolerates a missing items field', async () => {
const provider = new RazorpayProvider(TEST_RAZORPAY_KEY_ID, TEST_RAZORPAY_KEY_SECRET, 'test');
const all = vi.fn().mockResolvedValueOnce({ items: [{ id: 'plan_0' }] });
stubPlansAll(provider, all);
expect(await provider.listPlans()).toEqual([{ id: 'plan_0' }]);
expect(all).toHaveBeenCalledTimes(1);
const empty = vi.fn().mockResolvedValueOnce({});
stubPlansAll(provider, empty);
expect(await provider.listPlans()).toEqual([]);
expect(empty).toHaveBeenCalledTimes(1);
});
});