Files
knowledge-governance/web/src/BackupScheduleSection.test.tsx
T
姚章浩 e95a50ca3a fix(backup): 保存后即时反映主机状态;最近一次备份不再重复同一时间戳
- 后端 handleSaveBackupSchedule 改为先下发、再组装投影:此前页面保存成功那几行仍是
  下发前的主机计划,看起来像没生效(需等下一次刷新)
- 前端 runMoment:清单里的 created_at 就是目录戳,同值不再重复显示(原为
  '20261009T060736Z · 20261009T060736Z')
- 前端补 runMoment 用例(171 个 vitest 全绿)

面板实测:保存后立即显示 每天 03:30 / 保留 7 天 / 已一致,cron 条目 30 03 * * *
2026-10-09 14:12:10 +08:00

103 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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')
})
})