Portal FastAPI + 5 endpoints REST para Bootcamp Agentic AI con watsonx Orchestrate (FactorIT). Single container, Coolify-ready. - Landing brandeado FIT con formulario de registro (honeypot anti-bot) - Tokens itsdangerous para descargas (24h expiry) - 5 endpoints API: historical/available procedures, member-insights, schedule, generate-report (Jinja2 + Plotly) - SQLite con upsert-on-email para leads + log de descargas - Admin endpoints (HTTP Basic): leads.json, leads.csv, stats - 23 tests pytest pasando - Dockerfile listo para Coolify con volúmenes persistentes (/app/leads.db, /app/app/data/reports_output, /app/material) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
123 lines
3.7 KiB
Python
123 lines
3.7 KiB
Python
import os
|
|
from pathlib import Path
|
|
|
|
|
|
def test_landing_renders(client):
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
assert "Bootcamp Agentic AI" in response.text
|
|
assert 'name="email"' in response.text
|
|
assert 'name="website"' in response.text
|
|
|
|
|
|
def test_register_valid_form_redirects_with_token(client):
|
|
response = client.post(
|
|
"/register",
|
|
data={
|
|
"nombre": "Felipe Arentsen",
|
|
"email": "felipe@factorit.com",
|
|
"empresa": "FactorIT",
|
|
"consentimiento": "on",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert response.status_code == 303
|
|
assert response.headers["location"].startswith("/descargas?token=")
|
|
|
|
|
|
def test_register_honeypot_filled_silently_drops(client):
|
|
response = client.post(
|
|
"/register",
|
|
data={
|
|
"nombre": "Bot",
|
|
"email": "bot@spam.com",
|
|
"empresa": "Spam Inc",
|
|
"consentimiento": "on",
|
|
"website": "https://spam.com",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
assert response.status_code == 303
|
|
from app.db import get_lead_by_email
|
|
assert get_lead_by_email("bot@spam.com") is None
|
|
|
|
|
|
def test_register_duplicate_email_reissues_token(client):
|
|
for _ in range(2):
|
|
client.post(
|
|
"/register",
|
|
data={
|
|
"nombre": "Felipe",
|
|
"email": "dup@factorit.com",
|
|
"empresa": "FactorIT",
|
|
"consentimiento": "on",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
from app.db import get_lead_by_email
|
|
lead = get_lead_by_email("dup@factorit.com")
|
|
assert lead["times_registered"] == 2
|
|
|
|
|
|
def test_descargas_with_invalid_token_redirects_home(client):
|
|
response = client.get("/descargas?token=invalid-junk", follow_redirects=False)
|
|
assert response.status_code == 307
|
|
assert response.headers["location"].startswith("/?error=")
|
|
|
|
|
|
def test_descargas_with_valid_token_renders(client):
|
|
reg = client.post(
|
|
"/register",
|
|
data={
|
|
"nombre": "Maria",
|
|
"email": "maria@test.com",
|
|
"empresa": "Test Co",
|
|
"consentimiento": "on",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
token = reg.headers["location"].split("token=")[1]
|
|
response = client.get(f"/descargas?token={token}")
|
|
assert response.status_code == 200
|
|
assert "Hola Maria" in response.text
|
|
assert "Material técnico" in response.text
|
|
assert "Material funcional" in response.text
|
|
|
|
|
|
def test_download_with_valid_token_serves_file(client):
|
|
material_dir = Path(os.environ["MATERIAL_DIR"])
|
|
material_dir.mkdir(parents=True, exist_ok=True)
|
|
(material_dir / "taller-wox-tecnico.zip").write_bytes(b"PK\x03\x04 fake zip")
|
|
|
|
reg = client.post(
|
|
"/register",
|
|
data={
|
|
"nombre": "Test",
|
|
"email": "test-dl@test.com",
|
|
"empresa": "TC",
|
|
"consentimiento": "on",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
token = reg.headers["location"].split("token=")[1]
|
|
response = client.get(f"/download/taller-wox-tecnico.zip?token={token}")
|
|
assert response.status_code == 200
|
|
assert response.content == b"PK\x03\x04 fake zip"
|
|
assert "attachment" in response.headers["content-disposition"]
|
|
|
|
|
|
def test_download_invalid_filename_returns_404(client):
|
|
reg = client.post(
|
|
"/register",
|
|
data={
|
|
"nombre": "Test",
|
|
"email": "bad-fn@test.com",
|
|
"empresa": "TC",
|
|
"consentimiento": "on",
|
|
},
|
|
follow_redirects=False,
|
|
)
|
|
token = reg.headers["location"].split("token=")[1]
|
|
response = client.get(f"/download/etc-passwd?token={token}")
|
|
assert response.status_code == 404
|