Files
2026-07-13 12:35:43 +08:00

182 lines
5.3 KiB
Go

package tui
import (
"testing"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
)
func TestShouldAcceptOAuthPollFiltersStaleMessages(t *testing.T) {
msg := oauthPollMsg{state: "state-a", generation: 1, done: true, message: "ok"}
if shouldAcceptOAuthPoll(msg, "state-a", 2, oauthRemote) {
t.Fatal("accepted poll with stale generation")
}
if shouldAcceptOAuthPoll(msg, "state-b", 1, oauthRemote) {
t.Fatal("accepted poll with mismatched state")
}
if shouldAcceptOAuthPoll(msg, "state-a", 1, oauthIdle) {
t.Fatal("accepted poll while not in remote state")
}
if !shouldAcceptOAuthPoll(msg, "state-a", 1, oauthRemote) {
t.Fatal("rejected valid poll message")
}
}
func TestShouldAcceptOAuthStartFiltersStaleMessages(t *testing.T) {
msg := oauthStartMsg{state: "state-a", generation: 1, url: "https://example.com"}
if shouldAcceptOAuthStart(msg, 2) {
t.Fatal("accepted start with stale generation")
}
if !shouldAcceptOAuthStart(msg, 1) {
t.Fatal("rejected valid start message")
}
}
func TestShouldFailOAuthStatusPoll(t *testing.T) {
if shouldFailOAuthStatusPoll(4, 5) {
t.Fatal("failed too early on transient errors")
}
if !shouldFailOAuthStatusPoll(5, 5) {
t.Fatal("did not fail after max consecutive errors")
}
if !shouldFailOAuthStatusPoll(1, 0) {
t.Fatal("maxErrors<=0 should fail on first error")
}
}
func TestOAuthTabUpdateIgnoresStalePollMsg(t *testing.T) {
m := newOAuthTabModel(nil)
m.state = oauthRemote
m.authState = "state-current"
m.pollGeneration = 2
m.ready = true
m.viewport = viewport.New(80, 24)
m.viewport.SetContent(m.renderContent())
updated, cmd := m.Update(oauthPollMsg{
state: "state-old",
generation: 1,
done: true,
message: "should be ignored",
})
if cmd != nil {
t.Fatal("expected no command for stale poll")
}
if updated.state != oauthRemote {
t.Fatalf("state = %v, want oauthRemote", updated.state)
}
if updated.message != "" {
t.Fatalf("message changed by stale poll: %q", updated.message)
}
}
func TestOAuthTabUpdateAcceptsCurrentPollMsg(t *testing.T) {
m := newOAuthTabModel(nil)
m.state = oauthRemote
m.authState = "state-current"
m.pollGeneration = 3
m.ready = true
m.viewport = viewport.New(80, 24)
m.viewport.SetContent(m.renderContent())
updated, _ := m.Update(oauthPollMsg{
state: "state-current",
generation: 3,
done: true,
message: "Authentication successful",
})
if updated.state != oauthSuccess {
t.Fatalf("state = %v, want oauthSuccess", updated.state)
}
}
func TestOAuthTabEscRemoteIncrementsGenerationAndClearsState(t *testing.T) {
m := newOAuthTabModel(nil)
m.state = oauthRemote
m.authState = "state-to-cancel"
m.authURL = "https://example.com"
m.deviceFlow = true
m.pollGeneration = 4
m.ready = true
m.viewport = viewport.New(80, 24)
m.viewport.SetContent(m.renderContent())
updated, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEsc})
if updated.state != oauthIdle {
t.Fatalf("state = %v, want oauthIdle", updated.state)
}
if updated.pollGeneration != 5 {
t.Fatalf("pollGeneration = %d, want 5", updated.pollGeneration)
}
if updated.authState != "" || updated.authURL != "" || updated.deviceFlow {
t.Fatalf("remote fields not cleared: state=%q url=%q device=%v", updated.authState, updated.authURL, updated.deviceFlow)
}
// client is nil, so cancel command should be nil
if cmd != nil {
t.Fatal("expected nil cancel command when client is nil")
}
}
func TestOAuthTabEscWithActiveCallbackInputCancelsRemoteSession(t *testing.T) {
m := newOAuthTabModel(nil)
m.state = oauthRemote
m.authState = "state-to-cancel"
m.authURL = "https://example.com"
m.deviceFlow = false
m.inputActive = true
m.callbackInput.Focus()
m.callbackInput.SetValue("https://callback.example/?code=abc&state=state-to-cancel")
m.pollGeneration = 7
m.ready = true
m.viewport = viewport.New(80, 24)
m.viewport.SetContent(m.renderContent())
updated, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEsc})
if updated.state != oauthIdle {
t.Fatalf("state = %v, want oauthIdle", updated.state)
}
if updated.pollGeneration != 8 {
t.Fatalf("pollGeneration = %d, want 8", updated.pollGeneration)
}
if updated.inputActive {
t.Fatal("inputActive still true after esc cancel")
}
if updated.callbackInput.Value() != "" {
t.Fatalf("callback input not cleared: %q", updated.callbackInput.Value())
}
if updated.authState != "" || updated.authURL != "" {
t.Fatalf("remote fields not cleared: state=%q url=%q", updated.authState, updated.authURL)
}
// client is nil, so cancel command should be nil
if cmd != nil {
t.Fatal("expected nil cancel command when client is nil")
}
}
func TestOAuthTabStaleStartIsIgnored(t *testing.T) {
m := newOAuthTabModel(nil)
m.state = oauthIdle
m.pollGeneration = 2
m.ready = true
m.viewport = viewport.New(80, 24)
m.viewport.SetContent(m.renderContent())
updated, cmd := m.Update(oauthStartMsg{
url: "https://example.com",
state: "stale-state",
generation: 1,
})
if updated.state != oauthIdle {
t.Fatalf("state = %v, want oauthIdle after stale start", updated.state)
}
// client is nil in this unit test; cancel is skipped but state remains idle.
if cmd != nil {
t.Fatal("expected nil cancel command when client is nil")
}
if updated.authState != "" {
t.Fatalf("stale start should not set authState, got %q", updated.authState)
}
}