1e186369c9
Project CI / AI game creator shell Rust crates (push) Successful in 1m24s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m56s
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/446 Co-authored-by: Linghong <ink29535@proton.me> Co-committed-by: Linghong <ink29535@proton.me>
130 lines
4.3 KiB
TypeScript
130 lines
4.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
import {
|
|
cleanup,
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
within,
|
|
} from '@testing-library/react';
|
|
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
|
|
|
import {
|
|
AdminApiError,
|
|
listAdminAgcTrackingEvents,
|
|
} from '../api/adminApiClient';
|
|
import type { AdminAgcTrackingEventEntry } from '../api/adminApiTypes';
|
|
import { AdminAgcTrackingPage } from './AdminAgcTrackingPage';
|
|
|
|
vi.mock('../api/adminApiClient', async (original) => ({
|
|
...(await original<typeof import('../api/adminApiClient')>()),
|
|
listAdminAgcTrackingEvents: vi.fn(),
|
|
}));
|
|
vi.mock('../components/AdminUserReferenceButton', () => ({
|
|
AdminUserReferenceButton: () => null,
|
|
}));
|
|
|
|
const entry: AdminAgcTrackingEventEntry = {
|
|
eventId: 'event-1',
|
|
schemaVersion: 1,
|
|
eventName: 'project_save',
|
|
eventTime: '2026-09-21T12:00:00.123Z',
|
|
userId: 'user-1',
|
|
editorSessionId: 'session-1',
|
|
projectId: 'project-1',
|
|
creativeTaskId: 'project-1',
|
|
agentRunId: null,
|
|
agentTurnId: null,
|
|
status: 'success',
|
|
errorCode: null,
|
|
source: 'gui',
|
|
clientVersion: '1.0',
|
|
properties: { reason: 'manual' },
|
|
batchId: 'batch-1',
|
|
receivedAt: '2026-09-21T12:15:00.000Z',
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.mocked(listAdminAgcTrackingEvents).mockReset();
|
|
});
|
|
afterEach(cleanup);
|
|
|
|
test('翻页沿用游标,筛选和刷新从第一页重新查询,详情保留 null 和事件属性', async () => {
|
|
vi.mocked(listAdminAgcTrackingEvents)
|
|
.mockResolvedValueOnce({ entries: [entry], nextCursor: 'cursor-page-2' })
|
|
.mockResolvedValueOnce({
|
|
entries: [{ ...entry, eventId: 'event-2' }],
|
|
nextCursor: null,
|
|
})
|
|
.mockResolvedValue({ entries: [entry], nextCursor: 'cursor-new' });
|
|
render(<AdminAgcTrackingPage token="admin-token" onUnauthorized={vi.fn()} />);
|
|
await screen.findByText('保存项目', { selector: 'td' });
|
|
expect(listAdminAgcTrackingEvents).toHaveBeenLastCalledWith('admin-token', {
|
|
cursor: undefined,
|
|
limit: 50,
|
|
});
|
|
fireEvent.click(screen.getByText('查看详情'));
|
|
const dialog = screen.getByRole('dialog');
|
|
expect(within(dialog).getAllByText('—').length).toBe(3);
|
|
expect(within(dialog).getByText(/"reason": "manual"/)).toBeTruthy();
|
|
expect(within(dialog).getByText(entry.eventTime)).toBeTruthy();
|
|
fireEvent.click(within(dialog).getByLabelText('关闭详情'));
|
|
fireEvent.click(screen.getByText('下一页'));
|
|
await waitFor(() =>
|
|
expect(listAdminAgcTrackingEvents).toHaveBeenLastCalledWith('admin-token', {
|
|
cursor: 'cursor-page-2',
|
|
limit: 50,
|
|
}),
|
|
);
|
|
await waitFor(() =>
|
|
expect(screen.getByText('查询').closest('button')?.disabled).toBe(false),
|
|
);
|
|
fireEvent.click(screen.getByText('上一页'));
|
|
await screen.findByText('第 1 页');
|
|
expect(listAdminAgcTrackingEvents).toHaveBeenCalledTimes(2);
|
|
fireEvent.change(screen.getByLabelText('项目 ID'), {
|
|
target: { value: 'project-2' },
|
|
});
|
|
fireEvent.click(screen.getByText('查询'));
|
|
await waitFor(() =>
|
|
expect(listAdminAgcTrackingEvents).toHaveBeenLastCalledWith(
|
|
'admin-token',
|
|
expect.objectContaining({
|
|
projectId: 'project-2',
|
|
cursor: undefined,
|
|
limit: 50,
|
|
}),
|
|
),
|
|
);
|
|
expect(screen.getByText('第 1 页')).toBeTruthy();
|
|
await waitFor(() =>
|
|
expect(screen.getByText('刷新').closest('button')?.disabled).toBe(false),
|
|
);
|
|
fireEvent.click(screen.getByText('刷新'));
|
|
await waitFor(() =>
|
|
expect(listAdminAgcTrackingEvents).toHaveBeenCalledTimes(4),
|
|
);
|
|
});
|
|
|
|
test('空结果、权限失败与登录失效沿用后台反馈', async () => {
|
|
const unauthorized = vi.fn();
|
|
vi.mocked(listAdminAgcTrackingEvents)
|
|
.mockResolvedValueOnce({ entries: [], nextCursor: null })
|
|
.mockRejectedValueOnce(
|
|
new AdminApiError({ status: 403, message: '无权访问客户端埋点' }),
|
|
)
|
|
.mockRejectedValueOnce(
|
|
new AdminApiError({ status: 401, message: '登录失效' }),
|
|
);
|
|
render(<AdminAgcTrackingPage token="token" onUnauthorized={unauthorized} />);
|
|
await screen.findByText('暂无客户端埋点数据');
|
|
fireEvent.click(screen.getByText('刷新'));
|
|
expect((await screen.findByRole('alert')).textContent).toContain(
|
|
'无权访问客户端埋点',
|
|
);
|
|
fireEvent.click(screen.getByText('刷新'));
|
|
await waitFor(() =>
|
|
expect(unauthorized).toHaveBeenCalledWith('登录状态已失效'),
|
|
);
|
|
});
|