AGC认证载体序列化用例对齐

- 载体用例断言 message 为载荷序列化;Tauri 缺陷抛真 Error 时是 {},但上报仍优先用其自身 message/stack
- 上报用例的 message 断言改为序列化结果,并新增真 Error 优先分支用例
This commit is contained in:
2026-10-02 11:09:46 +08:00
parent 7673edb04e
commit 55d8667c07
3 changed files with 30 additions and 7 deletions
@@ -144,7 +144,10 @@ describe('认证失败的上报判据', () => {
await waitFor(() => expect(reportCalls()).toHaveLength(1));
expect(reportCalls()[0]?.[1]).toMatchObject({
source: 'unhandledrejection',
message: '未知客户端错误',
message: JSON.stringify({
type: 'authNetworkFailure',
reason: 'unreachable',
}),
});
} finally {
uninstall();
@@ -3,8 +3,8 @@
* 认证命令失败的 JS 侧载体。
*
* `invokeClientAuth` 是薄包装:Rust 的结构化拒绝原样装进 `ClientAuthErrorWrapper.error`,不读变体
* 字段(`serverMessage` 之类不保证每个变体都有)、不注入上下文、载体自身 `message` 留空;分流只按
* `error.error.type`。另外用编译期用例钉住变体全集:无字段变体直接取固定文案,带载荷变体先
* 字段(`serverMessage` 之类不保证每个变体都有)、不注入上下文,载体构造时把整份载荷序列化进
* `Error.message`;分流只按 `error.error.type`。另外用编译期用例钉住变体全集:无字段变体直接取固定文案,带载荷变体先
* `as` 取具名类型,可枚举的细分再按类型化 reason 分流;漏列任何变体或 reason,
* `expectNever(...)` 都编译不过。
*/
@@ -147,7 +147,7 @@ describe('认证命令失败的 JS 侧载体', () => {
expect(failure).toBeInstanceOf(ClientAuthErrorWrapper);
expect((failure as ClientAuthErrorWrapper).error).toBe(rejection);
expect((failure as Error).message).toBe('');
expect((failure as Error).message).toBe(JSON.stringify(rejection));
});
it('认证桥未安装时保持原样抛出,不包装成命令失败', async () => {
@@ -175,7 +175,8 @@ describe('认证命令失败的 JS 侧载体', () => {
).catch((error: unknown) => error);
expect(failure).toBeInstanceOf(ClientAuthErrorWrapper);
expect((failure as Error).message).toBe('');
// Error 没有可枚举属性,序列化就是空对象;上报链路对真 Error 优先用其自身 message/stack。
expect((failure as Error).message).toBe('{}');
expect((failure as ClientAuthErrorWrapper).error).toBe(rejection);
});
});
@@ -84,6 +84,7 @@ import {
submitErrorReportBatch,
subscribeClientErrorEvents,
} from '../src/services/errorReporting';
import type { ClientAuthError } from '../src/services/generated/ClientAuthError';
describe('客户端错误报告池', () => {
afterEach(() => {
@@ -209,7 +210,7 @@ describe('客户端错误报告池', () => {
expect(await getPendingClientErrorEvents()).toEqual([event]);
});
it('解包 ClientAuthErrorWrapper 里的原始拒绝值,上下文只认显式入参', async () => {
it('解包 ClientAuthErrorWrapper,把载体序列化后的 message 原样上报,上下文只认显式入参', async () => {
const original = { type: 'authNetworkFailure', reason: 'unreachable' };
await captureClientError(new ClientAuthErrorWrapper(original), {
@@ -219,13 +220,31 @@ describe('客户端错误报告池', () => {
expect(invoke).toHaveBeenCalledWith('report_client_error', {
source: 'unhandledrejection',
message: '未知客户端错误',
message: JSON.stringify(original),
stack: expect.any(String),
action: 'login',
page: undefined,
});
});
it('包装的真 Error 仍优先用它的 message/stack,而不是空对象序列化', async () => {
// Tauri 在映射外抛真 Error 属于它的缺陷:载体照装,但取文案时仍用这颗 Error 自己。
const original = new Error('IPC 桥接异常');
await captureClientError(
new ClientAuthErrorWrapper(original as unknown as ClientAuthError),
{ source: 'unhandledrejection' },
);
expect(invoke).toHaveBeenCalledWith('report_client_error', {
source: 'unhandledrejection',
message: 'IPC 桥接异常',
stack: original.stack,
action: undefined,
page: undefined,
});
});
it('保留 API 路由但隐藏 URL origin 与查询参数', () => {
const sanitized = normalizeDiagnosticText(
'请求超时:https://dev.genarrative.world/api/llm/models?token=secret#fragment',