AES-256-GCM secret storage keyed by MASTER_KEY, SMTP settings with test mail (lettre), persisted job runs with log and status, JobRunner with per-kind concurrency guard, 6-field cron schedules with defaults, scheduler loop. Settings and Jobs pages in the UI. FAKE_HOST mode for dev machines. Tests: 30 application, 8 infrastructure, 23 API, 16 Vitest, 6 Playwright. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
import { mount, flushPromises } from '@vue/test-utils'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import JobsPage from './JobsPage.vue'
|
|
import { useAuthStore } from '../stores/auth'
|
|
import { api } from '../api/client'
|
|
|
|
vi.mock('../api/client', () => ({
|
|
api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn() },
|
|
ApiError: class extends Error {},
|
|
}))
|
|
|
|
const run = {
|
|
id: 'r1',
|
|
kind: 'package_refresh',
|
|
status: 'success',
|
|
started_at: '2026-09-02T10:00:00Z',
|
|
finished_at: '2026-09-02T10:00:05Z',
|
|
log: 'hello\n',
|
|
triggered_by: 'admin@example.com',
|
|
}
|
|
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia())
|
|
vi.mocked(api.get).mockImplementation(async (path: string) => {
|
|
if (path.startsWith('/api/jobs/kinds')) return ['package_refresh']
|
|
return [run]
|
|
})
|
|
})
|
|
|
|
describe('JobsPage', () => {
|
|
it('lists runs and lets an admin run a job', async () => {
|
|
const auth = useAuthStore()
|
|
auth.user = { id: '1', email: 'a', display_name: 'A', role: 'admin', is_active: true }
|
|
vi.mocked(api.post).mockResolvedValue({ ...run, id: 'r2', status: 'running' })
|
|
const w = mount(JobsPage)
|
|
await flushPromises()
|
|
expect(w.text()).toContain('package_refresh')
|
|
expect(w.text()).toContain('success')
|
|
await w.find('select[name=kind]').setValue('package_refresh')
|
|
await w.find('button[name=run]').trigger('click')
|
|
await flushPromises()
|
|
expect(api.post).toHaveBeenCalledWith('/api/jobs/run', { kind: 'package_refresh' })
|
|
})
|
|
|
|
it('hides the run control for non-admins', async () => {
|
|
const auth = useAuthStore()
|
|
auth.user = { id: '1', email: 'a', display_name: 'A', role: 'user', is_active: true }
|
|
const w = mount(JobsPage)
|
|
await flushPromises()
|
|
expect(w.find('button[name=run]').exists()).toBe(false)
|
|
})
|
|
})
|