okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
61c43ca
main
1"""An ordinary HTTP client can recover identified chat without CLI or private APIs.""" 2 3import http .client 4import json 5import threading 6import unittest 7import urllib .parse 8 9from harness import Albedo ,Provider ,operation_id ,text 10 11 12class HTTPConsumerTest (unittest .TestCase ): 13def read_json (self ,response ): 14self .assertEqual ( 15 (response .getheader ("Content-Type" )or "" ).split (";" ,1 )[0 ], 16"application/json" , 17 ) 18payload = response .read (1048577 ) 19self .assertLessEqual (len (payload ),1048576 ) 20return json .loads (payload ) 21 22def test_identified_chat_reconnects_and_reconciles_durable_history (self ): 23requested = threading .Event () 24release = threading .Event () 25 26def reply (_ ): 27requested .set () 28if not release .wait (20 ): 29raise AssertionError ("consumer did not release provider" ) 30return text ("ordinary HTTP answer" ) 31 32provider = Provider (reply ) 33self .addCleanup (provider .close ) 34self .addCleanup (release .set ) 35with Albedo (provider )as app : 36address = urllib .parse .urlsplit (app .base ) 37headers = { 38"Authorization" :"Bearer " + app .connection ["token" ], 39"Content-Type" :"application/json" , 40 } 41connection = http .client .HTTPConnection ( 42address .hostname ,address .port ,timeout = 20 43 ) 44self .addCleanup (connection .close ) 45session_id = operation_id () 46intent = { 47"kind" :"new" , 48"workspace" :str (app .workspace ), 49"provider_profile" :app .profile , 50 } 51resource = f"/sessions/ { session_id } " 52connection .request ( 53"PUT" ,resource ,json .dumps (intent ), {** headers ,"If-None-Match" :"*" } 54 ) 55response = connection .getresponse () 56if response .status != 201 : 57self .fail ( 58f"session creation returned { response . status } : { response . read ()!r } " 59 ) 60self .assertEqual (response .getheader ("Location" ),resource ) 61created = self .read_json (response ) 62self .assertEqual (created ["id" ],session_id ) 63self .assertEqual ( 64created ["creation" ]["submitted" ]["workspace" ],intent ["workspace" ] 65 ) 66 67connection .request ("GET" ,resource ,headers = headers ) 68snapshot = self .read_json (connection .getresponse ()) 69cursor = snapshot ["cursor" ] 70input_id = operation_id () 71input_resource = f" { resource } /inputs/ { input_id } " 72message = {"kind" :"message" ,"text" :"ordinary HTTP question" } 73connection .request ("PUT" ,input_resource ,json .dumps (message ),headers ) 74accepted_response = connection .getresponse () 75self .assertEqual (accepted_response .status ,202 ) 76accepted = self .read_json (accepted_response ) 77self .assertEqual (accepted ["id" ],input_id ) 78self .assertEqual (accepted ["admission" ],"accepted" ) 79self .assertTrue (requested .wait (10 ),"input did not reach provider" ) 80 81# Resolve a lost acknowledgement through the resource itself. 82connection .request ("GET" ,input_resource ,headers = headers ) 83self .assertEqual (self .read_json (connection .getresponse ())["id" ],input_id ) 84connection .request ("PUT" ,input_resource ,json .dumps (message ),headers ) 85duplicate_response = connection .getresponse () 86self .assertEqual (duplicate_response .status ,202 ) 87self .assertEqual (self .read_json (duplicate_response )["id" ],input_id ) 88 89query = urllib .parse .urlencode ( 90 { 91"after_generation" :cursor ["generation" ], 92"after_seq" :cursor ["sequence" ], 93 } 94 ) 95stream = http .client .HTTPConnection ( 96address .hostname ,address .port ,timeout = 20 97 ) 98self .addCleanup (stream .close ) 99stream .request ( 100"GET" , 101resource + "?" + query , 102headers = {** headers ,"Accept" :"text/event-stream" }, 103 ) 104response = stream .getresponse () 105self .assertEqual (response .status ,200 ) 106self .assertEqual ( 107 (response .getheader ("Content-Type" )or "" ).split (";" ,1 )[0 ], 108"text/event-stream" , 109 ) 110release .set () 111consumed = cursor ["sequence" ] 112finished = False 113for line in response : 114if not line .startswith (b"data: " ): 115continue 116self .assertLessEqual (len (line [6 :].rstrip (b"\r\n" )),1048576 ) 117batch = json .loads (line [6 :]) 118self .assertEqual (batch ["generation" ],cursor ["generation" ]) 119sequences = [event ["sequence" ]for event in batch ["events" ]] 120self .assertEqual ( 121sequences ,list (range (consumed + 1 ,consumed + 1 + len (sequences ))) 122 ) 123self .assertEqual ( 124batch ["cursor" ],sequences [- 1 ]if sequences else consumed 125 ) 126consumed = batch ["cursor" ] 127if any (event ["type" ]== "turn_completed" for event in batch ["events" ]): 128finished = True 129break 130self .assertTrue (finished ,"watch closed without turn outcome" ) 131response .close () 132stream .close () 133 134# Reconnect with the saved pair, never an SSE transport ID. 135reconnect = http .client .HTTPConnection ( 136address .hostname ,address .port ,timeout = 20 137 ) 138self .addCleanup (reconnect .close ) 139query = urllib .parse .urlencode ( 140 {"after_generation" :cursor ["generation" ],"after_seq" :consumed } 141 ) 142reconnect .request ( 143"GET" , 144resource + "?" + query , 145headers = {** headers ,"Accept" :"text/event-stream" }, 146 ) 147resumed = reconnect .getresponse () 148for line in resumed : 149if line .startswith (b"data: " ): 150self .assertLessEqual (len (line [6 :].rstrip (b"\r\n" )),1048576 ) 151batch = json .loads (line [6 :]) 152self .assertEqual (batch ["generation" ],cursor ["generation" ]) 153self .assertFalse ( 154any (event ["type" ]== "reset" for event in batch ["events" ]) 155 ) 156self .assertTrue ( 157all (event ["sequence" ]> consumed for event in batch ["events" ]) 158 ) 159break 160else : 161self .fail ("reconnect closed without a batch" ) 162resumed .close () 163reconnect .close () 164 165connection .request ("GET" ,resource + "/history" ,headers = headers ) 166history = self .read_json (connection .getresponse ()) 167users = [entry for entry in history ["items" ]if entry ["kind" ]== "user" ] 168self .assertEqual ([entry ["input_id" ]for entry in users ], [input_id ]) 169self .assertEqual ( 170 [ 171part ["text" ] 172for entry in users 173for part in entry ["content" ] 174if part ["kind" ]== "text" 175 ], 176 [message ["text" ]], 177 ) 178self .assertIn ( 179"ordinary HTTP answer" , 180 [ 181part ["text" ] 182for entry in history ["items" ] 183for part in entry ["content" ] 184if part ["kind" ]== "text" 185 ], 186 ) 187connection .request ("GET" ,input_resource ,headers = headers ) 188outcome = self .read_json (connection .getresponse ()) 189self .assertEqual (outcome ["delivery" ],"committed" ) 190self .assertEqual (outcome ["turn" ]["state" ],"completed" ) 191self .assertEqual (len (provider .requests ),1 )