@@ -3,12 +3,19 @@ package tokenstore
33import (
44 "context"
55 "errors"
6+ "fmt"
67 "os"
78 "strings"
89 "testing"
910 "time"
11+
12+ "github.com/entireio/cli/internal/procsignal"
1013)
1114
15+ // notReturnedSentinel is the value fn returns when the test expects the
16+ // interrupt/timeout branch to win the select, so this value must never surface.
17+ const notReturnedSentinel = "should not be returned"
18+
1219func TestCallKeyringWithTimeout_ReturnsValueWhenFast (t * testing.T ) {
1320 t .Parallel ()
1421
@@ -55,7 +62,7 @@ func TestCallKeyringWithTimeout_DeadlineExceeded(t *testing.T) {
5562 start := time .Now ()
5663 _ , err := callKeyringWithTimeout ("get" , func () (string , error ) {
5764 time .Sleep (5 * time .Second )
58- return "should not be returned" , nil
65+ return notReturnedSentinel , nil
5966 })
6067 elapsed := time .Since (start )
6168
@@ -94,7 +101,7 @@ func TestCallKeyringWithInterrupt_AbortsOnSignal(t *testing.T) {
94101 _ , err := callKeyringWithInterrupt ("get" , 10 * time .Second , func () (string , error ) {
95102 close (started )
96103 time .Sleep (10 * time .Second ) // never completes within the test
97- return "should not be returned" , nil
104+ return notReturnedSentinel , nil
98105 }, interrupt )
99106 elapsed := time .Since (start )
100107
@@ -109,6 +116,66 @@ func TestCallKeyringWithInterrupt_AbortsOnSignal(t *testing.T) {
109116 }
110117}
111118
119+ // recordInterruptSignal must record a shared SIGINT marker for a Ctrl-C abort
120+ // (wrapped context.Canceled) so the CLI's signal-abort gate recognizes it
121+ // without racing the async top-level handler — but must leave the marker
122+ // untouched for a timeout or any non-abort error. This test mutates the
123+ // process-global procsignal state, so it can't run in parallel.
124+ func TestRecordInterruptSignal (t * testing.T ) {
125+ t .Run ("records SIGINT on interrupt abort" , func (t * testing.T ) {
126+ procsignal .Reset ()
127+ t .Cleanup (procsignal .Reset )
128+
129+ val , err := recordInterruptSignal (callKeyringWithInterruptResult ())
130+ if val != "" || ! errors .Is (err , context .Canceled ) {
131+ t .Fatalf ("passthrough changed value/err: val=%q err=%v" , val , err )
132+ }
133+ if got := procsignal .Load (); got != os .Interrupt {
134+ t .Fatalf ("procsignal.Load() = %v, want SIGINT" , got )
135+ }
136+ })
137+
138+ t .Run ("leaves marker unset on timeout" , func (t * testing.T ) {
139+ procsignal .Reset ()
140+ t .Cleanup (procsignal .Reset )
141+
142+ timeoutErr := fmt .Errorf ("get timed out: %w" , context .DeadlineExceeded )
143+ if _ , err := recordInterruptSignal ("" , timeoutErr ); ! errors .Is (err , context .DeadlineExceeded ) {
144+ t .Fatalf ("passthrough changed err: %v" , err )
145+ }
146+ if got := procsignal .Load (); got != nil {
147+ t .Fatalf ("procsignal.Load() = %v, want nil (timeout is not a signal abort)" , got )
148+ }
149+ })
150+
151+ t .Run ("leaves marker unset on success" , func (t * testing.T ) {
152+ procsignal .Reset ()
153+ t .Cleanup (procsignal .Reset )
154+
155+ if _ , err := recordInterruptSignal ("token" , nil ); err != nil {
156+ t .Fatalf ("passthrough changed err: %v" , err )
157+ }
158+ if got := procsignal .Load (); got != nil {
159+ t .Fatalf ("procsignal.Load() = %v, want nil" , got )
160+ }
161+ })
162+ }
163+
164+ // callKeyringWithInterruptResult produces the exact (val, err) shape the
165+ // interrupt branch returns, so the test exercises recordInterruptSignal against
166+ // the real wrapped error rather than a hand-rolled one.
167+ func callKeyringWithInterruptResult () (string , error ) {
168+ interrupt := make (chan os.Signal , 1 )
169+ interrupt <- os .Interrupt
170+ return callKeyringWithInterrupt ("get" , time .Second , func () (string , error ) {
171+ // The pre-loaded interrupt wins the select immediately; this brief
172+ // sleep just keeps fn from racing it, then the goroutine exits into
173+ // the buffered result channel (no leak).
174+ time .Sleep (50 * time .Millisecond )
175+ return notReturnedSentinel , nil
176+ }, interrupt )
177+ }
178+
112179func TestKeyringTimeout_DefaultWhenUnset (t * testing.T ) {
113180 t .Setenv (keyringTimeoutEnvVar , "" )
114181
0 commit comments