Files
sub2api/frontend/src/api/__tests__/admin.system.rollback.spec.ts
T
shaw 1c2e6503c6 feat: 版本徽章新增近3个历史版本在线回退与手动回退指引
后端:
- UpdateService 新增 ListRollbackVersions/RollbackToVersion,回退目标强制
  限定为比当前版本更旧的近3个正式版本(排除当前/更新/预发布/草稿),
  越界返回 400 ROLLBACK_VERSION_NOT_ALLOWED;下载复用既有 HTTPS 域名
  白名单 + checksum 校验 + 原子换二进制管线
- GitHubReleaseClient 新增 FetchRecentReleases(releases 列表 API)
- 新增 GET /admin/system/rollback-versions;POST /admin/system/rollback
  支持可选 {"version"} body,无 body 保持原 .backup 本地回退行为不变
- 端点均在 /admin 组 adminAuth(IsAdmin+TokenVersion)保护内

前端:
- VersionBadge「已是最新版本」状态下新增版本回退面板:radio 卡片选择
  近3个版本,手动回退方式按部署形态 tab 切换(脚本部署 curl 命令 /
  Docker 镜像 tag 指引),支持一键复制;release 构建提供一键回退并
  复用重启流程;源码构建仅提示不支持在线回退
- 下拉根元素重置 whitespace-normal,修复 sidebar-brand 的
  white-space:nowrap 继承导致的长文本溢出裁切

测试:服务层过滤/排序/上限/非法目标拒绝、handler 双模式与鉴权错误映射、
GitHub client 列表拉取、前端 API 请求体行为共 16 个新增用例
2026-07-09 22:30:04 +08:00

56 lines
1.5 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const { get, post } = vi.hoisted(() => ({
get: vi.fn(),
post: vi.fn(),
}))
vi.mock('../client', () => ({
apiClient: {
get,
post,
},
}))
import { getRollbackVersions, rollback, type RollbackVersionInfo } from '@/api/admin/system'
describe('admin system rollback API', () => {
beforeEach(() => {
get.mockReset()
post.mockReset()
})
it('getRollbackVersions fetches the rollback version list', async () => {
const versions: RollbackVersionInfo[] = [
{
version: '0.1.146',
published_at: '2026-07-07T00:00:00Z',
html_url: 'https://github.com/Wei-Shaw/sub2api/releases/tag/v0.1.146'
}
]
get.mockResolvedValue({ data: { versions } })
const result = await getRollbackVersions()
expect(get).toHaveBeenCalledWith('/admin/system/rollback-versions')
expect(result.versions).toEqual(versions)
})
it('rollback posts the target version in the request body', async () => {
post.mockResolvedValue({ data: { message: 'ok', need_restart: true } })
const result = await rollback('0.1.146')
expect(post).toHaveBeenCalledWith('/admin/system/rollback', { version: '0.1.146' })
expect(result.need_restart).toBe(true)
})
it('rollback without a version posts no body (legacy backup rollback)', async () => {
post.mockResolvedValue({ data: { message: 'ok', need_restart: true } })
await rollback()
expect(post).toHaveBeenCalledWith('/admin/system/rollback', undefined)
})
})