750b7abcd5
- .claude/CLAUDE.md - .claude/agents/fn-recopilador/SKILL.md - .claude/rules/INDEX.md - .claude/rules/cpp_apps.md - bash/functions/infra/build_cpp_windows.sh - cpp/CMakeLists.txt - cpp/PATTERNS.md - cpp/framework/app_base.cpp - cpp/framework/app_base.h - dev/issues/README.md - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
159 lines
4.9 KiB
Go
159 lines
4.9 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|