mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
670698090f
Catch unique constraint violation (SQLSTATE 23505) during route creation in ResolveConversation and fall back to Find, preventing duplicate key errors when concurrent inbound messages hit the same chat simultaneously. Add shared IsUniqueViolation helper to internal/db.
123 lines
2.8 KiB
Go
123 lines
2.8 KiB
Go
package db
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
func TestParseUUID(t *testing.T) {
|
|
validUUID := uuid.MustParse("550e8400-e29b-41d4-a716-446655440000")
|
|
tests := []struct {
|
|
name string
|
|
id string
|
|
wantErr bool
|
|
want pgtype.UUID
|
|
}{
|
|
{
|
|
name: "valid",
|
|
id: "550e8400-e29b-41d4-a716-446655440000",
|
|
wantErr: false,
|
|
want: pgtype.UUID{Bytes: validUUID, Valid: true},
|
|
},
|
|
{
|
|
name: "valid with whitespace",
|
|
id: " 550e8400-e29b-41d4-a716-446655440000 ",
|
|
wantErr: false,
|
|
want: pgtype.UUID{Bytes: validUUID, Valid: true},
|
|
},
|
|
{
|
|
name: "invalid format",
|
|
id: "not-a-uuid",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "empty",
|
|
id: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "partial",
|
|
id: "550e8400-e29b",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := ParseUUID(tt.id)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("ParseUUID() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !tt.wantErr && (got.Valid != tt.want.Valid || got.Bytes != tt.want.Bytes) {
|
|
t.Errorf("ParseUUID() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTimeFromPg(t *testing.T) {
|
|
now := time.Now()
|
|
tests := []struct {
|
|
name string
|
|
value pgtype.Timestamptz
|
|
want time.Time
|
|
}{
|
|
{"valid", pgtype.Timestamptz{Time: now, Valid: true}, now},
|
|
{"invalid", pgtype.Timestamptz{}, time.Time{}},
|
|
{"valid zero", pgtype.Timestamptz{Time: time.Time{}, Valid: true}, time.Time{}},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := TimeFromPg(tt.value)
|
|
if !got.Equal(tt.want) {
|
|
t.Errorf("TimeFromPg() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTextToString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
value pgtype.Text
|
|
want string
|
|
}{
|
|
{"valid", pgtype.Text{String: "hello", Valid: true}, "hello"},
|
|
{"invalid", pgtype.Text{}, ""},
|
|
{"valid empty", pgtype.Text{String: "", Valid: true}, ""},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := TextToString(tt.value); got != tt.want {
|
|
t.Errorf("TextToString() = %q, want %q", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsUniqueViolation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
want bool
|
|
}{
|
|
{"nil", nil, false},
|
|
{"plain error", fmt.Errorf("some error"), false},
|
|
{"unique violation", &pgconn.PgError{Code: "23505"}, true},
|
|
{"other pg error", &pgconn.PgError{Code: "23503"}, false},
|
|
{"wrapped unique violation", fmt.Errorf("wrapped: %w", &pgconn.PgError{Code: "23505"}), true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := IsUniqueViolation(tt.err); got != tt.want {
|
|
t.Errorf("IsUniqueViolation() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|