feat: manage embedded pi with thothctl

This commit is contained in:
2026-08-04 18:20:39 +02:00
parent c90299f24d
commit 20e59b8d32
8 changed files with 1122 additions and 1 deletions
+70 -1
View File
@@ -347,6 +347,59 @@ func TestRunPreservesChildExitCodes(t *testing.T) {
}
}
func TestRunPiStatusUsesImageBundledPi(t *testing.T) {
fixture := newCLIFixture(t, "THT_LLM_URL=https://llm.example.invalid\n")
fixture.setEnvironment(t)
var stdout, stderr bytes.Buffer
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "pi", "status"}, &stdout, &stderr)
if exitCode != 0 {
t.Fatalf("run() exit code = %d, stderr = %s", exitCode, stderr.String())
}
if stdout.String() != "Pi version: 0.80.3\n" {
t.Errorf("stdout = %q, want image-bundled Pi version", stdout.String())
}
assertInvocationContains(t, fixture.invocations(t), "exec", "-T", "core", "pi", "--version")
}
func TestRunPiUpdateRequiresExplicitConfirmationWithoutInvokingDocker(t *testing.T) {
fixture := newCLIFixture(t, "THT_LLM_URL=https://llm.example.invalid\n")
fixture.setEnvironment(t)
var stdout, stderr bytes.Buffer
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "pi", "update", "--version", "0.81.0"}, &stdout, &stderr)
if exitCode != 2 {
t.Errorf("run() exit code = %d, want 2", exitCode)
}
if !strings.Contains(stderr.String(), "requires --yes") {
t.Errorf("stderr = %q, want explicit confirmation guidance", stderr.String())
}
assertDockerNotInvoked(t, fixture)
}
func TestRunPiStatusPreservesDockerExitCodeAndRedactsDiagnostics(t *testing.T) {
fixture := newCLIFixture(t, "PI_TOKEN_FILE=%s\n")
secretPath := filepath.Join(fixture.root, "pi-secret")
if err := os.WriteFile(secretPath, []byte("pi-status-secret"), 0o600); err != nil {
t.Fatal(err)
}
fixture.setEnvironment(t, secretPath)
t.Setenv("THOTHCTL_FAKE_FAIL_ON", "version")
t.Setenv("THOTHCTL_FAKE_FAILURE", "pi-status-secret")
var stdout, stderr bytes.Buffer
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "pi", "status"}, &stdout, &stderr)
if exitCode != 41 {
t.Errorf("run() exit code = %d, want 41", exitCode)
}
if strings.Contains(stdout.String()+stderr.String(), "pi-status-secret") {
t.Errorf("Pi status exposed a secret: stdout=%q stderr=%q", stdout.String(), stderr.String())
}
}
type cliFixture struct {
root string
installationPath string
@@ -392,8 +445,9 @@ func newCLIFixture(t *testing.T, envTemplate string) cliFixture {
printf '%s\n' "$@" >> "$THOTHCTL_FAKE_ARGS"
printf '%s\n' -- >> "$THOTHCTL_FAKE_ARGS"
case " $* " in
*" config --format json "*) printf '%s\n' '{"volumes":{"settings":{}}}' ;;
*" 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"}]' ;;
*" pi --version "*) printf '%s\n' '0.80.3' ;;
*" logs "*) printf '%s\n' "$THOTHCTL_FAKE_LOG" ;;
esac
if [ "${THOTHCTL_FAKE_FAIL_ON:-}" = "version" ]; then
@@ -458,3 +512,18 @@ func assertDockerNotInvoked(t *testing.T, fixture cliFixture) {
t.Errorf("Docker was invoked: stat error = %v", err)
}
}
func assertInvocationContains(t *testing.T, invocations [][]string, want ...string) {
t.Helper()
for _, invocation := range invocations {
for start := range invocation {
if len(invocation)-start < len(want) {
continue
}
if strings.Join(invocation[start:start+len(want)], "\x00") == strings.Join(want, "\x00") {
return
}
}
}
t.Fatalf("invocations = %#v, want %#v", invocations, want)
}