okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
f0fd5bd
main
1"""Command mutations share conditional edits' checks, persistence, and context reset.""" 2 3import json 4import os 5from pathlib import Path 6import threading 7import unittest 8 9from harness import Albedo ,Provider ,ROOT ,exclusive ,release_fifo ,text ,wait_until 10from inspect_support import Inspection 11from kernel_support import editable_daemon 12 13 14@exclusive 15class SessionConfigureTest (unittest .TestCase ): 16"""Isolated global defaults, catalog, inspection, and copied kernel startup.""" 17 18marker :Path 19 20def setUp (self ): 21self .provider = Provider (lambda _request :text ("done" )) 22self .addCleanup (self .provider .close ) 23self .gate = None 24 25def prepare (app ): 26app .daemon .env ["ALBEDO_INSPECT" ]= "1" 27catalog = { 28"fixture-cloud" : { 29"api" :self .provider .url , 30"models" : { 31model : { 32"id" :model , 33"reasoning_options" : [ 34 {"type" :"effort" ,"values" :efforts } 35 ], 36 } 37for model ,efforts in { 38"first" : ["low" ,"high" ], 39"second" : ["medium" ,"high" ], 40"third" : ["low" ,"medium" ], 41 }.items () 42 }, 43 } 44 } 45 (app .home / "models.json" ).write_text (json .dumps (catalog )) 46app .write_extensions ({"models" : {"refreshHours" :0 }}) 47if ( 48self ._testMethodName 49== "test_commands_reject_changes_during_kernel_boot" 50 ): 51launcher ,bundle = editable_daemon (app .root ) 52app .env ["ALBEDO_DAEMON" ]= str (launcher ) 53self .gate = app .workspace / "kernel-gate" 54self .marker = app .workspace / "kernel-started" 55os .mkfifo (self .gate ) 56gate = self .gate 57self .addCleanup (lambda :gate .exists ()and release_fifo (gate )) 58 (bundle / "albedo_kernel.py" ).write_text ( 59"from pathlib import Path\n" 60f"Path( { str ( self . marker )!r } ).touch()\n" 61f"open( { str ( self . gate )!r } , 'rb').read(1) if Path( { str ( self . gate )!r } ).exists() else None\n" 62"import runpy\n" 63"runpy.run_module('albedo_cells', run_name='__main__', alter_sys=True)\n" 64 ) 65 66self .app = Albedo ( 67self .provider , 68prepare = prepare , 69providers = { 70"fixture" : { 71"extension" :"openai" , 72"baseUrl" :self .provider .url , 73"apiKey" :"fixture-key" , 74"model" :"first" , 75"effort" :"high" , 76"protocol" :"responses" , 77 } 78 }, 79 ).__enter__ () 80self .addCleanup (self .app .__exit__ ,None ,None ,None ) 81self .session = self .app .session () 82self .inspection = Inspection ( 83self .app ,ROOT / "test/e2e/albedo_session_configure_probe.erl" 84 ) 85 86def read (self ,path ): 87with self .app .api (path )as response : 88return json .load (response ) 89 90def configuration (self ): 91return self .read (f"/sessions/ { self . session } ?view=configuration" ) 92 93def defaults (self ): 94return self .read ("/settings?group=providers" ) 95 96def invoke (self ,method ,* values ): 97arguments = [self .session ,* values ] 98erl = ", " .join ( 99"null" if value is None else f"<< { json . dumps ( value ) } >>" 100for value in arguments 101 ) 102return self .inspection .call_json (method ,f"[ { erl } ]" ) 103 104def model (self ,model ,provider = None ,effort = None ): 105return self .invoke ("model" ,model ,provider ,effort ) 106 107def turn (self ): 108self .app .prompt (self .session ,"prepare a request" ).close () 109self .app .idle (self .session ) 110 111def context (self ): 112return self .read (f"/sessions/ { self . session } /context" ) 113 114def test_commands_save_defaults_resolve_effort_and_reset_prepared_context (self ): 115self .turn () 116self .assertEqual (self .context ()["state" ],"ready" ) 117cursor = self .app .stream_page (self .session ) 118selected = self .model ("second" )["ok" ] 119self .assertEqual ((selected ["model" ],selected ["effort" ]), ("second" ,"high" )) 120self .assertEqual (self .context ()["state" ],"pending" ) 121changes = self .app .stream_page (self .session ,cursor )["events" ] 122invalidated = { 123event ["data" ]["kind" ]for event in changes if event ["type" ]== "invalidate" 124 } 125self .assertTrue ({"session" ,"context" }.issubset (invalidated ),changes ) 126new = self .read (f"/sessions/ { self . app . session () } " ) 127self .assertEqual (new ["model" ],"second" ) 128 129self .turn () 130defaults = self .defaults () 131self .assertEqual ( 132self .invoke ("effort" ," LOW " )["error" ], 133"unsupported reasoning effort: low; available: medium, high" , 134 ) 135self .assertEqual (self .context ()["state" ],"ready" ) 136changed = self .invoke ("effort" ," MEDIUM " )["ok" ] 137self .assertEqual (changed ["effort" ],"medium" ) 138self .assertEqual (self .defaults (),defaults ) 139self .assertEqual (self .context ()["state" ],"pending" ) 140 141before = self .configuration () 142rejected = self .model ("third" ,effort = "high" ) 143self .assertIn ("unsupported reasoning effort" ,rejected ["error" ]) 144self .assertEqual (self .configuration (),before ) 145self .assertEqual (self .defaults (),defaults ) 146self .invoke ("effort" ,"high" ) 147self .assertEqual (self .model ("third" )["ok" ]["effort" ],"medium" ) 148self .assertEqual (self .configuration ()["effort" ],"medium" ) 149 150# HTTP edits share mutation validation, but never remember defaults. 151route = f"/sessions/ { self . session } ?view=configuration" 152with self .app .api (route )as response : 153json .load (response ) 154etag = response .headers ["ETag" ] 155defaults = self .defaults () 156with self .app .api ( 157route , 158 {"model" :"second" ,"effort" :"high" }, 159method = "PATCH" , 160headers = {"If-Match" :etag }, 161 )as response : 162json .load (response ) 163self .assertEqual (self .defaults (),defaults ) 164self .turn () 165self .assertEqual ( 166self .provider .requests [- 1 ]["request" ]["reasoning" ]["effort" ],"high" 167 ) 168 169def test_commands_reject_even_unchanged_values_during_a_turn (self ): 170entered ,release = threading .Event (),threading .Event () 171self .addCleanup (release .set ) 172 173def script (_request ): 174entered .set () 175release .wait (20 ) 176return text ("done" ) 177 178self .provider .script = script 179self .app .prompt (self .session ,"hold the turn" ).close () 180self .assertTrue (entered .wait (10 )) 181before ,defaults = self .configuration (),self .defaults () 182try : 183self .assertEqual ( 184self .model ("first" )["error" ],"model must be nonempty and session idle" 185 ) 186self .assertEqual ( 187self .invoke ("effort" ,"high" )["error" ], 188"switching effort requires an idle session" , 189 ) 190self .assertEqual (self .configuration (),before ) 191self .assertEqual (self .defaults (),defaults ) 192route = f"/sessions/ { self . session } ?view=configuration" 193with self .app .api (route )as response : 194json .load (response ) 195etag = response .headers ["ETag" ] 196with self .app .api ( 197route , 198 {"model" :"first" ,"effort" :"high" }, 199method = "PATCH" , 200headers = {"If-Match" :etag }, 201 )as response : 202json .load (response ) 203finally : 204release .set () 205self .app .idle (self .session ) 206 207def test_commands_reject_changes_during_kernel_boot (self ): 208self .app .prompt (self .session ,"wait for the kernel" ).close () 209wait_until ( 210lambda :self .marker .exists (),10 ,"kernel startup did not reach its gate" 211 ) 212before ,defaults = self .configuration (),self .defaults () 213try : 214self .assertEqual ( 215self .model ("second" )["error" ],"model must be nonempty and session idle" 216 ) 217self .assertEqual ( 218self .invoke ("effort" ,"low" )["error" ], 219"switching effort requires an idle session" , 220 ) 221self .assertEqual (self .configuration (),before ) 222self .assertEqual (self .defaults (),defaults ) 223finally : 224assert self .gate is not None 225wait_until ( 226lambda :release_fifo (self .gate ),10 ,"kernel did not open its gate" 227 ) 228self .gate .unlink () 229self .app .idle (self .session ) 230self .assertEqual (self .provider .requests [- 1 ]["request" ]["model" ],"first" ) 231 232 233if __name__ == "__main__" : 234unittest .main ()