import os import re from collections.abc import Callable from functools import wraps from typing import ( Any, Literal, Optional, ) from urllib.parse import urljoin import requests from galaxy_test.base.api_asserts import ( assert_has_keys, assert_status_code_is, assert_status_code_is_ok, ) from galaxy_test.base.api_util import baseauth_headers DEFAULT_TOOL_SHED_BOOTSTRAP_ADMIN_API_KEY = "TEST1234" DEFAULT_TOOL_SHED_USER_API_KEY = None def get_admin_api_key() -> str: """Test admin API key to use for functional tests. This key should be configured as a admin API key and should be able to create additional users and keys. """ for key in ["TOOL_SHED_CONFIG_BOOTSTRAP_ADMIN_API_KEY", "TOOL_SHED_CONFIG_OVERRIDE_BOOTSTRAP_ADMIN_API_KEY"]: value = os.environ.get(key, None) if value: return value return DEFAULT_TOOL_SHED_BOOTSTRAP_ADMIN_API_KEY def get_user_api_key() -> Optional[str]: """Test user API key to use for functional tests. If set, this should drive API based testing - if not set an admin API key will be used to create a new user and API key for tests. """ return os.environ.get("TOOL_SHED_TEST_USER_API_KEY", DEFAULT_TOOL_SHED_USER_API_KEY) def decorate_method(method: Callable): @wraps(method) def wrapper(self: "ShedApiInteractor", route: str, **kwd) -> requests.Response: url = urljoin(self.url, f"api/{route}") kwd = self._append_headers(kwd) return method(url, **kwd) return wrapper class ShedApiInteractor: url: str api_key: str def __init__(self, url: str, api_key: str): self.url = url self.api_key = api_key def create_api_key(self, email: str, password: str) -> str: headers = baseauth_headers(email, password) url = urljoin(self.url, "api/authenticate/baseauth") auth_response = requests.get(url, headers=headers) assert_status_code_is(auth_response, 200) auth_dict = auth_response.json() assert_has_keys(auth_dict, "api_key") return auth_dict["api_key"] def _append_headers(self, kwd): if "admin" in kwd: key = get_admin_api_key() else: key = self.api_key headers = kwd.get("headers", {}) headers["x-api-key"] = key kwd["headers"] = headers return kwd get = decorate_method(requests.get) post = decorate_method(requests.post) put = decorate_method(requests.put) delete = decorate_method(requests.delete) @property def api_version(self) -> Literal["v1", "v2"]: config = self.version() api_version = config.get("api_version", "v1") return api_version def version(self) -> dict[str, Any]: response = self.get("version") response.raise_for_status() return response.json() @property def hg_url_base(self): return self.url def create_user(admin_interactor: ShedApiInteractor, user_dict: dict[str, Any], assert_ok=True) -> dict[str, Any]: email = user_dict["email"] if "password" not in user_dict: user_dict["password"] = "testpass" if "remote_user_email" not in user_dict: user_dict["remote_user_email"] = email response = admin_interactor.post("users", json=user_dict) if assert_ok: assert_status_code_is_ok(response) return response.json() def ensure_user_with_email( admin_api_interactor: ShedApiInteractor, email: str, password: Optional[str] ) -> dict[str, Any]: all_users_response = admin_api_interactor.get("users") try: all_users_response.raise_for_status() except requests.exceptions.HTTPError as e: raise Exception( f"Failed to verify user with email [{email}] exists - perhaps you're targeting the wrong ToolShed server or using an incorrect admin API key. HTTP error: {e}" ) username = email_to_username(email) all_users = all_users_response.json() try: test_user = [user for user in all_users if user["username"] == username][0] except IndexError: request = {"email": email, "username": username, "password": password} test_user = create_user(admin_api_interactor, request, assert_ok=False) return test_user def email_to_username(email: str) -> str: """Pattern used for test user generation - does not use the API.""" return re.sub(r"[^a-z-\d]", "--", email.lower())