Files
tm_back/pkg/mongo/duplicate_test.go
Mazyar 3f0935ee3f Enhance duplicate key error handling in MongoDB operations
- Improved the `IsDuplicateKeyError` function to utilize structured error messages for better accuracy in identifying duplicate key errors.
- Introduced `duplicateKeyMessages` and `hasStructuredWriteException` functions to parse and extract relevant information from MongoDB error messages.
- Updated `DuplicateKeyMatchesField` to match duplicate key errors against specific fields by analyzing error messages rather than relying on substring matching.

This refactor enhances the clarity and reliability of duplicate key error handling, ensuring more precise identification of conflicts in MongoDB operations.
2026-06-07 14:40:42 +03:30

304 lines
7.7 KiB
Go

package mongo
import (
"errors"
"fmt"
"testing"
mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
)
func TestIsDuplicateKeyError(t *testing.T) {
t.Parallel()
tests := []struct {
name string
err error
want bool
}{
{
name: "nil error",
err: nil,
want: false,
},
{
name: "unrelated error",
err: errors.New("connection refused"),
want: false,
},
{
name: "write exception duplicate key",
err: writeExceptionWithMessage(
11000,
`E11000 duplicate key error collection: tm.users index: phone_idx dup key: { phone: "+1234567890" }`,
),
want: true,
},
{
name: "write exception non duplicate code",
err: writeExceptionWithMessage(
121,
`Document failed validation`,
),
want: false,
},
{
name: "wrapped write exception duplicate key",
err: fmt.Errorf(
"failed to create user: %w",
writeExceptionWithMessage(
11000,
`E11000 duplicate key error collection: tm.users index: username_idx dup key: { username: "admin" }`,
),
),
want: true,
},
{
name: "bulk write exception duplicate key",
err: bulkWriteExceptionWithMessage(
11000,
`E11000 duplicate key error collection: tm.customers index: phone_idx dup key: { phone: "+1234567890" }`,
),
want: true,
},
{
name: "string fallback duplicate key",
err: errors.New(`write exception: write errors: [E11000 duplicate key error collection: tm.users index: phone_idx dup key: { phone: "+1234567890" }] `),
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := IsDuplicateKeyError(tt.err); got != tt.want {
t.Fatalf("IsDuplicateKeyError() = %v, want %v", got, tt.want)
}
})
}
}
func TestDuplicateKeyMatchesField(t *testing.T) {
t.Parallel()
const phoneMsg = `E11000 duplicate key error collection: tm.users index: phone_idx dup key: { phone: "+1234567890" }`
const usernameMsg = `E11000 duplicate key error collection: tm.users index: username_idx dup key: { username: "admin" }`
const emailMsg = `E11000 duplicate key error collection: tm.customers index: email_1 dup key: { email: "user@example.com" }`
const phoneValueContainsNameMsg = `E11000 duplicate key error collection: tm.users index: phone_idx dup key: { phone: "username-like-value" }`
tests := []struct {
name string
err error
field string
want bool
}{
{
name: "nil error",
err: nil,
field: "phone",
want: false,
},
{
name: "empty field",
err: writeExceptionWithMessage(11000, phoneMsg),
field: "",
want: false,
},
{
name: "phone duplicate matches phone field",
err: writeExceptionWithMessage(11000, phoneMsg),
field: "phone",
want: true,
},
{
name: "phone duplicate does not match email field",
err: writeExceptionWithMessage(11000, phoneMsg),
field: "email",
want: false,
},
{
name: "username duplicate matches username field",
err: writeExceptionWithMessage(11000, usernameMsg),
field: "username",
want: true,
},
{
name: "username duplicate does not match name substring",
err: writeExceptionWithMessage(11000, usernameMsg),
field: "name",
want: false,
},
{
name: "email duplicate matches email field via default index suffix",
err: writeExceptionWithMessage(11000, emailMsg),
field: "email",
want: true,
},
{
name: "dup key value substring does not match unrelated field",
err: writeExceptionWithMessage(11000, phoneValueContainsNameMsg),
field: "name",
want: false,
},
{
name: "wrapped duplicate key matches field",
err: fmt.Errorf(
"failed to register customer: %w",
writeExceptionWithMessage(11000, phoneMsg),
),
field: "phone",
want: true,
},
{
name: "non duplicate write error does not match",
err: writeExceptionWithMessage(121, phoneMsg),
field: "phone",
want: false,
},
{
name: "unrelated error does not match",
err: errors.New("timeout"),
field: "phone",
want: false,
},
{
name: "string fallback duplicate key matches field",
err: errors.New(`write exception: write errors: [E11000 duplicate key error collection: tm.users index: phone_idx dup key: { phone: "+1234567890" }] `),
field: "phone",
want: true,
},
{
name: "string fallback duplicate key does not match unrelated field",
err: errors.New(`write exception: write errors: [E11000 duplicate key error collection: tm.users index: username_idx dup key: { username: "admin" }] `),
field: "name",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := DuplicateKeyMatchesField(tt.err, tt.field); got != tt.want {
t.Fatalf("DuplicateKeyMatchesField(%q) = %v, want %v", tt.field, got, tt.want)
}
})
}
}
func TestExtractDuplicateIndexName(t *testing.T) {
t.Parallel()
tests := []struct {
name string
message string
want string
}{
{
name: "phone index",
message: `E11000 duplicate key error collection: tm.users index: phone_idx dup key: { phone: "+1234567890" }`,
want: "phone_idx",
},
{
name: "missing index segment",
message: `E11000 duplicate key error collection: tm.users dup key: { phone: "+1234567890" }`,
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := extractDuplicateIndexName(tt.message); got != tt.want {
t.Fatalf("extractDuplicateIndexName() = %q, want %q", got, tt.want)
}
})
}
}
func TestExtractDuplicateKeyFields(t *testing.T) {
t.Parallel()
tests := []struct {
name string
message string
want []string
}{
{
name: "single field",
message: `index: phone_idx dup key: { phone: "+1234567890" }`,
want: []string{"phone"},
},
{
name: "compound dup key",
message: `index: user_company_idx dup key: { user_id: ObjectId('507f1f77bcf86cd799439011'), company_id: ObjectId('507f1f77bcf86cd799439012') }`,
want: []string{"user_id", "company_id"},
},
{
name: "value containing colon does not add false field",
message: `index: phone_idx dup key: { phone: "office:123" }`,
want: []string{"phone"},
},
{
name: "missing dup key segment",
message: `index: phone_idx`,
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := extractDuplicateKeyFields(tt.message)
if len(got) != len(tt.want) {
t.Fatalf("extractDuplicateKeyFields() = %v, want %v", got, tt.want)
}
for i := range got {
if got[i] != tt.want[i] {
t.Fatalf("extractDuplicateKeyFields() = %v, want %v", got, tt.want)
}
}
})
}
}
func TestIndexNameMatchesField(t *testing.T) {
t.Parallel()
tests := []struct {
indexName string
field string
want bool
}{
{indexName: "phone_idx", field: "phone", want: true},
{indexName: "phone_1", field: "phone", want: true},
{indexName: "phone", field: "phone", want: true},
{indexName: "username_idx", field: "name", want: false},
{indexName: "email_idx", field: "mail", want: false},
}
for _, tt := range tests {
t.Run(tt.indexName+"_"+tt.field, func(t *testing.T) {
t.Parallel()
if got := indexNameMatchesField(tt.indexName, tt.field); got != tt.want {
t.Fatalf("indexNameMatchesField(%q, %q) = %v, want %v", tt.indexName, tt.field, got, tt.want)
}
})
}
}
func writeExceptionWithMessage(code int, message string) error {
return mongodriver.WriteException{
WriteErrors: []mongodriver.WriteError{
{Code: code, Message: message},
},
}
}
func bulkWriteExceptionWithMessage(code int, message string) error {
return mongodriver.BulkWriteException{
WriteErrors: []mongodriver.BulkWriteError{
{WriteError: mongodriver.WriteError{Code: code, Message: message}},
},
}
}