diff --git a/local_hooks/app_tests/json_tests.py b/local_hooks/app_tests/json_tests.py index 84f8f5d..b98e3db 100644 --- a/local_hooks/app_tests/json_tests.py +++ b/local_hooks/app_tests/json_tests.py @@ -355,7 +355,13 @@ def check_action_param_prefixes(self): action_output = self._get_data_paths(action, parameters=True) action_output_path_values = set(action_output.keys()) - for param_name in action_parameters: + nonsecret_parameters = { + name + for name, options in action_parameters.items() + if options.get("data_type") not in {"password", "encrypted"} + } + + for param_name in nonsecret_parameters: formatted_param = f"action_result.parameter.{param_name}" if formatted_param not in action_output_path_values: # if this check fails that means the validate_json_schema test will also fail which is why it's fine to do this @@ -367,9 +373,9 @@ def check_action_param_prefixes(self): self._app_json["actions"][index]["output"].append(new_output_param) verbose.append(f"Missing action result output for {formatted_param}") - parameters_formatted = set( - f"action_result.parameter.{name}" for name in action_parameters - ) + parameters_formatted = { + f"action_result.parameter.{name}" for name in nonsecret_parameters + } action_output_to_remove = [] for data_path, idx_param in action_output.items(): if data_path not in parameters_formatted: diff --git a/local_hooks/tests/test_static_tests.py b/local_hooks/tests/test_static_tests.py index d8d57de..f049222 100644 --- a/local_hooks/tests/test_static_tests.py +++ b/local_hooks/tests/test_static_tests.py @@ -4,7 +4,7 @@ import subprocess import json from types import SimpleNamespace -from unittest.mock import patch +from unittest.mock import Mock, patch import pytest from pathlib import Path @@ -157,3 +157,65 @@ def test_connector_template_exemption_requires_exact_placeholder(tmp_path: Path) } assert suite._is_connector_template_placeholder() is False + + +@pytest.mark.parametrize("secret_data_type", ["password", "encrypted"]) +def test_action_param_prefixes_does_not_publish_secret_parameters(secret_data_type): + suite = JSONTests.__new__(JSONTests) + suite._parser = SimpleNamespace( + uv_lock_filepath=None, + app_json_name="app.json", + update_app_json=Mock(), + ) + suite._app_json = { + "actions": [ + { + "action": "run query", + "parameters": { + "credentials": {"data_type": secret_data_type}, + "query": {"data_type": "string"}, + }, + "output": [ + { + "data_path": "action_result.parameter.query", + "data_type": "string", + } + ], + } + ] + } + + result = suite.check_action_param_prefixes() + + assert result["success"] is True + suite._parser.update_app_json.assert_not_called() + + +@pytest.mark.parametrize("secret_data_type", ["password", "encrypted"]) +def test_action_param_prefixes_removes_secret_parameter_outputs(secret_data_type): + suite = JSONTests.__new__(JSONTests) + suite._parser = SimpleNamespace( + uv_lock_filepath=None, + app_json_name="app.json", + update_app_json=Mock(), + ) + suite._app_json = { + "actions": [ + { + "action": "run query", + "parameters": {"credentials": {"data_type": secret_data_type}}, + "output": [ + { + "data_path": "action_result.parameter.credentials", + "data_type": secret_data_type, + } + ], + } + ] + } + + result = suite.check_action_param_prefixes() + + assert result["success"] is False + assert suite._app_json["actions"][0]["output"] == [] + suite._parser.update_app_json.assert_called_once_with(suite._app_json)