okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
ed54422
main
1// A confirmed model switch must survive its following cap failure. Controlled 2// partial responses and stale deliveries cannot be produced deterministically 3// by the healthy daemon, so these tests isolate the UI state transition. 4package tui 5 6import ( 7"albedo/cli/internal/testwire" 8"encoding/json" 9"errors" 10"net/http" 11"testing" 12 13"albedo/cli/internal/config" 14"albedo/cli/internal/daemon" 15) 16 17func TestModelChangeRetainsConfirmedSelectionWhenCapFails (t * testing.T ) { 18for _ ,status := range []int {http .StatusBadRequest ,http .StatusOK } { 19t .Run (http .StatusText (status ),func (t * testing.T ) { 20requests := 0 21conn := commandTestConnection (t ,func (w http.ResponseWriter ,_ * http.Request ) { 22requests ++ 23if requests == 1 { 24writeSessionChange (w ,"session" ,"confirmed" ,"provider" ) 25return 26 } 27if requests == 2 { 28_ = json .NewEncoder (w ).Encode (testwire .SettingsChange ("providers" ,testwire .Settings ()["providers" ])) 29return 30 } 31w .WriteHeader (status ) 32_ ,_ = w .Write ([]byte (`{}` )) 33 }) 34session := & daemon.Session {ID :"session" ,Model :"old" ,Provider :"old-provider" ,Effort :"high" ,ETag :"\"session-a\"" } 35app := NewAppModel (conn ,Bootstrap {Settings : daemon.Settings {Profiles : config.Profiles {ETag :"\"providers-a\"" ,Providers :map [string ]config.Settings {"provider" : {Protocol :"responses" }}}}},session ,"" ,false ,nil ) 36app .SettingsETags = map [string ]string {"models" :"\"models-a\"" } 37t .Cleanup (app .Chat .Close ) 38app .State ,app .ModelPicker .Saving ,app .ModelGen = AppStateModelPicker ,true ,1 39raise := true 40msg := app .changeModelCmd ("requested" ,"provider" ,"" ,& raise ,app .ModelGen ,"provider/cap" )().(modelChangedMsg ) 41if msg .Err == nil || msg .Selection == nil { 42t .Fatalf ("lost confirmed switch or following failure: %+v" ,msg ) 43 } 44if status == http .StatusOK { 45var uncertain * daemon.UncertainOutcomeError 46var protocol * daemon.ProtocolError 47if ! errors .As (msg .Err ,& uncertain )|| ! errors .As (msg .Err ,& protocol ) { 48t .Fatalf ("invalid cap response lost typed uncertainty: %v" ,msg .Err ) 49 } 50 } 51updated ,cmd := app .Update (msg ) 52app = updated .(* AppModel ) 53if cmd != nil || requests != 3 { 54t .Fatalf ("cap failure caused another operation: requests=%d command=%v" ,requests ,cmd != nil ) 55 } 56if app .ActiveSession .Model != "confirmed" || app .Chat .Model != "confirmed" || app .ActiveSession .Provider != "provider" || app .Chat .Provider != "provider" || app .ActiveSession .Protocol != "responses" || app .ActiveSession .Effort != "" || app .Chat .Effort != "" { 57t .Fatalf ("did not apply confirmed switch: %+v" ,app .ActiveSession ) 58 } 59if app .State != AppStateChat || app .ModelPicker .Saving || len (app .Chat .Notices )!= 1 { 60t .Fatal ("confirmed switch did not settle picker and surface the cap failure" ) 61 } 62 }) 63 } 64} 65 66func TestModelChangeFailureDoesNotApplySelectionOrRaiseCap (t * testing.T ) { 67for _ ,status := range []int {http .StatusBadRequest ,http .StatusOK } { 68t .Run (http .StatusText (status ),func (t * testing.T ) { 69requests := 0 70conn := commandTestConnection (t ,func (w http.ResponseWriter ,_ * http.Request ) { 71requests ++ 72w .WriteHeader (status ) 73_ ,_ = w .Write ([]byte (`{}` )) 74 }) 75session := & daemon.Session {ID :"session" ,Model :"old" ,Provider :"provider" ,Effort :"low" ,ETag :"\"session-a\"" } 76app := NewAppModel (conn ,Bootstrap {Settings : daemon.Settings {Profiles : config.Profiles {ETag :"\"providers-a\"" ,Providers :map [string ]config.Settings {"provider" : {Protocol :"responses" }}}}},session ,"" ,false ,nil ) 77app .SettingsETags = map [string ]string {"models" :"\"models-a\"" } 78t .Cleanup (app .Chat .Close ) 79app .State ,app .ModelPicker .Saving ,app .ModelGen = AppStateModelPicker ,true ,1 80raise := true 81msg := app .changeModelCmd ("requested" ,"provider" ,"" ,& raise ,app .ModelGen ,"provider/cap" )().(modelChangedMsg ) 82updated ,cmd := app .Update (msg ) 83app = updated .(* AppModel ) 84if msg .Err == nil || msg .Selection != nil || requests != 1 || cmd != nil { 85t .Fatalf ("failed switch proceeded to cap or invented confirmation: %+v requests=%d" ,msg ,requests ) 86 } 87if app .ActiveSession .Model != "old" || app .Chat .Model != "old" || app .ActiveSession .Effort != "low" || app .Chat .Effort != "low" || app .State != AppStateModelPicker || app .ModelPicker .Saving || app .ModelPicker .Error == "" { 88t .Fatal ("failed switch changed confirmed state or failed to settle picker" ) 89 } 90 }) 91 } 92} 93 94func TestModelChangeIgnoresStaleSessionOrGeneration (t * testing.T ) { 95for _ ,scenario := range []struct { 96name ,sessionID string 97generation int 98 }{ 99 {"session" ,"other" ,1 }, 100 {"generation" ,"session" ,0 }, 101 } { 102t .Run (scenario .name ,func (t * testing.T ) { 103app := NewAppModel (daemon .NewConnection (daemon.ConnectionSnapshot {Port :1 },nil ),Bootstrap {Settings : daemon.Settings {Profiles : config.Profiles {}}},& daemon.Session {ID :"session" ,Model :"old" },"" ,false ,nil ) 104t .Cleanup (app .Chat .Close ) 105app .State ,app .ModelPicker .Saving ,app .ModelGen = AppStateModelPicker ,true ,1 106updated ,cmd := app .Update (modelChangedMsg { 107Selection :& daemon.ModelSelection {Model :"stale" }, 108Err :errors .New ("stale failure" ),SessionID :scenario .sessionID ,Gen :scenario .generation , 109 }) 110app = updated .(* AppModel ) 111if cmd != nil || app .ActiveSession .Model != "old" || app .Chat .Model != "old" || app .State != AppStateModelPicker || ! app .ModelPicker .Saving || app .ModelPicker .Error != "" || len (app .Chat .Notices )!= 0 { 112t .Fatal ("stale result changed current session, picker, or notices" ) 113 } 114 }) 115 } 116} 117 118func TestModelChangePreservesScreenAfterLeavingPicker (t * testing.T ) { 119app := NewAppModel (daemon .NewConnection (daemon.ConnectionSnapshot {Port :1 },nil ),Bootstrap {Settings : daemon.Settings {Profiles : config.Profiles {}}},& daemon.Session {ID :"session" ,Model :"old" },"" ,false ,nil ) 120t .Cleanup (app .Chat .Close ) 121app .State ,app .ModelGen = AppStateSessionPicker ,1 122updated ,cmd := app .Update (modelChangedMsg { 123Selection :& daemon.ModelSelection {Model :"confirmed" },SessionID :"session" ,Gen :1 , 124 }) 125app = updated .(* AppModel ) 126if cmd != nil || app .ActiveSession .Model != "confirmed" || app .Chat .Model != "confirmed" || app .State != AppStateSessionPicker { 127t .Fatal ("confirmed switch failed to update its session or changed an unrelated screen" ) 128 } 129}