package tui import ( "testing" "bee/audit/internal/app" "bee/audit/internal/platform" "bee/audit/internal/runtimeenv" tea "github.com/charmbracelet/bubbletea" ) func newTestModel() model { return newModel(app.New(platform.New()), runtimeenv.ModeLocal) } func sendKey(t *testing.T, m model, key tea.KeyType) model { t.Helper() next, _ := m.Update(tea.KeyMsg{Type: key}) return next.(model) } func TestUpdateMainMenuCursorNavigation(t *testing.T) { t.Parallel() m := newTestModel() m = sendKey(t, m, tea.KeyDown) if m.cursor != 1 { t.Fatalf("cursor=%d want 1 after down", m.cursor) } m = sendKey(t, m, tea.KeyDown) if m.cursor != 2 { t.Fatalf("cursor=%d want 2 after second down", m.cursor) } m = sendKey(t, m, tea.KeyUp) if m.cursor != 1 { t.Fatalf("cursor=%d want 1 after up", m.cursor) } } func TestUpdateMainMenuEnterActions(t *testing.T) { t.Parallel() tests := []struct { name string cursor int wantScreen screen wantBusy bool wantCmd bool }{ {name: "network", cursor: 0, wantScreen: screenNetwork}, {name: "services", cursor: 1, wantScreen: screenMain, wantBusy: true, wantCmd: true}, {name: "acceptance", cursor: 2, wantScreen: screenAcceptance}, {name: "run audit", cursor: 3, wantScreen: screenMain, wantBusy: true, wantCmd: true}, {name: "export", cursor: 4, wantScreen: screenMain, wantBusy: true, wantCmd: true}, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() m := newTestModel() m.cursor = test.cursor next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) got := next.(model) if got.screen != test.wantScreen { t.Fatalf("screen=%q want %q", got.screen, test.wantScreen) } if got.busy != test.wantBusy { t.Fatalf("busy=%v want %v", got.busy, test.wantBusy) } if (cmd != nil) != test.wantCmd { t.Fatalf("cmd present=%v want %v", cmd != nil, test.wantCmd) } }) } } func TestUpdateConfirmCancelViaKeys(t *testing.T) { t.Parallel() m := newTestModel() m.screen = screenConfirm m.pendingAction = actionRunNvidiaSAT next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRight}) got := next.(model) if got.cursor != 1 { t.Fatalf("cursor=%d want 1 after right", got.cursor) } next, _ = got.Update(tea.KeyMsg{Type: tea.KeyEnter}) got = next.(model) if got.screen != screenAcceptance { t.Fatalf("screen=%q want %q", got.screen, screenAcceptance) } if got.cursor != 0 { t.Fatalf("cursor=%d want 0 after cancel", got.cursor) } } func TestMainMenuSimpleTransitions(t *testing.T) { t.Parallel() tests := []struct { name string cursor int wantScreen screen }{ {name: "network", cursor: 0, wantScreen: screenNetwork}, {name: "acceptance", cursor: 2, wantScreen: screenAcceptance}, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() m := newTestModel() m.cursor = test.cursor next, cmd := m.handleMainMenu() got := next.(model) if cmd != nil { t.Fatalf("expected nil cmd for %s", test.name) } if got.screen != test.wantScreen { t.Fatalf("screen=%q want %q", got.screen, test.wantScreen) } if got.cursor != 0 { t.Fatalf("cursor=%d want 0", got.cursor) } }) } } func TestMainMenuAsyncActionsSetBusy(t *testing.T) { t.Parallel() tests := []struct { name string cursor int }{ {name: "services", cursor: 1}, {name: "run audit", cursor: 3}, {name: "export", cursor: 4}, {name: "check tools", cursor: 5}, {name: "log tail", cursor: 6}, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() m := newTestModel() m.cursor = test.cursor next, cmd := m.handleMainMenu() got := next.(model) if !got.busy { t.Fatalf("busy=false for %s", test.name) } if cmd == nil { t.Fatalf("expected async cmd for %s", test.name) } }) } } func TestEscapeNavigation(t *testing.T) { t.Parallel() tests := []struct { name string screen screen wantScreen screen }{ {name: "network to main", screen: screenNetwork, wantScreen: screenMain}, {name: "services to main", screen: screenServices, wantScreen: screenMain}, {name: "acceptance to main", screen: screenAcceptance, wantScreen: screenMain}, {name: "service action to services", screen: screenServiceAction, wantScreen: screenServices}, {name: "export targets to main", screen: screenExportTargets, wantScreen: screenMain}, {name: "interface pick to network", screen: screenInterfacePick, wantScreen: screenNetwork}, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() m := newTestModel() m.screen = test.screen m.cursor = 3 next, _ := m.updateKey(tea.KeyMsg{Type: tea.KeyEsc}) got := next.(model) if got.screen != test.wantScreen { t.Fatalf("screen=%q want %q", got.screen, test.wantScreen) } if got.cursor != 0 { t.Fatalf("cursor=%d want 0", got.cursor) } }) } } func TestOutputScreenReturnsToPreviousScreen(t *testing.T) { t.Parallel() m := newTestModel() m.screen = screenOutput m.prevScreen = screenNetwork m.title = "title" m.body = "body" next, _ := m.updateKey(tea.KeyMsg{Type: tea.KeyEnter}) got := next.(model) if got.screen != screenNetwork { t.Fatalf("screen=%q want %q", got.screen, screenNetwork) } if got.title != "" || got.body != "" { t.Fatalf("expected output state cleared, got title=%q body=%q", got.title, got.body) } } func TestAcceptanceConfirmFlow(t *testing.T) { t.Parallel() m := newTestModel() m.screen = screenAcceptance m.cursor = 0 next, cmd := m.handleAcceptanceMenu() got := next.(model) if cmd != nil { t.Fatal("expected nil cmd") } if got.screen != screenConfirm { t.Fatalf("screen=%q want %q", got.screen, screenConfirm) } if got.pendingAction != actionRunNvidiaSAT { t.Fatalf("pendingAction=%q want %q", got.pendingAction, actionRunNvidiaSAT) } next, _ = got.updateConfirm(tea.KeyMsg{Type: tea.KeyEsc}) got = next.(model) if got.screen != screenAcceptance { t.Fatalf("screen after esc=%q want %q", got.screen, screenAcceptance) } } func TestExportTargetSelectionOpensConfirm(t *testing.T) { t.Parallel() m := newTestModel() m.screen = screenExportTargets m.targets = []platform.RemovableTarget{{Device: "/dev/sdb1", FSType: "vfat", Size: "16G"}} next, cmd := m.handleExportTargetsMenu() got := next.(model) if cmd != nil { t.Fatal("expected nil cmd") } if got.screen != screenConfirm { t.Fatalf("screen=%q want %q", got.screen, screenConfirm) } if got.pendingAction != actionExportAudit { t.Fatalf("pendingAction=%q want %q", got.pendingAction, actionExportAudit) } if got.selectedTarget == nil || got.selectedTarget.Device != "/dev/sdb1" { t.Fatalf("selectedTarget=%+v want /dev/sdb1", got.selectedTarget) } } func TestInterfacePickStaticIPv4OpensForm(t *testing.T) { t.Parallel() m := newTestModel() m.pendingAction = actionStaticIPv4 m.interfaces = []platform.InterfaceInfo{{Name: "eth0"}} next, cmd := m.handleInterfacePickMenu() got := next.(model) if cmd != nil { t.Fatal("expected nil cmd") } if got.screen != screenStaticForm { t.Fatalf("screen=%q want %q", got.screen, screenStaticForm) } if got.selectedIface != "eth0" { t.Fatalf("selectedIface=%q want eth0", got.selectedIface) } if len(got.formFields) != 4 { t.Fatalf("len(formFields)=%d want 4", len(got.formFields)) } } func TestResultMsgUsesExplicitBackScreen(t *testing.T) { t.Parallel() m := newTestModel() m.screen = screenConfirm next, _ := m.Update(resultMsg{title: "done", body: "ok", back: screenNetwork}) got := next.(model) if got.screen != screenOutput { t.Fatalf("screen=%q want %q", got.screen, screenOutput) } if got.prevScreen != screenNetwork { t.Fatalf("prevScreen=%q want %q", got.prevScreen, screenNetwork) } } func TestConfirmCancelTarget(t *testing.T) { t.Parallel() m := newTestModel() m.pendingAction = actionExportAudit if got := m.confirmCancelTarget(); got != screenExportTargets { t.Fatalf("export cancel target=%q want %q", got, screenExportTargets) } m.pendingAction = actionRunNvidiaSAT if got := m.confirmCancelTarget(); got != screenAcceptance { t.Fatalf("sat cancel target=%q want %q", got, screenAcceptance) } m.pendingAction = actionNone if got := m.confirmCancelTarget(); got != screenMain { t.Fatalf("default cancel target=%q want %q", got, screenMain) } }