@@ -245,6 +245,102 @@ func TestRunTokenRecoveryRejectsInvalidatedSessionBeforeRecentRecovery(t *testin
245245 }
246246}
247247
248+ func TestRunTokenRecoveryRejectsSupersededClient (t * testing.T ) {
249+ lc := & LineClient {}
250+ lc .retire ()
251+ var calls int
252+ err := lc .runTokenRecovery (context .Background (), func (context.Context ) error {
253+ calls ++
254+ return nil
255+ })
256+ if ! errors .Is (err , errLineClientSuperseded ) {
257+ t .Fatalf ("err = %v, want errLineClientSuperseded" , err )
258+ }
259+ if calls != 0 {
260+ t .Fatalf ("recovery calls = %d, want 0" , calls )
261+ }
262+ }
263+
264+ func TestRecoverTokenDoesNotReloginAfterForcedLogoutRefresh (t * testing.T ) {
265+ lc := & LineClient {}
266+ var reloginCalls int
267+ err := lc .recoverTokenWith (
268+ context .Background (),
269+ func (context.Context ) error { return errLoggedOut },
270+ func (context.Context ) error {
271+ reloginCalls ++
272+ return nil
273+ },
274+ )
275+ if ! line .IsLoggedOut (err ) {
276+ t .Fatalf ("recoverTokenWith error = %v, want logged-out error" , err )
277+ }
278+ if reloginCalls != 0 {
279+ t .Fatalf ("relogin calls = %d, want 0" , reloginCalls )
280+ }
281+ }
282+
283+ func TestRecoverTokenDoesNotReloginAfterCancellation (t * testing.T ) {
284+ lc := & LineClient {}
285+ ctx , cancel := context .WithCancel (context .Background ())
286+ var reloginCalls int
287+ err := lc .recoverTokenWith (
288+ ctx ,
289+ func (context.Context ) error {
290+ cancel ()
291+ return context .Canceled
292+ },
293+ func (context.Context ) error {
294+ reloginCalls ++
295+ return nil
296+ },
297+ )
298+ if ! errors .Is (err , context .Canceled ) {
299+ t .Fatalf ("recoverTokenWith error = %v, want context.Canceled" , err )
300+ }
301+ if reloginCalls != 0 {
302+ t .Fatalf ("relogin calls = %d, want 0" , reloginCalls )
303+ }
304+ }
305+
306+ func TestForcedLogoutWinsOverInFlightRecovery (t * testing.T ) {
307+ lc := & LineClient {AccessToken : "old-token" }
308+ recoveryStarted := make (chan struct {})
309+ allowRecovery := make (chan struct {})
310+ recoveryDone := make (chan error , 1 )
311+ go func () {
312+ recoveryDone <- lc .runTokenRecovery (context .Background (), func (context.Context ) error {
313+ close (recoveryStarted )
314+ <- allowRecovery
315+ lc .setTokens ("recovered-token" , "" )
316+ return nil
317+ })
318+ }()
319+ <- recoveryStarted
320+
321+ logoutDone := make (chan struct {})
322+ go func () {
323+ lc .markLoggedOutByOtherClient (context .Background (), errLoggedOut )
324+ close (logoutDone )
325+ }()
326+ close (allowRecovery )
327+
328+ if err := <- recoveryDone ; err != nil {
329+ t .Fatalf ("recovery returned error: %v" , err )
330+ }
331+ select {
332+ case <- logoutDone :
333+ case <- time .After (time .Second ):
334+ t .Fatal ("forced logout did not complete after recovery" )
335+ }
336+ if lc .hasAccessToken () {
337+ t .Fatal ("in-flight recovery resurrected the invalidated session" )
338+ }
339+ if ! lc .isSessionInvalidated () {
340+ t .Fatal ("session was not invalidated after in-flight recovery" )
341+ }
342+ }
343+
248344func TestRunTokenRecoverySerializesConcurrentRecovery (t * testing.T ) {
249345 var lc LineClient
250346 var calls int32
0 commit comments