github.com/moov-io/imagecashletter@v0.10.1/internal/files/v2/files_test.go (about)

     1  package v2_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  	"mime/multipart"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"net/textproto"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/google/uuid"
    17  	"github.com/gorilla/mux"
    18  	"github.com/moov-io/base/log"
    19  	"github.com/moov-io/imagecashletter"
    20  	openapi "github.com/moov-io/imagecashletter/client"
    21  	v2 "github.com/moov-io/imagecashletter/internal/files/v2"
    22  	"github.com/moov-io/imagecashletter/internal/storage"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  func TestController_createFile_errors(t *testing.T) {
    27  	router := newRouter(t)
    28  
    29  	t.Run("unsupported content type", func(t *testing.T) {
    30  		rdr := strings.NewReader("real file")
    31  		resp, apiErr := createFile(t, router, rdr, "application/msword", "application/json")
    32  		require.Equal(t, http.StatusBadRequest, resp.Code)
    33  		require.Contains(t, apiErr.Error, "unsupported Content-Type")
    34  	})
    35  
    36  	t.Run("invalid json file", func(t *testing.T) {
    37  		rdr := strings.NewReader("real file")
    38  		resp, apiErr := createFile(t, router, rdr, "application/json", "application/json")
    39  		require.Equal(t, http.StatusBadRequest, resp.Code)
    40  		require.Contains(t, apiErr.Error, "problem reading file")
    41  	})
    42  
    43  	t.Run("invalid ascii file", func(t *testing.T) {
    44  		rdr := strings.NewReader("real file")
    45  		resp, apiErr := uploadFile(t, router, rdr, "text/plain", "application/json")
    46  		require.Equal(t, http.StatusBadRequest, resp.Code)
    47  		require.Contains(t, apiErr.Error, "parsing file")
    48  	})
    49  
    50  	t.Run("invalid ebcdic file", func(t *testing.T) {
    51  		rdr := strings.NewReader("real file")
    52  		resp, apiErr := uploadFile(t, router, rdr, "application/octet-stream", "application/json")
    53  		require.Equal(t, http.StatusBadRequest, resp.Code)
    54  		require.Contains(t, apiErr.Error, "parsing file")
    55  	})
    56  }
    57  
    58  func TestController_createJSONFile(t *testing.T) {
    59  	router := newRouter(t)
    60  
    61  	t.Run("returns JSON", func(t *testing.T) {
    62  		rdr := getTestData(t, "icl-valid.json")
    63  
    64  		resp, apiErr := createFile(t, router, rdr, "application/json", "application/json")
    65  		require.Empty(t, apiErr)
    66  
    67  		var created imagecashletter.File
    68  		require.NoError(t, json.NewDecoder(resp.Body).Decode(&created))
    69  
    70  		require.NotEmpty(t, created.ID)
    71  		require.Equal(t, "https://some.domain.io/files/"+created.ID, resp.Header().Get("Location"))
    72  		require.NotEmpty(t, created)
    73  		require.Equal(t, "231380104", created.Header.ImmediateDestination)
    74  		require.Len(t, created.CashLetters, 2)
    75  		require.Equal(t, 400000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
    76  	})
    77  
    78  	t.Run("invalid accept header returns JSON", func(t *testing.T) {
    79  		rdr := getTestData(t, "icl-valid.json")
    80  
    81  		resp, apiErr := createFile(t, router, rdr, "application/json", "foo/bar")
    82  		require.Empty(t, apiErr)
    83  
    84  		var created imagecashletter.File
    85  		require.NoError(t, json.NewDecoder(resp.Body).Decode(&created))
    86  
    87  		require.Contains(t, resp.Header().Get("Location"), created.ID)
    88  		require.NotEmpty(t, created.ID)
    89  		require.NotEmpty(t, created)
    90  		require.Equal(t, "231380104", created.Header.ImmediateDestination)
    91  		require.Len(t, created.CashLetters, 2)
    92  		require.Equal(t, 400000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
    93  	})
    94  
    95  	t.Run("returns EBCDIC file", func(t *testing.T) {
    96  		rdr := getTestData(t, "icl-valid.json")
    97  
    98  		resp, apiErr := createFile(t, router, rdr, "application/json", "application/octet-stream")
    99  		require.Empty(t, apiErr)
   100  
   101  		opts := []imagecashletter.ReaderOption{
   102  			imagecashletter.ReadVariableLineLengthOption(),
   103  			imagecashletter.ReadEbcdicEncodingOption(),
   104  		}
   105  		created, err := imagecashletter.NewReader(resp.Body, opts...).Read()
   106  		require.NoError(t, err)
   107  
   108  		require.Contains(t, resp.Header().Get("Content-Type"), "application/octet-stream")
   109  		require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9")
   110  		require.NotEmpty(t, created)
   111  		require.Equal(t, "231380104", created.Header.ImmediateDestination)
   112  		require.Len(t, created.CashLetters, 2)
   113  		require.Equal(t, 400000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
   114  	})
   115  
   116  	t.Run("returns ASCII file", func(t *testing.T) {
   117  		rdr := getTestData(t, "icl-valid.json")
   118  
   119  		resp, apiErr := createFile(t, router, rdr, "application/json", "text/plain")
   120  		require.Empty(t, apiErr)
   121  
   122  		opts := []imagecashletter.ReaderOption{
   123  			imagecashletter.ReadVariableLineLengthOption(),
   124  		}
   125  		created, err := imagecashletter.NewReader(resp.Body, opts...).Read()
   126  		require.NoError(t, err)
   127  
   128  		require.Contains(t, resp.Header().Get("Content-Type"), "text/plain")
   129  		require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9")
   130  		require.NotEmpty(t, created)
   131  		require.Equal(t, "231380104", created.Header.ImmediateDestination)
   132  		require.Len(t, created.CashLetters, 2)
   133  		require.Equal(t, 400000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
   134  	})
   135  }
   136  
   137  func TestController_uploadEBCDICFile(t *testing.T) {
   138  	router := newRouter(t)
   139  
   140  	t.Run("uploads EBCDIC; returns ASCII", func(t *testing.T) {
   141  		rdr := getTestData(t, "valid-ebcdic.x937")
   142  
   143  		resp, apiErr := uploadFile(t, router, rdr, "application/octet-stream", "text/plain")
   144  		require.Empty(t, apiErr)
   145  
   146  		// now read back in without EBCDIC option
   147  		created, err := imagecashletter.NewReader(resp.Body, imagecashletter.ReadVariableLineLengthOption()).Read()
   148  		require.NoError(t, err)
   149  		require.Contains(t, resp.Header().Get("Content-Type"), "text/plain")
   150  		require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9")
   151  		require.NotEmpty(t, created)
   152  		require.Equal(t, "061000146", created.Header.ImmediateDestination)
   153  		require.Len(t, created.CashLetters, 1)
   154  		require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
   155  	})
   156  
   157  	t.Run("uploads EBCDIC; returns EBCDIC", func(t *testing.T) {
   158  		rdr := getTestData(t, "valid-ebcdic.x937")
   159  
   160  		resp, apiErr := uploadFile(t, router, rdr, "application/octet-stream", "application/octet-stream")
   161  		require.Empty(t, apiErr)
   162  
   163  		// now read back in with EBCDIC option
   164  		created, err := imagecashletter.NewReader(resp.Body,
   165  			imagecashletter.ReadVariableLineLengthOption(),
   166  			imagecashletter.ReadEbcdicEncodingOption(),
   167  		).Read()
   168  		require.NoError(t, err)
   169  		require.Contains(t, resp.Header().Get("Content-Type"), "application/octet-stream")
   170  		require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9")
   171  		require.NotEmpty(t, created)
   172  		require.Equal(t, "061000146", created.Header.ImmediateDestination)
   173  		require.Len(t, created.CashLetters, 1)
   174  		require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
   175  	})
   176  
   177  	t.Run("uploads EBCDIC; returns JSON", func(t *testing.T) {
   178  		rdr := getTestData(t, "valid-ebcdic.x937")
   179  
   180  		resp, apiErr := uploadFile(t, router, rdr, "application/octet-stream", "application/json")
   181  		require.Empty(t, apiErr)
   182  
   183  		// now read back in without EBCDIC option
   184  		var created imagecashletter.File
   185  		require.NoError(t, json.NewDecoder(resp.Body).Decode(&created))
   186  		require.Contains(t, resp.Header().Get("Content-Type"), "application/json")
   187  		require.NotEmpty(t, created.ID)
   188  		require.Equal(t, "061000146", created.Header.ImmediateDestination)
   189  		require.Len(t, created.CashLetters, 1)
   190  		require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
   191  	})
   192  }
   193  
   194  func TestController_uploadASCIIFile(t *testing.T) {
   195  	router := newRouter(t)
   196  
   197  	t.Run("uploads ASCII; returns ASCII", func(t *testing.T) {
   198  		rdr := getTestData(t, "valid-ascii.x937")
   199  
   200  		resp, apiErr := uploadFile(t, router, rdr, "text/plain", "text/plain")
   201  		require.Empty(t, apiErr)
   202  
   203  		// inspect headers
   204  		require.Contains(t, resp.Header().Get("Content-Type"), "text/plain")
   205  		require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9")
   206  		location := resp.Header().Get("Location")
   207  		require.True(t, strings.HasPrefix(location, "https://some.domain.io/files/"))
   208  		resourceID := strings.TrimPrefix(location, "https://some.domain.io/files/")
   209  		require.NotEmpty(t, resourceID)
   210  		_, err := uuid.Parse(resourceID)
   211  		require.NoError(t, err)
   212  
   213  		// now read back in without EBCDIC option
   214  		created, err := imagecashletter.NewReader(resp.Body, imagecashletter.ReadVariableLineLengthOption()).Read()
   215  		require.NoError(t, err)
   216  		require.NotEmpty(t, created)
   217  		require.Equal(t, "061000146", created.Header.ImmediateDestination)
   218  		require.Len(t, created.CashLetters, 1)
   219  		require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
   220  	})
   221  
   222  	t.Run("uploads EBCDIC; returns EBCDIC", func(t *testing.T) {
   223  		rdr := getTestData(t, "valid-ascii.x937")
   224  
   225  		resp, apiErr := uploadFile(t, router, rdr, "text/plain", "application/octet-stream")
   226  		require.Empty(t, apiErr)
   227  
   228  		// now read back in with EBCDIC option
   229  		created, err := imagecashletter.NewReader(resp.Body,
   230  			imagecashletter.ReadVariableLineLengthOption(),
   231  			imagecashletter.ReadEbcdicEncodingOption(),
   232  		).Read()
   233  		require.NoError(t, err)
   234  		require.Contains(t, resp.Header().Get("Content-Type"), "application/octet-stream")
   235  		require.Contains(t, resp.Header().Get("Content-Disposition"), ".x9")
   236  		require.NotEmpty(t, created)
   237  		require.Equal(t, "061000146", created.Header.ImmediateDestination)
   238  		require.Len(t, created.CashLetters, 1)
   239  		require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
   240  	})
   241  
   242  	t.Run("uploads EBCDIC; returns JSON", func(t *testing.T) {
   243  		rdr := getTestData(t, "valid-ascii.x937")
   244  
   245  		resp, apiErr := uploadFile(t, router, rdr, "text/plain", "application/json")
   246  		require.Empty(t, apiErr)
   247  
   248  		// now read back in without EBCDIC option
   249  		var created imagecashletter.File
   250  		require.NoError(t, json.NewDecoder(resp.Body).Decode(&created))
   251  		require.Contains(t, resp.Header().Get("Content-Type"), "application/json")
   252  		require.NotEmpty(t, created.ID)
   253  		require.Equal(t, "061000146", created.Header.ImmediateDestination)
   254  		require.Len(t, created.CashLetters, 1)
   255  		require.Equal(t, 10000, created.CashLetters[0].CashLetterControl.CashLetterTotalAmount)
   256  	})
   257  }
   258  
   259  func createFile(t *testing.T, router *mux.Router, body io.Reader, contentType string, accept string) (*httptest.ResponseRecorder, openapi.Error) {
   260  	req, err := http.NewRequest(http.MethodPost, "https://some.domain.io/v2/files", body)
   261  	require.NoError(t, err)
   262  
   263  	req.Header.Set("Content-Type", contentType)
   264  	req.Header.Set("Accept", accept)
   265  
   266  	w := httptest.NewRecorder()
   267  	router.ServeHTTP(w, req)
   268  	w.Flush()
   269  
   270  	var apiErr openapi.Error
   271  	if w.Code >= 400 && w.Code < 500 {
   272  		require.NoError(t, json.NewDecoder(w.Body).Decode(&apiErr))
   273  	}
   274  
   275  	return w, apiErr
   276  }
   277  
   278  func uploadFile(t *testing.T, router *mux.Router, file io.Reader, contentType, accept string) (*httptest.ResponseRecorder, openapi.Error) {
   279  	// create the multipart-form writer
   280  	body := new(bytes.Buffer)
   281  	mw := multipart.NewWriter(body)
   282  
   283  	// set up headers for the "file" portion
   284  	partHeaders := make(textproto.MIMEHeader)
   285  	partHeaders.Set("Content-Disposition", `form-data; name="file"; filename="cashletter.x9"`)
   286  	partHeaders.Set("Content-Type", contentType)
   287  
   288  	// create a new multipart-form section with the headers
   289  	part, err := mw.CreatePart(partHeaders)
   290  	require.NoError(t, err)
   291  
   292  	// copy the file contents to the form section
   293  	_, err = io.Copy(part, file)
   294  	require.NoError(t, err)
   295  	require.NoError(t, mw.Close())
   296  
   297  	req, err := http.NewRequest(http.MethodPost, "https://some.domain.io/v2/files", body)
   298  	require.NoError(t, err)
   299  	req.Header.Set("Content-Type", mw.FormDataContentType())
   300  	req.Header.Set("Accept", accept)
   301  
   302  	w := httptest.NewRecorder()
   303  	router.ServeHTTP(w, req)
   304  	w.Flush()
   305  
   306  	var apiErr openapi.Error
   307  	if w.Code >= 400 && w.Code < 500 {
   308  		require.NoError(t, json.NewDecoder(w.Body).Decode(&apiErr))
   309  	}
   310  
   311  	return w, apiErr
   312  }
   313  
   314  func newRouter(t *testing.T) *mux.Router {
   315  	c := v2.NewController(log.NewTestLogger(), storage.NewInMemoryRepo())
   316  	router := mux.NewRouter()
   317  	c.AddRoutes(router)
   318  
   319  	return router
   320  }
   321  
   322  func getTestData(t *testing.T, filename string) io.Reader {
   323  	fd, err := os.Open(filepath.Join("..", "..", "..", "test", "testdata", filename))
   324  	require.NoError(t, err)
   325  	t.Cleanup(func() {
   326  		fd.Close()
   327  	})
   328  
   329  	return fd
   330  }