fix(webui): encode automation update values
This commit is contained in:
@@ -17,6 +17,7 @@ import time
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from urllib.parse import unquote
|
||||
|
||||
from loguru import logger
|
||||
from websockets.http11 import Request as WsRequest
|
||||
@@ -782,7 +783,10 @@ def _automation_values_from_request(request: WsRequest) -> dict[str, Any] | None
|
||||
try:
|
||||
values = json.loads(raw)
|
||||
except Exception:
|
||||
return None
|
||||
try:
|
||||
values = json.loads(unquote(raw))
|
||||
except Exception:
|
||||
return None
|
||||
return values if isinstance(values, dict) else None
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from urllib.parse import urlencode
|
||||
from urllib.parse import quote, urlencode
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
@@ -942,6 +942,26 @@ async def test_webui_automations_route_lists_all_jobs_and_allows_user_actions(
|
||||
assert by_id[user_job.id]["schedule"]["expr"] == "0 9 * * *"
|
||||
assert by_id[user_job.id]["schedule"]["tz"] == "UTC"
|
||||
|
||||
unicode_update = await _http_get(
|
||||
f"{base_url}/api/webui/automations/update?id={user_job.id}",
|
||||
headers={
|
||||
**auth,
|
||||
"X-Nanobot-Automation-Values": quote(
|
||||
json.dumps(
|
||||
{
|
||||
"name": "每日测验",
|
||||
"message": "问今日测验",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
safe="",
|
||||
),
|
||||
},
|
||||
)
|
||||
assert unicode_update.status_code == 200
|
||||
assert cron.get_job(user_job.id).name == "每日测验"
|
||||
assert cron.get_job(user_job.id).payload.message == "问今日测验"
|
||||
|
||||
malformed_update = await _http_get(
|
||||
f"{base_url}/api/webui/automations/update?id={user_job.id}",
|
||||
headers={
|
||||
@@ -950,7 +970,7 @@ async def test_webui_automations_route_lists_all_jobs_and_allows_user_actions(
|
||||
},
|
||||
)
|
||||
assert malformed_update.status_code == 400
|
||||
assert cron.get_job(user_job.id).payload.message == "Ask the daily quiz"
|
||||
assert cron.get_job(user_job.id).payload.message == "问今日测验"
|
||||
|
||||
invalid_cron_update = await _http_get(
|
||||
f"{base_url}/api/webui/automations/update?id={user_job.id}",
|
||||
|
||||
@@ -89,6 +89,10 @@ function mcpValuesHeader(values: Record<string, unknown>): HeadersInit | undefin
|
||||
return { "X-Nanobot-MCP-Values": JSON.stringify(payload) };
|
||||
}
|
||||
|
||||
function automationValuesHeader(values: AutomationUpdatePayload): HeadersInit {
|
||||
return { "X-Nanobot-Automation-Values": encodeURIComponent(JSON.stringify(values)) };
|
||||
}
|
||||
|
||||
function splitKey(key: string): { channel: string; chatId: string } {
|
||||
const idx = key.indexOf(":");
|
||||
if (idx === -1) return { channel: "", chatId: key };
|
||||
@@ -226,9 +230,7 @@ export async function updateAutomation(
|
||||
`${base}/api/webui/automations/update?${query}`,
|
||||
token,
|
||||
{
|
||||
headers: {
|
||||
"X-Nanobot-Automation-Values": JSON.stringify(values),
|
||||
},
|
||||
headers: automationValuesHeader(values),
|
||||
},
|
||||
API_READ_TIMEOUT_MS,
|
||||
);
|
||||
|
||||
@@ -125,25 +125,24 @@ describe("webui API helpers", () => {
|
||||
});
|
||||
|
||||
it("serializes workspace automation updates", async () => {
|
||||
await updateAutomation("tok", "job 1/2", {
|
||||
name: "Daily quiz",
|
||||
message: "Ask the quiz",
|
||||
const values = {
|
||||
name: "每日测验",
|
||||
message: "Ask 今日 quiz",
|
||||
schedule: { kind: "cron", expr: "0 9 * * *", tz: "Asia/Shanghai" },
|
||||
});
|
||||
} as const;
|
||||
await updateAutomation("tok", "job 1/2", values);
|
||||
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
"/api/webui/automations/update?id=job+1%2F2",
|
||||
expect.objectContaining({
|
||||
headers: {
|
||||
Authorization: "Bearer tok",
|
||||
"X-Nanobot-Automation-Values": JSON.stringify({
|
||||
name: "Daily quiz",
|
||||
message: "Ask the quiz",
|
||||
schedule: { kind: "cron", expr: "0 9 * * *", tz: "Asia/Shanghai" },
|
||||
}),
|
||||
"X-Nanobot-Automation-Values": encodeURIComponent(JSON.stringify(values)),
|
||||
},
|
||||
}),
|
||||
);
|
||||
const header = vi.mocked(fetch).mock.calls[0][1]?.headers as Record<string, string>;
|
||||
expect(header["X-Nanobot-Automation-Values"]).not.toContain("每日");
|
||||
});
|
||||
|
||||
it("fetches the WebUI skill summary", async () => {
|
||||
|
||||
@@ -518,7 +518,7 @@ describe("App layout", () => {
|
||||
);
|
||||
expect(updateCall).toBeTruthy();
|
||||
const headers = updateCall?.[1]?.headers as Record<string, string>;
|
||||
expect(JSON.parse(headers["X-Nanobot-Automation-Values"])).toEqual({
|
||||
expect(JSON.parse(decodeURIComponent(headers["X-Nanobot-Automation-Values"]))).toEqual({
|
||||
name: "Past one-shot",
|
||||
message: "Updated one-shot message",
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user