Files
siftlode/frontend/src/lib/auditText.dom.test.ts
T
peter fad656988a fix(activity): runs keep their true outcome and spend, the page reaches every kept row, a closing dialog leaves the server log alone
Backend
- A run's outcome write sits outside the job's own try: a database blip after a run that
  succeeded no longer records it as failed (the row waits for the next start's sweep).
- An interrupted run keeps what it spent (the sweep sums its quota rows, as finish does).
- The Scheduler's ad-hoc row says running while any backfill runs, not only the newest.
- recent_runs reads each job's last runs off its index instead of ranking the whole table.
- An interval change is stored with its audit row in one transaction, then rescheduled.
- One API instance per database is the stated contract (.env.example, jobruns).
- Turning full history on for a channel already complete records no run.
- Migration 0109 round-trips interrupted and ad-hoc runs; the run words' DB CHECKs are
  test-held to the enums; the account filter is one helper for the four activity reads.
- An audit row says how many of its request's log lines are still kept.

Frontend
- An "All" period reaches every row still kept (runs and admin actions outlive 90 days).
- Back/Forward is followed in place by the server log: a closing dialog no longer turns
  Live off; a pivot still opens a fresh log.
- Runs moved over from the audit log read their one-liner in the reader's language.
- Filter choices or accounts that failed to load say so beside the filters, with a retry.
- An old server-log link keeps its account, text and range; a remembered retired page
  opens its tab.
- A request whose lines are gone says so instead of linking to an empty list.
- The log lines and the history rows share one row frame, and the server log one list
  frame with the other tabs; an empty list is announced; each day's quota is readable.
2026-10-18 15:38:32 +02:00

142 lines
4.5 KiB
TypeScript

// @vitest-environment jsdom
//
// An audit row whose server summary is a fixed English line ("log_level = DEBUG", "rss_poll → 15
// min/run", "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 { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import i18n, { setLanguage } from "../i18n";
import type { AuditRow } from "./api";
import { auditText } from "./auditText";
afterEach(() => {
setLanguage("en");
});
const row = (over: Partial<AuditRow>): AuditRow => ({
id: 1,
created_at: null,
actor_id: null,
action: "sync_pause",
target_type: null,
target_id: null,
summary: null,
before: null,
after: null,
request_id: null,
log_lines: 0,
...over,
});
function details(r: AuditRow): string {
setLanguage("hu");
const t = i18n.getFixedT("hu");
const { head, sub } = auditText(t, r, "hu");
return [head, sub].filter(Boolean).join(" | ");
}
describe("an audit row's details", () => {
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("tell a secret's reset from its update, and name a choice", () => {
const reset = row({
action: "config_reset",
target_type: "config",
target_id: "tmdb_api_key",
summary: "tmdb_api_key → default",
});
expect(details(reset)).toMatch(/ \| titok visszaállítva/);
const choice = row({
action: "config_set",
target_type: "config",
target_id: "plex_transcode_hw",
before: { value: "auto" },
after: { value: "on" },
});
expect(details(choice)).toMatch(/ \| Automatikus → GPU$/);
});
it("name an interval, a batch and a count in the reader's language", () => {
expect(
details(
row({
action: "scheduler_interval",
target_type: "job",
target_id: "rss_poll",
summary: "rss_poll → 15 min/run",
after: { interval_minutes: 15 },
}),
),
).toBe("RSS-lekérdezés (új feltöltések) | 15 percenként");
expect(
details(row({ action: "maintenance_tune", after: { revalidate_batch: 50000 } })),
).toMatch(/^Futásonként újraellenőrzött videók \| 50\s000$/);
expect(
details(
row({ action: "audit_clear", summary: "Cleared 12 audit entries", after: { cleared: 12 } }),
),
).not.toContain("Cleared");
});
it("add nothing to a pause, whose action already says it", () => {
expect(details(row({ action: "sync_pause", summary: "Background sync paused" }))).toBe("");
});
it("say a token revoke and a token's file work in the reader's language", () => {
const revoke = details(
row({
action: "api_token_revoke",
target_type: "api_token",
target_id: "11",
summary: "revoked with every session of the account",
}),
);
expect(revoke).not.toMatch(/revoked/);
expect(revoke).toMatch(/munkamenet/);
const upload = details(
row({ action: "file_upload", target_type: "file", summary: "api token sfl_Q3qXFfcv…" }),
);
expect(upload).not.toMatch(/api token/);
expect(upload).toContain("sfl_Q3qXFfcv…");
// The line read above is the one the server writes.
const apitokens = readFileSync(
resolve(__dirname, "../../../backend/app/apitokens.py"),
"utf-8",
);
expect(apitokens).toContain('f"api token {self.token_prefix}…"');
});
it("keep a summary that is data", () => {
expect(details(row({ action: "invite_add", summary: "someone@example.com" }))).toBe(
"someone@example.com",
);
});
});