package persistence import ( "context" "database/sql" "strings" "testing" "time" _ "github.com/mattn/go-sqlite3" ) // 最小化测试套件 - 不依赖任何复杂模块 // 这些测试可以直接运行: go test -v -run Minimal ./api/persistence/ func TestMinimalConfigDefaults(t *testing.T) { cfg := DefaultDBConfig("postgres", "test-dsn") if cfg.DriverName != "postgres" { t.Errorf("expected DriverName=postgres, got %s", cfg.DriverName) } if cfg.MaxOpenConns != 25 { t.Errorf("expected MaxOpenConns=25, got %d", cfg.MaxOpenConns) } } func TestMinimalStrategyString(t *testing.T) { tests := []struct { strategy PersistenceStrategy expected string }{ {StrategyDBOnly, "DB_ONLY"}, {StrategyDBAndTrustlog, "DB_AND_TRUSTLOG"}, {StrategyTrustlogOnly, "TRUSTLOG_ONLY"}, } for _, tt := range tests { if tt.strategy.String() != tt.expected { t.Errorf("strategy.String() = %s, want %s", tt.strategy.String(), tt.expected) } } } func TestMinimalStatusEnums(t *testing.T) { if StatusNotTrustlogged != "NOT_TRUSTLOGGED" { t.Error("StatusNotTrustlogged incorrect") } if StatusTrustlogged != "TRUSTLOGGED" { t.Error("StatusTrustlogged incorrect") } if RetryStatusPending != "PENDING" { t.Error("RetryStatusPending incorrect") } } func TestMinimalDDLGeneration(t *testing.T) { drivers := []string{"postgres", "mysql", "sqlite3"} for _, driver := range drivers { opDDL, cursorDDL, retryDDL, err := GetDialectDDL(driver) if err != nil { t.Fatalf("GetDialectDDL(%s) failed: %v", driver, err) } if len(opDDL) == 0 { t.Errorf("%s: operation DDL is empty", driver) } if len(cursorDDL) == 0 { t.Errorf("%s: cursor DDL is empty", driver) } if len(retryDDL) == 0 { t.Errorf("%s: retry DDL is empty", driver) } } } func TestMinimalDDLContent(t *testing.T) { opDDL, _, _, _ := GetDialectDDL("postgres") requiredFields := []string{ "op_id", "client_ip", "server_ip", "trustlog_status", } for _, field := range requiredFields { if !strings.Contains(opDDL, field) { t.Errorf("operation DDL missing field: %s", field) } } } func TestMinimalDatabaseCreation(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("failed to open database: %v", err) } defer db.Close() // 获取 DDL opDDL, cursorDDL, retryDDL, err := GetDialectDDL("sqlite3") if err != nil { t.Fatalf("GetDialectDDL failed: %v", err) } // 创建表 if _, err := db.Exec(opDDL); err != nil { t.Fatalf("failed to create operation table: %v", err) } if _, err := db.Exec(cursorDDL); err != nil { t.Fatalf("failed to create cursor table: %v", err) } if _, err := db.Exec(retryDDL); err != nil { t.Fatalf("failed to create retry table: %v", err) } // 验证表存在 tables := []string{"operation", "trustlog_cursor", "trustlog_retry"} for _, table := range tables { var name string err = db.QueryRow( "SELECT name FROM sqlite_master WHERE type='table' AND name=?", table, ).Scan(&name) if err != nil { t.Errorf("table %s not found: %v", table, err) } } } func TestMinimalNullableIPFields(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("failed to open database: %v", err) } defer db.Close() // 创建表 opDDL, _, _, _ := GetDialectDDL("sqlite3") if _, err := db.Exec(opDDL); err != nil { t.Fatalf("failed to create table: %v", err) } ctx := context.Background() // 测试1: 插入 NULL IP _, err = db.ExecContext(ctx, ` INSERT INTO operation ( op_id, doid, producer_id, op_source, op_type, do_prefix, do_repository, trustlog_status, timestamp, client_ip, server_ip ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, "test-001", "10.1000/repo/obj", "producer-001", "DOIP", "Create", "10.1000", "repo", "NOT_TRUSTLOGGED", time.Now(), nil, nil) if err != nil { t.Fatalf("failed to insert with NULL IPs: %v", err) } // 验证 NULL var clientIP, serverIP sql.NullString err = db.QueryRowContext(ctx, "SELECT client_ip, server_ip FROM operation WHERE op_id = ?", "test-001", ).Scan(&clientIP, &serverIP) if err != nil { t.Fatalf("failed to query: %v", err) } if clientIP.Valid { t.Error("client_ip should be NULL") } if serverIP.Valid { t.Error("server_ip should be NULL") } // 测试2: 插入非 NULL IP _, err = db.ExecContext(ctx, ` INSERT INTO operation ( op_id, doid, producer_id, op_source, op_type, do_prefix, do_repository, trustlog_status, timestamp, client_ip, server_ip ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, "test-002", "10.1000/repo/obj2", "producer-001", "DOIP", "Create", "10.1000", "repo", "NOT_TRUSTLOGGED", time.Now(), "192.168.1.100", "10.0.0.50") if err != nil { t.Fatalf("failed to insert with IP values: %v", err) } // 验证非 NULL err = db.QueryRowContext(ctx, "SELECT client_ip, server_ip FROM operation WHERE op_id = ?", "test-002", ).Scan(&clientIP, &serverIP) if err != nil { t.Fatalf("failed to query: %v", err) } if !clientIP.Valid || clientIP.String != "192.168.1.100" { t.Errorf("client_ip should be '192.168.1.100', got %v", clientIP) } if !serverIP.Valid || serverIP.String != "10.0.0.50" { t.Errorf("server_ip should be '10.0.0.50', got %v", serverIP) } } func TestMinimalCursorTableInit(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("failed to open database: %v", err) } defer db.Close() _, cursorDDL, _, _ := GetDialectDDL("sqlite3") if _, err := db.Exec(cursorDDL); err != nil { t.Fatalf("failed to create cursor table: %v", err) } // 验证表结构(不再自动插入初始记录) var count int err = db.QueryRow("SELECT COUNT(*) FROM trustlog_cursor").Scan(&count) if err != nil { t.Fatalf("failed to count: %v", err) } if count != 0 { t.Errorf("expected 0 initial cursor records (empty table), got %d", count) } // 测试插入新记录 _, err = db.Exec(`INSERT INTO trustlog_cursor (cursor_key, cursor_value) VALUES (?, ?)`, "test-cursor", time.Now().Format(time.RFC3339Nano)) if err != nil { t.Fatalf("failed to insert cursor: %v", err) } // 验证插入 err = db.QueryRow("SELECT COUNT(*) FROM trustlog_cursor WHERE cursor_key = ?", "test-cursor").Scan(&count) if err != nil { t.Fatalf("failed to count after insert: %v", err) } if count != 1 { t.Errorf("expected 1 cursor record after insert, got %d", count) } } func TestMinimalRetryStatusUpdate(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("failed to open database: %v", err) } defer db.Close() _, _, retryDDL, _ := GetDialectDDL("sqlite3") if _, err := db.Exec(retryDDL); err != nil { t.Fatalf("failed to create retry table: %v", err) } ctx := context.Background() // 插入重试记录 _, err = db.ExecContext(ctx, ` INSERT INTO trustlog_retry (op_id, retry_count, retry_status, next_retry_at) VALUES (?, ?, ?, ?) `, "test-op-001", 0, "PENDING", time.Now().Add(1*time.Minute)) if err != nil { t.Fatalf("failed to insert retry record: %v", err) } // 更新状态 _, err = db.ExecContext(ctx, ` UPDATE trustlog_retry SET retry_count = retry_count + 1, retry_status = ? WHERE op_id = ? `, "RETRYING", "test-op-001") if err != nil { t.Fatalf("failed to update retry: %v", err) } // 验证更新 var count int var status string err = db.QueryRowContext(ctx, "SELECT retry_count, retry_status FROM trustlog_retry WHERE op_id = ?", "test-op-001", ).Scan(&count, &status) if err != nil { t.Fatalf("failed to query: %v", err) } if count != 1 { t.Errorf("expected retry_count=1, got %d", count) } if status != "RETRYING" { t.Errorf("expected status=RETRYING, got %s", status) } } func TestMinimalOperationStatusFlow(t *testing.T) { db, err := sql.Open("sqlite3", ":memory:") if err != nil { t.Fatalf("failed to open database: %v", err) } defer db.Close() opDDL, _, _, _ := GetDialectDDL("sqlite3") if _, err := db.Exec(opDDL); err != nil { t.Fatalf("failed to create table: %v", err) } ctx := context.Background() // 插入未存证记录 _, err = db.ExecContext(ctx, ` INSERT INTO operation ( op_id, doid, producer_id, op_source, op_type, do_prefix, do_repository, trustlog_status, timestamp ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `, "test-001", "10.1000/repo/obj", "producer-001", "DOIP", "Create", "10.1000", "repo", "NOT_TRUSTLOGGED", time.Now()) if err != nil { t.Fatalf("failed to insert: %v", err) } // 查询未存证记录 var count int err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM operation WHERE trustlog_status = ?", "NOT_TRUSTLOGGED", ).Scan(&count) if err != nil { t.Fatalf("failed to query: %v", err) } if count != 1 { t.Errorf("expected 1 untrustlogged operation, got %d", count) } // 更新为已存证 _, err = db.ExecContext(ctx, "UPDATE operation SET trustlog_status = ? WHERE op_id = ?", "TRUSTLOGGED", "test-001", ) if err != nil { t.Fatalf("failed to update: %v", err) } // 验证更新 var status string err = db.QueryRowContext(ctx, "SELECT trustlog_status FROM operation WHERE op_id = ?", "test-001", ).Scan(&status) if err != nil { t.Fatalf("failed to query: %v", err) } if status != "TRUSTLOGGED" { t.Errorf("expected status=TRUSTLOGGED, got %s", status) } }