From 302a10b88608392617dbe8e228711a86a236ac0d Mon Sep 17 00:00:00 2001 From: Randark Date: Tue, 18 Aug 2026 05:10:10 +0000 Subject: [PATCH] =?UTF-8?q?test(monitor-ui):=20=E9=85=8D=E9=A2=9D=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E6=B8=B2=E6=9F=93=E4=B8=8E=E5=BC=80=E5=85=B3=E9=97=A8?= =?UTF-8?q?=E6=8E=A7=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 MonitorQuotaView.spec:tier window/label 的 i18n 映射与未知 token 透出、阈值配色(≥90 红/≥75 黄/其余绿)、进度条宽度钳制、 多币种余额与单余额回退、plan_level 徽章、错误截断与失败态不渲染 tiers - 新增 MonitorCard.quota.spec:latest_quota 受系统开关门控, 无快照或开关关闭时不渲染 - 既有工厂/断言补 check_mode、account_id;provider 网格断言 4→8 --- .../admin/monitor/MonitorActionsCell.spec.ts | 2 + .../common/__tests__/MonitorQuotaView.spec.ts | 128 ++++++++++++++++++ .../user/__tests__/MonitorCard.quota.spec.ts | 91 +++++++++++++ .../ChannelMonitorView.duplicate.spec.ts | 2 + .../__tests__/ChannelMonitorView.grok.spec.ts | 2 +- 5 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/common/__tests__/MonitorQuotaView.spec.ts create mode 100644 frontend/src/components/user/__tests__/MonitorCard.quota.spec.ts diff --git a/frontend/src/components/admin/monitor/MonitorActionsCell.spec.ts b/frontend/src/components/admin/monitor/MonitorActionsCell.spec.ts index 31f9eca738..a7989ffeaa 100644 --- a/frontend/src/components/admin/monitor/MonitorActionsCell.spec.ts +++ b/frontend/src/components/admin/monitor/MonitorActionsCell.spec.ts @@ -34,6 +34,8 @@ function makeMonitor(overrides: Partial = {}): ChannelMonitor { extra_headers: {}, body_override_mode: 'off', body_override: null, + check_mode: 'probe', + account_id: null, ...overrides, } } diff --git a/frontend/src/components/common/__tests__/MonitorQuotaView.spec.ts b/frontend/src/components/common/__tests__/MonitorQuotaView.spec.ts new file mode 100644 index 0000000000..9f305d2156 --- /dev/null +++ b/frontend/src/components/common/__tests__/MonitorQuotaView.spec.ts @@ -0,0 +1,128 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' + +import type { MonitorQuotaSnapshot } from '@/api/admin/channelMonitor' +import MonitorQuotaView from '../MonitorQuotaView.vue' + +vi.mock('vue-i18n', async () => { + const actual = await vi.importActual('vue-i18n') + return { + ...actual, + // te() 恒真:已知 token 直接返回 i18n key,便于断言 window/label 映射。 + useI18n: () => ({ t: (key: string) => key, te: () => true }), + } +}) + +function makeSnapshot(overrides: Partial = {}): MonitorQuotaSnapshot { + return { + source: 'usage', + success: true, + fetched_at: '2026-08-18T00:00:00Z', + ...overrides, + } +} + +describe('MonitorQuotaView', () => { + it('renders nothing without a snapshot', () => { + const wrapper = mount(MonitorQuotaView, { props: { snapshot: null } }) + expect(wrapper.find('[data-testid="monitor-quota-view"]').exists()).toBe(false) + expect(wrapper.text()).toBe('') + }) + + it('maps tier window/label tokens through i18n and colors utilization', () => { + const wrapper = mount(MonitorQuotaView, { + props: { + snapshot: makeSnapshot({ + tiers: [ + { window: '5h', used_percent: 42.4 }, + { window: '7d', label: 'pro', used_percent: 80 }, + { window: 'weekly', label: 'unknown-token', used_percent: 95 }, + ], + }), + }, + }) + + const rows = wrapper.findAll('[data-testid="monitor-quota-view"] .flex.items-center') + // tier 行只在 success 且有数据时渲染 + expect(rows.length).toBeGreaterThanOrEqual(3) + const text = wrapper.text() + // 已知 window token 走 i18n + expect(text).toContain('monitorCommon.quota.windows.5h') + expect(text).toContain('monitorCommon.quota.windows.7d') + // 已知 label token 拼成 label/window + expect(text).toContain('monitorCommon.quota.labels.pro/monitorCommon.quota.windows.7d') + // 未知 label 原样透出(前向兼容) + expect(text).toContain('unknown-token/monitorCommon.quota.windows.weekly') + // 百分比取整 + expect(text).toContain('42%') + expect(text).toContain('80%') + expect(text).toContain('95%') + + const html = wrapper.html() + // 阈值配色:≥90 红 / ≥75 黄 / 其余绿(与账号页 CNProviderQuotaCell 对齐) + expect(html).toContain('bg-emerald-500') + expect(html).toContain('bg-amber-500') + expect(html).toContain('bg-red-500') + }) + + it('clamps the tier bar width into 0-100', () => { + const wrapper = mount(MonitorQuotaView, { + props: { + snapshot: makeSnapshot({ tiers: [{ window: '5h', used_percent: 240 }] }), + }, + }) + expect(wrapper.html()).toContain('width: 100%') + }) + + it('shows the plan level badge and multi-currency balances', () => { + const wrapper = mount(MonitorQuotaView, { + props: { + snapshot: makeSnapshot({ + plan_level: 'Max20', + balances: [ + { currency: 'CNY', balance: 12.5 }, + { currency: 'USD', balance: 0 }, + ], + }), + }, + }) + + expect(wrapper.text()).toContain('Max20') + expect(wrapper.text()).toContain('12.50 CNY') + expect(wrapper.text()).toContain('0.00 USD') + // 余额为 0 用红色警示 + expect(wrapper.html()).toContain('text-red-600') + }) + + it('falls back to the single balance + currency pair', () => { + const wrapper = mount(MonitorQuotaView, { + props: { snapshot: makeSnapshot({ balance: 3.2, currency: 'CNY' }) }, + }) + expect(wrapper.text()).toContain('3.20 CNY') + }) + + it('renders a truncated error state when the fetch failed', () => { + const longError = 'x'.repeat(60) + const wrapper = mount(MonitorQuotaView, { + props: { + snapshot: makeSnapshot({ success: false, error: longError }), + }, + }) + + const error = wrapper.get('[data-testid="monitor-quota-error"]') + expect(error.text()).toBe(`${'x'.repeat(48)}…`) + expect(error.attributes('title')).toBe(longError) + }) + + it('keeps failed snapshots from rendering tier rows', () => { + const wrapper = mount(MonitorQuotaView, { + props: { + snapshot: makeSnapshot({ + success: false, + tiers: [{ window: '5h', used_percent: 10 }], + }), + }, + }) + expect(wrapper.text()).not.toContain('10%') + }) +}) diff --git a/frontend/src/components/user/__tests__/MonitorCard.quota.spec.ts b/frontend/src/components/user/__tests__/MonitorCard.quota.spec.ts new file mode 100644 index 0000000000..93a8c7abc3 --- /dev/null +++ b/frontend/src/components/user/__tests__/MonitorCard.quota.spec.ts @@ -0,0 +1,91 @@ +import { mount } from '@vue/test-utils' +import { describe, expect, it, vi } from 'vitest' + +import type { UserMonitorView } from '@/api/channelMonitor' +import MonitorCard from '../monitor/MonitorCard.vue' + +const { isQuotaVisible } = vi.hoisted(() => ({ + isQuotaVisible: vi.fn(() => false), +})) + +vi.mock('@/utils/featureFlags', () => ({ + isChannelMonitorQuotaVisible: () => isQuotaVisible(), +})) + +vi.mock('vue-i18n', async () => { + const actual = await vi.importActual('vue-i18n') + return { + ...actual, + useI18n: () => ({ t: (key: string) => key, te: () => true }), + } +}) + +function makeItem(overrides: Partial = {}): UserMonitorView { + return { + id: 1, + name: 'claude-main', + provider: 'kimi', + group_name: '', + primary_model: 'quota', + primary_status: 'operational', + primary_latency_ms: null, + primary_ping_latency_ms: null, + availability_7d: 100, + extra_models: [], + timeline: [], + ...overrides, + } +} + +function mountCard(item: UserMonitorView) { + return mount(MonitorCard, { + props: { + item, + window: '7d', + availabilityValue: 100, + countdownSeconds: 0, + }, + global: { + stubs: { + MonitorMetricPair: true, + MonitorAvailabilityRow: true, + MonitorTimeline: true, + }, + }, + }) +} + +describe('MonitorCard quota snapshot visibility', () => { + it('hides the quota block when the system switch is off even if data exists', () => { + isQuotaVisible.mockReturnValue(false) + const wrapper = mountCard( + makeItem({ + latest_quota: { source: 'cn_quota', success: true, fetched_at: '2026-08-18T00:00:00Z' }, + }), + ) + expect(wrapper.find('[data-testid="monitor-quota-view"]').exists()).toBe(false) + }) + + it('renders the quota block when the switch is on and a snapshot exists', () => { + isQuotaVisible.mockReturnValue(true) + const wrapper = mountCard( + makeItem({ + latest_quota: { + source: 'cn_quota', + success: true, + plan_level: 'kimi-plus', + tiers: [{ window: 'daily', label: 'requests', used_percent: 60 }], + fetched_at: '2026-08-18T00:00:00Z', + }, + }), + ) + expect(wrapper.find('[data-testid="monitor-quota-view"]').exists()).toBe(true) + expect(wrapper.text()).toContain('kimi-plus') + }) + + it('never renders the quota block without a snapshot', () => { + isQuotaVisible.mockReturnValue(true) + const wrapper = mountCard(makeItem()) + expect(wrapper.find('[data-testid="monitor-quota-view"]').exists()).toBe(false) + }) +}) diff --git a/frontend/src/views/admin/__tests__/ChannelMonitorView.duplicate.spec.ts b/frontend/src/views/admin/__tests__/ChannelMonitorView.duplicate.spec.ts index 3eeb1d611f..5dd97fd534 100644 --- a/frontend/src/views/admin/__tests__/ChannelMonitorView.duplicate.spec.ts +++ b/frontend/src/views/admin/__tests__/ChannelMonitorView.duplicate.spec.ts @@ -96,6 +96,8 @@ function makeMonitor(overrides: Partial = {}): ChannelMonitor { extra_headers: {}, body_override_mode: 'off', body_override: null, + check_mode: 'probe', + account_id: null, ...overrides, } } diff --git a/frontend/src/views/admin/__tests__/ChannelMonitorView.grok.spec.ts b/frontend/src/views/admin/__tests__/ChannelMonitorView.grok.spec.ts index f3a47ffa61..94f619aea9 100644 --- a/frontend/src/views/admin/__tests__/ChannelMonitorView.grok.spec.ts +++ b/frontend/src/views/admin/__tests__/ChannelMonitorView.grok.spec.ts @@ -93,7 +93,7 @@ describe('channel monitor Grok provider', () => { expect(PROVIDERS).toContain(PROVIDER_GROK) const providerButtons = wrapper.findAll('[data-testid^="monitor-provider-"]') - expect(providerButtons).toHaveLength(4) + expect(providerButtons).toHaveLength(8) expect(providerButtons[0].element.parentElement?.className).toContain('grid-cols-2') expect(providerButtons[0].element.parentElement?.className).toContain('sm:grid-cols-4')