Files
privatae-bastion/client-go/schedulers_test.go
T
Privatae e4c9b69b46
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s
bastion 0.53.2
2026-10-20 04:42:14 +00:00

138 lines
6.6 KiB
Go

package main
import (
"strings"
"testing"
)
func TestClassifySchedCommand(t *testing.T) {
cases := []struct {
cmd string
sig string
auto bool
ok bool
}{
{"claude -p 'summarise inbox' >> /var/log/x", "claude-code", false, true},
{"cd /srv/app && /usr/local/bin/aider --yes", "aider", false, true},
{"HOME=/root PATH=/usr/bin:/bin python3 /opt/bot/run.py", "", false, false},
{"HOME=/root nohup node /opt/x/node_modules/@openai/codex/bin/codex.js", "codex", false, true},
{"/bin/sh -c \"cd /home/u && codex exec nightly\"", "codex", false, true},
{"timeout 3600 /usr/bin/goose run", "goose", false, true},
{"flock -n /tmp/l xdotool key ctrl+r", "xdotool", true, true},
{"/usr/bin/python3 -m playwright codegen", "playwright", true, true},
{"echo claude-code is great | mail -s hi root", "", false, false},
{"/usr/bin/backup.sh --full", "", false, false},
{"", "", false, false},
{`"C:\Program Files\nodejs\node.exe" C:\Users\u\AppData\npm\@anthropic-ai\claude-code\cli.js -p go`, "claude-code", false, true},
{`powershell.exe -NoProfile -Command "& 'C:\tools\chromedriver.exe' --port=9515"`, "chromedriver", true, true},
}
for _, c := range cases {
sig, auto, ok := classifySchedCommand(c.cmd)
if ok != c.ok || sig != c.sig || auto != c.auto {
t.Errorf("classifySchedCommand(%q) = (%q,%v,%v), want (%q,%v,%v)", c.cmd, sig, auto, ok, c.sig, c.auto, c.ok)
}
}
}
func TestSplitShellSegments(t *testing.T) {
got := splitShellSegments(`cd /x && echo "a && b" ; claude -p 'y | z' || true`)
want := []string{"cd /x", `echo "a && b"`, `claude -p 'y | z'`, "true"}
if strings.Join(got, "|") != strings.Join(want, "|") {
t.Errorf("splitShellSegments = %q, want %q", got, want)
}
}
func TestParseCrontab(t *testing.T) {
system := `# /etc/crontab: system-wide crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin
17 * * * * root cd / && run-parts --report /etc/cron.hourly
0 2 * * * deploy claude -p "nightly report" >> /var/log/nightly.log 2>&1
@reboot root /usr/local/bin/xdotool key super
`
got := parseCrontab(system, "/etc/crontab", "", true)
if len(got) != 3 {
t.Fatalf("system crontab: %d entries, want 3: %+v", len(got), got)
}
if got[1].User != "deploy" || got[1].Schedule != "0 2 * * *" || !strings.HasPrefix(got[1].Command, "claude -p") || got[1].Name != "/etc/crontab:5" {
t.Errorf("system entry = %+v", got[1])
}
if got[2].Schedule != "@reboot" || got[2].User != "root" || got[2].Command != "/usr/local/bin/xdotool key super" {
t.Errorf("@reboot entry = %+v", got[2])
}
user := `MAILTO=""
*/5 * * * * /home/u/bin/sync.sh
30 3 * * 1-5 cd /home/u/proj && aider --message "tidy" --yes
`
got = parseCrontab(user, "/var/spool/cron/crontabs/u", "u", false)
if len(got) != 2 || got[1].User != "u" || got[1].Schedule != "30 3 * * 1-5" || !strings.Contains(got[1].Command, "aider") {
t.Errorf("user crontab = %+v", got)
}
matched, scan := matchScheduled(append(parseCrontab(system, "/etc/crontab", "", true), got...), []string{"cron"}, true)
if scan.Entries != 5 || len(matched) != 3 {
t.Errorf("matchScheduled: %d matched of %d, want 3 of 5: %+v", len(matched), scan.Entries, matched)
}
for _, m := range matched {
if m.Signature == "" {
t.Errorf("matched entry without signature: %+v", m)
}
}
}
func TestParseSystemdTimersAndExecStart(t *testing.T) {
out := `Sat 2026-09-06 23:00:00 UTC 3h left Fri 2026-09-05 23:00:00 UTC 21h ago nightly-agent.timer nightly-agent.service
Sun 2026-09-07 00:00:00 UTC 4h left Sat 2026-09-06 00:00:00 UTC 20h ago logrotate.timer logrotate.service
- - stale.timer stale.service
`
m := parseSystemdTimers(out)
if m["nightly-agent.service"] != "nightly-agent.timer" || m["logrotate.service"] != "logrotate.timer" || m["stale.service"] != "stale.timer" {
t.Errorf("parseSystemdTimers = %v", m)
}
es := `{ path=/usr/local/bin/claude ; argv[]=/usr/local/bin/claude -p nightly --output-format json ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }`
if got := parseExecStart(es); got != "/usr/local/bin/claude -p nightly --output-format json" {
t.Errorf("parseExecStart = %q", got)
}
if got := parseExecStart("/usr/bin/true"); got != "/usr/bin/true" {
t.Errorf("parseExecStart(plain) = %q", got)
}
}
func TestParseLaunchdPlistJSON(t *testing.T) {
j := `{"Label":"com.example.nightly","ProgramArguments":["/usr/local/bin/python3","/Users/u/bot/run.py"],"StartCalendarInterval":{"Hour":2,"Minute":0},"RunAtLoad":false}`
e, ok := parseLaunchdPlistJSON([]byte(j), "/Users/u/Library/LaunchAgents/com.example.nightly.plist")
if !ok || e.Name != "com.example.nightly" || e.Command != "/usr/local/bin/python3 /Users/u/bot/run.py" || e.Schedule != "calendar" {
t.Errorf("launchd calendar = %+v %v", e, ok)
}
j = `{"Label":"com.example.keep","Program":"/opt/agent/goose","KeepAlive":true,"RunAtLoad":true,"StartInterval":300}`
e, ok = parseLaunchdPlistJSON([]byte(j), "x.plist")
if !ok || e.Command != "/opt/agent/goose" || e.Schedule != "every 300s, at load, keep alive" {
t.Errorf("launchd keepalive = %+v %v", e, ok)
}
if _, ok := parseLaunchdPlistJSON([]byte(`{"Label":"noprog"}`), "y.plist"); ok {
t.Error("plist without a program must be skipped")
}
if _, ok := parseLaunchdPlistJSON([]byte(`not json`), "z.plist"); ok {
t.Error("bad json must be skipped")
}
}
func TestParseWinTasksJSON(t *testing.T) {
many := `[{"n":"NightlyAgent","p":"\\Custom\\","u":"DESKTOP\\user","s":"Ready","a":["\"C:\\Program Files\\nodejs\\node.exe\" C:\\Users\\user\\AppData\\Roaming\\npm\\node_modules\\@anthropic-ai\\claude-code\\cli.js -p go"],"t":["MSFT_TaskDailyTrigger"]},{"n":"OneDrive Update","p":"\\","u":"S-1-5-18","s":"Ready","a":["\"%localappdata%\\Microsoft\\OneDrive\\OneDriveStandaloneUpdater.exe\""],"t":["MSFT_TaskTimeTrigger","MSFT_TaskLogonTrigger"]}]`
got := parseWinTasksJSON([]byte(many))
if len(got) != 2 || got[0].Name != `\Custom\NightlyAgent` || got[0].Schedule != "DailyTrigger" || got[0].User != `DESKTOP\user` {
t.Errorf("many = %+v", got)
}
matched, scan := matchScheduled(got, []string{"task-scheduler"}, true)
if scan.Entries != 2 || len(matched) != 1 || matched[0].Signature != "claude-code" {
t.Errorf("matched = %+v scan = %+v", matched, scan)
}
one := `{"n":"Solo","p":"\\","u":"u","s":"Ready","a":["\"chromedriver.exe\" --port=9515"],"t":[]}`
got = parseWinTasksJSON([]byte(one))
if len(got) != 1 || got[0].Command != "\"chromedriver.exe\" --port=9515" {
t.Errorf("one = %+v", got)
}
if got := parseWinTasksJSON([]byte(" ")); got != nil {
t.Errorf("empty = %+v", got)
}
}