fix job deliver target options (#1026)

This commit is contained in:
ekko
2026-05-25 20:04:50 +08:00
committed by GitHub
parent badb17cf8e
commit 689237f0fd
2 changed files with 177 additions and 5 deletions
@@ -52,8 +52,39 @@ const schedulePresets = computed(() => [
{ label: t('jobs.presetEveryMonth'), value: '0 9 1 * *' },
])
function hasText(value: unknown): boolean {
return typeof value === 'string' && value.trim().length > 0
}
function isDeliverTargetConfigured(key: string): boolean {
const config = settingsStore.platforms[key] || {}
switch (key) {
case 'telegram':
case 'discord':
case 'slack':
return hasText(config.token)
case 'whatsapp':
return config.enabled === true || config.enabled === 'true'
case 'matrix':
return hasText(config.token) && hasText(config.extra?.homeserver)
case 'weixin':
return hasText(config.token) && hasText(config.extra?.account_id)
case 'wecom':
return hasText(config.extra?.bot_id) && hasText(config.extra?.secret)
case 'feishu':
return hasText(config.extra?.app_id) && hasText(config.extra?.app_secret)
case 'dingtalk':
return (hasText(config.extra?.client_id) && hasText(config.extra?.client_secret))
|| (hasText(config.extra?.app_key) && hasText(config.extra?.client_secret))
case 'qqbot':
return hasText(config.extra?.app_id) && hasText(config.extra?.client_secret)
default:
return false
}
}
const targetOptions = computed(() => {
const options: Array<{ label: string; value: string }> = [
const options: Array<{ label: string; value: string; disabled?: boolean }> = [
{ label: t('jobs.origin'), value: 'origin' },
{ label: t('jobs.local'), value: 'local' },
]
@@ -67,12 +98,14 @@ const targetOptions = computed(() => {
{ key: 'wecom', label: 'WeCom' },
{ key: 'feishu', label: 'Feishu' },
{ key: 'dingtalk', label: 'DingTalk' },
{ key: 'qqbot', label: 'QQBot' },
]
for (const ch of channels) {
const config = settingsStore.platforms[ch.key] || {}
if (Object.keys(config).length > 0) {
options.push({ label: ch.label, value: ch.key })
}
options.push({
label: ch.label,
value: ch.key,
disabled: !isDeliverTargetConfigured(ch.key),
})
}
return options
})
@@ -80,6 +113,10 @@ const targetOptions = computed(() => {
const originalJob = ref<Job | null>(null)
onMounted(async () => {
if (Object.keys(settingsStore.platforms || {}).length === 0) {
await settingsStore.fetchSettings()
}
if (props.jobId) {
try {
const job = await getJob(props.jobId)
+135
View File
@@ -0,0 +1,135 @@
// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { defineComponent } from 'vue'
import { flushPromises, mount } from '@vue/test-utils'
const mockMessage = vi.hoisted(() => ({
warning: vi.fn(),
success: vi.fn(),
error: vi.fn(),
}))
const mockSettingsStore = vi.hoisted(() => ({
platforms: {} as Record<string, any>,
fetchSettings: vi.fn(async () => {
mockSettingsStore.platforms = {
telegram: { token: 'telegram-token' },
discord: { token: 'discord-token' },
slack: { token: 'slack-token' },
whatsapp: { enabled: true },
matrix: { token: 'matrix-token' },
weixin: { token: 'weixin-token' },
wecom: { extra: { bot_id: 'wecom-bot' } },
feishu: { extra: { app_id: 'feishu-app' } },
dingtalk: { extra: { client_id: 'dingtalk-client' } },
qqbot: { extra: { app_id: 'qq-app', client_secret: 'qq-secret' } },
}
}),
}))
const mockJobsStore = vi.hoisted(() => ({
createJob: vi.fn(),
updateJob: vi.fn(),
}))
vi.mock('@/stores/hermes/settings', () => ({
useSettingsStore: () => mockSettingsStore,
}))
vi.mock('@/stores/hermes/jobs', () => ({
useJobsStore: () => mockJobsStore,
}))
vi.mock('@/api/hermes/jobs', async () => {
const actual = await vi.importActual<any>('@/api/hermes/jobs')
return {
...actual,
getJob: vi.fn(),
}
})
vi.mock('vue-i18n', () => ({
useI18n: () => ({
t: (key: string) => key,
}),
}))
vi.mock('naive-ui', () => ({
NModal: defineComponent({
template: '<div class="n-modal-stub"><slot /><slot name="footer" /></div>',
}),
NForm: defineComponent({ template: '<form><slot /></form>' }),
NFormItem: defineComponent({ template: '<div><slot /></div>' }),
NInput: defineComponent({
props: { value: { type: String, required: false } },
emits: ['update:value'],
template: '<input class="n-input-stub" :value="value" @input="$emit(\'update:value\', $event.target.value)" />',
}),
NInputNumber: defineComponent({
props: { value: { required: false } },
emits: ['update:value'],
template: '<input class="n-input-number-stub" :value="value" type="number" @input="$emit(\'update:value\', Number($event.target.value))" />',
}),
NSelect: defineComponent({
props: { value: { required: false }, options: { type: Array, default: () => [] } },
emits: ['update:value'],
template: '<select class="n-select-stub"><option v-for="option in options" :key="option.value" :value="option.value" :disabled="option.disabled">{{ option.label }}</option></select>',
}),
NButton: defineComponent({
emits: ['click'],
template: '<button class="n-button-stub" @click.prevent="$emit(\'click\')"><slot /></button>',
}),
useMessage: () => mockMessage,
}))
import JobFormModal from '@/components/hermes/jobs/JobFormModal.vue'
describe('JobFormModal deliver targets', () => {
beforeEach(() => {
vi.clearAllMocks()
mockSettingsStore.platforms = {}
})
it('loads platform settings when the store has not been hydrated', async () => {
mount(JobFormModal, {
props: { jobId: null },
})
await flushPromises()
expect(mockSettingsStore.fetchSettings).toHaveBeenCalledOnce()
})
it('shows every supported platform channel in deliver target options', async () => {
mockSettingsStore.platforms = {
telegram: { token: 'telegram-token' },
whatsapp: { enabled: false },
qqbot: { extra: { app_id: 'qq-app', client_secret: 'qq-secret' } },
}
const wrapper = mount(JobFormModal, {
props: { jobId: null },
})
await flushPromises()
expect(mockSettingsStore.fetchSettings).not.toHaveBeenCalled()
const labels = wrapper.findAll('.n-select-stub')[1].text()
expect(labels).toContain('Telegram')
expect(labels).toContain('Discord')
expect(labels).toContain('Slack')
expect(labels).toContain('WhatsApp')
expect(labels).toContain('Matrix')
expect(labels).toContain('WeChat')
expect(labels).toContain('WeCom')
expect(labels).toContain('Feishu')
expect(labels).toContain('DingTalk')
expect(labels).toContain('QQBot')
const options = wrapper.findAll('.n-select-stub')[1].findAll('option')
const optionByValue = Object.fromEntries(options.map(option => [option.attributes('value'), option]))
expect(optionByValue.telegram.attributes('disabled')).toBeUndefined()
expect(optionByValue.qqbot.attributes('disabled')).toBeUndefined()
expect(optionByValue.discord.attributes('disabled')).toBe('')
expect(optionByValue.whatsapp.attributes('disabled')).toBe('')
})
})