package app_test import ( "errors" "path/filepath" "strings" "testing" "proxyctl/internal/app" "proxyctl/internal/profile" "proxyctl/internal/target" ) func TestApplicationSnapshotStructuresContextualTargetActions(t *testing.T) { states := []struct { id string state target.State action app.TargetActionKind }{ {id: "disabled", state: target.Disabled, action: app.EnableTargetAction}, {id: "enabled", state: target.Enabled, action: app.DisableTargetAction}, {id: "external", state: target.External, action: app.ApplyProfileTargetAction}, {id: "drifted", state: target.Drifted, action: app.ResolveDriftTargetAction}, } adapters := make([]target.Target, 0, len(states)) for _, item := range states { adapters = append(adapters, targetWithID{fakeTarget: &fakeTarget{available: true, state: item.state}, id: item.id}) } application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), adapters) if err != nil { t.Fatal(err) } createAndActivate(t, application) if result := application.Dispatch(app.Intent{Kind: app.RefreshTargets}); result.Err != nil { t.Fatal(result.Err) } snapshot := application.Snapshot() for i, want := range states { got := snapshot.Targets[i] if got.PrimaryAction.Kind != want.action || !got.PrimaryAction.Available || got.PrimaryAction.DisabledReason != "" { t.Errorf("%s primary action = %#v, want available %v", want.state, got.PrimaryAction, want.action) } } drift := snapshot.Targets[3] if len(drift.DriftResolutionActions) != 2 || drift.DriftResolutionActions[0].Kind != app.ReapplyActiveProfileAction || drift.DriftResolutionActions[1].Kind != app.RestoreOriginalAction { t.Fatalf("Drift Resolution actions = %#v", drift.DriftResolutionActions) } for _, action := range drift.DriftResolutionActions { if !action.Available || action.DisabledReason != "" { t.Errorf("Drift Resolution action = %#v, want available", action) } } } func TestApplicationDispatchesExplicitContextualTargetIntents(t *testing.T) { tests := []struct { name string state target.State intent app.IntentKind enabled bool }{ {name: "Enable", state: target.Disabled, intent: app.EnableTarget, enabled: true}, {name: "Disable", state: target.Enabled, intent: app.DisableTarget, enabled: false}, {name: "Apply Profile", state: target.External, intent: app.ApplyProfileToTarget, enabled: true}, {name: "Reapply Active Profile", state: target.Drifted, intent: app.ReapplyActiveProfile, enabled: true}, {name: "Restore Original", state: target.Drifted, intent: app.RestoreOriginal, enabled: false}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { adapter := &fakeTarget{available: true, state: target.Disabled} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter}) if err != nil { t.Fatal(err) } createAndActivate(t, application) adapter.state = test.state adapter.calls = nil if result := application.Dispatch(app.Intent{Kind: app.RefreshTargets}); result.Err != nil { t.Fatal(result.Err) } result := application.Dispatch(app.Intent{Kind: test.intent, TargetID: adapter.ID()}) if result.Err != nil || len(adapter.calls) != 1 || adapter.calls[0] != test.enabled { t.Fatalf("result = %#v, SetDesired calls = %#v", result, adapter.calls) } wantState := target.Disabled if test.enabled { wantState = target.Enabled } if result.Snapshot.Targets[0].State != wantState { t.Fatalf("final state = %s, want %s", result.Snapshot.Targets[0].State, wantState) } }) } } func TestApplicationRejectsContextualIntentWhenTargetStateChanged(t *testing.T) { adapter := &fakeTarget{available: true, state: target.External} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter}) if err != nil { t.Fatal(err) } createAndActivate(t, application) if result := application.Dispatch(app.Intent{Kind: app.RefreshTargets}); result.Err != nil { t.Fatal(result.Err) } result := application.Dispatch(app.Intent{Kind: app.EnableTarget, TargetID: adapter.ID()}) if result.Err == nil || !strings.Contains(result.Err.Error(), "External") || len(adapter.calls) != 0 { t.Fatalf("stale Enable intent = %#v, calls = %#v", result, adapter.calls) } } func TestApplicationReapplyPublishesTruthfulDriftWhenAdapterCannotConverge(t *testing.T) { adapter := &fakeTarget{available: true, state: target.Disabled} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter}) if err != nil { t.Fatal(err) } createAndActivate(t, application) adapter.state = target.Drifted adapter.setState = target.Drifted adapter.calls = nil if result := application.Dispatch(app.Intent{Kind: app.RefreshTargets}); result.Err != nil { t.Fatal(result.Err) } result := application.Dispatch(app.Intent{Kind: app.ReapplyActiveProfile, TargetID: adapter.ID()}) if result.Err != nil || result.Snapshot.Targets[0].State != target.Drifted || result.Progress[0].State != target.Drifted { t.Fatalf("truthful non-converged reapply = %#v", result) } } func TestApplicationRejectsStructuredUnavailableActionWithoutAdapterMutation(t *testing.T) { adapter := &fakeTarget{available: false, state: target.External} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter}) if err != nil { t.Fatal(err) } createAndActivate(t, application) adapter.calls = nil if result := application.Dispatch(app.Intent{Kind: app.RefreshTargets}); result.Err != nil { t.Fatal(result.Err) } if action := application.Snapshot().Targets[0].PrimaryAction; action.Available || !strings.Contains(action.DisabledReason, "unavailable") { t.Fatalf("unavailable action = %#v", action) } result := application.Dispatch(app.Intent{Kind: app.ApplyProfileToTarget, TargetID: adapter.ID()}) if result.Err == nil || !strings.Contains(result.Err.Error(), "unavailable") || len(adapter.calls) != 0 { t.Fatalf("unavailable action result = %#v, calls=%#v", result, adapter.calls) } } func TestApplicationRejectsProfileDependentIntentWithoutActiveProfile(t *testing.T) { adapter := &fakeTarget{available: true, state: target.External} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter}) if err != nil { t.Fatal(err) } if result := application.Dispatch(app.Intent{Kind: app.RefreshTargets}); result.Err != nil { t.Fatal(result.Err) } result := application.Dispatch(app.Intent{Kind: app.ApplyProfileToTarget, TargetID: adapter.ID()}) if result.Err == nil || !strings.Contains(result.Err.Error(), "no Active Profile") || len(adapter.calls) != 0 { t.Fatalf("missing Active Profile result = %#v, calls=%#v", result, adapter.calls) } } func TestApplicationReapplyAndRestoreFailuresPublishFreshDriftedState(t *testing.T) { for _, test := range []struct { name string intent app.IntentKind err string }{ {name: "reapply", intent: app.ReapplyActiveProfile, err: "write failed"}, {name: "restore ownership loss", intent: app.RestoreOriginal, err: "restoration evidence is insufficient; refusing to overwrite external changes"}, } { t.Run(test.name, func(t *testing.T) { adapter := &fakeTarget{available: true, state: target.Drifted, setErr: errors.New(test.err)} application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter}) if err != nil { t.Fatal(err) } createAndActivate(t, application) if result := application.Dispatch(app.Intent{Kind: app.RefreshTargets}); result.Err != nil { t.Fatal(result.Err) } result := application.Dispatch(app.Intent{Kind: test.intent, TargetID: adapter.ID()}) if result.Err == nil || !strings.Contains(result.Err.Error(), test.err) || result.Snapshot.Targets[0].State != target.Drifted || result.Snapshot.Targets[0].Evidence != "actual state" { t.Fatalf("failure = %#v", result) } if len(adapter.calls) != 1 || adapter.calls[0] != (test.intent == app.ReapplyActiveProfile) { t.Fatalf("SetDesired calls = %#v", adapter.calls) } }) } } func TestApplicationSnapshotDisablesProfileDependentActionsWithoutActiveProfile(t *testing.T) { adapters := []target.Target{ targetWithID{fakeTarget: &fakeTarget{available: true, state: target.Disabled}, id: "disabled"}, targetWithID{fakeTarget: &fakeTarget{available: true, state: target.External}, id: "external"}, targetWithID{fakeTarget: &fakeTarget{available: true, state: target.Drifted}, id: "drifted"}, } application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), adapters) if err != nil { t.Fatal(err) } if result := application.Dispatch(app.Intent{Kind: app.RefreshTargets}); result.Err != nil { t.Fatal(result.Err) } snapshot := application.Snapshot() for _, index := range []int{0, 1} { action := snapshot.Targets[index].PrimaryAction if action.Available || !strings.Contains(action.DisabledReason, "no Active Profile") { t.Errorf("profile-dependent primary action = %#v", action) } } drift := snapshot.Targets[2] if !drift.PrimaryAction.Available { t.Fatalf("Restore Original should keep Resolve available: %#v", drift.PrimaryAction) } if got := drift.DriftResolutionActions[0]; got.Available || !strings.Contains(got.DisabledReason, "no Active Profile") { t.Fatalf("Reapply action without Active Profile = %#v", got) } if got := drift.DriftResolutionActions[1]; !got.Available { t.Fatalf("Restore Original action without Active Profile = %#v", got) } }