mirror of
https://gh-proxy.org/https://github.com/klinsmaya/new-api.git
synced 2026-11-08 01:17:09 +08:00
142 lines
5.4 KiB
Go
142 lines
5.4 KiB
Go
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/glebarez/sqlite"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/driver/postgres"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
"gorm.io/gorm/schema"
|
|
)
|
|
|
|
// TestAuditLogSortIndexDatabaseMatrix verifies audit ordering and index migrations across supported databases.
|
|
func TestAuditLogSortIndexDatabaseMatrix(t *testing.T) {
|
|
for _, dialect := range []string{"sqlite", "mysql", "postgres"} {
|
|
t.Run(dialect, func(t *testing.T) {
|
|
var driver gorm.Dialector
|
|
switch dialect {
|
|
case "sqlite":
|
|
driver = sqlite.Open(":memory:")
|
|
case "mysql":
|
|
dsn := os.Getenv("TEST_MYSQL_DSN")
|
|
if dsn == "" {
|
|
t.Skip("TEST_MYSQL_DSN is not configured")
|
|
}
|
|
driver = mysql.Open(dsn)
|
|
case "postgres":
|
|
dsn := os.Getenv("TEST_POSTGRES_DSN")
|
|
if dsn == "" {
|
|
t.Skip("TEST_POSTGRES_DSN is not configured")
|
|
}
|
|
driver = postgres.Open(dsn)
|
|
}
|
|
db, err := gorm.Open(driver, &gorm.Config{NamingStrategy: schema.NamingStrategy{TablePrefix: "audit_sort_test_"}})
|
|
require.NoError(t, err)
|
|
sqlDB, err := db.DB()
|
|
require.NoError(t, err)
|
|
sqlDB.SetMaxOpenConns(1)
|
|
previousType := common.LogDatabaseType()
|
|
common.SetLogDatabaseType(common.DatabaseType(dialect))
|
|
t.Cleanup(func() {
|
|
common.SetLogDatabaseType(previousType)
|
|
assert.NoError(t, sqlDB.Close())
|
|
})
|
|
versionSQL := "SELECT version()"
|
|
if dialect == "sqlite" {
|
|
versionSQL = "SELECT sqlite_version()"
|
|
}
|
|
var version string
|
|
require.NoError(t, db.Raw(versionSQL).Scan(&version).Error)
|
|
t.Logf("database version: %s", version)
|
|
for _, upgrade := range []bool{false, true} {
|
|
t.Run(fmt.Sprintf("upgrade=%v", upgrade), func(t *testing.T) {
|
|
db := db
|
|
if dialect == "postgres" {
|
|
db = db.Begin()
|
|
require.NoError(t, db.Error)
|
|
t.Cleanup(func() { assert.NoError(t, db.Rollback().Error) })
|
|
schemaName := fmt.Sprintf("audit_sort_test_%d", time.Now().UnixNano())
|
|
require.NoError(t, db.Exec("CREATE SCHEMA ?", clause.Table{Name: schemaName}).Error)
|
|
require.NoError(t, db.Exec("SET LOCAL search_path TO ?", clause.Table{Name: schemaName}).Error)
|
|
} else {
|
|
t.Cleanup(func() { assert.NoError(t, db.Migrator().DropTable(&AuditLog{})) })
|
|
}
|
|
previousLogDB := LOG_DB
|
|
LOG_DB = db
|
|
t.Cleanup(func() { LOG_DB = previousLogDB })
|
|
require.NoError(t, MigrateAuditLogs())
|
|
if upgrade {
|
|
require.NoError(t, db.Migrator().DropIndex(&AuditLog{}, "idx_audit_time_event"))
|
|
}
|
|
entries := []AuditLog{
|
|
{EventId: "event-older", CreatedAt: 100, UserId: 1, ActorRole: common.RoleCommonUser},
|
|
{EventId: "event-a", CreatedAt: 200, UserId: 1, ActorRole: common.RoleCommonUser},
|
|
{EventId: "event-z", CreatedAt: 200, UserId: 1, ActorRole: common.RoleCommonUser},
|
|
}
|
|
require.NoError(t, db.Create(&entries).Error)
|
|
originalIndexes, err := db.Migrator().GetIndexes(&AuditLog{})
|
|
require.NoError(t, err)
|
|
for range 2 {
|
|
require.NoError(t, MigrateAuditLogs())
|
|
}
|
|
indexes, err := db.Migrator().GetIndexes(&AuditLog{})
|
|
require.NoError(t, err)
|
|
byName := make(map[string]gorm.Index, len(indexes))
|
|
for _, index := range indexes {
|
|
byName[index.Name()] = index
|
|
}
|
|
index, ok := byName["idx_audit_time_event"]
|
|
require.True(t, ok, "migration must create the audit ordering index")
|
|
var columns []string
|
|
if dialect == "postgres" {
|
|
// GORM's PostgreSQL GetIndexes does not preserve index key order.
|
|
require.NoError(t, db.Raw(`
|
|
SELECT attribute.attname
|
|
FROM pg_catalog.pg_index AS index_meta
|
|
CROSS JOIN LATERAL unnest(index_meta.indkey) WITH ORDINALITY AS index_column(attnum, ordinal_position)
|
|
JOIN pg_catalog.pg_attribute AS attribute
|
|
ON attribute.attrelid = index_meta.indrelid AND attribute.attnum = index_column.attnum
|
|
WHERE index_meta.indexrelid = to_regclass(?)
|
|
ORDER BY index_column.ordinal_position
|
|
`, index.Name()).Scan(&columns).Error)
|
|
} else {
|
|
columns = index.Columns()
|
|
}
|
|
assert.Equal(t, []string{"created_at", "event_id"}, columns)
|
|
for _, original := range originalIndexes {
|
|
preserved, ok := byName[original.Name()]
|
|
require.True(t, ok, "existing index %s must remain", original.Name())
|
|
assert.Equal(t, original.Columns(), preserved.Columns(), original.Name())
|
|
originalUnique, originalKnown := original.Unique()
|
|
preservedUnique, preservedKnown := preserved.Unique()
|
|
assert.Equal(t, originalKnown, preservedKnown, original.Name())
|
|
assert.Equal(t, originalUnique, preservedUnique, original.Name())
|
|
}
|
|
var stored []AuditLog
|
|
require.NoError(t, db.Order("id").Find(&stored).Error)
|
|
assert.Equal(t, entries, stored, "migration must preserve existing audit records")
|
|
logs, total, err := GetAuditLogs(AuditLogFilter{}, 0, 2, common.RoleRootUser)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 3, total)
|
|
require.Len(t, logs, 2)
|
|
assert.Equal(t, []string{"event-z", "event-a"}, []string{logs[0].EventId, logs[1].EventId})
|
|
logs, total, err = GetAuditLogs(AuditLogFilter{}, 2, 2, common.RoleRootUser)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 3, total)
|
|
require.Len(t, logs, 1)
|
|
assert.Equal(t, "event-older", logs[0].EventId)
|
|
assert.Error(t, db.Create(&AuditLog{EventId: "event-a"}).Error, "event IDs must remain unique")
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|