Files

142 lines
3.7 KiB
Go
Raw Permalink Normal View History

package db_test
import (
"errors"
"fmt"
"testing"
"github.com/J3vb/OwnCord/Server/db"
)
// ─── Sentinel error identity ─────────────────────────────────────────────────
func TestSentinelErrors_AreDistinct(t *testing.T) {
sentinels := []struct {
name string
err error
}{
{"ErrNotFound", db.ErrNotFound},
{"ErrForbidden", db.ErrForbidden},
{"ErrConflict", db.ErrConflict},
{"ErrBanned", db.ErrBanned},
{"ErrLastAdmin", db.ErrLastAdmin},
}
for i, a := range sentinels {
for j, b := range sentinels {
if i == j {
continue
}
if errors.Is(a.err, b.err) {
t.Errorf("%s should not match %s", a.name, b.name)
}
}
}
}
func TestSentinelErrors_MatchThemselves(t *testing.T) {
sentinels := []struct {
name string
err error
}{
{"ErrNotFound", db.ErrNotFound},
{"ErrForbidden", db.ErrForbidden},
{"ErrConflict", db.ErrConflict},
{"ErrBanned", db.ErrBanned},
{"ErrLastAdmin", db.ErrLastAdmin},
}
for _, tc := range sentinels {
t.Run(tc.name, func(t *testing.T) {
if !errors.Is(tc.err, tc.err) {
t.Errorf("errors.Is(%s, %s) = false, want true", tc.name, tc.name)
}
})
}
}
func TestSentinelErrors_MatchWrapped(t *testing.T) {
sentinels := []struct {
name string
err error
}{
{"ErrNotFound", db.ErrNotFound},
{"ErrForbidden", db.ErrForbidden},
{"ErrConflict", db.ErrConflict},
{"ErrBanned", db.ErrBanned},
{"ErrLastAdmin", db.ErrLastAdmin},
}
for _, tc := range sentinels {
t.Run(tc.name, func(t *testing.T) {
wrapped := fmt.Errorf("operation failed: %w", tc.err)
if !errors.Is(wrapped, tc.err) {
t.Errorf("errors.Is(wrapped, %s) = false, want true", tc.name)
}
})
}
}
func TestSentinelErrors_DoubleWrapped(t *testing.T) {
inner := fmt.Errorf("db query: %w", db.ErrNotFound)
outer := fmt.Errorf("handler: %w", inner)
if !errors.Is(outer, db.ErrNotFound) {
t.Error("double-wrapped ErrNotFound should still match")
}
}
func TestSentinelErrors_HaveNonEmptyMessages(t *testing.T) {
sentinels := []error{
db.ErrNotFound,
db.ErrForbidden,
db.ErrConflict,
db.ErrBanned,
db.ErrLastAdmin,
}
for _, err := range sentinels {
if err.Error() == "" {
t.Errorf("sentinel error has empty message")
}
}
}
// ─── IsUniqueConstraintError ─────────────────────────────────────────────────
func TestIsUniqueConstraintError_MatchesSQLiteMessage(t *testing.T) {
sqliteErr := errors.New("UNIQUE constraint failed: users.username")
if !db.IsUniqueConstraintError(sqliteErr) {
t.Error("should match SQLite UNIQUE constraint error")
}
}
func TestIsUniqueConstraintError_CaseSensitive(t *testing.T) {
lower := errors.New("unique constraint failed: users.username")
if db.IsUniqueConstraintError(lower) {
t.Error("should be case-sensitive (SQLite always uses uppercase UNIQUE)")
}
}
func TestIsUniqueConstraintError_NilError(t *testing.T) {
if db.IsUniqueConstraintError(nil) {
t.Error("nil error should return false")
}
}
func TestIsUniqueConstraintError_UnrelatedError(t *testing.T) {
unrelated := errors.New("connection refused")
if db.IsUniqueConstraintError(unrelated) {
t.Error("unrelated error should return false")
}
}
func TestIsUniqueConstraintError_WrappedSQLiteError(t *testing.T) {
inner := errors.New("UNIQUE constraint failed: users.email")
wrapped := fmt.Errorf("insert user: %w", inner)
// The function uses strings.Contains on err.Error(), so wrapping preserves the substring.
if !db.IsUniqueConstraintError(wrapped) {
t.Error("wrapped UNIQUE constraint error should match (message preserved in chain)")
}
}