Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: expose file.isIncoming as a query param #355

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions client/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ paths:
schema:
type: string
style: simple
- description: Optional flag to indicate if the file is incoming or outgoing.
SenderSupplied (tag 1500) is not required for incoming files.
explode: true
in: query
name: isIncoming
atonks2 marked this conversation as resolved.
Show resolved Hide resolved
required: false
schema:
example: true
type: boolean
style: form
requestBody:
content:
application/json:
Expand Down
5 changes: 5 additions & 0 deletions client/api_wire_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func (a *WireFilesApiService) AddFEDWireMessageToFile(ctx _context.Context, file
// CreateWireFileOpts Optional parameters for the method 'CreateWireFile'
type CreateWireFileOpts struct {
XRequestID optional.String
IsIncoming optional.Bool
}

/*
Expand All @@ -119,6 +120,7 @@ Create a new File object from either the plaintext or JSON representation.
- @param wireFile Content of the Wire file (in json or raw text)
- @param optional nil or *CreateWireFileOpts - Optional Parameters:
- @param "XRequestID" (optional.String) - Optional Request ID allows application developer to trace requests through the system's logs
- @param "IsIncoming" (optional.Bool) - Optional flag to indicate if the file is incoming or outgoing. SenderSupplied (tag 1500) is not required for incoming files.

@return WireFile
*/
Expand All @@ -139,6 +141,9 @@ func (a *WireFilesApiService) CreateWireFile(ctx _context.Context, wireFile Wire
localVarQueryParams := _neturl.Values{}
localVarFormParams := _neturl.Values{}

if localVarOptionals != nil && localVarOptionals.IsIncoming.IsSet() {
localVarQueryParams.Add("isIncoming", parameterToString(localVarOptionals.IsIncoming.Value(), ""))
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{"application/json", "text/plain"}

Expand Down
4 changes: 1 addition & 3 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,9 +392,7 @@ func setBody(body interface{}, contentType string) (bodyBuf *bytes.Buffer, err e
} else if jsonCheck.MatchString(contentType) {
err = json.NewEncoder(bodyBuf).Encode(body)
} else if xmlCheck.MatchString(contentType) {
encoder := xml.NewEncoder(bodyBuf)
defer encoder.Close()
err = encoder.Encode(body)
err = xml.NewEncoder(bodyBuf).Encode(body)
}

if err != nil {
Expand Down
1 change: 1 addition & 0 deletions client/docs/WireFilesApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------

**xRequestID** | **optional.String**| Optional Request ID allows application developer to trace requests through the system's logs |
**isIncoming** | **optional.Bool**| Optional flag to indicate if the file is incoming or outgoing. SenderSupplied (tag 1500) is not required for incoming files. |

### Return type

Expand Down
9 changes: 7 additions & 2 deletions cmd/server/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ func createFile(logger log.Logger, repo WireFileRepository) http.HandlerFunc {

w = wrapResponseWriter(logger, w, r)

req := wire.NewFile()
var opts []wire.FilePropertyFunc
if strings.EqualFold("true", r.URL.Query().Get("isIncoming")) {
opts = append(opts, wire.IncomingFile())
}

req := wire.NewFile(opts...)
req.ID = base.ID()

if strings.Contains(r.Header.Get("Content-Type"), "application/json") {
Expand All @@ -111,7 +116,7 @@ func createFile(logger log.Logger, repo WireFileRepository) http.HandlerFunc {
return
}
} else {
file, err := wire.NewReader(r.Body).Read()
file, err := wire.NewReader(r.Body, opts...).Read()
if err != nil {
err = logger.LogErrorf("error reading file: %v", err).Err()
moovhttp.Problem(w, err)
Expand Down
168 changes: 132 additions & 36 deletions cmd/server/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -159,7 +161,7 @@ func TestFiles_createFileJSON(t *testing.T) {
router := mux.NewRouter()
addFileRoutes(log.NewNopLogger(), router, repo)

t.Run("creates file from JSON", func(t *testing.T) {
t.Run("creates file from JSON - bank transfer", func(t *testing.T) {
w := httptest.NewRecorder()
bs, err := os.ReadFile(filepath.Join("..", "..", "test", "testdata", "fedWireMessage-BankTransfer.json"))
require.NoError(t, err)
Expand All @@ -177,7 +179,7 @@ func TestFiles_createFileJSON(t *testing.T) {
assert.NotNil(t, resp.FEDWireMessage.FIAdditionalFIToFI)
})

t.Run("creates file from JSON", func(t *testing.T) {
t.Run("creates file from JSON - customer transfer", func(t *testing.T) {
w := httptest.NewRecorder()
bs, err := os.ReadFile(filepath.Join("..", "..", "test", "testdata", "fedWireMessage-CustomerTransfer.json"))
require.NoError(t, err)
Expand Down Expand Up @@ -207,6 +209,134 @@ func TestFiles_createFileJSON(t *testing.T) {
})
}

func TestFiles_createIncomingFile(t *testing.T) {
repo := &testWireFileRepository{}
router := mux.NewRouter()
addFileRoutes(log.NewNopLogger(), router, repo)

// set up a message with no SenderSupplied field
fwm := mockFEDWireMessage()
fwm.SenderSupplied = nil
file := wire.NewFile()
file.AddFEDWireMessage(fwm)

// upload of outgoing file should fail without sender supplied
resp, _ := routerUploadJSON(t, router, file)
require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body)
require.Contains(t, resp.Body.String(), "SenderSupplied")

// upload of incoming file should succeed without sender supplied
resp, uploaded := routerUploadJSON(t, router, file, setQueryParam("isIncoming", "true"))
require.Equal(t, http.StatusCreated, resp.Code, resp.Body)
require.NotEmpty(t, uploaded.ID)
require.Nil(t, uploaded.FEDWireMessage.SenderSupplied)

// make sure the file was saved
resp, found := routerGetFile(t, router, uploaded.ID)
require.Equal(t, http.StatusOK, resp.Code, resp.Body)
require.Equal(t, uploaded.ID, found.ID)
require.Nil(t, found.FEDWireMessage.SenderSupplied)

// get file contents calls Validate()
// if isIncoming was passed properly, then the file should be valid
resp = routerGetFileContents(t, router, uploaded.ID)
require.Equal(t, http.StatusOK, resp.Code, resp.Body)
require.NotNil(t, resp.Body)

// upload raw should succeed without sender supplied
resp, rawUpload := routerUploadRaw(t, router, resp.Body, setQueryParam("isIncoming", "true"))
require.Equal(t, http.StatusCreated, resp.Code, resp.Body)
require.NotEmpty(t, rawUpload.ID)
require.Nil(t, rawUpload.FEDWireMessage.SenderSupplied)

// get new file
resp, found = routerGetFile(t, router, rawUpload.ID)
require.Equal(t, http.StatusOK, resp.Code, resp.Body)
require.Equal(t, rawUpload.ID, found.ID)
require.Nil(t, found.FEDWireMessage.SenderSupplied)

// get new file contents
resp = routerGetFileContents(t, router, rawUpload.ID)
require.Equal(t, http.StatusOK, resp.Code, resp.Body)
require.NotNil(t, resp.Body)
}

func setQueryParam(key, value string) func(values url.Values) url.Values {
return func(values url.Values) url.Values {
values.Set(key, value)
return values
}
}

func routerUploadJSON(t *testing.T, router *mux.Router, file *wire.File, queryOpts ...func(values url.Values) url.Values) (*httptest.ResponseRecorder, *wire.File) {
bs, err := json.Marshal(file)
require.NoError(t, err)

req := httptest.NewRequest("POST", "/files/create", bytes.NewReader(bs))
req.Header.Set("content-type", "application/json")

query := req.URL.Query()
for _, opt := range queryOpts {
query = opt(query)
}
req.URL.RawQuery = query.Encode()

w := httptest.NewRecorder()
router.ServeHTTP(w, req)
w.Flush()

var resp *wire.File
if w.Code == http.StatusCreated {
require.NoError(t, json.NewDecoder(w.Body).Decode(&resp))
}
return w, resp
}

func routerUploadRaw(t *testing.T, router *mux.Router, raw io.Reader, queryOpts ...func(values url.Values) url.Values) (*httptest.ResponseRecorder, *wire.File) {
req := httptest.NewRequest("POST", "/files/create", raw)
req.Header.Set("content-type", "text/plain")

query := req.URL.Query()
for _, opt := range queryOpts {
query = opt(query)
}
req.URL.RawQuery = query.Encode()

w := httptest.NewRecorder()
router.ServeHTTP(w, req)
w.Flush()

var resp *wire.File
if w.Code == http.StatusCreated {
_ = json.NewDecoder(w.Body).Decode(&resp)
}
return w, resp
}

func routerGetFile(t *testing.T, router *mux.Router, id string) (*httptest.ResponseRecorder, *wire.File) {
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/files/"+id, nil)

router.ServeHTTP(w, req)
w.Flush()

var file *wire.File
if w.Code == http.StatusOK {
_ = json.NewDecoder(w.Body).Decode(&file)
}
return w, file
}

func routerGetFileContents(t *testing.T, router *mux.Router, id string) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/files/"+id+"/contents", nil)

router.ServeHTTP(w, req)
w.Flush()

return w
}

func TestFiles_getFile(t *testing.T) {
req := httptest.NewRequest("GET", "/files/foo", nil)
repo := &testWireFileRepository{
Expand Down Expand Up @@ -487,37 +617,3 @@ func TestFiles_addFEDWireMessageToFile(t *testing.T) {
assert.Equal(t, http.StatusNotFound, w.Code, w.Body)
})
}

/*func TestFiles_removeFEDWireMessageFromFile(t *testing.T) {
f, err := readFile("fedWireMessage-CustomerTransfer.txt")
if err != nil {
t.Fatal(err)
}
repo := &testWireFileRepository{file: f}

FEDWireMessageID := base.ID()
repo.file.FEDWireMessage.ID = FedWireMessageID

w := httptest.NewRecorder()
req := httptest.NewRequest("DELETE", fmt.Sprintf("/files/foo/FEDWireMessage/%s", FEDWireMessageID), nil)

router := mux.NewRouter()
addFileRoutes(log.NewNopLogger(), router, repo)
router.ServeHTTP(w, req)
w.Flush()

if w.Code != http.StatusOK {
t.Errorf("bogus HTTP status: %d: %v", w.Code, w.Body.String())
}

// error case
repo.err = errors.New("bad error")

w = httptest.NewRecorder()
router.ServeHTTP(w, req)
w.Flush()

if w.Code != http.StatusBadRequest {
t.Errorf("bogus HTTP status: %d: %v", w.Code, w.Body.String())
}
}*/
4 changes: 2 additions & 2 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type File struct {
ID string `json:"id"`
FEDWireMessage FEDWireMessage `json:"fedWireMessage"`

isIncoming bool `json:"-"`
isIncoming bool
}

// NewFile constructs a file template
Expand Down Expand Up @@ -75,7 +75,7 @@ func (f *File) Validate() error {
// be rejected by other Financial Institutions or ACH tools.
func FileFromJSON(bs []byte) (*File, error) {
if len(bs) == 0 {
//return nil, errors.New("no JSON data provided")
// return nil, errors.New("no JSON data provided")
return nil, nil
}

Expand Down
7 changes: 7 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ paths:
example: rs4f9915
schema:
type: string
- name: isIncoming
in: query
description: Optional flag to indicate if the file is incoming or outgoing. SenderSupplied (tag 1500) is not required for incoming files.
required: false
schema:
type: boolean
example: true
requestBody:
description: Content of the Wire file (in json or raw text)
required: true
Expand Down
4 changes: 2 additions & 2 deletions reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ func (r *Reader) parseError(err error) error {
}

// NewReader returns a new ACH Reader that reads from r.
func NewReader(r io.Reader) *Reader {
func NewReader(r io.Reader, opts ...FilePropertyFunc) *Reader {
reader := &Reader{
scanner: bufio.NewScanner(r),
File: *NewFile(IncomingFile()),
File: *NewFile(opts...),
}

reader.scanner.Split(scanLinesWithSegmentFormat)
Expand Down
8 changes: 5 additions & 3 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (w *Writer) writeFEDWireMessage(file *File) error {

fwm := file.FEDWireMessage

if err := w.writeMandatory(fwm); err != nil {
if err := w.writeMandatory(fwm, file.isIncoming); err != nil {
return err
}

Expand Down Expand Up @@ -160,14 +160,16 @@ func (w *Writer) writeFedAppended(fwm FEDWireMessage) error {
return nil
}

func (w *Writer) writeMandatory(fwm FEDWireMessage) error {
func (w *Writer) writeMandatory(fwm FEDWireMessage, isIncoming bool) error {

if fwm.SenderSupplied != nil {
if _, err := w.w.WriteString(fwm.SenderSupplied.Format(w.FormatOptions) + w.NewlineCharacter); err != nil {
return err
}
} else {
return fieldError("SenderSupplied", ErrFieldRequired)
if !isIncoming {
return fieldError("SenderSupplied", ErrFieldRequired)
}
}

if fwm.TypeSubType != nil {
Expand Down
Loading