75 lines
2.6 KiB
Go
75 lines
2.6 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/transaction"
|
|
"RenewCMS/internal/infrastructure/persistence/database"
|
|
persistence "RenewCMS/internal/infrastructure/persistence/repositories"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
|
|
mysqldriver "github.com/go-sql-driver/mysql"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
)
|
|
|
|
type codedSQLiteError struct{ code int }
|
|
|
|
func (c codedSQLiteError) Error() string { return fmt.Sprintf("sqlite code %d", c.code) }
|
|
func (c codedSQLiteError) Code() int { return c.code }
|
|
|
|
func TestDriverLockContention(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
driver string
|
|
err error
|
|
want bool
|
|
}{
|
|
{"sqlite busy", database.DriverSQLite, codedSQLiteError{5}, true},
|
|
{"sqlite busy snapshot", database.DriverSQLite, fmt.Errorf("wrapped: %w", codedSQLiteError{5 | 2<<8}), true},
|
|
{"sqlite locked", database.DriverSQLite, codedSQLiteError{6}, true},
|
|
{"sqlite constraint", database.DriverSQLite, codedSQLiteError{19}, false},
|
|
{"sqlite plain error", database.DriverSQLite, errors.New("database is locked"), false},
|
|
{"mysql nowait", database.DriverMySQL, &mysqldriver.MySQLError{Number: 3572}, true},
|
|
{"mysql lock wait timeout", database.DriverMySQL, fmt.Errorf("wrapped: %w", &mysqldriver.MySQLError{Number: 1205}), true},
|
|
{"mysql duplicate", database.DriverMySQL, &mysqldriver.MySQLError{Number: 1062}, false},
|
|
{"postgres lock not available", database.DriverPostgres, &pgconn.PgError{Code: "55P03"}, true},
|
|
{"postgres unique violation", database.DriverPostgres, &pgconn.PgError{Code: "23505"}, false},
|
|
{"unknown driver", "oracle", codedSQLiteError{5}, false},
|
|
{"nil error", database.DriverSQLite, nil, false},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
if got := database.IsLockContentionFor(c.driver, c.err); got != c.want {
|
|
t.Errorf("got %v, want %v", got, c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTransactorClassifiesLockContention(t *testing.T) {
|
|
db := openContractSQLite(t)
|
|
transactor := persistence.NewTransactor(db)
|
|
run := func(failure error) error {
|
|
return transactor.Run(context.Background(), func(transaction.Repositories) error { return failure })
|
|
}
|
|
t.Run("a busy error becomes the domain sentinel and keeps its cause", func(t *testing.T) {
|
|
cause := codedSQLiteError{5}
|
|
err := run(cause)
|
|
if !errors.Is(err, transaction.ErrLockContention) || !errors.Is(err, cause) {
|
|
t.Errorf("err = %v", err)
|
|
}
|
|
})
|
|
t.Run("another error is returned untouched", func(t *testing.T) {
|
|
other := errors.New("other")
|
|
if err := run(other); err != other {
|
|
t.Errorf("err = %v", err)
|
|
}
|
|
})
|
|
t.Run("success stays nil", func(t *testing.T) {
|
|
if err := run(nil); err != nil {
|
|
t.Errorf("err = %v", err)
|
|
}
|
|
})
|
|
}
|