mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 14:38:27 +08:00
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
78 lines
2.4 KiB
TypeScript
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')
|
|
})
|
|
})
|