C
CULTIVA IA · IA-Ingenieria-MLOps

Patrones TDD para AgentFlow API

Ciclo Red–Green–Refactor completo con checkpoints, git notes y puertas de calidad — Fase 1: Autenticacion

FastAPI + PostgreSQL
pytest + pytest-cov
Conductor Framework
Python 3.12
Fase 1 · Task 1.2
Plan de Fase · conductor/tracks/user-auth_20260116/plan.md

Fase 1 — Autenticacion y usuarios

Task 1.1: Setup schema de base de datos
a1b2c3d
~
Task 1.2: Modelo User con validacion email
en curso
Task 1.3: Endpoint POST /auth/register
Task 1.4: Endpoint POST /auth/login (JWT)

Checkpoints de fase

FaseSHAFechaEstado
Fase 0 — Setup 9f3a11e 2026-06-01 verificado
Fase 1 — Auth pendiente pendiente
Fase 2 — Campanas pendiente pendiente
Ciclo TDD — Task 1.2: Modelo User con validacion de email
11 pasos del ciclo TDD completo
Task 1.2 · En ejecucion
01
Seleccionar

Seleccionar tarea pendiente del plan

Se lee plan.md y se identifica la proxima [ ] en la fase actual. No se salta a fases posteriores.

markdown · plan.md
# user-auth_20260116 · plan.md
- [x] **Task 1.1**: Setup schema de base de datos `a1b2c3d`
- [~] **Task 1.2**: Implementar modelo User con validacion email
- [ ] **Task 1.3**: Endpoint POST /auth/register
02
Marcar WIP

Marcar tarea como [~] en progress

Se actualiza plan.md y se hace un commit separado de estado antes de comenzar la implementacion.

bashgit commit
git add conductor/tracks/user-auth_20260116/plan.md
git commit -m "docs: mark task 1.2 as in progress

Track: user-auth_20260116"
03
RED

Escribir tests que FALLAN primero

Se crean tests para happy path, edge cases y condiciones de error antes de escribir una sola linea de produccion.

python · tests/test_user.pyFAIL esperado
import pytest
from agentflow.models.user import User

# --- Happy path ---
def test_validate_email_valido():
    u = User(email="alvaro@cultivaia.com")
    assert u.validate_email() is True

# --- Edge cases ---
def test_validate_email_sin_arroba():
    u = User(email="noesvalido.com")
    assert u.validate_email() is False

def test_validate_email_vacio():
    u = User(email="")
    assert u.validate_email() is False

def test_validate_email_sin_dominio():
    u = User(email="usuario@")
    assert u.validate_email() is False

# --- Error conditions ---
def test_validate_email_none_lanza_error():
    with pytest.raises(ValueError):
        User(email=None)
bash · resultado pytest5 FAILED
FAILED tests/test_user.py::test_validate_email_valido
FAILED tests/test_user.py::test_validate_email_sin_arroba
FAILED tests/test_user.py::test_validate_email_vacio
FAILED tests/test_user.py::test_validate_email_sin_dominio
FAILED tests/test_user.py::test_validate_email_none_lanza_error
ImportError: cannot import name 'User' from 'agentflow.models.user'
=> RED confirmado. Procediendo a GREEN.
04
GREEN

Implementar codigo minimo para pasar los tests

Sin optimizacion prematura. Solo lo necesario para que todos los tests se pongan en verde.

python · src/agentflow/models/user.pyimplementacion minima
import re
from dataclasses import dataclass

_EMAIL_RE = re.compile(r"^[^@]+@[^@]+\.[^@]+$")

@dataclass
class User:
    email: str

    def __post_init__(self):
        if self.email is None:
            raise ValueError("email no puede ser None")

    def validate_email(self) -> bool:
        if not self.email:
            return False
        return bool(_EMAIL_RE.match(self.email))
bash · resultado pytest5 PASSED
PASSED tests/test_user.py::test_validate_email_valido
PASSED tests/test_user.py::test_validate_email_sin_arroba
PASSED tests/test_user.py::test_validate_email_vacio
PASSED tests/test_user.py::test_validate_email_sin_dominio
PASSED tests/test_user.py::test_validate_email_none_lanza_error
5 passed in 0.12s  => GREEN alcanzado.
05
REFACTOR

Mejorar claridad sin romper los tests

Se anade type hint completo, docstring y se extrae la regex a una constante con nombre semantico. Tests deben permanecer GREEN tras cada cambio.

python · user.py — version refactorizada5 PASSED
import re
from dataclasses import dataclass

# RFC 5322 simplificado: requiere usuario, @, dominio y TLD
_VALID_EMAIL_PATTERN = re.compile(
    r"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$"
)

@dataclass
class User:
    """Representa un usuario de AgentFlow con validacion de email."""
    email: str

    def __post_init__(self) -> None:
        if self.email is None:
            raise ValueError("El campo email es obligatorio.")

    def validate_email(self) -> bool:
        """Devuelve True si el email tiene formato RFC valido."""
        if not self.email:
            return False
        return bool(_VALID_EMAIL_PATTERN.match(self.email))
06
Cobertura

Verificar cobertura >= 80 %

bash
pytest --cov=agentflow.models.user --cov-report=term-missing
agentflow/models/user.py94 %
tests/test_user.py100 %

Cobertura 94 % > objetivo 80 %. Puerta de calidad superada.

07
Desvios

Documentar desviaciones del plan

DEVIATION detectada en Task 1.2
Tipo: Desviacion tecnica
Original: regex simple r"^[^@]+@[^@]+"
Implementado: patron RFC 5322 simplificado con TLD obligatorio
Razon: detectamos durante RED que emails como user@host (sin TLD) debian rechazarse
Impacto: ningun nuevo paquete — solo cambio interno del patron
Accion: nota en plan.md + sin cambio en tech-stack.md
08
Commit

Commit atomico de la implementacion

bashb7e4f92
git add src/agentflow/models/user.py tests/test_user.py
git commit -m "feat(user): implement email validation model

- Add User dataclass with validate_email() method
- Enforce RFC 5322 simplified pattern (requires TLD)
- Raise ValueError on None email input
- 94% coverage on validation module

Task: 1.2
Track: user-auth_20260116"
09
Git Notes

Adjuntar git note con resumen semantico

bash
git notes add -m "Task 1.2: Modelo User con validacion de email

Summary:
- Dataclass User con campo email obligatorio
- validate_email() usa regex RFC 5322 simplificado
- Cubre: email vacio, None, sin @, sin TLD, valido

Files changed:
- src/agentflow/models/user.py (nuevo)
- tests/test_user.py (nuevo)

Decisions:
- Regex propia en lugar de email-validator lib
  Razon: evitar dependencia externa para validacion basica
- TLD obligatorio (min 2 chars) para mayor precision

Deviation:
- RFC simplificado en lugar de regex minima original
  Impacto: 0 nuevas dependencias"
10
Plan SHA

Actualizar plan.md con SHA del commit

markdown · plan.md
- [x] **Task 1.1**: Setup schema de base de datos `a1b2c3d`
- [x] **Task 1.2**: Modelo User con validacion email `b7e4f92`
  - DEVIATION: RFC 5322 simplificado en lugar de regex minima
  - Razon: precision en rechazo de emails sin TLD
- [ ] **Task 1.3**: Endpoint POST /auth/register
11
Plan Commit

Commit del update del plan

bashc1a3d55
git add conductor/tracks/user-auth_20260116/plan.md
git commit -m "docs: update plan - task 1.2 complete

Track: user-auth_20260116"
Puertas de Calidad — antes de marcar task completa
Tests pasando
SUPERADA
5/5 tests green. Sin regresiones vs. Task 1.1
📊
Cobertura >= 80 %
SUPERADA
94 % modulo user.py · 100 % tests
🔍
Linting · ruff
SUPERADA
0 errores · 0 warnings no justificados
🔒
Tipos · mypy
SUPERADA
Strict mode OK · return types presentes
📝
Documentacion
SUPERADA
Docstrings en clase y metodo publico
🛡️
Seguridad
SUPERADA
Sin secretos · Input validado · Sin deps nuevas
📦
Estado limpio
SUPERADA
git status clean · build verde post-commit
🚦
Sin regresiones
SUPERADA
Full suite: 12 tests · 12 passed · 0 failed
Checkpoint de Fase (despues de Task 1.4)
Phase 1 — Verificacion previa al checkpoint
Presentar al usuario antes de crear el commit de checkpoint
Mensaje al usuario (agente espera respuesta)
Fase 1 completa. Por favor verifica:
  1. [✓] Suite pytest pasa (automatizado)
  2. [✓] Cobertura 88% >= objetivo 80% (automatizado)
  3. [ ] Registro manual con email valido (requiere humano)
  4. [ ] Error visible con email invalido (requiere humano)

Responde con 'approved' para continuar con Fase 2,
o indica los problemas encontrados.
Checkpoint commit — tras aprobacion del usuario
d9f1c2a
$ git commit -m "checkpoint: phase 1 complete - user-auth_20260116

Verified:
- All tests passing (12/12)
- Coverage: 88%
- Manual verification approved

Phase 1 tasks:
- [x] Task 1.1: Setup database schema a1b2c3d
- [x] Task 1.2: User model + email valid b7e4f92
- [x] Task 1.3: POST /auth/register e8d5b11
- [x] Task 1.4: POST /auth/login (JWT) f3c9a0e"

[main d9f1c2a] checkpoint: phase 1 complete - user-auth_20260116
8 files changed, 247 insertions(+), 3 deletions(-)