完善证书历史版本恢复页面与测试
补充历史版本弹窗布局、移动端适配、稳定 history_id 恢复测试,并修复恢复成功后任务详情入口调用。前端全量测试和生产构建已通过。
This commit is contained in:
parent
bd5f31946c
commit
b75773b85e
@ -729,6 +729,45 @@ code {
|
||||
width: min(100%, 680px);
|
||||
}
|
||||
|
||||
.certificate-history-modal {
|
||||
width: min(100%, 940px);
|
||||
}
|
||||
|
||||
.certificate-history-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.certificate-history-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid #dfe8f2;
|
||||
border-radius: 14px;
|
||||
background: #fbfdff;
|
||||
}
|
||||
|
||||
.certificate-history-row > div {
|
||||
display: grid;
|
||||
gap: 9px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.certificate-history-row .domain-meta {
|
||||
row-gap: 7px;
|
||||
}
|
||||
|
||||
.certificate-history-row .domain-meta span:last-child {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.certificate-history-row .secondary-button {
|
||||
flex: 0 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.import-result {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
@ -1045,4 +1084,13 @@ code {
|
||||
.settings-web-controls {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.certificate-history-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.certificate-history-row .secondary-button,
|
||||
.certificate-history-row > .muted {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@ -904,7 +904,7 @@ export function useDashboard() {
|
||||
notice.value = `证书历史版本恢复任务已提交:${result.task.id},恢复完成后请另行确认远端部署状态`;
|
||||
showCertificateVersions.value = false;
|
||||
await loadData();
|
||||
await openTaskDetails(result.task);
|
||||
await taskMonitor.openTaskDetails(result.task);
|
||||
} catch (err) {
|
||||
versionsError.value = err.message || "提交证书历史版本恢复失败";
|
||||
} finally {
|
||||
|
||||
@ -99,6 +99,91 @@ test("证书导入使用组合式状态导出,避免点击时缺失处理函
|
||||
assert.ok(script.includes(` ${name},`));
|
||||
}
|
||||
});
|
||||
|
||||
test("证书历史版本使用稳定标识并提供独立恢复弹窗样式", async () => {
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const template = await readFile(new URL("./Dashboard.vue", import.meta.url), "utf8");
|
||||
const script = await readFile(new URL("./Dashboard.js", import.meta.url), "utf8");
|
||||
const stylesheet = await readFile(new URL("./Dashboard.css", import.meta.url), "utf8");
|
||||
for (const name of [
|
||||
"showCertificateVersions",
|
||||
"openCertificateVersions",
|
||||
"closeCertificateVersions",
|
||||
"restoreCertificateVersion",
|
||||
]) {
|
||||
assert.ok(template.includes(` ${name},`));
|
||||
}
|
||||
assert.match(script, /certificate\/versions/);
|
||||
assert.match(template, /version\.history_id/);
|
||||
assert.match(template, /certificate-history-modal/);
|
||||
for (const selector of [
|
||||
".certificate-history-modal",
|
||||
".certificate-history-list",
|
||||
".certificate-history-row",
|
||||
]) {
|
||||
assert.match(stylesheet, new RegExp(`${selector.replaceAll(".", "\\.")}\\s*\\{`));
|
||||
}
|
||||
});
|
||||
|
||||
test("证书历史版本读取列表并阻止恢复当前版本", async (t) => {
|
||||
const requests = [];
|
||||
const domain = { id: "domain-history", name: "example.test" };
|
||||
const versions = [
|
||||
{ history_id: "history-current", current: true, version: 2 },
|
||||
{ history_id: "history-old", current: false, version: 1 },
|
||||
];
|
||||
const { state } = await mountComposable(t, useDashboard, async (path, options = {}) => {
|
||||
requests.push({ path, options });
|
||||
if (path === `/api/v1/domains/${domain.id}/certificate/versions`) {
|
||||
return Response.json({ items: versions });
|
||||
}
|
||||
return Response.json({ items: [] });
|
||||
});
|
||||
await state.openCertificateVersions(domain);
|
||||
assert.equal(state.showCertificateVersions.value, true);
|
||||
assert.deepEqual(state.certificateVersions.value, versions);
|
||||
assert.equal(
|
||||
requests.some(({ path }) => path === `/api/v1/domains/${domain.id}/certificate/versions`),
|
||||
true,
|
||||
);
|
||||
window.confirm = () => assert.fail("当前版本不应触发恢复确认");
|
||||
await state.restoreCertificateVersion(versions[0]);
|
||||
assert.equal(
|
||||
requests.some(({ path }) => path.includes("/restore")),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("证书历史版本恢复使用 history_id、幂等键并打开任务详情", async (t) => {
|
||||
const requests = [];
|
||||
const domain = { id: "domain-history", name: "example.test" };
|
||||
const task = { id: "task-history", task_type: "certificate_restore", status: "queued" };
|
||||
const version = { history_id: "history-old", current: false, version: 1 };
|
||||
const { state } = await mountComposable(t, useDashboard, async (path, options = {}) => {
|
||||
requests.push({ path, options });
|
||||
if (path.endsWith("/certificate/versions")) return Response.json({ items: [version] });
|
||||
if (path.endsWith("/restore")) return Response.json({ accepted: true, task }, { status: 202 });
|
||||
if (path === `/api/v1/tasks/${task.id}`) return Response.json(task);
|
||||
if (path.endsWith(`/api/v1/tasks/${task.id}/attempts`)) return Response.json({ items: [] });
|
||||
if (path.endsWith(`/api/v1/tasks/${task.id}/deployment-results`)) return Response.json({ items: [] });
|
||||
return Response.json({ items: [] });
|
||||
});
|
||||
window.confirm = () => true;
|
||||
await state.openCertificateVersions(domain);
|
||||
await state.restoreCertificateVersion(version);
|
||||
const restoreRequest = requests.find(({ path }) => path.endsWith("/restore"));
|
||||
assert.ok(restoreRequest);
|
||||
assert.equal(
|
||||
restoreRequest.path,
|
||||
`/api/v1/domains/${domain.id}/certificate/versions/${version.history_id}/restore`,
|
||||
);
|
||||
assert.match(restoreRequest.options.headers["Idempotency-Key"], /^certificate-restore-domain-history-/);
|
||||
assert.equal(state.showCertificateVersions.value, false);
|
||||
assert.equal(state.showTaskDetails.value, true);
|
||||
assert.equal(state.taskDetails.value.id, task.id);
|
||||
assert.match(state.notice.value, /恢复完成后请另行确认远端部署状态/);
|
||||
});
|
||||
|
||||
test("完整页面逐个渲染六个板块,每次只有一个内容标题", async (t) => {
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const { parse, compileTemplate } = await import("@vue/compiler-sfc");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user