--- name: hoppscotch_login kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def hoppscotch_login(email: str, *, backend_url: str = \"http://localhost:3170\", mailpit_url: str = \"http://localhost:8025\", timeout_s: float = 15.0) -> dict" description: "Login headless contra un Hoppscotch self-hosted via magic link, leyendo el correo de verificacion desde una instancia Mailpit de pruebas. Reproduce el flujo sin navegador: POST /v1/auth/signin (deviceIdentifier) -> lee el correo 'Sign in' del email en Mailpit -> extrae el token (?token=...) del cuerpo -> POST /v1/auth/verify (Set-Cookie access_token + refresh_token). Devuelve los JWT de sesion que las mutations GraphQL protegidas esperan en la cookie access_token." tags: [hoppscotch, flow-replay, http, infra, auth] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [re, requests] params: - name: email desc: "correo del usuario que inicia sesion. Debe poder recibir el correo de verificacion en la instancia Mailpit indicada (en el self-host de pruebas, admin@example.com)." - name: backend_url desc: "base del backend Hoppscotch sin barra final. Los endpoints REST de auth cuelgan de {backend_url}/v1/auth/signin y /v1/auth/verify. Default http://localhost:3170." - name: mailpit_url desc: "base de la API de Mailpit donde aterriza el correo de verificacion, sin barra final. Default http://localhost:8025." - name: timeout_s desc: "timeout por request HTTP en segundos. Default 15.0." output: "dict. En exito: {status: 'ok', access_token: str, refresh_token: str, email: str}. En error (signin != 201, no llega correo 'Sign in', token no encontrado en el correo, verify != 200, o fallo de transporte): {status: 'error', error: str}. Nunca lanza por errores de red esperables." tested: true tests: - "test_golden_login_devuelve_tokens" - "test_verify_recibe_token_extraido_y_device_identifier" - "test_error_signin_no_201" - "test_error_correo_no_encontrado" - "test_error_token_no_en_correo" test_file_path: "python/functions/infra/hoppscotch_login_test.py" file_path: "python/functions/infra/hoppscotch_login.py" --- ## Ejemplo ```python import sys sys.path.insert(0, "python/functions") from infra.hoppscotch_login import hoppscotch_login from infra.hoppscotch_create_request import hoppscotch_create_request # 1) Obtener un JWT de sesion via magic link (headless, lee el correo de Mailpit). login = hoppscotch_login("admin@example.com") assert login["status"] == "ok", login["error"] token = login["access_token"] # 2) Usar el token para crear una request en una team collection. # El self-host de referencia exige team_id dentro del input. created = hoppscotch_create_request( collection_id="cmq8lt8ta000t0xls4ddy6sdz", method="GET", url="https://api.example.com/ping", title="Ping", team_id="cmq8kn0v500030xls1nvminjy", access_token=token, ) print(created) # {"status": "ok", "id": "...", "title": "Ping"} ``` ## Cuando usarla Cuando necesites un JWT de sesion de un Hoppscotch self-hosted para operar su API GraphQL protegida (crear/editar/borrar requests, gestionar collections) sin abrir el navegador. Es el primer paso de cualquier flujo CRUD del grupo `hoppscotch`: llama esto, captura `access_token`, y paselo a `hoppscotch_create_request` / `hoppscotch_update_request` / `hoppscotch_delete_request` / `hoppscotch_list_requests`. Requiere que el backend mande el correo de verificacion a una instancia Mailpit accesible (entorno de pruebas). ## Gotchas - **El access_token va como cookie, no como header Authorization.** Las mutations GraphQL leen el JWT de la cookie `access_token`. Cada funcion del grupo lo manda con `cookies={"access_token": ...}`. - **El token expira (~24h).** Cuando una llamada GraphQL devuelva un error de auth, re-loguea con `hoppscotch_login` para obtener un access_token fresco. - **Depende de Mailpit.** El flujo lee el correo de verificacion de una instancia Mailpit de pruebas. No funciona contra un backend que mande el correo a un buzon real al que esta funcion no pueda consultar por API. - **Secreto — nunca logear el token en crudo.** `access_token`/`refresh_token` son credenciales de sesion. No los imprimas ni los persistas en claro; trataelos como un secreto (vault/pass) si los guardas entre ejecuciones. - **Coincidencia del correo por subject + destinatario.** Se elige el mensaje mas reciente cuyo destinatario sea `email` y cuyo subject contenga "Sign in". Si hay varios magic links pendientes para el mismo email, se usa el ultimo de la lista. ## Capability growth log v1.0.0 — version inicial. Flujo magic link headless validado contra el self-host vivo (login + CRUD completo) el 10/06/2026.