fix(ui): сохранять черновики и результат настроек ACME
This commit is contained in:
1 parent
1c407b77de
commit
594e60a1f4
7 files changed
+223
-161
No files matched your search
@@ -0,0 +1,61 @@
|
||||
import { test, expect, type Page } from "@playwright/test"
|
||||
import { certificateState, server } from "./fixtures/certificates"
|
||||
const acme = { directoryUrl: "https://acme.example.test/directory", defaultZoneId: "old-zone", tokenConfigured: true }
|
||||
const directory = (page: Page) => page.getByRole("textbox", { name: "ACME directory URL", exact: true })
|
||||
const token = (page: Page) => page.getByLabel("Cloudflare API token", { exact: true })
|
||||
const save = (page: Page) => page.getByRole("button", { name: "Сохранить настройки", exact: true })
|
||||
const check = (page: Page) => page.getByRole("button", { name: "Проверить Cloudflare", exact: true })
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
||||
await page.route("**/api/**", r => {
|
||||
const path = new URL(r.request().url()).pathname
|
||||
return r.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [server] : path === "/api/certificates/state" ? certificateState() : path === "/api/certificates/renew-settings" ? { enabled: false, intervalSec: 300, renewBeforeDays: 30 } : acme })
|
||||
})
|
||||
})
|
||||
for (const malformed of [false, true]) test("ACME: неизвестные настройки после отказа " + malformed, async ({ page }) => {
|
||||
let fail = true; await page.route("**/api/certificates/acme-settings", r => r.fulfill(fail ? malformed ? { json: {} } : { status: 503, json: { error: "ACME чтение отказало" } } : { json: acme }))
|
||||
await page.goto("/certificates"); await expect(page.getByRole("alert", { name: "Настройки ACME", exact: true })).toBeVisible()
|
||||
await expect(save(page)).toBeDisabled(); await expect(check(page)).toBeDisabled(); await expect(directory(page)).toHaveValue("")
|
||||
await expect(page.getByText("Состояние токена неизвестно.", { exact: true })).toBeVisible()
|
||||
fail = false; await page.getByRole("button", { name: "Перечитать настройки ACME", exact: true }).click(); await expect(directory(page)).toHaveValue(acme.directoryUrl)
|
||||
})
|
||||
for (const kind of ["API", "malformed", "token-unconfirmed"]) test("ACME: отказ сохранения " + kind + " оставляет черновик и токен", async ({ page }) => {
|
||||
await page.route("**/api/certificates/acme-settings", r => r.request().method() === "GET" ? r.fulfill({ json: acme }) : r.fulfill(kind === "API" ? { status: 503, json: { error: "Сохранение ACME отказало" } } : { json: kind === "malformed" ? {} : { ...acme, tokenConfigured: false } }))
|
||||
await page.goto("/certificates"); await expect(directory(page)).toHaveValue(acme.directoryUrl)
|
||||
await directory(page).fill("https://new.example.test/directory"); await token(page).fill("test-draft-token"); await save(page).click()
|
||||
await expect(page.getByRole("alert", { name: "Настройки ACME", exact: true })).toContainText("Результат сохранения не подтверждён")
|
||||
await expect(token(page)).toHaveValue("test-draft-token"); await expect(directory(page)).toHaveValue("https://new.example.test/directory")
|
||||
await expect(page.getByText("Настройки ACME сохранены", { exact: true })).toHaveCount(0)
|
||||
})
|
||||
test("ACME: подтверждённое сохранение очищает только принятый токен", async ({ page }) => {
|
||||
let body: Record<string, unknown> = {}
|
||||
await page.route("**/api/certificates/acme-settings", r => { if (r.request().method() === "GET") return r.fulfill({ json: acme }); body = r.request().postDataJSON(); return r.fulfill({ json: { ...acme, directoryUrl: body.directoryUrl } }) })
|
||||
await page.goto("/certificates"); await expect(directory(page)).toHaveValue(acme.directoryUrl); await token(page).fill("test-draft-token"); await save(page).click()
|
||||
await expect(page.getByText("Настройки ACME сохранены", { exact: true })).toBeVisible(); await expect(token(page)).toHaveValue("")
|
||||
expect(body.cloudflareApiToken).toBe("test-draft-token")
|
||||
})
|
||||
test("ACME: Cloudflare отказ не меняет настройки или токен", async ({ page }) => {
|
||||
await page.route("**/api/certificates/acme-settings/test", r => r.fulfill({ status: 400, json: { ok: false, message: "Cloudflare denied" } }))
|
||||
await page.goto("/certificates"); await expect(directory(page)).toHaveValue(acme.directoryUrl); await token(page).fill("test-draft-token"); await check(page).click()
|
||||
await expect(page.getByRole("alert", { name: "Настройки ACME", exact: true })).toContainText("Cloudflare denied")
|
||||
await expect(token(page)).toHaveValue("test-draft-token"); await expect(page.getByText("Настройки ACME сохранены", { exact: true })).toHaveCount(0)
|
||||
})
|
||||
test("ACME: save и test разделяют single flight", async ({ page }) => {
|
||||
let writes = 0, tests = 0; let release!: () => void; const gate = new Promise<void>(r => release = r)
|
||||
await page.route("**/api/certificates/acme-settings", r => { if (r.request().method() === "PUT") writes++; return r.fulfill({ json: acme }) })
|
||||
await page.route("**/api/certificates/acme-settings/test", async r => { tests++; await gate; await r.fulfill({ json: { ok: true, message: "Cloudflare доступен" } }) })
|
||||
await page.goto("/certificates"); await expect(directory(page)).toHaveValue(acme.directoryUrl); await check(page).click()
|
||||
await expect(save(page)).toBeDisabled(); await expect(check(page)).toBeDisabled(); await expect(token(page)).toBeDisabled(); expect(writes).toBe(0); expect(tests).toBe(1)
|
||||
release(); await expect(page.getByRole("status").filter({ hasText: "Cloudflare доступен" })).toBeVisible()
|
||||
})
|
||||
test("ACME: background read не стирает черновик", async ({ page }) => {
|
||||
let second = false; await page.route("**/api/certificates/acme-settings", r => r.fulfill({ json: { ...acme, directoryUrl: second ? "https://server-new.example.test/directory" : acme.directoryUrl } }))
|
||||
await page.goto("/certificates"); await expect(directory(page)).toHaveValue(acme.directoryUrl); await directory(page).fill("https://draft.example.test/directory"); await token(page).fill("test-draft-token")
|
||||
second = true; await page.getByRole("button", { name: "Перечитать настройки ACME", exact: true }).click(); await expect(directory(page)).toBeEnabled()
|
||||
await expect(directory(page)).toHaveValue("https://draft.example.test/directory"); await expect(token(page)).toHaveValue("test-draft-token")
|
||||
})
|
||||
test("ACME: неверный URL не отправляет mutation", async ({ page }) => {
|
||||
let writes = 0; await page.route("**/api/certificates/acme-settings", r => { if (r.request().method() === "PUT") writes++; return r.fulfill({ json: acme }) })
|
||||
await page.goto("/certificates"); await expect(directory(page)).toHaveValue(acme.directoryUrl); await directory(page).fill("bad"); await save(page).click()
|
||||
await expect(page.getByRole("alert", { name: "Настройки ACME", exact: true })).toContainText("корректный URL"); expect(writes).toBe(0)
|
||||
})
|
||||
@@ -31,3 +31,23 @@ for (const fails of [false, true]) test("Сертификаты: позднее
|
||||
await expect(toggle).toBeChecked(); await expect(page.getByText("СТАРОЕ СОХРАНЕНИЕ ОТКАЗ", { exact: true })).toHaveCount(0)
|
||||
await expect(page.getByText("Автообновление выключено", { exact: true })).toHaveCount(0)
|
||||
})
|
||||
|
||||
for (const fails of [false, true]) test("Сертификаты: позднее сохранение ACME/" + fails, async ({ page }) => {
|
||||
let started = false, completed = false, release!: () => void; const gate = new Promise<void>(r => release = r)
|
||||
await page.route("**/health", r => r.fulfill({ headers, json: { ok: true } }))
|
||||
await page.route("**/api/**", r => {
|
||||
const path = new URL(r.request().url()).pathname
|
||||
return r.fulfill({ headers, json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [server] : path === "/api/certificates/state" ? state() : path === "/api/certificates/renew-settings" ? { enabled: true, intervalSec: 21600, renewBeforeDays: 30 } : { directoryUrl: "https://acme.example.test/directory", tokenConfigured: false } })
|
||||
})
|
||||
await page.route("**/api/certificates/acme-settings", async r => {
|
||||
if (r.request().method() !== "PUT") return r.fallback()
|
||||
started = true; await gate
|
||||
try { await r.fulfill(fails ? { headers, status: 503, json: { error: "СТАРОЕ СОХРАНЕНИЕ ACME ОТКАЗ" } } : { headers, json: { directoryUrl: "https://old.example.test/directory", tokenConfigured: true } }) } catch {} finally { completed = true }
|
||||
})
|
||||
await page.goto("/certificates"); const directory = page.getByRole("textbox", { name: "ACME directory URL", exact: true })
|
||||
await expect(directory).toHaveValue("https://acme.example.test/directory"); await directory.fill("https://old.example.test/directory"); await page.getByRole("button", { name: "Сохранить настройки", exact: true }).click(); await expect.poll(() => started).toBe(true)
|
||||
await page.goto("/settings"); const input = page.getByPlaceholder("http://localhost:8000"); await input.fill("http://localhost:8101"); await input.press("Enter")
|
||||
await page.goto("/certificates"); await expect(directory).toHaveValue("https://acme.example.test/directory"); release(); await expect.poll(() => completed).toBe(true)
|
||||
await expect(directory).toHaveValue("https://acme.example.test/directory"); await expect(page.getByText("СТАРОЕ СОХРАНЕНИЕ ACME ОТКАЗ", { exact: true })).toHaveCount(0)
|
||||
await expect(page.getByText("Настройки ACME сохранены", { exact: true })).toHaveCount(0)
|
||||
})
|
||||
Reference in new issue
Block a user