feat(infra): auto-commit con 88 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
"""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"]
|
||||
Reference in New Issue
Block a user