Repository: moov-io/customers
Pull request #156 (2020-09-15 18:10:12)

Account Ofac Refresh
This PR provides a PUT endpoint that allows an Account ofac search to be updated by running the ofac search again.


Patch:
diff --git a/api/client.yaml b/api/client.yaml
index e4b262d..d3a7051 100644
--- a/api/client.yaml
+++ b/api/client.yaml
@@ -573,6 +573,52 @@ paths:
             application/json:
               schema:
                 $ref: 'https://raw.githubusercontent.com/moov-io/api/master/openapi-common.yaml#/components/schemas/Error'
+  /customers/{customerID}/accounts/{accountID}/refresh/ofac:
+    put:
+      tags: [ Customers ]
+      summary: Refresh Account OFAC search
+      description: Refresh OFAC search for a given Account
+      operationId: refreshAccountOFACSearch
+      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: X-User-ID
+          in: header
+          description: Unique userID set by an auth proxy or client to identify and isolate objects.
+          example: e3cdf999
+          schema:
+            type: string
+        - name: customerID
+          in: path
+          description: customerID of the Customer to refresh OFAC search
+          required: true
+          schema:
+            type: string
+            example: e210a9d6
+        - name: accountID
+          in: path
+          description: accountID of the Account to get latest OFAC search
+          required: true
+          schema:
+            type: string
+            example: e210a9d6
+      responses:
+        '200':
+          description: OFAC search did not block Account
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/OFACSearch'
+        '400':
+          description: An error occurred when refreshing OFAC data, see error(s)
+          content:
+            application/json:
+              schema:
+                $ref: 'https://raw.githubusercontent.com/moov-io/api/master/openapi-common.yaml#/components/schemas/Error'
   /customers/{customerID}/accounts/{accountID}/validations:
     post:
       tags: [Customers]
diff --git a/cmd/server/accounts/router.go b/cmd/server/accounts/router.go
index 6b5bc6f..5613861 100644
--- a/cmd/server/accounts/router.go
+++ b/cmd/server/accounts/router.go
@@ -32,6 +32,7 @@ func RegisterRoutes(logger log.Logger, r *mux.Router, accounts Repository, valid
 	r.Methods("DELETE").Path("/customers/{customerID}/accounts/{accountID}").HandlerFunc(removeCustomerAccount(logger, accounts))
 
 	r.Methods("GET").Path("/customers/{customerID}/accounts/{accountID}/ofac").HandlerFunc(getAccountOfacSearch(logger, accounts))
+	r.Methods("PUT").Path("/customers/{customerID}/accounts/{accountID}/refresh/ofac").HandlerFunc(refreshAccountOfac(logger, accounts, ofac))
 
 	r.Methods("POST").Path("/customers/{customerID}/accounts/{accountID}/validations").HandlerFunc(initAccountValidation(logger, accounts, validations, validationStrategies))
 	r.Methods("GET").Path("/customers/{customerID}/accounts/{accountID}/validations/{validationID}").HandlerFunc(getAccountValidation(logger, accounts, validations))
@@ -240,3 +241,33 @@ func getAccountOfacSearch(logger log.Logger, repo Repository) func(http.Response
 		json.NewEncoder(w).Encode(result)
 	}
 }
+
+func refreshAccountOfac(logger log.Logger, repo Repository, ofac *AccountOfacSearcher) func(http.ResponseWriter, *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		w = route.Responder(logger, w, r)
+		w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+		requestID := moovhttp.GetRequestID(r)
+		customerID, accountID := route.GetCustomerID(w, r), getAccountID(w, r)
+		if customerID == "" || accountID == "" {
+			return
+		}
+		account, err := repo.getCustomerAccount(customerID, accountID)
+		if err != nil {
+			moovhttp.Problem(w, err)
+			return
+		}
+		err = ofac.StoreAccountOFACSearch(account, requestID)
+		if err != nil {
+			moovhttp.Problem(w, err)
+			return
+		}
+		result, err := repo.getLatestAccountOFACSearch(accountID)
+		if err != nil {
+			moovhttp.Problem(w, err)
+			return
+		}
+		w.WriteHeader(http.StatusOK)
+		json.NewEncoder(w).Encode(result)
+	}
+}
diff --git a/pkg/client/README.md b/pkg/client/README.md
index 707f205..4659613 100644
--- a/pkg/client/README.md
+++ b/pkg/client/README.md
@@ -55,6 +55,7 @@ Class | Method | HTTP request | Description
 *CustomersApi* | [**GetLatestOFACSearch**](docs/CustomersApi.md#getlatestofacsearch) | **Get** /customers/{customerID}/ofac | Get latest OFAC search
 *CustomersApi* | [**InitAccountValidation**](docs/CustomersApi.md#initaccountvalidation) | **Post** /customers/{customerID}/accounts/{accountID}/validations | Initiate Account Validation
 *CustomersApi* | [**Ping**](docs/CustomersApi.md#ping) | **Get** /ping | Ping Customers
+*CustomersApi* | [**RefreshAccountOFACSearch**](docs/CustomersApi.md#refreshaccountofacsearch) | **Put** /customers/{customerID}/accounts/{accountID}/refresh/ofac | Refresh Account OFAC search
 *CustomersApi* | [**RefreshOFACSearch**](docs/CustomersApi.md#refreshofacsearch) | **Put** /customers/{customerID}/refresh/ofac | Refresh customer OFAC search
 *CustomersApi* | [**ReplaceCustomerMetadata**](docs/CustomersApi.md#replacecustomermetadata) | **Put** /customers/{customerID}/metadata | Update customer metadata
 *CustomersApi* | [**SearchCustomers**](docs/CustomersApi.md#searchcustomers) | **Get** /customers | Get customers
diff --git a/pkg/client/api/openapi.yaml b/pkg/client/api/openapi.yaml
index 431fad2..ccf273e 100644
--- a/pkg/client/api/openapi.yaml
+++ b/pkg/client/api/openapi.yaml
@@ -722,6 +722,65 @@ paths:
       summary: Get latest OFAC search
       tags:
       - Customers
+  /customers/{customerID}/accounts/{accountID}/refresh/ofac:
+    put:
+      description: Refresh OFAC search for a given Account
+      operationId: refreshAccountOFACSearch
+      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: Unique userID set by an auth proxy or client to identify and
+          isolate objects.
+        example: e3cdf999
+        explode: false
+        in: header
+        name: X-User-ID
+        required: false
+        schema:
+          type: string
+        style: simple
+      - description: customerID of the Customer to refresh OFAC search
+        explode: false
+        in: path
+        name: customerID
+        required: true
+        schema:
+          example: e210a9d6
+          type: string
+        style: simple
+      - description: accountID of the Account to get latest OFAC search
+        explode: false
+        in: path
+        name: accountID
+        required: true
+        schema:
+          example: e210a9d6
+          type: string
+        style: simple
+      responses:
+        "200":
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/OFACSearch'
+          description: OFAC search did not block Account
+        "400":
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+          description: An error occurred when refreshing OFAC data, see error(s)
+      summary: Refresh Account OFAC search
+      tags:
+      - Customers
   /customers/{customerID}/accounts/{accountID}/validations:
     post:
       description: |
diff --git a/pkg/client/api_customers.go b/pkg/client/api_customers.go
index 3e899b7..6162bf3 100644
--- a/pkg/client/api_customers.go
+++ b/pkg/client/api_customers.go
@@ -1749,6 +1749,111 @@ func (a *CustomersApiService) Ping(ctx _context.Context) (*_nethttp.Response, er
 	return localVarHTTPResponse, nil
 }
 
+// RefreshAccountOFACSearchOpts Optional parameters for the method 'RefreshAccountOFACSearch'
+type RefreshAccountOFACSearchOpts struct {
+	XRequestID optional.String
+	XUserID    optional.String
+}
+
+/*
+RefreshAccountOFACSearch Refresh Account OFAC search
+Refresh OFAC search for a given Account
+ * @param ctx _context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
+ * @param customerID customerID of the Customer to refresh OFAC search
+ * @param accountID accountID of the Account to get latest OFAC search
+ * @param optional nil or *RefreshAccountOFACSearchOpts - Optional Parameters:
+ * @param "XRequestID" (optional.String) -  Optional requestID allows application developer to trace requests through the systems logs
+ * @param "XUserID" (optional.String) -  Unique userID set by an auth proxy or client to identify and isolate objects.
+@return OfacSearch
+*/
+func (a *CustomersApiService) RefreshAccountOFACSearch(ctx _context.Context, customerID string, accountID string, localVarOptionals *RefreshAccountOFACSearchOpts) (OfacSearch, *_nethttp.Response, error) {
+	var (
+		localVarHTTPMethod   = _nethttp.MethodPut
+		localVarPostBody     interface{}
+		localVarFormFileName string
+		localVarFileName     string
+		localVarFileBytes    []byte
+		localVarReturnValue  OfacSearch
+	)
+
+	// create path and map variables
+	localVarPath := a.client.cfg.BasePath + "/customers/{customerID}/accounts/{accountID}/refresh/ofac"
+	localVarPath = strings.Replace(localVarPath, "{"+"customerID"+"}", _neturl.QueryEscape(parameterToString(customerID, "")), -1)
+
+	localVarPath = strings.Replace(localVarPath, "{"+"accountID"+"}", _neturl.QueryEscape(parameterToString(accountID, "")), -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(), "")
+	}
+	if localVarOptionals != nil && localVarOptionals.XUserID.IsSet() {
+		localVarHeaderParams["X-User-ID"] = parameterToString(localVarOptionals.XUserID.Value(), "")
+	}
+	r, err := a.client.prepareRequest(ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes)
+	if err != nil {
+		return localVarReturnValue, nil, err
+	}
+
+	localVarHTTPResponse, err := a.client.callAPI(r)
+	if err != nil || localVarHTTPResponse == nil {
+		return localVarReturnValue, localVarHTTPResponse, err
+	}
+
+	localVarBody, err := _ioutil.ReadAll(localVarHTTPResponse.Body)
+	localVarHTTPResponse.Body.Close()
+	if err != nil {
+		return localVarReturnValue, 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 localVarReturnValue, localVarHTTPResponse, newErr
+			}
+			newErr.model = v
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type"))
+	if err != nil {
+		newErr := GenericOpenAPIError{
+			body:  localVarBody,
+			error: err.Error(),
+		}
+		return localVarReturnValue, localVarHTTPResponse, newErr
+	}
+
+	return localVarReturnValue, localVarHTTPResponse, nil
+}
+
 // RefreshOFACSearchOpts Optional parameters for the method 'RefreshOFACSearch'
 type RefreshOFACSearchOpts struct {
 	XRequestID optional.String
diff --git a/pkg/client/docs/CustomersApi.md b/pkg/client/docs/CustomersApi.md
index 5026134..6e8da16 100644
--- a/pkg/client/docs/CustomersApi.md
+++ b/pkg/client/docs/CustomersApi.md
@@ -21,6 +21,7 @@ Method | HTTP request | Description
 [**GetLatestOFACSearch**](CustomersApi.md#GetLatestOFACSearch) | **Get** /customers/{customerID}/ofac | Get latest OFAC search
 [**InitAccountValidation**](CustomersApi.md#InitAccountValidation) | **Post** /customers/{customerID}/accounts/{accountID}/validations | Initiate Account Validation
 [**Ping**](CustomersApi.md#Ping) | **Get** /ping | Ping Customers
+[**RefreshAccountOFACSearch**](CustomersApi.md#RefreshAccountOFACSearch) | **Put** /customers/{customerID}/accounts/{accountID}/refresh/ofac | Refresh Account OFAC search
 [**RefreshOFACSearch**](CustomersApi.md#RefreshOFACSearch) | **Put** /customers/{customerID}/refresh/ofac | Refresh customer OFAC search
 [**ReplaceCustomerMetadata**](CustomersApi.md#ReplaceCustomerMetadata) | **Put** /customers/{customerID}/metadata | Update customer metadata
 [**SearchCustomers**](CustomersApi.md#SearchCustomers) | **Get** /customers | Get customers
@@ -819,6 +820,54 @@ No authorization required
 [[Back to README]](../README.md)
 
 
+## RefreshAccountOFACSearch
+
+> OfacSearch RefreshAccountOFACSearch(ctx, customerID, accountID, optional)
+
+Refresh Account OFAC search
+
+Refresh OFAC search for a given Account
+
+### Required Parameters
+
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+**ctx** | **context.Context** | context for authentication, logging, cancellation, deadlines, tracing, etc.
+**customerID** | **string**| customerID of the Customer to refresh OFAC search | 
+**accountID** | **string**| accountID of the Account to get latest OFAC search | 
+ **optional** | ***RefreshAccountOFACSearchOpts** | optional parameters | nil if no parameters
+
+### Optional Parameters
+
+Optional parameters are passed through a pointer to a RefreshAccountOFACSearchOpts struct
+
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+
+
+ **xRequestID** | **optional.String**| Optional requestID allows application developer to trace requests through the systems logs | 
+ **xUserID** | **optional.String**| Unique userID set by an auth proxy or client to identify and isolate objects. | 
+
+### Return type
+
+[**OfacSearch**](OFACSearch.md)
+
+### 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)
+
+
 ## RefreshOFACSearch
 
 > OfacSearch RefreshOFACSearch(ctx, customerID, optional)


Test patch:
diff --git a/cmd/server/accounts/router_test.go b/cmd/server/accounts/router_test.go
index 5eb25f0..648baf3 100644
--- a/cmd/server/accounts/router_test.go
+++ b/cmd/server/accounts/router_test.go
@@ -121,6 +121,36 @@ func TestCreateAccountAndCheckAccountOfacSearch(t *testing.T) {
 	}
 }
 
+func TestRefreshAccountAndCheckAccountOfacSearch(t *testing.T) {
+	customerID := base.ID()
+
+	handler := setupRouter(t)
+
+	// first read, expect no accounts
+	accounts := httpReadAccounts(t, handler, customerID)
+	if len(accounts) != 0 {
+		t.Errorf("got accounts: %v", accounts)
+	}
+
+	// create an account
+	account := httpCreateAccount(t, handler, customerID)
+	if account.MaskedAccountNumber != "*8749" {
+		t.Errorf("masked account number: %q", account.MaskedAccountNumber)
+	}
+
+	// Check for ofac search after creating
+	accountOfacSearch := httpReadAccountOfacSearch(t, handler, customerID, account.AccountID)
+	if accountOfacSearch == nil {
+		t.Errorf("got account ofac search: %v", accountOfacSearch)
+	}
+
+	// Refresh account ofac
+	accountOfacRefresh := httpRefreshAccountOfac(t, handler, customerID, account.AccountID)
+	if accountOfacRefresh == nil {
+		t.Errorf("got account ofac search: %v", accountOfacRefresh)
+	}
+}
+
 func TestAccountCreationRequest(t *testing.T) {
 	req := &createAccountRequest{}
 	if err := req.validate(); err == nil {
@@ -346,3 +376,20 @@ func httpReadAccountOfacSearch(t *testing.T, handler *mux.Router, customerID, ac
 	}
 	return &ofacSearch
 }
+
+func httpRefreshAccountOfac(t *testing.T, handler *mux.Router, customerID, accountID string) *client.OfacSearch {
+	w := httptest.NewRecorder()
+	req := httptest.NewRequest("PUT", fmt.Sprintf("/customers/%s/accounts/%s/refresh/ofac", customerID, accountID), nil)
+	handler.ServeHTTP(w, req)
+	w.Flush()
+
+	if w.Code != http.StatusOK {
+		t.Errorf("bogus HTTP status %d: %v", w.Code, w.Body.String())
+	}
+
+	var ofacSearch client.OfacSearch
+	if err := json.NewDecoder(w.Body).Decode(&ofacSearch); err != nil {
+		t.Fatal(err)
+	}
+	return &ofacSearch
+}