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