diff --git a/src/openhound_github/resources/organization.py b/src/openhound_github/resources/organization.py index 51435fa..5ebcd57 100644 --- a/src/openhound_github/resources/organization.py +++ b/src/openhound_github/resources/organization.py @@ -4,6 +4,7 @@ from datetime import datetime from threading import Lock from typing import Any, Iterator +from urllib.parse import quote from dlt.sources.helpers import requests from dlt.sources.helpers.rest_client.client import RESTClient @@ -131,6 +132,10 @@ def _client_for_org(ctx: SourceContext, org_login: str) -> RESTClient: return ctx.client +def _encode_path_segment(value: str) -> str: + return quote(value, safe="") + + def _repository_roles(ctx: SourceContext) -> list[dict[str, Any]]: roles: list[dict[str, Any]] = [] for org in ctx.organizations: @@ -1297,6 +1302,7 @@ def environment_variables(environment: Environment, ctx: SourceContext): EnvironmentVariable (EnvironmentVariable): Environment variable record. """ env_name = environment.name + encoded_env_name = _encode_path_segment(env_name) env_node_id = environment.node_id full_repo_name = environment.repository_full_name @@ -1305,7 +1311,7 @@ def environment_variables(environment: Environment, ctx: SourceContext): client = _client_for_org(ctx, environment.org_login) for page in client.paginate( - f"/repos/{full_repo_name}/environments/{env_name}/variables" + f"/repos/{full_repo_name}/environments/{encoded_env_name}/variables" ): for item in page: yield { @@ -1341,10 +1347,11 @@ def environment_branch_policies(environment: Environment, ctx: SourceContext): repo_name = environment.repository_name repo_node_id = environment.repository_node_id env_name = environment.name + encoded_env_name = _encode_path_segment(env_name) env_node_id = environment.node_id client = _client_for_org(ctx, environment.org_login) for page in client.paginate( - f"/repos/{full_repo_name}/environments/{env_name}/deployment-branch-policies" + f"/repos/{full_repo_name}/environments/{encoded_env_name}/deployment-branch-policies" ): for policy in page: yield { @@ -1377,10 +1384,11 @@ def environment_secrets(environment: Environment, ctx: SourceContext): full_repo_name = environment.repository_full_name env_name = environment.name + encoded_env_name = _encode_path_segment(env_name) env_node_id = environment.node_id client = _client_for_org(ctx, environment.org_login) for page in client.paginate( - f"/repos/{full_repo_name}/environments/{env_name}/secrets" + f"/repos/{full_repo_name}/environments/{encoded_env_name}/secrets" ): for secret in page: yield { diff --git a/tests/test_environment_resources.py b/tests/test_environment_resources.py new file mode 100644 index 0000000..65b30a9 --- /dev/null +++ b/tests/test_environment_resources.py @@ -0,0 +1,73 @@ +from types import SimpleNamespace + +import pytest + +from openhound_github.resources.organization import ( + OrgContext, + SourceContext, + environment_branch_policies, + environment_secrets, + environment_variables, +) + + +class _FakeClient: + def __init__(self): + self.paginate_calls: list[tuple[str, dict]] = [] + + def paginate(self, path: str, **kwargs): + self.paginate_calls.append((path, kwargs)) + return iter([]) + + +def _ctx(client: _FakeClient) -> SourceContext: + return SourceContext( + client=client, + organizations=[OrgContext(client=client, org_name="acme")], + ) + + +def _environment(name: str) -> SimpleNamespace: + return SimpleNamespace( + name=name, + node_id="ENV_1", + org_login="acme", + repository_name="repo", + repository_full_name="acme/repo", + repository_node_id="REPO_1", + has_custom_branch_policies=True, + required_reviewers=False, + prevent_self_review=False, + ) + + +@pytest.mark.parametrize( + ("transformer", "suffix"), + [ + (environment_variables, "variables"), + (environment_secrets, "secrets"), + (environment_branch_policies, "deployment-branch-policies"), + ], +) +@pytest.mark.parametrize( + ("environment_name", "encoded_name"), + [ + ("feature/test", "feature%2Ftest"), + ("QA Environment", "QA%20Environment"), + ( + "PCF_PROD_Deployment / deploy-PROD", + "PCF_PROD_Deployment%20%2F%20deploy-PROD", + ), + ], +) +def test_environment_child_resources_encode_environment_names_in_paths( + transformer, suffix: str, environment_name: str, encoded_name: str +) -> None: + client = _FakeClient() + + rows = list(transformer.__wrapped__(_environment(environment_name), _ctx(client))) + + assert rows == [] + assert client.paginate_calls == [ + (f"/repos/acme/repo/environments/{encoded_name}/{suffix}", {}) + ]