From 07eebf1e23745c85de5c8d48b5162eddc19fffb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Le=C3=B3n=20Camacho?= Date: Fri, 2 Jun 2023 22:09:47 -0500 Subject: [PATCH 1/2] Add function to check error on unique index. --- db.go | 13 +++++++++++++ db_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/db.go b/db.go index 1d8ad2d..5a8cb6e 100644 --- a/db.go +++ b/db.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" + "regexp" "github.com/surrealdb/surrealdb.go/pkg/websocket" ) @@ -319,3 +320,15 @@ func isSlice(possibleSlice interface{}) bool { return slice } + +// IsDuplicateUniqueIdx returns if the error was caused by +// trying to create a record with a field that is duplicated +// in an unique index. This function will return false if the +// error was caused by a duplicated ID. +func IsDuplicateUniqueIdx(err error) bool { + if err == nil { + return false + } + match, _ := regexp.MatchString(`Database index .* already contains .*, with record .*`, err.Error()) + return match +} diff --git a/db_test.go b/db_test.go index cac65fd..01820a8 100644 --- a/db_test.go +++ b/db_test.go @@ -2,6 +2,7 @@ package surrealdb_test import ( "bytes" + "errors" "fmt" "os" "testing" @@ -437,3 +438,57 @@ func assertContains[K fmt.Stringer](s *SurrealDBTestSuite, input []K, matcher fu s.NotEmptyf(matching, "Input %+v did not contain matching element", fmt.Sprintf("%+v", input)) return matching } + +func TestIsDuplicateUniqueIdx(t *testing.T) { + type args struct { + err error + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "No special characters", + args: args{ + err: errors.New("sending request failed for method 'create': There was a problem with the database: Database index `userNameIdx` already contains 'Mark', with record `user:⟨2⟩`"), + }, + want: true, + }, + { + name: "With special characters", + args: args{ + err: errors.New("sending request failed for method 'create': There was a problem with the database: Database index \"''``\" already contains '\\', with record `user:⟨{ foo = []}⟩`"), + }, + want: true, + }, + { + name: "Wrong substring in the middle", + args: args{ + err: errors.New("sending request failed for method 'create': There was a problem with the database: Database index `userNameIdx` THIS MUST NOT PASS, with record `user:⟨2⟩`"), + }, + want: false, + }, + { + name: "With object field", + args: args{ + err: errors.New(" Database index `uniqueBlob` already contains { date: 'today', location: 'London' }, with record `foo:2`"), + }, + want: true, + }, + { + name: "Case sensitive", + args: args{ + err: errors.New(" database index `uniqueBlob` already contains { date: 'today', location: 'London' }, with record `foo:2`"), + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := surrealdb.IsDuplicateUniqueIdx(tt.args.err); got != tt.want { + t.Errorf("IsDuplicateUniqueIdx() = %v, want %v", got, tt.want) + } + }) + } +} From 1a4e90660be57f986ef906017e08dda635aaee6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Le=C3=B3n=20Camacho?= Date: Wed, 21 Jun 2023 01:02:46 -0500 Subject: [PATCH 2/2] Write tests using testify. --- db.go | 6 +-- db_test.go | 139 ++++++++++++++++++++++++++++++++--------------------- 2 files changed, 87 insertions(+), 58 deletions(-) diff --git a/db.go b/db.go index 5a8cb6e..8456f6e 100644 --- a/db.go +++ b/db.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "reflect" "regexp" @@ -321,10 +320,9 @@ func isSlice(possibleSlice interface{}) bool { return slice } -// IsDuplicateUniqueIdx returns if the error was caused by +// IsDuplicateUniqueIdx returns true if the error was caused by // trying to create a record with a field that is duplicated -// in an unique index. This function will return false if the -// error was caused by a duplicated ID. +// in an unique index. func IsDuplicateUniqueIdx(err error) bool { if err == nil { return false diff --git a/db_test.go b/db_test.go index 01820a8..fd3ff36 100644 --- a/db_test.go +++ b/db_test.go @@ -2,7 +2,6 @@ package surrealdb_test import ( "bytes" - "errors" "fmt" "os" "testing" @@ -11,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/surrealdb/surrealdb.go" - gorilla "github.com/surrealdb/surrealdb.go/pkg/gorilla" + "github.com/surrealdb/surrealdb.go/pkg/gorilla" "github.com/surrealdb/surrealdb.go/pkg/logger" "github.com/surrealdb/surrealdb.go/pkg/websocket" ) @@ -146,7 +145,7 @@ func (s *SurrealDBTestSuite) TestCreate() { s.Run("Single create works", func() { userData, err := s.db.Create("users", testUser{ - Username: "johnny", + Username: "tim", Password: "123", }) s.Require().NoError(err) @@ -157,7 +156,7 @@ func (s *SurrealDBTestSuite) TestCreate() { s.Require().NoError(err) s.Len(userSlice, 1) - s.Equal("johnny", userSlice[0].Username) + s.Equal("tim", userSlice[0].Username) s.Equal("123", userSlice[0].Password) }) @@ -166,7 +165,7 @@ func (s *SurrealDBTestSuite) TestCreate() { data := make([]testUser, 0) data = append(data, testUser{ - Username: "johnny", + Username: "lu", Password: "123"}, testUser{ Username: "joe", @@ -439,56 +438,88 @@ func assertContains[K fmt.Stringer](s *SurrealDBTestSuite, input []K, matcher fu return matching } -func TestIsDuplicateUniqueIdx(t *testing.T) { - type args struct { - err error +func (s *SurrealDBTestSuite) TestIsDuplicateUniqueIdx() { + query := `DEFINE INDEX uniqueNameIdx ON TABLE users COLUMNS username UNIQUE;` + _, err := s.db.Query(query, nil) // because of this query, I modified the 'username' field in other tests. + s.Require().NoError(err) + + userNoSpecialChars := testUser{ + Username: "johnny", + Password: "1234", } - tests := []struct { - name string - args args - want bool - }{ - { - name: "No special characters", - args: args{ - err: errors.New("sending request failed for method 'create': There was a problem with the database: Database index `userNameIdx` already contains 'Mark', with record `user:⟨2⟩`"), - }, - want: true, - }, - { - name: "With special characters", - args: args{ - err: errors.New("sending request failed for method 'create': There was a problem with the database: Database index \"''``\" already contains '\\', with record `user:⟨{ foo = []}⟩`"), - }, - want: true, - }, - { - name: "Wrong substring in the middle", - args: args{ - err: errors.New("sending request failed for method 'create': There was a problem with the database: Database index `userNameIdx` THIS MUST NOT PASS, with record `user:⟨2⟩`"), - }, - want: false, - }, - { - name: "With object field", - args: args{ - err: errors.New(" Database index `uniqueBlob` already contains { date: 'today', location: 'London' }, with record `foo:2`"), - }, - want: true, - }, - { - name: "Case sensitive", - args: args{ - err: errors.New(" database index `uniqueBlob` already contains { date: 'today', location: 'London' }, with record `foo:2`"), - }, - want: false, - }, + data, err := s.db.Create("users", userNoSpecialChars) // true + s.Require().NoError(err) + s.Require().NotNil(data) // we collect this result, so we can get the record it to use it for other test + + var createdUser []testUser + s.Require().NoError(surrealdb.Unmarshal(data, &createdUser)) + s.Require().NotEmpty(createdUser[0].ID) + + userWithSpecialChars := testUser{ + Username: "jim", + Password: "abcd", } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := surrealdb.IsDuplicateUniqueIdx(tt.args.err); got != tt.want { - t.Errorf("IsDuplicateUniqueIdx() = %v, want %v", got, tt.want) - } - }) + _, err = s.db.Create("users", userWithSpecialChars) // true + s.Require().NoError(err) + + userWithObjectID := map[string]interface{}{ + "username": struct { + Field1 string + }{ + Field1: "nestedID", + }, + "password": "4321", } + _, err = s.db.Create("users", userWithObjectID) + s.Require().NoError(err) + + // now, the actual tests... + msg := "the operation must fail" + s.Run("normal characters work", func() { + _, err = s.db.Create("users", userNoSpecialChars) + s.Require().NotNil(err, msg) // this validation is redundant, the function already checks for nil, but just in case + s.True(surrealdb.IsDuplicateUniqueIdx(err)) + }) + err = nil + + s.Run("special characters work", func() { + _, err = s.db.Create("users", userWithSpecialChars) + s.Require().NotNil(err, msg) + s.True(surrealdb.IsDuplicateUniqueIdx(err)) + }) + err = nil + + s.Run("records with objects as id work", func() { + _, err = s.db.Create("users", userWithObjectID) + s.Require().NotNil(err, msg) + s.True(surrealdb.IsDuplicateUniqueIdx(err)) + }) + err = nil + + // try other typical errors + s.Run("it returns false on other errors", func() { + _, err = s.db.Create("users", testUser{ID: createdUser[0].ID}) + s.Require().NotNil(err) + s.False(surrealdb.IsDuplicateUniqueIdx(err)) + + err = nil + + _, err = s.db.Select("users:notexists") + s.Equal(err, surrealdb.ErrNoRow) + s.False(surrealdb.IsDuplicateUniqueIdx(err)) + }) + err = nil + + // and finally, check with a tricky ID; this is the only way the function can give a false positive + trickyID := "Database index `userNameIdx` already contains 'Mark', with record `user:⟨2⟩`" + trickyUser := testUser{ID: trickyID} + _, err = s.db.Create("users", trickyUser) + s.Require().NoError(err) + s.Run("since the function works with regexp, this one should be true", func() { + _, err = s.db.Create("users", trickyUser) // we force a duplicate ID error + s.Require().NotNil(err) + // the matched string is contained in the error (although it's not the error itself, + // but the id of the record causing the problem), the function will give us a true. + s.True(surrealdb.IsDuplicateUniqueIdx(err)) + }) }