Files
sub2api/frontend/src/components/admin/monitor/MonitorActionsCell.spec.ts
T
yan9651688 e2e375d694 Make repeated channel-monitor setup safer
Admins often recreate monitors with the same endpoint, model, and request settings. A server-side duplicate keeps the stored API key out of the browser, creates a disabled copy for review, and uses stable operation identity to recover ambiguous retries without creating extra rows.

Constraint: Stored monitor API keys must never be returned to the browser
Constraint: Applying a request template must preserve internal duplicate recovery metadata
Rejected: Rebuild the monitor from list data | list responses only contain a masked API key
Rejected: Copy runtime state and history | a duplicate should start as an unverified configuration
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep duplicated monitors disabled until an administrator reviews and enables them
Tested: Go unit tests for repository, service, and admin handler; integration-tag compile; go vet; golangci-lint v2.9; frontend Vitest, ESLint, typecheck, production build; Playwright duplicate flow
Not-tested: PostgreSQL container integration locally because Docker is unavailable; CI will execute the database-backed suite
2026-07-16 16:30:19 +08:00

78 lines
2.4 KiB
TypeScript

import { mount } from '@vue/test-utils'
import { describe, expect, it, vi } from 'vitest'
import type { ChannelMonitor } from '@/api/admin/channelMonitor'
import MonitorActionsCell from '@/components/admin/monitor/MonitorActionsCell.vue'
vi.mock('vue-i18n', () => ({
useI18n: () => ({ t: (key: string) => key }),
}))
function makeMonitor(overrides: Partial<ChannelMonitor> = {}): ChannelMonitor {
return {
id: 42,
name: 'primary',
provider: 'openai',
api_mode: 'chat_completions',
endpoint: 'https://api.example.com',
api_key_masked: 'sk-t***',
primary_model: 'gpt-4o-mini',
extra_models: [],
group_name: '',
enabled: true,
interval_seconds: 60,
jitter_seconds: 0,
last_checked_at: null,
created_by: 1,
created_at: '2026-07-16T00:00:00Z',
updated_at: '2026-07-16T00:00:00Z',
primary_status: '',
primary_latency_ms: null,
availability_7d: 0,
extra_models_status: [],
template_id: null,
extra_headers: {},
body_override_mode: 'off',
body_override: null,
...overrides,
}
}
describe('MonitorActionsCell duplicate action', () => {
it('emits the selected monitor when duplicate is clicked', async () => {
const row = makeMonitor()
const wrapper = mount(MonitorActionsCell, {
props: { row, running: false, duplicating: false },
})
await wrapper.get('[data-testid="monitor-duplicate"]').trigger('click')
expect(wrapper.emitted('duplicate')).toEqual([[row]])
})
it('disables the action while the same monitor is being duplicated', () => {
const wrapper = mount(MonitorActionsCell, {
props: { row: makeMonitor(), running: false, duplicating: true },
})
const button = wrapper.get('[data-testid="monitor-duplicate"]')
expect(button.attributes('disabled')).toBeDefined()
expect(button.attributes('title')).toBe('admin.channelMonitor.duplicating')
expect(button.text()).toContain('admin.channelMonitor.duplicating')
})
it('disables the action when the stored API key cannot be decrypted', () => {
const wrapper = mount(MonitorActionsCell, {
props: {
row: makeMonitor({ api_key_decrypt_failed: true }),
running: false,
duplicating: false,
},
})
const button = wrapper.get('[data-testid="monitor-duplicate"]')
expect(button.attributes('disabled')).toBeDefined()
expect(button.attributes('title')).toBe('admin.channelMonitor.duplicateKeyUnavailable')
})
})