fix: harden thothctl pi lifecycle
This commit is contained in:
@@ -28,7 +28,7 @@ func TestUpdateBuildsPinnedVersionRecreatesOnlyCoreAndPersistsRecoveryState(t *t
|
||||
t.Fatalf("phase = %q, want %q", result.Phase, PhaseVerified)
|
||||
}
|
||||
assertCalled(t, fake.calls, "compose build --pull --build-arg PI_VERSION=0.81.0 core")
|
||||
assertCalled(t, fake.calls, "compose up --detach --no-deps --force-recreate core")
|
||||
assertCalled(t, fake.calls, "compose up --detach --wait --wait-timeout 45 --no-deps --force-recreate core")
|
||||
assertNotCalled(t, fake.calls, "frontend")
|
||||
if got := string(readStateBytes(t, result.StatePath)); strings.Contains(got, "secret") || !strings.Contains(got, `"phase": "verified"`) {
|
||||
t.Fatalf("state = %q, want credential-free verified metadata", got)
|
||||
@@ -83,7 +83,7 @@ func TestUpdateRollsBackAfterPostRecreateFailures(t *testing.T) {
|
||||
t.Fatalf("phase = %q, want %q", result.Phase, PhaseRolledBack)
|
||||
}
|
||||
assertCalled(t, fake.calls, "image tag sha256:old thothii-core:local")
|
||||
assertCalled(t, fake.calls, "compose up --detach --no-deps --force-recreate core")
|
||||
assertCalled(t, fake.calls, "compose up --detach --wait --wait-timeout 45 --no-deps --force-recreate core")
|
||||
if strings.Join(fake.volumes, ",") != "settings,pi-state,sessions,workspace-registry" {
|
||||
t.Fatalf("volumes changed: %v", fake.volumes)
|
||||
}
|
||||
@@ -138,7 +138,16 @@ func TestUpdateRequiresConfirmationAndDrainsActiveSessions(t *testing.T) {
|
||||
func TestRollbackRestoresInterruptedOrPreviouslyRecordedState(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
statePath := filepath.Join(t.TempDir(), "state.json")
|
||||
writeStateForTest(t, statePath, State{Version: 1, Phase: PhaseRecreated, Previous: Image{ID: "sha256:old", Reference: "thothii-core:local", Volumes: []string{"settings", "pi-state", "sessions", "workspace-registry"}}})
|
||||
configured, err := renderedCore(context.Background(), fake)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
previous, err := runningImage(context.Background(), fake, "thothii-core:local")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
previous.ConfigurationSHA = configured.ConfigurationSHA
|
||||
writeStateForTest(t, statePath, State{Version: 1, Phase: PhaseRecreated, Previous: previous})
|
||||
result, err := Rollback(context.Background(), fake, statePath, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Rollback() error = %v", err)
|
||||
@@ -147,7 +156,7 @@ func TestRollbackRestoresInterruptedOrPreviouslyRecordedState(t *testing.T) {
|
||||
t.Fatalf("phase = %q, want %q", result.Phase, PhaseRolledBack)
|
||||
}
|
||||
assertCalled(t, fake.calls, "image tag sha256:old thothii-core:local")
|
||||
assertCalled(t, fake.calls, "compose up --detach --no-deps --force-recreate core")
|
||||
assertCalled(t, fake.calls, "compose up --detach --wait --wait-timeout 45 --no-deps --force-recreate core")
|
||||
}
|
||||
|
||||
func TestUpdateRefusesToOverwriteInterruptedRecoveryState(t *testing.T) {
|
||||
@@ -161,6 +170,18 @@ func TestUpdateRefusesToOverwriteInterruptedRecoveryState(t *testing.T) {
|
||||
assertNotCalled(t, fake.calls, "compose")
|
||||
}
|
||||
|
||||
func TestRunningImageCapturesServerBindAndNamedMountIdentity(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
fake.mountsJSON = `[{"Type":"bind","Source":"/srv/thothii/data","Destination":"/data","RW":true},{"Type":"bind","Source":"/srv/thothii/pi","Destination":"/home/thoth/.pi","RW":true},{"Type":"volume","Name":"sessions","Source":"/var/lib/docker/volumes/sessions/_data","Destination":"/data/sessions","RW":true}]`
|
||||
image, err := runningImage(context.Background(), fake, "thothii-core:local")
|
||||
if err != nil {
|
||||
t.Fatalf("runningImage() error = %v", err)
|
||||
}
|
||||
if len(image.Mounts) != 3 || image.Mounts[0].Type != "bind" || image.Mounts[0].Destination != "/data" {
|
||||
t.Fatalf("mounts = %#v", image.Mounts)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeRunner struct {
|
||||
calls []string
|
||||
fail string
|
||||
@@ -168,6 +189,7 @@ type fakeRunner struct {
|
||||
activeSessions bool
|
||||
built bool
|
||||
volumes []string
|
||||
mountsJSON string
|
||||
}
|
||||
|
||||
func newFakeRunner() *fakeRunner {
|
||||
@@ -177,6 +199,9 @@ func newFakeRunner() *fakeRunner {
|
||||
func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose.Result, error) {
|
||||
call := strings.Join(args, " ")
|
||||
f.calls = append(f.calls, call)
|
||||
if strings.Contains(call, "image tag sha256:old") {
|
||||
f.fail = ""
|
||||
}
|
||||
if f.fail == "preflight" && strings.Contains(call, "config --format json") {
|
||||
return compose.Result{ExitCode: 1}, errors.New("provider token=secret")
|
||||
}
|
||||
@@ -199,8 +224,11 @@ func (f *fakeRunner) Run(_ context.Context, args []string, _ io.Reader) (compose
|
||||
return compose.Result{Stdout: "core-container\n"}, nil
|
||||
case strings.Contains(call, "inspect --format {{.Image}}"):
|
||||
return compose.Result{Stdout: "sha256:old\n"}, nil
|
||||
case strings.Contains(call, "inspect --format {{range .Mounts}}"):
|
||||
return compose.Result{Stdout: strings.Join(f.volumes, "\n") + "\n"}, nil
|
||||
case strings.Contains(call, "inspect --format {{json .Mounts}}"):
|
||||
if f.mountsJSON != "" {
|
||||
return compose.Result{Stdout: f.mountsJSON}, nil
|
||||
}
|
||||
return compose.Result{Stdout: `[{"Type":"volume","Name":"settings","Source":"settings","Destination":"/data/settings","RW":true},{"Type":"volume","Name":"pi-state","Source":"pi-state","Destination":"/home/thoth/.pi","RW":true},{"Type":"volume","Name":"sessions","Source":"sessions","Destination":"/data/sessions","RW":true},{"Type":"volume","Name":"workspace-registry","Source":"workspace-registry","Destination":"/data/workspace-registry","RW":true}]`}, nil
|
||||
case strings.Contains(call, "tht session list --json"):
|
||||
if f.activeSessions {
|
||||
f.activeSessions = false
|
||||
|
||||
Reference in New Issue
Block a user