Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions data/bounties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"bounties": [
{
"id": 1505292,
"category": "Memes",
"winners": 2,
"current_entries": 595,
"max_entries": 1000,
"duration_days": 5,
"reward_per_winner": 18.9,
"total_reward": 51481,
"extra_field": 4041,
"tags": ["recent@Memes", "top@Memes"],
"bounty_types": ["OPEN_BOUNTY", "LOW_COMP", "SELF_POST_OPP"],
"title": "Meme Bounty - Thanks to REUTERS",
"status": "open",
"detected_at": "2026-06-10T08:59:00Z",
"source": "SN"
}
]
}
144 changes: 144 additions & 0 deletions scripts/process_bounty.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""
Script para procesar y validar bounties detectados por el radar SN.
"""
import json
import sys
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, List

BOUNTIES_FILE = Path(__file__).parent.parent / "data" / "bounties.json"

REQUIRED_FIELDS = [
"id",
"category",
"winners",
"current_entries",
"max_entries",
"duration_days",
"reward_per_winner",
"total_reward",
"tags",
"bounty_types",
"title",
"status",
"detected_at",
"source"
]

def load_bounties() -> Dict[str, Any]:
"""Carga los bounties desde el archivo JSON."""
if not BOUNTIES_FILE.exists():
return {"bounties": []}
with open(BOUNTIES_FILE, 'r', encoding='utf-8') as f:
return json.load(f)

def save_bounties(data: Dict[str, Any]) -> None:
"""Guarda los bounties en el archivo JSON."""
with open(BOUNTIES_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)

def validate_bounty(bounty: Dict[str, Any]) -> List[str]:
"""Valida que un bounty tenga todos los campos requeridos."""
errors = []
for field in REQUIRED_FIELDS:
if field not in bounty:
errors.append(f"Campo requerido faltante: {field}")

# Validaciones adicionales
if "id" in bounty and not isinstance(bounty["id"], int):
errors.append("El campo 'id' debe ser un entero")

if "winners" in bounty and bounty["winners"] <= 0:
errors.append("El número de ganadores debe ser positivo")

if "current_entries" in bounty and "max_entries" in bounty:
if bounty["current_entries"] > bounty["max_entries"]:
errors.append("Las entradas actuales no pueden exceder el máximo")

if "status" in bounty and bounty["status"] not in ["open", "closed", "completed"]:
errors.append("Estado inválido. Debe ser: open, closed, o completed")

return errors

def add_bounty(bounty: Dict[str, Any]) -> bool:
"""Añade un nuevo bounty si no existe y es válido."""
data = load_bounties()

# Verificar si ya existe
existing_ids = [b["id"] for b in data["bounties"]]
if bounty["id"] in existing_ids:
print(f"Bounty con ID {bounty['id']} ya existe")
return False

# Validar
errors = validate_bounty(bounty)
if errors:
print("Errores de validación:")
for error in errors:
print(f" - {error}")
return False

# Añadir timestamp de procesamiento
bounty["processed_at"] = datetime.utcnow().isoformat() + "Z"

data["bounties"].append(bounty)
save_bounties(data)
print(f"Bounty {bounty['id']} añadido correctamente")
return True

def list_open_bounties() -> List[Dict[str, Any]]:
"""Lista todos los bounties abiertos."""
data = load_bounties()
return [b for b in data["bounties"] if b.get("status") == "open"]

def main() -> int:
"""Función principal."""
if len(sys.argv) < 2:
print("Uso: python process_bounty.py <comando> [args...]")
print("Comandos:")
print(" add <archivo_json> - Añade un bounty desde archivo JSON")
print(" list - Lista bounties abiertos")
print(" validate <archivo_json> - Valida un bounty sin guardarlo")
return 1

command = sys.argv[1]

if command == "add":
if len(sys.argv) < 3:
print("Se requiere archivo JSON")
return 1
with open(sys.argv[2], 'r', encoding='utf-8') as f:
bounty = json.load(f)
success = add_bounty(bounty)
return 0 if success else 1

elif command == "list":
open_bounties = list_open_bounties()
for bounty in open_bounties:
print(f"ID: {bounty['id']} | {bounty['title']} | {bounty['category']} | Entradas: {bounty['current_entries']}/{bounty['max_entries']}")
return 0

elif command == "validate":
if len(sys.argv) < 3:
print("Se requiere archivo JSON")
return 1
with open(sys.argv[2], 'r', encoding='utf-8') as f:
bounty = json.load(f)
errors = validate_bounty(bounty)
if errors:
print("Errores de validación:")
for error in errors:
print(f" - {error}")
return 1
else:
print("Bounty válido")
return 0

else:
print(f"Comando desconocido: {command}")
return 1

if __name__ == "__main__":
sys.exit(main())
152 changes: 152 additions & 0 deletions scripts/test_process_bounty.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""
Tests unitarios para process_bounty.py
"""
import json
import tempfile
import os
import sys
from pathlib import Path

# Añadir el directorio scripts al path
sys.path.insert(0, str(Path(__file__).parent))

from process_bounty import validate_bounty, load_bounties, save_bounties

def test_validate_bounty_valid():
"""Test que un bounty válido pasa la validación."""
bounty = {
"id": 1505292,
"category": "Memes",
"winners": 2,
"current_entries": 595,
"max_entries": 1000,
"duration_days": 5,
"reward_per_winner": 18.9,
"total_reward": 51481,
"tags": ["recent@Memes", "top@Memes"],
"bounty_types": ["OPEN_BOUNTY", "LOW_COMP", "SELF_POST_OPP"],
"title": "Meme Bounty - Thanks to REUTERS",
"status": "open",
"detected_at": "2026-06-10T08:59:00Z",
"source": "SN"
}
errors = validate_bounty(bounty)
assert errors == [], f"Bounty válido no debería tener errores: {errors}"
print("✓ test_validate_bounty_valid passed")

def test_validate_bounty_missing_fields():
"""Test que se detectan campos faltantes."""
bounty = {
"id": 1505292,
"category": "Memes"
# Faltan muchos campos requeridos
}
errors = validate_bounty(bounty)
assert len(errors) > 0, "Debería haber errores por campos faltantes"
assert any("winners" in e for e in errors), "Debería detectar 'winners' faltante"
print("✓ test_validate_bounty_missing_fields passed")

def test_validate_bounty_invalid_winners():
"""Test que se detecta winners inválido."""
bounty = {
"id": 1505292,
"category": "Memes",
"winners": 0, # Inválido
"current_entries": 595,
"max_entries": 1000,
"duration_days": 5,
"reward_per_winner": 18.9,
"total_reward": 51481,
"tags": ["recent@Memes"],
"bounty_types": ["OPEN_BOUNTY"],
"title": "Test",
"status": "open",
"detected_at": "2026-06-10T08:59:00Z",
"source": "SN"
}
errors = validate_bounty(bounty)
assert any("ganadores" in e.lower() for e in errors), "Debería detectar winners inválido"
print("✓ test_validate_bounty_invalid_winners passed")

def test_validate_bounty_entries_exceed_max():
"""Test que se detecta cuando current_entries > max_entries."""
bounty = {
"id": 1505292,
"category": "Memes",
"winners": 2,
"current_entries": 1500, # Excede max_entries
"max_entries": 1000,
"duration_days": 5,
"reward_per_winner": 18.9,
"total_reward": 51481,
"tags": ["recent@Memes"],
"bounty_types": ["OPEN_BOUNTY"],
"title": "Test",
"status": "open",
"detected_at": "2026-06-10T08:59:00Z",
"source": "SN"
}
errors = validate_bounty(bounty)
assert any("exceder" in e.lower() for e in errors), "Debería detectar entries > max"
print("✓ test_validate_bounty_entries_exceed_max passed")

def test_validate_bounty_invalid_status():
"""Test que se detecta status inválido."""
bounty = {
"id": 1505292,
"category": "Memes",
"winners": 2,
"current_entries": 595,
"max_entries": 1000,
"duration_days": 5,
"reward_per_winner": 18.9,
"total_reward": 51481,
"tags": ["recent@Memes"],
"bounty_types": ["OPEN_BOUNTY"],
"title": "Test",
"status": "invalid_status", # Inválido
"detected_at": "2026-06-10T08:59:00Z",
"source": "SN"
}
errors = validate_bounty(bounty)
assert any("estado" in e.lower() for e in errors), "Debería detectar status inválido"
print("✓ test_validate_bounty_invalid_status passed")

def test_load_save_bounties():
"""Test de carga y guardado de bounties."""
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
temp_file = f.name

try:
# Monkey patch del archivo
import process_bounty
original_file = process_bounty.BOUNTIES_FILE
process_bounty.BOUNTIES_FILE = Path(temp_file)

# Guardar datos de prueba
test_data = {"bounties": [{"id": 1, "title": "Test"}]}
save_bounties(test_data)

# Cargar y verificar
loaded = load_bounties()
assert loaded == test_data, "Los datos cargados deben coincidir con los guardados"

print("✓ test_load_save_bounties passed")
finally:
process_bounty.BOUNTIES_FILE = original_file
if os.path.exists(temp_file):
os.unlink(temp_file)

def run_all_tests():
"""Ejecuta todos los tests."""
test_validate_bounty_valid()
test_validate_bounty_missing_fields()
test_validate_bounty_invalid_winners()
test_validate_bounty_entries_exceed_max()
test_validate_bounty_invalid_status()
test_load_save_bounties()
print("\n✓ Todos los tests pasaron")

if __name__ == "__main__":
run_all_tests()