182 lines
5.3 KiB
Go
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)
|
|
}
|
|
}
|