feat: modelos y CRUD para unit_tests y e2e_tests

UnitTest en registry con Insert, GetByFunction, Search FTS5, Purge. E2ETest en fn_operations con Insert, Get, List, UpdateResult, Delete. Ambos con scan helpers y serialización JSON.
This commit is contained in:
2026-04-05 18:19:10 +02:00
parent 512eebf7f4
commit fea8fed75d
4 changed files with 243 additions and 0 deletions
+27
View File
@@ -167,6 +167,33 @@ type Log struct {
CreatedAt time.Time `json:"created_at"`
}
// E2ETestStatus represents the result of an e2e test run.
type E2ETestStatus string
const (
E2EPass E2ETestStatus = "pass"
E2EFail E2ETestStatus = "fail"
E2ESkip E2ETestStatus = "skip"
E2EPending E2ETestStatus = ""
)
// E2ETest is an integration test that verifies function composition within an app.
type E2ETest struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
RelationID string `json:"relation_id"`
Steps []string `json:"steps"`
InputFixture map[string]any `json:"input_fixture"`
Expected map[string]any `json:"expected"`
LastStatus E2ETestStatus `json:"last_status"`
LastRunAt string `json:"last_run_at"`
ExecutionID string `json:"execution_id"`
DurationMs int64 `json:"duration_ms"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TypeSnapshot is an immutable copy of a registry type at point of use.
type TypeSnapshot struct {
ID string `json:"id"`
+119
View File
@@ -903,3 +903,122 @@ func (db *DB) ListLogs(level LogLevel, source, entityID, executionID string, lim
}
return result, nil
}
// --- E2E Tests CRUD ---
// InsertE2ETest inserts or replaces an e2e test.
func (db *DB) InsertE2ETest(t *E2ETest) error {
now := time.Now().UTC()
if t.CreatedAt.IsZero() {
t.CreatedAt = now
}
t.UpdatedAt = now
_, err := db.conn.Exec(`
INSERT OR REPLACE INTO e2e_tests (
id, name, description, relation_id, steps, input_fixture,
expected, last_status, last_run_at, execution_id, duration_ms,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
t.ID, t.Name, t.Description, t.RelationID,
marshalStrings(t.Steps), marshalJSON(t.InputFixture), marshalJSON(t.Expected),
string(t.LastStatus), t.LastRunAt, t.ExecutionID, t.DurationMs,
t.CreatedAt.Format(time.RFC3339), t.UpdatedAt.Format(time.RFC3339),
)
return err
}
// GetE2ETest returns an e2e test by ID.
func (db *DB) GetE2ETest(id string) (*E2ETest, error) {
row := db.conn.QueryRow(`
SELECT id, name, description, relation_id, steps, input_fixture,
expected, last_status, last_run_at, execution_id, duration_ms,
created_at, updated_at
FROM e2e_tests WHERE id = ?`, id)
t, err := scanE2ETest(row)
if err != nil {
return nil, fmt.Errorf("e2e test %q not found: %w", id, err)
}
return t, nil
}
// ListE2ETests returns e2e tests with optional status filter.
func (db *DB) ListE2ETests(status E2ETestStatus) ([]E2ETest, error) {
where := []string{}
args := []any{}
if status != "" {
where = append(where, "last_status = ?")
args = append(args, string(status))
}
q := `SELECT id, name, description, relation_id, steps, input_fixture,
expected, last_status, last_run_at, execution_id, duration_ms,
created_at, updated_at
FROM e2e_tests`
if len(where) > 0 {
q += " WHERE " + strings.Join(where, " AND ")
}
q += " ORDER BY name"
rows, err := db.conn.Query(q, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var result []E2ETest
for rows.Next() {
var t E2ETest
var stepsJSON, fixtureJSON, expectedJSON, createdAt, updatedAt string
if err := rows.Scan(&t.ID, &t.Name, &t.Description, &t.RelationID,
&stepsJSON, &fixtureJSON, &expectedJSON,
&t.LastStatus, &t.LastRunAt, &t.ExecutionID, &t.DurationMs,
&createdAt, &updatedAt); err != nil {
return nil, fmt.Errorf("scanning e2e test: %w", err)
}
t.Steps = unmarshalStrings(stepsJSON)
t.InputFixture = unmarshalJSON(fixtureJSON)
t.Expected = unmarshalJSON(expectedJSON)
t.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
t.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
result = append(result, t)
}
return result, nil
}
// UpdateE2ETestResult updates the result fields after running an e2e test.
func (db *DB) UpdateE2ETestResult(id string, status E2ETestStatus, executionID string, durationMs int64) error {
now := time.Now().UTC()
_, err := db.conn.Exec(`
UPDATE e2e_tests SET last_status=?, last_run_at=?, execution_id=?, duration_ms=?, updated_at=?
WHERE id=?`,
string(status), now.Format(time.RFC3339), executionID, durationMs,
now.Format(time.RFC3339), id,
)
return err
}
// DeleteE2ETest removes an e2e test by ID.
func (db *DB) DeleteE2ETest(id string) error {
_, err := db.conn.Exec("DELETE FROM e2e_tests WHERE id = ?", id)
return err
}
func scanE2ETest(row *sql.Row) (*E2ETest, error) {
var t E2ETest
var stepsJSON, fixtureJSON, expectedJSON, createdAt, updatedAt string
err := row.Scan(&t.ID, &t.Name, &t.Description, &t.RelationID,
&stepsJSON, &fixtureJSON, &expectedJSON,
&t.LastStatus, &t.LastRunAt, &t.ExecutionID, &t.DurationMs,
&createdAt, &updatedAt)
if err != nil {
return nil, err
}
t.Steps = unmarshalStrings(stepsJSON)
t.InputFixture = unmarshalJSON(fixtureJSON)
t.Expected = unmarshalJSON(expectedJSON)
t.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
t.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
return &t, nil
}