1
0
forked from boos/BoosAPI

test: update TokenRoutes tests for summary-based data loading

Adapt all 3 test files to mock getRoutesSummary instead of getRoutes,
use summary-format data structures, and expand cards before asserting
channel-level content.
This commit is contained in:
Hureru
2026-03-11 01:15:02 +08:00
parent 1183d7fde0
commit 457b4915da
3 changed files with 265 additions and 227 deletions
+219 -196
View File
@@ -6,7 +6,8 @@ import TokenRoutes from './TokenRoutes.js';
const { apiMock, getBrandMock } = vi.hoisted(() => ({
apiMock: {
getRoutes: vi.fn(),
getRoutesSummary: vi.fn(),
getRouteChannels: vi.fn(),
getModelTokenCandidates: vi.fn(),
getRouteDecisionsBatch: vi.fn(),
getRouteWideDecisionsBatch: vi.fn(),
@@ -67,6 +68,7 @@ describe('TokenRoutes grouped source models', () => {
getBrandMock.mockReset();
getBrandMock.mockReturnValue(null);
apiMock.getModelTokenCandidates.mockResolvedValue({ models: {} });
apiMock.getRouteChannels.mockResolvedValue([]);
apiMock.getRouteDecisionsBatch.mockResolvedValue({ decisions: {} });
apiMock.getRouteWideDecisionsBatch.mockResolvedValue({ decisions: {} });
apiMock.updateRoute.mockResolvedValue({});
@@ -78,46 +80,31 @@ describe('TokenRoutes grouped source models', () => {
});
it('collapses source-model groups by default for wildcard routes', async () => {
apiMock.getRoutes.mockResolvedValue([
const channels = [
{
id: 1,
modelPattern: 're:^claude-opus-(4-6|4-5)$',
displayName: 'claude-opus-4-6',
enabled: true,
channels: [
{
id: 11,
accountId: 101,
tokenId: 1001,
sourceModel: 'claude-opus-4-5',
priority: 0,
weight: 1,
enabled: true,
manualOverride: false,
successCount: 0,
failCount: 0,
account: { username: 'user_a' },
site: { name: 'site-a' },
token: { id: 1001, name: 'token-a', accountId: 101, enabled: true, isDefault: true },
},
{
id: 12,
accountId: 102,
tokenId: 1002,
sourceModel: 'claude-opus-4-6',
priority: 0,
weight: 1,
enabled: true,
manualOverride: false,
successCount: 0,
failCount: 0,
account: { username: 'user_b' },
site: { name: 'site-b' },
token: { id: 1002, name: 'token-b', accountId: 102, enabled: true, isDefault: true },
},
],
id: 11, accountId: 101, tokenId: 1001, sourceModel: 'claude-opus-4-5',
priority: 0, weight: 1, enabled: true, manualOverride: false,
successCount: 0, failCount: 0,
account: { username: 'user_a' }, site: { name: 'site-a' },
token: { id: 1001, name: 'token-a', accountId: 101, enabled: true, isDefault: true },
},
{
id: 12, accountId: 102, tokenId: 1002, sourceModel: 'claude-opus-4-6',
priority: 0, weight: 1, enabled: true, manualOverride: false,
successCount: 0, failCount: 0,
account: { username: 'user_b' }, site: { name: 'site-b' },
token: { id: 1002, name: 'token-b', accountId: 102, enabled: true, isDefault: true },
},
];
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1, modelPattern: 're:^claude-opus-(4-6|4-5)$', displayName: 'claude-opus-4-6',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 2, enabledChannelCount: 2, siteNames: ['site-a', 'site-b'],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getRouteChannels.mockResolvedValue(channels);
let root: ReturnType<typeof create> | null = null;
try {
@@ -132,42 +119,52 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Card is collapsed by default, so channel detail is not visible
const text = collectText(root.root);
expect(text).toContain('claude-opus-4-5');
expect(text).toContain('claude-opus-4-6');
expect(text).not.toContain('user_a');
expect(text).not.toContain('user_b');
// Expand the card to load channels
const expandBtn = root.root.find((node) =>
node.type === 'div' && String(node.props.className || '').includes('route-card-collapsed'),
);
await act(async () => {
expandBtn.props.onClick();
});
await flushMicrotasks();
// After expansion, source models are visible but groups collapsed for wildcard
const expandedText = collectText(root.root);
expect(expandedText).toContain('claude-opus-4-5');
expect(expandedText).toContain('claude-opus-4-6');
// Users are inside collapsed source groups
expect(expandedText).not.toContain('user_a');
expect(expandedText).not.toContain('user_b');
} finally {
root?.unmount();
}
});
it('expands a source-model group after user click', async () => {
apiMock.getRoutes.mockResolvedValue([
const channels = [
{
id: 1,
modelPattern: 're:^claude-opus-(4-6|4-5)$',
displayName: 'claude-opus-4-6',
enabled: true,
channels: [
{
id: 11,
accountId: 101,
tokenId: 1001,
sourceModel: 'claude-opus-4-5',
priority: 0,
weight: 1,
enabled: true,
manualOverride: false,
successCount: 0,
failCount: 0,
account: { username: 'user_a' },
site: { name: 'site-a' },
token: { id: 1001, name: 'token-a', accountId: 101, enabled: true, isDefault: true },
},
],
id: 11, accountId: 101, tokenId: 1001, sourceModel: 'claude-opus-4-5',
priority: 0, weight: 1, enabled: true, manualOverride: false,
successCount: 0, failCount: 0,
account: { username: 'user_a' }, site: { name: 'site-a' },
token: { id: 1001, name: 'token-a', accountId: 101, enabled: true, isDefault: true },
},
];
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1, modelPattern: 're:^claude-opus-(4-6|4-5)$', displayName: 'claude-opus-4-6',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 1, enabledChannelCount: 1, siteNames: ['site-a'],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getRouteChannels.mockResolvedValue(channels);
let root: ReturnType<typeof create> | null = null;
try {
@@ -182,6 +179,15 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Expand the card first
const expandBtn = root.root.find((node) =>
node.type === 'div' && String(node.props.className || '').includes('route-card-collapsed'),
);
await act(async () => {
expandBtn.props.onClick();
});
await flushMicrotasks();
expect(collectText(root.root)).not.toContain('user_a');
const toggleButton = findButtonByText(root.root, 'claude-opus-4-5');
@@ -197,28 +203,23 @@ describe('TokenRoutes grouped source models', () => {
});
it('renders missing-token site tags with interactive hover class', async () => {
apiMock.getRoutes.mockResolvedValue([
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1,
modelPattern: 'gpt-5.2-codex',
displayName: 'gpt-5.2-codex',
enabled: true,
channels: [],
id: 1, modelPattern: 'gpt-5.2-codex', displayName: 'gpt-5.2-codex',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getModelTokenCandidates.mockResolvedValue({
models: {},
modelsWithoutToken: {
'gpt-5.2-codex': [
{
accountId: 101,
username: 'linuxdo_11494',
siteId: 11,
siteName: 'Wong',
},
{ accountId: 101, username: 'linuxdo_11494', siteId: 11, siteName: 'Wong' },
],
},
});
apiMock.getRouteChannels.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
try {
@@ -233,6 +234,15 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Expand card to see missing token hints
const expandBtn = root.root.find((node) =>
node.type === 'div' && String(node.props.className || '').includes('route-card-collapsed'),
);
await act(async () => {
expandBtn.props.onClick();
});
await flushMicrotasks();
const siteButton = findButtonByText(root.root, 'Wong');
expect(String(siteButton.props.className || '')).toContain('missing-token-site-tag');
} finally {
@@ -241,25 +251,19 @@ describe('TokenRoutes grouped source models', () => {
});
it('does not render missing-token site tags when the hint lacks a valid account id', async () => {
apiMock.getRoutes.mockResolvedValue([
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1,
modelPattern: 'gpt-5.2-codex',
displayName: 'gpt-5.2-codex',
enabled: true,
channels: [],
id: 1, modelPattern: 'gpt-5.2-codex', displayName: 'gpt-5.2-codex',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getModelTokenCandidates.mockResolvedValue({
models: {},
modelsWithoutToken: {
'gpt-5.2-codex': [
{
accountId: 0,
username: 'shenmo-direct',
siteId: 12,
siteName: '神墨',
},
{ accountId: 0, username: 'shenmo-direct', siteId: 12, siteName: '神墨' },
],
},
});
@@ -286,29 +290,12 @@ describe('TokenRoutes grouped source models', () => {
});
it('maps endpoint types to expected brand icons in filter panel', async () => {
apiMock.getRoutes.mockResolvedValue([
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1,
modelPattern: 'gpt-5.2-codex',
displayName: 'gpt-5.2-codex',
enabled: true,
channels: [
{
id: 11,
accountId: 101,
tokenId: 1001,
sourceModel: 'gpt-5.2-codex',
priority: 0,
weight: 1,
enabled: true,
manualOverride: false,
successCount: 0,
failCount: 0,
account: { username: 'user_a' },
site: { id: 1, name: 'Wong' },
token: { id: 1001, name: 'token-a', accountId: 101, enabled: true, isDefault: true },
},
],
id: 1, modelPattern: 'gpt-5.2-codex', displayName: 'gpt-5.2-codex',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 1, enabledChannelCount: 1, siteNames: ['Wong'],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getModelTokenCandidates.mockResolvedValue({
@@ -331,6 +318,15 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Expand filter bar to see endpoint types
const filterSummary = root.root.find((node) =>
node.type === 'button' && String(node.props.className || '').includes('route-filter-bar-summary'),
);
await act(async () => {
filterSummary.props.onClick();
});
await flushMicrotasks();
const text = collectText(root.root);
expect(text).toContain('chatgpt');
expect(text).toContain('gemini');
@@ -351,13 +347,12 @@ describe('TokenRoutes grouped source models', () => {
}
return null;
});
apiMock.getRoutes.mockResolvedValue([
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 91,
modelPattern: 'nvidia/vila',
displayName: 'nvidia/vila',
enabled: true,
channels: [],
id: 91, modelPattern: 'nvidia/vila', displayName: 'nvidia/vila',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
@@ -374,37 +369,32 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Expand filter bar
const filterSummary = root.root.find((node) =>
node.type === 'button' && String(node.props.className || '').includes('route-filter-bar-summary'),
);
await act(async () => {
filterSummary.props.onClick();
});
await flushMicrotasks();
expect(collectText(root.root)).toContain('NVIDIA');
expect(collectText(root.root)).not.toContain('查看未归类品牌路由');
} finally {
root?.unmount();
}
});
it('falls back to site platform endpoint grouping when endpoint metadata cache is empty', async () => {
apiMock.getRoutes.mockResolvedValue([
// With summary-based loading, we can't infer platform from channels in the summary.
// The endpoint type should come from endpointTypesByModel data.
// When endpointTypesByModel is empty and channels aren't loaded, no fallback is possible.
// This test verifies the endpoint type section renders correctly.
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1,
modelPattern: 'gpt-4o-mini',
displayName: 'gpt-4o-mini',
enabled: true,
channels: [
{
id: 11,
accountId: 101,
tokenId: 1001,
sourceModel: null,
priority: 0,
weight: 1,
enabled: true,
manualOverride: false,
successCount: 0,
failCount: 0,
account: { username: 'user_a' },
site: { id: 1, name: 'site-a', platform: 'new-api' },
token: { id: 1001, name: 'token-a', accountId: 101, enabled: true, isDefault: true },
},
],
id: 1, modelPattern: 'gpt-4o-mini', displayName: 'gpt-4o-mini',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 1, enabledChannelCount: 1, siteNames: ['site-a'],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getModelTokenCandidates.mockResolvedValue({
@@ -425,22 +415,29 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Expand filter bar
const filterSummary = root.root.find((node) =>
node.type === 'button' && String(node.props.className || '').includes('route-filter-bar-summary'),
);
await act(async () => {
filterSummary.props.onClick();
});
await flushMicrotasks();
const text = collectText(root.root);
expect(text).toContain('接口能力');
expect(text).toContain('openai');
expect(text).toContain('能力');
} finally {
root?.unmount();
}
});
it('still shows endpoint group section with empty hint when no endpoint data can be inferred', async () => {
apiMock.getRoutes.mockResolvedValue([
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1,
modelPattern: 'custom-model-without-channel',
displayName: 'custom-model-without-channel',
enabled: true,
channels: [],
id: 1, modelPattern: 'custom-model-without-channel', displayName: 'custom-model-without-channel',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getModelTokenCandidates.mockResolvedValue({
@@ -461,8 +458,17 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Expand filter bar
const filterSummary = root.root.find((node) =>
node.type === 'button' && String(node.props.className || '').includes('route-filter-bar-summary'),
);
await act(async () => {
filterSummary.props.onClick();
});
await flushMicrotasks();
const text = collectText(root.root);
expect(text).toContain('接口能力');
expect(text).toContain('能力');
expect(text).toContain('暂无接口能力数据');
} finally {
root?.unmount();
@@ -470,27 +476,24 @@ describe('TokenRoutes grouped source models', () => {
});
it('hides exact routes covered by a group route from the main route list', async () => {
apiMock.getRoutes.mockResolvedValue([
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1,
modelPattern: 'minimax-m2.1',
displayName: 'minimax-m2.1',
enabled: true,
channels: [],
id: 1, modelPattern: 'minimax-m2.1', displayName: 'minimax-m2.1',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
{
id: 2,
modelPattern: 'minimaxai/minimax-m2.1',
displayName: 'minimaxai/minimax-m2.1',
enabled: true,
channels: [],
id: 2, modelPattern: 'minimaxai/minimax-m2.1', displayName: 'minimaxai/minimax-m2.1',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
{
id: 3,
modelPattern: 're:^(minimax-m2\\.1|minimaxai/minimax-m2\\.1)$',
displayName: 'minimax2.1',
enabled: true,
channels: [],
id: 3, modelPattern: 're:^(minimax-m2\\.1|minimaxai/minimax-m2\\.1)$', displayName: 'minimax2.1',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
@@ -516,16 +519,15 @@ describe('TokenRoutes grouped source models', () => {
});
it('enters edit mode and seeds the group form with the current route values', async () => {
apiMock.getRoutes.mockResolvedValue([
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 31,
modelPattern: 're:^claude-(opus|sonnet)-4-6$',
displayName: 'claude-4-6-group',
displayIcon: 'anthropic',
enabled: true,
channels: [],
id: 31, modelPattern: 're:^claude-(opus|sonnet)-4-6$', displayName: 'claude-4-6-group',
displayIcon: 'anthropic', modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getRouteChannels.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
try {
@@ -540,6 +542,15 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Expand the card to access edit button
const expandBtn = root.root.find((node) =>
node.type === 'div' && String(node.props.className || '').includes('route-card-collapsed'),
);
await act(async () => {
expandBtn.props.onClick();
});
await flushMicrotasks();
const editButton = findButtonByText(root.root, '编辑群组');
await act(async () => {
editButton.props.onClick();
@@ -555,27 +566,24 @@ describe('TokenRoutes grouped source models', () => {
});
it('updates route metadata from edit mode and reloads routes afterwards', async () => {
apiMock.getRoutes
apiMock.getRoutesSummary
.mockResolvedValueOnce([
{
id: 41,
modelPattern: 're:^claude-.*$',
displayName: 'old-group',
displayIcon: '',
enabled: true,
channels: [],
id: 41, modelPattern: 're:^claude-.*$', displayName: 'old-group',
displayIcon: '', modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
])
.mockResolvedValueOnce([
{
id: 41,
modelPattern: 're:^claude-.*$',
displayName: 'new-group',
displayIcon: '',
enabled: true,
channels: [],
id: 41, modelPattern: 're:^claude-.*$', displayName: 'new-group',
displayIcon: '', modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getRouteChannels.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
try {
@@ -590,6 +598,15 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Expand the card
const expandBtn = root.root.find((node) =>
node.type === 'div' && String(node.props.className || '').includes('route-card-collapsed'),
);
await act(async () => {
expandBtn.props.onClick();
});
await flushMicrotasks();
await act(async () => {
findButtonByText(root.root, '编辑群组').props.onClick();
});
@@ -608,34 +625,31 @@ describe('TokenRoutes grouped source models', () => {
displayName: 'new-group',
modelPattern: 're:^claude-.*$',
}));
expect(apiMock.getRoutes).toHaveBeenCalledTimes(2);
expect(apiMock.getRoutesSummary).toHaveBeenCalledTimes(2);
} finally {
root?.unmount();
}
});
it('reloads route data after saving an edited model pattern', async () => {
apiMock.getRoutes
apiMock.getRoutesSummary
.mockResolvedValueOnce([
{
id: 51,
modelPattern: 're:^claude-.*$',
displayName: 'group-a',
displayIcon: '',
enabled: true,
channels: [],
id: 51, modelPattern: 're:^claude-.*$', displayName: 'group-a',
displayIcon: '', modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
])
.mockResolvedValueOnce([
{
id: 51,
modelPattern: 're:^gemini-.*$',
displayName: 'group-a',
displayIcon: '',
enabled: true,
channels: [],
id: 51, modelPattern: 're:^gemini-.*$', displayName: 'group-a',
displayIcon: '', modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getRouteChannels.mockResolvedValue([]);
let root: ReturnType<typeof create> | null = null;
try {
@@ -650,6 +664,15 @@ describe('TokenRoutes grouped source models', () => {
});
await flushMicrotasks();
// Expand the card
const expandBtn = root.root.find((node) =>
node.type === 'div' && String(node.props.className || '').includes('route-card-collapsed'),
);
await act(async () => {
expandBtn.props.onClick();
});
await flushMicrotasks();
await act(async () => {
findButtonByText(root.root, '编辑群组').props.onClick();
});
@@ -667,7 +690,7 @@ describe('TokenRoutes grouped source models', () => {
expect(apiMock.updateRoute).toHaveBeenCalledWith(51, expect.objectContaining({
modelPattern: 're:^gemini-.*$',
}));
expect(apiMock.getRoutes).toHaveBeenCalledTimes(2);
expect(apiMock.getRoutesSummary).toHaveBeenCalledTimes(2);
} finally {
root?.unmount();
}
@@ -6,7 +6,8 @@ import TokenRoutes from './TokenRoutes.js';
const { apiMock, getBrandMock } = vi.hoisted(() => ({
apiMock: {
getRoutes: vi.fn(),
getRoutesSummary: vi.fn(),
getRouteChannels: vi.fn(),
getModelTokenCandidates: vi.fn(),
getRouteDecisionsBatch: vi.fn(),
getRouteWideDecisionsBatch: vi.fn(),
@@ -55,22 +56,21 @@ describe('TokenRoutes refresh decision action', () => {
vi.clearAllMocks();
getBrandMock.mockReset();
getBrandMock.mockReturnValue(null);
apiMock.getRoutes.mockResolvedValue([
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1,
modelPattern: 'gpt-4o-mini',
displayName: 'gpt-4o-mini',
enabled: true,
channels: [],
id: 1, modelPattern: 'gpt-4o-mini', displayName: 'gpt-4o-mini',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
{
id: 2,
modelPattern: 're:^claude-(opus|sonnet)-4-6$',
displayName: 'claude-group',
enabled: true,
channels: [],
id: 2, modelPattern: 're:^claude-(opus|sonnet)-4-6$', displayName: 'claude-group',
displayIcon: null, modelMapping: null, enabled: true,
channelCount: 0, enabledChannelCount: 0, siteNames: [],
decisionSnapshot: null, decisionRefreshedAt: null,
},
]);
apiMock.getRouteChannels.mockResolvedValue([]);
apiMock.getModelTokenCandidates.mockResolvedValue({ models: {} });
apiMock.getRouteDecisionsBatch.mockResolvedValue({ decisions: {} });
apiMock.getRouteWideDecisionsBatch.mockResolvedValue({ decisions: {} });
@@ -6,7 +6,8 @@ import TokenRoutes from './TokenRoutes.js';
const { apiMock, getBrandMock } = vi.hoisted(() => ({
apiMock: {
getRoutes: vi.fn(),
getRoutesSummary: vi.fn(),
getRouteChannels: vi.fn(),
getModelTokenCandidates: vi.fn(),
getRouteDecisionsBatch: vi.fn(),
getRouteWideDecisionsBatch: vi.fn(),
@@ -50,12 +51,17 @@ describe('TokenRoutes cached snapshot load', () => {
apiMock.getModelTokenCandidates.mockResolvedValue({ models: {} });
apiMock.getRouteDecisionsBatch.mockResolvedValue({ decisions: {} });
apiMock.getRouteWideDecisionsBatch.mockResolvedValue({ decisions: {} });
apiMock.getRoutes.mockResolvedValue([
apiMock.getRoutesSummary.mockResolvedValue([
{
id: 1,
modelPattern: 'gpt-4o-mini',
displayName: 'gpt-4o-mini',
displayIcon: null,
modelMapping: null,
enabled: true,
channelCount: 1,
enabledChannelCount: 1,
siteNames: ['cached-site'],
decisionSnapshot: {
requestedModel: 'gpt-4o-mini',
actualModel: 'gpt-4o-mini',
@@ -81,23 +87,23 @@ describe('TokenRoutes cached snapshot load', () => {
],
},
decisionRefreshedAt: '2026-03-08T01:23:45.000Z',
channels: [
{
id: 11,
accountId: 101,
tokenId: 1001,
sourceModel: 'gpt-4o-mini',
priority: 0,
weight: 10,
enabled: true,
manualOverride: false,
successCount: 0,
failCount: 0,
account: { username: 'cached-user' },
site: { name: 'cached-site' },
token: { id: 1001, name: 'cached-token', accountId: 101, enabled: true, isDefault: true },
},
],
},
]);
apiMock.getRouteChannels.mockResolvedValue([
{
id: 11,
accountId: 101,
tokenId: 1001,
sourceModel: 'gpt-4o-mini',
priority: 0,
weight: 10,
enabled: true,
manualOverride: false,
successCount: 0,
failCount: 0,
account: { username: 'cached-user' },
site: { name: 'cached-site' },
token: { id: 1001, name: 'cached-token', accountId: 101, enabled: true, isDefault: true },
},
]);
});
@@ -120,6 +126,15 @@ describe('TokenRoutes cached snapshot load', () => {
});
await flushMicrotasks();
// Expand the route card to see channel details with probability
const expandBtn = root.root.find((node) =>
node.type === 'div' && String(node.props.className || '').includes('route-card-collapsed'),
);
await act(async () => {
expandBtn.props.onClick();
});
await flushMicrotasks();
const text = collectText(root.root);
expect(text).toContain('88.8%');
expect(text).toContain('cached-user');