Add postgres support (#135)
Revert fixture fixes for postgres Use postgres connection string with spaces instead of url Fix label order Make postgres tests in ci less verbose Add sequence update script Skip resets in postgres Remove option to skip resets in postgres Make postgres tests in ci verboseq Update test fixtures database Fix file tests on postgres Add postgres options to sample config Make sure tests init test fixtures before running the actual tests Fix issues with IDs too big to fit in an int Fix duplicate auto incremented IDs Refactor / Fix team tests Refactor team member tests Fix team member create Fix label test Fix getting labels Fix test fixtures for postgresql Fix connection string params Disable ssl mode on postgres integration tests Disable ssl mode on postgres tests Use sprintf to create the connection string for postgresql fixup! Add postgres support Add postgres support Added generate as a make dependency for make build Clarify docs on building Co-authored-by: kolaente <k@knt.li> Co-authored-by: Jan Tojnar <jtojnar@gmail.com> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/135
This commit is contained in:
15
vendor/github.com/go-testfixtures/testfixtures/v3/.editorconfig
generated
vendored
Normal file
15
vendor/github.com/go-testfixtures/testfixtures/v3/.editorconfig
generated
vendored
Normal file
@ -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
|
1
vendor/github.com/go-testfixtures/testfixtures/v3/.gitattributes
generated
vendored
Normal file
1
vendor/github.com/go-testfixtures/testfixtures/v3/.gitattributes
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text eol=lf
|
29
vendor/github.com/go-testfixtures/testfixtures/v3/.gitignore
generated
vendored
Normal file
29
vendor/github.com/go-testfixtures/testfixtures/v3/.gitignore
generated
vendored
Normal file
@ -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
|
41
vendor/github.com/go-testfixtures/testfixtures/v3/.goreleaser.yml
generated
vendored
Normal file
41
vendor/github.com/go-testfixtures/testfixtures/v3/.goreleaser.yml
generated
vendored
Normal file
@ -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}}"
|
4
vendor/github.com/go-testfixtures/testfixtures/v3/.sample.env
generated
vendored
Normal file
4
vendor/github.com/go-testfixtures/testfixtures/v3/.sample.env
generated
vendored
Normal file
@ -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"
|
83
vendor/github.com/go-testfixtures/testfixtures/v3/CHANGELOG.md
generated
vendored
Normal file
83
vendor/github.com/go-testfixtures/testfixtures/v3/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
# Changelog
|
||||
|
||||
## v3.1.1 - 2019-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 - 2019-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 😢.
|
9
vendor/github.com/go-testfixtures/testfixtures/v3/Dockerfile
generated
vendored
Normal file
9
vendor/github.com/go-testfixtures/testfixtures/v3/Dockerfile
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
FROM golang:1.13.5-alpine
|
||||
|
||||
RUN apk update
|
||||
RUN apk add alpine-sdk
|
||||
|
||||
WORKDIR /testfixtures
|
||||
COPY . .
|
||||
|
||||
RUN go mod download
|
21
vendor/github.com/go-testfixtures/testfixtures/v3/LICENSE
generated
vendored
Normal file
21
vendor/github.com/go-testfixtures/testfixtures/v3/LICENSE
generated
vendored
Normal file
@ -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.
|
464
vendor/github.com/go-testfixtures/testfixtures/v3/README.md
generated
vendored
Normal file
464
vendor/github.com/go-testfixtures/testfixtures/v3/README.md
generated
vendored
Normal file
@ -0,0 +1,464 @@
|
||||
# testfixtures
|
||||
|
||||
[][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 {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## 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
|
59
vendor/github.com/go-testfixtures/testfixtures/v3/Taskfile.yml
generated
vendored
Normal file
59
vendor/github.com/go-testfixtures/testfixtures/v3/Taskfile.yml
generated
vendored
Normal file
@ -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'
|
37
vendor/github.com/go-testfixtures/testfixtures/v3/docker-compose.yml
generated
vendored
Normal file
37
vendor/github.com/go-testfixtures/testfixtures/v3/docker-compose.yml
generated
vendored
Normal file
@ -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
|
155
vendor/github.com/go-testfixtures/testfixtures/v3/dump.go
generated
vendored
Normal file
155
vendor/github.com/go-testfixtures/testfixtures/v3/dump.go
generated
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
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))
|
||||
rows, err := d.db.Query(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fixtures := make([]interface{}, 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(map[string]interface{}, len(entries))
|
||||
for i, column := range columns {
|
||||
entryMap[column] = 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
|
||||
}
|
14
vendor/github.com/go-testfixtures/testfixtures/v3/go.mod
generated
vendored
Normal file
14
vendor/github.com/go-testfixtures/testfixtures/v3/go.mod
generated
vendored
Normal file
@ -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
|
26
vendor/github.com/go-testfixtures/testfixtures/v3/go.sum
generated
vendored
Normal file
26
vendor/github.com/go-testfixtures/testfixtures/v3/go.sum
generated
vendored
Normal file
@ -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=
|
71
vendor/github.com/go-testfixtures/testfixtures/v3/helper.go
generated
vendored
Normal file
71
vendor/github.com/go-testfixtures/testfixtures/v3/helper.go
generated
vendored
Normal file
@ -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
|
||||
}
|
44
vendor/github.com/go-testfixtures/testfixtures/v3/json.go
generated
vendored
Normal file
44
vendor/github.com/go-testfixtures/testfixtures/v3/json.go
generated
vendored
Normal file
@ -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
|
||||
}
|
131
vendor/github.com/go-testfixtures/testfixtures/v3/mysql.go
generated
vendored
Normal file
131
vendor/github.com/go-testfixtures/testfixtures/v3/mysql.go
generated
vendored
Normal file
@ -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
|
||||
}
|
296
vendor/github.com/go-testfixtures/testfixtures/v3/postgresql.go
generated
vendored
Normal file
296
vendor/github.com/go-testfixtures/testfixtures/v3/postgresql.go
generated
vendored
Normal file
@ -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, ".")
|
||||
}
|
75
vendor/github.com/go-testfixtures/testfixtures/v3/sqlite.go
generated
vendored
Normal file
75
vendor/github.com/go-testfixtures/testfixtures/v3/sqlite.go
generated
vendored
Normal file
@ -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()
|
||||
}
|
153
vendor/github.com/go-testfixtures/testfixtures/v3/sqlserver.go
generated
vendored
Normal file
153
vendor/github.com/go-testfixtures/testfixtures/v3/sqlserver.go
generated
vendored
Normal file
@ -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")
|
||||
}
|
558
vendor/github.com/go-testfixtures/testfixtures/v3/testfixtures.go
generated
vendored
Normal file
558
vendor/github.com/go-testfixtures/testfixtures/v3/testfixtures.go
generated
vendored
Normal file
@ -0,0 +1,558 @@
|
||||
package testfixtures // import "github.com/go-testfixtures/testfixtures/v3"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"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 = 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 = 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) 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
|
||||
}
|
43
vendor/github.com/go-testfixtures/testfixtures/v3/time.go
generated
vendored
Normal file
43
vendor/github.com/go-testfixtures/testfixtures/v3/time.go
generated
vendored
Normal file
@ -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)
|
||||
}
|
Reference in New Issue
Block a user