41b882e1fe
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m50s
Project CI / Backend tests (pull_request) Failing after 15s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 2m26s
Project CI / Native shell tests (pull_request) Failing after 36s
Project CI / Repository checks (pull_request) Failing after 26s
Project CI / Frontend tests (pull_request) Successful in 2m35s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m25s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 11m17s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 12m41s
- 主站「我的」页签常用功能宫格补回「邀请好友」,邀请码与邀请链接拆成两个复制动作 - 邀请弹层下沉到 packages/shared/src/components/PlatformProfileReferralModal,主站与客户端共用一份实现 - 邀请奖励说明不再写死次数与金额,每日上限由 todayInviterRewardCount 与 todayInviterRewardRemaining 推导 - 奖励说明从面板正文移入右上角帮助按钮,鼠标悬浮展开、触屏与键盘点击开合 - 帮助按钮复用平台 .platform-icon-button 配色,说明气泡复用 PlatformStatusMessage 的 warning token - AGC 客户端账号菜单新增「邀请码」与「玩家社区」两项,弹层标题与菜单入口文案保持一致 - 新增 Tauri 命令 read_profile_referral_invite_center,用登录 origin 补全 inviteLinkPath 后交给渲染层 - 客户端邀请中心按登录账号隔离,切号重置并作废在飞响应,不本地另存邀请码 - 新命令登记进 apps/ai-game-creator-shell/scripts/check-config.mjs 的 native-only 白名单门禁 - 微信群与 QQ 群二维码从 media/social-media-group 迁入共享组件 assets - 移除已下线文件的 Tailwind @source 与客户端专用弹层样式,原地删除旧邀请弹层实现 - 同步决策记录与产品基线文档中邀请奖励、跨端复用与文案口径
107 lines
3.8 KiB
TypeScript
107 lines
3.8 KiB
TypeScript
// @vitest-environment jsdom
|
|
/**
|
|
* 账户与钱包 typed façade 的边界。
|
|
*
|
|
* 渲染层只提交结构化业务参数;平台 origin、Bearer、envelope 与错误分类由 Rust 持有。
|
|
* 这里验证 IPC 载荷、登录失效时「续期后重试一次」,以及业务失败不重放。
|
|
*/
|
|
import { beforeEach, expect, test, vi } from 'vitest';
|
|
|
|
const invoke = vi.fn();
|
|
const platformSession = vi.hoisted(() => ({
|
|
generation: 1,
|
|
refresh: vi.fn(),
|
|
}));
|
|
|
|
// 与仓库其它 App 内调用方同口径:通过 `window.__TAURI__.core.invoke` 解析原生桥。
|
|
window.__TAURI__ = {
|
|
core: { invoke: (...args: unknown[]) => invoke(...args) },
|
|
} as never;
|
|
vi.mock('../src/services/platformSession', () => ({
|
|
currentPlatformSessionGeneration: () => platformSession.generation,
|
|
requestPlatformSessionRefresh: (...args: unknown[]) =>
|
|
platformSession.refresh(...args),
|
|
}));
|
|
|
|
import {
|
|
confirmClientWechatProfileRechargeOrder,
|
|
createClientProfileRechargeOrder,
|
|
getClientProfileRechargeCenter,
|
|
getClientProfileReferralInviteCenter,
|
|
getClientProfileWalletLedger,
|
|
redeemClientProfileRewardCode,
|
|
} from '../src/services/accountHost';
|
|
|
|
beforeEach(() => {
|
|
invoke.mockReset();
|
|
platformSession.generation = 1;
|
|
platformSession.refresh.mockReset();
|
|
platformSession.refresh.mockResolvedValue({
|
|
status: 'refreshed',
|
|
generation: 1,
|
|
user: { id: 'user-1' },
|
|
});
|
|
});
|
|
|
|
test('账户读取只提交结构化参数,不携带 token 或远端地址', async () => {
|
|
invoke.mockResolvedValue({ walletBalance: 1 });
|
|
|
|
await getClientProfileRechargeCenter();
|
|
await getClientProfileWalletLedger();
|
|
await getClientProfileReferralInviteCenter();
|
|
await createClientProfileRechargeOrder('product-1');
|
|
await confirmClientWechatProfileRechargeOrder('order-1');
|
|
await redeemClientProfileRewardCode('CODE-1');
|
|
|
|
expect(invoke.mock.calls).toEqual([
|
|
['read_profile_recharge_center', undefined],
|
|
['read_profile_wallet_ledger', undefined],
|
|
['read_profile_referral_invite_center', undefined],
|
|
['create_profile_recharge_order', { productId: 'product-1' }],
|
|
['confirm_wechat_profile_recharge_order', { orderId: 'order-1' }],
|
|
['redeem_profile_reward_code', { code: 'CODE-1' }],
|
|
]);
|
|
const serialized = JSON.stringify(invoke.mock.calls);
|
|
expect(serialized).not.toContain('token');
|
|
expect(serialized).not.toContain('Authorization');
|
|
expect(serialized).not.toContain('http');
|
|
});
|
|
|
|
test('登录失效时续期一次并重放,其它业务失败原样抛出', async () => {
|
|
invoke
|
|
.mockRejectedValueOnce(new Error('authentication-required: 请先登录'))
|
|
.mockResolvedValueOnce({ entries: [] });
|
|
|
|
await expect(getClientProfileWalletLedger()).resolves.toEqual({
|
|
entries: [],
|
|
});
|
|
expect(platformSession.refresh).toHaveBeenCalledTimes(1);
|
|
expect(invoke).toHaveBeenCalledTimes(2);
|
|
|
|
invoke.mockReset();
|
|
platformSession.refresh.mockClear();
|
|
invoke.mockRejectedValueOnce(new Error('读取泥点账单失败:余额服务不可用'));
|
|
|
|
await expect(getClientProfileWalletLedger()).rejects.toThrow(
|
|
'读取泥点账单失败:余额服务不可用',
|
|
);
|
|
expect(platformSession.refresh).not.toHaveBeenCalled();
|
|
expect(invoke).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('续期期间换号时不重放旧身份的付费请求', async () => {
|
|
invoke.mockRejectedValueOnce(
|
|
new Error('authentication-required: 登录已过期'),
|
|
);
|
|
// 续期在飞期间另一个账号完成登录:代际已经推进,旧身份的续期结果不算数。
|
|
platformSession.refresh.mockImplementationOnce(async () => {
|
|
platformSession.generation = 2;
|
|
return { status: 'refreshed', generation: 1, user: { id: 'user-1' } };
|
|
});
|
|
|
|
await expect(createClientProfileRechargeOrder('product-1')).rejects.toThrow(
|
|
'登录账号已变化,原请求已停止',
|
|
);
|
|
expect(invoke).toHaveBeenCalledTimes(1);
|
|
});
|