Files
ai-ops/internal/service/audit_service_integration_test.go
2026-05-12 17:48:22 +08:00

115 lines
3.6 KiB
Go

package service
import (
"context"
"crypto/rand"
"encoding/hex"
"os"
"path/filepath"
"sort"
"testing"
"time"
"github.com/company/ai-ops/internal/config"
"github.com/company/ai-ops/internal/database"
)
func setupServicePGIntegration(t *testing.T) context.Context {
t.Helper()
ctx := context.Background()
if database.Pool == nil {
ports := []int{15432, 5432}
var lastErr error
for _, port := range ports {
lastErr = database.Init(config.DatabaseConfig{Host: "localhost", Port: port, User: "aiops", Password: "aiops123", DBName: "ai_ops", SSLMode: "disable", PoolSize: 4})
if lastErr == nil {
break
}
database.Close()
database.Pool = nil
}
if lastErr != nil {
t.Skipf("PostgreSQL integration database not available: %v", lastErr)
}
}
files, err := filepath.Glob(filepath.Join("..", "..", "tech", "migrations", "*.up.sql"))
if err != nil {
t.Fatal(err)
}
sort.Strings(files)
if _, err := database.Pool.Exec(ctx, `SELECT pg_advisory_lock(424242001)`); err != nil {
t.Fatal(err)
}
defer database.Pool.Exec(ctx, `SELECT pg_advisory_unlock(424242001)`)
for _, f := range files {
b, err := os.ReadFile(f)
if err != nil {
t.Fatal(err)
}
if _, err := database.Pool.Exec(ctx, string(b)); err != nil {
t.Fatalf("apply migration %s: %v", f, err)
}
}
return ctx
}
func serviceTestUUID(t *testing.T) string {
t.Helper()
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
t.Fatal(err)
}
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
return hex.EncodeToString(b[0:4]) + "-" + hex.EncodeToString(b[4:6]) + "-" + hex.EncodeToString(b[6:8]) + "-" + hex.EncodeToString(b[8:10]) + "-" + hex.EncodeToString(b[10:16])
}
func cleanupAudit(t *testing.T, ctx context.Context, ids ...string) {
t.Helper()
for _, id := range ids {
_, _ = database.Pool.Exec(ctx, `DELETE FROM ai_ops_audits WHERE id=$1 OR parent_audit_id=$1 OR object_id=$1`, id)
}
}
func TestAuditServiceRecordListRollback(t *testing.T) {
ctx := setupServicePGIntegration(t)
svc := NewAuditService()
id := serviceTestUUID(t)
defer cleanupAudit(t, ctx, id)
log := &AuditLog{ID: id, TenantID: "tenant", ObjectType: "rule", ObjectID: id, Action: "update", BeforeState: map[string]any{"enabled": false}, AfterState: map[string]any{"enabled": true}, RequestID: "req", ResultCode: "SUCCESS", SourceIP: "127.0.0.1", ActorID: "actor", RiskLevel: "normal"}
if err := svc.Record(ctx, log); err != nil {
t.Fatal(err)
}
logs, total, err := svc.List(ctx, "rule", id, 0, 500)
if err != nil || total != 1 || len(logs) != 1 || logs[0].ID != id {
t.Fatalf("list = total=%d logs=%+v err=%v", total, logs, err)
}
rollback, err := svc.Rollback(ctx, id)
if err != nil {
t.Fatal(err)
}
if rollback.Action != "rollback" || rollback.ParentAuditID == nil || *rollback.ParentAuditID != id || rollback.RiskLevel != "high" {
t.Fatalf("rollback = %+v", rollback)
}
cleanupAudit(t, ctx, rollback.ID)
}
func TestAuditServiceRollbackRejectsMissingBeforeState(t *testing.T) {
ctx := setupServicePGIntegration(t)
svc := NewAuditService()
id := serviceTestUUID(t)
defer cleanupAudit(t, ctx, id)
log := &AuditLog{ID: id, TenantID: "tenant", ObjectType: "rule", ObjectID: id, Action: "create", AfterState: map[string]any{"enabled": true}, RequestID: "req", ResultCode: "SUCCESS", SourceIP: "127.0.0.1", ActorID: "actor", RiskLevel: "normal", CreatedAt: time.Now()}
if err := svc.Record(ctx, log); err != nil {
t.Fatal(err)
}
if _, err := svc.Rollback(ctx, id); err == nil {
t.Fatal("expected rollback error without before state")
}
if _, err := svc.Rollback(ctx, serviceTestUUID(t)); err == nil {
t.Fatal("expected missing audit error")
}
}