package e2e import ( "encoding/json" "os" "path/filepath" "testing" ) func statusRun(t *testing.T, env *syncEnv, jsonFlag bool) (string, error) { t.Helper() args := []string{"status", "--config", env.config} if jsonFlag { args = append(args, "--json") } cmd := agentLibCmd(args...) cmd.Env = append(os.Environ(), "HOME="+env.home, "USERPROFILE="+env.home, "AGENT_LIB_STATE_DIR="+env.stateDir, ) out, err := cmd.CombinedOutput() return string(out), err } func TestStatusInSync(t *testing.T) { h := newHarness(t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) env.writeConfig(t, env.workRepo, "") syncOnce(t, env) out, err := statusRun(t, env, false) if err != nil { t.Fatalf("in-sync status must exit 0: %v\n%s", err, out) } for _, want := range []string{"in sync", "deployed revision", "repository revision", "agent-lib"} { if !contains(out, want) { t.Errorf("status output missing %q:\n%s", want, out) } } } func TestStatusDriftNonZero(t *testing.T) { h := newHarness(t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) env.writeConfig(t, env.workRepo, "") syncOnce(t, env) os.WriteFile(filepath.Join(env.home, ".agents/skills/own-skill/SKILL.md"), []byte("---\nname: Own Skill\n---\n# my edits\n"), 0o644) p := filepath.Join(env.workRepo, "skills/own-skill/SKILL.md") os.WriteFile(p, []byte("---\nname: Own Skill\ndescription: v2\n---\n# Upstream v2\n"), 0o644) commitWorkRepo(t, h, env.workRepo, "change own-skill") out, err := statusRun(t, env, false) if err == nil { t.Fatalf("blocked-update drift must exit non-zero:\n%s", out) } for _, want := range []string{"skipped", "own-skill", "locally modified", "drift"} { if !contains(out, want) { t.Errorf("status missing %q:\n%s", want, out) } } jsonOut, err := statusRun(t, env, true) if err == nil { t.Fatalf("json status must keep the drift exit code:\n%s", jsonOut) } var report struct { InSync bool `json:"in_sync"` Drift bool `json:"problematic_drift"` Skipped []struct { Item string `json:"item"` Reason string `json:"reason"` } `json:"skipped_items"` Pending []struct { Item string `json:"item"` } `json:"pending_changes"` } if err := json.Unmarshal([]byte(jsonOut), &report); err != nil { t.Fatalf("status --json not parseable: %v\n%s", err, jsonOut) } if !report.Drift || report.InSync { t.Errorf("json flags wrong: %+v", report) } if len(report.Skipped) != 1 || report.Skipped[0].Item != "skills/own-skill" { t.Errorf("skipped item missing from json: %+v", report.Skipped) } } func TestStatusUnrecognizedButNoDrift(t *testing.T) { h := newHarness(t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) env.writeConfig(t, env.workRepo, "") syncOnce(t, env) mine := filepath.Join(env.home, ".agents/skills/my-own-thing") os.MkdirAll(mine, 0o755) os.WriteFile(filepath.Join(mine, "SKILL.md"), []byte("---\nname: Mine\n---\n"), 0o644) out, err := statusRun(t, env, false) if err != nil { t.Fatalf("unrecognized items alone are not problematic drift:\n%s", out) } if !contains(out, "unrecognized local items") || !contains(out, "my-own-thing") { t.Errorf("unrecognized item must be listed:\n%s", out) } if contains(out, "drift:") { t.Errorf("no drift line expected:\n%s", out) } } func TestStatusNeverSynced(t *testing.T) { h := newHarness(t) env := newSyncEnv(t, h) buildWorkRepo(t, h, env.workRepo) env.writeConfig(t, env.workRepo, "") out, err := statusRun(t, env, true) if err != nil { t.Fatalf("fresh status must exit 0: %v\n%s", err, out) } var report struct { DeployedRev string `json:"deployed_revision"` Pending []struct { Item string `json:"item"` } `json:"pending_changes"` } if err := json.Unmarshal([]byte(out), &report); err != nil { t.Fatalf("json not parseable: %v", err) } if report.DeployedRev != "" { t.Errorf("never synced must have empty deployed revision: %q", report.DeployedRev) } if len(report.Pending) == 0 { t.Error("fresh status must list everything as pending deploy") } }