okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
4434577
main
1// A controlled daemon exposes unnecessary list requests and preserves exact 2// error routing; real-daemon E2E covers the CLI commands and shortened IDs. 3package app 4 5import ( 6"context" 7"encoding/json" 8"errors" 9"fmt" 10"net" 11"net/http" 12"net/http/httptest" 13"slices" 14"strings" 15"testing" 16 17"albedo/cli/internal/daemon" 18"albedo/cli/internal/testwire" 19) 20 21func TestSessionResolutionReadsFullIDsAndListsOnlyPrefixes (t * testing.T ) { 22const id = "019c1234-5678-7000-8000-000000000001" 23const sibling = "019c1234-5678-7000-8000-000000000002" 24for _ ,scenario := range []struct { 25name ,requested ,wantID ,wantError string 26status int 27ids []string 28paths []string 29 }{ 30 { 31name :"full" ,requested :id ,wantID :id , 32paths : []string {"/sessions/" + id + "?view=configuration" }, 33 }, 34 { 35name :"missing full" ,requested :id ,status :404 , 36wantError :fmt .Sprintf ("no session matches %q; run albedo sessions to see the available sessions" ,id ), 37paths : []string {"/sessions/" + id + "?view=configuration" }, 38 }, 39 { 40name :"refused full" ,requested :id , 41status :403 ,wantError :"refused" , 42paths : []string {"/sessions/" + id + "?view=configuration" }, 43 }, 44 { 45name :"prefix" ,requested :id [:8 ], 46ids : []string {id },wantID :id , 47paths : []string {"/sessions?scope=roots&limit=200" }, 48 }, 49 { 50name :"exact match wins" ,requested :id [:8 ], 51ids : []string {id ,id [:8 ]},wantID :id [:8 ], 52paths : []string {"/sessions?scope=roots&limit=200" }, 53 }, 54 { 55name :"ambiguous prefix" ,requested :id [:8 ], 56ids : []string {id ,sibling },wantError :"more than one session ID" , 57paths : []string {"/sessions?scope=roots&limit=200" }, 58 }, 59 { 60name :"missing prefix" ,requested :"missing" , 61wantError :"no session matches" , 62paths : []string {"/sessions?scope=roots&limit=200" }, 63 }, 64 { 65name :"uppercase stays exact" ,requested :strings .ToUpper (id ), 66wantError :"no session matches" , 67paths : []string {"/sessions?scope=roots&limit=200" }, 68 }, 69 } { 70t .Run (scenario .name ,func (t * testing.T ) { 71var paths []string 72server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter ,r * http.Request ) { 73paths = append (paths ,r .URL .RequestURI ()) 74w .Header ().Set ("Content-Type" ,"application/json" ) 75if scenario .status != 0 { 76w .WriteHeader (scenario .status ) 77_ = json .NewEncoder (w ).Encode (map [string ]any {"status" :scenario .status ,"code" :"resource_not_found" ,"detail" :"refused" }) 78return 79 } 80if r .URL .Path == "/sessions" { 81items := []any {} 82for _ ,sessionID := range scenario .ids { 83items = append (items ,testwire .Session (sessionID ,testwire .GenerationA ,0 )) 84 } 85_ = json .NewEncoder (w ).Encode (map [string ]any {"items" :items ,"next" :nil }) 86return 87 } 88session := testwire .Session (id ,testwire .GenerationA ,0 ) 89resource := session ["configuration_resource" ].(map [string ]any ) 90w .Header ().Set ("ETag" ,resource ["etag" ].(string )) 91_ = json .NewEncoder (w ).Encode (resource ["value" ]) 92 })) 93defer server .Close () 94connection := daemon .NewConnection (daemon.ConnectionSnapshot {Port :server .Listener .Addr ().(* net.TCPAddr ).Port },nil ) 95defer connection .HTTPClient ().CloseIdleConnections () 96resolved ,err := resolveSession (t .Context (),connection ,scenario .requested ) 97if resolved != scenario .wantID || (scenario .wantError == "" && err != nil )|| (scenario .wantError != "" && (err == nil || ! strings .Contains (err .Error (),scenario .wantError ))) { 98t .Fatalf ("resolution: id=%q error=%v" ,resolved ,err ) 99 } 100if scenario .status == 403 { 101if problem ,ok := errors.AsType [* daemon.APIError ](err );! ok || problem .StatusCode != 403 { 102t .Fatalf ("lost original refusal: %v" ,err ) 103 } 104 } 105if ! slices .Equal (paths ,scenario .paths ) { 106t .Fatalf ("requests=%v, want %v" ,paths ,scenario .paths ) 107 } 108 }) 109 } 110} 111 112func TestPrepareOpenReusesBootstrapReads (t * testing.T ) { 113const root = "019c1234-5678-7000-8000-000000000001" 114const child = "019c1234-5678-7000-8000-000000000002" 115for _ ,scenario := range []struct { 116name ,requested ,selected ,wantError string 117roots []string 118status int 119cancel bool 120 }{ 121 {name :"root full" ,requested :root ,selected :root ,roots : []string {root }}, 122 {name :"root prefix" ,requested :root [:8 ],selected :root ,roots : []string {root }}, 123 {name :"child full" ,requested :child ,selected :child ,roots : []string {root }}, 124 {name :"missing full" ,requested :child ,roots : []string {root },status :http .StatusNotFound ,wantError :"no session matches" }, 125 {name :"refused full" ,requested :child ,roots : []string {root },status :http .StatusForbidden ,wantError :"refused" }, 126 {name :"cancelled capture" ,requested :child ,roots : []string {root },cancel :true }, 127 {name :"ambiguous prefix" ,requested :root [:8 ],roots : []string {root ,child },wantError :"more than one session ID" }, 128 {name :"missing prefix" ,requested :"missing" ,roots : []string {root },wantError :"no session matches" }, 129 {name :"uppercase full" ,requested :strings .ToUpper (root ),roots : []string {root },wantError :"no session matches" }, 130 } { 131t .Run (scenario .name ,func (t * testing.T ) { 132ctx ,cancel := context .WithCancel (t .Context ()) 133defer cancel () 134var paths []string 135server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter ,r * http.Request ) { 136paths = append (paths ,r .URL .RequestURI ()) 137w .Header ().Set ("Content-Type" ,"application/json" ) 138switch r .URL .Path { 139case "/sessions" : 140if r .Method != http .MethodGet { 141t .Error ("failed lookup attempted to create a replacement session" ) 142w .WriteHeader (http .StatusBadRequest ) 143return 144 } 145items := []any {} 146for _ ,id := range scenario .roots { 147items = append (items ,testwire .Session (id ,testwire .GenerationA ,0 )) 148 } 149_ = json .NewEncoder (w ).Encode (map [string ]any {"items" :items ,"next" :nil }) 150case "/settings" : 151settings := testwire .Settings () 152settings ["providers" ].(map [string ]any )["default_profile" ]= "fixture" 153_ = json .NewEncoder (w ).Encode (settings ) 154case "/sessions/" + scenario .requested ,"/sessions/" + scenario .selected : 155if scenario .cancel { 156cancel () 157<- r .Context ().Done () 158return 159 } 160if scenario .status != 0 { 161w .WriteHeader (scenario .status ) 162_ = json .NewEncoder (w ).Encode (map [string ]any {"status" :scenario .status ,"code" :"resource_not_found" ,"detail" :"refused" }) 163return 164 } 165captured := testwire .Session (scenario .selected ,testwire .GenerationA ,0 ) 166captured ["name" ]= "Captured session" 167_ = json .NewEncoder (w ).Encode (captured ) 168default : 169t .Errorf ("unexpected request %s" ,r .URL .RequestURI ()) 170w .WriteHeader (http .StatusBadRequest ) 171 } 172 })) 173defer server .Close () 174connection := daemon .NewConnection (daemon.ConnectionSnapshot {Port :server .Listener .Addr ().(* net.TCPAddr ).Port },nil ) 175defer connection .HTTPClient ().CloseIdleConnections () 176service := Service {Connect :func (context.Context ) (* daemon.Connection ,error ) {return connection ,nil }} 177opened ,err := service .PrepareOpen (ctx ,OpenOptions {SessionID :scenario .requested ,Workspace :"/bootstrap-work" ,Fresh :true }) 178if scenario .cancel { 179if ! errors .Is (err ,context .Canceled ) { 180t .Fatalf ("lost cancellation: %v" ,err ) 181 } 182 }else if scenario .wantError != "" { 183if err == nil || ! strings .Contains (err .Error (),scenario .wantError ) { 184t .Fatalf ("lookup error=%v, want %q" ,err ,scenario .wantError ) 185 } 186if scenario .status == http .StatusForbidden { 187if problem ,ok := errors.AsType [* daemon.APIError ](err );! ok || problem .StatusCode != http .StatusForbidden { 188t .Fatalf ("lost original refusal: %v" ,err ) 189 } 190 } 191 }else { 192if err != nil || opened .Selected == nil || opened .Selected .ID != scenario .selected || opened .Selected .Title != "Captured session" { 193t .Fatalf ("selected=%+v error=%v" ,opened .Selected ,err ) 194 } 195count := 0 196for _ ,session := range opened .Sessions { 197if session .ID == scenario .selected { 198count ++ 199if session .Title != "Captured session" { 200t .Fatal ("bootstrap retained stale root summary" ) 201 } 202 } 203 } 204if count != 1 || opened .LoginRequired || opened .Workspace != "/bootstrap-work" || opened .Settings .Profiles .Active != "fixture" { 205t .Fatalf ("bootstrap: selected count=%d, prepared=%+v" ,count ,opened ) 206 } 207if scenario .selected == child && opened .Sessions [0 ].ID != child { 208t .Fatal ("child was not prepended to bootstrap sessions" ) 209 } 210 } 211wantPaths := []string {"/sessions?scope=roots&limit=200" } 212if scenario .status != 0 || scenario .cancel || scenario .selected != "" { 213id := scenario .selected 214if id == "" { 215id = scenario .requested 216 } 217wantPaths = append (wantPaths ,"/settings" ,"/sessions/" + id + "?tail=0" ) 218 } 219if ! slices .Equal (paths ,wantPaths ) { 220t .Fatalf ("requests=%v, want %v" ,paths ,wantPaths ) 221 } 222 }) 223 } 224}