From c91fd9743acbb724a3163e9391df1e5bcceb9cf1 Mon Sep 17 00:00:00 2001 From: James Page <james@bob.co.za> Date: Tue, 21 May 2024 15:22:09 +0200 Subject: [PATCH] Add NormalizeEmail and ValidateIPAddress functions Added function to normalise an email address. Currently setup with the default rules for how some email domains allow plus-addressing and other variations, but we can extend it if we need to. Added function to validate and clean an IP address. Added tests for both functions, as well as for StripEmail. --- go.mod | 1 + go.sum | 2 + utils/utils.go | 16 +++++ utils/utils_test.go | 148 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 utils/utils_test.go diff --git a/go.mod b/go.mod index abf221b..3078df4 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/aws/aws-lambda-go v1.26.0 github.com/aws/aws-sdk-go v1.44.180 github.com/aws/aws-secretsmanager-caching-go v1.1.0 + github.com/dimuska139/go-email-normalizer/v2 v2.0.0 github.com/dlsniper/debugger v0.6.0 github.com/go-pg/pg/v10 v10.10.6 github.com/go-redis/redis/v8 v8.11.4 diff --git a/go.sum b/go.sum index cc0f301..23b6fde 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dimuska139/go-email-normalizer/v2 v2.0.0 h1:LH41ypO4BFast9bc8hNu6YEkRvLloHFYihSjfwiARSg= +github.com/dimuska139/go-email-normalizer/v2 v2.0.0/go.mod h1:2Gil1j/rfUKJ5BHc/uxxyRiuk3YTg6/C3D7dz7jVQfw= github.com/dlsniper/debugger v0.6.0 h1:AyPoOtJviCmig9AKNRAPPw5B5UyB+cI72zY3Jb+6LlA= github.com/dlsniper/debugger v0.6.0/go.mod h1:FFdRcPU2Yo4P411bp5U97DHJUSUMKcqw1QMGUu0uVb8= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/utils/utils.go b/utils/utils.go index d06280d..526025d 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -3,11 +3,13 @@ package utils import ( "bytes" emailverifier "github.com/AfterShip/email-verifier" + normalizer "github.com/dimuska139/go-email-normalizer/v2" "github.com/jinzhu/now" "github.com/mohae/deepcopy" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors" "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils" "math" + "net" "net/url" "os" "reflect" @@ -149,6 +151,11 @@ func StripEmail(email string) (strippedEmail string, strippedDomain string) { return strippedEmail, strippedDomain } +func NormalizeEmail(email string) string { + emailNormalizer := normalizer.NewNormalizer() + return emailNormalizer.Normalize(email) +} + // IsUrlStrict Returns whether a URL is valid in a strict way (Must have scheme and host) func IsUrlStrict(str string) bool { u, err := url.Parse(str) @@ -258,3 +265,12 @@ func DetermineDaysLeft(fromDateLocal time.Time, toDateLocal time.Time) int { toDate := now.With(toDateLocal).EndOfDay() return int(math.Floor(toDate.Sub(fromDate).Hours() / 24)) } + +func ValidateIPAddress(ipAddress string) (cleanedIPAddress string, err error) { + ipAddress = strings.ToLower(strings.TrimSpace(ipAddress)) + ip := net.ParseIP(ipAddress) + if ip == nil { + return "", errors.Error("invalid IP address") + } + return ipAddress, nil +} diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 0000000..b1a0769 --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,148 @@ +package utils + +import ( + "gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors" + "testing" +) + +func TestStripEmail(t *testing.T) { + tests := []struct { + name string + email string + wantStripped string + wantDomain string + }{ + { + name: "Test with + symbol", + email: "test+extra@gmail.com", + wantStripped: "test@gmail.com", + wantDomain: "gmail.com", + }, + { + name: "Test without + symbol", + email: "test@gmail.com", + wantStripped: "test@gmail.com", + wantDomain: "gmail.com", + }, + { + name: "Test with multiple + symbols", + email: "test+extra+more@gmail.com", + wantStripped: "test@gmail.com", + wantDomain: "gmail.com", + }, + { + name: "Test with different domain", + email: "test+extra@yahoo.com", + wantStripped: "test@yahoo.com", + wantDomain: "yahoo.com", + }, + { + name: "Test with subdomain", + email: "test+extra@mail.example.com", + wantStripped: "test@mail.example.com", + wantDomain: "mail.example.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotStripped, gotDomain := StripEmail(tt.email) + if gotStripped != tt.wantStripped { + t.Errorf("StripEmail() gotStripped = %v, want %v", gotStripped, tt.wantStripped) + } + if gotDomain != tt.wantDomain { + t.Errorf("StripEmail() gotDomain = %v, want %v", gotDomain, tt.wantDomain) + } + }) + } +} + +func TestNormalizeEmail(t *testing.T) { + tests := []struct { + name string + email string + wantNormalized string + }{ + { + name: "Test with + symbol", + email: "test+extra@gmail.com", + wantNormalized: "test@gmail.com", + }, + { + name: "Test without + symbol", + email: "test@gmail.com", + wantNormalized: "test@gmail.com", + }, + { + name: "Test with multiple + symbols", + email: "test+extra+more@gmail.com", + wantNormalized: "test@gmail.com", + }, + { + name: "Test with different domain", + email: "test-extra@yahoo.com", + wantNormalized: "testextra@yahoo.com", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotNormalized := NormalizeEmail(tt.email) + if gotNormalized != tt.wantNormalized { + t.Errorf("NormalizeEmail() gotNormalized = %v, want %v", gotNormalized, tt.wantNormalized) + } + }) + } +} + +func TestValidateIPAddress(t *testing.T) { + tests := []struct { + name string + ip string + want string + err error + }{ + { + name: "Test with valid IPv4", + ip: "192.168.1.1", + want: "192.168.1.1", + err: nil, + }, + { + name: "Test with valid IPv6", + ip: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + want: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + err: nil, + }, + { + name: "Test with invalid IP", + ip: "999.999.999.999", + want: "", + err: errors.Error("invalid IP address"), + }, + { + name: "Test with empty string", + ip: "", + want: "", + err: errors.Error("invalid IP address"), + }, + { + name: "Test with non-IP string", + ip: "not an ip address", + want: "", + err: errors.Error("invalid IP address"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ValidateIPAddress(tt.ip) + if got != tt.want { + t.Errorf("ValidateIPAddress() got = %v, want %v", got, tt.want) + } + if err != nil && err.Error() != tt.err.Error() { + t.Errorf("ValidateIPAddress() err = %v, want %v", err, tt.err) + } + }) + } +} -- GitLab