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.
142 lines
4.5 KiB
TypeScript
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",
|
|
);
|
|
});
|
|
});
|