* fix: parameterize slow/error SQL logs to avoid leaking credentials * fix: validate SQL_SLOW_THRESHOLD_MS range * fix: sanitize database driver error messages in SQL logs * refactor: sanitize at gorm log writer seam to keep caller attribution
103 lines
3.2 KiB
Go
103 lines
3.2 KiB
Go
package model
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/glebarez/sqlite"
|
|
"github.com/go-sql-driver/mysql"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// 保护契约:数据库驱动错误消息可能内联数据值,非 DEBUG 下日志只保留错误码。
|
|
func TestSanitizeDBErrorStripsDriverMessage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
want string
|
|
leaked string
|
|
}{
|
|
{
|
|
name: "mysql duplicate entry",
|
|
err: &mysql.MySQLError{Number: 1062, Message: "Duplicate entry 'secret-value' for key 'users.idx'"},
|
|
want: "mysql error 1062",
|
|
leaked: "secret-value",
|
|
},
|
|
{
|
|
name: "postgres unique violation",
|
|
err: &pgconn.PgError{Code: "23505", Message: "duplicate key value", Detail: "Key (k)=(secret-value) already exists."},
|
|
want: "postgres error SQLSTATE 23505",
|
|
leaked: "secret-value",
|
|
},
|
|
{
|
|
name: "clickhouse exception",
|
|
err: &proto.Exception{Code: 241, Message: "Memory limit exceeded while processing 'secret-value'"},
|
|
want: "clickhouse error 241",
|
|
leaked: "secret-value",
|
|
},
|
|
{
|
|
name: "wrapped driver error",
|
|
err: fmt.Errorf("exec failed: %w", &mysql.MySQLError{Number: 1064, Message: "syntax error near 'secret-value'"}),
|
|
want: "mysql error 1064",
|
|
leaked: "secret-value",
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := sanitizeDBError(tc.err)
|
|
require.Error(t, got)
|
|
assert.Equal(t, tc.want, got.Error())
|
|
assert.NotContains(t, got.Error(), tc.leaked)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSanitizeDBErrorSQLiteDriver(t *testing.T) {
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
execErr := db.Exec("INSERT INTO missing_table (k) VALUES (?)", "secret-value").Error
|
|
require.Error(t, execErr)
|
|
|
|
got := sanitizeDBError(execErr)
|
|
assert.Regexp(t, `^sqlite error \d+$`, got.Error())
|
|
assert.NotContains(t, got.Error(), "secret-value")
|
|
}
|
|
|
|
func TestSanitizeDBErrorKeepsNonDriverErrors(t *testing.T) {
|
|
err := fmt.Errorf("dial tcp 127.0.0.1:3306: connect: connection refused")
|
|
assert.Equal(t, err, sanitizeDBError(err))
|
|
}
|
|
|
|
// 保护契约:经 gorm 真实链路,错误日志同时满足 SQL 参数化、驱动错误脱敏、
|
|
// 调用点归因到业务代码;DEBUG=true 恢复参数值与错误原文。
|
|
func TestGormLoggerEndToEndSanitizedOutput(t *testing.T) {
|
|
previousDebug := common.DebugEnabled
|
|
t.Cleanup(func() { common.DebugEnabled = previousDebug })
|
|
|
|
execQuery := func() string {
|
|
var buf bytes.Buffer
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{Logger: newGormLogger(&buf)})
|
|
require.NoError(t, err)
|
|
db.Exec("SELECT * FROM missing_table WHERE k = ?", "secret-value")
|
|
return buf.String()
|
|
}
|
|
|
|
common.DebugEnabled = false
|
|
out := execQuery()
|
|
assert.Contains(t, out, "k = ?")
|
|
assert.NotContains(t, out, "secret-value")
|
|
assert.Contains(t, out, "sqlite error")
|
|
assert.Contains(t, out, "gorm_logger_test.go")
|
|
|
|
common.DebugEnabled = true
|
|
debugOut := execQuery()
|
|
assert.Contains(t, debugOut, "secret-value")
|
|
assert.Contains(t, debugOut, "no such table")
|
|
}
|