Repository: moov-io/customers (go)

Issue:
customers: need to validate that only one customer address has type "Primary"
_Originally posted by @adamdecaf in https://github.com/moov-io/customers/pull/209#discussion_r499029815_

Pull request description:
validation: ensure that a primary address is not created if one already exists for the customer
Ensures that a customer cannot have two address with type `primary` through the various endpoints used to create or update a customer's addresses.

Fixes #215 

Patch:
diff --git a/api/client.yaml b/api/client.yaml
index 767cfe2..57fcb93 100644
--- a/api/client.yaml
+++ b/api/client.yaml
@@ -206,16 +206,10 @@ paths:
             type: string
         - name: count
           in: query
-          description: Optional parameter for searching by specifying the amount to return
+          description: Optional parameter for searching for customers by specifying the amount to return
           example: 20
           schema:
             type: string
-        - name: customerIDs
-          in: query
-          description: Optional parameter for searching by customers' IDs
-          example: e210a9d6-d755-4455-9bd2-9577ea7e1081,970ef15d-a4e1-473f-b5d7-da38163b0ba3
-          schema:
-            type: string
       responses:
         '200':
           description: Customers were successfully retrieved
diff --git a/docker-compose.yml b/docker-compose.yml
index c66192d..4de592b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -20,7 +20,7 @@ services:
       FEDACH_DATA_PATH: './data/fed/FedACHdir.txt'
       FEDWIRE_DATA_PATH: './data/fed/fpddir.txt'
   watchman:
-    image: moov/watchman:v0.15.1
+    image: moov/watchman:v0.15.0
     ports:
       - "8084:8084"
       - "9094:9094"
diff --git a/pkg/client/api/openapi.yaml b/pkg/client/api/openapi.yaml
index f842cc6..7d605ac 100644
--- a/pkg/client/api/openapi.yaml
+++ b/pkg/client/api/openapi.yaml
@@ -230,8 +230,8 @@ paths:
         schema:
           type: string
         style: form
-      - description: Optional parameter for searching by specifying the amount to
-          return
+      - description: Optional parameter for searching for customers by specifying
+          the amount to return
         example: 20
         explode: true
         in: query
@@ -240,15 +240,6 @@ paths:
         schema:
           type: string
         style: form
-      - description: Optional parameter for searching by customers' IDs
-        example: e210a9d6-d755-4455-9bd2-9577ea7e1081,970ef15d-a4e1-473f-b5d7-da38163b0ba3
-        explode: true
-        in: query
-        name: customerIDs
-        required: false
-        schema:
-          type: string
-        style: form
       responses:
         "200":
           content:
diff --git a/pkg/client/api_customers.go b/pkg/client/api_customers.go
index 9cf55ef..24e6677 100644
--- a/pkg/client/api_customers.go
+++ b/pkg/client/api_customers.go
@@ -2517,13 +2517,12 @@ func (a *CustomersApiService) ReplaceCustomerMetadata(ctx _context.Context, cust
 
 // SearchCustomersOpts Optional parameters for the method 'SearchCustomers'
 type SearchCustomersOpts struct {
-	Query       optional.String
-	Email       optional.String
-	Status      optional.String
-	Type_       optional.String
-	Skip        optional.String
-	Count       optional.String
-	CustomerIDs optional.String
+	Query  optional.String
+	Email  optional.String
+	Status optional.String
+	Type_  optional.String
+	Skip   optional.String
+	Count  optional.String
 }
 
 /*
@@ -2536,8 +2535,7 @@ Search for customers using different filter parameters
  * @param "Status" (optional.String) -  Optional parameter for searching by customer status
  * @param "Type_" (optional.String) -  Optional parameter for searching by customer type
  * @param "Skip" (optional.String) -  Optional parameter for searching for customers by skipping over an initial group
- * @param "Count" (optional.String) -  Optional parameter for searching by specifying the amount to return
- * @param "CustomerIDs" (optional.String) -  Optional parameter for searching by customers' IDs
+ * @param "Count" (optional.String) -  Optional parameter for searching for customers by specifying the amount to return
 @return []Customer
 */
 func (a *CustomersApiService) SearchCustomers(ctx _context.Context, localVarOptionals *SearchCustomersOpts) ([]Customer, *_nethttp.Response, error) {
@@ -2574,9 +2572,6 @@ func (a *CustomersApiService) SearchCustomers(ctx _context.Context, localVarOpti
 	if localVarOptionals != nil && localVarOptionals.Count.IsSet() {
 		localVarQueryParams.Add("count", parameterToString(localVarOptionals.Count.Value(), ""))
 	}
-	if localVarOptionals != nil && localVarOptionals.CustomerIDs.IsSet() {
-		localVarQueryParams.Add("customerIDs", parameterToString(localVarOptionals.CustomerIDs.Value(), ""))
-	}
 	// to determine the Content-Type header
 	localVarHTTPContentTypes := []string{}
 
diff --git a/pkg/client/docs/CustomersApi.md b/pkg/client/docs/CustomersApi.md
index e50e9ee..5fccbaf 100644
--- a/pkg/client/docs/CustomersApi.md
+++ b/pkg/client/docs/CustomersApi.md
@@ -1218,8 +1218,7 @@ Name | Type | Description  | Notes
  **status** | **optional.String**| Optional parameter for searching by customer status | 
  **type_** | **optional.String**| Optional parameter for searching by customer type | 
  **skip** | **optional.String**| Optional parameter for searching for customers by skipping over an initial group | 
- **count** | **optional.String**| Optional parameter for searching by specifying the amount to return | 
- **customerIDs** | **optional.String**| Optional parameter for searching by customers&#39; IDs | 
+ **count** | **optional.String**| Optional parameter for searching for customers by specifying the amount to return | 
 
 ### Return type
 
diff --git a/pkg/customers/address.go b/pkg/customers/address.go
index 5451d9b..ee327b4 100644
--- a/pkg/customers/address.go
+++ b/pkg/customers/address.go
@@ -2,6 +2,7 @@ package customers
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"net/http"
 	"strings"
@@ -10,10 +11,13 @@ import (
 	moovhttp "github.com/moov-io/base/http"
 	"github.com/moov-io/base/log"
 
-	"github.com/moov-io/customers/pkg/client"
 	"github.com/moov-io/customers/pkg/route"
 )
 
+var (
+	ErrAddressTypeDuplicate = errors.New("customer already has an address with type 'primary'")
+)
+
 func AddCustomerAddressRoutes(logger log.Logger, r *mux.Router, repo CustomerRepository) {
 	logger = logger.WithKeyValue("package", "customers")
 
@@ -29,19 +33,46 @@ func createCustomerAddress(logger log.Logger, repo CustomerRepository) http.Hand
 			return
 		}
 
-		var req address
-		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		var reqAddr address
+		if err := json.NewDecoder(r.Body).Decode(&reqAddr); err != nil {
 			moovhttp.Problem(w, err)
 			return
 		}
 
-		if err := repo.addCustomerAddress(customerID, req); err != nil {
+		cust, err := repo.GetCustomer(customerID)
+		if err != nil {
 			moovhttp.Problem(w, err)
 			return
 		}
+		if cust == nil {
+			w.WriteHeader(http.StatusNotFound)
+			return
+		}
 
-		logger.Log(fmt.Sprintf("added address for customer=%s", customerID))
+		// todo: vince-10/12/2020: we need to perform this conversion for validation til we develop a clean separation layer between client and model structs
+		var addrs []address
+		for _, addr := range cust.Addresses {
+			addrs = append(addrs, address{
+				Type:       strings.ToLower(addr.Type),
+				Address1:   addr.Address1,
+				Address2:   addr.Address2,
+				City:       addr.City,
+				State:      addr.State,
+				PostalCode: addr.PostalCode,
+				Country:    addr.Country,
+			})
+		}
+		if err := validateAddresses(append(addrs, reqAddr)); err != nil {
+			moovhttp.Problem(w, err)
+			return
+		}
 
+		if err := repo.addCustomerAddress(customerID, reqAddr); err != nil {
+			moovhttp.Problem(w, err)
+			return
+		}
+
+		logger.Log(fmt.Sprintf("added address for customer=%s", customerID))
 		respondWithCustomer(logger, w, customerID, requestID, repo)
 	}
 }
@@ -66,6 +97,21 @@ func updateCustomerAddress(logger log.Logger, repo CustomerRepository) http.Hand
 			return
 		}
 
+		if req.Type == "primary" {
+			cust, err := repo.GetCustomer(customerID)
+			if err != nil {
+				moovhttp.Problem(w, err)
+				return
+			}
+
+			for _, addr := range cust.Addresses {
+				if addr.Type == "primary" && addr.AddressID != addressId {
+					moovhttp.Problem(w, ErrAddressTypeDuplicate)
+					return
+				}
+			}
+		}
+
 		if err := repo.updateCustomerAddress(customerID, addressId, req); err != nil {
 			logger.LogErrorF("error updating customer's address: customer=%s address=%s: %v", customerID, addressId, err)
 			moovhttp.Problem(w, err)
@@ -111,30 +157,6 @@ func getAddressId(w http.ResponseWriter, r *http.Request) string {
 }
 
 type updateCustomerAddressRequest struct {
-	Type       string `json:"type"`
-	Address1   string `json:"address1"`
-	Address2   string `json:"address2"`
-	City       string `json:"city"`
-	State      string `json:"state"`
-	PostalCode string `json:"postalCode"`
-	Country    string `json:"country"`
-	Validated  bool   `json:"validated"`
-}
-
-func (req *updateCustomerAddressRequest) validate() error {
-	switch strings.ToLower(req.Type) {
-	case "primary", "secondary":
-		return nil
-	default:
-		return fmt.Errorf("updateCustomerAddressRequest: unknown type: %s", req.Type)
-	}
-}
-
-func containsValidPrimaryAddress(addrs []client.CustomerAddress) bool {
-	for i := range addrs {
-		if strings.EqualFold(addrs[i].Type, "primary") && addrs[i].Validated {
-			return true
-		}
-	}
-	return false
+	address   `json:",inline"`
+	Validated bool `json:"validated"`
 }
diff --git a/pkg/customers/customer_search.go b/pkg/customers/customer_search.go
index 45e8824..68d2267 100644
--- a/pkg/customers/customer_search.go
+++ b/pkg/customers/customer_search.go
@@ -5,7 +5,6 @@
 package customers
 
 import (
-	"database/sql"
 	"encoding/json"
 	"fmt"
 	"net/http"
@@ -14,7 +13,6 @@ import (
 	moovhttp "github.com/moov-io/base/http"
 
 	"github.com/moov-io/base/log"
-
 	"github.com/moov-io/customers/pkg/client"
 	"github.com/moov-io/customers/pkg/route"
 )
@@ -28,7 +26,7 @@ func searchCustomers(logger log.Logger, repo CustomerRepository) http.HandlerFun
 			return
 		}
 
-		params, err := parseSearchParams(r)
+		params, err := readSearchParams(r)
 		if err != nil {
 			moovhttp.Problem(w, err)
 			return
@@ -57,25 +55,15 @@ type SearchParams struct {
 	Type         string
 	Skip         int64
 	Count        int64
-	CustomerIDs  []string
 }
 
-func parseSearchParams(r *http.Request) (SearchParams, error) {
-	queryParams := r.URL.Query()
-	getQueryParam := func(key string) string {
-		return strings.ToLower(strings.TrimSpace(queryParams.Get(key)))
-	}
+func readSearchParams(r *http.Request) (SearchParams, error) {
 	params := SearchParams{
-		Query:  getQueryParam("query"),
-		Email:  getQueryParam("email"),
-		Status: getQueryParam("status"),
-		Type:   getQueryParam("type"),
-	}
-	customerIDsInput := getQueryParam("customerIDs")
-	if customerIDsInput != "" {
-		params.CustomerIDs = strings.Split(customerIDsInput, ",")
+		Query:  strings.ToLower(strings.TrimSpace(r.URL.Query().Get("query"))),
+		Email:  strings.ToLower(strings.TrimSpace(r.URL.Query().Get("email"))),
+		Status: strings.ToLower(strings.TrimSpace(r.URL.Query().Get("status"))),
+		Type:   strings.ToLower(strings.TrimSpace(r.URL.Query().Get("type"))),
 	}
-
 	skip, count, exists, err := moovhttp.GetSkipAndCount(r)
 	if exists && err != nil {
 		return params, err
@@ -96,103 +84,54 @@ func (r *sqlCustomerRepository) searchCustomers(params SearchParams) ([]*client.
 	}
 	defer stmt.Close()
 
+	var customerIDs []string
 	customers := make([]*client.Customer, 0)
+
+	// grab customerIDs
 	rows, err := stmt.Query(args...)
 	if err != nil {
-		return nil, err
+		return customers, err
 	}
 	for rows.Next() {
-		var c client.Customer
-		var birthDate *string
-		err := rows.Scan(
-			&c.CustomerID,
-			&c.FirstName,
-			&c.MiddleName,
-			&c.LastName,
-			&c.NickName,
-			&c.Suffix,
-			&c.Type,
-			&birthDate,
-			&c.Status,
-			&c.Email,
-			&c.CreatedAt,
-			&c.LastModified,
-		)
-		if err != nil {
-			return nil, err
+		var customerID string
+		if err := rows.Scan(&customerID); err == nil {
+			customerIDs = append(customerIDs, customerID)
+		} else {
+			return customers, err
 		}
-		customers = append(customers, &c)
-	}
-
-	if len(customers) == 0 {
-		return customers, nil
-	}
-
-	var customerIDs []string
-	for _, c := range customers {
-		customerIDs = append(customerIDs, c.CustomerID)
-	}
-
-	phonesByCustomerID, err := r.getPhones(customerIDs)
-	if err != nil {
-		return nil, fmt.Errorf("fetching customer phones: %v", err)
-	}
-	addressesByCustomerID, err := r.getAddresses(customerIDs)
-	if err != nil {
-		return nil, fmt.Errorf("fetching customer addresses: %v", err)
-	}
-	metadataByCustomerID, err := r.getMetadata(customerIDs)
-	if err != nil {
-		return nil, fmt.Errorf("fetching customer metadata: %v", err)
 	}
 
-	for _, c := range customers {
-		c.Phones = phonesByCustomerID[c.CustomerID]
-		c.Addresses = addressesByCustomerID[c.CustomerID]
-		c.Metadata = metadataByCustomerID[c.CustomerID].Metadata
+	// Read each Customer
+	for i := range customerIDs {
+		cust, err := r.GetCustomer(customerIDs[i])
+		if err != nil {
+			return customers, fmt.Errorf("search: customerID=%s error=%v", customerIDs[i], err)
+		}
+		customers = append(customers, cust)
 	}
-
 	return customers, nil
 }
 
 func buildSearchQuery(params SearchParams) (string, []interface{}) {
 	var args []interface{}
-	query := `select customer_id, first_name, middle_name, last_name, nick_name, suffix, type, birth_date, status, email, created_at, last_modified
-from customers where deleted_at is null`
-
-	if params.Organization != "" {
-		query += " and organization = ?"
-		args = append(args, params.Organization)
-	}
-
+	query := `select customer_id from customers where deleted_at is null and organization = ?`
+	args = append(args, params.Organization)
 	if params.Query != "" {
-		// warning: this will ONLY work for MySQL
-		query += " and lower(concat(first_name,' ', last_name)) LIKE ?"
-		args = append(args, fmt.Sprintf("%%%s%%", params.Query))
+		query += " and lower(first_name) || \" \" || lower(last_name) LIKE ?"
+		args = append(args, "%"+params.Query+"%")
 	}
-
 	if params.Email != "" {
 		query += " and lower(email) like ?"
 		args = append(args, "%"+params.Email)
 	}
-
 	if params.Status != "" {
 		query += " and status like ?"
 		args = append(args, "%"+params.Status)
 	}
-
 	if params.Type != "" {
 		query += " and type like ?"
 		args = append(args, "%"+params.Type)
 	}
-
-	if len(params.CustomerIDs) > 0 {
-		query += fmt.Sprintf(" and customer_id in (?%s)", strings.Repeat(",?", len(params.CustomerIDs)-1))
-		for _, id := range params.CustomerIDs {
-			args = append(args, id)
-		}
-	}
-
 	query += " order by created_at desc limit ?"
 	args = append(args, fmt.Sprintf("%d", params.Count))
 
@@ -203,116 +142,3 @@ from customers where deleted_at is null`
 	query += ";"
 	return query, args
 }
-
-func (r *sqlCustomerRepository) getPhones(customerIDs []string) (map[string][]client.Phone, error) {
-	query := fmt.Sprintf(
-		"select customer_id, number, valid, type from customers_phones where customer_id in (?%s)",
-		strings.Repeat(",?", len(customerIDs)-1),
-	)
-
-	rows, err := r.queryRowsByCustomerIDs(query, customerIDs)
-	if err != nil {
-		return nil, err
-	}
-	defer rows.Close()
-
-	ret := make(map[string][]client.Phone)
-	for rows.Next() {
-		var p client.Phone
-		var customerID string
-		err := rows.Scan(
-			&customerID,
-			&p.Number,
-			&p.Valid,
-			&p.Type,
-		)
-		if err != nil {
-			return nil, fmt.Errorf("scanning row: %v", err)
-		}
-		ret[customerID] = append(ret[customerID], p)
-	}
-
-	return ret, nil
-}
-
-func (r *sqlCustomerRepository) getAddresses(customerIDs []string) (map[string][]client.CustomerAddress, error) {
-	query := fmt.Sprintf(
-		"select customer_id, address_id, type, address1, address2, city, state, postal_code, country, validated from customers_addresses where customer_id in (?%s) and deleted_at is null;",
-		strings.Repeat(",?", len(customerIDs)-1),
-	)
-	rows, err := r.queryRowsByCustomerIDs(query, customerIDs)
-	if err != nil {
-		return nil, err
-	}
-	defer rows.Close()
-
-	ret := make(map[string][]client.CustomerAddress)
-	for rows.Next() {
-		var a client.CustomerAddress
-		var customerID string
-		if err := rows.Scan(
-			&customerID,
-			&a.AddressID,
-			&a.Type,
-			&a.Address1,
-			&a.Address2,
-			&a.City,
-			&a.State,
-			&a.PostalCode,
-			&a.Country,
-			&a.Validated,
-		); err != nil {
-			return nil, fmt.Errorf("scanning row: %v", err)
-		}
-		ret[customerID] = append(ret[customerID], a)
-	}
-
-	return ret, nil
-}
-
-func (r *sqlCustomerRepository) getMetadata(customerIDs []string) (map[string]client.CustomerMetadata, error) {
-	query := fmt.Sprintf(
-		"select customer_id, meta_key, meta_value from customer_metadata where customer_id in (?%s);",
-		strings.Repeat(",?", len(customerIDs)-1),
-	)
-	rows, err := r.queryRowsByCustomerIDs(query, customerIDs)
-	if err != nil {
-		return nil, err
-	}
-	defer rows.Close()
-
-	ret := make(map[string]client.CustomerMetadata)
-	for rows.Next() {
-		m := client.CustomerMetadata{
-			Metadata: make(map[string]string),
-		}
-		var customerID string
-		var k, v string
-		if err := rows.Scan(&customerID, &k, &v); err != nil {
-			return nil, fmt.Errorf("scanning row: %v", err)
-		}
-		m.Metadata[k] = v
-		ret[customerID] = m
-	}
-	return ret, nil
-}
-
-func (r *sqlCustomerRepository) queryRowsByCustomerIDs(query string, customerIDs []string) (*sql.Rows, error) {
-	stmt, err := r.db.Prepare(query)
-	if err != nil {
-		return nil, fmt.Errorf("preparing query: %v", err)
-	}
-	defer stmt.Close()
-
-	var args []interface{}
-	for _, id := range customerIDs {
-		args = append(args, id)
-	}
-
-	rows, err := stmt.Query(args...)
-	if err != nil {
-		return nil, fmt.Errorf("executing query: %v", err)
-	}
-
-	return rows, nil
-}
diff --git a/pkg/customers/customers.go b/pkg/customers/customers.go
index 02df5e9..d3f3f9f 100644
--- a/pkg/customers/customers.go
+++ b/pkg/customers/customers.go
@@ -140,6 +140,12 @@ type address struct {
 }
 
 func (add address) validate() error {
+	switch t := strings.ToLower(add.Type); t {
+	case "primary", "secondary":
+	default:
+		return fmt.Errorf("unknown type: %s", t)
+	}
+
 	if !usstates.Valid(add.State) {
 		return fmt.Errorf("create customer: invalid state=%s", add.State)
 	}
@@ -148,19 +154,18 @@ func (add address) validate() error {
 
 func (req customerRequest) validate() error {
 	if req.FirstName == "" || req.LastName == "" {
-		return errors.New("create customer: empty name field(s)")
+		return errors.New("invalid customer fields: empty name field(s)")
 	}
 	if err := validateCustomerType(req.Type); err != nil {
-		return fmt.Errorf("create customer: %v", err)
+		return fmt.Errorf("invalid customer type: %v", err)
 	}
 	if err := validateMetadata(req.Metadata); err != nil {
-		return fmt.Errorf("create customer: %v", err)
+		return fmt.Errorf("invalid customer metadata: %v", err)
 	}
-	for i := range req.Addresses {
-		if err := req.Addresses[i].validate(); err != nil {
-			return fmt.Errorf("address=%v validation failed: %v", req.Addresses[i], err)
-		}
+	if err := validateAddresses(req.Addresses); err != nil {
+		return fmt.Errorf("invalid customer addresses: %v", err)
 	}
+
 	return nil
 }
 
@@ -188,6 +193,25 @@ func validateMetadata(meta map[string]string) error {
 	return nil
 }
 
+func validateAddresses(addrs []address) error {
+	hasPrimaryAddr := false
+	for _, addr := range addrs {
+		if hasPrimaryAddr {
+			return ErrAddressTypeDuplicate
+		}
+
+		if err := addr.validate(); err != nil {
+			return fmt.Errorf("validating address: %v", err)
+		}
+
+		if addr.Type == "primary" {
+			hasPrimaryAddr = true
+		}
+	}
+
+	return nil
+}
+
 func (req customerRequest) asCustomer(storage *ssnStorage) (*client.Customer, *SSN, error) {
 	if req.CustomerID == "" {
 		req.CustomerID = base.ID()
@@ -400,6 +424,7 @@ type CustomerRepository interface {
 
 	searchCustomers(params SearchParams) ([]*client.Customer, error)
 
+	getCustomerMetadata(customerID string) (map[string]string, error)
 	replaceCustomerMetadata(customerID string, metadata map[string]string) error
 
 	addCustomerAddress(customerID string, address address) error
@@ -609,19 +634,99 @@ func (r *sqlCustomerRepository) updateAddressesByCustomerID(tx *sql.Tx, customer
 }
 
 func (r *sqlCustomerRepository) GetCustomer(customerID string) (*client.Customer, error) {
-	custs, err := r.searchCustomers(SearchParams{
-		Count:       1,
-		CustomerIDs: []string{customerID},
-	})
+	query := `select first_name, middle_name, last_name, nick_name, suffix, type, birth_date, status, email, created_at, last_modified from customers where customer_id = ? and deleted_at is null limit 1;`
+	stmt, err := r.db.Prepare(query)
+	if err != nil {
+		return nil, err
+	}
+
+	row := stmt.QueryRow(customerID)
+
+	var birthDate *string
+	var cust client.Customer
+	cust.CustomerID = customerID
+	err = row.Scan(&cust.FirstName, &cust.MiddleName, &cust.LastName, &cust.NickName, &cust.Suffix, &cust.Type, &birthDate, &cust.Status, &cust.Email, &cust.CreatedAt,
+		&cust.LastModified)
+	stmt.Close()
+	if err != nil && !strings.Contains(err.Error(), "no rows in result set") {
+		return nil, fmt.Errorf("GetCustomer: %v", err)
+	}
+	if cust.FirstName == "" {
+		return nil, nil // not found
+	}
+	if birthDate != nil {
+		cust.BirthDate = *birthDate
+	}
+
+	phones, err := r.readPhones(customerID)
+	if err != nil {
+		return nil, fmt.Errorf("GetCustomer: readPhones: %v", err)
+	}
+	cust.Phones = phones
+
+	addresses, err := r.readAddresses(customerID)
+	if err != nil {
+		return nil, fmt.Errorf("GetCustomer: readAddresses: %v", err)
+	}
+	cust.Addresses = addresses
+
+	metadata, err := r.getCustomerMetadata(customerID)
+	if err != nil {
+		return nil, fmt.Errorf("GetCustomer: getCustomerMetadata: %v", err)
+	}
+	cust.Metadata = metadata
+
+	return &cust, nil
+}
+
+func (r *sqlCustomerRepository) readPhones(customerID string) ([]client.Phone, error) {
+	query := `select number, valid, type from customers_phones where customer_id = ?;`
+	stmt, err := r.db.Prepare(query)
+	if err != nil {
+		return nil, fmt.Errorf("GetCustomer: prepare customers_phones: err=%v", err)
+	}
+	defer stmt.Close()
+
+	rows, err := stmt.Query(customerID)
+	if err != nil {
+		return nil, fmt.Errorf("GetCustomer: query customers_phones: err=%v", err)
+	}
+	defer rows.Close()
+
+	var phones []client.Phone
+	for rows.Next() {
+		var p client.Phone
+		if err := rows.Scan(&p.Number, &p.Valid, &p.Type); err != nil {
+			return nil, fmt.Errorf("GetCustomer: scan customers_phones: err=%v", err)
+		}
+		phones = append(phones, p)
+	}
+	return phones, rows.Err()
+}
+
+func (r *sqlCustomerRepository) readAddresses(customerID string) ([]client.CustomerAddress, error) {
+	query := `select address_id, type, address1, address2, city, state, postal_code, country, validated from customers_addresses where customer_id = ? and deleted_at is null;`
+	stmt, err := r.db.Prepare(query)
 	if err != nil {
-		return nil, fmt.Errorf("getting customer: %v", err)
+		return nil, fmt.Errorf("readAddresses: prepare customers_addresses: err=%v", err)
 	}
+	defer stmt.Close()
 
-	if len(custs) == 0 {
-		return nil, errors.New("customer not found")
+	rows, err := stmt.Query(customerID)
+	if err != nil {
+		return nil, fmt.Errorf("readAddresses: query customers_addresses: err=%v", err)
 	}
+	defer rows.Close()
 
-	return custs[0], nil
+	var adds []client.CustomerAddress
+	for rows.Next() {
+		var a client.CustomerAddress
+		if err := rows.Scan(&a.AddressID, &a.Type, &a.Address1, &a.Address2, &a.City, &a.State, &a.PostalCode, &a.Country, &a.Validated); err != nil {
+			return nil, fmt.Errorf("readAddresses: scan customers_addresses: err=%v", err)
+		}
+		adds = append(adds, a)
+	}
+	return adds, rows.Err()
 }
 
 func (r *sqlCustomerRepository) updateCustomerStatus(customerID string, status client.CustomerStatus, comment string) error {
@@ -655,6 +760,32 @@ func (r *sqlCustomerRepository) updateCustomerStatus(customerID string, status c
 	return tx.Commit()
 }
 
+func (r *sqlCustomerRepository) getCustomerMetadata(customerID string) (map[string]string, error) {
+	out := make(map[string]string)
+
+	query := `select meta_key, meta_value from customer_metadata where customer_id = ?;`
+	stmt, err := r.db.Prepare(query)
+	if err != nil {
+		return out, fmt.Errorf("getCustomerMetadata: prepare: %v", err)
+	}
+	defer stmt.Close()
+
+	rows, err := stmt.Query(customerID)
+	if err != nil {
+		return out, fmt.Errorf("getCustomerMetadata: query: %v", err)
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+		key, value := "", ""
+		if err := rows.Scan(&key, &value); err != nil {
+			return out, fmt.Errorf("getCustomerMetadata: scan: %v", err)
+		}
+		out[key] = value
+	}
+	return out, nil
+}
+
 func (r *sqlCustomerRepository) replaceCustomerMetadata(customerID string, metadata map[string]string) error {
 	tx, err := r.db.Begin()
 	if err != nil {


Test patch:
diff --git a/pkg/customers/address_test.go b/pkg/customers/address_test.go
index 0ec89f1..fc9f94f 100644
--- a/pkg/customers/address_test.go
+++ b/pkg/customers/address_test.go
@@ -27,7 +27,7 @@ func TestCustomers__addCustomerAddress(t *testing.T) {
 	err := repo.CreateCustomer(cust, "organization")
 	require.NoError(t, err)
 
-	address := address{
+	addrPayload := address{
 		Address1:   "123 1st st",
 		City:       "Denver",
 		State:      "CO",
@@ -35,7 +35,7 @@ func TestCustomers__addCustomerAddress(t *testing.T) {
 		Country:    "USA",
 		Type:       "primary",
 	}
-	payload, err := json.Marshal(address)
+	payload, err := json.Marshal(addrPayload)
 	require.NoError(t, err)
 
 	w := httptest.NewRecorder()
@@ -53,16 +53,29 @@ func TestCustomers__addCustomerAddress(t *testing.T) {
 	got := customerResp.Addresses[0]
 	want := client.CustomerAddress{
 		AddressID:  got.AddressID,
-		Type:       address.Type,
-		Address1:   address.Address1,
-		Address2:   address.Address2,
-		City:       address.City,
-		State:      address.State,
-		PostalCode: address.PostalCode,
-		Country:    address.Country,
+		Type:       addrPayload.Type,
+		Address1:   addrPayload.Address1,
+		Address2:   addrPayload.Address2,
+		City:       addrPayload.City,
+		State:      addrPayload.State,
+		PostalCode: addrPayload.PostalCode,
+		Country:    addrPayload.Country,
 	}
 
 	require.Equal(t, want, got)
+
+	/* Error on duplicate type primary */
+	w = httptest.NewRecorder()
+	payload, err = json.Marshal(addrPayload)
+	require.NoError(t, err)
+	req = httptest.NewRequest("POST", fmt.Sprintf("/customers/%s/addresses", cust.CustomerID), bytes.NewReader(payload))
+	router.ServeHTTP(w, req)
+	require.Equal(t, http.StatusBadRequest, w.Code)
+	var errResp struct {
+		ErrorMsg string `json:"error"`
+	}
+	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
+	require.Contains(t, errResp.ErrorMsg, ErrAddressTypeDuplicate.Error())
 }
 
 func TestCustomers__updateCustomerAddress(t *testing.T) {
@@ -77,36 +90,59 @@ func TestCustomers__updateCustomerAddress(t *testing.T) {
 	err := repo.CreateCustomer(cust, "organization")
 	require.NoError(t, err)
 
-	address := address{
-		Address1:   "123 1st st",
-		City:       "Denver",
-		State:      "CO",
-		PostalCode: "12345",
-		Country:    "USA",
-		Type:       "primary",
+	addrRequests := []address{
+		{
+			Address1:   "111 1st st",
+			City:       "Denver",
+			State:      "CO",
+			PostalCode: "12345",
+			Country:    "USA",
+			Type:       "primary",
+		},
+		{
+			Address1:   "222 2nd st",
+			City:       "Denver",
+			State:      "CO",
+			PostalCode: "12345",
+			Country:    "USA",
+			Type:       "secondary",
+		},
+	}
+	for _, req := range addrRequests {
+		require.NoError(t, repo.addCustomerAddress(cust.CustomerID, req))
+		cust, err = repo.GetCustomer(cust.CustomerID) // refresh customer object after updating address
+		require.NoError(t, err)
+	}
+
+	// find address with primaryid
+	var primaryAddressID string
+	var secondaryAddressID string
+	for _, addr := range cust.Addresses {
+		if addr.Type == "primary" {
+			primaryAddressID = addr.AddressID
+		} else {
+			secondaryAddressID = addr.AddressID
+		}
 	}
-	require.NoError(t, repo.addCustomerAddress(cust.CustomerID, address))
-	cust, err = repo.GetCustomer(cust.CustomerID) // refresh customer object after updating address
-	require.NoError(t, err)
 
 	updateReq := updateCustomerAddressRequest{
-		Type:       "primary",
-		Address1:   "555 5th st",
-		City:       "Denver",
-		State:      "CO",
-		PostalCode: "12345",
-		Country:    "USA",
-		Validated:  true,
+		address: address{
+			Type:       "primary",
+			Address1:   "555 5th st",
+			City:       "Denver",
+			State:      "CO",
+			PostalCode: "12345",
+			Country:    "USA",
+		},
+		Validated: true,
 	}
 	payload, err := json.Marshal(updateReq)
 	require.NoError(t, err)
 
-	addressID := cust.Addresses[0].AddressID
-
 	router := mux.NewRouter()
 	AddCustomerAddressRoutes(log.NewNopLogger(), router, repo)
 
-	url := fmt.Sprintf("/customers/%s/addresses/%s", cust.CustomerID, addressID)
+	url := fmt.Sprintf("/customers/%s/addresses/%s", cust.CustomerID, primaryAddressID)
 	req, err := http.NewRequest("PUT", url, bytes.NewReader(payload))
 	require.NoError(t, err)
 
@@ -115,12 +151,17 @@ func TestCustomers__updateCustomerAddress(t *testing.T) {
 
 	res := httptest.NewRecorder()
 	router.ServeHTTP(res, req)
-	require.Equal(t, http.StatusOK, res.Code)
+	require.Equalf(t, http.StatusOK, res.Code, "response body: %s", res.Body.String())
 
 	var customerResp *client.Customer
 	require.NoError(t, json.Unmarshal(res.Body.Bytes(), &customerResp))
 
-	got := customerResp.Addresses[0]
+	var got client.CustomerAddress
+	for _, a := range customerResp.Addresses {
+		if a.AddressID == primaryAddressID {
+			got = a
+		}
+	}
 	want := client.CustomerAddress{
 		AddressID:  got.AddressID,
 		Type:       updateReq.Type,
@@ -133,6 +174,22 @@ func TestCustomers__updateCustomerAddress(t *testing.T) {
 		Validated:  updateReq.Validated,
 	}
 	require.Equal(t, want, got)
+
+	/* Error when trying to update a secondary address to primary when one already exists */
+	res = httptest.NewRecorder()
+	payload, err = json.Marshal(updateReq)
+	require.NoError(t, err)
+	url = fmt.Sprintf("/customers/%s/addresses/%s", cust.CustomerID, secondaryAddressID)
+
+	req, err = http.NewRequest("PUT", url, bytes.NewReader(payload))
+	require.NoError(t, err)
+	router.ServeHTTP(res, req)
+	require.Equal(t, http.StatusBadRequest, res.Code)
+	var errResp struct {
+		ErrorMsg string `json:"error"`
+	}
+	require.NoError(t, json.Unmarshal(res.Body.Bytes(), &errResp))
+	require.Contains(t, errResp.ErrorMsg, ErrAddressTypeDuplicate.Error())
 }
 
 func TestCustomers__deleteCustomerAddress(t *testing.T) {
@@ -214,7 +271,7 @@ func TestCustomerRepository__updateCustomerAddress(t *testing.T) {
 	err := repo.CreateCustomer(cust, "organization")
 	require.NoError(t, err)
 
-	address := address{
+	addrRequest := address{
 		Address1:   "123 1st st",
 		City:       "Denver",
 		State:      "CO",
@@ -222,20 +279,22 @@ func TestCustomerRepository__updateCustomerAddress(t *testing.T) {
 		Country:    "USA",
 		Type:       "primary",
 	}
-	require.NoError(t, repo.addCustomerAddress(cust.CustomerID, address))
+	require.NoError(t, repo.addCustomerAddress(cust.CustomerID, addrRequest))
 
 	cust, err = repo.GetCustomer(cust.CustomerID)
 	require.NoError(t, err)
 
 	addressID := cust.Addresses[0].AddressID
 	updateReq := updateCustomerAddressRequest{
-		Type:       "primary",
-		Address1:   "555 5th st",
-		City:       "Denver",
-		State:      "CO",
-		PostalCode: "12345",
-		Country:    "USA",
-		Validated:  true,
+		address: address{
+			Type:       "primary",
+			Address1:   "555 5th st",
+			City:       "Denver",
+			State:      "CO",
+			PostalCode: "12345",
+			Country:    "USA",
+		},
+		Validated: true,
 	}
 	err = repo.updateCustomerAddress(cust.CustomerID, addressID, updateReq)
 	require.NoError(t, err)
diff --git a/pkg/customers/approval_test.go b/pkg/customers/approval_test.go
index 9bbcaf9..ad5001d 100644
--- a/pkg/customers/approval_test.go
+++ b/pkg/customers/approval_test.go
@@ -54,26 +54,3 @@ func TestCustomers__updateCustomerStatus(t *testing.T) {
 	require.NotNil(t, customer)
 	require.Equal(t, updateStatusRequest.Status, repo.updatedStatus)
 }
-
-func TestCustomers__containsValidPrimaryAddress(t *testing.T) {
-	if containsValidPrimaryAddress(nil) {
-		t.Error("no addresses, so can't be found")
-	}
-	addresses := []client.CustomerAddress{
-		{
-			Type:      "Primary",
-			Validated: false,
-		},
-	}
-	if containsValidPrimaryAddress(addresses) {
-		t.Error("Address isn't validated")
-	}
-	addresses[0].Validated = true
-	if !containsValidPrimaryAddress(addresses) {
-		t.Error("Address should be Primary and Validated")
-	}
-	addresses[0].Type = "Secondary"
-	if containsValidPrimaryAddress(addresses) {
-		t.Error("Address is Secondary")
-	}
-}
diff --git a/pkg/customers/customer_search_test.go b/pkg/customers/customer_search_test.go
index 6a8e65b..9433d7c 100644
--- a/pkg/customers/customer_search_test.go
+++ b/pkg/customers/customer_search_test.go
@@ -7,24 +7,20 @@ package customers
 import (
 	"database/sql"
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"net/http/httptest"
-	"strings"
 	"testing"
 
 	"github.com/gorilla/mux"
 	"github.com/moov-io/base/log"
-	"github.com/stretchr/testify/require"
 
 	"github.com/moov-io/customers/internal/database"
 	"github.com/moov-io/customers/pkg/client"
 )
 
 func TestCustomersSearchRouter(t *testing.T) {
-	db := database.CreateTestMySQLDB(t)
-	repo := NewCustomerRepo(log.NewNopLogger(), db.DB)
-	defer db.Close()
+	repo := createTestCustomerRepository(t)
+	defer repo.close()
 
 	router := mux.NewRouter()
 	AddCustomerRoutes(log.NewNopLogger(), router, repo, nil, nil)
@@ -81,114 +77,82 @@ func TestCustomersSearchRouter(t *testing.T) {
 	}
 }
 
-func TestRepository__searchCustomers(t *testing.T) {
-	logger := log.NewNopLogger()
-	org := "test"
-	createCustomer := func(repo CustomerRepository, i int) *client.Customer {
-		var req = &customerRequest{
-			FirstName: fmt.Sprintf("jane-%d", i),
-			LastName:  fmt.Sprintf("doe-%d", i),
-			Email:     fmt.Sprintf("jane-%d@moov.com", i),
-			Phones: []phone{
-				{
-					Number: "555-555-5555",
-					Type:   "primary",
-				},
-			},
-			Addresses: []address{
-				{
-					Type:       "primary",
-					Address1:   "123 Cool St.",
-					City:       "San Francisco",
-					State:      "CA",
-					PostalCode: "94030",
-					Country:    "US",
-				},
-			},
-			Metadata: map[string]string{"key": "val"},
+func TestCustomerSearch__query(t *testing.T) {
+	sqliteDB := database.CreateTestSqliteDB(t)
+	defer sqliteDB.Close()
+
+	mysqlDB := database.CreateTestMySQLDB(t)
+	defer mysqlDB.Close()
+
+	prepare := func(db *sql.DB, query string) error {
+		stmt, err := db.Prepare(query)
+		if err != nil {
+			return err
 		}
-		cust, _, _ := req.asCustomer(testCustomerSSNStorage(t))
-		require.NoError(t, repo.CreateCustomer(cust, org))
-		return cust
+		return stmt.Close()
 	}
 
-	tests := []struct {
-		desc string
-		db   *sql.DB
-	}{
-		{
-			desc: "sqlite",
-			db:   database.CreateTestSqliteDB(t).DB,
+	// Query search
+	query, args := buildSearchQuery(
+		SearchParams{
+			Organization: "foo",
+			Query:        "jane doe",
+			Count:        100,
 		},
-		{
-			desc: "mysql",
-			db:   database.CreateTestMySQLDB(t).DB,
+	)
+	if query != "select customer_id from customers where deleted_at is null and organization = ? and lower(first_name) || \" \" || lower(last_name) LIKE ? order by created_at desc limit ?;" {
+		t.Errorf("unexpected query: %q", query)
+	}
+	if err := prepare(sqliteDB.DB, query); err != nil {
+		t.Errorf("sqlite: %v", err)
+	}
+	if err := prepare(mysqlDB.DB, query); err != nil {
+		t.Errorf("mysql: %v", err)
+	}
+	if len(args) != 3 {
+		t.Errorf("unexpected args: %#v", args)
+	}
+
+	// Eamil search
+	query, args = buildSearchQuery(
+		SearchParams{
+			Organization: "foo",
+			Email:        "jane.doe@moov.io",
 		},
+	)
+	if query != "select customer_id from customers where deleted_at is null and organization = ? and lower(email) like ? order by created_at desc limit ?;" {
+		t.Errorf("unexpected query: %q", query)
+	}
+	if err := prepare(sqliteDB.DB, query); err != nil {
+		t.Errorf("sqlite: %v", err)
+	}
+	if err := prepare(mysqlDB.DB, query); err != nil {
+		t.Errorf("mysql: %v", err)
+	}
+	if len(args) != 3 {
+		t.Errorf("unexpected args: %#v", args)
 	}
 
-	for _, tc := range tests {
-		t.Run(tc.desc, func(t *testing.T) {
-			defer tc.db.Close()
-			repo := NewCustomerRepo(logger, tc.db)
-
-			n := 20 // seed database with customers
-			var customers []*client.Customer
-			for i := 0; i < n; i++ {
-				customers = append(customers, createCustomer(repo, i))
-			}
-
-			/* Search by Count */
-			params := SearchParams{
-				Organization: org,
-				Count:        5,
-			}
-			got, err := repo.searchCustomers(params)
-			require.NoError(t, err)
-			require.Len(t, got, int(params.Count))
-
-			/* Search by email */
-			params = SearchParams{
-				Organization: org,
-				Count:        1,
-				Email:        "JaNe-3@moov.com",
-			}
-			got, err = repo.searchCustomers(params)
-			require.NoError(t, err)
-			require.Equal(t, strings.ToLower(params.Email), got[0].Email)
-
-			if tc.desc == "mysql" {
-				/* Search by query */
-				params = SearchParams{
-					Organization: org,
-					Count:        100,
-					Query:        "jane-10",
-				}
-				got, err = repo.searchCustomers(params)
-				require.NoError(t, err)
-				require.Len(t, got, 1)
-			}
-
-			/* Search by customerIDs */
-			wantIDs := make([]string, 3)
-			for i := 0; i < len(wantIDs); i++ {
-				wantIDs[i] = customers[i].CustomerID
-			}
-
-			got, err = repo.searchCustomers(SearchParams{
-				Organization: org,
-				CustomerIDs:  wantIDs,
-				Count:        10,
-			})
-			require.NoError(t, err)
-			require.Len(t, got, len(wantIDs))
-
-			var gotIDs []string
-			for _, c := range got {
-				gotIDs = append(gotIDs, c.CustomerID)
-			}
-
-			require.ElementsMatch(t, wantIDs, gotIDs)
-		})
+	// Query and Eamil saerch
+	query, args = buildSearchQuery(
+		SearchParams{
+			Organization: "foo",
+			Query:        "jane doe",
+			Email:        "jane.doe@moov.io",
+			Count:        25,
+		},
+	)
+	if query != "select customer_id from customers where deleted_at is null and organization = ? and lower(first_name) || \" \" || lower(last_name) LIKE ? and lower(email) like ? order by created_at desc limit ?;" {
+		t.Errorf("unexpected query: %q", query)
+	}
+	if err := prepare(sqliteDB.DB, query); err != nil {
+		t.Errorf("sqlite: %v", err)
+	}
+	if err := prepare(mysqlDB.DB, query); err != nil {
+		t.Errorf("mysql: %v", err)
+	}
+	if len(args) != 4 {
+		t.Errorf("unexpected args: %#v", args)
 	}
 }
 
@@ -228,7 +192,7 @@ func TestGet100MostRecentlyCreatedCustomersWhenSpecifyingMoreThanAvailable(t *te
 	scope.assert.Equal(100, len(customers))
 }
 
-func TestSearchCustomersWithVerifiedStatus(t *testing.T) {
+func TestGetCustomersWithVerifiedStatus(t *testing.T) {
 	// Create two customers. 1 with Unknown STATUS and 1 with Verified
 	scope := Setup(t)
 	customer := scope.CreateCustomer("John", "Doe", "john.doe@email.com", client.INDIVIDUAL)
@@ -252,12 +216,8 @@ func TestSearchCustomersWithVerifiedStatus(t *testing.T) {
 	}
 }
 
-func TestSearchCustomersByName(t *testing.T) {
+func TestGetCustomersByName(t *testing.T) {
 	scope := Setup(t)
-	scope.customerRepo = &sqlCustomerRepository{
-		logger: log.NewNopLogger(),
-		db:     database.CreateTestMySQLDB(t).DB,
-	}
 	_ = scope.CreateCustomer("Jane", "Doe", "jane.doe@gmail.com", client.INDIVIDUAL)
 	_ = scope.CreateCustomer("John", "Doe", "jane.doe@gmail.com", client.INDIVIDUAL)
 
@@ -280,7 +240,7 @@ func TestSearchCustomersByName(t *testing.T) {
 	scope.assert.Equal(0, len(customers))
 }
 
-func TestSearchCustomersByEmail(t *testing.T) {
+func TestGetCustomersByEmail(t *testing.T) {
 	scope := Setup(t)
 	_ = scope.CreateCustomer("Jane", "Doe", "jane.doe@gmail.com", client.INDIVIDUAL)
 	_ = scope.CreateCustomer("John", "Doe", "john.doe@gmail.com", client.INDIVIDUAL)
@@ -295,12 +255,8 @@ func TestSearchCustomersByEmail(t *testing.T) {
 	scope.assert.Equal(0, len(customers))
 }
 
-func TestSearchCustomersByNameAndEmail(t *testing.T) {
+func TestGetCustomersByNameAndEmail(t *testing.T) {
 	scope := Setup(t)
-	scope.customerRepo = &sqlCustomerRepository{
-		logger: log.NewNopLogger(),
-		db:     database.CreateTestMySQLDB(t).DB,
-	}
 	_ = scope.CreateCustomer("Jane", "Doe", "jane.doe@gmail.com", client.INDIVIDUAL)
 	_ = scope.CreateCustomer("John", "Doe", "john.doe@gmail.com", client.INDIVIDUAL)
 
@@ -326,7 +282,7 @@ func TestSearchCustomersByNameAndEmail(t *testing.T) {
 	scope.assert.Equal(0, len(customers))
 }
 
-func TestSearchCustomersUsingPaging(t *testing.T) {
+func TestGetCustomersUsingPaging(t *testing.T) {
 	scope := Setup(t)
 	_ = scope.CreateCustomers(30, client.INDIVIDUAL)
 
@@ -347,7 +303,7 @@ func TestSearchCustomersUsingPaging(t *testing.T) {
 	scope.assert.Equal(0, len(customers))
 }
 
-func TestSearchCustomersUsingPagingFailure(t *testing.T) {
+func TestGetCustomersUsingPagingFailure(t *testing.T) {
 	scope := Setup(t)
 	_ = scope.CreateCustomers(30, client.INDIVIDUAL)
 
@@ -361,7 +317,7 @@ func TestSearchCustomersUsingPagingFailure(t *testing.T) {
 	scope.assert.Equal(0, len(customers))
 }
 
-func TestSearchCustomersByType(t *testing.T) {
+func TestGetCustomersByType(t *testing.T) {
 	scope := Setup(t)
 	_ = scope.CreateCustomers(10, client.INDIVIDUAL)
 	_ = scope.CreateCustomers(20, client.BUSINESS)
diff --git a/pkg/customers/customers_test.go b/pkg/customers/customers_test.go
index 6d5a009..4c7ac3a 100644
--- a/pkg/customers/customers_test.go
+++ b/pkg/customers/customers_test.go
@@ -6,9 +6,11 @@ package customers
 
 import (
 	"bytes"
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
 	"strconv"
@@ -76,6 +78,11 @@ func (r *testCustomerRepository) searchCustomers(params SearchParams) ([]*client
 	return nil, nil
 }
 
+func (r *testCustomerRepository) getCustomerMetadata(customerID string) (map[string]string, error) {
+	out := make(map[string]string)
+	return out, r.err
+}
+
 func (r *testCustomerRepository) replaceCustomerMetadata(customerID string, metadata map[string]string) error {
 	return r.err
 }
@@ -216,7 +223,7 @@ func TestCustomers__DeleteCustomer(t *testing.T) {
 	require.Equal(t, http.StatusNoContent, w.Code)
 	require.Empty(t, w.Body)
 	got, err = repo.GetCustomer(customer.CustomerID)
-	require.Error(t, err)
+	require.NoError(t, err)
 	require.Nil(t, got)
 }
 
@@ -251,7 +258,7 @@ func TestCustomerRepository__CreateCustomer(t *testing.T) {
 	check(t, &sqlCustomerRepository{mysqlDB.DB, log.NewNopLogger()})
 }
 
-func TestCustomers__searchCustomersError(t *testing.T) {
+func TestCustomers__GetCustomersError(t *testing.T) {
 	repo := &testCustomerRepository{err: errors.New("bad error")}
 
 	w := httptest.NewRecorder()
@@ -303,6 +310,7 @@ func TestCustomers__customerRequest(t *testing.T) {
 		State:      "CA",
 		PostalCode: "90210",
 		Country:    "US",
+		Type:       "primary",
 	})
 	if err := req.validate(); err != nil {
 		t.Errorf("unexpected error: %v", err)
@@ -322,7 +330,7 @@ func TestCustomers__customerRequest(t *testing.T) {
 }
 
 func TestCustomers__addressValidate(t *testing.T) {
-	add := address{}
+	add := address{Type: "primary"}
 
 	add.State = "IA"
 	if err := add.validate(); err != nil {
@@ -338,7 +346,7 @@ func TestCustomers__addressValidate(t *testing.T) {
 func TestCustomers__CreateCustomer(t *testing.T) {
 	w := httptest.NewRecorder()
 	phone := `{"number": "555.555.5555", "type": "mobile"}`
-	address := `{"type": "home", "address1": "123 1st St", "city": "Denver", "state": "CO", "postalCode": "12345", "country": "USA"}`
+	address := `{"type": "primary", "address1": "123 1st St", "city": "Denver", "state": "CO", "postalCode": "12345", "country": "USA"}`
 	body := fmt.Sprintf(`{"firstName": "jane", "lastName": "doe", "email": "jane@example.com", "birthDate": "1991-04-01", "ssn": "123456789", "type": "individual", "phones": [%s], "addresses": [%s]}`, phone, address)
 	req := httptest.NewRequest("POST", "/customers", strings.NewReader(body))
 	req.Header.Set("x-organization", "test")
@@ -423,6 +431,7 @@ func TestCustomers__updateCustomer(t *testing.T) {
 				State:      "CA",
 				PostalCode: "90210",
 				Country:    "US",
+				Type:       "primary",
 			},
 		},
 	}
@@ -454,6 +463,7 @@ func TestCustomers__updateCustomer(t *testing.T) {
 			State:      "CA",
 			PostalCode: "90210",
 			Country:    "US",
+			Type:       "primary",
 		},
 	}
 	payload, err := json.Marshal(&updateReq)
@@ -479,6 +489,38 @@ func TestCustomers__updateCustomer(t *testing.T) {
 	want.CreatedAt = got.CreatedAt
 	want.LastModified = got.LastModified
 	require.Equal(t, want, got)
+
+	/* Error when settings two addresses as primary */
+	updateReq.Addresses = []address{
+		{
+			Address1:   "555 5th st",
+			City:       "real city",
+			State:      "CA",
+			PostalCode: "90210",
+			Country:    "US",
+			Type:       "primary",
+		},
+		{
+			Address1:   "444 4th st",
+			City:       "real city",
+			State:      "CA",
+			PostalCode: "90210",
+			Country:    "US",
+			Type:       "primary",
+		},
+	}
+	payload, err = json.Marshal(&updateReq)
+	require.NoError(t, err)
+	req = req.Clone(context.Background())
+	req.Body = ioutil.NopCloser(bytes.NewReader(payload))
+	w = httptest.NewRecorder()
+	router.ServeHTTP(w, req)
+	require.Equal(t, http.StatusBadRequest, w.Code)
+	var errResp struct {
+		ErrorMsg string `json:"error"`
+	}
+	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &errResp))
+	require.Contains(t, errResp.ErrorMsg, ErrAddressTypeDuplicate.Error())
 }
 
 func createTestCustomerRepository(t *testing.T) *sqlCustomerRepository {
@@ -493,8 +535,8 @@ func TestCustomers__repository(t *testing.T) {
 	defer repo.close()
 
 	cust, err := repo.GetCustomer(base.ID())
-	if err == nil {
-		t.Errorf("expected not found error: %v", err)
+	if err != nil {
+		t.Errorf("unexpected error: %v", err)
 	}
 	if cust != nil {
 		t.Error("expected no Customer")
@@ -700,7 +742,7 @@ func TestCustomersRepository__addCustomerAddress(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	// add an Address
+	// add an address
 	if err := repo.addCustomerAddress(cust.CustomerID, address{
 		Address1:   "123 1st st",
 		City:       "fake city",
@@ -853,16 +895,12 @@ func TestCustomerRepository__metadata(t *testing.T) {
 	repo := createTestCustomerRepository(t)
 	defer repo.close()
 
-	getMetadata := func(customerID string) map[string]string {
-		meta, err := repo.getMetadata([]string{customerID})
-		if err != nil {
-			t.Fatal(err)
-		}
-		return meta[customerID].Metadata
-	}
 	customerID := base.ID()
 
-	meta := getMetadata(customerID)
+	meta, err := repo.getCustomerMetadata(customerID)
+	if err != nil {
+		t.Fatal(err)
+	}
 	if len(meta) != 0 {
 		t.Errorf("unknown metadata: %#v", meta)
 	}
@@ -871,8 +909,10 @@ func TestCustomerRepository__metadata(t *testing.T) {
 	if err := repo.replaceCustomerMetadata(customerID, map[string]string{"key": "bar"}); err != nil {
 		t.Fatal(err)
 	}
-
-	meta = getMetadata(customerID)
+	meta, err = repo.getCustomerMetadata(customerID)
+	if err != nil {
+		t.Fatal(err)
+	}
 	if len(meta) != 1 || meta["key"] != "bar" {
 		t.Errorf("unknown metadata: %#v", meta)
 	}