v0.27.2 5960dd8

oauth_test.go RawHistoryFolder

1package coordinator
2
3import (
4	"context"
5	"testing"
6	"time"
7
8	"github.com/stretchr/testify/assert"
9
10	"github.com/bnema/dumber/internal/application/port"
11	"github.com/bnema/dumber/internal/domain/entity"
12	"github.com/bnema/dumber/internal/infrastructure/webkit"
13)
14
15func TestComposeOnClose_Order(t *testing.T) {
16	var calls []string
17
18	composed := composeOnClose(
19		func() { calls = append(calls, "existing") },
20		func() { calls = append(calls, "next") },
21	)
22	composed()
23
24	assert.Equal(t, []string{"existing", "next"}, calls)
25}
26
27func TestComposeOnURIChanged_Order(t *testing.T) {
28	var calls []string
29
30	composed := composeOnURIChanged(
31		func(_ string) { calls = append(calls, "existing") },
32		func(_ string) { calls = append(calls, "next") },
33	)
34	composed("https://example.com")
35
36	assert.Equal(t, []string{"existing", "next"}, calls)
37}
38
39func TestComposeOnLoadChanged_Order(t *testing.T) {
40	var calls []string
41
42	composed := composeOnLoadChanged(
43		func(_ webkit.LoadEvent) { calls = append(calls, "existing") },
44		func(_ webkit.LoadEvent) { calls = append(calls, "next") },
45	)
46	composed(webkit.LoadCommitted)
47
48	assert.Equal(t, []string{"existing", "next"}, calls)
49}
50
51func TestHandlePopupOAuthClose_SuccessSchedulesParentRefresh(t *testing.T) {
52	parentPaneID := entity.PaneID("parent-pane")
53	popupID := port.WebViewID(101)
54
55	c := &ContentCoordinator{
56		webViews:     make(map[entity.PaneID]*webkit.WebView),
57		popupOAuth:   make(map[port.WebViewID]*popupOAuthState),
58		popupRefresh: make(map[entity.PaneID]*time.Timer),
59	}
60
61	c.trackOAuthPopup(popupID, parentPaneID)
62	c.capturePopupOAuthState(popupID, "https://app/callback?code=123")
63	c.handlePopupOAuthClose(context.Background(), popupID)
64
65	c.popupMu.RLock()
66	_, exists := c.popupOAuth[popupID]
67	refreshTimer := c.popupRefresh[parentPaneID]
68	c.popupMu.RUnlock()
69
70	assert.False(t, exists, "oauth state should be removed after close handling")
71	assert.NotNil(t, refreshTimer, "successful oauth close should schedule parent refresh")
72
73	waitFor(t, time.Second, func() bool {
74		c.popupMu.RLock()
75		defer c.popupMu.RUnlock()
76		return c.popupRefresh[parentPaneID] == nil
77	})
78}
79
80func TestHandlePopupOAuthClose_ErrorDoesNotScheduleParentRefresh(t *testing.T) {
81	parentPaneID := entity.PaneID("parent-pane")
82	popupID := port.WebViewID(102)
83
84	c := &ContentCoordinator{
85		webViews:     make(map[entity.PaneID]*webkit.WebView),
86		popupOAuth:   make(map[port.WebViewID]*popupOAuthState),
87		popupRefresh: make(map[entity.PaneID]*time.Timer),
88	}
89
90	c.trackOAuthPopup(popupID, parentPaneID)
91	c.capturePopupOAuthState(popupID, "https://app/callback?error=access_denied")
92	c.handlePopupOAuthClose(context.Background(), popupID)
93
94	c.popupMu.RLock()
95	defer c.popupMu.RUnlock()
96	assert.Nil(t, c.popupRefresh[parentPaneID], "oauth errors should not refresh parent pane")
97}
98
99func TestIsOAuthURL_DoesNotTreatGenericStateParamAsOAuth(t *testing.T) {
100	url := "https://github.com/apps/linear/installations/new?state=Ar4EJk1ao3eyEDgSbYOtG8Cr4"
101
102	assert.False(t, IsOAuthURL(url))
103}
104
105func TestIsOAuthURL_DetectsOAuthAuthorizeRequest(t *testing.T) {
106	url := "https://auth.openai.com/oauth/authorize?response_type=code&client_id=app_123&redirect_uri=http%3A%2F%2Flocalhost%3A1455%2Fauth%2Fcallback&scope=openid+profile+email+offline_access&state=abc"
107
108	assert.True(t, IsOAuthURL(url))
109}
110
111func TestShouldForceCloseOnSafetyTimeout(t *testing.T) {
112	t.Run("does not force close during in-progress auth challenge", func(t *testing.T) {
113		url := "https://accounts.google.com/v3/signin/challenge/dp?TL=AHU8sQabc"
114		assert.False(t, shouldForceCloseOnSafetyTimeout(url))
115	})
116
117	t.Run("does not force close when callback is reached", func(t *testing.T) {
118		url := "http://localhost:1455/auth/callback?code=abc123&state=xyz"
119		assert.False(t, shouldForceCloseOnSafetyTimeout(url))
120	})
121}
122
123func waitFor(t *testing.T, timeout time.Duration, condition func() bool) {
124	t.Helper()
125
126	deadline := time.Now().Add(timeout)
127	for time.Now().Before(deadline) {
128		if condition() {
129			return
130		}
131		time.Sleep(20 * time.Millisecond)
132	}
133	t.Fatalf("condition not met before timeout (%s)", timeout)
134}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close