feat(audit): job runs and config changes read in the reader's language (E30 S5)

The audit log showed the server's fixed English lines ("rss_poll: ok — new=5, failed=0",
"log_level = DEBUG", "Background sync paused") to a Hungarian reader. The details are rebuilt from the
row's data on the client: a job run names the job and its result as the inbox does, a config change
names the field and its values (on/off, grouped counts, "default"), a pause/resume adds nothing the
action does not say. A failed run keeps its error text (diagnostic). parseSummary reads enrich's bare
count ("50") back as `enriched`, as the server's `_result_data` names it — the audit and old notices
keep only the one-liner. No backend change; old rows render too.
This commit is contained in:
2026-09-30 01:08:29 +02:00
parent 95b94a1d72
commit 2819049133
7 changed files with 159 additions and 8 deletions
@@ -0,0 +1,94 @@
// @vitest-environment jsdom
//
// E30: an audit row whose server summary is a fixed English line ("rss_poll: ok — new=5, failed=0",
// "log_level = DEBUG", "Background sync paused") reads in the reader's language, rebuilt from the row's
// data through the REAL catalog. A row whose summary is data (an e-mail, a URL) keeps it.
import { afterEach, describe, expect, it } from "vitest";
import { render } from "@testing-library/react";
import i18n, { setLanguage } from "../i18n";
import type { AuditRow } from "../lib/api";
import { auditColumns } from "./auditColumns";
afterEach(() => setLanguage("en"));
const row = (over: Partial<AuditRow>): AuditRow => ({
id: 1,
created_at: null,
actor: null,
action: "job_run",
target_type: null,
target_id: null,
summary: null,
before: null,
after: null,
...over,
});
function details(r: AuditRow): string {
setLanguage("hu");
const t = i18n.getFixedT("hu");
const col = auditColumns(t, "hu", []).find((c) => c.key === "details")!;
const { container } = render(<>{col.render!(r)}</>);
return [...container.querySelectorAll(".wrap-break-word")].map((e) => e.textContent).join(" | ");
}
describe("an audit row's details", () => {
it("name a job run and its result in the reader's language", () => {
const r = row({
target_type: "job",
target_id: "rss_poll",
summary: "rss_poll: ok — new=5, failed=0",
after: { status: "ok", summary: "new=5, failed=0" },
});
expect(details(r)).toBe("RSS-lekérdezés (új feltöltések) kész | új videó: 5 · sikertelen: 0");
});
it("keep a failed run's error text, which is diagnostic", () => {
const r = row({
target_type: "job",
target_id: "enrich",
after: { status: "error", summary: "HTTP 403: quotaExceeded" },
});
expect(details(r)).toBe(
"Nem sikerült lefuttatni: Adatdúsítás + élő frissítés | HTTP 403: quotaExceeded",
);
});
it("name a config change's field and values", () => {
const set = row({
action: "config_set",
target_type: "config",
target_id: "plex_enabled",
summary: "plex_enabled = True",
before: { value: false },
after: { value: true },
});
expect(details(set)).toMatch(/ \| Ki → Be$/);
expect(details(set)).not.toContain("plex_enabled");
const reset = row({
action: "config_reset",
target_type: "config",
target_id: "download_upload_max_bytes",
summary: "download_upload_max_bytes → default",
before: { value: 5242880 },
});
expect(details(reset)).toMatch(/ \| 5\s242\s880 → alapérték$/);
const secret = row({
action: "config_set",
target_type: "config",
target_id: "youtube_api_key",
summary: "youtube_api_key = (secret updated)",
});
expect(details(secret)).toMatch(/ \| titok frissítve/);
});
it("add nothing to a pause, whose action already says it", () => {
expect(details(row({ action: "sync_pause", summary: "Background sync paused" }))).toBe("");
});
it("keep a summary that is data", () => {
expect(details(row({ action: "invite_add", summary: "someone@example.com" }))).toBe(
"someone@example.com",
);
});
});