okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
47735ec
main
1// A login response can arrive after Bubble Tea has stopped. A controlled peer 2// holds that response until actual terminal shutdown, which E2E cannot schedule. 3package terminal 4 5import ( 6"context" 7"encoding/json" 8"errors" 9"io" 10"net" 11"net/http" 12"net/http/httptest" 13"os" 14"strings" 15"sync" 16"sync/atomic" 17"testing" 18"time" 19 20"albedo/cli/internal/app" 21"albedo/cli/internal/daemon" 22"albedo/cli/internal/testwire" 23) 24 25func TestLoginProgramExitCancelsCreationCompletedAfterShutdown (t * testing.T ) { 26started ,cancelled := make (chan string ,1 ),make (chan string ,1 ) 27release := make (chan struct {}) 28var releaseOnce sync.Once 29releaseLogin := func () {releaseOnce .Do (func () {close (release ) }) } 30var opened ,shutdown atomic.Bool 31server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter ,r * http.Request ) { 32switch { 33case r .URL .Path == "/server" : 34_ ,_ = io .WriteString (w ,testwire .Server ) 35case r .URL .Path == "/settings" : 36_ = json .NewEncoder (w ).Encode (testwire .Settings ()) 37case r .URL .Path == "/auth" : 38_ ,_ = io .WriteString (w ,`{"providers":[{"id":"provider","label":"Provider","detail":"","kind":"model","flows":["browser"],"fields":[]}],"accounts":[]}` ) 39case strings .HasPrefix (r .URL .Path ,"/auth/logins/" ): 40id := strings .TrimPrefix (r .URL .Path ,"/auth/logins/" ) 41if r .Method == http .MethodDelete { 42cancelled <- id 43w .WriteHeader (http .StatusNoContent ) 44return 45 } 46if r .Method != http .MethodPut { 47t .Errorf ("unexpected login request: %s" ,r .Method ) 48http .Error (w ,"unexpected" ,500 ) 49return 50 } 51started <- id 52<- release 53w .Header ().Set ("ETag" ,`"login-a"` ) 54w .WriteHeader (http .StatusCreated ) 55_ = json .NewEncoder (w ).Encode (map [string ]any {"id" :id ,"provider" :"provider" ,"url" :"https://provider.example/login" ,"expires_at" :"2099-01-01T00:00:00Z" ,"state" :"waiting" ,"instructions" :nil ,"progress" :"waiting" ,"accounts" : []any {},"failure" :nil }) 56case r .URL .Path == "/server/shutdown" : 57shutdown .Store (true ) 58http .Error (w ,"unexpected shutdown" ,500 ) 59default : 60t .Errorf ("unexpected request: %s %s" ,r .Method ,r .URL ) 61http .NotFound (w ,r ) 62 } 63 })) 64t .Cleanup (server .Close ) 65t .Cleanup (releaseLogin ) 66conn ,err := daemon .Attach (t .Context (), daemon.ConnectionSnapshot {Port :server .Listener .Addr ().(* net.TCPAddr ).Port ,Token :"token" },nil ) 67if err != nil { 68t .Fatal (err ) 69 } 70t .Cleanup (conn .HTTPClient ().CloseIdleConnections ) 71input ,writer ,err := os .Pipe () 72if err != nil { 73t .Fatal (err ) 74 } 75t .Cleanup (func () {input .Close ();writer .Close () }) 76service := Service {In :input ,Out :io .Discard ,OpenBrowser :func (string ) {opened .Store (true ) }} 77ctx ,cancel := context .WithCancel (t .Context ()) 78t .Cleanup (cancel ) 79finished := make (chan error ,1 ) 80go func () {finished <- service .Login (ctx , app.PreparedOpen {Connection :conn },"" ,"provider" ) }() 81var id string 82select { 83case id = <- started : 84case err := <- finished : 85t .Fatalf ("login exited before creation began: %v" ,err ) 86case <- time .After (5 * time .Second ): 87t .Fatal ("login creation did not start" ) 88 } 89cancel () 90select { 91case err := <- finished : 92if ! errors .Is (err ,context .Canceled ) { 93t .Fatalf ("program cancellation lost: %v" ,err ) 94 } 95case <- time .After (5 * time .Second ): 96t .Fatal ("program kept waiting for the held login response" ) 97 } 98// The event loop has returned before the daemon finishes its admitted request. 99releaseLogin () 100select { 101case got := <- cancelled : 102if got != id { 103t .Fatalf ("cancelled %q instead of the created login %q" ,got ,id ) 104 } 105case <- time .After (5 * time .Second ): 106t .Fatal ("late login creation survived terminal shutdown" ) 107 } 108if opened .Load ()|| shutdown .Load () { 109t .Fatal ("shutdown opened a browser or stopped the shared daemon" ) 110 } 111if _ ,err := daemon .ProbeServer (t .Context (),conn );err != nil { 112t .Fatalf ("shared daemon stopped responding: %v" ,err ) 113 } 114}