Files
mesh-catalog/modules/systemd/cmd/systemd-tools/journal_test.go
T
jochen 66106d93ac
mesh/delivery-group group feat/journal-window-on-the-seat delivering: 0 of 2 delivered
mesh/merge-gate pass: builds systemd → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery held for a person: its group feat/journal-window-on-the-seat's composed check did not pass for the heads that merged; a person decides that…
systemd: the journal verb reads a window, and failed is the seat's verb
An incident is read for the minutes it happened in (the operator's direction
2026-10-07): journal takes since, until, priority and a fixed-string match.
Every value is one word of journalctl's argv, held to the forms journalctl
reads, so nothing reaches a shell or is read as an option under sudo. What
the unit printed of a secret is redacted, as docker_logs does, before the
match is applied, so a match cannot find one.

systemd_failed becomes the seat's failed, with an optional scope. Needs the
controller's seat with these verbs (mesh-controller, same branch): an older
controller refuses a claim serving a verb its seat does not promise.
2026-10-07 19:15:20 +02:00

244 lines
8.5 KiB
Go

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")
}
}