返回 DeepSeek-Reasonix
authentication_app_test.go
根目录 / desktop / authentication_app_test.go
1 package main
2
3 import (
4 "context"
5 "errors"
6 "sync/atomic"
7 "testing"
8 "time"
9
10 "reasonix/internal/control"
11 )
12
13 type desktopAuthenticationRunner struct{ calls atomic.Int32 }
14
15 func (r *desktopAuthenticationRunner) Run(context.Context, string) error {
16 r.calls.Add(1)
17 return nil
18 }
19
20 func newDesktopAuthenticationTab(t *testing.T, app *App, id string, state control.AuthenticationState) (*WorkspaceTab, *control.Controller, *desktopAuthenticationRunner) {
21 t.Helper()
22 runner := &desktopAuthenticationRunner{}
23 ctrl := control.New(control.Options{Runner: runner, Authentication: state})
24 t.Cleanup(ctrl.Close)
25 tab := &WorkspaceTab{ID: id, Ctrl: ctrl, Ready: true, Scope: "global"}
26 app.tabs[id] = tab
27 return tab, ctrl, runner
28 }
29
30 func TestDesktopAuthenticationAdmissionDoesNotCreateTurn(t *testing.T) {
31 app := NewApp()
32 tab, ctrl, runner := newDesktopAuthenticationTab(t, app, "missing", control.AuthenticationState{
33 Status: control.AuthenticationMissingCredential, ProviderName: "relay", ModelRef: "relay/chat",
34 })
35 app.activeTabID = tab.ID
36
37 err := app.SubmitToTab(tab.ID, "must remain a draft")
38 var authErr *control.AuthenticationError
39 if !errors.As(err, &authErr) || authErr.State.Status != control.AuthenticationMissingCredential {
40 t.Fatalf("SubmitToTab error = %v, want missing-credential AuthenticationError", err)
41 }
42 if runner.calls.Load() != 0 || ctrl.Turn() != 0 || len(ctrl.History()) != 0 {
43 t.Fatalf("blocked submit changed runtime: calls=%d turn=%d history=%d", runner.calls.Load(), ctrl.Turn(), len(ctrl.History()))
44 }
45 }
46
47 func TestRetryAuthenticationForTabIsSingleUseAndTabLocal(t *testing.T) {
48 app := NewApp()
49 first, firstCtrl, _ := newDesktopAuthenticationTab(t, app, "first", control.AuthenticationState{
50 Status: control.AuthenticationRejected, ProviderName: "first-provider", HTTPStatus: 401,
51 })
52 second, secondCtrl, _ := newDesktopAuthenticationTab(t, app, "second", control.AuthenticationState{
53 Status: control.AuthenticationRejected, ProviderName: "second-provider", HTTPStatus: 403,
54 })
55 app.activeTabID = second.ID
56
57 state, err := app.RetryAuthenticationForTab(first.ID)
58 if err != nil || !state.Ready() {
59 t.Fatalf("first retry = %+v, %v", state, err)
60 }
61 if _, err := app.RetryAuthenticationForTab(first.ID); err == nil {
62 t.Fatal("second retry was accepted without another rejection")
63 }
64 if got := firstCtrl.AuthenticationState(); !got.Ready() {
65 t.Fatalf("first tab state = %+v, want ready for one attempt", got)
66 }
67 if got := secondCtrl.AuthenticationState(); got.Status != control.AuthenticationRejected || got.ProviderName != "second-provider" {
68 t.Fatalf("retry leaked into second tab: %+v", got)
69 }
70 }
71
72 func TestRetryAuthenticationRejectsMissingCredential(t *testing.T) {
73 app := NewApp()
74 tab, _, _ := newDesktopAuthenticationTab(t, app, "missing", control.AuthenticationState{Status: control.AuthenticationMissingCredential})
75 if _, err := app.RetryAuthenticationForTab(tab.ID); err == nil {
76 t.Fatal("missing credential incorrectly accepted authentication retry")
77 }
78 }
79
80 func TestRetryAuthenticationPublishesAuthoritativeTabRefresh(t *testing.T) {
81 app := NewApp()
82 app.ctx = context.Background()
83 tab, _, _ := newDesktopAuthenticationTab(t, app, "retry-refresh", control.AuthenticationState{Status: control.AuthenticationRejected, ProviderName: "relay", HTTPStatus: 401})
84 app.activeTabID = tab.ID
85 installNoopRuntimeEvents(app)
86 refreshed := make(chan bool, 1)
87 app.runtimeEvents.emit = func(_ context.Context, name string, payload ...any) {
88 if name != tabMetaRefreshEventChannel {
89 return
90 }
91 for _, item := range app.ListTabs() {
92 if item.ID == tab.ID && item.Authentication != nil && item.Authentication.Ready() {
93 refreshed <- true
94 return
95 }
96 }
97 }
98 if _, err := app.RetryAuthenticationForTab(tab.ID); err != nil {
99 t.Fatal(err)
100 }
101 select {
102 case <-refreshed:
103 case <-time.After(5 * time.Second):
104 t.Fatal("retry did not publish a refresh of authoritative authentication state")
105 }
106 }
107
107 lines GO