fix(contact): add email normalization for contacts (#531)

f-cappelli created

Change summary

config/cache.go       |  9 +++++++--
config/config_test.go | 28 ++++++++++++++++++++++++++++
2 files changed, 35 insertions(+), 2 deletions(-)

Detailed changes

config/cache.go 🔗

@@ -148,13 +148,17 @@ func LoadContactsCache() (*ContactsCache, error) {
 	return &cache, nil
 }
 
+func normalizeContactEmail(email string) string {
+	return strings.ToLower(strings.Trim(strings.TrimSpace(email), ","))
+}
+
 // AddContact adds or updates a contact in the cache.
 func AddContact(name, email string) error {
 	if email == "" {
 		return nil
 	}
 
-	email = strings.ToLower(strings.Trim(strings.TrimSpace(email), ","))
+	email = normalizeContactEmail(email)
 	name = strings.TrimSpace(name)
 
 	cache, err := LoadContactsCache()
@@ -166,7 +170,8 @@ func AddContact(name, email string) error {
 	found := false
 	for i, c := range cache.Contacts {
 		if strings.EqualFold(c.Email, email) {
-			// Update existing contact
+			// Normalize the stored email to a canonical lowercase form.
+			cache.Contacts[i].Email = email
 			cache.Contacts[i].UseCount++
 			cache.Contacts[i].LastUsed = time.Now()
 			// Update name if we have a better one

config/config_test.go 🔗

@@ -201,6 +201,34 @@ func TestConfigGetAccountByEmail(t *testing.T) {
 	}
 }
 
+func TestAddContactNormalizesEmailAndDeduplicates(t *testing.T) {
+	t.Setenv("HOME", t.TempDir())
+
+	if err := AddContact("Alice", "Alice@Example.com"); err != nil {
+		t.Fatalf("AddContact() failed: %v", err)
+	}
+	if err := AddContact("", "alice@example.com"); err != nil {
+		t.Fatalf("AddContact() failed: %v", err)
+	}
+
+	cache, err := LoadContactsCache()
+	if err != nil {
+		t.Fatalf("LoadContactsCache() failed: %v", err)
+	}
+
+	if len(cache.Contacts) != 1 {
+		t.Fatalf("Expected 1 contact after deduplication, got %d", len(cache.Contacts))
+	}
+
+	contact := cache.Contacts[0]
+	if contact.Email != "alice@example.com" {
+		t.Errorf("Expected normalized email alice@example.com, got %s", contact.Email)
+	}
+	if contact.UseCount != 2 {
+		t.Errorf("Expected UseCount 2 after duplicate add, got %d", contact.UseCount)
+	}
+}
+
 // TestConfigHasAccounts tests the HasAccounts method.
 func TestConfigHasAccounts(t *testing.T) {
 	cfg := &Config{}