修复系统设置提交只读字段导致保存失败并补充契约回归
This commit is contained in:
parent
aa66335c05
commit
3e0e3afd9f
@ -1,4 +1,5 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||
import { settingsPayload } from "./SettingsPayload.js";
|
||||
|
||||
const defaultDNSVerificationTimeoutSeconds = 60;
|
||||
const aliyunDNSVerificationTimeoutSeconds = 6;
|
||||
@ -880,18 +881,7 @@ export function useDashboard() {
|
||||
}
|
||||
settingsSaving.value = true;
|
||||
try {
|
||||
const current = settingsForm.value;
|
||||
const payload = {
|
||||
...current,
|
||||
dns: {
|
||||
provider: current.dns.provider,
|
||||
},
|
||||
};
|
||||
if (current.dns.credential_ref && current.dns.verification_token) {
|
||||
payload.dns.credential_ref = current.dns.credential_ref;
|
||||
payload.dns.verification_token = current.dns.verification_token;
|
||||
payload.dns.test_domain = current.dns.test_domain;
|
||||
}
|
||||
const payload = settingsPayload(settingsForm.value);
|
||||
const response = await fetchAdmin("/api/v1/settings", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
||||
@ -176,3 +176,84 @@ test("settings.savedDnsCredentialsRemainOptionalUntilChanged", async (t) => {
|
||||
await state.testDNS();
|
||||
assert.equal(state.dnsReadyToSave.value, true);
|
||||
});
|
||||
|
||||
|
||||
test("settingsPayloadContract", async (t) => {
|
||||
const { settingsPayload } = await import("./SettingsPayload.js");
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const contract = await readFile(
|
||||
new URL("../../../../backend/internal/httpapi/settings_api.go", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const { state } = await setup(t, () => verifiedResponse());
|
||||
const form = JSON.parse(JSON.stringify(state.settingsForm.value));
|
||||
form.notification.http_credential_configured = true;
|
||||
form.notification.smtp_credential_configured = true;
|
||||
form.notification.http_credentials = { token: "test-only" };
|
||||
form.notification.recipients = ["owner@example.test"];
|
||||
form.notification.retry_count = 0;
|
||||
form.dns.credentials = { access_key_id: "not-for-settings" };
|
||||
form.dns.credential_ref = "secret://test/new";
|
||||
form.dns.verification_token = "verified-token";
|
||||
form.dns.test_domain = "example.test";
|
||||
const payload = settingsPayload(form);
|
||||
for (const [section, type] of Object.entries({
|
||||
server: "Server", web: "Web", paths: "Paths", acme: "ACME",
|
||||
dns: "DNS", notification: "Notification",
|
||||
})) {
|
||||
const body = contract.split(`type settings${type}Request struct {`)[1].split("\n}")[0];
|
||||
const allowed = [...body.matchAll(/json:"([^"]+)"/g)].map((match) => match[1]);
|
||||
for (const key of Object.keys(payload[section])) {
|
||||
assert.ok(allowed.includes(key), `${section}.${key}`);
|
||||
}
|
||||
}
|
||||
assert.equal("smtp_credential_configured" in payload.notification, false);
|
||||
assert.equal("http_credential_configured" in payload.notification, false);
|
||||
assert.equal("credentials" in payload.dns, false);
|
||||
assert.equal(payload.dns.verification_token, "verified-token");
|
||||
assert.deepEqual(payload.notification.http_credentials, { token: "test-only" });
|
||||
assert.deepEqual(payload.notification.recipients, ["owner@example.test"]);
|
||||
assert.equal(payload.notification.retry_count, 0);
|
||||
assert.equal(payload.web.enabled, true);
|
||||
assert.equal(payload.notification.smtp_enabled, false);
|
||||
assert.equal(form.notification.smtp_credential_configured, true);
|
||||
});
|
||||
|
||||
test("settingsReadEditSaveContract", async (t) => {
|
||||
const saved = {
|
||||
server: { host: "127.0.0.1", port: 8443, https_mode: "proxy", proxy_https_port: 443 },
|
||||
web: { enabled: true, hostname: "example.test", public_ip: "" },
|
||||
paths: { certificate: "/var/lib/scert/certificates" },
|
||||
acme: { renewal_threshold_days: 6 },
|
||||
dns: { provider: "aliyun", credential_configured: true, verification_state: "verified" },
|
||||
notification: {
|
||||
smtp_credential_configured: true, http_credential_configured: true,
|
||||
recipients: ["owner@example.test"], retry_intervals_secs: [30, 120, 600],
|
||||
},
|
||||
response_only: true,
|
||||
};
|
||||
let submitted;
|
||||
const { state } = await mountComposable(t, useDashboard, async (path, options = {}) => {
|
||||
if (path.endsWith("/settings")) {
|
||||
if (options.method === "PUT") {
|
||||
submitted = JSON.parse(options.body);
|
||||
return Response.json({ settings: saved });
|
||||
}
|
||||
return Response.json(saved);
|
||||
}
|
||||
return Response.json({ items: [] });
|
||||
});
|
||||
state.openSettings();
|
||||
state.settingsForm.value.acme.renewal_threshold_days = 91;
|
||||
assert.equal(state.dnsReadyToSave.value, true);
|
||||
await state.saveSettings();
|
||||
assert.equal(state.settingsError.value, "");
|
||||
assert.equal(state.showSettings.value, false);
|
||||
assert.equal(submitted.acme.renewal_threshold_days, 91);
|
||||
assert.deepEqual(submitted.dns, { provider: "aliyun" });
|
||||
assert.equal("smtp_credential_configured" in submitted.notification, false);
|
||||
assert.equal("http_credential_configured" in submitted.notification, false);
|
||||
assert.equal("response_only" in submitted, false);
|
||||
assert.deepEqual(submitted.notification.recipients, ["owner@example.test"]);
|
||||
assert.deepEqual(submitted.notification.retry_intervals_secs, [30, 120, 600]);
|
||||
});
|
||||
|
||||
40
frontend/src/views/Dashboard/SettingsPayload.js
Normal file
40
frontend/src/views/Dashboard/SettingsPayload.js
Normal file
@ -0,0 +1,40 @@
|
||||
const requestFields = {
|
||||
server: ["host", "port", "https_mode", "proxy_https_port"],
|
||||
web: ["enabled", "hostname", "public_ip"],
|
||||
paths: ["certificate"],
|
||||
acme: [
|
||||
"ca", "environment", "account_email", "renewal_check_interval_days",
|
||||
"renewal_near_expiry_days", "renewal_threshold_days",
|
||||
"renewal_urgent_threshold_days", "renewal_max_retries",
|
||||
"renewal_retry_interval_mins", "propagation_timeout_secs",
|
||||
"propagation_polling_interval_secs",
|
||||
],
|
||||
notification: [
|
||||
"http_enabled", "smtp_enabled", "smtp_host", "smtp_port",
|
||||
"smtp_username", "smtp_password", "smtp_mode", "smtp_allow_plaintext_auth",
|
||||
"http_credentials", "provider", "endpoint", "http_path", "http_login_path",
|
||||
"http_auth_mode", "event_mode", "include_deployment_details",
|
||||
"include_certificate_serial", "include_management_url", "sender_name",
|
||||
"sender_email", "recipients", "timeout_secs", "retry_count",
|
||||
"retry_intervals_secs",
|
||||
],
|
||||
};
|
||||
|
||||
export function settingsPayload(current) {
|
||||
const payload = {};
|
||||
for (const [section, fields] of Object.entries(requestFields)) {
|
||||
const source = current[section] || {};
|
||||
payload[section] = Object.fromEntries(
|
||||
fields.filter((field) => source[field] !== undefined)
|
||||
.map((field) => [field, source[field]]),
|
||||
);
|
||||
}
|
||||
const dns = current.dns || {};
|
||||
payload.dns = { provider: dns.provider };
|
||||
if (dns.credential_ref && dns.verification_token) {
|
||||
payload.dns.credential_ref = dns.credential_ref;
|
||||
payload.dns.verification_token = dns.verification_token;
|
||||
payload.dns.test_domain = dns.test_domain;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user