package app_test import ( "context" "errors" "os" "path/filepath" "strings" "testing" "proxyctl/internal/app" "proxyctl/internal/dockerclient" "proxyctl/internal/operationlock" "proxyctl/internal/profile" "proxyctl/internal/shell" "proxyctl/internal/target" ) type coordinatedTarget struct { id string state target.State applied []string appliedName string failName string availabilityCalls int inspectCalls int } func (t *coordinatedTarget) ID() string { return t.id } func (t *coordinatedTarget) Description() target.Description { return target.Description{Name: t.id, Summary: "summary", Details: "effects, non-effects, ownership, permissions, and side effects"} } func (t *coordinatedTarget) Availability(context.Context) target.Availability { t.availabilityCalls++ return target.Availability{Available: true, Evidence: "detected"} } func (t *coordinatedTarget) Inspect(_ context.Context, active *profile.Profile) target.Inspection { t.inspectCalls++ if t.state == target.Enabled && (active == nil || (t.appliedName != "" && active.Name != t.appliedName)) { return target.Inspection{State: target.Drifted, Evidence: "managed configuration does not match the Active Profile"} } return target.Inspection{State: t.state, Evidence: "fresh actual configuration"} } func (t *coordinatedTarget) Confirmation(bool) string { return "" } func (t *coordinatedTarget) SetDesired(_ context.Context, enabled bool, active *profile.Profile) target.OperationResult { if !enabled { t.state = target.Disabled return target.OperationResult{Inspection: target.Inspection{State: t.state, Evidence: "disabled"}} } t.applied = append(t.applied, active.Name) if active.Name == t.failName { t.state = target.Drifted return target.OperationResult{Inspection: target.Inspection{State: target.Enabled, Evidence: "stale operation state"}, Err: errors.New("failed using http://alice:secret@proxy.example")} } t.state = target.Enabled t.appliedName = active.Name return target.OperationResult{Inspection: target.Inspection{State: t.state, Evidence: "matches " + active.Name}} } func TestSecondApplicationUsesPersistedActiveProfileChangedByFirstApplication(t *testing.T) { root := t.TempDir() storePath := filepath.Join(root, "profiles.json") bootstrap, err := app.New(profile.NewFileStore(storePath)) if err != nil { t.Fatal(err) } for _, name := range []string{"old", "new"} { result := bootstrap.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: profile.Profile{Name: name, Mode: profile.SingleMode, SingleURL: "http://" + name + ".example:8080"}}) if result.Err != nil { t.Fatalf("create %s Profile: %v", name, result.Err) } } if result := bootstrap.Dispatch(app.Intent{Kind: app.ActivateProfile, Name: "old"}); result.Err != nil { t.Fatalf("activate old Profile: %v", result.Err) } firstTarget := &coordinatedTarget{id: "shell", state: target.Disabled} secondTarget := &coordinatedTarget{id: "shell", state: target.Disabled} first, err := app.NewWithTargetsAndLock(profile.NewFileStore(storePath), []target.Target{firstTarget}, operationlock.New(filepath.Join(root, "operation.lock"))) if err != nil { t.Fatal(err) } second, err := app.NewWithTargetsAndLock(profile.NewFileStore(storePath), []target.Target{secondTarget}, operationlock.New(filepath.Join(root, "operation.lock"))) if err != nil { t.Fatal(err) } if got := second.Snapshot().ActiveProfile; got != "old" { t.Fatalf("second Application starting Active Profile = %q, want old", got) } refreshApplicationTargets(t, second) if result := first.Dispatch(app.Intent{Kind: app.ActivateProfile, Name: "new"}); result.Err != nil { t.Fatalf("first Application activates new Profile: %v", result.Err) } result := second.Dispatch(app.Intent{Kind: app.EnableTarget, TargetID: "shell"}) if result.Err != nil { t.Fatalf("second Application enables Target: %v", result.Err) } if secondTarget.appliedName != "new" { t.Fatalf("second Application applied stale Profile %q, want freshly persisted Active Profile new", secondTarget.appliedName) } if result.Snapshot.ActiveProfile != "new" { t.Fatalf("second Application snapshot Active Profile = %q, want new", result.Snapshot.ActiveProfile) } } func TestActiveProfileSwitchCoordinatesEnabledTargetsWithoutRollback(t *testing.T) { first := &coordinatedTarget{id: "first", state: target.Disabled} second := &coordinatedTarget{id: "second", state: target.Disabled, failName: "new"} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{first, second}) if err != nil { t.Fatal(err) } for _, name := range []string{"old", "new"} { result := application.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: profile.Profile{Name: name, SingleURL: "http://alice:secret@proxy.example:8080"}}) if result.Err != nil { t.Fatal(result.Err) } } if result := application.Dispatch(app.Intent{Kind: app.ActivateProfile, Name: "old"}); result.Err != nil { t.Fatal(result.Err) } refreshApplicationTargets(t, application) for _, id := range []string{"first", "second"} { if result := application.Dispatch(app.Intent{Kind: app.EnableTarget, TargetID: id}); result.Err != nil { t.Fatal(result.Err) } } inspectCallsBeforeSwitch := second.inspectCalls var final app.OperationUpdate var failedCompletion app.OperationUpdate stream := application.StartOperation(app.Intent{Kind: app.ActivateProfile, Name: "new"}) for update := range stream.Updates { final = update progress := update.Progress() if len(progress) == 1 && progress[0].TargetID == "second" && progress[0].Phase == app.TargetProgressFailed { failedCompletion = update } } if final.Snapshot.ActiveProfile != "new" { t.Fatalf("Active Profile = %q", final.Snapshot.ActiveProfile) } if final.Err() == nil || strings.Contains(final.Err().Error(), "secret") || !strings.Contains(final.Err().Error(), "second") { t.Fatalf("redacted partial failure = %v", final.Err()) } if got := final.Snapshot.Targets; got[0].State != target.Enabled || got[1].State != target.Drifted { t.Fatalf("truthful partial states = %#v", got) } progress := final.Progress() if len(progress) != 2 || progress[0].TargetID != "first" || progress[0].State != target.Enabled || progress[1].TargetID != "second" || progress[1].State != target.Drifted || progress[1].Evidence != "fresh actual configuration" { t.Fatalf("per-Target progress = %#v", progress) } failedProgress := failedCompletion.Progress() if len(failedProgress) != 1 || failedProgress[0].State != target.Drifted || failedProgress[0].Evidence != "fresh actual configuration" || failedProgress[0].Err == nil { t.Fatalf("failed completion event was not truthful: %#v", failedCompletion) } if failedCompletion.Snapshot.Targets[1].State != target.Drifted || failedCompletion.Snapshot.Targets[1].Evidence != "fresh actual configuration" { t.Fatalf("failed completion snapshot was not truthful: %#v", failedCompletion.Snapshot.Targets[1]) } if second.inspectCalls < inspectCallsBeforeSwitch+2 { t.Fatalf("failed SetDesired was not explicitly re-inspected: Inspect calls before=%d after=%d", inspectCallsBeforeSwitch, second.inspectCalls) } if !strings.Contains(final.Err().Error(), "fresh actual configuration") || !strings.Contains(final.Err().Error(), "Drifted") { t.Fatalf("failure summary omitted fresh inspection: %v", final.Err()) } if len(first.applied) != 2 || first.applied[1] != "new" { t.Fatalf("successful Target was not updated: %#v", first.applied) } } func TestApplicationCoordinatesMultipleRealAndFakeHermeticAdaptersThroughPartialFailure(t *testing.T) { directory := t.TempDir() shellAdapter := shell.New(shell.Configuration{ Shell: "/bin/bash", EnvironmentFile: filepath.Join(directory, "shell.env"), RecoveryFile: filepath.Join(directory, "shell.json"), TrustedRoots: []string{directory}, }, nil, shell.MapEnvironment{}) dockerAdapter := dockerclient.New(dockerclient.Configuration{ ConfigFile: filepath.Join(directory, "docker", "config.json"), RecoveryFile: filepath.Join(directory, "docker", "recovery.json"), TrustedRoots: []string{directory}, }, nil) failure := &coordinatedTarget{id: "failing-fake", state: target.Disabled, failName: "new"} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(directory, "profiles.json")), []target.Target{shellAdapter, dockerAdapter, failure}) if err != nil { t.Fatal(err) } for name, endpoint := range map[string]string{"old": "http://old-proxy.example:8080", "new": "http://new-proxy.example:9090"} { if result := application.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: profile.Profile{Name: name, SingleURL: endpoint}}); result.Err != nil { t.Fatal(result.Err) } } if result := application.Dispatch(app.Intent{Kind: app.ActivateProfile, Name: "old"}); result.Err != nil { t.Fatal(result.Err) } refreshApplicationTargets(t, application) for _, id := range []string{shellAdapter.ID(), dockerAdapter.ID(), failure.ID()} { if result := application.Dispatch(app.Intent{Kind: app.EnableTarget, TargetID: id}); result.Err != nil { t.Fatalf("enable %s: %v", id, result.Err) } } result := application.Dispatch(app.Intent{Kind: app.ActivateProfile, Name: "new"}) if result.Err == nil || result.Snapshot.ActiveProfile != "new" { t.Fatalf("partial switch = %#v", result) } states := map[string]target.State{} for _, item := range result.Snapshot.Targets { states[item.ID] = item.State } if states["shell"] != target.Enabled || states["docker-containers"] != target.Enabled || states["failing-fake"] != target.Drifted { t.Fatalf("real/fake Target states = %#v", states) } generated, err := os.ReadFile(filepath.Join(directory, "shell.env")) if err != nil || !strings.Contains(string(generated), "new-proxy.example:9090") { t.Fatalf("real Shell effect = %q, %v", generated, err) } dockerConfig, err := os.ReadFile(filepath.Join(directory, "docker", "config.json")) if err != nil || !strings.Contains(string(dockerConfig), "new-proxy.example:9090") { t.Fatalf("real Docker client effect = %q, %v", dockerConfig, err) } } func TestActiveProfileProgressReusesAvailabilityCache(t *testing.T) { adapter := &coordinatedTarget{id: "shell", state: target.Disabled} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter}) if err != nil { t.Fatal(err) } for _, name := range []string{"old", "new"} { if result := application.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: profile.Profile{Name: name, SingleURL: "http://proxy.example:8080"}}); result.Err != nil { t.Fatal(result.Err) } } if result := application.Dispatch(app.Intent{Kind: app.ActivateProfile, Name: "old"}); result.Err != nil { t.Fatal(result.Err) } refreshApplicationTargets(t, application) if result := application.Dispatch(app.Intent{Kind: app.EnableTarget, TargetID: "shell"}); result.Err != nil { t.Fatal(result.Err) } callsBeforeSwitch := adapter.availabilityCalls var updates int for range application.StartOperation(app.Intent{Kind: app.ActivateProfile, Name: "new"}).Updates { updates++ } if updates < 3 { t.Fatalf("progress updates = %d", updates) } if adapter.availabilityCalls != callsBeforeSwitch { t.Fatalf("availability probes during progress = %d, want cached count %d", adapter.availabilityCalls, callsBeforeSwitch) } application.Dispatch(app.Intent{Kind: app.RefreshTargets}) if adapter.availabilityCalls != callsBeforeSwitch+1 { t.Fatalf("explicit refresh probe count = %d, want %d", adapter.availabilityCalls, callsBeforeSwitch+1) } } func TestActiveProfileSwitchStreamsApplyingAndCompletionForEachTarget(t *testing.T) { adapter := &coordinatedTarget{id: "shell", state: target.Disabled} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter}) if err != nil { t.Fatal(err) } for _, name := range []string{"old", "new"} { if result := application.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: profile.Profile{Name: name, SingleURL: "http://proxy.example:8080"}}); result.Err != nil { t.Fatal(result.Err) } } if result := application.Dispatch(app.Intent{Kind: app.ActivateProfile, Name: "old"}); result.Err != nil { t.Fatal(result.Err) } refreshApplicationTargets(t, application) if result := application.Dispatch(app.Intent{Kind: app.EnableTarget, TargetID: "shell"}); result.Err != nil { t.Fatal(result.Err) } updates := application.StartOperation(app.Intent{Kind: app.ActivateProfile, Name: "new"}).Updates applying := <-updates completed := <-updates final := <-updates applyingProgress := applying.Progress() if len(applyingProgress) != 1 || applyingProgress[0].Phase != app.TargetProgressRunning || applyingProgress[0].TargetID != "shell" { t.Fatalf("applying update = %#v", applying) } completedProgress := completed.Progress() if len(completedProgress) != 1 || completedProgress[0].Phase != app.TargetProgressSucceeded || completedProgress[0].State != target.Enabled { t.Fatalf("completion update = %#v", completed) } if !final.IsSucceeded() || final.Snapshot.ActiveProfile != "new" { t.Fatalf("final update = %#v", final) } } func TestDeletingFinalProfileLeavesManagedTargetsDriftedAndDisableStillRestoresExternal(t *testing.T) { directory := t.TempDir() configuration := dockerclient.Configuration{ ConfigFile: filepath.Join(directory, "docker", "config.json"), RecoveryFile: filepath.Join(directory, "state", "docker.json"), TrustedRoots: []string{directory}, } if err := os.MkdirAll(filepath.Dir(configuration.ConfigFile), 0o700); err != nil { t.Fatal(err) } const externalConfig = `{"proxies":{"default":{"httpProxy":"http://external.example:3128"}},"auths":{"registry.example":{}}}` if err := os.WriteFile(configuration.ConfigFile, []byte(externalConfig), 0o600); err != nil { t.Fatal(err) } adapter := dockerclient.New(configuration, nil) application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(directory, "profiles.json")), []target.Target{adapter}) if err != nil { t.Fatal(err) } created := application.Dispatch(app.Intent{Kind: app.CreateProfile, Profile: profile.Profile{Name: "only", SingleURL: "http://proxy.example:8080"}}) if created.Err != nil { t.Fatal(created.Err) } if activated := application.Dispatch(app.Intent{Kind: app.ActivateProfile, Name: "only"}); activated.Err != nil { t.Fatal(activated.Err) } refreshApplicationTargets(t, application) if enabled := application.Dispatch(app.Intent{Kind: app.ApplyProfileToTarget, TargetID: adapter.ID()}); enabled.Err != nil { t.Fatal(enabled.Err) } unconfirmed := application.Dispatch(app.Intent{Kind: app.DeleteProfile, Name: "only"}) if unconfirmed.Err == nil || unconfirmed.Snapshot.ActiveProfile != "only" { t.Fatalf("unconfirmed final delete = %#v", unconfirmed) } deleted := application.Dispatch(app.Intent{Kind: app.DeleteProfile, Name: "only", Confirm: true}) if deleted.Err != nil || deleted.Snapshot.ActiveProfile != "" || deleted.Snapshot.Targets[0].State != target.Drifted { t.Fatalf("confirmed final delete = %#v", deleted) } enable := application.Dispatch(app.Intent{Kind: app.ReapplyActiveProfile, TargetID: adapter.ID()}) if enable.Err == nil || !strings.Contains(enable.Err.Error(), "Profile management") { t.Fatalf("re-enable without Active Profile = %v", enable.Err) } disabled := application.Dispatch(app.Intent{Kind: app.RestoreOriginal, TargetID: adapter.ID()}) if disabled.Err != nil || disabled.Snapshot.Targets[0].State != target.External { t.Fatalf("disable after final delete = %#v", disabled) } restored, err := os.ReadFile(configuration.ConfigFile) if err != nil { t.Fatal(err) } if !strings.Contains(string(restored), "external.example:3128") || !strings.Contains(string(restored), "registry.example") { t.Fatalf("External Docker client configuration was not restored: %s", restored) } }