113 lines
3.9 KiB
TypeScript
113 lines
3.9 KiB
TypeScript
|
|
import { describe, it, expect } from 'vitest';
|
|||
|
|
import crypto from 'crypto';
|
|||
|
|
import { generateSign, verifySign } from '@/lib/alipay/sign';
|
|||
|
|
|
|||
|
|
// 生成测试用 RSA 密钥对
|
|||
|
|
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
|
|||
|
|
modulusLength: 2048,
|
|||
|
|
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|||
|
|
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 提取裸 base64(去掉 PEM 头尾)
|
|||
|
|
const barePrivateKey = privateKey
|
|||
|
|
.replace(/-----BEGIN PRIVATE KEY-----/, '')
|
|||
|
|
.replace(/-----END PRIVATE KEY-----/, '')
|
|||
|
|
.replace(/\n/g, '');
|
|||
|
|
const barePublicKey = publicKey
|
|||
|
|
.replace(/-----BEGIN PUBLIC KEY-----/, '')
|
|||
|
|
.replace(/-----END PUBLIC KEY-----/, '')
|
|||
|
|
.replace(/\n/g, '');
|
|||
|
|
|
|||
|
|
describe('Alipay RSA2 Sign', () => {
|
|||
|
|
const testParams: Record<string, string> = {
|
|||
|
|
app_id: '2021000000000000',
|
|||
|
|
method: 'alipay.trade.page.pay',
|
|||
|
|
charset: 'utf-8',
|
|||
|
|
timestamp: '2026-03-05 12:00:00',
|
|||
|
|
version: '1.0',
|
|||
|
|
biz_content: '{"out_trade_no":"order-001","total_amount":"100.00"}',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
describe('generateSign', () => {
|
|||
|
|
it('should generate a valid RSA2 signature', () => {
|
|||
|
|
const sign = generateSign(testParams, privateKey);
|
|||
|
|
expect(sign).toBeTruthy();
|
|||
|
|
expect(typeof sign).toBe('string');
|
|||
|
|
// base64 格式
|
|||
|
|
expect(() => Buffer.from(sign, 'base64')).not.toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should produce consistent signatures for same input', () => {
|
|||
|
|
const sign1 = generateSign(testParams, privateKey);
|
|||
|
|
const sign2 = generateSign(testParams, privateKey);
|
|||
|
|
expect(sign1).toBe(sign2);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should filter out sign and sign_type fields', () => {
|
|||
|
|
const paramsWithSign = { ...testParams, sign: 'old_sign', sign_type: 'RSA2' };
|
|||
|
|
const sign1 = generateSign(testParams, privateKey);
|
|||
|
|
const sign2 = generateSign(paramsWithSign, privateKey);
|
|||
|
|
expect(sign1).toBe(sign2);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should filter out empty values', () => {
|
|||
|
|
const paramsWithEmpty = { ...testParams, empty_field: '' };
|
|||
|
|
const sign1 = generateSign(testParams, privateKey);
|
|||
|
|
const sign2 = generateSign(paramsWithEmpty, privateKey);
|
|||
|
|
expect(sign1).toBe(sign2);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should sort parameters alphabetically', () => {
|
|||
|
|
const reversed: Record<string, string> = {};
|
|||
|
|
const keys = Object.keys(testParams).reverse();
|
|||
|
|
for (const key of keys) {
|
|||
|
|
reversed[key] = testParams[key];
|
|||
|
|
}
|
|||
|
|
const sign1 = generateSign(testParams, privateKey);
|
|||
|
|
const sign2 = generateSign(reversed, privateKey);
|
|||
|
|
expect(sign1).toBe(sign2);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
describe('verifySign', () => {
|
|||
|
|
it('should verify a valid signature', () => {
|
|||
|
|
const sign = generateSign(testParams, privateKey);
|
|||
|
|
const valid = verifySign(testParams, publicKey, sign);
|
|||
|
|
expect(valid).toBe(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should reject an invalid signature', () => {
|
|||
|
|
const valid = verifySign(testParams, publicKey, 'invalid_base64_signature');
|
|||
|
|
expect(valid).toBe(false);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should reject tampered params', () => {
|
|||
|
|
const sign = generateSign(testParams, privateKey);
|
|||
|
|
const tampered = { ...testParams, total_amount: '999.99' };
|
|||
|
|
const valid = verifySign(tampered, publicKey, sign);
|
|||
|
|
expect(valid).toBe(false);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
describe('PEM auto-formatting', () => {
|
|||
|
|
it('should work with bare base64 private key (no PEM headers)', () => {
|
|||
|
|
const sign = generateSign(testParams, barePrivateKey);
|
|||
|
|
const valid = verifySign(testParams, publicKey, sign);
|
|||
|
|
expect(valid).toBe(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should work with bare base64 public key (no PEM headers)', () => {
|
|||
|
|
const sign = generateSign(testParams, privateKey);
|
|||
|
|
const valid = verifySign(testParams, barePublicKey, sign);
|
|||
|
|
expect(valid).toBe(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('should work with both bare keys', () => {
|
|||
|
|
const sign = generateSign(testParams, barePrivateKey);
|
|||
|
|
const valid = verifySign(testParams, barePublicKey, sign);
|
|||
|
|
expect(valid).toBe(true);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
});
|