--- name: build_hoppscotch_collection kind: function lang: py domain: infra version: "1.0.0" purity: pure signature: "def build_hoppscotch_collection(calls: list[dict], *, name: str = \"Collection\", request_names: list[str] | None = None) -> dict" description: "Helper interno de serializacion del grupo hoppscotch: convierte call specs (method/url/headers/body/body_type) en el formato HoppRESTRequest/coleccion Hoppscotch (request v:2). Lo usan hoppscotch_create_request y hoppscotch_update_request para construir el campo request de las mutations GraphQL del self-host. NO uses el dict resultante para escribir un .json e importarlo a mano: el flujo canonico es operar el self-host por la API (ver docs/capabilities/hoppscotch.md). Pura: solo stdlib, sin red." tags: [hoppscotch, flow-replay, http, infra, python] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "" imports: [] params: - name: calls desc: "lista de call specs (tipicamente la salida de har_extract_calls). Cada elemento es un dict con claves opcionales: method (str), url (str), headers (dict name->value), cookies (dict name->value), body (str|None), body_type (json|form|raw|None). Otras claves como status o sets_cookies se ignoran." - name: name desc: "nombre de la coleccion Hoppscotch resultante. Default 'Collection'." - name: request_names desc: "nombres explicitos por request, alineados por indice con calls. Si se pasa y existe el indice, sobreescribe el nombre derivado. None = derivar todos como ' '." output: "dict de coleccion Hoppscotch JSON-serializable: {\"v\": 1, \"name\", \"folders\": [], \"requests\": [...]}. Cada request lleva v:'2', endpoint, name, method (upper), headers (lista key/value/active con header Cookie inyectado si habia cookies), body (contentType+body segun body_type), auth none, params/requestVariables vacios." tested: true tests: - "test_golden_get_simple" - "test_edge_post_json_con_headers_y_cookies" - "test_request_names_sobreescribe_nombre_derivado" - "test_form_body_genera_contenttype_urlencoded" - "test_raw_body_genera_contenttype_text_plain" - "test_body_type_desconocido_da_body_null" - "test_lista_vacia" - "test_call_spec_sin_url_ni_method" - "test_sin_cookies_no_anade_header_cookie" test_file_path: "python/functions/infra/build_hoppscotch_collection_test.py" file_path: "python/functions/infra/build_hoppscotch_collection.py" --- ## Ejemplo ```python import json from build_hoppscotch_collection import build_hoppscotch_collection # Call specs tal cual salen de har_extract_calls. calls = [ { "method": "GET", "url": "https://api.example.com/api/search?q=foo", "headers": {"Accept": "application/json"}, }, { "method": "POST", "url": "https://api.example.com/login", "headers": {"Content-Type": "application/json"}, "cookies": {"session": "abc", "csrf": "xyz"}, "body": '{"user":"neo","pass":"<>"}', "body_type": "json", }, ] collection = build_hoppscotch_collection(calls, name="Example flow") # { # "v": 1, # "name": "Example flow", # "folders": [], # "requests": [ # { # "v": "2", # "endpoint": "https://api.example.com/api/search?q=foo", # "name": "GET /api/search", # "params": [], # "headers": [{"key": "Accept", "value": "application/json", "active": True}], # "method": "GET", # "auth": {"authType": "none", "authActive": True}, # "preRequestScript": "", # "testScript": "", # "body": {"contentType": None, "body": None}, # "requestVariables": [], # }, # { # "v": "2", # "endpoint": "https://api.example.com/login", # "name": "POST /login", # "params": [], # "headers": [ # {"key": "Content-Type", "value": "application/json", "active": True}, # {"key": "Cookie", "value": "session=abc; csrf=xyz", "active": True}, # ], # "method": "POST", # "auth": {"authType": "none", "authActive": True}, # "preRequestScript": "", # "testScript": "", # "body": {"contentType": "application/json", "body": '{"user":"neo","pass":"<>"}'}, # "requestVariables": [], # }, # ], # } # Listo para escribir a disco e importar en la app Desktop / hopp CLI. with open("flow.collection.json", "w") as f: json.dump(collection, f, indent=2) ``` ## Cuando usarla Usala cuando quieras abrir en la GUI de Hoppscotch unas peticiones que ya grabaste y destilaste con el patron grabar->destilar->reproducir (HAR -> har_filter_flows -> har_extract_calls). Pasas las call specs por esta funcion, guardas el dict resultante como `.json` y lo importas en la app Desktop o con el CLI `hopp`. Es la salida amigable para humanos del flujo de replay: cuando prefieras inspeccionar/tocar las peticiones a mano en el GUI antes de promoverlas a una funcion-accion del registry con http_replay_sequence. La funcion inversa, parse_hoppscotch_collection, reimporta una coleccion editada en el GUI de vuelta a call specs. ## Gotchas - **Genera el formato canonico estable v1/v2.** La coleccion sale como `v:1` y cada request como `v:"2"` — la forma de los fixtures oficiales de Hoppscotch, garantizada importable. Hoppscotch la migra automaticamente a su ultima version interna al importar; no intentes emitir la version "ultima" a mano. - **Las cookies se inyectan como header Cookie.** Si un call spec trae `cookies` no vacio, se anade un unico header `Cookie` al final con formato `k1=v1; k2=v2`. Hoppscotch no tiene un slot de cookies separado en el request, asi que viajan en headers; al reimportar con parse_hoppscotch_collection se vuelven a separar. - **Los secretos NO se sustituyen.** La funcion copia headers, cookies y body tal cual. Tokens de sesion, `Authorization` y contrasenas viajan en claro en el dict resultante. Si quieres parametrizar, es el caller quien debe marcar los valores con `<>` (referencia a variable de environment de Hoppscotch) antes de llamar a esta funcion. NO commitear el `.json` resultante sin redactar. - **Claves extra del call spec se ignoran.** `status`, `sets_cookies` y cualquier otra clave que no sea method/url/headers/cookies/body/body_type no aparecen en la coleccion (son metadata de la captura, no del request a reproducir).