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 }