package main // The journal verb's window, priority and match (the operator's direction 2026-10-07): every value one // word of journalctl's argv, nothing read as an option, the cap kept, a secret never answered — and // what has failed on the seat. import ( "fmt" "strings" "testing" ) // journalOf is a manager whose journalctl answers the given lines and whose unit has the given // Environment=, keeping each call. func journalOf(lines []string, env string, calls *[]call) *Manager { return operator(fake(func(c call) Ran { if contains(c.args, "journalctl") || c.cmd == "journalctl" { return Ran{Stdout: strings.Join(lines, "\n") + "\n"} } if contains(c.args, "--property=Environment") { return Ran{Stdout: env + "\n"} } return Ran{} }, calls)) } func journalArgs(t *testing.T, calls []call) []string { t.Helper() for _, c := range calls { if c.cmd == "sudo" && len(c.args) > 1 && c.args[1] == "journalctl" { return c.args[2:] } if c.cmd == "journalctl" { return c.args } } t.Fatalf("journalctl was not run: %+v", calls) return nil } func TestAWindowAndAPriorityAreEachOneWordOfJournalctl(t *testing.T) { var calls []call m := journalOf([]string{"a"}, "", &calls) _, err := m.Journal(System, "mail.service", JournalQuery{Lines: 50, Since: "-30min", Until: "2026-10-07T10:00:00.5Z", Priority: "warning"}) if err != nil { t.Fatal(err) } got := strings.Join(journalArgs(t, calls), " ") want := "--no-pager --output=short-iso --unit=mail.service --since=-30min --until=@1791367201 --priority=4 --lines=50" if got != want { t.Fatalf("journalctl was given\n %s\nnot\n %s", got, want) } if calls[0].cmd != "sudo" || calls[0].args[0] != "-n" { t.Fatalf("the system journal was not read escalated: %+v", calls[0]) } } func TestTheFormsAWindowIsReadIn(t *testing.T) { for _, ok := range []string{"-30min", "-2h", "+1h", "-1h30min", "2h ago", "30min ago", "yesterday", "now", "today", "2026-10-07T09:30:00Z", "2026-10-07T09:30:00+02:00", "2026-10-07", "2026-10-07 09:30", "2026-10-07 09:30:15"} { if _, err := (JournalQuery{Since: ok}).valid(); err != nil { t.Errorf("%q refused: %v", ok, err) } } for _, bad := range []string{"--user", "-u", "-D/etc", "--directory=/", "-30min --merge", "-30min;id", "$(id)", "-x", "--", "1h; rm", "2026-10-07T09:30:00", "last week", "-30min\n--merge"} { if _, err := (JournalQuery{Since: bad}).valid(); err == nil { t.Errorf("since %q accepted", bad) } if _, err := (JournalQuery{Until: bad}).valid(); err == nil { t.Errorf("until %q accepted", bad) } } if _, err := (JournalQuery{Since: "2026-10-07T10:00:00Z", Until: "2026-10-07T09:00:00Z"}).valid(); err == nil { t.Error("a window ending before it starts was accepted") } } func TestPriorityIsANumberOrAName(t *testing.T) { for in, want := range map[string]string{"0": "0", "7": "7", "err": "3", "ERROR": "3", "warning": "4", "debug": "7", "emerg": "0"} { q, err := (JournalQuery{Priority: in}).valid() if err != nil || q.Priority != want { t.Errorf("%q: %q %v", in, q.Priority, err) } } for _, bad := range []string{"8", "-1", "--user", "3..5", "loud", "33"} { if _, err := (JournalQuery{Priority: bad}).valid(); err == nil { t.Errorf("priority %q accepted", bad) } } } func TestNothingRunsWhenAValueIsRefused(t *testing.T) { var calls []call m := journalOf(nil, "", &calls) for _, q := range []JournalQuery{{Since: "--merge"}, {Until: "-D/"}, {Priority: "--user"}, {Lines: -1}, {Match: "a\nb"}, {Match: strings.Repeat("x", LongestMatch+1)}} { if _, err := m.Journal(System, "x.service", q); err == nil { t.Errorf("%+v accepted", q) } } if _, err := m.Journal(System, "--merge", JournalQuery{}); err == nil { t.Error("an option was accepted as a unit") } if len(calls) != 0 { t.Fatalf("something ran: %+v", calls) } } func TestTheCapIsKeptAndTheDefaultIsAHundred(t *testing.T) { for in, want := range map[int]int{0: DefaultLines, 1: 1, 2000: 2000, 2001: 2000, 1 << 30: 2000} { q, err := (JournalQuery{Lines: in}).valid() if err != nil || q.Lines != want { t.Errorf("%d: %d %v", in, q.Lines, err) } } // What the seat's schema carries is text, and an agent may send a number: both read the same. for _, a := range []map[string]any{{"lines": "250"}, {"lines": float64(250)}} { q, err := journalQuery(a) if err != nil || q.Lines != 250 { t.Errorf("%v: %+v %v", a, q, err) } } for _, a := range []map[string]any{{"lines": "many"}, {"lines": 2.5}, {"since": float64(3)}, {"match": []any{"x"}}} { if _, err := journalQuery(a); err == nil { t.Errorf("%v accepted", a) } } } func TestAMatchIsAFixedStringOverTheWindowsLastLines(t *testing.T) { var calls []call lines := []string{"one (a.b)", "two a.b", "three axb", "four (a.b)"} r, err := journalOf(lines, "", &calls).Journal(System, "x.service", JournalQuery{Lines: 1, Match: "(a.b)"}) if err != nil { t.Fatal(err) } if got := r["lines"].([]string); len(got) != 1 || got[0] != "four (a.b)" { t.Fatalf("%v", got) } if r["scanned"] != 4 || r["count"] != 1 || r["match"] != "(a.b)" { t.Fatalf("%v", r) } if a := journalArgs(t, calls); a[len(a)-1] != fmt.Sprintf("--lines=%d", MatchScan) { t.Fatalf("a match was not looked for over a scan: %v", a) } for _, a := range journalArgs(t, calls) { if strings.Contains(a, "a.b") || strings.HasPrefix(a, "--grep") { t.Fatalf("the match reached journalctl: %v", a) } } } func TestASecretTheUnitPrintedIsNeverAnsweredNorFoundByAMatch(t *testing.T) { env := `HOME=/var/lib/x "DB_PASSWORD=hunter2hunter2" DATABASE_URL=postgres://app:s3cr3tpw@db/app PORT=5432` lines := []string{ "starting with password hunter2hunter2", "connecting to postgres://app:s3cr3tpw@db/app", "proxy at https://u:otherpassword@example.test/", "ran: tool --password=flagsecret1 --token tokensecret2", "API_TOKEN=abcdefghij set", "nothing secret here", } r, err := journalOf(lines, env, nil).Journal(System, "x.service", JournalQuery{}) if err != nil { t.Fatal(err) } all := strings.Join(r["lines"].([]string), "\n") for _, secret := range []string{"hunter2hunter2", "s3cr3tpw", "otherpassword", "flagsecret1", "tokensecret2", "abcdefghij"} { if strings.Contains(all, secret) { t.Errorf("%s was answered:\n%s", secret, all) } } if !strings.Contains(all, "[redacted: DB_PASSWORD]") || !strings.Contains(all, "nothing secret here") { t.Fatalf("%s", all) } if r["redacted"] == nil || r["leak"] == nil { t.Fatalf("the redaction was not said: %v", r) } found, _ := journalOf(lines, env, nil).Journal(System, "x.service", JournalQuery{Match: "hunter2"}) if found["count"] != 0 { t.Fatalf("a match found a secret: %v", found) } } func TestAnUnreadableEnvironmentStillRedactsByShapeAndSaysSo(t *testing.T) { m := operator(fake(func(c call) Ran { if contains(c.args, "--property=Environment") { return Ran{Status: 1, Stderr: "Failed to get properties: Access denied\n"} } return Ran{Stdout: "postgres://app:s3cr3tpw@db/app\n"} }, nil)) r, err := m.Journal(System, "x.service", JournalQuery{}) if err != nil { t.Fatal(err) } if strings.Contains(strings.Join(r["lines"].([]string), ""), "s3cr3tpw") || r["redaction"] == nil { t.Fatalf("%v", r) } } func TestALongLineIsCut(t *testing.T) { r, _ := journalOf([]string{strings.Repeat("y", LongestLine+10)}, "", nil).Journal(System, "x.service", JournalQuery{}) if l := r["lines"].([]string)[0]; len(l) > LongestLine+len("…") { t.Fatalf("a line of %d bytes", len(l)) } } func TestTheEnvironmentPropertyIsReadAsWords(t *testing.T) { got := environment(`A=1 "B=two words" 'C=x\'y' D=`) if strings.Join(got, "|") != "A=1|B=two words|C=x'y|D=" { t.Fatalf("%q", got) } } func TestFailedIsOnTheSeatAndNarrowsToAScope(t *testing.T) { var calls []call m := operator(fake(func(call) Ran { return Ran{Stdout: list} }, &calls)) var failed func(map[string]any) (any, error) for _, tool := range tools(m) { if tool.Name == seat+".failed" { failed = tool.Run } if tool.Name == "systemd_failed" { t.Fatal("the module still serves its own systemd_failed beside the seat's verb") } } if failed == nil { t.Fatal("the seat's failed is not served") } both, err := failed(map[string]any{}) if err != nil || len(both.(map[string]any)) != 2 { t.Fatalf("%v %v", both, err) } calls = nil one, err := failed(map[string]any{"scope": "system"}) if err != nil || len(one.(map[string]any)) != 1 || len(calls) != 1 || contains(calls[0].args, "--user") { t.Fatalf("%v %v %+v", one, err, calls) } if _, err := failed(map[string]any{"scope": "everything"}); err == nil { t.Fatal("a scope that is none was accepted") } }