Files
qipai/backend/tests/cleaning-payout-service.test.mjs
T

178 lines
5.2 KiB
JavaScript

import assert from 'node:assert/strict';
import { loadConfig } from '../dist/config.js';
import {
CleaningPayoutError,
CleaningPayoutService
} from '../dist/cleaning/cleaning-payout-service.js';
assert.equal(loadConfig({
NODE_ENV: 'production',
QIPAI_MQTT_USERNAME: 'backend-test',
QIPAI_MQTT_PASSWORD: 'not-a-real-secret',
QIPAI_JWT_SECRET: 'production-cleaning-payout-secret-long-enough',
QIPAI_CLEANING_PAYOUT_MOCK_ENABLED: 'true'
}).payment.cleaningPayoutMockEnabled, false);
assert.equal(loadConfig({
NODE_ENV: 'test',
QIPAI_CLEANING_PAYOUT_MOCK_ENABLED: 'true'
}).payment.cleaningPayoutMockEnabled, true);
const actor = {
tenantId: '7',
userId: '21',
access: { roles: ['TENANT_ADMIN'], capabilities: ['tenant.manage'], storeIds: [] },
traceId: 'cleaning-payout-test'
};
{
const harness = createHarness({ transferState: 'SUCCESS' });
const result = await harness.service.executeWechatTransfer({
...actor,
settlementId: '501',
mode: 'API'
});
assert.equal(result.transferState, 'SUCCESS');
assert.equal(harness.repository.paid[0].payoutChannel, 'WECHAT_TRANSFER');
assert.equal(harness.state.transferInput.amountCents, 1200);
assert.equal(harness.state.transferInput.openid, 'openid-cleaner');
}
{
const harness = createHarness({ transferState: 'WAIT_USER_CONFIRM', packageInfo: 'package-info' });
const result = await harness.service.executeWechatTransfer({
...actor,
settlementId: '501',
mode: 'API',
note: 'need user confirm'
});
assert.equal(result.transferState, 'WAIT_USER_CONFIRM');
assert.equal(harness.repository.pending[0].payoutState, 'WAIT_USER_CONFIRM');
assert.equal(harness.repository.pending[0].payoutPackageInfo, 'package-info');
assert.equal(harness.repository.paid.length, 0);
}
{
const harness = createHarness({ transferState: 'FAIL', failReason: 'REAL_NAME_CHECK_FAILED' });
const result = await harness.service.executeWechatTransfer({
...actor,
settlementId: '501',
mode: 'API'
});
assert.equal(result.transferState, 'FAIL');
assert.equal(harness.repository.failures[0].error, 'REAL_NAME_CHECK_FAILED');
}
{
const harness = createHarness({ mockEnabled: true });
const result = await harness.service.executeWechatTransfer({
...actor,
settlementId: '501',
mode: 'MOCK'
});
assert.equal(result.transferState, 'SUCCESS');
assert.equal(harness.repository.paid[0].payoutChannel, 'WECHAT_TRANSFER_MOCK');
}
{
const harness = createHarness({ mockEnabled: false });
await assert.rejects(
() => harness.service.executeWechatTransfer({
...actor,
settlementId: '501',
mode: 'MOCK'
}),
(error) => error instanceof CleaningPayoutError
&& error.code === 'CLEANING_PAYOUT_MOCK_DISABLED'
);
}
function createHarness(options = {}) {
const state = {
transferInput: null,
settlement: {
id: '501',
settlementNo: 'CLS-20260627-501',
cleanerUserId: '31',
storeId: '11',
status: options.status ?? 'CONFIRMED',
totalRewardCents: 1200,
payoutReference: '',
payoutState: ''
},
account: {
id: '41',
platformAppId: '9',
storeId: '11',
merchantId: '1900000109',
credentialRef: 'wechat-cleaning',
authorizationStatus: options.authorizationStatus ?? 'AUTHORIZED'
}
};
const pool = {
async execute(sql) {
if (sql.includes('FROM qipai_cleaning_settlements')) {
return [[state.settlement], []];
}
if (sql.includes('FROM qipai_collection_accounts')) {
return [[state.account], []];
}
if (sql.includes('FROM qipai_user_identities')) {
return [[{ openid: 'openid-cleaner' }], []];
}
throw new Error(`Unexpected SQL: ${sql}`);
}
};
const repository = {
paid: [],
failures: [],
pending: [],
async markSettlementPaid(input) {
this.paid.push(input);
return { ...state.settlement, status: 'PAID', payoutReference: input.payoutReference };
},
async recordSettlementPayoutFailure(input) {
this.failures.push(input);
return { ...state.settlement, payoutError: input.error };
},
async recordSettlementPayoutPending(input) {
this.pending.push(input);
return { ...state.settlement, payoutState: input.payoutState };
}
};
const client = {
async createMerchantTransfer(_credential, input) {
state.transferInput = input;
return {
outBillNo: input.outBillNo,
transferBillNo: 'wx-transfer-501',
state: options.transferState ?? 'SUCCESS',
failReason: options.failReason ?? '',
packageInfo: options.packageInfo ?? ''
};
}
};
const credential = {
appId: 'wx-test',
merchantId: '1900000109',
serialNo: 'serial',
privateKeyPem: 'private-key',
apiV3Key: '0123456789abcdef0123456789abcdef',
platformCertificates: {},
transferSceneId: '1000',
transferSceneReportInfos: [{ infoType: '岗位类型', infoContent: '保洁员' }]
};
return {
state,
repository,
service: new CleaningPayoutService(
pool,
repository,
client,
new Map([['wechat-cleaning', credential]]),
options.mockEnabled ?? false
)
};
}
console.log('PASS: M08-B cleaning payout service handles WeChat transfer states and mock gate.');