From 2b09a6fd305ffb3ab147978b32364f478e65085c Mon Sep 17 00:00:00 2001 From: Daniel Tonks Date: Fri, 25 Oct 2024 09:01:37 -0600 Subject: [PATCH] build bundle control records when creating files (#381) --- file.go | 4 + go.mod | 2 - go.sum | 86 +--- internal/files/environment_test.go | 208 ++++++++ internal/files/files_test.go | 488 +++++++----------- internal/files/mock_repo_test.go | 39 ++ test/testdata/missing-bundle-control.json | 591 ++++++++++++++++++++++ 7 files changed, 1041 insertions(+), 377 deletions(-) create mode 100644 internal/files/environment_test.go create mode 100644 internal/files/mock_repo_test.go create mode 100644 test/testdata/missing-bundle-control.json diff --git a/file.go b/file.go index 1ca5fdf4..458cc1ac 100644 --- a/file.go +++ b/file.go @@ -253,6 +253,10 @@ func (f *File) Create() error { fileTotalAmount = fileTotalAmount + rd.ItemAmount } + + if err := b.build(); err != nil { + return fmt.Errorf("building bundle %s: %w", b.BundleHeader.BundleSequenceNumber, err) + } } } diff --git a/go.mod b/go.mod index 7532bb32..bd02cab9 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,6 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/kr/text v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -32,7 +31,6 @@ require ( github.com/rickar/cal/v2 v2.1.19 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8b69b46a..91eb5b9c 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,14 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= -github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= @@ -20,13 +17,8 @@ github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -34,63 +26,29 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/moov-io/base v0.48.5 h1:QaTyTo6eFFFV35R9l/GdePQN40IJti9knD5hqdWPnnM= -github.com/moov-io/base v0.48.5/go.mod h1:D5ZV9COV/qtCjTQuYpq7gGInCk64AhOQI6UY4kt4Rq8= -github.com/moov-io/base v0.49.2 h1:R9c9r6Bhlp94f5mgjsT/tbb81Rji0RD/z3ZTjdsuTDo= -github.com/moov-io/base v0.49.2/go.mod h1:ri6h0lEraRhho1S7Qe9uZy3mH77fkXU4Vnt3KjAVzaw= -github.com/moov-io/base v0.49.3 h1:35/hhHCAr1K4+Ars2jLk7TNQ5I+G/lzfPVmeXkCHAl4= -github.com/moov-io/base v0.49.3/go.mod h1:ri6h0lEraRhho1S7Qe9uZy3mH77fkXU4Vnt3KjAVzaw= -github.com/moov-io/base v0.49.4 h1:vN80/CR4qLa9D3VZAv9qGYHHG6joXdvKy5YmOJ/9cB4= -github.com/moov-io/base v0.49.4/go.mod h1:FeToCMJo5ynGkSsNvZJUNz753JCDhK04q+JLeaI/9NY= -github.com/moov-io/base v0.50.0 h1:fsZCV/OE7ovDCHWdkAs0bKAwUnFWeCrTPMb50NHJwv8= -github.com/moov-io/base v0.50.0/go.mod h1:jcQD47qZfnkxNtFm+195Qy8xtzjMCzd96vcJeyOY4SM= -github.com/moov-io/base v0.51.1 h1:Dwv7QKluvtHKBIrRcA1t+Sc6hOFTvmswgP5pGMK198E= -github.com/moov-io/base v0.51.1/go.mod h1:xTpQ584ny4VO9zNLmPn+rux6KRXtfQJgvphj4UfORJg= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/moov-io/base v0.53.0 h1:rpPWEbd/NTWApLzFq2AYbCZUlIv99OtvQcan7yArJVE= github.com/moov-io/base v0.53.0/go.mod h1:F2cdACBgJHNemPrOxvc88ezIqFL6ymErB4hOuPR+axg= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE= -github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U= -github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= -github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s= -github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rickar/cal/v2 v2.1.13 h1:FENBPXxDPyL1OWGf9ZdpWGcEiGoSjt0UZED8VOxvK0c= -github.com/rickar/cal/v2 v2.1.13/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= -github.com/rickar/cal/v2 v2.1.15 h1:bm6ll40ph9BLvY35Sy5KdT6GxN7UY56ZwCq/cJAxdew= -github.com/rickar/cal/v2 v2.1.15/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= -github.com/rickar/cal/v2 v2.1.16 h1:OCbac1I/JTsagh3oyzNbXJWIb5LfdtO79tK+ZAxGjto= -github.com/rickar/cal/v2 v2.1.16/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= -github.com/rickar/cal/v2 v2.1.17 h1:alox9Xxc0iokwVX1Qx5Zt8mvbaMYJ/YWx/d6v9aR8iw= -github.com/rickar/cal/v2 v2.1.17/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= github.com/rickar/cal/v2 v2.1.19 h1:rhWU/LGZnBwsEiJXMRAM9JooLH/8OHN8wWZ9qq13XJk= github.com/rickar/cal/v2 v2.1.19/go.mod h1:/fdlMcx7GjPlIBibMzOM9gMvDBsrK+mOtRXdTzUqV/A= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= @@ -104,16 +62,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= -golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg= -golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -125,12 +73,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -139,14 +81,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -154,18 +90,10 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/files/environment_test.go b/internal/files/environment_test.go new file mode 100644 index 00000000..7f2cb950 --- /dev/null +++ b/internal/files/environment_test.go @@ -0,0 +1,208 @@ +package files + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/gorilla/mux" + "github.com/moov-io/base/log" + "github.com/moov-io/imagecashletter" + "github.com/moov-io/imagecashletter/internal/storage" + "github.com/stretchr/testify/require" +) + +type testEnvironment struct { + router *mux.Router + repo storage.ICLFileRepository +} + +type envOptionFunc func(*testEnvironment) + +func withRepo(repo storage.ICLFileRepository) envOptionFunc { + return func(env *testEnvironment) { + env.repo = repo + } +} + +func newTestEnvironment(t *testing.T, opts ...envOptionFunc) *testEnvironment { + t.Helper() + + env := &testEnvironment{ + router: mux.NewRouter(), + repo: storage.NewInMemoryRepo(), + } + + for i := range opts { + opts[i](env) + } + + AppendRoutes(log.NewNopLogger(), env.router, env.repo) + + return env +} + +func (env *testEnvironment) listFiles(t *testing.T) (*httptest.ResponseRecorder, []*imagecashletter.File) { + t.Helper() + + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/files", nil) + env.router.ServeHTTP(w, req) + w.Flush() + + var files []*imagecashletter.File + if w.Code == http.StatusOK { + require.NoError(t, json.NewDecoder(w.Body).Decode(&files)) + } + + return w, files +} + +func (env *testEnvironment) getFile(t *testing.T, fileID string) (*httptest.ResponseRecorder, *imagecashletter.File) { + t.Helper() + + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/files/"+fileID, nil) + env.router.ServeHTTP(w, req) + w.Flush() + + var file *imagecashletter.File + if w.Code == http.StatusOK { + require.NoError(t, json.NewDecoder(w.Body).Decode(&file)) + } + + return w, file +} + +func (env *testEnvironment) getFileContents(t *testing.T, fileID string) (*httptest.ResponseRecorder, []byte) { + t.Helper() + + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/files/"+fileID+"/contents", nil) + env.router.ServeHTTP(w, req) + w.Flush() + + return w, w.Body.Bytes() +} + +func (env *testEnvironment) createFile(t *testing.T, contentType string, file io.Reader) (*httptest.ResponseRecorder, *imagecashletter.File) { + t.Helper() + + w := httptest.NewRecorder() + req := httptest.NewRequest("POST", "/files/create", file) + req.Header.Set("Accept", "application/json") + if contentType != "" { + req.Header.Set("Content-Type", contentType) + } + + env.router.ServeHTTP(w, req) + w.Flush() + + var resp *imagecashletter.File + if w.Code == http.StatusCreated { + require.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) + } + + return w, resp +} + +func (env *testEnvironment) validateFile(t *testing.T, fileID string) *httptest.ResponseRecorder { + t.Helper() + + w := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/files/"+fileID+"/validate", nil) + env.router.ServeHTTP(w, req) + w.Flush() + + return w +} + +func (env *testEnvironment) updateFileHeader(t *testing.T, fileID string, header imagecashletter.FileHeader) (*httptest.ResponseRecorder, *imagecashletter.File) { + t.Helper() + + w := httptest.NewRecorder() + bs, _ := json.Marshal(header) + req := httptest.NewRequest("POST", "/files/"+fileID, io.NopCloser(bytes.NewReader(bs))) + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json") + + env.router.ServeHTTP(w, req) + w.Flush() + + var file *imagecashletter.File + if w.Code == http.StatusCreated { + require.NoError(t, json.NewDecoder(w.Body).Decode(&file)) + } + + return w, file +} + +func (env *testEnvironment) addCashLetter(t *testing.T, fileID string, cashLetter imagecashletter.CashLetter) (*httptest.ResponseRecorder, *imagecashletter.File) { + t.Helper() + + w := httptest.NewRecorder() + bs, _ := json.Marshal(cashLetter) + req := httptest.NewRequest("POST", "/files/"+fileID+"/cashLetters", io.NopCloser(bytes.NewReader(bs))) + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json") + + env.router.ServeHTTP(w, req) + w.Flush() + + var resp *imagecashletter.File + if w.Code == http.StatusOK { + require.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) + } + + return w, resp +} + +func (env *testEnvironment) removeCashLetter(t *testing.T, fileID, cashLetterID string) *httptest.ResponseRecorder { + t.Helper() + + w := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/files/"+fileID+"/cashLetters/"+cashLetterID, nil) + env.router.ServeHTTP(w, req) + w.Flush() + + return w +} + +func (env *testEnvironment) deleteFile(t *testing.T, fileID string) *httptest.ResponseRecorder { + t.Helper() + + w := httptest.NewRecorder() + req := httptest.NewRequest("DELETE", "/files/"+fileID, nil) + env.router.ServeHTTP(w, req) + w.Flush() + + return w +} + +func openTestFile(t *testing.T, filename string) io.Reader { + t.Helper() + + fd, err := os.Open(filepath.Join("..", "..", "test", "testdata", filename)) + require.NoError(t, err) + t.Cleanup(func() { + fd.Close() + }) + return fd +} + +func parseTestFile(t *testing.T, filename string) *imagecashletter.File { + t.Helper() + + f, err := imagecashletter.NewReader( + openTestFile(t, filename), + imagecashletter.ReadVariableLineLengthOption(), + ).Read() + require.NoError(t, err) + + return &f +} diff --git a/internal/files/files_test.go b/internal/files/files_test.go index 8e464f2e..5bec8478 100644 --- a/internal/files/files_test.go +++ b/internal/files/files_test.go @@ -6,25 +6,17 @@ package files import ( "bytes" - "encoding/json" "errors" - "fmt" "net/http" "net/http/httptest" - "os" - "path/filepath" "strings" "testing" - "github.com/stretchr/testify/assert" - - "github.com/stretchr/testify/require" - + "github.com/gdamore/encoding" "github.com/moov-io/base" "github.com/moov-io/imagecashletter" - - "github.com/gorilla/mux" - "github.com/moov-io/base/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestFileId(t *testing.T) { @@ -48,34 +40,20 @@ func TestCashLetterId(t *testing.T) { } func TestFiles_getFiles(t *testing.T) { - req := httptest.NewRequest("GET", "/files", nil) repo := &testICLFileRepository{ file: &imagecashletter.File{ ID: base.ID(), }, } - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) - - t.Run("returns one file", func(t *testing.T) { - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() + env := newTestEnvironment(t, withRepo(repo)) - require.Equal(t, http.StatusOK, w.Code, w.Body) - var files []*imagecashletter.File - require.NoError(t, json.NewDecoder(w.Body).Decode(&files)) - require.Len(t, files, 1) - }) + resp, files := env.listFiles(t) + require.Equal(t, http.StatusOK, resp.Code, resp.Body) + require.Len(t, files, 1) - t.Run("repo error", func(t *testing.T) { - w := httptest.NewRecorder() - repo.err = errors.New("bad error") - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) - }) + repo.err = errors.New("bad error") + resp, _ = env.listFiles(t) + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) } func TestFiles_determineBufferSize(t *testing.T) { @@ -91,390 +69,308 @@ func TestFiles_determineBufferSize(t *testing.T) { } func TestFiles_createFile(t *testing.T) { - w := httptest.NewRecorder() - fd, _ := os.Open(filepath.Join("..", "..", "test", "testdata", "valid-ebcdic.x937")) - req := httptest.NewRequest("POST", "/files/create", fd) repo := &testICLFileRepository{} - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusCreated, w.Code, w.Body.String()) - - var resp imagecashletter.File - require.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) + env := newTestEnvironment(t, withRepo(repo)) - require.Equal(t, "Wave Money", resp.Header.ImmediateDestinationName) + resp, file := env.createFile(t, "", openTestFile(t, "valid-ebcdic.x937")) + require.Equal(t, http.StatusCreated, resp.Code, resp.Body) + require.Equal(t, "Wave Money", file.Header.ImmediateDestinationName) // error case repo.err = errors.New("bad error") + resp, _ = env.createFile(t, "", openTestFile(t, "valid-ebcdic.x937")) + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) +} - w = httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusBadRequest, w.Code, w.Body.String()) +func TestFiles_create_missingBundleControl(t *testing.T) { + env := newTestEnvironment(t) + + wantBundleControl := imagecashletter.NewBundleControl() + wantBundleControl.BundleItemsCount = 4 + wantBundleControl.BundleTotalAmount = 400000 + wantBundleControl.MICRValidTotalAmount = 400000 + wantBundleControl.BundleImagesCount = 4 + wantBundleControl.CreditTotalIndicator = 0 + + resp, file := env.createFile(t, "application/json", openTestFile(t, "missing-bundle-control.json")) + require.Equal(t, http.StatusCreated, resp.Code, resp.Body) + require.Len(t, file.CashLetters, 1) + require.Equal(t, "118507", file.CashLetters[0].CashLetterHeader.CashLetterID) + require.Len(t, file.CashLetters[0].Bundles, 1) + require.Len(t, file.CashLetters[0].Bundles[0].Checks, 4) + require.Equal(t, *wantBundleControl, *file.CashLetters[0].Bundles[0].BundleControl) + + // GET the file + resp, rawFile := env.getFileContents(t, file.ID) + require.Equal(t, http.StatusOK, resp.Code, resp.Body) + + // POST the fetched file + resp, newFile := env.createFile(t, "application/octet-stream", bytes.NewReader(rawFile)) + require.Equal(t, http.StatusCreated, resp.Code, resp.Body) + require.Len(t, newFile.CashLetters, 1) + require.Equal(t, "118507", newFile.CashLetters[0].CashLetterHeader.CashLetterID) + require.Len(t, newFile.CashLetters[0].Bundles, 1) + require.Len(t, newFile.CashLetters[0].Bundles[0].Checks, 4) + require.Equal(t, *wantBundleControl, *newFile.CashLetters[0].Bundles[0].BundleControl) } func TestFiles_createFileJSON(t *testing.T) { - w := httptest.NewRecorder() - fd, _ := os.Open(filepath.Join("..", "..", "test", "testdata", "icl-valid.json")) - req := httptest.NewRequest("POST", "/files/create", fd) - req.Header.Set("Content-Type", "application/json") - repo := &testICLFileRepository{} - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) - router.ServeHTTP(w, req) - w.Flush() + env := newTestEnvironment(t) - require.Equal(t, http.StatusCreated, w.Code, w.Body) - var resp imagecashletter.File - require.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) - assert.Equal(t, "US", resp.Header.CountryCode) + resp, file := env.createFile(t, "application/json", openTestFile(t, "icl-valid.json")) + require.Equal(t, http.StatusCreated, resp.Code, resp.Body) + require.Equal(t, "US", file.Header.CountryCode) // error case - w = httptest.NewRecorder() - req = httptest.NewRequest("POST", "/files/create", strings.NewReader("{invalid-json")) - req.Header.Set("content-type", "application/json") - - router.ServeHTTP(w, req) - w.Flush() + resp, _ = env.createFile(t, "application/json", strings.NewReader("{invalid-json")) - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) } func TestFiles_getFile(t *testing.T) { - repo := &testICLFileRepository{} - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) - req := httptest.NewRequest("GET", "/files/foo", nil) + fileID := base.ID() + repo := &testICLFileRepository{ + file: &imagecashletter.File{ + ID: fileID, + }, + } + env := newTestEnvironment(t, withRepo(repo)) t.Run("file not found", func(t *testing.T) { - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusNotFound, w.Code, w.Body) + resp, _ := env.getFile(t, "foo") + require.Equal(t, http.StatusNotFound, resp.Code, resp.Body) }) t.Run("successful request", func(t *testing.T) { - w := httptest.NewRecorder() - repo.file = &imagecashletter.File{ - ID: base.ID(), - } - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusOK, w.Code, w.Body) - var file imagecashletter.File - require.NoError(t, json.NewDecoder(w.Body).Decode(&file)) - assert.NotEmpty(t, file.ID) + resp, file := env.getFile(t, fileID) + require.Equal(t, http.StatusOK, resp.Code, resp.Body) + require.Equal(t, fileID, file.ID) }) t.Run("repo error", func(t *testing.T) { - w := httptest.NewRecorder() + defer func() { + repo.err = nil + }() repo.err = errors.New("bad error") - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) + resp, _ := env.getFile(t, fileID) + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) }) } func TestFiles_updateFileHeader(t *testing.T) { - repo := &testICLFileRepository{} - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) - f := readFile(t, "BNK20180905121042882-A.icl") + env := newTestEnvironment(t) + + f := parseTestFile(t, "BNK20180905121042882-A.icl") f.ID = base.ID() + f.Header.UserField = "before" + require.NoError(t, env.repo.SaveFile(f)) t.Run("file not found", func(t *testing.T) { - var buf bytes.Buffer - require.NoError(t, json.NewEncoder(&buf).Encode(f.Header)) - w := httptest.NewRecorder() - req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s", f.ID), &buf) - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusNotFound, w.Code, w.Body) + resp, _ := env.updateFileHeader(t, "foo", f.Header) + + require.Equal(t, http.StatusNotFound, resp.Code, resp.Body) }) t.Run("successful request", func(t *testing.T) { - var buf bytes.Buffer - require.NoError(t, json.NewEncoder(&buf).Encode(f.Header)) - w := httptest.NewRecorder() - req := httptest.NewRequest("POST", fmt.Sprintf("/files/%s", f.ID), &buf) - repo.file = &imagecashletter.File{ - ID: f.ID, // create a file without FileHeader so it's updated - } - router.ServeHTTP(w, req) - w.Flush() + newHeader := f.Header + newHeader.UserField = "after" - require.Equal(t, http.StatusCreated, w.Code, w.Body) - assert.Equal(t, repo.file.Header.CountryCode, f.Header.CountryCode) + resp, file := env.updateFileHeader(t, f.ID, newHeader) + require.Equal(t, http.StatusCreated, resp.Code, resp.Body) + assert.Equal(t, "after", file.Header.UserField) + + // check the repo as well + updated, err := env.repo.GetFile(f.ID) + require.NoError(t, err) + assert.Equal(t, "after", updated.Header.UserField) }) } func TestFiles_deleteFile(t *testing.T) { - req := httptest.NewRequest("DELETE", "/files/foo", nil) - repo := &testICLFileRepository{} - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) + env := newTestEnvironment(t) + + f1 := base.ID() + f2 := base.ID() + require.NoError(t, env.repo.SaveFile(&imagecashletter.File{ID: f1})) + require.NoError(t, env.repo.SaveFile(&imagecashletter.File{ID: f2})) t.Run("file not found", func(t *testing.T) { - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() + resp := env.deleteFile(t, "foo") - require.Equal(t, http.StatusNotFound, w.Code, w.Body) + require.Equal(t, http.StatusNotFound, resp.Code, resp.Body) }) t.Run("successful request", func(t *testing.T) { - repo.file = &imagecashletter.File{} - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() + resp := env.deleteFile(t, f1) - require.Equal(t, http.StatusOK, w.Code, w.Body) + require.Equal(t, http.StatusOK, resp.Code, resp.Body) + + listResp, files := env.listFiles(t) + require.Equal(t, http.StatusOK, listResp.Code, listResp.Body) + require.Len(t, files, 1) + require.Equal(t, f2, files[0].ID) }) t.Run("repo error", func(t *testing.T) { - repo.err = errors.New("bad error") - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() + repo := &testICLFileRepository{ + err: errors.New("bad error"), + } + mockEnv := newTestEnvironment(t, withRepo(repo)) + resp := mockEnv.deleteFile(t, f1) - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) }) } func TestFiles_getFileContents(t *testing.T) { - req := httptest.NewRequest("GET", "/files/foo/contents", nil) - router := mux.NewRouter() - repo := &testICLFileRepository{} - AppendRoutes(log.NewNopLogger(), router, repo) + env := newTestEnvironment(t) + f := parseTestFile(t, "BNK20180905121042882-A.icl") + f.ID = base.ID() + f.Header.UserField = "user" + require.NoError(t, env.repo.SaveFile(f)) t.Run("file not found", func(t *testing.T) { - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() + resp, _ := env.getFileContents(t, "foo") - require.Equal(t, http.StatusNotFound, w.Code, w.Body) + require.Equal(t, http.StatusNotFound, resp.Code, resp.Body) }) t.Run("successful request", func(t *testing.T) { - w := httptest.NewRecorder() - f := readFile(t, "BNK20180905121042882-A.icl") - repo.file = f - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusOK, w.Code, w.Body) - assert.Equal(t, "text/plain", w.Header().Get("Content-Type"), "unexpected content type") + resp, file := env.getFileContents(t, f.ID) + + require.Equal(t, http.StatusOK, resp.Code, resp.Body) + require.Equal(t, "text/plain", resp.Header().Get("Content-Type"), "unexpected content type") + wantUserField, _ := encoding.EBCDIC.NewEncoder().String("user") + require.Contains(t, string(file), wantUserField) }) t.Run("repo error", func(t *testing.T) { - repo.err = errors.New("bad error") - - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() + repo := &testICLFileRepository{ + err: errors.New("bad error"), + } + mockEnv := newTestEnvironment(t, withRepo(repo)) + resp, _ := mockEnv.getFileContents(t, "foo") - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) }) - } func TestFiles_validateFile(t *testing.T) { - req := httptest.NewRequest("GET", "/files/foo/validate", nil) - repo := &testICLFileRepository{} - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) - f := readFile(t, "BNK20180905121042882-A.icl") + env := newTestEnvironment(t) + f := parseTestFile(t, "BNK20180905121042882-A.icl") + f.ID = base.ID() + require.NoError(t, env.repo.SaveFile(f)) t.Run("file not found", func(t *testing.T) { - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() + resp := env.validateFile(t, "foo") - require.Equal(t, http.StatusNotFound, w.Code, w.Body) + require.Equal(t, http.StatusNotFound, resp.Code, resp.Body) }) t.Run("valid file", func(t *testing.T) { - w := httptest.NewRecorder() - repo.file = f - router.ServeHTTP(w, req) - w.Flush() + resp := env.validateFile(t, f.ID) - require.Equal(t, http.StatusOK, w.Code, w.Body) - assert.Contains(t, w.Body.String(), `"{\"error\": null}"`) + require.Equal(t, http.StatusOK, resp.Code, resp.Body) + assert.Contains(t, resp.Body.String(), `"{\"error\": null}"`) }) t.Run("invalid file", func(t *testing.T) { - w := httptest.NewRecorder() - // make the file invalid - repo.file.Header = imagecashletter.NewFileHeader() - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) + invalidFile := *f + invalidFile.ID = base.ID() + invalidFile.Header = imagecashletter.NewFileHeader() + require.NoError(t, env.repo.SaveFile(&invalidFile)) + resp := env.validateFile(t, invalidFile.ID) + + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) + require.Contains(t, resp.Body.String(), "TestFileIndicator is a mandatory field") }) t.Run("repo error", func(t *testing.T) { - w := httptest.NewRecorder() - repo.err = errors.New("bad error") - router.ServeHTTP(w, req) - w.Flush() + repo := &testICLFileRepository{ + err: errors.New("bad error"), + } + mockEnv := newTestEnvironment(t, withRepo(repo)) + resp := mockEnv.validateFile(t, "foo") - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) }) } func TestFiles_addCashLetterToFile(t *testing.T) { - repo := &testICLFileRepository{} - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) - f := readFile(t, "BNK20180905121042882-A.icl") + env := newTestEnvironment(t) + + f := parseTestFile(t, "BNK20180905121042882-A.icl") cashLetter := f.CashLetters[0] f.CashLetters = nil + f.ID = base.ID() + require.NoError(t, env.repo.SaveFile(f)) t.Run("file not found", func(t *testing.T) { - var buf bytes.Buffer - require.NoError(t, json.NewEncoder(&buf).Encode(cashLetter)) - req := httptest.NewRequest("POST", "/files/foo/cashLetters", &buf) - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusNotFound, w.Code, w.Body) + resp, _ := env.addCashLetter(t, "foo", cashLetter) + + require.Equal(t, http.StatusNotFound, resp.Code, resp.Body) }) t.Run("successful request", func(t *testing.T) { - var buf bytes.Buffer - require.NoError(t, json.NewEncoder(&buf).Encode(cashLetter)) - req := httptest.NewRequest("POST", "/files/foo/cashLetters", &buf) - w := httptest.NewRecorder() - repo.file = f - router.ServeHTTP(w, req) - w.Flush() - - require.Equal(t, http.StatusOK, w.Code, w.Body) - var out imagecashletter.File - require.NoError(t, json.NewDecoder(w.Body).Decode(&out)) - assert.Len(t, out.CashLetters, 1, "expected one cashLetter") + resp, file := env.addCashLetter(t, f.ID, cashLetter) + + require.Equal(t, http.StatusOK, resp.Code, resp.Body) + assert.Len(t, file.CashLetters, 1, "expected one cashLetter") }) t.Run("repo error", func(t *testing.T) { - var buf bytes.Buffer - require.NoError(t, json.NewEncoder(&buf).Encode(cashLetter)) - req := httptest.NewRequest("POST", "/files/foo/cashLetters", &buf) - w := httptest.NewRecorder() - repo.file = nil - repo.err = errors.New("bad error") - router.ServeHTTP(w, req) - w.Flush() + repo := &testICLFileRepository{ + err: errors.New("bad error"), + } + mockEnv := newTestEnvironment(t, withRepo(repo)) + resp, _ := mockEnv.addCashLetter(t, "foo", cashLetter) - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) }) } func TestFiles_removeCashLetterFromFile(t *testing.T) { - repo := &testICLFileRepository{} - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) - f := readFile(t, "BNK20180905121042882-A.icl") + env := newTestEnvironment(t) + + f := parseTestFile(t, "BNK20180905121042882-A.icl") + f.ID = base.ID() cashLetterId := base.ID() f.CashLetters[0].ID = cashLetterId - req := httptest.NewRequest("DELETE", fmt.Sprintf("/files/foo/cashLetters/%s", cashLetterId), nil) + require.NoError(t, env.repo.SaveFile(f)) t.Run("file not found", func(t *testing.T) { - w := httptest.NewRecorder() - router.ServeHTTP(w, req) - w.Flush() + resp := env.removeCashLetter(t, "foo", cashLetterId) - require.Equal(t, http.StatusNotFound, w.Code, w.Body) + require.Equal(t, http.StatusNotFound, resp.Code, resp.Body) }) t.Run("successful request", func(t *testing.T) { - w := httptest.NewRecorder() - repo.file = f - router.ServeHTTP(w, req) - w.Flush() + resp := env.removeCashLetter(t, f.ID, cashLetterId) - require.Equal(t, http.StatusOK, w.Code, w.Body) + require.Equal(t, http.StatusOK, resp.Code, resp.Body) + file, err := env.repo.GetFile(f.ID) + require.NoError(t, err) + require.Len(t, file.CashLetters, 1) + require.NotEqual(t, cashLetterId, file.CashLetters[0].ID) }) t.Run("repo error", func(t *testing.T) { - w := httptest.NewRecorder() - repo.file = nil - repo.err = errors.New("bad error") - router.ServeHTTP(w, req) - w.Flush() + repo := &testICLFileRepository{ + err: errors.New("bad error"), + } + mockEnv := newTestEnvironment(t, withRepo(repo)) + resp := mockEnv.removeCashLetter(t, f.ID, cashLetterId) - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) }) } func TestFiles_createFile_Issue228(t *testing.T) { - repo := &testICLFileRepository{} - router := mux.NewRouter() - AppendRoutes(log.NewNopLogger(), router, repo) - - w := httptest.NewRecorder() - fd, _ := os.Open(filepath.Join("..", "..", "test", "testdata", "issue228.json")) - req := httptest.NewRequest("POST", "/files/create", fd) - req.Header.Set("Content-Type", "application/json") - - router.ServeHTTP(w, req) - - require.Equal(t, http.StatusBadRequest, w.Code, w.Body) - type apiError struct { - Error string `json:"error"` - } - var wantErr apiError - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &wantErr)) - require.Contains(t, wantErr.Error, "CashLetterControl record is mandatory") -} - -func readFile(t *testing.T, filename string) *imagecashletter.File { - t.Helper() - - fd, err := os.Open(filepath.Join("..", "..", "test", "testdata", filename)) - require.NoError(t, err) - f, err := imagecashletter.NewReader(fd, imagecashletter.ReadVariableLineLengthOption()).Read() - require.NoError(t, err) - return &f -} + env := newTestEnvironment(t) -type testICLFileRepository struct { - err error - - file *imagecashletter.File -} - -func (r *testICLFileRepository) GetFiles() ([]*imagecashletter.File, error) { - if r.err != nil { - return nil, r.err - } - return []*imagecashletter.File{r.file}, nil -} - -func (r *testICLFileRepository) GetFile(fileId string) (*imagecashletter.File, error) { - if r.err != nil { - return nil, r.err - } - return r.file, nil -} - -func (r *testICLFileRepository) SaveFile(file *imagecashletter.File) error { - if r.err == nil { // only persist if we're not error'ing - r.file = file - } - return r.err -} + resp, _ := env.createFile(t, "application/json", openTestFile(t, "issue228.json")) -func (r *testICLFileRepository) DeleteFile(fileId string) error { - return r.err + require.Equal(t, http.StatusBadRequest, resp.Code, resp.Body) + require.Contains(t, resp.Body.String(), "CashLetterControl record is mandatory") } diff --git a/internal/files/mock_repo_test.go b/internal/files/mock_repo_test.go new file mode 100644 index 00000000..15d8ce74 --- /dev/null +++ b/internal/files/mock_repo_test.go @@ -0,0 +1,39 @@ +package files + +import "github.com/moov-io/imagecashletter" + +type testICLFileRepository struct { + err error + + file *imagecashletter.File +} + +func (r *testICLFileRepository) GetFiles() ([]*imagecashletter.File, error) { + if r.err != nil { + return nil, r.err + } + return []*imagecashletter.File{r.file}, nil +} + +func (r *testICLFileRepository) GetFile(fileId string) (*imagecashletter.File, error) { + if r.err != nil { + return nil, r.err + } + + if r.file != nil && r.file.ID == fileId { + return r.file, nil + } + + return nil, nil +} + +func (r *testICLFileRepository) SaveFile(file *imagecashletter.File) error { + if r.err == nil { // only persist if we're not error'ing + r.file = file + } + return r.err +} + +func (r *testICLFileRepository) DeleteFile(fileId string) error { + return r.err +} diff --git a/test/testdata/missing-bundle-control.json b/test/testdata/missing-bundle-control.json new file mode 100644 index 00000000..d0d073a1 --- /dev/null +++ b/test/testdata/missing-bundle-control.json @@ -0,0 +1,591 @@ +{ + "fileHeader": { + "standardLevel": "03", + "testIndicator": "P", + "resendIndicator": "N", + "immediateOrigin": "000000123", + "immediateOriginName": "Immediate Origin", + "immediateDestination": "000000123", + "immediateDestinationName": "Immediate Destination", + "fileCreationDate": "2024-10-23T00:00:00Z", + "fileCreationTime": "2024-10-23T00:00:00Z" + }, + "cashLetters": [ + { + "cashLetterHeader": { + "collectionTypeIndicator": "01", + "destinationRoutingNumber": "000000123", + "eceInstitutionRoutingNumber": "000000123", + "recordTypeIndicator": "I", + "DocumentationTypeIndicator": "G", + "originatorContactName": "", + "originatorContactPhoneNumber": "", + "fedWorkType": "C", + "cashLetterBusinessDate": "2024-10-23T00:00:00Z", + "cashLetterCreationDate": "2024-10-23T00:00:00Z", + "cashLetterCreationTime": "0000-01-01T13:42:17Z", + "cashLetterID": "118507" + }, + "bundles": [ + { + "bundleHeader": { + "collectionTypeIndicator": "01", + "destinationRoutingNumber": "000000123", + "eceInstitutionRoutingNumber": "000000123", + "cycleNumber": "01", + "BundleSequenceNumber": "0001", + "bundleBusinessDate": "2024-10-23T00:00:00Z", + "bundleCreationDate": "2024-10-23T00:00:00Z", + "bundleCreationTime": "0000-01-01T13:42:17Z" + }, + "checks": [ + { + "id": "", + "auxiliaryOnUs": "123456789", + "externalProcessingCode": "", + "payorBankRoutingNumber": "03130001", + "payorBankCheckDigit": "2", + "onUs": "5558881", + "itemAmount": 100000, + "eceInstitutionItemSequenceNumber": "1", + "documentationTypeIndicator": "G", + "returnAcceptanceIndicator": "D", + "micrValidIndicator": 1, + "bofdIndicator": "Y", + "addendumCount": 3, + "correctionIndicator": 0, + "archiveTypeIndicator": "B", + "checkDetailAddendumA": [ + { + "id": "", + "recordNumber": 1, + "returnLocationRoutingNumber": "121042882", + "bofdEndorsementDate": "2018-10-03T00:00:00Z", + "bofdItemSequenceNumber": "1", + "bofdAccountNumber": "123456789", + "bofdBranchCode": "01", + "payeeName": "Test Payee", + "truncationIndicator": "Y", + "bofdConversionIndicator": "1", + "bofdCorrectionIndicator": 0, + "userField": "" + } + ], + "checkDetailAddendumB": [ + { + "id": "", + "imageReferenceKeyIndicator": 1, + "microfilmArchiveSequenceNumber": "1A", + "imageReferenceKeyLength": "0034", + "imageReferenceKey": "0", + "description": "CD Addendum B", + "userField": "" + } + ], + "checkDetailAddendumC": [ + { + "id": "", + "recordNumber": 2, + "endorsingBankRoutingNumber": "121042882", + "bofdEndorsementBusinessDate": "2018-10-03T00:00:00Z", + "endorsingBankItemSequenceNumber": "1", + "truncationIndicator": "Y", + "endorsingBankConversionIndicator": "1", + "endorsingBankCorrectionIndicator": 0, + "returnReason": "A", + "userField": "", + "endorsingBankIdentifier": 0 + } + ], + "imageViewDetail": [ + { + "id": "", + "imageIndicator": 1, + "imageCreatorRoutingNumber": "031300012", + "imageCreatorDate": "2018-10-03T00:00:00Z", + "imageViewFormatIndicator": "00", + "imageViewCompressionAlgorithm": "00", + "imageViewDataSize": "0000000", + "viewSideIndicator": 0, + "viewDescriptor": "00", + "digitalSignatureIndicator": 0, + "digitalSignatureMethod": "00", + "securityKeySize": 0, + "protectedDataStart": 0, + "protectedDataLength": 0, + "imageRecreateIndicator": 0, + "userField": "", + "overrideIndicator": "0" + } + ], + "imageViewData": [ + { + "id": "", + "eceInstitutionRoutingNumber": "121042882", + "bundleBusinessDate": "2018-10-03T00:00:00Z", + "cycleNumber": "1", + "eceInstitutionItemSequenceNumber": "1", + "securityOriginatorName": "Sec Orig Name", + "securityAuthenticatorName": "Sec Auth Name", + "securityKeyName": "SECURE", + "clippingOrigin": 0, + "clippingCoordinateH1": "", + "clippingCoordinateH2": "", + "clippingCoordinateV1": "", + "clippingCoordinateV2": "", + "lengthImageReferenceKey": "0000", + "imageReferenceKey": "", + "lengthDigitalSignature": "0", + "digitalSignature": "", + "lengthImageData": "0000001", + "imageData": "IA==" + } + ], + "imageViewAnalysis": [ + { + "id": "", + "globalImageQuality": 2, + "globalImageUsability": 2, + "imagingBankSpecificTest": 0, + "partialImage": 2, + "excessiveImageSkew": 2, + "piggybackImage": 2, + "tooLightOrTooDark": 2, + "streaksAndOrBands": 2, + "belowMinimumImageSize": 2, + "exceedsMaximumImageSize": 2, + "imageEnabledPOD": 1, + "sourceDocumentBad": 0, + "dateUsability": 2, + "payeeUsability": 2, + "convenienceAmountUsability": 2, + "amountInWordsUsability": 2, + "signatureUsability": 2, + "payorNameAddressUsability": 2, + "micrLineUsability": 2, + "memoLineUsability": 2, + "payorBankNameAddressUsability": 2, + "payeeEndorsementUsability": 2, + "bofdEndorsementUsability": 2, + "transitEndorsementUsability": 2, + "userField": "" + } + ] + }, + { + "id": "", + "auxiliaryOnUs": "123456789", + "externalProcessingCode": "", + "payorBankRoutingNumber": "03130001", + "payorBankCheckDigit": "2", + "onUs": "5558881", + "itemAmount": 100000, + "eceInstitutionItemSequenceNumber": "2", + "documentationTypeIndicator": "G", + "returnAcceptanceIndicator": "D", + "micrValidIndicator": 1, + "bofdIndicator": "Y", + "addendumCount": 3, + "correctionIndicator": 0, + "archiveTypeIndicator": "B", + "checkDetailAddendumA": [ + { + "id": "", + "recordNumber": 2, + "returnLocationRoutingNumber": "121042882", + "bofdEndorsementDate": "2018-10-03T00:00:00Z", + "bofdItemSequenceNumber": "2", + "bofdAccountNumber": "123456789", + "bofdBranchCode": "01", + "payeeName": "Test Payee", + "truncationIndicator": "Y", + "bofdConversionIndicator": "1", + "bofdCorrectionIndicator": 0, + "userField": "" + } + ], + "checkDetailAddendumB": [ + { + "id": "", + "imageReferenceKeyIndicator": 1, + "microfilmArchiveSequenceNumber": "1A", + "imageReferenceKeyLength": "0034", + "imageReferenceKey": "0", + "description": "CD Addendum B", + "userField": "" + } + ], + "checkDetailAddendumC": [ + { + "id": "", + "recordNumber": 3, + "endorsingBankRoutingNumber": "121042882", + "bofdEndorsementBusinessDate": "2018-10-03T00:00:00Z", + "endorsingBankItemSequenceNumber": "2", + "truncationIndicator": "Y", + "endorsingBankConversionIndicator": "1", + "endorsingBankCorrectionIndicator": 0, + "returnReason": "A", + "userField": "", + "endorsingBankIdentifier": 0 + } + ], + "imageViewDetail": [ + { + "id": "", + "imageIndicator": 1, + "imageCreatorRoutingNumber": "031300012", + "imageCreatorDate": "2018-10-03T00:00:00Z", + "imageViewFormatIndicator": "00", + "imageViewCompressionAlgorithm": "00", + "imageViewDataSize": "0000000", + "viewSideIndicator": 0, + "viewDescriptor": "00", + "digitalSignatureIndicator": 0, + "digitalSignatureMethod": "00", + "securityKeySize": 0, + "protectedDataStart": 0, + "protectedDataLength": 0, + "imageRecreateIndicator": 0, + "userField": "", + "overrideIndicator": "0" + } + ], + "imageViewData": [ + { + "id": "", + "eceInstitutionRoutingNumber": "121042882", + "bundleBusinessDate": "2018-10-03T00:00:00Z", + "cycleNumber": "1", + "eceInstitutionItemSequenceNumber": "1", + "securityOriginatorName": "Sec Orig Name", + "securityAuthenticatorName": "Sec Auth Name", + "securityKeyName": "SECURE", + "clippingOrigin": 0, + "clippingCoordinateH1": "", + "clippingCoordinateH2": "", + "clippingCoordinateV1": "", + "clippingCoordinateV2": "", + "lengthImageReferenceKey": "0000", + "imageReferenceKey": "", + "lengthDigitalSignature": "0", + "digitalSignature": "", + "lengthImageData": "0000001", + "imageData": "IA==" + } + ], + "imageViewAnalysis": [ + { + "id": "", + "globalImageQuality": 2, + "globalImageUsability": 2, + "imagingBankSpecificTest": 0, + "partialImage": 2, + "excessiveImageSkew": 2, + "piggybackImage": 2, + "tooLightOrTooDark": 2, + "streaksAndOrBands": 2, + "belowMinimumImageSize": 2, + "exceedsMaximumImageSize": 2, + "imageEnabledPOD": 1, + "sourceDocumentBad": 0, + "dateUsability": 2, + "payeeUsability": 2, + "convenienceAmountUsability": 2, + "amountInWordsUsability": 2, + "signatureUsability": 2, + "payorNameAddressUsability": 2, + "micrLineUsability": 2, + "memoLineUsability": 2, + "payorBankNameAddressUsability": 2, + "payeeEndorsementUsability": 2, + "bofdEndorsementUsability": 2, + "transitEndorsementUsability": 2, + "userField": "" + } + ] + }, + { + "id": "", + "auxiliaryOnUs": "123456789", + "externalProcessingCode": "", + "payorBankRoutingNumber": "03130001", + "payorBankCheckDigit": "2", + "onUs": "5558881", + "itemAmount": 100000, + "eceInstitutionItemSequenceNumber": "1", + "documentationTypeIndicator": "G", + "returnAcceptanceIndicator": "D", + "micrValidIndicator": 1, + "bofdIndicator": "Y", + "addendumCount": 3, + "correctionIndicator": 0, + "archiveTypeIndicator": "B", + "checkDetailAddendumA": [ + { + "id": "", + "recordNumber": 1, + "returnLocationRoutingNumber": "121042882", + "bofdEndorsementDate": "2018-10-03T00:00:00Z", + "bofdItemSequenceNumber": "1", + "bofdAccountNumber": "123456789", + "bofdBranchCode": "01", + "payeeName": "Test Payee", + "truncationIndicator": "Y", + "bofdConversionIndicator": "1", + "bofdCorrectionIndicator": 0, + "userField": "" + } + ], + "checkDetailAddendumB": [ + { + "id": "", + "imageReferenceKeyIndicator": 1, + "microfilmArchiveSequenceNumber": "1A", + "imageReferenceKeyLength": "0034", + "imageReferenceKey": "0", + "description": "CD Addendum B", + "userField": "" + } + ], + "checkDetailAddendumC": [ + { + "id": "", + "recordNumber": 2, + "endorsingBankRoutingNumber": "121042882", + "bofdEndorsementBusinessDate": "2018-10-03T00:00:00Z", + "endorsingBankItemSequenceNumber": "1", + "truncationIndicator": "Y", + "endorsingBankConversionIndicator": "1", + "endorsingBankCorrectionIndicator": 0, + "returnReason": "A", + "userField": "", + "endorsingBankIdentifier": 0 + } + ], + "imageViewDetail": [ + { + "id": "", + "imageIndicator": 1, + "imageCreatorRoutingNumber": "031300012", + "imageCreatorDate": "2018-10-03T00:00:00Z", + "imageViewFormatIndicator": "00", + "imageViewCompressionAlgorithm": "00", + "imageViewDataSize": "0000000", + "viewSideIndicator": 0, + "viewDescriptor": "00", + "digitalSignatureIndicator": 0, + "digitalSignatureMethod": "00", + "securityKeySize": 0, + "protectedDataStart": 0, + "protectedDataLength": 0, + "imageRecreateIndicator": 0, + "userField": "", + "overrideIndicator": "0" + } + ], + "imageViewData": [ + { + "id": "", + "eceInstitutionRoutingNumber": "121042882", + "bundleBusinessDate": "2018-10-03T00:00:00Z", + "cycleNumber": "1", + "eceInstitutionItemSequenceNumber": "1", + "securityOriginatorName": "Sec Orig Name", + "securityAuthenticatorName": "Sec Auth Name", + "securityKeyName": "SECURE", + "clippingOrigin": 0, + "clippingCoordinateH1": "", + "clippingCoordinateH2": "", + "clippingCoordinateV1": "", + "clippingCoordinateV2": "", + "lengthImageReferenceKey": "0000", + "imageReferenceKey": "", + "lengthDigitalSignature": "0", + "digitalSignature": "", + "lengthImageData": "0000001", + "imageData": "IA==" + } + ], + "imageViewAnalysis": [ + { + "id": "", + "globalImageQuality": 2, + "globalImageUsability": 2, + "imagingBankSpecificTest": 0, + "partialImage": 2, + "excessiveImageSkew": 2, + "piggybackImage": 2, + "tooLightOrTooDark": 2, + "streaksAndOrBands": 2, + "belowMinimumImageSize": 2, + "exceedsMaximumImageSize": 2, + "imageEnabledPOD": 1, + "sourceDocumentBad": 0, + "dateUsability": 2, + "payeeUsability": 2, + "convenienceAmountUsability": 2, + "amountInWordsUsability": 2, + "signatureUsability": 2, + "payorNameAddressUsability": 2, + "micrLineUsability": 2, + "memoLineUsability": 2, + "payorBankNameAddressUsability": 2, + "payeeEndorsementUsability": 2, + "bofdEndorsementUsability": 2, + "transitEndorsementUsability": 2, + "userField": "" + } + ] + }, + { + "id": "", + "auxiliaryOnUs": "123456789", + "externalProcessingCode": "", + "payorBankRoutingNumber": "03130001", + "payorBankCheckDigit": "2", + "onUs": "5558881", + "itemAmount": 100000, + "eceInstitutionItemSequenceNumber": "2", + "documentationTypeIndicator": "G", + "returnAcceptanceIndicator": "D", + "micrValidIndicator": 1, + "bofdIndicator": "Y", + "addendumCount": 3, + "correctionIndicator": 0, + "archiveTypeIndicator": "B", + "checkDetailAddendumA": [ + { + "id": "", + "recordNumber": 2, + "returnLocationRoutingNumber": "121042882", + "bofdEndorsementDate": "2018-10-03T00:00:00Z", + "bofdItemSequenceNumber": "2", + "bofdAccountNumber": "123456789", + "bofdBranchCode": "01", + "payeeName": "Test Payee", + "truncationIndicator": "Y", + "bofdConversionIndicator": "1", + "bofdCorrectionIndicator": 0, + "userField": "" + } + ], + "checkDetailAddendumB": [ + { + "id": "", + "imageReferenceKeyIndicator": 1, + "microfilmArchiveSequenceNumber": "1A", + "imageReferenceKeyLength": "0034", + "imageReferenceKey": "0", + "description": "CD Addendum B", + "userField": "" + } + ], + "checkDetailAddendumC": [ + { + "id": "", + "recordNumber": 3, + "endorsingBankRoutingNumber": "121042882", + "bofdEndorsementBusinessDate": "2018-10-03T00:00:00Z", + "endorsingBankItemSequenceNumber": "2", + "truncationIndicator": "Y", + "endorsingBankConversionIndicator": "1", + "endorsingBankCorrectionIndicator": 0, + "returnReason": "A", + "userField": "", + "endorsingBankIdentifier": 0 + } + ], + "imageViewDetail": [ + { + "id": "", + "imageIndicator": 1, + "imageCreatorRoutingNumber": "031300012", + "imageCreatorDate": "2018-10-03T00:00:00Z", + "imageViewFormatIndicator": "00", + "imageViewCompressionAlgorithm": "00", + "imageViewDataSize": "0000000", + "viewSideIndicator": 0, + "viewDescriptor": "00", + "digitalSignatureIndicator": 0, + "digitalSignatureMethod": "00", + "securityKeySize": 0, + "protectedDataStart": 0, + "protectedDataLength": 0, + "imageRecreateIndicator": 0, + "userField": "", + "overrideIndicator": "0" + } + ], + "imageViewData": [ + { + "id": "", + "eceInstitutionRoutingNumber": "121042882", + "bundleBusinessDate": "2018-10-03T00:00:00Z", + "cycleNumber": "1", + "eceInstitutionItemSequenceNumber": "1", + "securityOriginatorName": "Sec Orig Name", + "securityAuthenticatorName": "Sec Auth Name", + "securityKeyName": "SECURE", + "clippingOrigin": 0, + "clippingCoordinateH1": "", + "clippingCoordinateH2": "", + "clippingCoordinateV1": "", + "clippingCoordinateV2": "", + "lengthImageReferenceKey": "0000", + "imageReferenceKey": "", + "lengthDigitalSignature": "0", + "digitalSignature": "", + "lengthImageData": "0000001", + "imageData": "IA==" + } + ], + "imageViewAnalysis": [ + { + "id": "", + "globalImageQuality": 2, + "globalImageUsability": 2, + "imagingBankSpecificTest": 0, + "partialImage": 2, + "excessiveImageSkew": 2, + "piggybackImage": 2, + "tooLightOrTooDark": 2, + "streaksAndOrBands": 2, + "belowMinimumImageSize": 2, + "exceedsMaximumImageSize": 2, + "imageEnabledPOD": 1, + "sourceDocumentBad": 0, + "dateUsability": 2, + "payeeUsability": 2, + "convenienceAmountUsability": 2, + "amountInWordsUsability": 2, + "signatureUsability": 2, + "payorNameAddressUsability": 2, + "micrLineUsability": 2, + "memoLineUsability": 2, + "payorBankNameAddressUsability": 2, + "payeeEndorsementUsability": 2, + "bofdEndorsementUsability": 2, + "transitEndorsementUsability": 2, + "userField": "" + } + ] + } + ] + } + ], + "cashLetterControl": { + "cashLetterBundleCount": 1, + "cashLetterItemsCount": 4, + "cashLetterTotalAmount": 400000, + "settlementDate": "2024-10-23T00:00:00Z" + } + } + ], + "FileControl": { + "id": "", + "cashLetterCount": 1, + "totalItemCount": 4, + "fileTotalAmount": 400000 + } +} \ No newline at end of file