eb8dbf66a1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
227 lines
7.0 KiB
Python
227 lines
7.0 KiB
Python
"""Tests para hoppscotch_set_environment.
|
|
|
|
Deterministas: monkeypatchean requests.post (capa de red) y pass_get_secret.
|
|
Verifican crear vs actualizar (idempotencia por nombre), resolucion de secretos
|
|
`pass:` (fuerza secret=True, key en resolved_secrets, valor crudo fuera del
|
|
output), y abortar sin llamar la mutation si pass falla.
|
|
|
|
Hay un test e2e real marcado skip por defecto (self-host vivo).
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
import infra.hoppscotch_set_environment # noqa: F401
|
|
|
|
# El __init__ rebinds el nombre a la funcion; recuperamos el submodulo real.
|
|
mod = sys.modules["infra.hoppscotch_set_environment"]
|
|
|
|
|
|
class _FakeResponse:
|
|
def __init__(self, status_code=200, json_data=None):
|
|
self.status_code = status_code
|
|
self._json = json_data
|
|
|
|
def json(self):
|
|
if self._json is None:
|
|
raise ValueError("no json")
|
|
return self._json
|
|
|
|
|
|
def _make_post(call_log, list_envs, mutation_result):
|
|
"""Construye un fake requests.post que distingue listado de mutation.
|
|
|
|
El listado lleva 'teamEnvironments' en la query; cualquier otra es mutation.
|
|
Cada llamada se registra en call_log para inspeccion.
|
|
"""
|
|
|
|
def fake_post(url, **kwargs):
|
|
payload = kwargs["json"]
|
|
query = payload["query"]
|
|
call_log.append({"url": url, "kwargs": kwargs, "query": query})
|
|
if "teamEnvironments" in query:
|
|
return _FakeResponse(
|
|
200, {"data": {"team": {"teamEnvironments": list_envs}}}
|
|
)
|
|
# mutation (create o update)
|
|
field = (
|
|
"updateTeamEnvironment"
|
|
if "updateTeamEnvironment" in query
|
|
else "createTeamEnvironment"
|
|
)
|
|
return _FakeResponse(200, {"data": {field: mutation_result}})
|
|
|
|
return fake_post
|
|
|
|
|
|
def test_crea_cuando_no_existe(monkeypatch):
|
|
calls = []
|
|
monkeypatch.setattr(
|
|
mod.requests,
|
|
"post",
|
|
_make_post(calls, list_envs=[], mutation_result={"id": "env-1", "name": "test_env"}),
|
|
)
|
|
|
|
result = mod.hoppscotch_set_environment(
|
|
"team-1",
|
|
"test_env",
|
|
[{"key": "foo", "value": "bar", "secret": False}],
|
|
access_token="ACCESS-JWT",
|
|
)
|
|
|
|
assert result["status"] == "ok"
|
|
assert result["id"] == "env-1"
|
|
assert result["action"] == "created"
|
|
assert result["resolved_secrets"] == []
|
|
|
|
# Segunda llamada = mutation createTeamEnvironment con las variables.
|
|
mutation = calls[1]
|
|
assert "createTeamEnvironment" in mutation["query"]
|
|
assert mutation["kwargs"]["cookies"] == {"access_token": "ACCESS-JWT"}
|
|
gql_vars = mutation["kwargs"]["json"]["variables"]
|
|
assert gql_vars["n"] == "test_env"
|
|
assert gql_vars["t"] == "team-1"
|
|
sent_vars = json.loads(gql_vars["v"])
|
|
assert sent_vars == [{"key": "foo", "value": "bar", "secret": False}]
|
|
|
|
|
|
def test_actualiza_cuando_existe(monkeypatch):
|
|
calls = []
|
|
monkeypatch.setattr(
|
|
mod.requests,
|
|
"post",
|
|
_make_post(
|
|
calls,
|
|
list_envs=[{"id": "env-existing", "name": "test_env"}],
|
|
mutation_result={"id": "env-existing", "name": "test_env"},
|
|
),
|
|
)
|
|
|
|
result = mod.hoppscotch_set_environment(
|
|
"team-1",
|
|
"test_env",
|
|
[{"key": "foo", "value": "bar"}],
|
|
access_token="A",
|
|
)
|
|
|
|
assert result["status"] == "ok"
|
|
assert result["id"] == "env-existing"
|
|
assert result["action"] == "updated"
|
|
|
|
mutation = calls[1]
|
|
assert "updateTeamEnvironment" in mutation["query"]
|
|
gql_vars = mutation["kwargs"]["json"]["variables"]
|
|
assert gql_vars["id"] == "env-existing"
|
|
|
|
|
|
def test_resuelve_secreto_desde_pass(monkeypatch):
|
|
calls = []
|
|
monkeypatch.setattr(
|
|
mod.requests,
|
|
"post",
|
|
_make_post(calls, list_envs=[], mutation_result={"id": "env-2", "name": "e"}),
|
|
)
|
|
|
|
def fake_pass(path, **kwargs):
|
|
assert path == "apis/lpd"
|
|
return {"status": "ok", "value": "TOP-SECRET-VALUE"}
|
|
|
|
monkeypatch.setattr(mod, "pass_get_secret", fake_pass)
|
|
|
|
result = mod.hoppscotch_set_environment(
|
|
"team-1",
|
|
"e",
|
|
[
|
|
{"key": "plain", "value": "visible", "secret": False},
|
|
{"key": "apikey", "value": "pass:apis/lpd", "secret": False},
|
|
],
|
|
access_token="A",
|
|
)
|
|
|
|
assert result["status"] == "ok"
|
|
# El key resuelto aparece, pero NUNCA el valor crudo.
|
|
assert result["resolved_secrets"] == ["apikey"]
|
|
assert "TOP-SECRET-VALUE" not in json.dumps(result)
|
|
|
|
# La variable resuelta viaja con el valor real y secret=True forzado.
|
|
mutation = calls[1]
|
|
sent_vars = json.loads(mutation["kwargs"]["json"]["variables"]["v"])
|
|
by_key = {v["key"]: v for v in sent_vars}
|
|
assert by_key["apikey"]["value"] == "TOP-SECRET-VALUE"
|
|
assert by_key["apikey"]["secret"] is True
|
|
assert by_key["plain"]["value"] == "visible"
|
|
assert by_key["plain"]["secret"] is False
|
|
|
|
|
|
def test_error_pass_no_llama_mutation(monkeypatch):
|
|
calls = []
|
|
|
|
def fake_post(url, **kwargs):
|
|
calls.append(kwargs["json"]["query"])
|
|
return _FakeResponse(200, {"data": {"team": {"teamEnvironments": []}}})
|
|
|
|
monkeypatch.setattr(mod.requests, "post", fake_post)
|
|
|
|
def fake_pass(path, **kwargs):
|
|
return {"status": "error", "error": "pass not installed"}
|
|
|
|
monkeypatch.setattr(mod, "pass_get_secret", fake_pass)
|
|
|
|
result = mod.hoppscotch_set_environment(
|
|
"team-1",
|
|
"e",
|
|
[{"key": "apikey", "value": "pass:apis/lpd"}],
|
|
access_token="A",
|
|
)
|
|
|
|
assert result["status"] == "error"
|
|
assert "apikey" in result["error"]
|
|
# No se hizo ninguna llamada de red (ni listado ni mutation): aborta antes.
|
|
assert calls == []
|
|
|
|
|
|
@pytest.mark.skip(reason="e2e real contra self-host vivo")
|
|
def test_e2e_create_then_update_live():
|
|
"""End-to-end real contra el Hoppscotch self-host vivo.
|
|
|
|
login -> set_environment("test_env") -> created -> set_environment de nuevo
|
|
-> updated. Limpia el env al final con deleteTeamEnvironment.
|
|
"""
|
|
sys.path.insert(0, "python/functions")
|
|
from infra.hoppscotch_login import hoppscotch_login
|
|
import requests
|
|
|
|
team_id = "cmq8kn0v500030xls1nvminjy"
|
|
token = hoppscotch_login("admin@example.com")["access_token"]
|
|
|
|
first = mod.hoppscotch_set_environment(
|
|
team_id,
|
|
"test_env",
|
|
[{"key": "foo", "value": "bar", "secret": False}],
|
|
access_token=token,
|
|
)
|
|
assert first["status"] == "ok"
|
|
assert first["action"] == "created"
|
|
env_id = first["id"]
|
|
|
|
second = mod.hoppscotch_set_environment(
|
|
team_id,
|
|
"test_env",
|
|
[{"key": "foo", "value": "baz", "secret": False}],
|
|
access_token=token,
|
|
)
|
|
assert second["status"] == "ok"
|
|
assert second["action"] == "updated"
|
|
assert second["id"] == env_id
|
|
|
|
# Cleanup: borra el env de prueba.
|
|
del_q = "mutation($id:ID!){ deleteTeamEnvironment(id:$id) }"
|
|
requests.post(
|
|
"http://localhost:3170/graphql",
|
|
json={"query": del_q, "variables": {"id": env_id}},
|
|
cookies={"access_token": token},
|
|
timeout=15.0,
|
|
)
|