Repository: moov-io/customers (go)

Issue:
Delete documents endpoint
There's no endpoint today for deleting a document. It's possible that a document be incorrectly added at which point it's best to remove from the list of documents related to a customer. Tombstoning a document seems most appropriate.

Desired endpoint: `DELETE /customers/:customerID/documents/:documentID`

Pull request:
http: endpoint to delete a customer's document
Added an endpoint to soft delete a customer's document.

Fixes #176 

Patch:
diff --git a/api/client.yaml b/api/client.yaml
index a1710ac..2b1b2e5 100644
--- a/api/client.yaml
+++ b/api/client.yaml
@@ -1203,6 +1203,44 @@ paths:
             application/json:
               schema:
                 $ref: 'https://raw.githubusercontent.com/moov-io/api/master/openapi-common.yaml#/components/schemas/Error'
+    delete:
+      description: Remove a customer's document
+      operationId: deleteCustomerDocument
+      parameters:
+        - name: X-Request-ID
+          in: header
+          description: Optional requestID allows application developer to trace requests through the systems logs
+          example: rs4f9915
+          schema:
+            type: string
+        - name: customerID
+          in: path
+          description: ID of the customer that owns the document
+          required: true
+          schema:
+            example: e210a9d6-d755-4455-9bd2-9577ea7e1081
+            type: string
+          style: simple
+        - name: documentID
+          in: path
+          description: ID of the document
+          required: true
+          schema:
+            example: 9577ea7e1081
+            type: string
+          style: simple
+      responses:
+        "204":
+          description: Customer's document successfully deleted
+        '400':
+          description: Failed to delete customer's document, see error(s)
+          content:
+            application/json:
+              schema:
+                $ref: 'https://raw.githubusercontent.com/moov-io/api/master/openapi-common.yaml#/components/schemas/Error'
+      summary: Delete a customer's document
+      tags:
+        - Customers
   /customers/{customerID}/ofac:
     get:
       tags: [Customers]
diff --git a/internal/database/mysql.go b/internal/database/mysql.go
index 8435ca8..075b1f9 100644
--- a/internal/database/mysql.go
+++ b/internal/database/mysql.go
@@ -65,7 +65,7 @@ var (
 		),
 		execsql(
 			"create_documents",
-			`create table if not exists documents(document_id varchar(40) primary key, customer_id varchar(40), type varchar(120), content_type varchar(40), uploaded_at datetime);`,
+			`create table if not exists documents(document_id varchar(40) primary key, customer_id varchar(40), type varchar(120), content_type integer, uploaded_at datetime);`,
 		),
 		execsql(
 			"create_disclaimers",
@@ -126,7 +126,11 @@ var (
 		),
 		execsql(
 			"create_namespace_configuration",
-			`create table if not exists namespace_configuration(namespace varchar(40) primary key not null, legal_entity varchar(40) not null, primary_account varchar(40) not null);`,
+			`create table namespace_configuration(namespace varchar(40) primary key not null, legal_entity varchar(40) not null, primary_account varchar(40) not null);`,
+		),
+		execsql(
+			"add_deleted_at__to__documents",
+			"alter table documents add column deleted_at datetime;",
 		),
 	)
 )
diff --git a/internal/database/sqlite.go b/internal/database/sqlite.go
index a8d4d5f..beaaa62 100644
--- a/internal/database/sqlite.go
+++ b/internal/database/sqlite.go
@@ -61,7 +61,7 @@ var (
 		),
 		execsql(
 			"create_documents",
-			`create table if not exists documents(document_id primary key, customer_id, type, content_type, uploaded_at datetime);`,
+			`create table if not exists documents(document_id primary key, customer_id, type, content_type, uploaded_at datetime, deleted_at datetime);`,
 		),
 		execsql(
 			"create_disclaimers",
diff --git a/pkg/client/README.md b/pkg/client/README.md
index 1dc2153..71d7612 100644
--- a/pkg/client/README.md
+++ b/pkg/client/README.md
@@ -48,6 +48,7 @@ Class | Method | HTTP request | Description
 *CustomersApi* | [**DecryptAccountNumber**](docs/CustomersApi.md#decryptaccountnumber) | **Post** /customers/{customerID}/accounts/{accountID}/decrypt | Decrypt Account Number
 *CustomersApi* | [**DeleteCustomer**](docs/CustomersApi.md#deletecustomer) | **Delete** /customers/{customerID} | Delete Customer by ID
 *CustomersApi* | [**DeleteCustomerAccount**](docs/CustomersApi.md#deletecustomeraccount) | **Delete** /customers/{customerID}/accounts | Delete Customer Account
+*CustomersApi* | [**DeleteCustomerDocument**](docs/CustomersApi.md#deletecustomerdocument) | **Delete** /customers/{customerID}/documents/{documentID} | Delete a customer&#39;s document
 *CustomersApi* | [**GetAccountValidation**](docs/CustomersApi.md#getaccountvalidation) | **Get** /customers/{customerID}/accounts/{accountID}/validations/{validationID} | Get Account Validation
 *CustomersApi* | [**GetCustomer**](docs/CustomersApi.md#getcustomer) | **Get** /customers/{customerID} | Retrieve customer
 *CustomersApi* | [**GetCustomerAccountByID**](docs/CustomersApi.md#getcustomeraccountbyid) | **Get** /customers/{customerID}/accounts/{accountID} | Get Customer Account by ID
diff --git a/pkg/client/api/openapi.yaml b/pkg/client/api/openapi.yaml
index 7fbcdc2..3098fd8 100644
--- a/pkg/client/api/openapi.yaml
+++ b/pkg/client/api/openapi.yaml
@@ -1398,6 +1398,50 @@ paths:
       tags:
       - Customers
   /customers/{customerID}/documents/{documentID}:
+    delete:
+      description: Remove a customer's document
+      operationId: deleteCustomerDocument
+      parameters:
+      - description: Optional requestID allows application developer to trace requests
+          through the systems logs
+        example: rs4f9915
+        explode: false
+        in: header
+        name: X-Request-ID
+        required: false
+        schema:
+          type: string
+        style: simple
+      - description: ID of the customer that owns the document
+        explode: false
+        in: path
+        name: customerID
+        required: true
+        schema:
+          example: e210a9d6-d755-4455-9bd2-9577ea7e1081
+          type: string
+        style: simple
+      - description: ID of the document
+        explode: false
+        in: path
+        name: documentID
+        required: true
+        schema:
+          example: 9577ea7e1081
+          type: string
+        style: simple
+      responses:
+        "204":
+          description: Customer's document successfully deleted
+        "400":
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+          description: Failed to delete customer's document, see error(s)
+      summary: Delete a customer's document
+      tags:
+      - Customers
     get:
       description: Retrieve the referenced document
       operationId: getCustomerDocumentContents
diff --git a/pkg/client/api_customers.go b/pkg/client/api_customers.go
index f43d9c9..ce61b46 100644
--- a/pkg/client/api_customers.go
+++ b/pkg/client/api_customers.go
@@ -839,6 +839,95 @@ func (a *CustomersApiService) DeleteCustomerAccount(ctx _context.Context, custom
 	return localVarHTTPResponse, nil
 }
 
+// DeleteCustomerDocumentOpts Optional parameters for the method 'DeleteCustomerDocument'
+type DeleteCustomerDocumentOpts struct {
+	XRequestID optional.String
+}
+
+/*
+DeleteCustomerDocument Delete a customer's document
+Remove a customer&#39;s document
+ * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
+ * @param customerID ID of the customer that owns the document
+ * @param documentID ID of the document
+ * @param optional nil or *DeleteCustomerDocumentOpts - Optional Parameters:
+ * @param "XRequestID" (optional.String) -  Optional requestID allows application developer to trace requests through the systems logs
+*/
+func (a *CustomersApiService) DeleteCustomerDocument(ctx _context.Context, customerID string, documentID string, localVarOptionals *DeleteCustomerDocumentOpts) (*_nethttp.Response, error) {
+	var (
+		localVarHTTPMethod   = _nethttp.MethodDelete
+		localVarPostBody     interface{}
+		localVarFormFileName string
+		localVarFileName     string
+		localVarFileBytes    []byte
+	)
+
+	// create path and map variables
+	localVarPath := a.client.cfg.BasePath + "/customers/{customerID}/documents/{documentID}"
+	localVarPath = strings.Replace(localVarPath, "{"+"customerID"+"}", _neturl.QueryEscape(parameterToString(customerID, "")), -1)
+
+	localVarPath = strings.Replace(localVarPath, "{"+"documentID"+"}", _neturl.QueryEscape(parameterToString(documentID, "")), -1)
+
+	localVarHeaderParams := make(map[string]string)
+	localVarQueryParams := _neturl.Values{}
+	localVarFormParams := _neturl.Values{}
+
+	// to determine the Content-Type header
+	localVarHTTPContentTypes := []string{}
+
+	// set Content-Type header
+	localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes)
+	if localVarHTTPContentType != "" {
+		localVarHeaderParams["Content-Type"] = localVarHTTPContentType
+	}
+
+	// to determine the Accept header
+	localVarHTTPHeaderAccepts := []string{"application/json"}
+
+	// set Accept header
+	localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts)
+	if localVarHTTPHeaderAccept != "" {
+		localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept
+	}
+	if localVarOptionals != nil && localVarOptionals.XRequestID.IsSet() {
+		localVarHeaderParams["X-Request-ID"] = parameterToString(localVarOptionals.XRequestID.Value(), "")
+	}
+	r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	localVarHTTPResponse, err := a.client.callAPI(r)
+	if err != nil || localVarHTTPResponse == nil {
+		return localVarHTTPResponse, err
+	}
+
+	localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body)
+	localVarHTTPResponse.Body.Close()
+	if err != nil {
+		return localVarHTTPResponse, err
+	}
+
+	if localVarHTTPResponse.StatusCode >= 300 {
+		newErr := GenericOpenAPIError{
+			body:  localVarBody,
+			error: localVarHTTPResponse.Status,
+		}
+		if localVarHTTPResponse.StatusCode == 400 {
+			var v Error
+			err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+			if err != nil {
+				newErr.error = err.Error()
+				return localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+		}
+		return localVarHTTPResponse, newErr
+	}
+
+	return localVarHTTPResponse, nil
+}
+
 // GetAccountValidationOpts Optional parameters for the method 'GetAccountValidation'
 type GetAccountValidationOpts struct {
 	XRequestID optional.String
diff --git a/pkg/client/docs/CustomersApi.md b/pkg/client/docs/CustomersApi.md
index 00def72..74c2993 100644
--- a/pkg/client/docs/CustomersApi.md
+++ b/pkg/client/docs/CustomersApi.md
@@ -12,6 +12,7 @@ Method | HTTP request | Description
 [**DecryptAccountNumber**](CustomersApi.md#DecryptAccountNumber) | **Post** /customers/{customerID}/accounts/{accountID}/decrypt | Decrypt Account Number
 [**DeleteCustomer**](CustomersApi.md#DeleteCustomer) | **Delete** /customers/{customerID} | Delete Customer by ID
 [**DeleteCustomerAccount**](CustomersApi.md#DeleteCustomerAccount) | **Delete** /customers/{customerID}/accounts | Delete Customer Account
+[**DeleteCustomerDocument**](CustomersApi.md#DeleteCustomerDocument) | **Delete** /customers/{customerID}/documents/{documentID} | Delete a customer&#39;s document
 [**GetAccountValidation**](CustomersApi.md#GetAccountValidation) | **Get** /customers/{customerID}/accounts/{accountID}/validations/{validationID} | Get Account Validation
 [**GetCustomer**](CustomersApi.md#GetCustomer) | **Get** /customers/{customerID} | Retrieve customer
 [**GetCustomerAccountByID**](CustomersApi.md#GetCustomerAccountByID) | **Get** /customers/{customerID}/accounts/{accountID} | Get Customer Account by ID
@@ -413,6 +414,53 @@ No authorization required
 [[Back to README]](../README.md)
 
 
+## DeleteCustomerDocument
+
+> DeleteCustomerDocument(ctx, customerID, documentID, optional)
+
+Delete a customer's document
+
+Remove a customer's document
+
+### Required Parameters
+
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc.
+**customerID** | **string**| ID of the customer that owns the document | 
+**documentID** | **string**| ID of the document | 
+ **optional** | ***DeleteCustomerDocumentOpts** | optional parameters | nil if no parameters
+
+### Optional Parameters
+
+Optional parameters are passed through a pointer to a DeleteCustomerDocumentOpts struct
+
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+
+
+ **xRequestID** | **optional.String**| Optional requestID allows application developer to trace requests through the systems logs | 
+
+### Return type
+
+ (empty response body)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints)
+[[Back to Model list]](../README.md#documentation-for-models)
+[[Back to README]](../README.md)
+
+
 ## GetAccountValidation
 
 > AccountValidationResponse GetAccountValidation(ctx, customerID, accountID, validationID, optional)
diff --git a/pkg/customers/customers.go b/pkg/customers/customers.go
index eab1104..d098226 100644
--- a/pkg/customers/customers.go
+++ b/pkg/customers/customers.go
@@ -447,11 +447,9 @@ func (r *sqlCustomerRepository) getCustomer(customerID string) (*client.Customer
 
 	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)
+	err = row.Scan(&cust.FirstName, &cust.MiddleName, &cust.LastName, &cust.NickName, &cust.Suffix, &cust.Type, &cust.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)
@@ -459,9 +457,6 @@ func (r *sqlCustomerRepository) getCustomer(customerID string) (*client.Customer
 	if cust.FirstName == "" {
 		return nil, nil // not found
 	}
-	if birthDate != nil {
-		cust.BirthDate = *birthDate
-	}
 
 	phones, err := r.readPhones(customerID)
 	if err != nil {
diff --git a/pkg/customers/documents.go b/pkg/customers/documents.go
index 0221204..cfa4490 100644
--- a/pkg/customers/documents.go
+++ b/pkg/customers/documents.go
@@ -18,6 +18,7 @@ import (
 
 	"github.com/moov-io/base"
 	moovhttp "github.com/moov-io/base/http"
+
 	"github.com/moov-io/customers/pkg/client"
 	"github.com/moov-io/customers/pkg/route"
 
@@ -36,6 +37,7 @@ func AddDocumentRoutes(logger log.Logger, r *mux.Router, repo DocumentRepository
 	r.Methods("GET").Path("/customers/{customerID}/documents").HandlerFunc(getCustomerDocuments(logger, repo))
 	r.Methods("POST").Path("/customers/{customerID}/documents").HandlerFunc(uploadCustomerDocument(logger, repo, bucketFactory))
 	r.Methods("GET").Path("/customers/{customerID}/documents/{documentId}").HandlerFunc(retrieveRawDocument(logger, repo, bucketFactory))
+	r.Methods("DELETE").Path("/customers/{customerID}/documents/{documentId}").HandlerFunc(deleteCustomerDocument(logger, repo))
 }
 
 func getDocumentID(w http.ResponseWriter, r *http.Request) string {
@@ -142,7 +144,6 @@ func uploadCustomerDocument(logger log.Logger, repo DocumentRepository, bucketFa
 			ContentType:        contentType,
 		})
 		if err != nil {
-			logger.Log("documents", fmt.Sprintf("problem uploading document=%s: %v", doc.DocumentID, err), "requestID", requestID)
 			moovhttp.Problem(w, err)
 			return
 		}
@@ -199,6 +200,29 @@ func retrieveRawDocument(logger log.Logger, repo DocumentRepository, bucketFacto
 	}
 }
 
+func deleteCustomerDocument(logger log.Logger, repo DocumentRepository) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		w = route.Responder(logger, w, r)
+		requestID := moovhttp.GetRequestID(r)
+
+		customerID, documentID := route.GetCustomerID(w, r), getDocumentID(w, r)
+		if customerID == "" || documentID == "" {
+			return
+		}
+
+		err := repo.deleteCustomerDocument(customerID, documentID)
+		if err != nil {
+			moovhttp.Problem(w, fmt.Errorf("deleting document: %v", err))
+			logger.Log("documents", fmt.Sprintf("error deleting document=%s for customer=%s: %v", documentID, customerID, err), "requestID", requestID)
+			return
+		}
+
+		logger.Log("documents", fmt.Sprintf("successfully deleted document=%s for customer=%s", documentID, customerID), "requestID", requestID)
+
+		w.WriteHeader(http.StatusNoContent)
+	}
+}
+
 func makeDocumentKey(customerID, documentId string) string {
 	return fmt.Sprintf("customer-%s-document-%s", customerID, documentId)
 }
@@ -206,6 +230,7 @@ func makeDocumentKey(customerID, documentId string) string {
 type DocumentRepository interface {
 	getCustomerDocuments(customerID string) ([]*client.Document, error)
 	writeCustomerDocument(customerID string, doc *client.Document) error
+	deleteCustomerDocument(customerID string, documentID string) error
 }
 
 type sqlDocumentRepository struct {
@@ -220,7 +245,7 @@ func NewDocumentRepo(logger log.Logger, db *sql.DB) DocumentRepository {
 	}
 }
 func (r *sqlDocumentRepository) getCustomerDocuments(customerID string) ([]*client.Document, error) {
-	query := `select document_id, type, content_type, uploaded_at from documents where customer_id = ?`
+	query := `select document_id, type, content_type, uploaded_at from documents where customer_id = ? and deleted_at is null`
 	stmt, err := r.db.Prepare(query)
 	if err != nil {
 		return nil, fmt.Errorf("getCustomerDocuments: prepare %v", err)
@@ -257,3 +282,15 @@ func (r *sqlDocumentRepository) writeCustomerDocument(customerID string, doc *cl
 	}
 	return nil
 }
+
+func (r *sqlDocumentRepository) deleteCustomerDocument(customerID string, documentID string) error {
+	query := `update documents set deleted_at = ? where customer_id = ? and document_id = ? and deleted_at is null;`
+	stmt, err := r.db.Prepare(query)
+	if err != nil {
+		return err
+	}
+	defer stmt.Close()
+
+	_, err = stmt.Exec(time.Now(), customerID, documentID)
+	return err
+}


Test patch:
diff --git a/pkg/customers/customers_test.go b/pkg/customers/customers_test.go
index 0806a89..d7a595f 100644
--- a/pkg/customers/customers_test.go
+++ b/pkg/customers/customers_test.go
@@ -224,14 +224,6 @@ func TestCustomerRepository__createCustomer(t *testing.T) {
 		if err := repo.createCustomer(cust, "namespace"); err != nil {
 			t.Fatal(err)
 		}
-
-		cust, err := repo.getCustomer(cust.CustomerID)
-		if err != nil {
-			t.Fatal(err)
-		}
-		if cust == nil {
-			t.Error("got nil Customer")
-		}
 	}
 
 	// SQLite tests
diff --git a/pkg/customers/documents_test.go b/pkg/customers/documents_test.go
index 9b9d65c..0475e24 100644
--- a/pkg/customers/documents_test.go
+++ b/pkg/customers/documents_test.go
@@ -18,8 +18,11 @@ import (
 	"path/filepath"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/moov-io/base"
+	"github.com/stretchr/testify/require"
+
 	"github.com/moov-io/customers/internal/database"
 	"github.com/moov-io/customers/pkg/client"
 
@@ -46,6 +49,11 @@ func (r *testDocumentRepository) writeCustomerDocument(customerID string, doc *c
 	return r.err
 }
 
+func (r *testDocumentRepository) deleteCustomerDocument(customerID string, documentID string) error {
+	r.written = nil
+	return r.err
+}
+
 func TestDocuments__getDocumentID(t *testing.T) {
 	w := httptest.NewRecorder()
 	req := httptest.NewRequest("GET", "/ping", nil)
@@ -181,6 +189,35 @@ func TestDocumentsUploadAndRetrieval(t *testing.T) {
 	}
 }
 
+func TestDocuments__delete(t *testing.T) {
+	db := database.CreateTestSqliteDB(t)
+	repo := &sqlDocumentRepository{db.DB, log.NewNopLogger()}
+
+	router := mux.NewRouter()
+	AddDocumentRoutes(log.NewNopLogger(), router, repo, testBucket)
+
+	customerID := base.ID()
+	// create document
+	doc := &client.Document{
+		DocumentID:  base.ID(),
+		Type:        "DriversLicense",
+		ContentType: "image/png",
+		UploadedAt:  time.Now(),
+	}
+	err := repo.writeCustomerDocument(customerID, doc)
+	require.NoError(t, err)
+
+	w := httptest.NewRecorder()
+	req := httptest.NewRequest("DELETE", fmt.Sprintf("/customers/%s/documents/%s", customerID, doc.DocumentID), nil)
+	router.ServeHTTP(w, req)
+
+	require.Equal(t, http.StatusNoContent, w.Code)
+
+	docs, err := repo.getCustomerDocuments(customerID)
+	require.NoError(t, err)
+	require.Empty(t, docs)
+}
+
 func TestDocuments__uploadCustomerDocument(t *testing.T) {
 	repo := &testDocumentRepository{}
 
@@ -239,10 +276,7 @@ func TestDocumentRepository(t *testing.T) {
 			t.Errorf("got %d unexpected documents: %#v", len(docs), docs)
 		}
 		if docs[0].DocumentID != doc.DocumentID {
-			t.Errorf("got docs[0].DocumentID=%q / want doc.DocumentID=%q", docs[0].DocumentID, doc.DocumentID)
-		}
-		if docs[0].ContentType != "image/png" {
-			t.Errorf("docs[0].ContentType=%q", docs[0].ContentType)
+			t.Errorf("docs[0].DocumentID=%s doc.DocumentID=%s", docs[0].DocumentID, doc.DocumentID)
 		}
 	}
 
@@ -256,3 +290,58 @@ func TestDocumentRepository(t *testing.T) {
 	defer mysqlDB.Close()
 	check(t, &sqlDocumentRepository{mysqlDB.DB, log.NewNopLogger()})
 }
+
+func TestDocumentsRepository__Delete(t *testing.T) {
+	db := database.CreateTestSqliteDB(t)
+	repo := &sqlDocumentRepository{db.DB, log.NewNopLogger()}
+
+	type document struct {
+		*client.Document
+		deleted bool
+	}
+
+	customerID := base.ID()
+	docs := make([]*document, 10)
+	for i := 0; i < len(docs); i++ {
+		doc := &client.Document{
+			DocumentID:  base.ID(),
+			Type:        "DriversLicense",
+			ContentType: "image/png",
+		}
+		err := repo.writeCustomerDocument(customerID, doc)
+		require.NoError(t, err)
+		docs[i] = &document{
+			Document: doc,
+		}
+	}
+
+	// mark documents to be deleted
+	indexesToDelete := []int{1, 2, 5, 8}
+	for _, idx := range indexesToDelete {
+		require.Less(t, idx, len(docs))
+		docs[idx].deleted = true
+		require.NoError(t,
+			repo.deleteCustomerDocument(customerID, docs[idx].DocumentID),
+		)
+	}
+
+	deletedDocIDs := make(map[string]bool)
+	// query all documents that have been marked as deleted
+	query := `select document_id from documents where deleted_at is not null;`
+	stmt, err := repo.db.Prepare(query)
+	require.NoError(t, err)
+
+	rows, err := stmt.Query()
+	require.NoError(t, err)
+
+	for rows.Next() {
+		var ID *string
+		require.NoError(t, rows.Scan(&ID))
+		deletedDocIDs[*ID] = true
+	}
+
+	for _, doc := range docs {
+		_, ok := deletedDocIDs[doc.DocumentID]
+		require.Equal(t, doc.deleted, ok)
+	}
+}