import contextlib import importlib.util import io import os import sys import types import unittest from pathlib import Path from unittest.mock import patch REPO_ROOT = Path(__file__).resolve().parents[3] TOOLS_SCRIPTS_DIR = REPO_ROOT / "tools" / "scripts" if str(TOOLS_SCRIPTS_DIR) not in sys.path: sys.path.insert(0, str(TOOLS_SCRIPTS_DIR)) def load_module(relative_path: str, module_name: str): module_path = REPO_ROOT / relative_path spec = importlib.util.spec_from_file_location(module_name, module_path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None fake_httpx = types.ModuleType("httpx") fake_httpx.ConnectError = type("ConnectError", (Exception,), {}) fake_httpx.TimeoutException = type("TimeoutException", (Exception,), {}) fake_httpx.Response = FakeResponse fake_httpx.get = lambda *args, **kwargs: None fake_dotenv = types.ModuleType("dotenv") fake_dotenv.load_dotenv = lambda *args, **kwargs: None with patch.dict(sys.modules, {"httpx": fake_httpx, "dotenv": fake_dotenv}): spec.loader.exec_module(module) return module class FakeResponse: def __init__(self, status_code: int, payload: dict): self.status_code = status_code self._payload = payload def json(self): return self._payload class WhatsAppConfigLoggingSecurityTests(unittest.TestCase): MODULE_PATHS = [ ("skills/whatsapp-cloud-api/scripts/validate_config.py", "whatsapp_validate_root"), ( "plugins/antigravity-awesome-skills/skills/whatsapp-cloud-api/scripts/validate_config.py", "whatsapp_validate_codex_plugin", ), ( "plugins/antigravity-awesome-skills-claude/skills/whatsapp-cloud-api/scripts/validate_config.py", "whatsapp_validate_claude_plugin", ), ] REQUIRED_ENV = { "WHATSAPP_TOKEN": "token-secret-123", "PHONE_NUMBER_ID": "phone-id-456", "WABA_ID": "waba-id-789", "APP_SECRET": "app-secret-000", "VERIFY_TOKEN": "verify-token-999", } def _run_main(self, module, responses): stdout = io.StringIO() with patch.dict(os.environ, self.REQUIRED_ENV, clear=False): with patch.object(module.httpx, "get", side_effect=responses): with patch.object(module.os.path, "exists", return_value=False): with patch.object(sys, "argv", ["validate_config.py"]): with contextlib.redirect_stdout(stdout): with self.assertRaises(SystemExit) as exit_context: module.main() return exit_context.exception.code, stdout.getvalue() def test_success_output_omits_sensitive_api_values(self): for relative_path, module_name in self.MODULE_PATHS: with self.subTest(relative_path=relative_path): module = load_module(relative_path, module_name) exit_code, output = self._run_main( module, [ FakeResponse( 200, { "display_phone_number": "+39 333 123 4567", "verified_name": "Top Secret Brand", "code_verification_status": "VERIFIED", "quality_rating": "GREEN", }, ), FakeResponse(200, {"data": [{"id": "123"}, {"id": "456"}]}), ], ) self.assertEqual(exit_code, 0) self.assertIn("Detailed API payloads are intentionally omitted", output) self.assertIn("OK - Phone-number endpoint reachable.", output) self.assertIn("OK - WABA phone-numbers endpoint reachable.", output) self.assertNotIn("+39 333 123 4567", output) self.assertNotIn("Top Secret Brand", output) self.assertNotIn("VERIFIED", output) self.assertNotIn("GREEN", output) def test_failure_output_omits_error_payload_details(self): for relative_path, module_name in self.MODULE_PATHS: with self.subTest(relative_path=relative_path): module = load_module(relative_path, module_name) exit_code, output = self._run_main( module, [ FakeResponse( 401, { "error": { "message": "Invalid OAuth access token.", "code": 190, } }, ), FakeResponse( 403, { "error": { "message": "User does not have access to this WABA.", "code": 10, } }, ), ], ) self.assertEqual(exit_code, 1) self.assertIn("FAIL - Graph API rejected the phone-number lookup.", output) self.assertIn("FAIL - Graph API rejected the WABA lookup.", output) self.assertNotIn("Invalid OAuth access token.", output) self.assertNotIn("User does not have access to this WABA.", output) self.assertNotIn("190", output) self.assertNotIn("10", output) if __name__ == "__main__": unittest.main()