diff options
author | techknowlogick <techknowlogick@gitea.io> | 2020-06-17 15:07:58 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-17 22:07:58 +0300 |
commit | 9e6a79bea9d7911c81b86a6d3715d340fc19032a (patch) | |
tree | 3c5982227a3ed7481786185f5fdf5a8c1107f279 /vendor/github.com/go-testfixtures | |
parent | 1645d4a5d8def3cc5451e068aa0a321e028a889b (diff) | |
download | gitea-9e6a79bea9d7911c81b86a6d3715d340fc19032a.tar.gz gitea-9e6a79bea9d7911c81b86a6d3715d340fc19032a.zip |
upgrade to use testfixtures v3 (#11904)
* upgrade to use testfixtures v3
* simplify logic
* make vendor
* update per @lunny
* Update templates/repo/empty.tmpl
* Update templates/repo/empty.tmpl
Co-authored-by: Lauris BH <lauris@nix.lv>
Diffstat (limited to 'vendor/github.com/go-testfixtures')
22 files changed, 2409 insertions, 0 deletions
diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/.editorconfig b/vendor/github.com/go-testfixtures/testfixtures/v3/.editorconfig new file mode 100644 index 0000000000..05f44c7500 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org/ + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = tab +indent_size = 8 + +[*.{yml,md}] +indent_style = space +indent_size = 2 diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/.gitattributes b/vendor/github.com/go-testfixtures/testfixtures/v3/.gitattributes new file mode 100644 index 0000000000..fcadb2cf97 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/.gitignore b/vendor/github.com/go-testfixtures/testfixtures/v3/.gitignore new file mode 100644 index 0000000000..8772d9ba0c --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/.gitignore @@ -0,0 +1,29 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +*.sqlite3 +.env +/testfixtures +/dist diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/.goreleaser.yml b/vendor/github.com/go-testfixtures/testfixtures/v3/.goreleaser.yml new file mode 100644 index 0000000000..cab247177d --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/.goreleaser.yml @@ -0,0 +1,41 @@ +build: + binary: testfixtures + main: ./cmd/testfixtures + goos: + - windows + - darwin + - linux + goarch: + - 386 + - amd64 + ignore: + - goos: darwin + goarch: 386 + flags: + - -tags=sqlite + +archives: + - name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}" + format_overrides: + - goos: windows + format: zip + +release: + draft: true + +snapshot: + name_template: "{{.Tag}}" + +checksum: + name_template: "testfixtures_checksums.txt" + +nfpms: + - vendor: testfixtures + homepage: https://github.com/go-testfixtures/testfixtures + maintainer: Andrey Nering <andrey.nering@gmail.com> + description: Ruby on Rails like test fixtures for Go. + license: MIT + formats: + - deb + - rpm + file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}" diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/.sample.env b/vendor/github.com/go-testfixtures/testfixtures/v3/.sample.env new file mode 100644 index 0000000000..fcc7cd0c74 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/.sample.env @@ -0,0 +1,4 @@ +PG_CONN_STRING="user=postgres dbname=testfixtures_test sslmode=disable" +MYSQL_CONN_STRING="root:@/testfixtures_test?multiStatements=true" +SQLITE_CONN_STRING="testdb.sqlite3" +SQLSERVER_CONN_STRING="server=localhost\SQLExpress;database=testfixtures_test;user id=sa;password=sqlserver;encrypt=disable" diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/CHANGELOG.md b/vendor/github.com/go-testfixtures/testfixtures/v3/CHANGELOG.md new file mode 100644 index 0000000000..2b8aa4b70a --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/CHANGELOG.md @@ -0,0 +1,93 @@ +# Changelog + +## v3.2.0 - 2020-05-10 + +- Add support for loading multiple files and directories + ([#65](https://github.com/go-testfixtures/testfixtures/pull/65)). + +## v3.1.2 - 2020-04-26 + +- Dump: Fix column order in generated YAML files + ([#62](https://github.com/go-testfixtures/testfixtures/pull/62)). + +## v3.1.1 - 2020-01-11 + +- testfixtures now work with both `mssql` and `sqlserver` drivers. + Note that [the `mssql` one is deprecated](https://github.com/denisenkom/go-mssqldb#deprecated), + though. So try to migrate to `sqlserver` once possible. + +## v3.1.0 - 2020-01-09 + +- Using `sqlserver` driver instead of the deprecated `mssql` + ([#58](https://github.com/go-testfixtures/testfixtures/pull/58)). + +## v3.0.0 - 2019-12-26 + +### Breaking changes + +- The import path changed from `gopkg.in/testfixtures.v2` to + `github.com/go-testfixtures/testfixtures/v3`. +- This package no longer support Oracle databases. This decision was + taken because too few people actually used this package with Oracle and it + was the most difficult to test (we didn't run on CI due the lack of an + official Docker image, etc). +- The public API was totally rewritten to be more flexible and ideomatic. + It now uses functional options. It differs from v2, but should be easy + enough to upgrade. +- Some deprecated APIs from v2 were removed as well. +- This now requires Go >= 1.13. + +### New features + +- We now have a CLI so you can easily use testfixtures to load a sample + database from fixtures if you want. +- Templating via [text/template](https://golang.org/pkg/text/template/) + is now available. This allows some fancier use cases like generating data + or specific columns dynamically. +- It's now possible to choose which time zone to use when parsing timestamps + from fixtures. The default is the same as before, whatever is set on + `time.Local`. +- Errors now use the new `%w` verb only available on Go >= 1.13. + +### MISC + +- Travis and AppVeyor are gone. We're using GitHub Actions exclusively now. + The whole suite is ran inside Docker (with help of Docker Compose), so it's + easy to run tests locally as well. + +Check the new README for some examples! + +## v2.6.0 - 2019-10-24 + +- Add support for TimescaleDB + ([#53](https://github.com/go-testfixtures/testfixtures/pull/53)). + +## v2.5.3 - 2018-12-15 + +- Fixes related to use of foreign key pragmas on MySQL (#43). + +## v2.5.2 - 2018-11-25 + +- This library now supports [Go Modules](https://github.com/golang/go/wiki/Modules); +- Also allow `.yaml` (as an alternative to `.yml`) as the file extension (#42). + +## v2.5.1 - 2018-11-04 + +- Allowing disabling reset of PostgreSQL sequences (#38). + +## v2.5.0 - 2018-09-07 + +- Add public function DetectTestDatabase (#35, #36). + +## v2.4.5 - 2018-07-07 + +- Fix for MySQL/MariaDB: ignoring views on operations that should be run only on tables (#33). + +## v2.4.4 - 2018-07-02 + +- Fix for multiple schemas on Microsoft SQL Server (#29 and #30); +- Configuring AppVeyor CI to also test for Microsoft SQL Server. + +--- + +Sorry, we don't have changelog for older releases 😢. diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/Dockerfile b/vendor/github.com/go-testfixtures/testfixtures/v3/Dockerfile new file mode 100644 index 0000000000..e33dafb07b --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/Dockerfile @@ -0,0 +1,9 @@ +FROM golang:1.14-alpine + +RUN apk update +RUN apk add alpine-sdk + +WORKDIR /testfixtures +COPY . . + +RUN go mod download diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/LICENSE b/vendor/github.com/go-testfixtures/testfixtures/v3/LICENSE new file mode 100644 index 0000000000..894ee77ed2 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Andrey Nering + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/README.md b/vendor/github.com/go-testfixtures/testfixtures/v3/README.md new file mode 100644 index 0000000000..56ca8aa455 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/README.md @@ -0,0 +1,483 @@ +# testfixtures + +[![GoDoc](https://godoc.org/github.com/go-testfixtures/testfixtures?status.svg)][doc] + +> ***Warning***: this package will wipe the database data before loading the +fixtures! It is supposed to be used on a test database. Please, double check +if you are running it against the correct database. + +> **TIP**: There are options not described in this README page. It's +> recommended that you also check [the documentation][doc]. + +Writing tests is hard, even more when you have to deal with an SQL database. +This package aims to make writing functional tests for web apps written in +Go easier. + +Basically this package mimics the ["Ruby on Rails' way"][railstests] of writing tests +for database applications, where sample data is kept in fixtures files. Before +the execution of every test, the test database is cleaned and the fixture data +is loaded into the database. + +The idea is running tests against a real database, instead of relying in mocks, +which is boring to setup and may lead to production bugs not being caught in +the tests. + +## Installation + +First, import it like this: + +```go +import ( + "github.com/go-testfixtures/testfixtures/v3" +) +``` + +## Usage + +Create a folder for the fixture files. Each file should contain data for a +single table and have the name `<table_name>.yml`: + +``` +myapp/ + myapp.go + myapp_test.go + ... + fixtures/ + posts.yml + comments.yml + tags.yml + posts_tags.yml + ... +``` + +The file would look like this (it can have as many record you want): + +```yml +# comments.yml +- id: 1 + post_id: 1 + content: A comment... + author_name: John Doe + author_email: john@doe.com + created_at: 2020-12-31 23:59:59 + updated_at: 2020-12-31 23:59:59 + +- id: 2 + post_id: 2 + content: Another comment... + author_name: John Doe + author_email: john@doe.com + created_at: 2020-12-31 23:59:59 + updated_at: 2020-12-31 23:59:59 + +# ... +``` + +An YAML object or array will be converted to JSON. It will be stored on a native +JSON type like JSONB on PostgreSQL or as a TEXT or VARCHAR column on other +databases. + +```yml +- id: 1 + post_attributes: + author: John Due + author_email: john@due.com + title: "..." + tags: + - programming + - go + - testing + post: "..." +``` + +If you need to write raw SQL, probably to call a function, prefix the value +of the column with `RAW=`: + +```yml +- id: 1 + uuid_column: RAW=uuid_generate_v4() + postgis_type_column: RAW=ST_GeomFromText('params...') + created_at: RAW=NOW() + updated_at: RAW=NOW() +``` + +Your tests would look like this: + +```go +package myapp + +import ( + "database/sql" + + _ "github.com/lib/pq" + "github.com/go-testfixtures/testfixtures/v3" +) + +var ( + db *sql.DB + fixtures *testfixtures.Loader +) + +func TestMain(m *testing.M) { + var err error + + // Open connection to the test database. + // Do NOT import fixtures in a production database! + // Existing data would be deleted. + db, err = sql.Open("postgres", "dbname=myapp_test") + if err != nil { + ... + } + + fixtures, err := testfixtures.New( + testfixtures.Database(db), // You database connection + testfixtures.Dialect("postgres"), // Available: "postgresql", "timescaledb", "mysql", "mariadb", "sqlite" and "sqlserver" + testfixtures.Directory("testdata/fixtures"), // the directory containing the YAML files + ) + if err != nil { + ... + } + + os.Exit(m.Run()) +} + +func prepareTestDatabase() { + if err := fixtures.Load(); err != nil { + ... + } +} + +func TestX(t *testing.T) { + prepareTestDatabase() + + // Your test here ... +} + +func TestY(t *testing.T) { + prepareTestDatabase() + + // Your test here ... +} + +func TestZ(t *testing.T) { + prepareTestDatabase() + + // Your test here ... +} +``` + +Alternatively, you can use the `Files` option, to specify which +files you want to load into the database: + +```go +fixtures, err := testfixtures.New( + testfixtures.Database(db), + testfixtures.Dialect("postgres"), + testfixtures.Files( + "fixtures/orders.yml", + "fixtures/customers.yml", + ), +) +if err != nil { + ... +} + +fixtures, err := testfixtures.NewFiles(db, &testfixtures.PostgreSQL{}, + "fixtures/orders.yml", + "fixtures/customers.yml", + // add as many files you want +) +if err != nil { + ... +} +``` + +With `Paths` option, you can specify the paths that fixtures will load +from. Path can be directory or file. If directory, we will search YAML files +in it. + +```go +fixtures, err := testfixtures.New( + testfixtures.Database(db), + testfixtures.Dialect("postgres"), + testfixtures.Paths( + "fixtures/orders.yml", + "fixtures/customers.yml", + "common_fixtures/users" + ), +) +if err != nil { + ... +} +``` + +## Security check + +In order to prevent you from accidentally wiping the wrong database, this +package will refuse to load fixtures if the database name (or database +filename for SQLite) doesn't contains "test". If you want to disable this +check, use: + +```go +testfixtures.New( + ... + testfixtures.DangerousSkipTestDatabaseCheck(), +) +``` + +## Sequences + +For PostgreSQL, this package also resets all sequences to a high +number to prevent duplicated primary keys while running the tests. +The default is 10000, but you can change that with: + +```go +testfixtures.New( + ... + testfixtures.ResetSequencesTo(10000), +) +``` + +Or, if you want to skip the reset of sequences entirely: + +```go +testfixtures.New( + ... + testfixtures.SkipResetSequences(), +) +``` + +## Compatible databases + +### PostgreSQL / TimescaleDB + +This package has two approaches to disable foreign keys while importing fixtures +for PostgreSQL databases: + +#### With `DISABLE TRIGGER` + +This is the default approach. For that use: + +```go +testfixtures.New( + ... + testfixtures.Dialect("postgres"), // or "timescaledb" +) +``` + +With the above snippet this package will use `DISABLE TRIGGER` to temporarily +disabling foreign key constraints while loading fixtures. This work with any +version of PostgreSQL, but it is **required** to be connected in the database +as a SUPERUSER. You can make a PostgreSQL user a SUPERUSER with: + +```sql +ALTER USER your_user SUPERUSER; +``` + +#### With `ALTER CONSTRAINT` + +This approach don't require to be connected as a SUPERUSER, but only work with +PostgreSQL versions >= 9.4. Try this if you are getting foreign key violation +errors with the previous approach. It is as simple as using: + +```go +testfixtures.New( + ... + testfixtures.Dialect("postgres"), + testfixtures.UseAlterConstraint(), +) +``` + +Tested using the [github.com/lib/pq](https://github.com/lib/pq) driver. + +### MySQL / MariaDB + +Just make sure the connection string have +[the multistatement parameter](https://github.com/go-sql-driver/mysql#multistatements) +set to true, and use: + +```go +testfixtures.New( + ... + testfixtures.Dialect("mysql"), // or "mariadb" +) +``` + +Tested using the [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) driver. + +### SQLite + +SQLite is also supported. It is recommended to create foreign keys as +`DEFERRABLE` (the default) to prevent problems. See more +[on the SQLite documentation](https://www.sqlite.org/foreignkeys.html#fk_deferred). +(Foreign key constraints are no-op by default on SQLite, but enabling it is +recommended). + +```go +testfixtures.New( + ... + testfixtures.Dialect("sqlite"), +) +``` + +Tested using the [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) driver. + +### Microsoft SQL Server + +SQL Server support requires SQL Server >= 2008. Inserting on `IDENTITY` columns +are handled as well. Just make sure you are logged in with a user with +`ALTER TABLE` permission. + +```go +testfixtures.New( + ... + testfixtures.Dialect("sqlserver"), +) +``` + +Tested using the `mssql` and `sqlserver` drivers from the +[github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) lib. + +## Templating + +Testfixtures supports templating, but it's disabled by default. Most people +won't need it, but it may be useful to dynamically generate data. + +Enable it by doing: + +```go +testfixtures.New( + ... + testfixtures.Template(), + + // the above options are optional + TemplateFuncs(...), + TemplateDelims("{{", "}}"), + TemplateOptions("missingkey=zero"), + TemplateData(...), +) +``` + +The YAML file could look like this: + +```yaml +# It's possible generate values... +- id: {{sha256 "my-awesome-post}} + title: My Awesome Post + text: {{randomText}} + +# ... or records +{{range $post := $.Posts}} +- id: {{$post.Id}} + title: {{$post.Title}} + text: {{$post.Text}} +{{end}} +``` + +## Generating fixtures for a existing database + +The following code will generate a YAML file for each table of the database +into a given folder. It may be useful to boostrap a test scenario from a sample +database of your app. + +```go +dumper, err := testfixtures.NewDumper( + testfixtures.DumpDatabase(db), + testfixtures.DumpDialect("postgres"), // or your database of choice + testfixtures.DumpDirectory("tmp/fixtures"), + textfixtures.DumpTables( // optional, will dump all table if not given + "posts", + "comments", + "tags", + ) +) +if err != nil { + ... +} +if err := dumper.Dump(); err != nil { + ... +} +``` + +> This was intended to run in small sample databases. It will likely break +if run in a production/big database. + +## Gotchas + +### Parallel testing + +This library doesn't yet support running tests in parallel! Running tests +in parallel can result in random data being present in the database, which +will likely cause tests to randomly/intermittently fail. + +This is specially tricky since it's not immediately clear that `go test ./...` +run tests for each package in parallel. If more than one package use this +library, you can face this issue. Please, use `go test -p 1 ./...` or run tests +for each package in separated commands to fix this issue. + +If you're looking into being able to run tests in parallel you can try using +testfixtures together with the [txdb][gotxdb] package, which allows wrapping +each test run in a transaction. + +## CLI + +We also have a CLI to load fixtures in a given database. +Grab it from the [releases page](https://github.com/go-testfixtures/testfixtures/releases) +and use it like: + +```bash +testfixtures -d postgres -c "postgres://user:password@localhost/database" -D testdata/fixtures +``` + +The connection string changes for each database driver. + +Use `--help` for all flags. + +## Contributing + +We recommend you to [install Task](https://taskfile.dev/#/installation) and +Docker before contributing to this package, since some stuff is automated +using these tools. + +It's recommended to use Docker Compose to run tests, since it runs tests for +all supported databases once. To do that you just need to run: + +```bash +task docker +``` + +But if you want to run tests locally, copy the `.sample.env` file as `.env` +and edit it according to your database setup. You'll need to create a database +(likely names `testfixtures_test`) before continuing. Then run the command +for the database you want to run tests against: + +```bash +task test:pg # PostgreSQL +task test:mysql # MySQL +task test:sqlite # SQLite +task test:sqlserver # Microsoft SQL Server +``` + +GitHub Actions (CI) runs the same Docker setup available locally. + +## Alternatives + +If you don't think using fixtures is a good idea, you can try one of these +packages instead: + +- [factory-go][factorygo]: Factory for Go. Inspired by Python's Factory Boy +and Ruby's Factory Girl +- [go-txdb (Single transaction SQL driver for Go)][gotxdb]: Use a single +database transaction for each functional test, so you can rollback to +previous state between tests to have the same database state in all tests +- [go-sqlmock][gosqlmock]: A mock for the sql.DB interface. This allow you to +unit test database code without having to connect to a real database +- [dbcleaner][dbcleaner] - Clean database for testing, inspired by +database_cleaner for Ruby + +[doc]: https://pkg.go.dev/github.com/go-testfixtures/testfixtures/v3?tab=doc +[railstests]: http://guides.rubyonrails.org/testing.html#the-test-database +[gotxdb]: https://github.com/DATA-DOG/go-txdb +[gosqlmock]: https://github.com/DATA-DOG/go-sqlmock +[factorygo]: https://github.com/bluele/factory-go +[dbcleaner]: https://github.com/khaiql/dbcleaner diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/Taskfile.yml b/vendor/github.com/go-testfixtures/testfixtures/v3/Taskfile.yml new file mode 100644 index 0000000000..a4ae1c899e --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/Taskfile.yml @@ -0,0 +1,59 @@ +# https://taskfile.org + +version: '2' + +tasks: + build: + cmds: + - go build -v -tags sqlite -o ./testfixtures{{exeExt}} ./cmd/testfixtures + + test-cli: + cmds: + - ./testfixtures -d sqlite -c testdb.sqlite3 -D testdata/fixtures + + test:pg: + desc: Test PostgreSQL + cmds: + - task: test-db + vars: {DATABASE: postgresql} + + test:mysql: + desc: Test MySQL + cmds: + - task: test:db + vars: {DATABASE: mysql} + + test:sqlite: + desc: Test SQLite + cmds: + - task: test-db + vars: {DATABASE: sqlite} + + test:sqlserver: + desc: Test SQLServer + cmds: + - task: test-db + vars: {DATABASE: sqlserver} + + test-db: + cmds: + - go test -v -tags {{.DATABASE}} + + goreleaser:test: + desc: Tests release process without publishing + cmds: + - goreleaser --snapshot --rm-dist + + docker: + cmds: + - task: docker:build + - task: docker:test + + docker:build: + cmds: + - docker build -t testfixtures . + + docker:test: + cmds: + - docker-compose down -v + - docker-compose run testfixtures go test -v -tags 'postgresql sqlite mysql sqlserver' diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/docker-compose.yml b/vendor/github.com/go-testfixtures/testfixtures/v3/docker-compose.yml new file mode 100644 index 0000000000..44afd56e1c --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/docker-compose.yml @@ -0,0 +1,37 @@ +version: '3' + +services: + testfixtures: + image: testfixtures + depends_on: + - postgresql + - mysql + - sqlserver + environment: + PGPASSWORD: postgres + PG_CONN_STRING: host=postgresql user=postgres dbname=testfixtures_test port=5432 sslmode=disable + + MYSQL_CONN_STRING: root:mysql@tcp(mysql)/testfixtures_test?multiStatements=true + + SQLITE_CONN_STRING: testfixtures_test.sqlite3 + + SQLSERVER_CONN_STRING: server=sqlserver;database=master;user id=sa;password=SQL@1server;encrypt=disable + + postgresql: + image: postgres:12.1-alpine + environment: + POSTGRES_DB: testfixtures_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + + mysql: + image: mysql:8.0 + environment: + MYSQL_DATABASE: testfixtures_test + MYSQL_ROOT_PASSWORD: mysql + + sqlserver: + image: mcr.microsoft.com/mssql/server:2019-latest + environment: + ACCEPT_EULA: 'Y' + SA_PASSWORD: SQL@1server diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/dump.go b/vendor/github.com/go-testfixtures/testfixtures/v3/dump.go new file mode 100644 index 0000000000..fe6f63350f --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/dump.go @@ -0,0 +1,165 @@ +package testfixtures + +import ( + "database/sql" + "fmt" + "os" + "path/filepath" + "unicode/utf8" + + "gopkg.in/yaml.v2" +) + +// Dumper is resposible for dumping fixtures from the database into a +// directory. +type Dumper struct { + db *sql.DB + helper helper + dir string + + tables []string +} + +// NewDumper creates a new dumper with the given options. +// +// The "DumpDatabase", "DumpDialect" and "DumpDirectory" options are required. +func NewDumper(options ...func(*Dumper) error) (*Dumper, error) { + d := &Dumper{} + + for _, option := range options { + if err := option(d); err != nil { + return nil, err + } + } + + return d, nil +} + +// DumpDatabase sets the database to be dumped. +func DumpDatabase(db *sql.DB) func(*Dumper) error { + return func(d *Dumper) error { + d.db = db + return nil + } +} + +// DumpDialect informs Loader about which database dialect you're using. +// +// Possible options are "postgresql", "timescaledb", "mysql", "mariadb", +// "sqlite" and "sqlserver". +func DumpDialect(dialect string) func(*Dumper) error { + return func(d *Dumper) error { + h, err := helperForDialect(dialect) + if err != nil { + return err + } + d.helper = h + return nil + } +} + +// DumpDirectory sets the directory where the fixtures files will be created. +func DumpDirectory(dir string) func(*Dumper) error { + return func(d *Dumper) error { + d.dir = dir + return nil + } +} + +// DumpTables allows you to choose which tables you want to dump. +// +// If not informed, Dumper will dump all tables by default. +func DumpTables(tables ...string) func(*Dumper) error { + return func(d *Dumper) error { + d.tables = tables + return nil + } +} + +// Dump dumps the databases as YAML fixtures. +func (d *Dumper) Dump() error { + tables := d.tables + if len(tables) == 0 { + var err error + tables, err = d.helper.tableNames(d.db) + if err != nil { + return err + } + } + + for _, table := range tables { + if err := d.dumpTable(table); err != nil { + return err + } + } + return nil +} + +func (d *Dumper) dumpTable(table string) error { + query := fmt.Sprintf("SELECT * FROM %s", d.helper.quoteKeyword(table)) + + stmt, err := d.db.Prepare(query) + if err != nil { + return err + } + defer stmt.Close() + + rows, err := stmt.Query() + if err != nil { + return err + } + defer rows.Close() + + columns, err := rows.Columns() + if err != nil { + return err + } + + fixtures := make([]yaml.MapSlice, 0, 10) + for rows.Next() { + entries := make([]interface{}, len(columns)) + entryPtrs := make([]interface{}, len(entries)) + for i := range entries { + entryPtrs[i] = &entries[i] + } + if err := rows.Scan(entryPtrs...); err != nil { + return err + } + + entryMap := make([]yaml.MapItem, len(entries)) + for i, column := range columns { + entryMap[i] = yaml.MapItem{ + Key: column, + Value: convertValue(entries[i]), + } + } + fixtures = append(fixtures, entryMap) + } + if err = rows.Err(); err != nil { + return err + } + + filePath := filepath.Join(d.dir, table+".yml") + f, err := os.Create(filePath) + if err != nil { + return err + } + defer f.Close() + + data, err := yaml.Marshal(fixtures) + if err != nil { + return err + } + _, err = f.Write(data) + return err +} + +func convertValue(value interface{}) interface{} { + switch v := value.(type) { + case []byte: + if utf8.Valid(v) { + return string(v) + } + } + return value +} diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/go.mod b/vendor/github.com/go-testfixtures/testfixtures/v3/go.mod new file mode 100644 index 0000000000..b5f0ae0eea --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/go.mod @@ -0,0 +1,14 @@ +module github.com/go-testfixtures/testfixtures/v3 + +require ( + github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 + github.com/go-sql-driver/mysql v1.4.1 + github.com/joho/godotenv v1.3.0 + github.com/lib/pq v1.3.0 + github.com/mattn/go-sqlite3 v2.0.2+incompatible + github.com/spf13/pflag v1.0.5 + google.golang.org/appengine v1.3.0 // indirect + gopkg.in/yaml.v2 v2.2.7 +) + +go 1.13 diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/go.sum b/vendor/github.com/go-testfixtures/testfixtures/v3/go.sum new file mode 100644 index 0000000000..38616db809 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/go.sum @@ -0,0 +1,26 @@ +github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 h1:OGNva6WhsKst5OZf7eZOklDztV3hwtTHovdrLHV+MsA= +github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U= +github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/helper.go b/vendor/github.com/go-testfixtures/testfixtures/v3/helper.go new file mode 100644 index 0000000000..06ba5b24e3 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/helper.go @@ -0,0 +1,71 @@ +package testfixtures + +import ( + "database/sql" + "fmt" +) + +const ( + paramTypeDollar = iota + 1 + paramTypeQuestion + paramTypeAtSign +) + +type loadFunction func(tx *sql.Tx) error + +type helper interface { + init(*sql.DB) error + disableReferentialIntegrity(*sql.DB, loadFunction) error + paramType() int + databaseName(queryable) (string, error) + tableNames(queryable) ([]string, error) + isTableModified(queryable, string) (bool, error) + afterLoad(queryable) error + quoteKeyword(string) string + whileInsertOnTable(*sql.Tx, string, func() error) error +} + +type queryable interface { + Exec(string, ...interface{}) (sql.Result, error) + Query(string, ...interface{}) (*sql.Rows, error) + QueryRow(string, ...interface{}) *sql.Row +} + +// batchSplitter is an interface with method which returns byte slice for +// splitting SQL batches. This need to split sql statements and run its +// separately. +// +// For Microsoft SQL Server batch splitter is "GO". For details see +// https://docs.microsoft.com/en-us/sql/t-sql/language-elements/sql-server-utilities-statements-go +type batchSplitter interface { + splitter() []byte +} + +var ( + _ helper = &mySQL{} + _ helper = &postgreSQL{} + _ helper = &sqlite{} + _ helper = &sqlserver{} +) + +type baseHelper struct{} + +func (baseHelper) init(_ *sql.DB) error { + return nil +} + +func (baseHelper) quoteKeyword(str string) string { + return fmt.Sprintf(`"%s"`, str) +} + +func (baseHelper) whileInsertOnTable(_ *sql.Tx, _ string, fn func() error) error { + return fn() +} + +func (baseHelper) isTableModified(_ queryable, _ string) (bool, error) { + return true, nil +} + +func (baseHelper) afterLoad(_ queryable) error { + return nil +} diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/json.go b/vendor/github.com/go-testfixtures/testfixtures/v3/json.go new file mode 100644 index 0000000000..f954a17a7f --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/json.go @@ -0,0 +1,44 @@ +package testfixtures + +import ( + "database/sql/driver" + "encoding/json" +) + +var ( + _ driver.Valuer = jsonArray{} + _ driver.Valuer = jsonMap{} +) + +type jsonArray []interface{} + +func (a jsonArray) Value() (driver.Value, error) { + return json.Marshal(a) +} + +type jsonMap map[string]interface{} + +func (m jsonMap) Value() (driver.Value, error) { + return json.Marshal(m) +} + +// Go refuses to convert map[interface{}]interface{} to JSON because JSON only support string keys +// So it's necessary to recursively convert all map[interface]interface{} to map[string]interface{} +func recursiveToJSON(v interface{}) (r interface{}) { + switch v := v.(type) { + case []interface{}: + for i, e := range v { + v[i] = recursiveToJSON(e) + } + r = jsonArray(v) + case map[interface{}]interface{}: + newMap := make(map[string]interface{}, len(v)) + for k, e := range v { + newMap[k.(string)] = recursiveToJSON(e) + } + r = jsonMap(newMap) + default: + r = v + } + return +} diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/mysql.go b/vendor/github.com/go-testfixtures/testfixtures/v3/mysql.go new file mode 100644 index 0000000000..2d1d890207 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/mysql.go @@ -0,0 +1,131 @@ +package testfixtures + +import ( + "database/sql" + "fmt" +) + +type mySQL struct { + baseHelper + tables []string + tablesChecksum map[string]int64 +} + +func (h *mySQL) init(db *sql.DB) error { + var err error + h.tables, err = h.tableNames(db) + if err != nil { + return err + } + + return nil +} + +func (*mySQL) paramType() int { + return paramTypeQuestion +} + +func (*mySQL) quoteKeyword(str string) string { + return fmt.Sprintf("`%s`", str) +} + +func (*mySQL) databaseName(q queryable) (string, error) { + var dbName string + err := q.QueryRow("SELECT DATABASE()").Scan(&dbName) + return dbName, err +} + +func (h *mySQL) tableNames(q queryable) ([]string, error) { + query := ` + SELECT table_name + FROM information_schema.tables + WHERE table_schema = ? + AND table_type = 'BASE TABLE'; + ` + dbName, err := h.databaseName(q) + if err != nil { + return nil, err + } + + rows, err := q.Query(query, dbName) + if err != nil { + return nil, err + } + defer rows.Close() + + var tables []string + for rows.Next() { + var table string + if err = rows.Scan(&table); err != nil { + return nil, err + } + tables = append(tables, table) + } + if err = rows.Err(); err != nil { + return nil, err + } + return tables, nil + +} + +func (h *mySQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + if _, err = tx.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil { + return err + } + + err = loadFn(tx) + _, err2 := tx.Exec("SET FOREIGN_KEY_CHECKS = 1") + if err != nil { + return err + } + if err2 != nil { + return err2 + } + + return tx.Commit() +} + +func (h *mySQL) isTableModified(q queryable, tableName string) (bool, error) { + checksum, err := h.getChecksum(q, tableName) + if err != nil { + return true, err + } + + oldChecksum := h.tablesChecksum[tableName] + + return oldChecksum == 0 || checksum != oldChecksum, nil +} + +func (h *mySQL) afterLoad(q queryable) error { + if h.tablesChecksum != nil { + return nil + } + + h.tablesChecksum = make(map[string]int64, len(h.tables)) + for _, t := range h.tables { + checksum, err := h.getChecksum(q, t) + if err != nil { + return err + } + h.tablesChecksum[t] = checksum + } + return nil +} + +func (h *mySQL) getChecksum(q queryable, tableName string) (int64, error) { + sql := fmt.Sprintf("CHECKSUM TABLE %s", h.quoteKeyword(tableName)) + var ( + table string + checksum int64 + ) + if err := q.QueryRow(sql).Scan(&table, &checksum); err != nil { + return 0, err + } + return checksum, nil +} diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/postgresql.go b/vendor/github.com/go-testfixtures/testfixtures/v3/postgresql.go new file mode 100644 index 0000000000..a8ccd1d949 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/postgresql.go @@ -0,0 +1,296 @@ +package testfixtures + +import ( + "database/sql" + "fmt" + "strings" +) + +type postgreSQL struct { + baseHelper + + useAlterConstraint bool + skipResetSequences bool + resetSequencesTo int64 + + tables []string + sequences []string + nonDeferrableConstraints []pgConstraint + tablesChecksum map[string]string +} + +type pgConstraint struct { + tableName string + constraintName string +} + +func (h *postgreSQL) init(db *sql.DB) error { + var err error + + h.tables, err = h.tableNames(db) + if err != nil { + return err + } + + h.sequences, err = h.getSequences(db) + if err != nil { + return err + } + + h.nonDeferrableConstraints, err = h.getNonDeferrableConstraints(db) + if err != nil { + return err + } + + return nil +} + +func (*postgreSQL) paramType() int { + return paramTypeDollar +} + +func (*postgreSQL) databaseName(q queryable) (string, error) { + var dbName string + err := q.QueryRow("SELECT current_database()").Scan(&dbName) + return dbName, err +} + +func (h *postgreSQL) tableNames(q queryable) ([]string, error) { + var tables []string + + sql := ` + SELECT pg_namespace.nspname || '.' || pg_class.relname + FROM pg_class + INNER JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace + WHERE pg_class.relkind = 'r' + AND pg_namespace.nspname NOT IN ('pg_catalog', 'information_schema') + AND pg_namespace.nspname NOT LIKE 'pg_toast%' + AND pg_namespace.nspname NOT LIKE '\_timescaledb%'; + ` + rows, err := q.Query(sql) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var table string + if err = rows.Scan(&table); err != nil { + return nil, err + } + tables = append(tables, table) + } + if err = rows.Err(); err != nil { + return nil, err + } + return tables, nil +} + +func (h *postgreSQL) getSequences(q queryable) ([]string, error) { + const sql = ` + SELECT pg_namespace.nspname || '.' || pg_class.relname AS sequence_name + FROM pg_class + INNER JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace + WHERE pg_class.relkind = 'S' + AND pg_namespace.nspname NOT LIKE '\_timescaledb%' + ` + + rows, err := q.Query(sql) + if err != nil { + return nil, err + } + defer rows.Close() + + var sequences []string + for rows.Next() { + var sequence string + if err = rows.Scan(&sequence); err != nil { + return nil, err + } + sequences = append(sequences, sequence) + } + if err = rows.Err(); err != nil { + return nil, err + } + return sequences, nil +} + +func (*postgreSQL) getNonDeferrableConstraints(q queryable) ([]pgConstraint, error) { + var constraints []pgConstraint + + sql := ` + SELECT table_schema || '.' || table_name, constraint_name + FROM information_schema.table_constraints + WHERE constraint_type = 'FOREIGN KEY' + AND is_deferrable = 'NO' + AND table_schema NOT LIKE '\_timescaledb%' + ` + rows, err := q.Query(sql) + if err != nil { + return nil, err + } + + defer rows.Close() + for rows.Next() { + var constraint pgConstraint + if err = rows.Scan(&constraint.tableName, &constraint.constraintName); err != nil { + return nil, err + } + constraints = append(constraints, constraint) + } + if err = rows.Err(); err != nil { + return nil, err + } + return constraints, nil +} + +func (h *postgreSQL) disableTriggers(db *sql.DB, loadFn loadFunction) (err error) { + defer func() { + // re-enable triggers after load + var sql string + for _, table := range h.tables { + sql += fmt.Sprintf("ALTER TABLE %s ENABLE TRIGGER ALL;", h.quoteKeyword(table)) + } + if _, err2 := db.Exec(sql); err2 != nil && err == nil { + err = err2 + } + }() + + tx, err := db.Begin() + if err != nil { + return err + } + + var sql string + for _, table := range h.tables { + sql += fmt.Sprintf("ALTER TABLE %s DISABLE TRIGGER ALL;", h.quoteKeyword(table)) + } + if _, err = tx.Exec(sql); err != nil { + return err + } + + if err = loadFn(tx); err != nil { + tx.Rollback() + return err + } + + return tx.Commit() +} + +func (h *postgreSQL) makeConstraintsDeferrable(db *sql.DB, loadFn loadFunction) (err error) { + defer func() { + // ensure constraint being not deferrable again after load + var sql string + for _, constraint := range h.nonDeferrableConstraints { + sql += fmt.Sprintf("ALTER TABLE %s ALTER CONSTRAINT %s NOT DEFERRABLE;", h.quoteKeyword(constraint.tableName), h.quoteKeyword(constraint.constraintName)) + } + if _, err2 := db.Exec(sql); err2 != nil && err == nil { + err = err2 + } + }() + + var sql string + for _, constraint := range h.nonDeferrableConstraints { + sql += fmt.Sprintf("ALTER TABLE %s ALTER CONSTRAINT %s DEFERRABLE;", h.quoteKeyword(constraint.tableName), h.quoteKeyword(constraint.constraintName)) + } + if _, err := db.Exec(sql); err != nil { + return err + } + + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + if _, err = tx.Exec("SET CONSTRAINTS ALL DEFERRED"); err != nil { + return err + } + + if err = loadFn(tx); err != nil { + return err + } + + return tx.Commit() +} + +func (h *postgreSQL) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { + // ensure sequences being reset after load + if !h.skipResetSequences { + defer func() { + if err2 := h.resetSequences(db); err2 != nil && err == nil { + err = err2 + } + }() + } + + if h.useAlterConstraint { + return h.makeConstraintsDeferrable(db, loadFn) + } + return h.disableTriggers(db, loadFn) +} + +func (h *postgreSQL) resetSequences(db *sql.DB) error { + resetSequencesTo := h.resetSequencesTo + if resetSequencesTo == 0 { + resetSequencesTo = 10000 + } + + for _, sequence := range h.sequences { + _, err := db.Exec(fmt.Sprintf("SELECT SETVAL('%s', %d)", sequence, resetSequencesTo)) + if err != nil { + return err + } + } + return nil +} + +func (h *postgreSQL) isTableModified(q queryable, tableName string) (bool, error) { + checksum, err := h.getChecksum(q, tableName) + if err != nil { + return false, err + } + + oldChecksum := h.tablesChecksum[tableName] + + return oldChecksum == "" || checksum != oldChecksum, nil +} + +func (h *postgreSQL) afterLoad(q queryable) error { + if h.tablesChecksum != nil { + return nil + } + + h.tablesChecksum = make(map[string]string, len(h.tables)) + for _, t := range h.tables { + checksum, err := h.getChecksum(q, t) + if err != nil { + return err + } + h.tablesChecksum[t] = checksum + } + return nil +} + +func (h *postgreSQL) getChecksum(q queryable, tableName string) (string, error) { + sqlStr := fmt.Sprintf(` + SELECT md5(CAST((array_agg(t.*)) AS TEXT)) + FROM %s AS t + `, + h.quoteKeyword(tableName), + ) + + var checksum sql.NullString + if err := q.QueryRow(sqlStr).Scan(&checksum); err != nil { + return "", err + } + return checksum.String, nil +} + +func (*postgreSQL) quoteKeyword(s string) string { + parts := strings.Split(s, ".") + for i, p := range parts { + parts[i] = fmt.Sprintf(`"%s"`, p) + } + return strings.Join(parts, ".") +} diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/sqlite.go b/vendor/github.com/go-testfixtures/testfixtures/v3/sqlite.go new file mode 100644 index 0000000000..d3d3153415 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/sqlite.go @@ -0,0 +1,75 @@ +package testfixtures + +import ( + "database/sql" + "path/filepath" +) + +type sqlite struct { + baseHelper +} + +func (*sqlite) paramType() int { + return paramTypeQuestion +} + +func (*sqlite) databaseName(q queryable) (string, error) { + var seq int + var main, dbName string + err := q.QueryRow("PRAGMA database_list").Scan(&seq, &main, &dbName) + if err != nil { + return "", err + } + dbName = filepath.Base(dbName) + return dbName, nil +} + +func (*sqlite) tableNames(q queryable) ([]string, error) { + query := ` + SELECT name + FROM sqlite_master + WHERE type = 'table'; + ` + rows, err := q.Query(query) + if err != nil { + return nil, err + } + defer rows.Close() + + var tables []string + for rows.Next() { + var table string + if err = rows.Scan(&table); err != nil { + return nil, err + } + tables = append(tables, table) + } + if err = rows.Err(); err != nil { + return nil, err + } + return tables, nil +} + +func (*sqlite) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { + defer func() { + if _, err2 := db.Exec("PRAGMA defer_foreign_keys = OFF"); err2 != nil && err == nil { + err = err2 + } + }() + + if _, err = db.Exec("PRAGMA defer_foreign_keys = ON"); err != nil { + return err + } + + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + if err = loadFn(tx); err != nil { + return err + } + + return tx.Commit() +} diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/sqlserver.go b/vendor/github.com/go-testfixtures/testfixtures/v3/sqlserver.go new file mode 100644 index 0000000000..c862a3b62b --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/sqlserver.go @@ -0,0 +1,153 @@ +package testfixtures + +import ( + "database/sql" + "fmt" + "strings" +) + +type sqlserver struct { + baseHelper + + paramTypeCache int + tables []string +} + +func (h *sqlserver) init(db *sql.DB) error { + var err error + + // NOTE(@andreynering): The SQL Server lib (github.com/denisenkom/go-mssqldb) + // supports both the "?" style (when using the deprecated "mssql" driver) + // and the "@p1" style (when using the new "sqlserver" driver). + // + // Since we don't have a way to know which driver it's been used, + // this is a small hack to detect the allowed param style. + var v int + if err := db.QueryRow("SELECT ?", 1).Scan(&v); err == nil && v == 1 { + h.paramTypeCache = paramTypeQuestion + } else { + h.paramTypeCache = paramTypeAtSign + } + + h.tables, err = h.tableNames(db) + if err != nil { + return err + } + + return nil +} + +func (h *sqlserver) paramType() int { + return h.paramTypeCache +} + +func (*sqlserver) quoteKeyword(s string) string { + parts := strings.Split(s, ".") + for i, p := range parts { + parts[i] = fmt.Sprintf(`[%s]`, p) + } + return strings.Join(parts, ".") +} + +func (*sqlserver) databaseName(q queryable) (string, error) { + var dbName string + err := q.QueryRow("SELECT DB_NAME()").Scan(&dbName) + return dbName, err +} + +func (*sqlserver) tableNames(q queryable) ([]string, error) { + rows, err := q.Query("SELECT table_schema + '.' + table_name FROM information_schema.tables WHERE table_name <> 'spt_values'") + if err != nil { + return nil, err + } + defer rows.Close() + + var tables []string + for rows.Next() { + var table string + if err = rows.Scan(&table); err != nil { + return nil, err + } + tables = append(tables, table) + } + if err = rows.Err(); err != nil { + return nil, err + } + return tables, nil +} + +func (h *sqlserver) tableHasIdentityColumn(q queryable, tableName string) (bool, error) { + sql := fmt.Sprintf(` + SELECT COUNT(*) + FROM SYS.IDENTITY_COLUMNS + WHERE OBJECT_ID = OBJECT_ID('%s') + `, tableName) + var count int + if err := q.QueryRow(sql).Scan(&count); err != nil { + return false, err + } + return count > 0, nil + +} + +func (h *sqlserver) whileInsertOnTable(tx *sql.Tx, tableName string, fn func() error) (err error) { + hasIdentityColumn, err := h.tableHasIdentityColumn(tx, tableName) + if err != nil { + return err + } + if hasIdentityColumn { + defer func() { + _, err2 := tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s OFF", h.quoteKeyword(tableName))) + if err2 != nil && err == nil { + err = fmt.Errorf("testfixtures: could not disable identity insert: %w", err2) + } + }() + + _, err := tx.Exec(fmt.Sprintf("SET IDENTITY_INSERT %s ON", h.quoteKeyword(tableName))) + if err != nil { + return fmt.Errorf("testfixtures: could not enable identity insert: %w", err) + } + } + return fn() +} + +func (h *sqlserver) disableReferentialIntegrity(db *sql.DB, loadFn loadFunction) (err error) { + // ensure the triggers are re-enable after all + defer func() { + var sql string + for _, table := range h.tables { + sql += fmt.Sprintf("ALTER TABLE %s WITH CHECK CHECK CONSTRAINT ALL;", h.quoteKeyword(table)) + } + if _, err2 := db.Exec(sql); err2 != nil && err == nil { + err = err2 + } + }() + + var sql string + for _, table := range h.tables { + sql += fmt.Sprintf("ALTER TABLE %s NOCHECK CONSTRAINT ALL;", h.quoteKeyword(table)) + } + if _, err := db.Exec(sql); err != nil { + return err + } + + tx, err := db.Begin() + if err != nil { + return err + } + defer tx.Rollback() + + if err = loadFn(tx); err != nil { + return err + } + + return tx.Commit() +} + +// splitter is a batchSplitter interface implementation. We need it for +// SQL Server because commands like a `CREATE SCHEMA...` and a `CREATE TABLE...` +// could not be executed in the same batch. +// See https://docs.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/ms175502(v=sql.105)#rules-for-using-batches +func (*sqlserver) splitter() []byte { + return []byte("GO\n") +} diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/testfixtures.go b/vendor/github.com/go-testfixtures/testfixtures/v3/testfixtures.go new file mode 100644 index 0000000000..7125b38c1d --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/testfixtures.go @@ -0,0 +1,599 @@ +package testfixtures // import "github.com/go-testfixtures/testfixtures/v3" + +import ( + "bytes" + "database/sql" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "text/template" + "time" + + "gopkg.in/yaml.v2" +) + +// Loader is the responsible to loading fixtures. +type Loader struct { + db *sql.DB + helper helper + fixturesFiles []*fixtureFile + + skipTestDatabaseCheck bool + location *time.Location + + template bool + templateFuncs template.FuncMap + templateLeftDelim string + templateRightDelim string + templateOptions []string + templateData interface{} +} + +type fixtureFile struct { + path string + fileName string + content []byte + insertSQLs []insertSQL +} + +type insertSQL struct { + sql string + params []interface{} +} + +var ( + testDatabaseRegexp = regexp.MustCompile("(?i)test") + + errDatabaseIsRequired = fmt.Errorf("testfixtures: database is required") + errDialectIsRequired = fmt.Errorf("testfixtures: dialect is required") +) + +// New instantiates a new Loader instance. The "Database" and "Driver" +// options are required. +func New(options ...func(*Loader) error) (*Loader, error) { + l := &Loader{ + templateLeftDelim: "{{", + templateRightDelim: "}}", + templateOptions: []string{"missingkey=zero"}, + } + + for _, option := range options { + if err := option(l); err != nil { + return nil, err + } + } + + if l.db == nil { + return nil, errDatabaseIsRequired + } + if l.helper == nil { + return nil, errDialectIsRequired + } + + if err := l.helper.init(l.db); err != nil { + return nil, err + } + if err := l.buildInsertSQLs(); err != nil { + return nil, err + } + + return l, nil +} + +// Database sets an existing sql.DB instant to Loader. +func Database(db *sql.DB) func(*Loader) error { + return func(l *Loader) error { + l.db = db + return nil + } +} + +// Dialect informs Loader about which database dialect you're using. +// +// Possible options are "postgresql", "timescaledb", "mysql", "mariadb", +// "sqlite" and "sqlserver". +func Dialect(dialect string) func(*Loader) error { + return func(l *Loader) error { + h, err := helperForDialect(dialect) + if err != nil { + return err + } + l.helper = h + return nil + } +} + +func helperForDialect(dialect string) (helper, error) { + switch dialect { + case "postgres", "postgresql", "timescaledb": + return &postgreSQL{}, nil + case "mysql", "mariadb": + return &mySQL{}, nil + case "sqlite", "sqlite3": + return &sqlite{}, nil + case "mssql", "sqlserver": + return &sqlserver{}, nil + default: + return nil, fmt.Errorf(`testfixtures: unrecognized dialect "%s"`, dialect) + } +} + +// UseAlterConstraint If true, the contraint disabling will do +// using ALTER CONTRAINT sintax, only allowed in PG >= 9.4. +// If false, the constraint disabling will use DISABLE TRIGGER ALL, +// which requires SUPERUSER privileges. +// +// Only valid for PostgreSQL. Returns an error otherwise. +func UseAlterConstraint() func(*Loader) error { + return func(l *Loader) error { + pgHelper, ok := l.helper.(*postgreSQL) + if !ok { + return fmt.Errorf("testfixtures: UseAlterConstraint is only valid for PostgreSQL databases") + } + pgHelper.useAlterConstraint = true + return nil + } +} + +// SkipResetSequences prevents Loader from reseting sequences after loading +// fixtures. +// +// Only valid for PostgreSQL. Returns an error otherwise. +func SkipResetSequences() func(*Loader) error { + return func(l *Loader) error { + pgHelper, ok := l.helper.(*postgreSQL) + if !ok { + return fmt.Errorf("testfixtures: SkipResetSequences is only valid for PostgreSQL databases") + } + pgHelper.skipResetSequences = true + return nil + } +} + +// ResetSequencesTo sets the value the sequences will be reset to. +// +// Defaults to 10000. +// +// Only valid for PostgreSQL. Returns an error otherwise. +func ResetSequencesTo(value int64) func(*Loader) error { + return func(l *Loader) error { + pgHelper, ok := l.helper.(*postgreSQL) + if !ok { + return fmt.Errorf("testfixtures: ResetSequencesTo is only valid for PostgreSQL databases") + } + pgHelper.resetSequencesTo = value + return nil + } +} + +// DangerousSkipTestDatabaseCheck will make Loader not check if the database +// name contains "test". Use with caution! +func DangerousSkipTestDatabaseCheck() func(*Loader) error { + return func(l *Loader) error { + l.skipTestDatabaseCheck = true + return nil + } +} + +// Directory informs Loader to load YAML files from a given directory. +func Directory(dir string) func(*Loader) error { + return func(l *Loader) error { + fixtures, err := l.fixturesFromDir(dir) + if err != nil { + return err + } + l.fixturesFiles = append(l.fixturesFiles, fixtures...) + return nil + } +} + +// Files informs Loader to load a given set of YAML files. +func Files(files ...string) func(*Loader) error { + return func(l *Loader) error { + fixtures, err := l.fixturesFromFiles(files...) + if err != nil { + return err + } + l.fixturesFiles = append(l.fixturesFiles, fixtures...) + return nil + } +} + +// Paths inform Loader to load a given set of YAML files and directories. +func Paths(paths ...string) func(*Loader) error { + return func(l *Loader) error { + fixtures, err := l.fixturesFromPaths(paths...) + if err != nil { + return err + } + l.fixturesFiles = append(l.fixturesFiles, fixtures...) + return nil + } +} + +// Location makes Loader use the given location by default when parsing +// dates. If not given, by default it uses the value of time.Local. +func Location(location *time.Location) func(*Loader) error { + return func(l *Loader) error { + l.location = location + return nil + } +} + +// Template makes loader process each YAML file as an template using the +// text/template package. +// +// For more information on how templates work in Go please read: +// https://golang.org/pkg/text/template/ +// +// If not given the YAML files are parsed as is. +func Template() func(*Loader) error { + return func(l *Loader) error { + l.template = true + return nil + } +} + +// TemplateFuncs allow choosing which functions will be available +// when processing templates. +// +// For more information see: https://golang.org/pkg/text/template/#Template.Funcs +func TemplateFuncs(funcs template.FuncMap) func(*Loader) error { + return func(l *Loader) error { + if !l.template { + return fmt.Errorf(`testfixtures: the Template() options is required in order to use the TemplateFuns() option`) + } + + l.templateFuncs = funcs + return nil + } +} + +// TemplateDelims allow choosing which delimiters will be used for templating. +// This defaults to "{{" and "}}". +// +// For more information see https://golang.org/pkg/text/template/#Template.Delims +func TemplateDelims(left, right string) func(*Loader) error { + return func(l *Loader) error { + if !l.template { + return fmt.Errorf(`testfixtures: the Template() options is required in order to use the TemplateDelims() option`) + } + + l.templateLeftDelim = left + l.templateRightDelim = right + return nil + } +} + +// TemplateOptions allows you to specific which text/template options will +// be enabled when processing templates. +// +// This defaults to "missingkey=zero". Check the available options here: +// https://golang.org/pkg/text/template/#Template.Option +func TemplateOptions(options ...string) func(*Loader) error { + return func(l *Loader) error { + if !l.template { + return fmt.Errorf(`testfixtures: the Template() options is required in order to use the TemplateOptions() option`) + } + + l.templateOptions = options + return nil + } +} + +// TemplateData allows you to specify which data will be available +// when processing templates. Data is accesible by prefixing it with a "." +// like {{.MyKey}}. +func TemplateData(data interface{}) func(*Loader) error { + return func(l *Loader) error { + if !l.template { + return fmt.Errorf(`testfixtures: the Template() options is required in order to use the TemplateData() option`) + } + + l.templateData = data + return nil + } +} + +// EnsureTestDatabase returns an error if the database name does not contains +// "test". +func (l *Loader) EnsureTestDatabase() error { + dbName, err := l.helper.databaseName(l.db) + if err != nil { + return err + } + if !testDatabaseRegexp.MatchString(dbName) { + return fmt.Errorf(`testfixtures: database "%s" does not appear to be a test database`, dbName) + } + return nil +} + +// Load wipes and after load all fixtures in the database. +// if err := fixtures.Load(); err != nil { +// ... +// } +func (l *Loader) Load() error { + if !l.skipTestDatabaseCheck { + if err := l.EnsureTestDatabase(); err != nil { + return err + } + } + + err := l.helper.disableReferentialIntegrity(l.db, func(tx *sql.Tx) error { + for _, file := range l.fixturesFiles { + modified, err := l.helper.isTableModified(tx, file.fileNameWithoutExtension()) + if err != nil { + return err + } + if !modified { + continue + } + if err := file.delete(tx, l.helper); err != nil { + return err + } + + err = l.helper.whileInsertOnTable(tx, file.fileNameWithoutExtension(), func() error { + for j, i := range file.insertSQLs { + if _, err := tx.Exec(i.sql, i.params...); err != nil { + return &InsertError{ + Err: err, + File: file.fileName, + Index: j, + SQL: i.sql, + Params: i.params, + } + } + } + return nil + }) + if err != nil { + return err + } + } + return nil + }) + if err != nil { + return err + } + return l.helper.afterLoad(l.db) +} + +// InsertError will be returned if any error happens on database while +// inserting the record. +type InsertError struct { + Err error + File string + Index int + SQL string + Params []interface{} +} + +func (e *InsertError) Error() string { + return fmt.Sprintf( + "testfixtures: error inserting record: %v, on file: %s, index: %d, sql: %s, params: %v", + e.Err, + e.File, + e.Index, + e.SQL, + e.Params, + ) +} + +func (l *Loader) buildInsertSQLs() error { + for _, f := range l.fixturesFiles { + var records interface{} + if err := yaml.Unmarshal(f.content, &records); err != nil { + return fmt.Errorf("testfixtures: could not unmarshal YAML: %w", err) + } + + switch records := records.(type) { + case []interface{}: + f.insertSQLs = make([]insertSQL, 0, len(records)) + + for _, record := range records { + recordMap, ok := record.(map[interface{}]interface{}) + if !ok { + return fmt.Errorf("testfixtures: could not cast record: not a map[interface{}]interface{}") + } + + sql, values, err := l.buildInsertSQL(f, recordMap) + if err != nil { + return err + } + + f.insertSQLs = append(f.insertSQLs, insertSQL{sql, values}) + } + case map[interface{}]interface{}: + f.insertSQLs = make([]insertSQL, 0, len(records)) + + for _, record := range records { + recordMap, ok := record.(map[interface{}]interface{}) + if !ok { + return fmt.Errorf("testfixtures: could not cast record: not a map[interface{}]interface{}") + } + + sql, values, err := l.buildInsertSQL(f, recordMap) + if err != nil { + return err + } + + f.insertSQLs = append(f.insertSQLs, insertSQL{sql, values}) + } + default: + return fmt.Errorf("testfixtures: fixture is not a slice or map") + } + } + + return nil +} + +func (f *fixtureFile) fileNameWithoutExtension() string { + return strings.Replace(f.fileName, filepath.Ext(f.fileName), "", 1) +} + +func (f *fixtureFile) delete(tx *sql.Tx, h helper) error { + if _, err := tx.Exec(fmt.Sprintf("DELETE FROM %s", h.quoteKeyword(f.fileNameWithoutExtension()))); err != nil { + return fmt.Errorf(`testfixtures: could not clean table "%s": %w`, f.fileNameWithoutExtension(), err) + } + return nil +} + +func (l *Loader) buildInsertSQL(f *fixtureFile, record map[interface{}]interface{}) (sqlStr string, values []interface{}, err error) { + var ( + sqlColumns = make([]string, 0, len(record)) + sqlValues = make([]string, 0, len(record)) + i = 1 + ) + for key, value := range record { + keyStr, ok := key.(string) + if !ok { + err = fmt.Errorf("testfixtures: record map key is not a string") + return + } + + sqlColumns = append(sqlColumns, l.helper.quoteKeyword(keyStr)) + + // if string, try convert to SQL or time + // if map or array, convert to json + switch v := value.(type) { + case string: + if strings.HasPrefix(v, "RAW=") { + sqlValues = append(sqlValues, strings.TrimPrefix(v, "RAW=")) + continue + } + + if t, err := l.tryStrToDate(v); err == nil { + value = t + } + case []interface{}, map[interface{}]interface{}: + value = recursiveToJSON(v) + } + + switch l.helper.paramType() { + case paramTypeDollar: + sqlValues = append(sqlValues, fmt.Sprintf("$%d", i)) + case paramTypeQuestion: + sqlValues = append(sqlValues, "?") + case paramTypeAtSign: + sqlValues = append(sqlValues, fmt.Sprintf("@p%d", i)) + } + + values = append(values, value) + i++ + } + + sqlStr = fmt.Sprintf( + "INSERT INTO %s (%s) VALUES (%s)", + l.helper.quoteKeyword(f.fileNameWithoutExtension()), + strings.Join(sqlColumns, ", "), + strings.Join(sqlValues, ", "), + ) + return +} + +func (l *Loader) fixturesFromDir(dir string) ([]*fixtureFile, error) { + fileinfos, err := ioutil.ReadDir(dir) + if err != nil { + return nil, fmt.Errorf(`testfixtures: could not stat directory "%s": %w`, dir, err) + } + + files := make([]*fixtureFile, 0, len(fileinfos)) + + for _, fileinfo := range fileinfos { + fileExt := filepath.Ext(fileinfo.Name()) + if !fileinfo.IsDir() && (fileExt == ".yml" || fileExt == ".yaml") { + fixture := &fixtureFile{ + path: path.Join(dir, fileinfo.Name()), + fileName: fileinfo.Name(), + } + fixture.content, err = ioutil.ReadFile(fixture.path) + if err != nil { + return nil, fmt.Errorf(`testfixtures: could not read file "%s": %w`, fixture.path, err) + } + if err := l.processFileTemplate(fixture); err != nil { + return nil, err + } + files = append(files, fixture) + } + } + return files, nil +} + +func (l *Loader) fixturesFromFiles(fileNames ...string) ([]*fixtureFile, error) { + var ( + fixtureFiles = make([]*fixtureFile, 0, len(fileNames)) + err error + ) + + for _, f := range fileNames { + fixture := &fixtureFile{ + path: f, + fileName: filepath.Base(f), + } + fixture.content, err = ioutil.ReadFile(fixture.path) + if err != nil { + return nil, fmt.Errorf(`testfixtures: could not read file "%s": %w`, fixture.path, err) + } + if err := l.processFileTemplate(fixture); err != nil { + return nil, err + } + fixtureFiles = append(fixtureFiles, fixture) + } + + return fixtureFiles, nil +} + +func (l *Loader) fixturesFromPaths(paths ...string) ([]*fixtureFile, error) { + fixtureExtractor := func(p string, isDir bool) ([]*fixtureFile, error) { + if isDir { + return l.fixturesFromDir(p) + } + + return l.fixturesFromFiles(p) + } + + var fixtureFiles []*fixtureFile + + for _, p := range paths { + f, err := os.Stat(p) + if err != nil { + return nil, fmt.Errorf(`testfixtures: could not stat path "%s": %w`, p, err) + } + + fixtures, err := fixtureExtractor(p, f.IsDir()) + if err != nil { + return nil, err + } + + fixtureFiles = append(fixtureFiles, fixtures...) + } + + return fixtureFiles, nil +} + +func (l *Loader) processFileTemplate(f *fixtureFile) error { + if !l.template { + return nil + } + + t := template.New(""). + Funcs(l.templateFuncs). + Delims(l.templateLeftDelim, l.templateRightDelim). + Option(l.templateOptions...) + t, err := t.Parse(string(f.content)) + if err != nil { + return fmt.Errorf(`textfixtures: error on parsing template in %s: %w`, f.fileName, err) + } + + var buffer bytes.Buffer + if err := t.Execute(&buffer, l.templateData); err != nil { + return fmt.Errorf(`textfixtures: error on executing template in %s: %w`, f.fileName, err) + } + + f.content = buffer.Bytes() + return nil +} diff --git a/vendor/github.com/go-testfixtures/testfixtures/v3/time.go b/vendor/github.com/go-testfixtures/testfixtures/v3/time.go new file mode 100644 index 0000000000..3d50a98cc9 --- /dev/null +++ b/vendor/github.com/go-testfixtures/testfixtures/v3/time.go @@ -0,0 +1,43 @@ +package testfixtures + +import ( + "fmt" + "time" +) + +var timeFormats = [...]string{ + "2006-01-02", + "2006-01-02 15:04", + "2006-01-02 15:04:05", + "20060102", + "20060102 15:04", + "20060102 15:04:05", + "02/01/2006", + "02/01/2006 15:04", + "02/01/2006 15:04:05", + "2006-01-02T15:04-07:00", + "2006-01-02T15:04:05-07:00", + "2006-01-02T15:04:05Z07:00", + "2006-01-02 15:04:05Z07:00", + "2006-01-02T15:04:05Z0700", + "2006-01-02 15:04:05Z0700", + "2006-01-02T15:04:05Z07", + "2006-01-02 15:04:05Z07", + "2006-01-02 15:04:05 MST", +} + +func (l *Loader) tryStrToDate(s string) (time.Time, error) { + loc := l.location + if loc == nil { + loc = time.Local + } + + for _, f := range timeFormats { + t, err := time.ParseInLocation(f, s, loc) + if err != nil { + continue + } + return t, nil + } + return time.Time{}, fmt.Errorf(`testfixtures: could not convert string "%s" to time`, s) +} |