okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

nekomimi.petcli: gofmt the colab sign-in types and test132f576

main
5.5 KiB155 linesraw
1// Login's lists and removal question are keystroke behaviour the e2e suite
2// reaches only through the daemon's flows: a letter typed into the always-live
3// search must not remove a provider (the old bare "d" did), removal waits for
4// enter, and a text step takes letters as input rather than as actions.
5package tui
6
7import (
8	"albedo/cli/internal/daemon"
9	"strings"
10	"testing"
11
12	"github.com/charmbracelet/x/ansi"
13
14	tea "charm.land/bubbletea/v2"
15)
16
17func TestLoginRemovalAsksThenTakesEnterOnly(t *testing.T) {
18	m := loginModel(120, 30)
19	m, _ = m.Update(tea.KeyPressMsg{Code: 'd', Mod: tea.ModCtrl})
20	if m.Step != StepRemove || !m.confirm.asking() {
21		t.Fatalf("ctrl+d did not ask about the highlighted provider (step %v):\n%s", m.Step, ansi.Strip(m.View()))
22	}
23	m, cmd := m.Update(tea.KeyPressMsg{Code: 'x', Text: "x"})
24	if cmd != nil || m.Step != StepRemove {
25		t.Fatal("a stray key changed the removal question")
26	}
27	m, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyEscape})
28	if m.Step != StepChoose || m.confirm.asking() {
29		t.Fatalf("esc did not keep the provider (step %v)", m.Step)
30	}
31	m, _ = m.Update(tea.KeyPressMsg{Code: 'd', Mod: tea.ModCtrl})
32	m, cmd = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
33	if m.Step != StepSaving || cmd == nil {
34		t.Fatalf("enter did not remove (step %v)", m.Step)
35	}
36}
37
38func TestLoginLetterInTheSearchFiltersInsteadOfRemoving(t *testing.T) {
39	m := loginModel(120, 30)
40	m, _ = m.Update(tea.KeyPressMsg{Code: 'd', Text: "d"})
41	if m.Step != StepChoose || m.confirm.asking() {
42		t.Fatalf("a bare letter started a removal (step %v)", m.Step)
43	}
44	if m.ChoosePicker.input.Value() != "d" {
45		t.Fatalf("the letter did not reach the search: %q", m.ChoosePicker.input.Value())
46	}
47}
48
49func TestLoginFilterFuzzilyFindsAProvider(t *testing.T) {
50	m := loginModel(120, 30)
51	for _, r := range "cla" {
52		m, _ = m.Update(tea.KeyPressMsg{Code: r, Text: string(r)})
53	}
54	view := ansi.Strip(m.View())
55	if !strings.Contains(view, "claude-team") || strings.Contains(view, "Antigravity") {
56		t.Fatalf("the search did not narrow to the claude providers:\n%s", view)
57	}
58	if item, ok := m.ChoosePicker.Highlighted(); !ok || !strings.HasPrefix(item.ID, "use:claude") && !strings.HasPrefix(item.ID, "signin:claude") {
59		t.Fatalf("the best match is not a claude row: %+v", item)
60	}
61}
62
63func TestLoginDetailShowsWithoutEnter(t *testing.T) {
64	m := loginModel(120, 30)
65	view := ansi.Strip(m.View())
66	if !strings.Contains(view, "a saved provider profile") {
67		t.Fatalf("the highlighted provider's detail is not in the pane:\n%s", view)
68	}
69}
70
71func TestLoginKeyEntryTakesLettersAsInput(t *testing.T) {
72	m := loginModel(120, 30)
73	m.askAPIKey()
74	for _, r := range "dx" {
75		m, _ = m.Update(tea.KeyPressMsg{Code: r, Text: string(r)})
76	}
77	if m.TextInput.Value() != "dx" || m.Step != StepAPIKey {
78		t.Fatalf("typing into the key step went elsewhere: %q on step %v", m.TextInput.Value(), m.Step)
79	}
80}
81func TestServiceSignInCompletionSkipsModelsAndReturnsToChoose(t *testing.T) {
82	m := loginModel(120, 30)
83	serviceLogin := daemon.SignIn{Provider: "colab", Label: "Google Colab", Kind: "service", Flows: []string{"browser"}}
84	m.SignIns = append(m.SignIns, serviceLogin)
85	m.startSignIn(serviceLogin)
86
87	serviceAcc := daemon.Account{ID: "acc-colab", Provider: "colab", Label: "colab-user@example.com", Kind: "service"}
88	m, _ = m.Update(signInStatusMsg{
89		State:    "complete",
90		Accounts: []daemon.Account{serviceAcc},
91		Gen:      m.Generation,
92	})
93
94	if m.Step != StepChoose {
95		t.Fatalf("service login completion did not return to StepChoose, got: %v", m.Step)
96	}
97	if !strings.Contains(m.Status, "Signed in to Google Colab") {
98		t.Fatalf("unexpected status after service login completion: %q", m.Status)
99	}
100	if m.Error != "" {
101		t.Fatalf("unexpected error after service login completion: %q", m.Error)
102	}
103	found := false
104	for _, acc := range m.Accounts {
105		if acc.ID == "acc-colab" {
106			found = true
107			break
108		}
109	}
110	if !found {
111		t.Fatal("new service account was not stored in model accounts")
112	}
113}
114
115func TestServiceAccountSelectionDoesNotDraftModelProfile(t *testing.T) {
116	m := loginModel(120, 30)
117	serviceAcc := daemon.Account{ID: "acc-colab", Provider: "colab", Label: "colab-user@example.com", Kind: "service"}
118	m.Accounts = append(m.Accounts, serviceAcc)
119	m.buildChoosePicker()
120
121	cmd := m.chooseAccount(serviceAcc)
122	if cmd != nil {
123		t.Fatalf("expected nil cmd for service account choose, got %v", cmd)
124	}
125	if m.Step != StepChoose {
126		t.Fatalf("expected to remain on StepChoose, got %v", m.Step)
127	}
128	if !strings.Contains(m.Status, "ready for use with tools") {
129		t.Fatalf("unexpected status: %q", m.Status)
130	}
131}
132
133func TestServiceAccountAndSignInNotesInChoosePicker(t *testing.T) {
134	m := loginModel(120, 30)
135	m.SignIns = append(m.SignIns, daemon.SignIn{Provider: "colab", Label: "Google Colab", Kind: "service"})
136	m.Accounts = append(m.Accounts, daemon.Account{ID: "acc-colab", Provider: "colab", Label: "colab-user@example.com", Kind: "service"})
137	m.buildChoosePicker()
138
139	var accountItem, signInItem *PickerItem
140	for i := range m.ChoosePicker.items {
141		item := &m.ChoosePicker.items[i]
142		if strings.HasPrefix(item.ID, "account:") && item.Label == "colab-user@example.com" {
143			accountItem = item
144		}
145		if item.ID == "signin:colab" {
146			signInItem = item
147		}
148	}
149	if accountItem == nil || accountItem.Note != "service account for tools; its tokens stay in creds.json" {
150		t.Fatalf("unexpected service account note: %+v", accountItem)
151	}
152	if signInItem == nil || signInItem.Note != "service sign-in for tools; its tokens stay in creds.json" {
153		t.Fatalf("unexpected service sign-in note: %+v", signInItem)
154	}
155}