feat(updates): check Gitea automatically and deploy on owner confirmation
Add owner-only update controls, a read-only SSH checker and independent systemd deployment services. Build before maintenance; back up code and database; restore on failed deployment. Preserve runtime configuration and gate business writes during the switch.
This commit is contained in:
1 parent
ed8dc01ed9
commit
f3ad539998
28 files changed
+1346
-1
No files matched your search
@@ -0,0 +1,55 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { computed, ref } from 'vue'
|
||||
import SystemUpdates from '@/views/settings/SystemUpdates.vue'
|
||||
import type { UpdateStatus } from '@/services/systemUpdates'
|
||||
const state = ref<UpdateStatus>({ configured: true, can_manage: true })
|
||||
const apply = vi.fn().mockResolvedValue(undefined)
|
||||
const check = vi.fn().mockResolvedValue(undefined)
|
||||
vi.mock('@/composables/useSystemUpdates', () => ({ useSystemUpdates: () => ({
|
||||
state, busy: computed(() => state.value.update?.status === 'building'), error: ref(''),
|
||||
refresh: vi.fn().mockResolvedValue(undefined), check, apply,
|
||||
}) }))
|
||||
vi.mock('@/layouts/DashboardLayout.vue', () => ({ default: { template: '<div><slot /></div>' } }))
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
state.value = { configured: true, can_manage: true, current: { revision: 'a'.repeat(40), subject: 'current' },
|
||||
latest: { revision: 'b'.repeat(40), subject: 'new' }, check_interval_minutes: 30, update_available: true,
|
||||
update: { status: 'idle', message: '' } }
|
||||
})
|
||||
|
||||
describe('SystemUpdates', () => {
|
||||
it('requires explicit confirmation before starting an update', async () => {
|
||||
const wrapper = mount(SystemUpdates)
|
||||
await flushPromises()
|
||||
const button = wrapper.findAll('button').find(b => b.text() === '更新到此版本')!
|
||||
await button.trigger('click')
|
||||
expect(wrapper.find('[role=dialog]').exists()).toBe(true)
|
||||
expect(apply).not.toHaveBeenCalled()
|
||||
await wrapper.findAll('button').find(b => b.text() === '备份并开始更新')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(apply).toHaveBeenCalledOnce()
|
||||
wrapper.unmount()
|
||||
})
|
||||
it('disables installation when there is no newer version or an update is running', async () => {
|
||||
state.value.update_available = false
|
||||
const wrapper = mount(SystemUpdates)
|
||||
await flushPromises()
|
||||
expect(wrapper.find('.update-actions button').attributes('disabled')).toBeDefined()
|
||||
state.value.update_available = true
|
||||
state.value.update = { status: 'building', message: '正在构建' }
|
||||
await flushPromises()
|
||||
expect(wrapper.find('.update-actions button').attributes('disabled')).toBeDefined()
|
||||
expect(wrapper.text()).toContain('正在构建')
|
||||
wrapper.unmount()
|
||||
})
|
||||
it('shows a failed update and recovery result', async () => {
|
||||
state.value.update = { status: 'rolled_back', message: '已恢复原版本', error: '构建失败' }
|
||||
const wrapper = mount(SystemUpdates)
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('已恢复原版本')
|
||||
expect(wrapper.text()).toContain('构建失败')
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user