Files
fn_registry/python/functions/infra/hoppscotch_login_test.py
T
egutierrez eb8dbf66a1 feat(infra): auto-commit con 88 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-11 00:16:46 +02:00

217 lines
6.4 KiB
Python

"""Tests para hoppscotch_login.
Deterministas: monkeypatchean requests.Session para no tocar la red. Simulan el
flujo magic link completo (signin -> mailpit list -> mailpit message -> verify)
y verifican que se devuelven los JWT, asi como los caminos de error.
"""
import sys
import infra.hoppscotch_login # noqa: F401 (registra el submodulo en sys.modules)
# El __init__ del paquete rebinds el nombre `hoppscotch_login` a la funcion,
# que sombrea el submodulo. Recuperamos el submodulo real desde sys.modules
# para monkeypatchear su simbolo `requests`.
mod = sys.modules["infra.hoppscotch_login"]
class _FakeResponse:
"""Respuesta HTTP mockeada minima: status_code, json(), text."""
def __init__(self, status_code=200, json_data=None, text=""):
self.status_code = status_code
self._json = json_data
self.text = text
def json(self):
if self._json is None:
raise ValueError("no json")
return self._json
class _FakeCookies:
def __init__(self, store):
self._store = store
def get(self, name):
return self._store.get(name)
class _FakeSession:
"""Session mockeada: despacha por (method, path) a respuestas predefinidas."""
def __init__(self, routes, cookie_store):
self._routes = routes
self.cookies = _FakeCookies(cookie_store)
self.calls = []
def _dispatch(self, method, url, **kwargs):
self.calls.append((method, url, kwargs))
for (m, fragment), resp in self._routes.items():
if m == method and fragment in url:
return resp
raise AssertionError(f"unexpected {method} {url}")
def post(self, url, **kwargs):
return self._dispatch("POST", url, **kwargs)
def get(self, url, **kwargs):
return self._dispatch("GET", url, **kwargs)
def close(self):
pass
def _install_session(monkeypatch, routes, cookie_store):
session = _FakeSession(routes, cookie_store)
monkeypatch.setattr(mod.requests, "Session", lambda: session)
return session
def test_golden_login_devuelve_tokens(monkeypatch):
routes = {
("POST", "/v1/auth/signin"): _FakeResponse(
201, {"deviceIdentifier": "dev-123"}
),
("GET", "/api/v1/messages"): _FakeResponse(
200,
{
"messages": [
{
"ID": "msg-1",
"Subject": "Sign in to Hoppscotch",
"To": [{"Address": "admin@example.com"}],
}
]
},
),
("GET", "/api/v1/message/msg-1"): _FakeResponse(
200,
{
"Text": "Click here",
"HTML": (
"<a href='http://localhost:3170/?token="
"eyJhbGciOi.JhbGci_Q-zz'>Sign in</a>"
),
},
),
("POST", "/v1/auth/verify"): _FakeResponse(200, {"ok": True}),
}
_install_session(
monkeypatch,
routes,
{"access_token": "ACCESS-JWT", "refresh_token": "REFRESH-JWT"},
)
result = mod.hoppscotch_login("admin@example.com")
assert result["status"] == "ok"
assert result["access_token"] == "ACCESS-JWT"
assert result["refresh_token"] == "REFRESH-JWT"
assert result["email"] == "admin@example.com"
def test_verify_recibe_token_extraido_y_device_identifier(monkeypatch):
routes = {
("POST", "/v1/auth/signin"): _FakeResponse(
201, {"deviceIdentifier": "dev-xyz"}
),
("GET", "/api/v1/messages"): _FakeResponse(
200,
{
"messages": [
{
"ID": "m9",
"Subject": "Sign in",
"To": [{"Address": "admin@example.com"}],
}
]
},
),
("GET", "/api/v1/message/m9"): _FakeResponse(
200,
{"Text": "verify at ?token=abc.DEF-123_456", "HTML": ""},
),
("POST", "/v1/auth/verify"): _FakeResponse(200, {}),
}
session = _install_session(
monkeypatch, routes, {"access_token": "A", "refresh_token": "R"}
)
result = mod.hoppscotch_login("admin@example.com")
assert result["status"] == "ok"
# El POST a verify llevo el token extraido del correo + el deviceIdentifier.
verify_call = next(
c for c in session.calls if c[0] == "POST" and "verify" in c[1]
)
sent = verify_call[2]["json"]
assert sent["token"] == "abc.DEF-123_456"
assert sent["deviceIdentifier"] == "dev-xyz"
def test_error_signin_no_201(monkeypatch):
routes = {
("POST", "/v1/auth/signin"): _FakeResponse(
500, None, text="boom"
),
}
_install_session(monkeypatch, routes, {})
result = mod.hoppscotch_login("admin@example.com")
assert result["status"] == "error"
assert "signin returned 500" in result["error"]
def test_error_correo_no_encontrado(monkeypatch):
routes = {
("POST", "/v1/auth/signin"): _FakeResponse(
201, {"deviceIdentifier": "d"}
),
("GET", "/api/v1/messages"): _FakeResponse(
200,
{
"messages": [
{
"ID": "x",
"Subject": "Newsletter",
"To": [{"Address": "other@example.com"}],
}
]
},
),
}
_install_session(monkeypatch, routes, {})
result = mod.hoppscotch_login("admin@example.com")
assert result["status"] == "error"
assert "no 'Sign in' email" in result["error"]
def test_error_token_no_en_correo(monkeypatch):
routes = {
("POST", "/v1/auth/signin"): _FakeResponse(
201, {"deviceIdentifier": "d"}
),
("GET", "/api/v1/messages"): _FakeResponse(
200,
{
"messages": [
{
"ID": "m",
"Subject": "Sign in",
"To": [{"Address": "admin@example.com"}],
}
]
},
),
("GET", "/api/v1/message/m"): _FakeResponse(
200, {"Text": "no token here", "HTML": "<p>nada</p>"}
),
}
_install_session(monkeypatch, routes, {})
result = mod.hoppscotch_login("admin@example.com")
assert result["status"] == "error"
assert "token not found" in result["error"]