From 590a3ad64fce87136b12092a9dc9d9303aa0aa9d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 12 Sep 2026 05:56:50 +0000 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20automatizaci=C3=B3n=20de=20publicac?= =?UTF-8?q?i=C3=B3n=20de=20promociones=20para=20tryonyou.pro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Se agrega script en Python (`scripts/auto_deploy_v2.py`) para validar campos obligatorios (title, description, discount_code, valid_until) en el contenido JSON promocional y desplegarlo en la API de producción. - Se agrega wrapper en Bash (`scripts/auto_deploy_v2.sh`) con mensajes y control de errores en español. - Se incluyen pruebas unitarias en `tests/test_auto_deploy_v2.py` utilizando pytest y mock para simular las llamadas a la red. Co-authored-by: LVT-ENG <214667862+LVT-ENG@users.noreply.github.com> --- promociones_procesadas/promotion_example.json | 6 ++ scripts/auto_deploy_v2.py | 63 +++++++++++ scripts/auto_deploy_v2.sh | 31 ++++++ tests/test_auto_deploy_v2.py | 102 ++++++++++++++++++ 4 files changed, 202 insertions(+) create mode 100644 promociones_procesadas/promotion_example.json create mode 100755 scripts/auto_deploy_v2.py create mode 100755 scripts/auto_deploy_v2.sh create mode 100644 tests/test_auto_deploy_v2.py diff --git a/promociones_procesadas/promotion_example.json b/promociones_procesadas/promotion_example.json new file mode 100644 index 0000000000..8fed1fb753 --- /dev/null +++ b/promociones_procesadas/promotion_example.json @@ -0,0 +1,6 @@ +{ + "title": "Summer Sale 2024", + "description": "Get 50% off on all items this summer!", + "discount_code": "SUMMER50", + "valid_until": "2024-08-31T23:59:59Z" +} diff --git a/scripts/auto_deploy_v2.py b/scripts/auto_deploy_v2.py new file mode 100755 index 0000000000..3e802367b3 --- /dev/null +++ b/scripts/auto_deploy_v2.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# scripts/auto_deploy_v2.py +import sys +import json +import os +import urllib.request +import urllib.error +from typing import Dict, Any + +def validar_promocion(data: Dict[str, Any]) -> None: + campos_requeridos = ["title", "description", "discount_code", "valid_until"] + for campo in campos_requeridos: + if campo not in data: + raise ValueError(f"Falta el campo requerido: {campo}") + +def main(): + if len(sys.argv) < 2: + print("Uso: python3 auto_deploy_v2.py ") + sys.exit(1) + + archivo = sys.argv[1] + + try: + with open(archivo, 'r', encoding='utf-8') as f: + data = json.load(f) + except FileNotFoundError: + print(f"Error: No se encontró el archivo '{archivo}'.") + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: El archivo no es un JSON válido. Detalles: {e}") + sys.exit(1) + + try: + validar_promocion(data) + except ValueError as e: + print(f"Error de validación: {e}") + sys.exit(1) + + api_key = os.environ.get("TRYONYOU_API_KEY") + if not api_key: + print("Error: La variable de entorno TRYONYOU_API_KEY no está definida.") + sys.exit(1) + + api_url = "https://api.tryonyou.pro/v1/promotions" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {api_key}" + } + + try: + req = urllib.request.Request(api_url, data=json.dumps(data).encode("utf-8"), headers=headers, method="POST") + response = urllib.request.urlopen(req) + response_body = response.read().decode('utf-8') + print(f"Despliegue exitoso. Respuesta de la API: {response_body}") + except urllib.error.URLError as e: + print(f"Error en el despliegue de red o HTTP: {e}") + sys.exit(1) + except Exception as e: + print(f"Error inesperado durante el despliegue: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/scripts/auto_deploy_v2.sh b/scripts/auto_deploy_v2.sh new file mode 100755 index 0000000000..edcf629009 --- /dev/null +++ b/scripts/auto_deploy_v2.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# scripts/auto_deploy_v2.sh + +if [ -z "$1" ]; then + echo "Error: Debes proporcionar la ruta a un archivo JSON de promoción." + echo "Uso: $0 " + exit 1 +fi + +FILE_PATH="$1" + +# Obtenemos la ruta absoluta del directorio del script bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PYTHON_SCRIPT="$SCRIPT_DIR/auto_deploy_v2.py" + +echo "Iniciando despliegue de promoción: $FILE_PATH" + +python3 "$PYTHON_SCRIPT" "$FILE_PATH" +EXIT_CODE=$? + +if [ $EXIT_CODE -eq 0 ]; then + echo "=========================================" + echo "✅ Despliegue exitoso." + echo "=========================================" + exit 0 +else + echo "=========================================" + echo "❌ Error en el despliegue. Revisa los logs arriba." + echo "=========================================" + exit $EXIT_CODE +fi diff --git a/tests/test_auto_deploy_v2.py b/tests/test_auto_deploy_v2.py new file mode 100644 index 0000000000..2583c840d7 --- /dev/null +++ b/tests/test_auto_deploy_v2.py @@ -0,0 +1,102 @@ +import os +import json +import pytest +import subprocess +from unittest import mock +import urllib.error + +# Assuming we want to run the python script via subprocess for end-to-end tests +# or import and test specific functions if needed. We'll use subprocess for simplicity +# to cover arguments, env vars and outputs properly. + +SCRIPT_PATH = os.path.join(os.path.dirname(__file__), "..", "scripts", "auto_deploy_v2.py") + +@pytest.fixture +def valid_json_path(tmp_path): + data = { + "title": "Test Promo", + "description": "50% off", + "discount_code": "TEST50", + "valid_until": "2024-12-31T23:59:59Z" + } + p = tmp_path / "valid_promo.json" + p.write_text(json.dumps(data), encoding='utf-8') + return str(p) + +@pytest.fixture +def invalid_json_path(tmp_path): + data = { + "title": "Test Promo", + "discount_code": "TEST50" + # missing description and valid_until + } + p = tmp_path / "invalid_promo.json" + p.write_text(json.dumps(data), encoding='utf-8') + return str(p) + +def test_missing_argument(): + result = subprocess.run( + ["python3", SCRIPT_PATH], + capture_output=True, + text=True + ) + assert result.returncode == 1 + assert "Uso:" in result.stdout + +def test_file_not_found(): + result = subprocess.run( + ["python3", SCRIPT_PATH, "nonexistent_file.json"], + capture_output=True, + text=True + ) + assert result.returncode == 1 + assert "No se encontró el archivo" in result.stdout + +def test_invalid_json(invalid_json_path): + result = subprocess.run( + ["python3", SCRIPT_PATH, invalid_json_path], + capture_output=True, + text=True + ) + assert result.returncode == 1 + assert "Error de validación" in result.stdout + assert "Falta el campo requerido" in result.stdout + +def test_missing_api_key(valid_json_path, monkeypatch): + monkeypatch.delenv("TRYONYOU_API_KEY", raising=False) + result = subprocess.run( + ["python3", SCRIPT_PATH, valid_json_path], + capture_output=True, + text=True, + env=os.environ + ) + assert result.returncode == 1 + assert "TRYONYOU_API_KEY no está definida" in result.stdout + +@mock.patch("urllib.request.urlopen") +def test_successful_deploy(mock_urlopen, valid_json_path, monkeypatch): + monkeypatch.setenv("TRYONYOU_API_KEY", "test_key") + + mock_response = mock.MagicMock() + mock_response.read.return_value = b'{"status": "success"}' + mock_urlopen.return_value = mock_response + + # Import script as module to mock urlopen easily instead of subprocess + import sys + import importlib.util + spec = importlib.util.spec_from_file_location("auto_deploy", SCRIPT_PATH) + auto_deploy = importlib.util.module_from_spec(spec) + + # Mock sys.argv + test_args = ["auto_deploy_v2.py", valid_json_path] + with mock.patch.object(sys, 'argv', test_args): + # We need to capture stdout for asserting + from io import StringIO + with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout: + spec.loader.exec_module(auto_deploy) + auto_deploy.main() + output = mock_stdout.getvalue() + + assert "Despliegue exitoso" in output + assert '{"status": "success"}' in output + mock_urlopen.assert_called_once() From bd6c428407606b43148b92d9603f6d64c3356b53 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 12 Sep 2026 06:00:12 +0000 Subject: [PATCH 2/3] fix: apply ruff lints for imports and unused variables The initial commit failed CI due to python lint issues: - `scripts/auto_deploy_v2.py`: unsorted imports - `tests/test_auto_deploy_v2.py`: unsorted imports and unused import `urllib.error` This resolves the linting errors using `ruff check --fix`. Co-authored-by: LVT-ENG <214667862+LVT-ENG@users.noreply.github.com> --- scripts/auto_deploy_v2.py | 7 ++++--- tests/test_auto_deploy_v2.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/auto_deploy_v2.py b/scripts/auto_deploy_v2.py index 3e802367b3..d0638d84a3 100755 --- a/scripts/auto_deploy_v2.py +++ b/scripts/auto_deploy_v2.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 # scripts/auto_deploy_v2.py +import os import sys import json -import os -import urllib.request import urllib.error -from typing import Dict, Any +import urllib.request +from typing import Any, Dict + def validar_promocion(data: Dict[str, Any]) -> None: campos_requeridos = ["title", "description", "discount_code", "valid_until"] diff --git a/tests/test_auto_deploy_v2.py b/tests/test_auto_deploy_v2.py index 2583c840d7..9ea073fdd3 100644 --- a/tests/test_auto_deploy_v2.py +++ b/tests/test_auto_deploy_v2.py @@ -1,9 +1,9 @@ import os import json -import pytest import subprocess from unittest import mock -import urllib.error + +import pytest # Assuming we want to run the python script via subprocess for end-to-end tests # or import and test specific functions if needed. We'll use subprocess for simplicity From acfe98ba8668a77bfff8377422e3042b4588207e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 12 Sep 2026 06:05:02 +0000 Subject: [PATCH 3/3] fix: type checking issues in auto deploy tests The initial fix applied ruff checking, but `pyright` checks (which are part of `rye run lint` in the CI) failed due to missing type annotations in the tests file. Added type hints to the pytest fixtures and variables, resolved Pyright warnings for unknown parameters and mocked objects, and removed unused imports in `tests/test_auto_deploy_v2.py`. This resolves the remaining type checking issues that failed the CI lint job. Co-authored-by: LVT-ENG <214667862+LVT-ENG@users.noreply.github.com> --- tests/test_auto_deploy_v2.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/test_auto_deploy_v2.py b/tests/test_auto_deploy_v2.py index 9ea073fdd3..aef5b043bc 100644 --- a/tests/test_auto_deploy_v2.py +++ b/tests/test_auto_deploy_v2.py @@ -1,5 +1,6 @@ import os import json +import pathlib import subprocess from unittest import mock @@ -12,7 +13,7 @@ SCRIPT_PATH = os.path.join(os.path.dirname(__file__), "..", "scripts", "auto_deploy_v2.py") @pytest.fixture -def valid_json_path(tmp_path): +def valid_json_path(tmp_path: pathlib.Path) -> str: data = { "title": "Test Promo", "description": "50% off", @@ -24,7 +25,7 @@ def valid_json_path(tmp_path): return str(p) @pytest.fixture -def invalid_json_path(tmp_path): +def invalid_json_path(tmp_path: pathlib.Path) -> str: data = { "title": "Test Promo", "discount_code": "TEST50" @@ -34,7 +35,7 @@ def invalid_json_path(tmp_path): p.write_text(json.dumps(data), encoding='utf-8') return str(p) -def test_missing_argument(): +def test_missing_argument() -> None: result = subprocess.run( ["python3", SCRIPT_PATH], capture_output=True, @@ -43,7 +44,7 @@ def test_missing_argument(): assert result.returncode == 1 assert "Uso:" in result.stdout -def test_file_not_found(): +def test_file_not_found() -> None: result = subprocess.run( ["python3", SCRIPT_PATH, "nonexistent_file.json"], capture_output=True, @@ -52,7 +53,7 @@ def test_file_not_found(): assert result.returncode == 1 assert "No se encontró el archivo" in result.stdout -def test_invalid_json(invalid_json_path): +def test_invalid_json(invalid_json_path: str) -> None: result = subprocess.run( ["python3", SCRIPT_PATH, invalid_json_path], capture_output=True, @@ -62,7 +63,7 @@ def test_invalid_json(invalid_json_path): assert "Error de validación" in result.stdout assert "Falta el campo requerido" in result.stdout -def test_missing_api_key(valid_json_path, monkeypatch): +def test_missing_api_key(valid_json_path: str, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("TRYONYOU_API_KEY", raising=False) result = subprocess.run( ["python3", SCRIPT_PATH, valid_json_path], @@ -74,7 +75,7 @@ def test_missing_api_key(valid_json_path, monkeypatch): assert "TRYONYOU_API_KEY no está definida" in result.stdout @mock.patch("urllib.request.urlopen") -def test_successful_deploy(mock_urlopen, valid_json_path, monkeypatch): +def test_successful_deploy(mock_urlopen: mock.MagicMock, valid_json_path: str, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("TRYONYOU_API_KEY", "test_key") mock_response = mock.MagicMock() @@ -85,6 +86,7 @@ def test_successful_deploy(mock_urlopen, valid_json_path, monkeypatch): import sys import importlib.util spec = importlib.util.spec_from_file_location("auto_deploy", SCRIPT_PATH) + assert spec is not None auto_deploy = importlib.util.module_from_spec(spec) # Mock sys.argv @@ -93,7 +95,10 @@ def test_successful_deploy(mock_urlopen, valid_json_path, monkeypatch): # We need to capture stdout for asserting from io import StringIO with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout: + assert spec.loader is not None spec.loader.exec_module(auto_deploy) + + # Since auto_deploy is constructed dynamically, mypy/pyright doesn't know it has main() auto_deploy.main() output = mock_stdout.getvalue()