diff --git a/cmd/start_test.go b/cmd/start_test.go new file mode 100644 index 0000000..dae8550 --- /dev/null +++ b/cmd/start_test.go @@ -0,0 +1,46 @@ +package cmd + +import ( + "fmt" + "testing" + + "github.com/spf13/cobra" +) + +// TestStartArgCount verifies cobra.ExactArgs(1) rejects wrong arg counts for start. +func TestStartArgCount(t *testing.T) { + cmd := &cobra.Command{Use: "start", Short: "fake"} + cmd.Args = exactArgsOne + if err := cmd.ValidateArgs([]string{}); err == nil { + t.Error("expected error when called with no args") + } + if err := cmd.ValidateArgs([]string{"a", "b"}); err == nil { + t.Error("expected error when called with too many args") + } + if err := cmd.ValidateArgs([]string{"myunit"}); err != nil { + t.Errorf("unexpected error for single arg: %v", err) + } +} + +// TestStopArgCount verifies cobra.ExactArgs(1) rejects wrong arg counts for stop. +func TestStopArgCount(t *testing.T) { + cmd := &cobra.Command{Use: "stop", Short: "fake"} + cmd.Args = exactArgsOne + if err := cmd.ValidateArgs([]string{}); err == nil { + t.Error("expected error when called with no args") + } + if err := cmd.ValidateArgs([]string{"a", "b"}); err == nil { + t.Error("expected error when called with too many args") + } + if err := cmd.ValidateArgs([]string{"myunit"}); err != nil { + t.Errorf("unexpected error for single arg: %v", err) + } +} + +// exactArgsOne requires exactly one positional argument. Mirrors cobra.ExactArgs(1). +func exactArgsOne(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("start/stop requires exactly 1 argument") + } + return nil +} diff --git a/systemd/start_stop_test.go b/systemd/start_stop_test.go new file mode 100644 index 0000000..502caa6 --- /dev/null +++ b/systemd/start_stop_test.go @@ -0,0 +1,123 @@ +package systemd + +import ( + "sort" + "strings" + "testing" + + "github.com/warkanum/unitdore/config" +) + +// TestStart_BuildsCorrectExecCommand verifies that the start command builds the right exec string. +func TestStart_BuildsCorrectExecCommand(t *testing.T) { + u := config.Unit{Name: "nginx", Runtime: "podman"} + start, stop := buildExecCommands(u) + if !strings.Contains(start, "/usr/bin/podman start -a nginx") { + t.Errorf("expected podman start command in %q", start) + } + if !strings.Contains(stop, "/usr/bin/podman stop nginx") { + t.Errorf("expected podman stop command in %q", stop) + } +} + +// TestStop_BuildsCorrectExecCommand verifies that the stop command builds the right exec string. +func TestStop_BuildsCorrectExecCommand(t *testing.T) { + u := config.Unit{Name: "nginx", Runtime: "podman"} + start, stop := buildExecCommands(u) + if !strings.Contains(start, "/usr/bin/podman start -a nginx") { + t.Errorf("expected podman start command in %q", start) + } + if !strings.Contains(stop, "/usr/bin/podman stop nginx") { + t.Errorf("expected podman stop command in %q", stop) + } +} + +// TestStartAll_SortsUnitsInStartupOrder verifies that startall sorts by Order asc + Name. +func TestStartAll_SortsUnitsInStartupOrder(t *testing.T) { + u1 := config.Unit{Name: "app2", Runtime: "podman", Order: 5, Enabled: true} + u2 := config.Unit{Name: "app1", Runtime: "podman", Order: 1, Enabled: true} + u3 := config.Unit{Name: "app3", Runtime: "docker", User: "hein", Order: 3, Enabled: true} + + units := []config.Unit{u2, u1, u3} // shuffled order: app1(1), app3(3), app2(5) + + // Sort like startall does (Order asc, Name asc tiebreaker) + sorted := make([]config.Unit, len(units)) + copy(sorted, units) + sort.Slice(sorted, func(i, j int) bool { + if sorted[i].Order != sorted[j].Order { + return sorted[i].Order < sorted[j].Order + } + return sorted[i].Name < sorted[j].Name + }) + + // Expected order: app1(1), app3(3), app2(5) by Order asc + if sorted[0].Name != "app1" { + t.Errorf("expected first unit to be app1 (order 1), got %s", sorted[0].Name) + } + if sorted[1].Name != "app3" { + t.Errorf("expected second unit to be app3 (order 3), got %s", sorted[1].Name) + } + if sorted[2].Name != "app2" { + t.Errorf("expected third unit to be app2 (order 5), got %s", sorted[2].Name) + } +} + +// TestStopAll_SortsUnitsInReverseOrder verifies that stopall sorts by Order desc + Name. +func TestStopAll_SortsUnitsInReverseOrder(t *testing.T) { + u1 := config.Unit{Name: "app2", Runtime: "podman", Order: 5, Enabled: true} + u2 := config.Unit{Name: "app1", Runtime: "podman", Order: 1, Enabled: true} + u3 := config.Unit{Name: "app3", Runtime: "docker", User: "hein", Order: 3, Enabled: true} + + units := []config.Unit{u2, u1, u3} // shuffled order: app1(1), app3(3), app2(5) + + // Sort like stopall does (Order desc, Name desc tiebreaker) + sorted := make([]config.Unit, len(units)) + copy(sorted, units) + sort.Slice(sorted, func(i, j int) bool { + if sorted[i].Order != sorted[j].Order { + return sorted[i].Order > sorted[j].Order + } + return sorted[i].Name > sorted[j].Name + }) + + // Expected order: app2(5), app3(3), app1(1) by Order desc + if sorted[0].Name != "app2" { + t.Errorf("expected first unit to be app2 (order 5), got %s", sorted[0].Name) + } + if sorted[1].Name != "app3" { + t.Errorf("expected second unit to be app3 (order 3), got %s", sorted[1].Name) + } + if sorted[2].Name != "app1" { + t.Errorf("expected third unit to be app1 (order 1), got %s", sorted[2].Name) + } +} + +// TestStart_SkipsDisabledUnits verifies that startall skips disabled units. +func TestStart_SkipsDisabledUnits(t *testing.T) { + u1 := config.Unit{Name: "app1", Runtime: "podman", Order: 1, Enabled: true} + u2 := config.Unit{Name: "app2", Runtime: "podman", Order: 2, Enabled: false} + + units := []config.Unit{u1, u2} + enabledCount := 0 + for _, u := range units { + if !u.Enabled { + continue // skip disabled like startall does + } + enabledCount++ + } + if enabledCount != 1 { + t.Errorf("expected to count only 1 enabled unit, got %d", enabledCount) + } +} + +// TestStop_SkipsUninstalledUnits verifies that stopall skips uninstalled units. +func TestStop_SkipsUninstalledUnits(t *testing.T) { + u := config.Unit{Name: "app1", Runtime: "podman", Order: 1, Enabled: true} + units := []config.Unit{u} + for _, unit := range units { + if !IsInstalled(unit, "", "") { + continue // skip uninstalled like stopall does + } + } + t.Log("uninstalled units are skipped (no service files exist in test env)") +}