├── .github └── workflows │ ├── standard-go-test.yml │ └── standard-stale.yml ├── .gitignore ├── LICENSE ├── README.md ├── SHOULDERS.md ├── fix ├── fix.go ├── fix_test.go ├── init-context-fixtures │ └── contextsample.toml ├── init-fixtures │ └── sample.toml ├── plush.go ├── plush_test.go └── scenario.go ├── go.mod ├── go.sum ├── model.go ├── session.go ├── suite.go ├── suite_test.go └── version.go /.github/workflows/standard-go-test.yml: -------------------------------------------------------------------------------- 1 | name: Standard Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | call-standard-test: 10 | name: Test 11 | uses: gobuffalo/.github/.github/workflows/go-test.yml@v1 12 | secrets: inherit 13 | -------------------------------------------------------------------------------- /.github/workflows/standard-stale.yml: -------------------------------------------------------------------------------- 1 | name: Standard Autocloser 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | 7 | jobs: 8 | call-standard-autocloser: 9 | name: Autocloser 10 | uses: gobuffalo/.github/.github/workflows/stale.yml@v1 11 | secrets: inherit 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | doc 4 | tmp 5 | pkg 6 | *.gem 7 | *.pid 8 | coverage 9 | coverage.data 10 | build/* 11 | *.pbxuser 12 | *.mode1v3 13 | .svn 14 | profile 15 | .console_history 16 | .sass-cache/* 17 | .rake_tasks~ 18 | *.log.lck 19 | solr/ 20 | .jhw-cache/ 21 | jhw.* 22 | *.sublime* 23 | node_modules/ 24 | dist/ 25 | generated/ 26 | .vendor/ 27 | bin/* 28 | gin-bin 29 | .idea/ 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Mark Bates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Suite 2 | 3 | [![Standard Test](https://github.com/gobuffalo/suite/actions/workflows/standard-go-test.yml/badge.svg)](https://github.com/gobuffalo/suite/actions/workflows/standard-go-test.yml) 4 | [![Go Reference](https://pkg.go.dev/badge/github.com/gobuffalo/suite/v4.svg)](https://pkg.go.dev/github.com/gobuffalo/suite/v4) 5 | 6 | Suite is a package meant to make testing [gobuffalo.io](http://gobuffalo.io) applications easier. 7 | 8 | ## Setup 9 | 10 | This is the entry point into your unit testing suite. The `Test_ActionSuite(t *testing.T)` function is 11 | compatible with the `go test` command, and it should: 12 | 13 | - Create and configure your new test suite instance (`ActionSuite` in this case) 14 | - Call `suite.Run` with the `*testing.T` passed by the Go testing system, and your new `ActionSuite` instance 15 | 16 | ```go 17 | package actions_test 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/gobuffalo/suite/v4" 23 | "github.com/gobuffalo/toodo/actions" 24 | ) 25 | 26 | type ActionSuite struct { 27 | *suite.Action 28 | } 29 | 30 | func Test_ActionSuite(t *testing.T) { 31 | as := &ActionSuite{suite.NewAction(actions.App())} 32 | suite.Run(t, as) 33 | } 34 | ``` 35 | 36 | ## Usage 37 | 38 | This is where you write your actual test logic. The rules for test names are similar, but not the same, as with `go test`: 39 | 40 | - Each test is a method on your `*ActionSuite` 41 | - Test method names should start with `Test` (note the upper case `T`) 42 | - Test methods should have no arguments 43 | 44 | A few additional notes: 45 | 46 | - To avoid race conditions on the testing database, always use the `ActionSuite` variable called `DB` to access the database (not your production app's database) 47 | - You can access the raw `*testing.T` value if needed with `as.T()` 48 | - `ActionSuite` has support for [`testify`](https://github.com/stretchr/testify)'s [`require` package](https://godoc.org/github.com/stretchr/testify/require) and [`assert` package](https://godoc.org/github.com/stretchr/testify/assert) 49 | - ... So try to use one of those instead packages of using the raw methods on the `*testing.T` 50 | - The default database that `suite` will connect to is called `testing` in your [database.yml](https://github.com/markbates/pop#connecting-to-databases) 51 | 52 | ```go 53 | package actions_test 54 | 55 | import ( 56 | "fmt" 57 | 58 | "github.com/gobuffalo/toodo/models" 59 | ) 60 | 61 | func (as *ActionSuite) Test_TodosResource_List() { 62 | todos := models.Todos{ 63 | {Title: "buy milk"}, 64 | {Title: "read a good book"}, 65 | } 66 | for _, t := range todos { 67 | err := as.DB.Create(&t) 68 | as.NoError(err) 69 | } 70 | 71 | res := as.HTML("/todos").Get() 72 | body := res.Body.String() 73 | for _, t := range todos { 74 | as.Contains(body, fmt.Sprintf("

%s

", t.Title)) 75 | } 76 | } 77 | 78 | func (as *ActionSuite) Test_TodosResource_New() { 79 | res := as.HTML("/todos/new").Get() 80 | as.Contains(res.Body.String(), "

New Todo

") 81 | } 82 | 83 | func (as *ActionSuite) Test_TodosResource_Create() { 84 | todo := &models.Todo{Title: "Learn Go"} 85 | res := as.HTML("/todos").Post(todo) 86 | as.Equal(301, res.Code) 87 | as.Equal("/todos", res.Location()) 88 | 89 | err := as.DB.First(todo) 90 | as.NoError(err) 91 | as.NotZero(todo.ID) 92 | as.NotZero(todo.CreatedAt) 93 | as.Equal("Learn Go", todo.Title) 94 | } 95 | 96 | func (as *ActionSuite) Test_TodosResource_Create_Errors() { 97 | todo := &models.Todo{} 98 | res := as.HTML("/todos").Post(todo) 99 | as.Equal(422, res.Code) 100 | as.Contains(res.Body.String(), "Title can not be blank.") 101 | 102 | c, err := as.DB.Count(todo) 103 | as.NoError(err) 104 | as.Equal(0, c) 105 | } 106 | 107 | func (as *ActionSuite) Test_TodosResource_Update() { 108 | todo := &models.Todo{Title: "Lern Go"} 109 | verrs, err := as.DB.ValidateAndCreate(todo) 110 | as.NoError(err) 111 | as.False(verrs.HasAny()) 112 | 113 | res := as.HTML("/todos/%s", todo.ID).Put(&models.Todo{ID: todo.ID, Title: "Learn Go"}) 114 | as.Equal(200, res.Code) 115 | 116 | err = as.DB.Reload(todo) 117 | as.NoError(err) 118 | as.Equal("Learn Go", todo.Title) 119 | } 120 | ``` 121 | 122 | ## Fixtures (Test Data) 123 | 124 | Often it is useful to load a series of data into the database at the start of the test to make testing easier. For example, you need to have a user in the database to log a person into the application, or you need some data in the database to test destroying that data. Fixtures let us solve these problems easily. 125 | 126 | ### Using Fixtures 127 | 128 | First you need to setup your test suite to use fixtures. You can do this by using `suite.NewActionWithFixtures` or `suite.NewModelWithFixtures` methods to create new test suites that take an `fs.FS` pointing to where the files for this suite live. 129 | 130 | ```go 131 | package actions 132 | 133 | import ( 134 | "os" 135 | "testing" 136 | 137 | "github.com/gobuffalo/suite/v4" 138 | ) 139 | 140 | type ActionSuite struct { 141 | *suite.Action 142 | } 143 | 144 | func Test_ActionSuite(t *testing.T) { 145 | action, err := suite.NewActionWithFixtures(App(), os.DirFS("../fixtures")) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | 150 | as := &ActionSuite{ 151 | Action: action, 152 | } 153 | suite.Run(t, as) 154 | } 155 | ``` 156 | 157 | Once your suite is set up, you can create `N` numbers of `*.toml` files in the directory you've chosen for your fixtures, in this example, `../fixtures`. 158 | 159 | ### Example Fixture File 160 | 161 | ```toml 162 | [[scenario]] 163 | name = "lots of widgets" 164 | 165 | [[scenario.table]] 166 | name = "widgets" 167 | 168 | [[scenario.table.row]] 169 | id = "<%= uuidNamed("widget") %>" 170 | name = "This is widget #1" 171 | body = "some widget body" 172 | created_at = "<%= now() %>" 173 | updated_at = "<%= now() %>" 174 | 175 | [[scenario.table.row]] 176 | id = "<%= uuid() %>" 177 | name = "This is widget #2" 178 | body = "some widget body" 179 | created_at = "<%= now() %>" 180 | updated_at = "<%= now() %>" 181 | 182 | [[scenario.table]] 183 | name = "users" 184 | 185 | [[scenario.table.row]] 186 | id = "<%= uuid() %>" 187 | name = "Mark Bates" 188 | admin = true 189 | price = 19.99 190 | widget_id = "<%= uuidNamed("widget") %>" 191 | created_at = "<%= now() %>" 192 | updated_at = "<%= now() %>" 193 | ``` 194 | 195 | #### Helper Methods 196 | 197 | The `*.toml` files all get run through [https://github.com/gobuffalo/plush](https://github.com/gobuffalo/plush) before they're decoded, so you can make use of the helpful helper methods that ship with Plush. 198 | 199 | We've also add a couple of useful helpers for you as well: 200 | 201 | - `uuid()` - returns a new `github.com/gobuffalo/uuid.UUID` 202 | - `now()` - returns `time.Now()` 203 | - `nowAdd(s)` and `nowSub(s)` - similar to `now()` but `s` amount of seconds is added or substracted, respectively, from the return value 204 | - `uuidNamed(name)` - will attempt to return a previously declared UUID with that name, useful, for relations/associations. If there was one that wasn't defined with that name, a new one will be created. 205 | - `hash(string, opts)` - will create the hashed value of the string (useful for creating a password), you can define the cost as an opts (the default is `bcrypt.DefaultCost`) 206 | 207 | ### Using in Tests 208 | 209 | In your suite tests you need to call the `LoadFixture` method giving it the name of the fixtures you would like to use for this test. 210 | 211 | ```go 212 | func (as *ActionSuite) Test_WidgetsResource_List() { 213 | as.LoadFixture("lots of widgets") 214 | res := as.HTML("/widgets").Get() 215 | 216 | body := res.Body.String() 217 | as.Contains(body, "widget #1") 218 | as.Contains(body, "widget #2") 219 | } 220 | ``` 221 | 222 | ### FAQs 223 | 224 | - _Can I call `LoadFixture` more than once in a test?_ - Absolutely! Call it as many times as you want! 225 | - _Can I load multiple rows into a table in one scenario?_ - Absolutely! 226 | - _Can I load data into multiple tables in one scenario?_ - Absolutely! 227 | - _Will it load all my fixtures?_ - No, you have to load specific scenarios, so don't be afraid to create lots of scenarios and only call the ones you need per test. 228 | - _Will this pollute my database, and how do I clear data between tests?_ - No need to worry, the suite will truncate any data in your database between test runs, so you never have to worry about it. 229 | -------------------------------------------------------------------------------- /SHOULDERS.md: -------------------------------------------------------------------------------- 1 | # Suite Stands on the Shoulders of Giants 2 | 3 | Suite does not try to reinvent the wheel! Instead, it uses the already great wheels developed by the Go community and puts them all together in the best way possible. Without these giants, this project would not be possible. Please make sure to check them out and thank them for all of their hard work. 4 | 5 | Thank you to the following **GIANTS**: 6 | 7 | * [github.com/BurntSushi/toml](https://godoc.org/github.com/BurntSushi/toml) 8 | * [github.com/Masterminds/semver/v3](https://godoc.org/github.com/Masterminds/semver/v3) 9 | * [github.com/aymerick/douceur](https://godoc.org/github.com/aymerick/douceur) 10 | * [github.com/cockroachdb/apd](https://godoc.org/github.com/cockroachdb/apd) 11 | * [github.com/coreos/go-systemd](https://godoc.org/github.com/coreos/go-systemd) 12 | * [github.com/cpuguy83/go-md2man/v2](https://godoc.org/github.com/cpuguy83/go-md2man/v2) 13 | * [github.com/creack/pty](https://godoc.org/github.com/creack/pty) 14 | * [github.com/davecgh/go-spew](https://godoc.org/github.com/davecgh/go-spew) 15 | * [github.com/dustin/go-humanize](https://godoc.org/github.com/dustin/go-humanize) 16 | * [github.com/fatih/color](https://godoc.org/github.com/fatih/color) 17 | * [github.com/fatih/structs](https://godoc.org/github.com/fatih/structs) 18 | * [github.com/felixge/httpsnoop](https://godoc.org/github.com/felixge/httpsnoop) 19 | * [github.com/fsnotify/fsnotify](https://godoc.org/github.com/fsnotify/fsnotify) 20 | * [github.com/go-kit/log](https://godoc.org/github.com/go-kit/log) 21 | * [github.com/go-logfmt/logfmt](https://godoc.org/github.com/go-logfmt/logfmt) 22 | * [github.com/go-sql-driver/mysql](https://godoc.org/github.com/go-sql-driver/mysql) 23 | * [github.com/go-stack/stack](https://godoc.org/github.com/go-stack/stack) 24 | * [github.com/gobuffalo/attrs](https://godoc.org/github.com/gobuffalo/attrs) 25 | * [github.com/gobuffalo/buffalo](https://godoc.org/github.com/gobuffalo/buffalo) 26 | * [github.com/gobuffalo/envy](https://godoc.org/github.com/gobuffalo/envy) 27 | * [github.com/gobuffalo/events](https://godoc.org/github.com/gobuffalo/events) 28 | * [github.com/gobuffalo/fizz](https://godoc.org/github.com/gobuffalo/fizz) 29 | * [github.com/gobuffalo/flect](https://godoc.org/github.com/gobuffalo/flect) 30 | * [github.com/gobuffalo/genny/v2](https://godoc.org/github.com/gobuffalo/genny/v2) 31 | * [github.com/gobuffalo/github_flavored_markdown](https://godoc.org/github.com/gobuffalo/github_flavored_markdown) 32 | * [github.com/gobuffalo/grift](https://godoc.org/github.com/gobuffalo/grift) 33 | * [github.com/gobuffalo/helpers](https://godoc.org/github.com/gobuffalo/helpers) 34 | * [github.com/gobuffalo/here](https://godoc.org/github.com/gobuffalo/here) 35 | * [github.com/gobuffalo/httptest](https://godoc.org/github.com/gobuffalo/httptest) 36 | * [github.com/gobuffalo/logger](https://godoc.org/github.com/gobuffalo/logger) 37 | * [github.com/gobuffalo/meta](https://godoc.org/github.com/gobuffalo/meta) 38 | * [github.com/gobuffalo/middleware](https://godoc.org/github.com/gobuffalo/middleware) 39 | * [github.com/gobuffalo/nulls](https://godoc.org/github.com/gobuffalo/nulls) 40 | * [github.com/gobuffalo/packd](https://godoc.org/github.com/gobuffalo/packd) 41 | * [github.com/gobuffalo/plush/v4](https://godoc.org/github.com/gobuffalo/plush/v4) 42 | * [github.com/gobuffalo/pop/v6](https://godoc.org/github.com/gobuffalo/pop/v6) 43 | * [github.com/gobuffalo/refresh](https://godoc.org/github.com/gobuffalo/refresh) 44 | * [github.com/gobuffalo/tags/v3](https://godoc.org/github.com/gobuffalo/tags/v3) 45 | * [github.com/gobuffalo/validate/v3](https://godoc.org/github.com/gobuffalo/validate/v3) 46 | * [github.com/gofrs/uuid](https://godoc.org/github.com/gofrs/uuid) 47 | * [github.com/google/go-cmp](https://godoc.org/github.com/google/go-cmp) 48 | * [github.com/google/renameio](https://godoc.org/github.com/google/renameio) 49 | * [github.com/gorilla/css](https://godoc.org/github.com/gorilla/css) 50 | * [github.com/gorilla/handlers](https://godoc.org/github.com/gorilla/handlers) 51 | * [github.com/gorilla/mux](https://godoc.org/github.com/gorilla/mux) 52 | * [github.com/gorilla/securecookie](https://godoc.org/github.com/gorilla/securecookie) 53 | * [github.com/gorilla/sessions](https://godoc.org/github.com/gorilla/sessions) 54 | * [github.com/inconshreveable/mousetrap](https://godoc.org/github.com/inconshreveable/mousetrap) 55 | * [github.com/jackc/chunkreader](https://godoc.org/github.com/jackc/chunkreader) 56 | * [github.com/jackc/chunkreader/v2](https://godoc.org/github.com/jackc/chunkreader/v2) 57 | * [github.com/jackc/pgconn](https://godoc.org/github.com/jackc/pgconn) 58 | * [github.com/jackc/pgio](https://godoc.org/github.com/jackc/pgio) 59 | * [github.com/jackc/pgmock](https://godoc.org/github.com/jackc/pgmock) 60 | * [github.com/jackc/pgpassfile](https://godoc.org/github.com/jackc/pgpassfile) 61 | * [github.com/jackc/pgproto3](https://godoc.org/github.com/jackc/pgproto3) 62 | * [github.com/jackc/pgproto3/v2](https://godoc.org/github.com/jackc/pgproto3/v2) 63 | * [github.com/jackc/pgservicefile](https://godoc.org/github.com/jackc/pgservicefile) 64 | * [github.com/jackc/pgtype](https://godoc.org/github.com/jackc/pgtype) 65 | * [github.com/jackc/pgx/v4](https://godoc.org/github.com/jackc/pgx/v4) 66 | * [github.com/jackc/puddle](https://godoc.org/github.com/jackc/puddle) 67 | * [github.com/jmoiron/sqlx](https://godoc.org/github.com/jmoiron/sqlx) 68 | * [github.com/joho/godotenv](https://godoc.org/github.com/joho/godotenv) 69 | * [github.com/kballard/go-shellquote](https://godoc.org/github.com/kballard/go-shellquote) 70 | * [github.com/kisielk/gotool](https://godoc.org/github.com/kisielk/gotool) 71 | * [github.com/konsorten/go-windows-terminal-sequences](https://godoc.org/github.com/konsorten/go-windows-terminal-sequences) 72 | * [github.com/kr/pretty](https://godoc.org/github.com/kr/pretty) 73 | * [github.com/kr/pty](https://godoc.org/github.com/kr/pty) 74 | * [github.com/kr/text](https://godoc.org/github.com/kr/text) 75 | * [github.com/lib/pq](https://godoc.org/github.com/lib/pq) 76 | * [github.com/luna-duclos/instrumentedsql](https://godoc.org/github.com/luna-duclos/instrumentedsql) 77 | * [github.com/mattn/go-colorable](https://godoc.org/github.com/mattn/go-colorable) 78 | * [github.com/mattn/go-isatty](https://godoc.org/github.com/mattn/go-isatty) 79 | * [github.com/mattn/go-sqlite3](https://godoc.org/github.com/mattn/go-sqlite3) 80 | * [github.com/microcosm-cc/bluemonday](https://godoc.org/github.com/microcosm-cc/bluemonday) 81 | * [github.com/mitchellh/go-homedir](https://godoc.org/github.com/mitchellh/go-homedir) 82 | * [github.com/monoculum/formam](https://godoc.org/github.com/monoculum/formam) 83 | * [github.com/nicksnyder/go-i18n](https://godoc.org/github.com/nicksnyder/go-i18n) 84 | * [github.com/pelletier/go-toml](https://godoc.org/github.com/pelletier/go-toml) 85 | * [github.com/pkg/diff](https://godoc.org/github.com/pkg/diff) 86 | * [github.com/pkg/errors](https://godoc.org/github.com/pkg/errors) 87 | * [github.com/pmezard/go-difflib](https://godoc.org/github.com/pmezard/go-difflib) 88 | * [github.com/psanford/memfs](https://godoc.org/github.com/psanford/memfs) 89 | * [github.com/rogpeppe/go-internal](https://godoc.org/github.com/rogpeppe/go-internal) 90 | * [github.com/rs/xid](https://godoc.org/github.com/rs/xid) 91 | * [github.com/rs/zerolog](https://godoc.org/github.com/rs/zerolog) 92 | * [github.com/russross/blackfriday/v2](https://godoc.org/github.com/russross/blackfriday/v2) 93 | * [github.com/satori/go.uuid](https://godoc.org/github.com/satori/go.uuid) 94 | * [github.com/sergi/go-diff](https://godoc.org/github.com/sergi/go-diff) 95 | * [github.com/shopspring/decimal](https://godoc.org/github.com/shopspring/decimal) 96 | * [github.com/sirupsen/logrus](https://godoc.org/github.com/sirupsen/logrus) 97 | * [github.com/sourcegraph/annotate](https://godoc.org/github.com/sourcegraph/annotate) 98 | * [github.com/sourcegraph/syntaxhighlight](https://godoc.org/github.com/sourcegraph/syntaxhighlight) 99 | * [github.com/spf13/cobra](https://godoc.org/github.com/spf13/cobra) 100 | * [github.com/spf13/pflag](https://godoc.org/github.com/spf13/pflag) 101 | * [github.com/stretchr/objx](https://godoc.org/github.com/stretchr/objx) 102 | * [github.com/stretchr/testify](https://godoc.org/github.com/stretchr/testify) 103 | * [github.com/unrolled/secure](https://godoc.org/github.com/unrolled/secure) 104 | * [github.com/yuin/goldmark](https://godoc.org/github.com/yuin/goldmark) 105 | * [github.com/zenazn/goji](https://godoc.org/github.com/zenazn/goji) 106 | * [go.uber.org/atomic](https://godoc.org/go.uber.org/atomic) 107 | * [go.uber.org/multierr](https://godoc.org/go.uber.org/multierr) 108 | * [go.uber.org/tools](https://godoc.org/go.uber.org/tools) 109 | * [go.uber.org/zap](https://godoc.org/go.uber.org/zap) 110 | * [golang.org/x/crypto](https://godoc.org/golang.org/x/crypto) 111 | * [golang.org/x/lint](https://godoc.org/golang.org/x/lint) 112 | * [golang.org/x/mod](https://godoc.org/golang.org/x/mod) 113 | * [golang.org/x/net](https://godoc.org/golang.org/x/net) 114 | * [golang.org/x/sync](https://godoc.org/golang.org/x/sync) 115 | * [golang.org/x/sys](https://godoc.org/golang.org/x/sys) 116 | * [golang.org/x/term](https://godoc.org/golang.org/x/term) 117 | * [golang.org/x/text](https://godoc.org/golang.org/x/text) 118 | * [golang.org/x/tools](https://godoc.org/golang.org/x/tools) 119 | * [golang.org/x/xerrors](https://godoc.org/golang.org/x/xerrors) 120 | * [gopkg.in/check.v1](https://godoc.org/gopkg.in/check.v1) 121 | * [gopkg.in/errgo.v2](https://godoc.org/gopkg.in/errgo.v2) 122 | * [gopkg.in/inconshreveable/log15.v2](https://godoc.org/gopkg.in/inconshreveable/log15.v2) 123 | * [gopkg.in/yaml.v2](https://godoc.org/gopkg.in/yaml.v2) 124 | * [gopkg.in/yaml.v3](https://godoc.org/gopkg.in/yaml.v3) 125 | * [honnef.co/go/tools](https://godoc.org/honnef.co/go/tools) 126 | -------------------------------------------------------------------------------- /fix/fix.go: -------------------------------------------------------------------------------- 1 | package fix 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | "path/filepath" 7 | "sync" 8 | 9 | "github.com/BurntSushi/toml" 10 | "github.com/gobuffalo/plush/v4" 11 | ) 12 | 13 | var scenes = map[string]Scenario{} 14 | var moot = &sync.RWMutex{} 15 | 16 | func InitWithContext(fsys fs.FS, ctx *plush.Context) error { 17 | err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { 18 | if err != nil { 19 | return err 20 | } 21 | 22 | if d.IsDir() { 23 | return nil 24 | } 25 | 26 | if filepath.Ext(d.Name()) != ".toml" { 27 | return nil 28 | } 29 | 30 | f, err := fsys.Open(path) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | x, err := renderWithContext(f, ctx) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | sc := Scenarios{} 41 | _, err = toml.Decode(x, &sc) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | moot.Lock() 47 | for _, s := range sc.Scenarios { 48 | scenes[s.Name] = s 49 | } 50 | moot.Unlock() 51 | return nil 52 | }) 53 | return err 54 | } 55 | 56 | func Init(fsys fs.FS) error { 57 | err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { 58 | if err != nil { 59 | return err 60 | } 61 | 62 | if d.IsDir() { 63 | return nil 64 | } 65 | 66 | if filepath.Ext(d.Name()) != ".toml" { 67 | return nil 68 | } 69 | 70 | f, err := fsys.Open(path) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | x, err := render(f) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | sc := Scenarios{} 81 | _, err = toml.Decode(x, &sc) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | moot.Lock() 87 | for _, s := range sc.Scenarios { 88 | scenes[s.Name] = s 89 | } 90 | moot.Unlock() 91 | return nil 92 | }) 93 | return err 94 | } 95 | 96 | func Find(name string) (Scenario, error) { 97 | moot.RLock() 98 | s, ok := scenes[name] 99 | moot.RUnlock() 100 | if !ok { 101 | return Scenario{}, fmt.Errorf("could not find a scenario named %q", name) 102 | } 103 | return s, nil 104 | } 105 | -------------------------------------------------------------------------------- /fix/fix_test.go: -------------------------------------------------------------------------------- 1 | package fix 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/gobuffalo/plush/v4" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_Init_And_Find(t *testing.T) { 12 | r := require.New(t) 13 | 14 | fsys := os.DirFS("./init-fixtures") 15 | r.NoError(Init(fsys)) 16 | 17 | s, err := Find("lots of widgets") 18 | r.NoError(err) 19 | r.Equal("lots of widgets", s.Name) 20 | 21 | r.Len(s.Tables, 2) 22 | 23 | table := s.Tables[0] 24 | r.Equal("widgets", table.Name) 25 | r.Len(table.Row, 3) 26 | 27 | row := table.Row[0] 28 | r.NotZero(row["id"]) 29 | r.NotZero(row["created_at"]) 30 | r.NotZero(row["updated_at"]) 31 | r.Equal("This is widget #1", row["name"]) 32 | r.Equal("some widget body", row["body"]) 33 | 34 | wid := row["id"] 35 | 36 | row = table.Row[1] 37 | r.NotZero(row["id"]) 38 | r.NotZero(row["created_at"]) 39 | r.NotZero(row["updated_at"]) 40 | r.Equal("This is widget #2", row["name"]) 41 | r.Equal("some widget body", row["body"]) 42 | 43 | row = table.Row[2] 44 | r.NotZero(row["id"]) 45 | r.NotZero(row["created_at"]) 46 | r.NotZero(row["updated_at"]) 47 | r.Equal("This is widget #3", row["name"]) 48 | r.Equal("some widget body", row["body"]) 49 | 50 | table = s.Tables[1] 51 | r.Equal("users", table.Name) 52 | r.Len(table.Row, 1) 53 | 54 | row = table.Row[0] 55 | r.NotZero(row["id"]) 56 | r.NotZero(row["created_at"]) 57 | r.NotZero(row["updated_at"]) 58 | r.True(row["admin"].(bool)) 59 | r.Equal(19.99, row["price"].(float64)) 60 | r.Equal(wid, row["widget_id"]) 61 | } 62 | 63 | func Test_InitWithContext_And_Find_CustomConfig(t *testing.T) { 64 | r := require.New(t) 65 | 66 | fsys := os.DirFS("./init-context-fixtures") 67 | ctx := plush.NewContextWith(map[string]interface{}{ 68 | "double": func(num int, help plush.HelperContext) int { 69 | return num * 2 70 | }, 71 | }) 72 | r.NoError(InitWithContext(fsys, ctx)) 73 | 74 | s, err := Find("widget with context") 75 | r.NoError(err) 76 | r.Equal("widget with context", s.Name) 77 | 78 | r.Len(s.Tables, 2) 79 | 80 | table := s.Tables[0] 81 | r.Equal("widgets", table.Name) 82 | r.Len(table.Row, 2) 83 | 84 | row := table.Row[0] 85 | r.NotZero(row["id"]) 86 | r.NotZero(row["created_at"]) 87 | r.NotZero(row["updated_at"]) 88 | r.Equal("This is widget #1", row["name"]) 89 | r.Equal("some widget body", row["body"]) 90 | 91 | wid := row["id"] 92 | 93 | row = table.Row[1] 94 | r.NotZero(row["id"]) 95 | r.NotZero(row["created_at"]) 96 | r.NotZero(row["updated_at"]) 97 | r.Equal("This is widget #2", row["name"]) 98 | r.Equal("some widget body", row["body"]) 99 | 100 | table = s.Tables[1] 101 | r.Equal("users", table.Name) 102 | r.Len(table.Row, 1) 103 | 104 | row = table.Row[0] 105 | r.NotZero(row["id"]) 106 | r.NotZero(row["created_at"]) 107 | r.NotZero(row["updated_at"]) 108 | r.True(row["admin"].(bool)) 109 | r.Equal(int64(36), row["price"].(int64)) 110 | r.Equal(wid, row["widget_id"]) 111 | } 112 | -------------------------------------------------------------------------------- /fix/init-context-fixtures/contextsample.toml: -------------------------------------------------------------------------------- 1 | [[scenario]] 2 | name = "widget with context" 3 | 4 | [[scenario.table]] 5 | name = "widgets" 6 | 7 | [[scenario.table.row]] 8 | id = "<%= uuidNamed("widget") %>" 9 | name = "This is widget #1" 10 | body = "some widget body" 11 | created_at = "<%= now() %>" 12 | updated_at = "<%= now() %>" 13 | 14 | [[scenario.table.row]] 15 | id = "<%= uuid() %>" 16 | name = "This is widget #2" 17 | body = "some widget body" 18 | created_at = "<%= now() %>" 19 | updated_at = "<%= now() %>" 20 | 21 | [[scenario.table]] 22 | name = "users" 23 | 24 | [[scenario.table.row]] 25 | id = "<%= uuid() %>" 26 | name = "Mark Bates" 27 | admin = true 28 | price = <%= double(18) %> 29 | widget_id = "<%= uuidNamed("widget") %>" 30 | created_at = "<%= now() %>" 31 | updated_at = "<%= now() %>" 32 | 33 | -------------------------------------------------------------------------------- /fix/init-fixtures/sample.toml: -------------------------------------------------------------------------------- 1 | [[scenario]] 2 | name = "lots of widgets" 3 | 4 | [[scenario.table]] 5 | name = "widgets" 6 | 7 | [[scenario.table.row]] 8 | id = "<%= uuidNamed("widget") %>" 9 | name = "This is widget #1" 10 | body = "some widget body" 11 | created_at = "<%= now() %>" 12 | updated_at = "<%= now() %>" 13 | 14 | [[scenario.table.row]] 15 | id = "<%= uuid() %>" 16 | name = "This is widget #2" 17 | body = "some widget body" 18 | created_at = "<%= now() %>" 19 | updated_at = "<%= now() %>" 20 | 21 | [[scenario.table.row]] 22 | id = "<%= uuid() %>" 23 | name = "This is widget #3" 24 | body = "some widget body" 25 | created_at = "<%= nowSub(3600) %>" 26 | updated_at = "<%= nowAdd(3600) %>" 27 | 28 | [[scenario.table]] 29 | name = "users" 30 | 31 | [[scenario.table.row]] 32 | id = "<%= uuid() %>" 33 | name = "Mark Bates" 34 | admin = true 35 | price = 19.99 36 | widget_id = "<%= uuidNamed("widget") %>" 37 | created_at = "<%= now() %>" 38 | updated_at = "<%= now() %>" 39 | 40 | -------------------------------------------------------------------------------- /fix/plush.go: -------------------------------------------------------------------------------- 1 | package fix 2 | 3 | import ( 4 | "io" 5 | "time" 6 | 7 | "github.com/gobuffalo/plush/v4" 8 | "github.com/gofrs/uuid" 9 | "golang.org/x/crypto/bcrypt" 10 | ) 11 | 12 | func renderWithContext(r io.Reader, ctx *plush.Context) (string, error) { 13 | cm := map[string]interface{}{ 14 | "uuid": func() uuid.UUID { 15 | u, _ := uuid.NewV4() 16 | return u 17 | }, 18 | "uuidNamed": uuidNamed, 19 | "now": now, 20 | "hash": hash, 21 | "nowAdd": nowAdd, 22 | "nowSub": nowSub, 23 | } 24 | for k, v := range cm { 25 | if !ctx.Has(k) { 26 | ctx.Set(k, v) 27 | } 28 | } 29 | return plush.RenderR(r, ctx) 30 | } 31 | 32 | func render(r io.Reader) (string, error) { 33 | ctx := plush.NewContextWith(map[string]interface{}{ 34 | "uuid": func() uuid.UUID { 35 | u, _ := uuid.NewV4() 36 | return u 37 | }, 38 | "uuidNamed": uuidNamed, 39 | "now": now, 40 | "hash": hash, 41 | "nowAdd": nowAdd, 42 | "nowSub": nowSub, 43 | }) 44 | 45 | return renderWithContext(r, ctx) 46 | } 47 | 48 | func hash(s string, opts map[string]interface{}, help plush.HelperContext) (string, error) { 49 | cost := bcrypt.DefaultCost 50 | if i, ok := opts["cost"].(int); ok { 51 | cost = i 52 | } 53 | ph, err := bcrypt.GenerateFromPassword([]byte(s), cost) 54 | return string(ph), err 55 | } 56 | 57 | func now(help plush.HelperContext) string { 58 | return time.Now().Format(time.RFC3339) 59 | } 60 | 61 | func nowAdd(s int) string { 62 | return time.Now().Add(time.Second * time.Duration(s)).Format(time.RFC3339) 63 | } 64 | 65 | func nowSub(s int) string { 66 | return time.Now().Add(time.Second * -time.Duration(s)).Format(time.RFC3339) 67 | } 68 | 69 | func uuidNamed(name string, help plush.HelperContext) uuid.UUID { 70 | u, _ := uuid.NewV4() 71 | if ux, ok := help.Value(name).(uuid.UUID); ok { 72 | return ux 73 | } 74 | help.Set(name, u) 75 | return u 76 | } 77 | -------------------------------------------------------------------------------- /fix/plush_test.go: -------------------------------------------------------------------------------- 1 | package fix 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/gobuffalo/plush/v4" 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func Test_hash(t *testing.T) { 12 | r := require.New(t) 13 | s, err := hash("password", map[string]interface{}{}, plush.HelperContext{}) 14 | r.NoError(err) 15 | r.NotEqual("password", s) 16 | } 17 | 18 | func Test_nowAdd(t *testing.T) { 19 | offset := 1000 20 | r := require.New(t) 21 | tStr := nowAdd(offset) 22 | r.NotEmpty(tStr) 23 | exp := time.Now().Add(time.Second * time.Duration(offset)) 24 | act, err := time.Parse(time.RFC3339, tStr) 25 | r.NoError(err) 26 | r.WithinDuration(exp, act, time.Second*10) 27 | } 28 | 29 | func Test_nowSub(t *testing.T) { 30 | offset := 1000 31 | r := require.New(t) 32 | tStr := nowSub(offset) 33 | r.NotEmpty(tStr) 34 | exp := time.Now().Add(time.Second * -time.Duration(offset)) 35 | act, err := time.Parse(time.RFC3339, tStr) 36 | r.NoError(err) 37 | r.WithinDuration(exp, act, time.Second*10) 38 | } 39 | -------------------------------------------------------------------------------- /fix/scenario.go: -------------------------------------------------------------------------------- 1 | package fix 2 | 3 | type Scenario struct { 4 | Name string `toml:"name"` 5 | Tables Tables `toml:"table"` 6 | } 7 | 8 | // type Scenarios []Scenario 9 | type Scenarios struct { 10 | Scenarios []Scenario `toml:"scenario"` 11 | } 12 | 13 | type Table struct { 14 | Name string `toml:"name"` 15 | Row []map[string]interface{} `toml:"row"` 16 | } 17 | 18 | type Tables []Table 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gobuffalo/suite/v4 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/BurntSushi/toml v1.3.2 7 | github.com/gobuffalo/buffalo v1.1.0 8 | github.com/gobuffalo/envy v1.10.2 9 | github.com/gobuffalo/httptest v1.5.2 10 | github.com/gobuffalo/middleware v1.0.0 11 | github.com/gobuffalo/plush/v4 v4.1.19 12 | github.com/gobuffalo/pop/v6 v6.1.1 13 | github.com/gofrs/uuid v4.4.0+incompatible 14 | github.com/gorilla/sessions v1.2.2 15 | github.com/stretchr/testify v1.8.4 16 | golang.org/x/crypto v0.31.0 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 3 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 4 | github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= 5 | github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 6 | github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= 7 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 8 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 9 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 10 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 11 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 12 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 13 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 14 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 15 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 16 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 18 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 20 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 21 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 22 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 23 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 24 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 25 | github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= 26 | github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 27 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 28 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 29 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 30 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 31 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 32 | github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= 33 | github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 34 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 35 | github.com/gobuffalo/attrs v1.0.3/go.mod h1:KvDJCE0avbufqS0Bw3UV7RQynESY0jjod+572ctX4t8= 36 | github.com/gobuffalo/buffalo v1.1.0 h1:6y1fUC47QWevaM1ImukJFHNgxiRIT+Y74VcP4ZQaz80= 37 | github.com/gobuffalo/buffalo v1.1.0/go.mod h1:lLsx9Y8bFYu9uvQyIEB3M0QA908ChHUPjwOGumZWARU= 38 | github.com/gobuffalo/envy v1.10.2 h1:EIi03p9c3yeuRCFPOKcSfajzkLb3hrRjEpHGI8I2Wo4= 39 | github.com/gobuffalo/envy v1.10.2/go.mod h1:qGAGwdvDsaEtPhfBzb3o0SfDea8ByGn9j8bKmVft9z8= 40 | github.com/gobuffalo/events v1.4.3 h1:JYDq7NbozP10zaN9Ijfem6Ozox2KacU2fU38RyquXM8= 41 | github.com/gobuffalo/events v1.4.3/go.mod h1:2BwfpV5X63t8xkUcVqIv4IbyAobJazRSVu1F1pgf3rc= 42 | github.com/gobuffalo/fizz v1.14.4 h1:8uume7joF6niTNWN582IQ2jhGTUoa9g1fiV/tIoGdBs= 43 | github.com/gobuffalo/fizz v1.14.4/go.mod h1:9/2fGNXNeIFOXEEgTPJwiK63e44RjG+Nc4hfMm1ArGM= 44 | github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= 45 | github.com/gobuffalo/flect v1.0.0 h1:eBFmskjXZgAOagiTXJH25Nt5sdFwNRcb8DKZsIsAUQI= 46 | github.com/gobuffalo/flect v1.0.0/go.mod h1:l9V6xSb4BlXwsxEMj3FVEub2nkdQjWhPvD8XTTlHPQc= 47 | github.com/gobuffalo/genny/v2 v2.1.0/go.mod h1:4yoTNk4bYuP3BMM6uQKYPvtP6WsXFGm2w2EFYZdRls8= 48 | github.com/gobuffalo/github_flavored_markdown v1.1.3/go.mod h1:IzgO5xS6hqkDmUh91BW/+Qxo/qYnvfzoz3A7uLkg77I= 49 | github.com/gobuffalo/github_flavored_markdown v1.1.4 h1:WacrEGPXUDX+BpU1GM/Y0ADgMzESKNWls9hOTG1MHVs= 50 | github.com/gobuffalo/github_flavored_markdown v1.1.4/go.mod h1:Vl9686qrVVQou4GrHRK/KOG3jCZOKLUqV8MMOAYtlso= 51 | github.com/gobuffalo/grift v1.5.2 h1:mC0vHRs+nXz+JhkH3sv+rVnnTQRDXrUrOXOPYpgPjpo= 52 | github.com/gobuffalo/grift v1.5.2/go.mod h1:Uf/3T2AR1Vv+t84EPmxCjqQ8oyJwXs0FAoLMFUn/JVs= 53 | github.com/gobuffalo/helpers v0.6.7 h1:C9CedoRSfgWg2ZoIkVXgjI5kgmSpL34Z3qdnzpfNVd8= 54 | github.com/gobuffalo/helpers v0.6.7/go.mod h1:j0u1iC1VqlCaJEEVkZN8Ia3TEzfj/zoXANqyJExTMTA= 55 | github.com/gobuffalo/here v0.6.7/go.mod h1:vuCfanjqckTuRlqAitJz6QC4ABNnS27wLb816UhsPcc= 56 | github.com/gobuffalo/httptest v1.5.2 h1:GpGy520SfY1QEmyPvaqmznTpG4gEQqQ82HtHqyNEreM= 57 | github.com/gobuffalo/httptest v1.5.2/go.mod h1:FA23yjsWLGj92mVV74Qtc8eqluc11VqcWr8/C1vxt4g= 58 | github.com/gobuffalo/logger v1.0.7 h1:LTLwWelETXDYyqF/ASf0nxaIcdEOIJNxRokPcfI/xbU= 59 | github.com/gobuffalo/logger v1.0.7/go.mod h1:u40u6Bq3VVvaMcy5sRBclD8SXhBYPS0Qk95ubt+1xJM= 60 | github.com/gobuffalo/meta v0.3.3 h1:GwPWdbdnp4JrKASvMLa03OtmzISq7z/nE7T6aMqzoYM= 61 | github.com/gobuffalo/meta v0.3.3/go.mod h1:o4B099IUFUfK4555Guqxz1zHAqyuUQ/KtHXi8WvVeFE= 62 | github.com/gobuffalo/middleware v1.0.0 h1:7k3jWjdit45aK5Ri9DAKBKAp1QL3bXe2PCtWBBomMww= 63 | github.com/gobuffalo/middleware v1.0.0/go.mod h1:ubE1XogeGL39dXeS0PEKLeEAdFcGXRMMwTW3RGXK/b4= 64 | github.com/gobuffalo/nulls v0.4.2 h1:GAqBR29R3oPY+WCC7JL9KKk9erchaNuV6unsOSZGQkw= 65 | github.com/gobuffalo/nulls v0.4.2/go.mod h1:EElw2zmBYafU2R9W4Ii1ByIj177wA/pc0JdjtD0EsH8= 66 | github.com/gobuffalo/packd v1.0.2/go.mod h1:sUc61tDqGMXON80zpKGp92lDb86Km28jfvX7IAyxFT8= 67 | github.com/gobuffalo/plush/v4 v4.1.16/go.mod h1:6t7swVsarJ8qSLw1qyAH/KbrcSTwdun2ASEQkOznakg= 68 | github.com/gobuffalo/plush/v4 v4.1.18/go.mod h1:xi2tJIhFI4UdzIL8sxZtzGYOd2xbBpcFbLZlIPGGZhU= 69 | github.com/gobuffalo/plush/v4 v4.1.19 h1:o0E5gEJw+ozkAwQoCeiaWC6VOU2lEmX+GhtGkwpqZ8o= 70 | github.com/gobuffalo/plush/v4 v4.1.19/go.mod h1:WiKHJx3qBvfaDVlrv8zT7NCd3dEMaVR/fVxW4wqV17M= 71 | github.com/gobuffalo/pop/v6 v6.1.1 h1:eUDBaZcb0gYrmFnKwpuTEUA7t5ZHqNfvS4POqJYXDZY= 72 | github.com/gobuffalo/pop/v6 v6.1.1/go.mod h1:1n7jAmI1i7fxuXPZjZb0VBPQDbksRtCoFnrDV5IsvaI= 73 | github.com/gobuffalo/refresh v1.13.3 h1:HYQlI6RiqWUf2yzCXvUHAYqm9M9/teVnox+mjzo/9rQ= 74 | github.com/gobuffalo/refresh v1.13.3/go.mod h1:NkzgLKZGk5suOvgvOD0/VALog0fH29Ib7fwym9JmRxA= 75 | github.com/gobuffalo/tags/v3 v3.1.4 h1:X/ydLLPhgXV4h04Hp2xlbI2oc5MDaa7eub6zw8oHjsM= 76 | github.com/gobuffalo/tags/v3 v3.1.4/go.mod h1:ArRNo3ErlHO8BtdA0REaZxijuWnWzF6PUXngmMXd2I0= 77 | github.com/gobuffalo/validate/v3 v3.3.3 h1:o7wkIGSvZBYBd6ChQoLxkz2y1pfmhbI4jNJYh6PuNJ4= 78 | github.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g= 79 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 80 | github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 81 | github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 82 | github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= 83 | github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 84 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 85 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 86 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 87 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 88 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 89 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 90 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 91 | github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= 92 | github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= 93 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 94 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 95 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 96 | github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= 97 | github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 98 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 99 | github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= 100 | github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= 101 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 102 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 103 | github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= 104 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 105 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 106 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 107 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 108 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 109 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 110 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 111 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 112 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 113 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 114 | github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= 115 | github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= 116 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 117 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 118 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 119 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 120 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= 121 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 122 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 123 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 124 | github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= 125 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 126 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 127 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 128 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 129 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 130 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 131 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 132 | github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= 133 | github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 134 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= 135 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 136 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 137 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 138 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 139 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 140 | github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= 141 | github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 142 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 143 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 144 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 145 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 146 | github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= 147 | github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= 148 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 149 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 150 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 151 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 152 | github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= 153 | github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= 154 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 155 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 156 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 157 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 158 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 159 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 160 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 161 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 162 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 163 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 164 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 165 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 166 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 167 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 168 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 169 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 170 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 171 | github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= 172 | github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 173 | github.com/luna-duclos/instrumentedsql v1.1.3 h1:t7mvC0z1jUt5A0UQ6I/0H31ryymuQRnJcWCiqV3lSAA= 174 | github.com/luna-duclos/instrumentedsql v1.1.3/go.mod h1:9J1njvFds+zN7y85EDhN9XNQLANWwZt2ULeIC8yMNYs= 175 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 176 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 177 | github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= 178 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 179 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 180 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 181 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 182 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 183 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 184 | github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 185 | github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 186 | github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= 187 | github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 188 | github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50= 189 | github.com/microcosm-cc/bluemonday v1.0.22 h1:p2tT7RNzRdCi0qmwxG+HbqD6ILkmwter1ZwVZn1oTxA= 190 | github.com/microcosm-cc/bluemonday v1.0.22/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= 191 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 192 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 193 | github.com/monoculum/formam v3.5.5+incompatible h1:iPl5csfEN96G2N2mGu8V/ZB62XLf9ySTpC8KRH6qXec= 194 | github.com/monoculum/formam v3.5.5+incompatible/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= 195 | github.com/nicksnyder/go-i18n v1.10.1/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4= 196 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 197 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 198 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 199 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 200 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 201 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 202 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 203 | github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef h1:NKxTG6GVGbfMXc2mIk+KphcH6hagbVXhcFkbTgYleTI= 204 | github.com/psanford/memfs v0.0.0-20210214183328-a001468d78ef/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI= 205 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 206 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 207 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 208 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 209 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 210 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 211 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 212 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 213 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 214 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 215 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 216 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 217 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= 218 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 219 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 220 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 221 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 222 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 223 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= 224 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= 225 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= 226 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= 227 | github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 228 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 229 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 230 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 231 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 232 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 233 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 234 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 235 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 236 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 237 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 238 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 239 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 240 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 241 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 242 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 243 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 244 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 245 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 246 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 247 | github.com/unrolled/secure v1.13.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= 248 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 249 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 250 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 251 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 252 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 253 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 254 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 255 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 256 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 257 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 258 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 259 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 260 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 261 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 262 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 263 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 264 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 265 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 266 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 267 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 268 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 269 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 270 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 271 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 272 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 273 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 274 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 275 | golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= 276 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 277 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 278 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 279 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 280 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 281 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 282 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 283 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 284 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 285 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 286 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 287 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 288 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 289 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 290 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 291 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 292 | golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 293 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 294 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 295 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 296 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 297 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 298 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 299 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= 300 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 301 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 302 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 303 | golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 304 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 305 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 306 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 307 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 308 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 309 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 310 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 311 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 312 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 313 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 318 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 321 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 323 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 324 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 325 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 326 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 327 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 328 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 329 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 330 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 331 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 332 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 333 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 334 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 335 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 336 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 337 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 338 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 339 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 340 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 341 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 342 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 343 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 344 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 345 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 346 | golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= 347 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 348 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 349 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 350 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 351 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 352 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 353 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 354 | golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 355 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 356 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 357 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 358 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 359 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 360 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 361 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 362 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 363 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 364 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 365 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 366 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 367 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 368 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 369 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 370 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 371 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 372 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 373 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 374 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 375 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 376 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 377 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 378 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 379 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 380 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 381 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 382 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 383 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 384 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 385 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 386 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 387 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 388 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 389 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 390 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 391 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 392 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 393 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 394 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 395 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 396 | -------------------------------------------------------------------------------- /model.go: -------------------------------------------------------------------------------- 1 | package suite 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | "strings" 7 | 8 | "github.com/gobuffalo/envy" 9 | "github.com/gobuffalo/plush/v4" 10 | "github.com/gobuffalo/pop/v6" 11 | "github.com/gobuffalo/suite/v4/fix" 12 | "github.com/stretchr/testify/require" 13 | "github.com/stretchr/testify/suite" 14 | ) 15 | 16 | // Model suite 17 | type Model struct { 18 | suite.Suite 19 | *require.Assertions 20 | DB *pop.Connection 21 | Fixtures fs.FS 22 | } 23 | 24 | // SetupTest clears database 25 | func (m *Model) SetupTest() { 26 | m.Assertions = require.New(m.T()) 27 | if m.DB != nil { 28 | err := m.CleanDB() 29 | m.NoError(err) 30 | } 31 | } 32 | 33 | // TearDownTest will be called after tests finish 34 | func (m *Model) TearDownTest() {} 35 | 36 | // DBDelta checks database table count change for a passed table name. 37 | func (m *Model) DBDelta(delta int, name string, fn func()) { 38 | sc, err := m.DB.Count(name) 39 | m.NoError(err) 40 | fn() 41 | ec, err := m.DB.Count(name) 42 | m.NoError(err) 43 | m.Equal(sc+delta, ec) 44 | } 45 | 46 | // LoadFixture loads a named fixture into the database. 47 | func (m *Model) LoadFixture(name string) { 48 | sc, err := fix.Find(name) 49 | m.NoError(err) 50 | db := m.DB.Store 51 | 52 | for _, table := range sc.Tables { 53 | for _, row := range table.Row { 54 | q := "insert into " + table.Name 55 | keys := []string{} 56 | skeys := []string{} 57 | for k := range row { 58 | keys = append(keys, k) 59 | skeys = append(skeys, ":"+k) 60 | } 61 | 62 | q = q + fmt.Sprintf(" (%s) values (%s)", strings.Join(keys, ","), strings.Join(skeys, ",")) 63 | _, err = db.NamedExec(q, row) 64 | m.NoError(err) 65 | } 66 | } 67 | } 68 | 69 | // NewModel creates a new model suite 70 | func NewModel() *Model { 71 | m := &Model{} 72 | c, err := pop.Connect(envy.Get("GO_ENV", "test")) 73 | if err == nil { 74 | m.DB = c 75 | } 76 | return m 77 | } 78 | 79 | // NewModelWithFixturesAndContext creates a new model suite with fixtures and a passed context. 80 | func NewModelWithFixturesAndContext(fsys fs.FS, ctx *plush.Context) (*Model, error) { 81 | m := NewModel() 82 | m.Fixtures = fsys 83 | return m, fix.InitWithContext(fsys, ctx) 84 | } 85 | 86 | // NewModelWithFixtures creates a new model with passed fixtures box 87 | func NewModelWithFixtures(fsys fs.FS) (*Model, error) { 88 | m := NewModel() 89 | m.Fixtures = fsys 90 | return m, fix.Init(fsys) 91 | } 92 | 93 | func (m *Model) Run(name string, subtest func()) bool { 94 | return m.Suite.Run(name, func() { 95 | m.Assertions = require.New(m.Suite.T()) 96 | subtest() 97 | }) 98 | } 99 | 100 | // CleanDB clears records from the database, this function is 101 | // useful to run before tests to ensure other tests are not 102 | // affecting the one running. 103 | func (m *Model) CleanDB() error { 104 | if m.DB == nil { 105 | return nil 106 | } 107 | 108 | switch m.DB.Dialect.Name() { 109 | case "postgres": 110 | deleteAllQuery := `DO 111 | $func$ 112 | DECLARE 113 | _tbl text; 114 | _sch text; 115 | BEGIN 116 | FOR _sch, _tbl IN 117 | SELECT schemaname, tablename 118 | FROM pg_tables 119 | WHERE tablename <> '%s' AND schemaname NOT IN ('pg_catalog', 'information_schema') AND tableowner = current_user 120 | LOOP 121 | EXECUTE format('ALTER TABLE %%I.%%I DISABLE TRIGGER ALL;', _sch, _tbl); 122 | EXECUTE format('DELETE FROM %%I.%%I CASCADE', _sch, _tbl); 123 | EXECUTE format('ALTER TABLE %%I.%%I ENABLE TRIGGER ALL;', _sch, _tbl); 124 | END LOOP; 125 | END 126 | $func$;` 127 | 128 | q := m.DB.RawQuery(fmt.Sprintf(deleteAllQuery, m.DB.MigrationTableName())) 129 | return q.Exec() 130 | default: 131 | return m.DB.TruncateAll() 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | package suite 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/sessions" 7 | ) 8 | 9 | type sessionStore struct { 10 | sessions map[string]*sessions.Session 11 | } 12 | 13 | func (s *sessionStore) Get(r *http.Request, name string) (*sessions.Session, error) { 14 | if s, ok := s.sessions[name]; ok { 15 | return s, nil 16 | } 17 | return s.New(r, name) 18 | } 19 | 20 | func (s *sessionStore) New(r *http.Request, name string) (*sessions.Session, error) { 21 | sess := sessions.NewSession(s, name) 22 | s.sessions[name] = sess 23 | return sess, nil 24 | } 25 | 26 | func (s *sessionStore) Save(r *http.Request, w http.ResponseWriter, sess *sessions.Session) error { 27 | if s.sessions == nil { 28 | s.sessions = map[string]*sessions.Session{} 29 | } 30 | s.sessions[sess.Name()] = sess 31 | return nil 32 | } 33 | 34 | //NewSessionStore for action suite 35 | func newSessionStore() sessions.Store { 36 | return &sessionStore{ 37 | sessions: map[string]*sessions.Session{}, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /suite.go: -------------------------------------------------------------------------------- 1 | package suite 2 | 3 | import ( 4 | "io/fs" 5 | "testing" 6 | 7 | "github.com/gobuffalo/buffalo" 8 | "github.com/gobuffalo/httptest" 9 | "github.com/gobuffalo/middleware/csrf" 10 | "github.com/stretchr/testify/suite" 11 | ) 12 | 13 | // Action suite 14 | type Action struct { 15 | *Model 16 | Session *buffalo.Session 17 | App *buffalo.App 18 | csrf buffalo.MiddlewareFunc 19 | } 20 | 21 | // HTML creates an httptest.Request with HTML content type. 22 | func (as *Action) HTML(u string, args ...interface{}) *httptest.Request { 23 | return httptest.New(as.App).HTML(u, args...) 24 | } 25 | 26 | // JSON creates an httptest.JSON request 27 | func (as *Action) JSON(u string, args ...interface{}) *httptest.JSON { 28 | return httptest.New(as.App).JSON(u, args...) 29 | } 30 | 31 | // XML creates an httptest.XML request 32 | func (as *Action) XML(u string, args ...interface{}) *httptest.XML { 33 | return httptest.New(as.App).XML(u, args...) 34 | } 35 | 36 | // SetupTest sets the session store, CSRF and clears database 37 | func (as *Action) SetupTest() { 38 | as.App.SessionStore = newSessionStore() 39 | s, _ := as.App.SessionStore.New(nil, as.App.SessionName) 40 | as.Session = &buffalo.Session{ 41 | Session: s, 42 | } 43 | 44 | if as.Model != nil { 45 | as.Model.SetupTest() 46 | } 47 | as.csrf = csrf.New 48 | csrf.New = func(next buffalo.Handler) buffalo.Handler { 49 | return func(c buffalo.Context) error { 50 | return next(c) 51 | } 52 | } 53 | } 54 | 55 | // TearDownTest resets csrf 56 | func (as *Action) TearDownTest() { 57 | csrf.New = as.csrf 58 | if as.Model != nil { 59 | as.Model.TearDownTest() 60 | } 61 | } 62 | 63 | // NewAction returns new Action for given buffalo.App 64 | func NewAction(app *buffalo.App) *Action { 65 | as := &Action{ 66 | App: app, 67 | Model: NewModel(), 68 | } 69 | return as 70 | } 71 | 72 | // NewActionWithFixtures creates a new ActionSuite with passed box for fixtures. 73 | func NewActionWithFixtures(app *buffalo.App, fsys fs.FS) (*Action, error) { 74 | m, err := NewModelWithFixtures(fsys) 75 | if err != nil { 76 | return nil, err 77 | } 78 | as := &Action{ 79 | App: app, 80 | Model: m, 81 | } 82 | return as, nil 83 | } 84 | 85 | //Run the passed suite 86 | func Run(t *testing.T, s suite.TestingSuite) { 87 | suite.Run(t, s) 88 | } 89 | -------------------------------------------------------------------------------- /suite_test.go: -------------------------------------------------------------------------------- 1 | package suite 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/gobuffalo/buffalo" 8 | "github.com/gobuffalo/buffalo/render" 9 | ) 10 | 11 | type aSuite struct { 12 | *Action 13 | } 14 | 15 | func Test_aSuite(t *testing.T) { 16 | app := buffalo.New(buffalo.Options{}) 17 | app.GET("/session-hello", func(c buffalo.Context) error { 18 | if n, ok := c.Session().Get("name").(string); ok { 19 | return c.Render(200, render.String(n)) 20 | } 21 | return c.Error(500, fmt.Errorf("could not find name in session")) 22 | }) 23 | as := &aSuite{NewAction(app)} 24 | Run(t, as) 25 | } 26 | 27 | func (as *aSuite) Test_Session() { 28 | as.Session.Set("name", "Homer Simpson") 29 | res := as.HTML("/session-hello").Get() 30 | as.Equal(200, res.Code) 31 | as.Contains(res.Body.String(), "Homer Simpson") 32 | 33 | as.Session.Clear() 34 | res = as.HTML("/session-hello").Get() 35 | as.Equal(500, res.Code) 36 | } 37 | 38 | func (as *aSuite) Test_Session_Resets() { 39 | res := as.HTML("/session-hello").Get() 40 | as.Equal(500, res.Code) 41 | } 42 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package suite 2 | 3 | // Version of Suite 4 | const Version = "v4.0.4" 5 | --------------------------------------------------------------------------------