mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-10-15 21:35:31 +00:00
* feat: 支持 v2 插件、原生扩展与签名发布 * fix: 修复测试 * feat: 增强宿主 SDK、运行观测与制品维护能力 * feat: 增强文件传输、配置观测与本地开发能力 * feat: 支持 SSE 实时事件流并完善开发脚手架 * feat: 完善长连接鉴权复核与关闭回收 * feat: 增强生命周期与任务关闭可靠性 * feat: 收紧健康检查契约并补齐前端与真实网络验收 * feat: 增加SDK溯源与离线更新工具并验证跨版本兼容 * feat: 增加持久化业务示例与受控 Python 交付 * feat: 增加统一运行诊断与多worker状态观测 * feat: 增加固定目标发布等待与自动验收 * fix: 修复多环境插件部署前缀与代理配置不一致 * fix: 修复测试
115 lines
4.4 KiB
JavaScript
115 lines
4.4 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { compileScript, parse } from '@vue/compiler-sfc'
|
|
import { computed, reactive } from 'vue'
|
|
import { getPluginReleaseView } from '../../src/utils/pluginReleaseFormatter.js'
|
|
|
|
const plugin = reactive({
|
|
source: 'artifact',
|
|
enabled: '0',
|
|
release: {
|
|
status: 'pending_restart',
|
|
prepareStatus: 'prepared',
|
|
healthyWorkers: 0,
|
|
disabledWorkers: 0,
|
|
liveWorkers: 0,
|
|
expectedWorkers: 2,
|
|
missingWorkers: 2,
|
|
mismatchWorkers: 0,
|
|
staleWorkers: 0,
|
|
failedWorkers: 0,
|
|
restartRequired: true,
|
|
},
|
|
})
|
|
const view = computed(() => getPluginReleaseView(plugin))
|
|
assert.equal(view.value.label, '等待重启')
|
|
assert.equal(view.value.workerSummary, '已就绪 0 · 在线 0 / 预期 2')
|
|
assert.equal(view.value.preparationLabel, '准备完成')
|
|
assert.equal(view.value.restartRequired, true)
|
|
|
|
plugin.release.status = 'partial'
|
|
plugin.release.liveWorkers = 2
|
|
plugin.release.healthyWorkers = 1
|
|
plugin.release.mismatchWorkers = 1
|
|
assert.equal(view.value.label, '部分生效')
|
|
assert.equal(view.value.tagType, 'warning')
|
|
assert.equal(view.value.counts.mismatch, '1')
|
|
assert.match(view.value.workerSummary, /^已就绪 1/)
|
|
|
|
plugin.release.status = 'active'
|
|
plugin.release.healthyWorkers = 2
|
|
plugin.release.restartRequired = false
|
|
assert.equal(view.value.label, '已生效')
|
|
assert.equal(view.value.tagType, 'success')
|
|
assert.equal(view.value.restartRequired, false)
|
|
|
|
plugin.enabled = '1'
|
|
plugin.release.status = 'disabled'
|
|
plugin.release.healthyWorkers = 0
|
|
plugin.release.disabledWorkers = 2
|
|
assert.equal(view.value.label, '已停用')
|
|
assert.equal(view.value.workerSummary, '已停用 2 · 在线 2 / 预期 2')
|
|
|
|
plugin.release.verificationError = '公钥已撤销'
|
|
plugin.release.lastError = 'older maintenance failure'
|
|
assert.equal(view.value.label, '验证失败', 'signature failure must not retain a healthy label')
|
|
assert.equal(view.value.tagType, 'danger')
|
|
assert.equal(view.value.error, '公钥已撤销')
|
|
|
|
const missing = getPluginReleaseView({ source: 'artifact' })
|
|
assert.equal(missing.label, '状态未知')
|
|
assert.equal(missing.unavailable, true)
|
|
assert.equal(missing.workerSummary, '已就绪 未知 · 在线 未知 / 预期 未知')
|
|
assert.equal(missing.restartRequired, false)
|
|
for (const value of [undefined, null, -1, NaN, '2', true]) {
|
|
assert.equal(getPluginReleaseView({ release: { healthyWorkers: value } }).counts.healthy, '未知')
|
|
}
|
|
assert.equal(getPluginReleaseView({ release: { status: 'no_target' } }).label, '未选择目标')
|
|
assert.equal(
|
|
getPluginReleaseView({ release: { status: 'failed', prepareStatus: 'failed' } }).preparationLabel,
|
|
'准备失败'
|
|
)
|
|
assert.equal(getPluginReleaseView({ release: { status: 'new_server_state' } }).label, '状态未知')
|
|
assert.equal(
|
|
getPluginReleaseView({ release: { lastError: 'failed preparation' } }).error,
|
|
'failed preparation'
|
|
)
|
|
|
|
// 执行页面的真实操作限制:即使旧版接口响应缺少能力元数据,制品记录也应保持只读。
|
|
const pageSource = readFileSync(
|
|
new URL('../../src/views/system/plugin/index.vue', import.meta.url),
|
|
'utf8'
|
|
)
|
|
const { descriptor } = parse(pageSource)
|
|
const compiled = compileScript(descriptor, { id: 'plugin-release-read-only-test' })
|
|
const gateNames = [
|
|
'canSelectForBatch',
|
|
'canInstall',
|
|
'canUpgrade',
|
|
'canUninstall',
|
|
'isOrphanPlugin',
|
|
'isOperationBlocked',
|
|
'isCapabilityOperationBlocked',
|
|
]
|
|
const gateSource = compiled.scriptSetupAst
|
|
.filter(
|
|
(statement) => statement.type === 'FunctionDeclaration' && gateNames.includes(statement.id.name)
|
|
)
|
|
.map((statement) => descriptor.scriptSetup.content.slice(statement.start, statement.end))
|
|
.join('\n')
|
|
const gates = new Function(`${gateSource}\nreturn {${gateNames.join(',')}}`)()
|
|
for (const status of ['discovered', 'installed', 'pending_upgrade', 'error']) {
|
|
const artifact = { source: 'artifact', enabled: '0', installedVersion: '1.0.0', status }
|
|
assert.equal(gates.canSelectForBatch(artifact), false)
|
|
assert.equal(gates.canInstall(artifact), false)
|
|
assert.equal(gates.canUpgrade(artifact), false)
|
|
assert.equal(gates.canUninstall(artifact), false)
|
|
for (const operation of ['install', 'upgrade', 'enable', 'disable', 'uninstall']) {
|
|
assert.equal(gates.isOperationBlocked(artifact, operation), true)
|
|
}
|
|
}
|
|
const local = { source: 'local', status: 'discovered' }
|
|
assert.equal(gates.canSelectForBatch(local), true)
|
|
assert.equal(gates.canInstall(local), true)
|
|
assert.equal(gates.isOperationBlocked(local, 'install'), false)
|