fix: make server operations executable

This commit is contained in:
2026-08-05 11:06:34 +02:00
parent a707fb442c
commit 96fe5bfa79
11 changed files with 1299 additions and 43 deletions
+58
View File
@@ -79,6 +79,8 @@ func TestUsageDocumentsClosedConfigureUpdateSourcesAndMaintenanceRecovery(t *tes
"--source pull --image IMAGE@sha256:DIGEST",
"pi maintenance status",
"pi maintenance recover --yes",
"sessions migrate --yes",
"remove --yes ID...",
} {
if !strings.Contains(usage, required) {
t.Errorf("usage missing %q", required)
@@ -86,6 +88,48 @@ func TestUsageDocumentsClosedConfigureUpdateSourcesAndMaintenanceRecovery(t *tes
}
}
func TestRunSessionsMigrateRequiresExplicitConfirmationBeforeDocker(t *testing.T) {
fixture := newCLIFixture(t, "")
fixture.setProfile(t, "server")
fixture.setEnvironment(t)
var stdout, stderr bytes.Buffer
code := run(context.Background(), []string{
"--installation", fixture.installationPath, "sessions", "migrate",
}, &stdout, &stderr)
if code != 2 || !strings.Contains(stderr.String(), "sessions migrate requires --yes") {
t.Fatalf("exit = %d, stderr = %q", code, stderr.String())
}
assertDockerNotInvoked(t, fixture)
}
func TestRunRemoveDisplaysExactInstallationTargetsBeforeConfirmation(t *testing.T) {
fixture := newCLIFixture(t, "")
fixture.setProfile(t, "server")
fixture.setEnvironment(t)
t.Setenv("THOTHCTL_FAKE_STOPPED_PS", `[{"ID":"core-id","Name":"exact-core","Service":"core","State":"exited"},{"ID":"front-id","Name":"exact-frontend","Service":"frontend","State":"exited"}]`)
var stdout, stderr bytes.Buffer
code := run(context.Background(), []string{
"--installation", fixture.installationPath, "remove",
}, &stdout, &stderr)
if code != 2 || !strings.Contains(stderr.String(), "re-run with remove --yes core-id front-id") {
t.Fatalf("exit = %d, stderr = %q", code, stderr.String())
}
for _, value := range []string{"exact-core", "core-id", "exact-frontend", "front-id", "exited"} {
if !strings.Contains(stdout.String(), value) {
t.Errorf("target display %q missing %q", stdout.String(), value)
}
}
calls := fixture.invocations(t)
if len(calls) != 1 {
t.Fatalf("Docker calls = %#v", calls)
}
assertInvocationContains(t, calls, "ps", "--all", "--format", "json", "core", "frontend")
}
type wizardRunner struct{ calls []string }
func (r *wizardRunner) Run(_ context.Context, args []string, _ io.Reader) (compose.Result, error) {
@@ -615,6 +659,7 @@ func newCLIFixture(t *testing.T, envTemplate string) cliFixture {
printf '%s\n' "$@" >> "$THOTHCTL_FAKE_ARGS"
printf '%s\n' -- >> "$THOTHCTL_FAKE_ARGS"
case " $* " in
*" ps --all --format json core frontend "*) printf '%s\n' "${THOTHCTL_FAKE_STOPPED_PS:-[]}" ;;
*" config --format json "*) printf '%s\n' '{"volumes":{"settings":{}},"services":{"core":{"image":"thothii-core:local","environment":{"THT_LLM_URL":"https://llm.example.invalid"}}}}' ;;
*" ps --format json "*) printf '%s\n' '[{"Service":"core","State":"running","Health":"healthy"},{"Service":"frontend","State":"running","Health":"healthy"}]' ;;
*"io.thothii.pi.version"*) printf '%s\n' '0.80.3' ;;
@@ -661,6 +706,19 @@ func (f cliFixture) setEnvContents(t *testing.T, env string) {
t.Setenv("THOTHCTL_FAKE_LOG", "")
t.Setenv("THOTHCTL_FAKE_FAILURE", "")
t.Setenv("THOTHCTL_FAKE_FAIL_ON", "")
t.Setenv("THOTHCTL_FAKE_STOPPED_PS", "[]")
}
func (f cliFixture) setProfile(t *testing.T, profile string) {
t.Helper()
composePath := filepath.Join(f.projectDirectory, "deploy", "compose."+profile+".yaml")
if err := os.WriteFile(composePath, []byte("services: {}\n"), 0o600); err != nil {
t.Fatal(err)
}
contents := "profile: " + profile + "\nprojectDirectory: " + f.projectDirectory + "\nenvFile: " + f.envFile + "\n"
if err := os.WriteFile(f.installationPath, []byte(contents), 0o600); err != nil {
t.Fatal(err)
}
}
func (f cliFixture) invocations(t *testing.T) [][]string {