| 97 | |
| 98 | |
| 99 | class Runner: |
| 100 | def __init__(self, base_url: str, api_key: str, scenario: dict[str, Any]): |
| 101 | self.base_url = base_url |
| 102 | self.api_key = api_key |
| 103 | self.scenario = scenario |
| 104 | self.vars: dict[str, str] = {} |
| 105 | self._http = httpx.Client(base_url=base_url, timeout=30) |
| 106 | |
| 107 | def close(self): |
| 108 | self._http.close() |
| 109 | |
| 110 | def _raw(self, method: str, path: str, body: Any = None) -> httpx.Response: |
| 111 | headers = {"Authorization": f"Bearer {self.api_key}"} |
| 112 | if body is not None: |
| 113 | headers["Content-Type"] = "application/json" |
| 114 | return self._http.request(method, path, headers=headers, json=body) |
| 115 | |
| 116 | def resolve(self, s: str) -> str: |
| 117 | s = s.replace("{base_url}", self.base_url) |
| 118 | s = s.replace("{api_key}", self.api_key) |
| 119 | for k, v in self.vars.items(): |
| 120 | s = s.replace(f"{{{k}}}", v) |
| 121 | return s |
| 122 | |
| 123 | def resolve_value(self, v: Any) -> Any: |
| 124 | if isinstance(v, str): |
| 125 | return self.resolve(v) |
| 126 | if isinstance(v, list): |
| 127 | return [self.resolve_value(item) for item in v] |
| 128 | if isinstance(v, dict): |
| 129 | return {k: self.resolve_value(val) for k, val in v.items()} |
| 130 | return v |
| 131 | |
| 132 | def auth_override(self, step: dict[str, Any]) -> str | None: |
| 133 | return step.get("auth_override") or self.scenario.get("auth_override") |
| 134 | |
| 135 | def has_auth_override(self, step: dict[str, Any]) -> bool: |
| 136 | return self.auth_override(step) is not None |
| 137 | |
| 138 | def execute_setup(self) -> bool: |
| 139 | """Returns True if scenario should be skipped (needs store access).""" |
| 140 | setup = self.scenario.get("setup") or [] |
| 141 | for s in setup: |
| 142 | if "inject_message" in s or "verify_domain" in s: |
| 143 | return True |
| 144 | |
| 145 | if "register_domain" in s: |
| 146 | domain = self.resolve(s["register_domain"]) |
| 147 | resp = self._raw("POST", "/v1/domains", {"domain": domain}) |
| 148 | if resp.status_code >= 400 and resp.status_code != 409: |
| 149 | resp.raise_for_status() |
| 150 | |
| 151 | if "register_agent" in s: |
| 152 | agent = s["register_agent"] |
| 153 | email = self.resolve(agent["email"]) |
| 154 | resp = self._raw("POST", "/v1/agents", {"email": email}) |
| 155 | if resp.status_code >= 400 and resp.status_code != 409: |
| 156 | resp.raise_for_status() |
no outgoing calls