feat: update ingest, registry, and UI flows
This commit is contained in:
@@ -4,19 +4,17 @@ import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
|
||||
"reanimator/internal/ingest"
|
||||
"reanimator/internal/repository"
|
||||
"reanimator/internal/repository/migrate"
|
||||
)
|
||||
|
||||
func TestIngestLogBundleIdempotent(t *testing.T) {
|
||||
@@ -82,66 +80,31 @@ func TestIngestLogBundleIdempotent(t *testing.T) {
|
||||
assertCount(t, db, "log_bundles", 1)
|
||||
assertCount(t, db, "observations", 2)
|
||||
assertCountQuery(t, db, "SELECT COUNT(*) FROM installations WHERE removed_at IS NULL", 2)
|
||||
assertCountQuery(t, db, "SELECT COUNT(*) FROM components WHERE first_seen_at IS NOT NULL", 2)
|
||||
assertCountQuery(t, db, "SELECT COUNT(*) FROM parts WHERE first_seen_at IS NOT NULL", 2)
|
||||
}
|
||||
|
||||
func applyMigrations(db *sql.DB) error {
|
||||
paths := []string{
|
||||
filepath.Join("migrations", "0001_init", "up.sql"),
|
||||
filepath.Join("migrations", "0002_registry", "up.sql"),
|
||||
filepath.Join("migrations", "0003_ingest", "up.sql"),
|
||||
filepath.Join("migrations", "0004_timeline", "up.sql"),
|
||||
filepath.Join("migrations", "0005_tickets", "up.sql"),
|
||||
filepath.Join("migrations", "0006_analytics", "up.sql"),
|
||||
filepath.Join("migrations", "0007_hardware_ingest", "up.sql"),
|
||||
filepath.Join("migrations", "0008_service_uniques", "up.sql"),
|
||||
}
|
||||
for _, path := range paths {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := execStatements(db, string(data)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func execStatements(db *sql.DB, sqlText string) error {
|
||||
statements := strings.Split(sqlText, ";")
|
||||
for _, stmt := range statements {
|
||||
stmt = strings.TrimSpace(stmt)
|
||||
if stmt == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := db.Exec(stmt); err != nil {
|
||||
var mysqlErr *mysql.MySQLError
|
||||
if errors.As(err, &mysqlErr) && (mysqlErr.Number == 1060 || mysqlErr.Number == 1061) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return migrate.EnsureSchema(db, "migrations")
|
||||
}
|
||||
|
||||
func cleanupRegistry(db *sql.DB) error {
|
||||
statements := []string{
|
||||
"DELETE FROM failure_events",
|
||||
"DELETE FROM asset_firmware_states",
|
||||
"DELETE FROM machine_firmware_states",
|
||||
"DELETE FROM ticket_links",
|
||||
"DELETE FROM tickets",
|
||||
"DELETE FROM timeline_events",
|
||||
"DELETE FROM observations",
|
||||
"DELETE FROM log_bundles",
|
||||
"DELETE FROM installations",
|
||||
"DELETE FROM components",
|
||||
"DELETE FROM assets",
|
||||
"DELETE FROM parts",
|
||||
"DELETE FROM machines",
|
||||
"DELETE FROM locations",
|
||||
"DELETE FROM projects",
|
||||
"DELETE FROM customers",
|
||||
"DELETE FROM lots",
|
||||
"DELETE FROM id_sequences",
|
||||
"DELETE FROM schema_migrations",
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
if _, err := db.Exec(stmt); err != nil {
|
||||
@@ -151,43 +114,38 @@ func cleanupRegistry(db *sql.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertCustomer(t *testing.T, db *sql.DB, name string) int64 {
|
||||
var testIDSeq uint64
|
||||
|
||||
func insertCustomer(t *testing.T, db *sql.DB, name string) string {
|
||||
t.Helper()
|
||||
result, err := db.Exec(`INSERT INTO customers (name) VALUES (?)`, name)
|
||||
if err != nil {
|
||||
id := nextTestID("CR")
|
||||
if _, err := db.Exec(`INSERT INTO customers (id, name) VALUES (?, ?)`, id, name); err != nil {
|
||||
t.Fatalf("insert customer: %v", err)
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
t.Fatalf("customer id: %v", err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func insertProject(t *testing.T, db *sql.DB, customerID int64, name string) int64 {
|
||||
func insertProject(t *testing.T, db *sql.DB, customerID string, name string) string {
|
||||
t.Helper()
|
||||
result, err := db.Exec(`INSERT INTO projects (customer_id, name) VALUES (?, ?)`, customerID, name)
|
||||
if err != nil {
|
||||
id := nextTestID("PJ")
|
||||
if _, err := db.Exec(`INSERT INTO projects (id, customer_id, name) VALUES (?, ?, ?)`, id, customerID, name); err != nil {
|
||||
t.Fatalf("insert project: %v", err)
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
t.Fatalf("project id: %v", err)
|
||||
return id
|
||||
}
|
||||
|
||||
func insertAsset(t *testing.T, db *sql.DB, projectID string, name, serial string) string {
|
||||
t.Helper()
|
||||
id := nextTestID("ME")
|
||||
if _, err := db.Exec(`INSERT INTO machines (id, project_id, customer_id, location_id, name, vendor_serial) VALUES (?, ?, NULL, NULL, ?, ?)`, id, projectID, name, serial); err != nil {
|
||||
t.Fatalf("insert asset: %v", err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func insertAsset(t *testing.T, db *sql.DB, projectID int64, name, serial string) int64 {
|
||||
t.Helper()
|
||||
result, err := db.Exec(`INSERT INTO assets (project_id, name, vendor_serial) VALUES (?, ?, ?)`, projectID, name, serial)
|
||||
if err != nil {
|
||||
t.Fatalf("insert asset: %v", err)
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
t.Fatalf("asset id: %v", err)
|
||||
}
|
||||
return id
|
||||
func nextTestID(prefix string) string {
|
||||
next := atomic.AddUint64(&testIDSeq, 1)
|
||||
return fmt.Sprintf("%s-%07d", prefix, next)
|
||||
}
|
||||
|
||||
func assertCount(t *testing.T, db *sql.DB, table string, expected int) {
|
||||
|
||||
Reference in New Issue
Block a user