e95a50ca3a
- 后端 handleSaveBackupSchedule 改为先下发、再组装投影:此前页面保存成功那几行仍是 下发前的主机计划,看起来像没生效(需等下一次刷新) - 前端 runMoment:清单里的 created_at 就是目录戳,同值不再重复显示(原为 '20261009T060736Z · 20261009T060736Z') - 前端补 runMoment 用例(171 个 vitest 全绿) 面板实测:保存后立即显示 每天 03:30 / 保留 7 天 / 已一致,cron 条目 30 03 * * *
103 lines
5.6 KiB
TypeScript
103 lines
5.6 KiB
TypeScript
import { renderToStaticMarkup } from 'react-dom/server'
|
||
import { describe, expect, it, vi } from 'vitest'
|
||
|
||
import { BackupScheduleView, backupDaysValid, backupMonthsValid, backupTimeValid, formatBackupBytes, runMoment } from './BackupScheduleSection'
|
||
import type { BackupSchedule } from './backupSchedule'
|
||
|
||
const base: BackupSchedule = {
|
||
enabled: true, schedule_time: '03:30', retention_days: 7, retention_months: 3,
|
||
updated_by: 'yaozhanghao', updated_at: '2026-10-09T12:00:00Z',
|
||
last_applied_at: '2026-10-09T12:00:00Z', last_applied_summary: '每天 03:30 · 保留 7 天 · 每月归档 3 份',
|
||
host_configured: true, host_target: 'weknora-test', host_reachable: true, host_error: '',
|
||
host_schedule_enabled: true, host_schedule_time: '03:30', host_keep_days: 7, host_keep_months: 3,
|
||
host_cron_lines: ['30 03 * * * root /bin/bash /opt/weknora-test/logical-backup.sh >> /srv/kb-backups/logs/logical-backup.log 2>&1'],
|
||
host_cron_service: 'active', host_script_present: true,
|
||
last_run_stamp: '20261009T052909Z', last_run_created_at: '2026-10-09T05:29:09Z', last_run_duration_seconds: 33,
|
||
last_run_total_bytes: 778_502_144, last_run_databases: ['weknora_test', 'dobotai_prod'],
|
||
last_run_failed_databases: [], last_run_error: '', in_sync: true,
|
||
applied: true, apply_error: '', running: false, running_started_at: '', run_last_error: '',
|
||
}
|
||
|
||
const view = (overrides: Partial<BackupSchedule> = {}, extra: Record<string, unknown> = {}) => {
|
||
const schedule = { ...base, ...overrides }
|
||
return renderToStaticMarkup(<BackupScheduleView
|
||
schedule={schedule}
|
||
isAdmin={(extra.isAdmin as boolean) ?? true}
|
||
loading={false} probing={false} busy={(extra.busy as 'save' | 'apply' | 'run' | null) ?? null}
|
||
error={(extra.error as string | null) ?? null} message={(extra.message as string) ?? ''}
|
||
enabledDraft={(extra.enabledDraft as boolean) ?? schedule.enabled}
|
||
timeDraft={(extra.timeDraft as string) ?? schedule.schedule_time}
|
||
daysDraft={(extra.daysDraft as string) ?? String(schedule.retention_days)}
|
||
monthsDraft={(extra.monthsDraft as string) ?? String(schedule.retention_months)}
|
||
onEnabledChange={vi.fn()} onTimeChange={vi.fn()} onDaysChange={vi.fn()} onMonthsChange={vi.fn()}
|
||
onSave={vi.fn()} onApply={vi.fn()} onRunNow={vi.fn()} onReload={vi.fn()} />)
|
||
}
|
||
|
||
describe('BackupScheduleView', () => {
|
||
it('管理员看到定时时间/储存天数与下发按钮,并显示主机上生效的计划与最近一次结果', () => {
|
||
const html = view()
|
||
for (const text of ['存储与备份', '目标库所在主机', '定时时间', '储存天数', '每月归档保留',
|
||
'保存并下发到主机', '重新下发当前设置', '立即备份一次', 'weknora-test · 已连接',
|
||
'每天 03:30 · 保留 7 天 · 每月 3 份', '已一致', '20261009T052909Z', '33 秒', '742.4 MiB',
|
||
'weknora_test、dobotai_prod']) expect(html).toContain(text)
|
||
expect(html).toContain('type="time"')
|
||
expect(html).toContain('value="03:30"')
|
||
})
|
||
|
||
it('非管理员只读:没有保存/下发/立即备份按钮,只给当前设置', () => {
|
||
const html = view({}, { isAdmin: false })
|
||
expect(html).toContain('定时备份设置仅管理员可修改')
|
||
expect(html).not.toContain('保存并下发到主机')
|
||
expect(html).not.toContain('立即备份一次')
|
||
})
|
||
|
||
it('未接线:显式说明缺少主机凭据,下发与立即备份不可点', () => {
|
||
const html = view({ host_configured: false, host_reachable: false, host_error: '目标主机凭据未挂载(BACKUP_HOST_SSH_CONFIG / BACKUP_HOST_TARGET)', in_sync: false })
|
||
expect(html).toContain('未接线(缺少主机凭据)')
|
||
expect(html).toContain('待下发')
|
||
expect(html).toContain('目标主机凭据未挂载')
|
||
expect(html).toContain('disabled')
|
||
})
|
||
|
||
it('待下发(主机上的计划与设置不一致)时提示', () => {
|
||
const html = view({ host_schedule_time: '05:00', in_sync: false })
|
||
expect(html).toContain('待下发')
|
||
expect(html).toContain('每天 05:00')
|
||
})
|
||
|
||
it('在途备份:按钮变"备份进行中…"并给出开始时间', () => {
|
||
const html = view({ running: true, running_started_at: '2026-10-09T12:30:00Z' })
|
||
expect(html).toContain('备份进行中…')
|
||
expect(html).toContain('正在备份')
|
||
})
|
||
|
||
it('非法输入就地拦截,且保存按钮在未改动时不可点', () => {
|
||
const html = view({}, { timeDraft: '25:99', daysDraft: '0' })
|
||
expect(html).toContain('定时时间需为 HH:MM(00:00~23:59),储存天数 1~365,每月归档 0~24。')
|
||
expect(html).toContain('disabled')
|
||
expect(backupTimeValid('03:30')).toBe(true)
|
||
expect(backupTimeValid('24:00')).toBe(false)
|
||
expect(backupDaysValid(0)).toBe(false)
|
||
expect(backupDaysValid(365)).toBe(true)
|
||
expect(backupMonthsValid(0)).toBe(true)
|
||
expect(backupMonthsValid(25)).toBe(false)
|
||
})
|
||
|
||
it('展示失败的库与最近一次读取错误', () => {
|
||
const html = view({ last_run_failed_databases: ['rdsadmin'], last_run_error: '清单无法解析' })
|
||
expect(html).toContain('rdsadmin')
|
||
expect(html).toContain('清单无法解析')
|
||
})
|
||
|
||
it('最近一次备份不重复显示同一个时间戳', () => {
|
||
expect(runMoment({ last_run_stamp: '20261009T060736Z', last_run_created_at: '20261009T060736Z' })).toBe('')
|
||
expect(runMoment({ last_run_stamp: '20261009T060736Z', last_run_created_at: '' })).toBe('')
|
||
expect(runMoment({ last_run_stamp: '20261009T060736Z', last_run_created_at: '2026-10-09T06:07:36Z' })).not.toBe('')
|
||
})
|
||
|
||
it('体积格式化:MiB/GiB', () => {
|
||
expect(formatBackupBytes(0)).toBe('—')
|
||
expect(formatBackupBytes(1024 * 1024)).toBe('1.0 MiB')
|
||
expect(formatBackupBytes(2 * 1024 * 1024 * 1024)).toBe('2.00 GiB')
|
||
})
|
||
}) |