fix: harden Pi lifecycle recovery
This commit is contained in:
@@ -2,8 +2,13 @@ package pi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
)
|
||||
|
||||
func TestDoctorRequiresExternalEndpointAuthPiStateAndHealth(t *testing.T) {
|
||||
@@ -16,6 +21,69 @@ func TestDoctorRequiresExternalEndpointAuthPiStateAndHealth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureRestoresAndVerifiesOldSettingsAfterEveryPostSnapshotFailure(t *testing.T) {
|
||||
for _, failure := range []string{"helper", "readback", "digest"} {
|
||||
t.Run(failure, func(t *testing.T) {
|
||||
fake := &configureRunner{failure: failure, settings: Defaults{Provider: "old", Model: "old-model", Thinking: "low"}}
|
||||
err := Configure(context.Background(), fake, Defaults{Provider: "new", Model: "new-model", Thinking: "high"})
|
||||
if err == nil {
|
||||
t.Fatal("Configure() error = nil, want injected failure")
|
||||
}
|
||||
if fake.settings != (Defaults{Provider: "old", Model: "old-model", Thinking: "low"}) {
|
||||
t.Fatalf("settings after failure = %#v, want old snapshot", fake.settings)
|
||||
}
|
||||
minimumReads := 3
|
||||
if failure == "helper" {
|
||||
minimumReads = 2
|
||||
}
|
||||
if fake.settingsReads < minimumReads {
|
||||
t.Fatalf("settings read count = %d, want capture/failure reads plus verified restore", fake.settingsReads)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type configureRunner struct {
|
||||
failure string
|
||||
settings Defaults
|
||||
settingsReads int
|
||||
configReads int
|
||||
}
|
||||
|
||||
func (f *configureRunner) Run(_ context.Context, args []string, _ io.Reader) (compose.Result, error) {
|
||||
call := strings.Join(args, " ")
|
||||
switch {
|
||||
case strings.Contains(call, "config --format json"):
|
||||
f.configReads++
|
||||
endpoint := "https://llm.example.invalid"
|
||||
if f.failure == "digest" && f.configReads > 1 {
|
||||
endpoint = "https://drift.example.invalid"
|
||||
}
|
||||
return compose.Result{Stdout: `{"services":{"core":{"image":"thothii-core:local","environment":{"THT_LLM_URL":"` + endpoint + `"}}}}`}, nil
|
||||
case strings.Contains(call, "/models"):
|
||||
return compose.Result{Stdout: `{"models":[{"provider":"old","id":"old-model"},{"provider":"new","id":"new-model"}]}`}, nil
|
||||
case strings.Contains(call, "settings-cli.js"):
|
||||
if strings.Contains(call, "--provider new") {
|
||||
f.settings = Defaults{Provider: "new", Model: "new-model", Thinking: "high"}
|
||||
if f.failure == "helper" {
|
||||
return compose.Result{ExitCode: 17}, errors.New("injected helper failure")
|
||||
}
|
||||
} else {
|
||||
f.settings = Defaults{Provider: "old", Model: "old-model", Thinking: "low"}
|
||||
}
|
||||
return compose.Result{}, nil
|
||||
case strings.Contains(call, "/settings"):
|
||||
f.settingsReads++
|
||||
if f.failure == "readback" && f.settingsReads == 2 {
|
||||
return compose.Result{Stdout: `{}`}, nil
|
||||
}
|
||||
contents, _ := json.Marshal(f.settings)
|
||||
return compose.Result{Stdout: string(contents)}, nil
|
||||
default:
|
||||
return compose.Result{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigureValidatesBackendModelOptionsWritesRealCoreSettingsAndUsesUpstreamIdentity(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
if err := Configure(context.Background(), fake, Defaults{Provider: "provider", Model: "model", Thinking: "medium"}); err != nil {
|
||||
@@ -43,3 +111,16 @@ func TestTestUsesOnlySanitizedPiAndCoreProbes(t *testing.T) {
|
||||
t.Fatalf("probe commands expose secret: %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestRequiresConfiguredProviderAndModelToMatchOneAvailableEntry(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
fake.modelsWire = `{"models":[{"id":"different-model","provider":"provider"}]}`
|
||||
if err := Test(context.Background(), fake); err == nil || !strings.Contains(err.Error(), "configured provider/model") {
|
||||
t.Fatalf("Test() error = %v, want exact settings/model mismatch", err)
|
||||
}
|
||||
fake.modelsWire = `{"models":[{"id":"model","provider":"provider"}]}`
|
||||
if err := Test(context.Background(), fake); err != nil {
|
||||
t.Fatalf("Test() exact match error = %v", err)
|
||||
}
|
||||
assertCalled(t, fake.calls, "pi --version")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user