bddc25b4e0
保留环境变量管理员作为 owner,并新增可持久化管理的 member 账号 建立一级页面权限、实时会话失效和后端逐请求鉴权 统一操作记录与配置记录展示管理员显示名称,隐藏内部主体标识 补充账号管理页面、生成绑定、定向测试和技术文档
175 lines
4.9 KiB
TypeScript
175 lines
4.9 KiB
TypeScript
import { afterEach, expect, test, vi } from 'vitest';
|
|
|
|
import {
|
|
createAdminAccount,
|
|
executeAdminRechargeRefund,
|
|
getAdminUserDetail,
|
|
listAdminRechargeOrders,
|
|
resolveAdminRechargeRefundManualReview,
|
|
updateAdminAccount,
|
|
} from './adminApiClient';
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
test('后台账号创建和更新携带 owner 会话与 Tab 权限', async () => {
|
|
const fetchMock = vi.fn().mockImplementation(() =>
|
|
Promise.resolve(
|
|
new Response(JSON.stringify({account: {accountId: 'member-1'}}), {
|
|
status: 200,
|
|
}),
|
|
),
|
|
);
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
await createAdminAccount('owner-token', {
|
|
username: 'operator',
|
|
displayName: '运营',
|
|
password: 'secret123',
|
|
tabPermissions: ['dashboard', 'tracking'],
|
|
enabled: true,
|
|
});
|
|
await updateAdminAccount('owner-token', 'member/1', {
|
|
displayName: '运营二组',
|
|
tabPermissions: ['tracking'],
|
|
enabled: false,
|
|
});
|
|
|
|
expect(fetchMock.mock.calls[0]?.[0]).toBe('/admin/api/accounts');
|
|
expect(fetchMock.mock.calls[0]?.[1]).toEqual(
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
headers: expect.objectContaining({Authorization: 'Bearer owner-token'}),
|
|
}),
|
|
);
|
|
expect(fetchMock.mock.calls[1]?.[0]).toBe('/admin/api/accounts/member%2F1');
|
|
expect(fetchMock.mock.calls[1]?.[1]).toEqual(
|
|
expect.objectContaining({
|
|
method: 'PUT',
|
|
body: JSON.stringify({
|
|
displayName: '运营二组',
|
|
tabPermissions: ['tracking'],
|
|
enabled: false,
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
test('充值订单查询按后台契约序列化筛选参数', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue(
|
|
new Response(JSON.stringify({ entries: [] }), {
|
|
status: 200,
|
|
headers: { 'content-type': 'application/json' },
|
|
}),
|
|
);
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
await listAdminRechargeOrders('token-1', {
|
|
orderId: 'order 1',
|
|
userId: 'user-1',
|
|
providerTransactionId: 'wx-1',
|
|
paymentChannel: 'wechat_native',
|
|
status: 'paid',
|
|
createdAfter: '2026-07-01T00:00:00.000Z',
|
|
createdBefore: '2026-07-13T23:59:00.000Z',
|
|
limit: 50,
|
|
});
|
|
|
|
const requestUrl = String(fetchMock.mock.calls[0]?.[0]);
|
|
const parsed = new URL(requestUrl, 'http://admin.local');
|
|
expect(parsed.pathname).toBe('/admin/api/profile/recharge-orders');
|
|
expect(Object.fromEntries(parsed.searchParams)).toEqual({
|
|
orderId: 'order 1',
|
|
providerTransactionId: 'wx-1',
|
|
userId: 'user-1',
|
|
paymentChannel: 'wechat_native',
|
|
status: 'paid',
|
|
createdAfter: '2026-07-01T00:00:00.000Z',
|
|
createdBefore: '2026-07-13T23:59:00.000Z',
|
|
limit: '50',
|
|
});
|
|
});
|
|
|
|
test('用户详情只发送实际提供的用户定位字段', async () => {
|
|
const fetchMock = vi
|
|
.fn()
|
|
.mockResolvedValue(
|
|
new Response(JSON.stringify({ userId: 'user-1' }), { status: 200 }),
|
|
);
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
await getAdminUserDetail('token-1', { publicUserCode: 'TN1001' });
|
|
|
|
const requestUrl = String(fetchMock.mock.calls[0]?.[0]);
|
|
const parsed = new URL(requestUrl, 'http://admin.local');
|
|
expect(parsed.pathname).toBe('/admin/api/profile/users/detail');
|
|
expect(Object.fromEntries(parsed.searchParams)).toEqual({
|
|
publicUserCode: 'TN1001',
|
|
});
|
|
});
|
|
|
|
test('退款执行使用独立 execute 管理员路由', async () => {
|
|
const fetchMock = vi
|
|
.fn()
|
|
.mockResolvedValue(
|
|
new Response(JSON.stringify({ outRefundNo: 'refund-1' }), {
|
|
status: 200,
|
|
}),
|
|
);
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
await executeAdminRechargeRefund('token-1', {
|
|
orderId: 'order-1',
|
|
refundAmountCents: 300,
|
|
requestId: 'request-1',
|
|
reason: '用户申请',
|
|
});
|
|
|
|
expect(String(fetchMock.mock.calls[0]?.[0])).toBe(
|
|
'/admin/api/profile/recharge-refunds/execute',
|
|
);
|
|
expect(fetchMock.mock.calls[0]?.[1]).toEqual(
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
orderId: 'order-1',
|
|
refundAmountCents: 300,
|
|
requestId: 'request-1',
|
|
reason: '用户申请',
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
test('退款人工复核使用独立 resolve 管理员路由', async () => {
|
|
const fetchMock = vi
|
|
.fn()
|
|
.mockResolvedValue(
|
|
new Response(JSON.stringify({ outRefundNo: 'refund-1' }), {
|
|
status: 200,
|
|
}),
|
|
);
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
await resolveAdminRechargeRefundManualReview('token-1', {
|
|
outRefundNo: 'refund-1',
|
|
reason: '已核对微信商户平台原始账单',
|
|
expectedErrorCode: 'provider_transaction_id_mismatch',
|
|
});
|
|
|
|
expect(String(fetchMock.mock.calls[0]?.[0])).toBe(
|
|
'/admin/api/profile/recharge-refunds/manual-review/resolve',
|
|
);
|
|
expect(fetchMock.mock.calls[0]?.[1]).toEqual(
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
outRefundNo: 'refund-1',
|
|
reason: '已核对微信商户平台原始账单',
|
|
expectedErrorCode: 'provider_transaction_id_mismatch',
|
|
}),
|
|
}),
|
|
);
|
|
});
|