"""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": ( "Sign in" ), }, ), ("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": "

nada

"} ), } _install_session(monkeypatch, routes, {}) result = mod.hoppscotch_login("admin@example.com") assert result["status"] == "error" assert "token not found" in result["error"]