package infra import ( "database/sql" "os" "testing" "time" _ "github.com/mattn/go-sqlite3" ) // createTestProposalsDB crea una BD en memoria con el schema minimo de proposals // para los tests de ProposalFromFailure. func createTestProposalsDB(t *testing.T) string { t.Helper() f, err := os.CreateTemp("", "proposals_test_*.db") if err != nil { t.Fatalf("create temp db: %v", err) } f.Close() path := f.Name() t.Cleanup(func() { os.Remove(path) }) db, err := sql.Open("sqlite3", "file:"+path+"?_journal_mode=WAL") if err != nil { t.Fatalf("open temp db: %v", err) } defer db.Close() _, err = db.Exec(`CREATE TABLE IF NOT EXISTS proposals ( id TEXT PRIMARY KEY, kind TEXT NOT NULL CHECK(kind IN ('new_function','new_type','improve_function','improve_type','new_pipeline')), target_id TEXT NOT NULL DEFAULT '', title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', evidence TEXT NOT NULL DEFAULT '{}', status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','approved','rejected','implemented')), created_by TEXT NOT NULL DEFAULT '', reviewed_by TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL, updated_at TEXT NOT NULL )`) if err != nil { t.Fatalf("create proposals table: %v", err) } return path } func TestProposalFromFailure(t *testing.T) { t.Run("no inserta nada cuando todos los checks pasan", func(t *testing.T) { dbPath := createTestProposalsDB(t) results := []CheckResult{ {ID: "check-ok", Status: "pass", Severity: "critical"}, {ID: "check-skip", Status: "skip", Severity: "warning"}, } ids, err := ProposalFromFailure(dbPath, "app_test", results, "exec_001") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(ids) != 0 { t.Errorf("expected 0 proposals, got %d", len(ids)) } }) t.Run("inserta proposal por cada check fallido", func(t *testing.T) { dbPath := createTestProposalsDB(t) results := []CheckResult{ {ID: "check-api", Status: "fail", Severity: "critical", ExitCode: 1, Error: "connection refused"}, {ID: "check-perf", Status: "fail", Severity: "warning", ExitCode: 0, Stdout: "slow"}, {ID: "check-ok", Status: "pass", Severity: "critical"}, } ids, err := ProposalFromFailure(dbPath, "app_test", results, "exec_002") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(ids) != 2 { t.Errorf("expected 2 proposals, got %d: %v", len(ids), ids) } }) t.Run("proposal critica usa kind new_function", func(t *testing.T) { dbPath := createTestProposalsDB(t) results := []CheckResult{ {ID: "check-critical", Status: "fail", Severity: "critical", ExitCode: 2}, } ids, err := ProposalFromFailure(dbPath, "app_x", results, "exec_003") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(ids) != 1 { t.Fatalf("expected 1 proposal, got %d", len(ids)) } db, _ := sql.Open("sqlite3", "file:"+dbPath) defer db.Close() var kind, status, createdBy string err = db.QueryRow("SELECT kind, status, created_by FROM proposals WHERE id = ?", ids[0]).Scan(&kind, &status, &createdBy) if err != nil { t.Fatalf("query proposal: %v", err) } if kind != "new_function" { t.Errorf("expected kind=new_function, got %q", kind) } if status != "pending" { t.Errorf("expected status=pending, got %q", status) } if createdBy != "reactive_loop" { t.Errorf("expected created_by=reactive_loop, got %q", createdBy) } }) t.Run("proposal warning usa kind improve_function", func(t *testing.T) { dbPath := createTestProposalsDB(t) results := []CheckResult{ {ID: "check-warning", Status: "fail", Severity: "warning"}, } ids, err := ProposalFromFailure(dbPath, "app_y", results, "exec_004") if err != nil { t.Fatalf("unexpected error: %v", err) } if len(ids) != 1 { t.Fatalf("expected 1 proposal, got %d", len(ids)) } db, _ := sql.Open("sqlite3", "file:"+dbPath) defer db.Close() var kind string _ = db.QueryRow("SELECT kind FROM proposals WHERE id = ?", ids[0]).Scan(&kind) if kind != "improve_function" { t.Errorf("expected kind=improve_function, got %q", kind) } }) t.Run("proposals tienen timestamp reciente", func(t *testing.T) { dbPath := createTestProposalsDB(t) before := time.Now().UTC().Add(-time.Second) results := []CheckResult{ {ID: "check-ts", Status: "fail", Severity: "critical"}, } ids, err := ProposalFromFailure(dbPath, "app_z", results, "exec_005") if err != nil { t.Fatalf("unexpected error: %v", err) } after := time.Now().UTC().Add(time.Second) db, _ := sql.Open("sqlite3", "file:"+dbPath) defer db.Close() var createdAt string _ = db.QueryRow("SELECT created_at FROM proposals WHERE id = ?", ids[0]).Scan(&createdAt) ts, err := time.Parse(time.RFC3339, createdAt) if err != nil { t.Fatalf("parse created_at: %v", err) } if ts.Before(before) || ts.After(after) { t.Errorf("created_at %v out of expected range [%v, %v]", ts, before, after) } }) }