Files
RenewCMS/test/lock_contention_driver_test.go
T

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)
}
})
}