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: vikunja/api#135
This commit is contained in:
jtojnar 2020-02-16 21:42:04 +00:00 committed by konrad
parent 7e42724439
commit ce5be947b4
107 changed files with 9886 additions and 1595 deletions

View File

@ -6,16 +6,26 @@ workspace:
path: src/code.vikunja.io/api
services:
- name: test-db-unit
- name: test-mysql-unit
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
- name: test-db-integration
- name: test-mysql-integration
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
- name: test-postgres-unit
image: postgres:12
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
- name: test-postgres-integration
image: postgres:12
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
trigger:
branch:
@ -78,7 +88,7 @@ steps:
environment:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_HOST: test-db-unit
VIKUNJA_DATABASE_HOST: test-mysql-unit
VIKUNJA_DATABASE_USER: root
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
@ -88,6 +98,23 @@ steps:
when:
event: [ push, tag, pull_request ]
- name: test-postgres
image: vikunja/golang-build:latest
pull: true
environment:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_HOST: test-postgres-unit
VIKUNJA_DATABASE_USER: postgres
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- make test
depends_on: [ build ]
when:
event: [ push, tag, pull_request ]
- name: integration-test
image: vikunja/golang-build:latest
pull: true
@ -115,7 +142,7 @@ steps:
environment:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_HOST: test-db-integration
VIKUNJA_DATABASE_HOST: test-mysql-integration
VIKUNJA_DATABASE_USER: root
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
@ -125,6 +152,23 @@ steps:
when:
event: [ push, tag, pull_request ]
- name: integration-test-postgres
image: vikunja/golang-build:latest
pull: true
environment:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_HOST: test-postgres-integration
VIKUNJA_DATABASE_USER: postgres
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- make integration-test
depends_on: [ build ]
when:
event: [ push, tag, pull_request ]
---
########
# Build a release when pushing to master

View File

@ -64,7 +64,8 @@ clean:
.PHONY: test
test:
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -cover -coverprofile cover.out $(PACKAGES)
# We run everything sequentially and not in parallel to prevent issues with real test databases
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -p 1 -cover -coverprofile cover.out $(PACKAGES)
.PHONY: test-coverage
test-coverage: test
@ -72,7 +73,8 @@ test-coverage: test
.PHONY: integration-test
integration-test:
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) code.vikunja.io/api/pkg/integrations
# We run everything sequentially and not in parallel to prevent issues with real test databases
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -p 1 code.vikunja.io/api/pkg/integrations
.PHONY: lint
lint:

View File

@ -30,7 +30,7 @@ service:
timezone: GMT
database:
# Database type to use. Supported types are mysql and sqlite.
# Database type to use. Supported types are mysql, postgres and sqlite.
type: "sqlite"
# Database user which is used to connect to the database.
user: "vikunja"
@ -42,12 +42,15 @@ database:
database: "vikunja"
# When using sqlite, this is the path where to store the data
Path: "./vikunja.db"
# Sets the max open connections to the database. Only used when using mysql.
# Sets the max open connections to the database. Only used when using mysql and postgres.
maxopenconnections: 100
# Sets the maximum number of idle connections to the db.
maxidleconnections: 50
# The maximum lifetime of a single db connection in miliseconds.
maxconnectionlifetime: 10000
# Secure connection mode. Only used with postgres.
# (see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters)
sslmode: disable
cache:
# If cache is enabled or not

View File

@ -29,6 +29,24 @@ To restore it, simply pipe it back into the `mysql` command:
mysql -u <user> -p -h <db-host> <database> < vkunja-backup.sql
{{< /highlight >}}
## PostgreSQL
To create a backup from PostgreSQL use the `pg_dump` command:
{{< highlight bash >}}
pg_dump -U <user> -h <db-host> <database> > vikunja-backup.sql
{{< /highlight >}}
You might be prompted for the password of the database user.
To restore it, simply pipe it back into the `psql` command:
{{< highlight bash >}}
psql -U <user> -h <db-host> <database> < vikunja-backup.sql
{{< /highlight >}}
For more information, please visit the [relevant PostgreSQL documentation](https://www.postgresql.org/docs/12/backup-dump.html).
## SQLite
To backup sqllite databases, it is enough to copy the database elsewhere.

View File

@ -73,7 +73,7 @@ service:
timezone: GMT
database:
# Database type to use. Supported types are mysql and sqlite.
# Database type to use. Supported types are mysql, postgres and sqlite.
type: "sqlite"
# Database user which is used to connect to the database.
user: "vikunja"
@ -85,12 +85,15 @@ database:
database: "vikunja"
# When using sqlite, this is the path where to store the data
Path: "./vikunja.db"
# Sets the max open connections to the database. Only used when using mysql.
# Sets the max open connections to the database. Only used when using mysql and postgres.
maxopenconnections: 100
# Sets the maximum number of idle connections to the db.
maxidleconnections: 50
# The maximum lifetime of a single db connection in miliseconds.
maxconnectionlifetime: 10000
# Secure connection mode. Only used with postgres.
# (see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters)
sslmode: disable
cache:
# If cache is enabled or not

View File

@ -55,6 +55,7 @@ After=network.target
# Depending on how you configured Vikunja, you may want to uncomment these:
#Requires=mysql.service
#Requires=mariadb.service
#Requires=postgresql.service
#Requires=redis.service
[Service]

5
go.mod
View File

@ -35,6 +35,7 @@ require (
github.com/go-openapi/spec v0.19.4 // indirect
github.com/go-redis/redis v6.15.2+incompatible
github.com/go-sql-driver/mysql v1.5.0
github.com/go-testfixtures/testfixtures/v3 v3.1.1
github.com/go-xorm/core v0.6.2 // indirect
github.com/go-xorm/xorm v0.7.9 // indirect
github.com/golang/protobuf v1.3.2 // indirect
@ -45,9 +46,9 @@ require (
github.com/labstack/echo/v4 v4.1.14
github.com/labstack/gommon v0.3.0
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
github.com/lib/pq v1.3.0
github.com/mailru/easyjson v0.7.0 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
@ -74,8 +75,6 @@ require (
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/testfixtures.v2 v2.5.3
gopkg.in/yaml.v2 v2.2.7 // indirect
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4
src.techknowlogick.com/xormigrate v1.1.0

15
go.sum
View File

@ -1,5 +1,4 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
@ -54,10 +53,9 @@ github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkE
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e h1:LzwWXEScfcTu7vUZNlDDWDARoSGEtvlDKK2BYHowNeE=
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
@ -110,6 +108,8 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-testfixtures/testfixtures/v3 v3.1.1 h1:SBIfzULODQQ7JV6AD931MvAz8pnkk6QCfMIcoOBDaXQ=
github.com/go-testfixtures/testfixtures/v3 v3.1.1/go.mod h1:RZctY24ixituGC73XlAV1gkCwYMVwiSwPm26MNlQIhE=
github.com/go-xorm/core v0.6.2 h1:EJLcSxf336POJr670wKB55Mah9f93xzvGYzNRgnT8/Y=
github.com/go-xorm/core v0.6.2/go.mod h1:bwPIfLdm/FzWgVUH8WPVlr+uJhscvNGFcaZKXsI3n2c=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
@ -211,13 +211,11 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-oci8 v0.0.0-20181115070430-6eefff3c767c/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 h1:gheNi9lnffYyVyqQzJqY7lo+M3bCDVw5fLU/jSuCMhc=
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@ -290,6 +288,8 @@ github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmq
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -330,7 +330,6 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -454,8 +453,6 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/testfixtures.v2 v2.5.3 h1:P8gDACSLJGxutzBqbzvfiXYgmQ2s00LIr4uAvWBCPAg=
gopkg.in/testfixtures.v2 v2.5.3/go.mod h1:rGPtsOtPcZhs7AsHYf1WmufW1hEsM6DXdLrYz60nrQQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=

View File

@ -55,6 +55,7 @@ const (
DatabaseMaxOpenConnections Key = `database.maxopenconnections`
DatabaseMaxIdleConnections Key = `database.maxidleconnections`
DatabaseMaxConnectionLifetime Key = `database.maxconnectionlifetime`
DatabaseSslMode Key = `database.sslmode`
CacheEnabled Key = `cache.enabled`
CacheType Key = `cache.type`
@ -181,6 +182,7 @@ func InitDefaultConfig() {
DatabaseMaxOpenConnections.setDefault(100)
DatabaseMaxIdleConnections.setDefault(50)
DatabaseMaxConnectionLifetime.setDefault(10000)
DatabaseSslMode.setDefault("disable")
// Cacher
CacheEnabled.setDefault(false)

View File

@ -21,7 +21,9 @@ import (
"code.vikunja.io/api/pkg/log"
"encoding/gob"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"xorm.io/core"
"xorm.io/xorm"
@ -29,6 +31,7 @@ import (
xrc "gitea.com/xorm/xorm-redis-cache"
_ "github.com/go-sql-driver/mysql" // Because.
_ "github.com/lib/pq" // Because.
_ "github.com/mattn/go-sqlite3" // Because.
)
@ -53,6 +56,11 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
if err != nil {
return
}
} else if config.DatabaseType.GetString() == "postgres" {
engine, err = initPostgresEngine()
if err != nil {
return
}
} else {
// Otherwise use sqlite
engine, err = initSqliteEngine()
@ -113,6 +121,46 @@ func initMysqlEngine() (engine *xorm.Engine, err error) {
return
}
// parsePostgreSQLHostPort parses given input in various forms defined in
// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
// and returns proper host and port number.
func parsePostgreSQLHostPort(info string) (string, string) {
host, port := "127.0.0.1", "5432"
if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
idx := strings.LastIndex(info, ":")
host = info[:idx]
port = info[idx+1:]
} else if len(info) > 0 {
host = info
}
return host, port
}
func initPostgresEngine() (engine *xorm.Engine, err error) {
host, port := parsePostgreSQLHostPort(config.DatabaseHost.GetString())
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
host,
port,
url.PathEscape(config.DatabaseUser.GetString()),
url.PathEscape(config.DatabasePassword.GetString()),
config.DatabaseDatabase.GetString(),
config.DatabaseSslMode.GetString(),
)
engine, err = xorm.NewEngine("postgres", connStr)
if err != nil {
return
}
engine.SetMaxOpenConns(config.DatabaseMaxOpenConnections.GetInt())
engine.SetMaxIdleConns(config.DatabaseMaxIdleConnections.GetInt())
max, err := time.ParseDuration(strconv.Itoa(config.DatabaseMaxConnectionLifetime.GetInt()) + `ms`)
if err != nil {
return
}
engine.SetConnMaxLifetime(max)
return
}
func initSqliteEngine() (engine *xorm.Engine, err error) {
path := config.DatabasePath.GetString()
if path == "" {

View File

@ -19,41 +19,92 @@ package db
import (
"code.vikunja.io/api/pkg/config"
"fmt"
"github.com/go-testfixtures/testfixtures/v3"
"github.com/stretchr/testify/assert"
"gopkg.in/testfixtures.v2"
"path/filepath"
"testing"
)
var fixtures *testfixtures.Context
var fixtures *testfixtures.Loader
// InitFixtures initialize test fixtures for a test database
func InitFixtures(tablenames ...string) (err error) {
var helper testfixtures.Helper = &testfixtures.SQLite{}
if config.DatabaseType.GetString() == "mysql" {
helper = &testfixtures.MySQL{}
}
var testfiles func(loader *testfixtures.Loader) error
dir := filepath.Join(config.ServiceRootpath.GetString(), "pkg", "db", "fixtures")
testfixtures.SkipDatabaseNameCheck(true)
// If fixture table names are specified, load them
// Otherwise, load all fixtures
if len(tablenames) > 0 {
for i, name := range tablenames {
tablenames[i] = filepath.Join(dir, name+".yml")
}
fixtures, err = testfixtures.NewFiles(x.DB().DB, helper, tablenames...)
testfiles = testfixtures.Files(tablenames...)
} else {
fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir)
testfiles = testfixtures.Directory(dir)
}
loaderOptions := []func(loader *testfixtures.Loader) error{
testfixtures.Database(x.DB().DB),
testfixtures.Dialect(config.DatabaseType.GetString()),
testfixtures.DangerousSkipTestDatabaseCheck(),
testfiles,
}
if config.DatabaseType.GetString() == "postgres" {
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
}
fixtures, err = testfixtures.New(loaderOptions...)
if err != nil {
return err
}
return err
}
// LoadFixtures load fixtures for a test database
func LoadFixtures() error {
return fixtures.Load()
err := fixtures.Load()
if err != nil {
return err
}
// Copied from https://github.com/go-gitea/gitea/blob/master/models/test_fixtures.go#L39
// Now if we're running postgres we need to tell it to update the sequences
if x.Dialect().DriverName() == "postgres" {
results, err := x.QueryString(`SELECT 'SELECT SETVAL(' ||
quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
FROM pg_class AS S,
pg_depend AS D,
pg_class AS T,
pg_attribute AS C,
pg_tables AS PGT
WHERE S.relkind = 'S'
AND S.oid = D.objid
AND D.refobjid = T.oid
AND D.refobjid = C.attrelid
AND D.refobjsubid = C.attnum
AND T.relname = PGT.tablename
ORDER BY S.relname;`)
if err != nil {
fmt.Printf("Failed to generate sequence update: %v\n", err)
return err
}
for _, r := range results {
for _, value := range r {
_, err = x.Exec(value)
if err != nil {
fmt.Printf("Failed to update sequence: %s Error: %v\n", value, err)
return err
}
}
}
}
return err
}
// LoadAndAssertFixtures loads all fixtures defined before and asserts they are correctly loaded

View File

@ -57,11 +57,11 @@ func TestCreate(t *testing.T) {
content: []byte("testfile"),
}
ta := &testauth{id: 1}
_, err := Create(tf, "testfile", 100, ta)
createdFile, err := Create(tf, "testfile", 100, ta)
assert.NoError(t, err)
// Check the file was created correctly
file := &File{ID: 2}
file := &File{ID: createdFile.ID}
err = file.LoadFileMetaByID()
assert.NoError(t, err)
assert.Equal(t, int64(1), file.CreatedByID)

View File

@ -114,6 +114,11 @@ func dropTableColum(x *xorm.Engine, tableName, col string) error {
if err != nil {
return err
}
case "postgres":
_, err := x.Exec("ALTER TABLE " + tableName + " DROP COLUMN " + col)
if err != nil {
return err
}
default:
log.Fatal("Unknown db.")
}
@ -130,6 +135,11 @@ func modifyColumn(x *xorm.Engine, tableName, col, newDefinition string) error {
if err != nil {
return err
}
case "postgres":
_, err := x.Exec("ALTER TABLE " + tableName + " ALTER COLUMN " + col + " " + newDefinition)
if err != nil {
return err
}
default:
log.Fatal("Unknown db.")
}

View File

@ -154,8 +154,10 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, res
// Because of this whole thing, we need this extra switch here to only group by Task IDs if needed.
// Probably not the most ideal solution.
var groupBy = "labels.id,label_task.task_id"
var selectStmt = "labels.*, label_task.task_id"
if opts.GroupByLabelIDsOnly {
groupBy = "labels.id"
selectStmt = "labels.*"
}
// Get all labels associated with these tasks
@ -166,11 +168,12 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, res
}
err = x.Table("labels").
Select("labels.*, label_task.task_id").
Select(selectStmt).
Join("LEFT", "label_task", "label_task.label_id = labels.id").
Where(cond).
And("labels.title LIKE ?", "%"+opts.Search+"%").
GroupBy(groupBy).
OrderBy("labels.id ASC").
Limit(getLimitFromPageIndex(opts.Page, opts.PerPage)).
Find(&labels)
if err != nil {

View File

@ -83,7 +83,6 @@ func TestLabel_ReadAll(t *testing.T) {
},
},
{
TaskID: 1,
Label: Label{
ID: 4,
Title: "Label #4 - visible via other task",

View File

@ -21,6 +21,7 @@ import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
_ "github.com/go-sql-driver/mysql" // Because.
_ "github.com/lib/pq" // Because.
"xorm.io/xorm"
_ "github.com/mattn/go-sqlite3" // Because.

View File

@ -86,7 +86,7 @@ func TestNamespace_Create(t *testing.T) {
assert.Equal(t, "Dolor sit amet.", dummynamespace.Description)
// Try updating one with a nonexistant owner
dummynamespace.Owner.ID = 94829838572
dummynamespace.Owner.ID = 999999
err = dummynamespace.Update()
assert.Error(t, err)
assert.True(t, user.IsErrUserDoesNotExist(err))

View File

@ -19,6 +19,7 @@ package models
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
@ -30,6 +31,7 @@ import (
func TestTaskAttachment_ReadOne(t *testing.T) {
t.Run("Normal File", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t)
ta := &TaskAttachment{
ID: 1,
@ -50,6 +52,7 @@ func TestTaskAttachment_ReadOne(t *testing.T) {
assert.Equal(t, []byte("testfile1"), content)
})
t.Run("Nonexisting Attachment", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t)
ta := &TaskAttachment{
ID: 9999,
@ -59,6 +62,7 @@ func TestTaskAttachment_ReadOne(t *testing.T) {
assert.True(t, IsErrTaskAttachmentDoesNotExist(err))
})
t.Run("Existing Attachment, Nonexisting File", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t)
ta := &TaskAttachment{
ID: 2,
@ -88,6 +92,7 @@ func (t *testfile) Close() error {
}
func TestTaskAttachment_NewAttachment(t *testing.T) {
db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t)
// Assert the file is being stored correctly
ta := TaskAttachment{
@ -118,16 +123,18 @@ func TestTaskAttachment_NewAttachment(t *testing.T) {
}
func TestTaskAttachment_ReadAll(t *testing.T) {
db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t)
ta := &TaskAttachment{TaskID: 1}
as, _, _, err := ta.ReadAll(&user.User{ID: 1}, "", 0, 50)
attachments, _ := as.([]*TaskAttachment)
assert.NoError(t, err)
assert.Len(t, attachments, 3)
assert.Len(t, attachments, 2)
assert.Equal(t, "test", attachments[0].File.Name)
}
func TestTaskAttachment_Delete(t *testing.T) {
db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t)
t.Run("Normal", func(t *testing.T) {
ta := &TaskAttachment{ID: 1}
@ -156,12 +163,14 @@ func TestTaskAttachment_Rights(t *testing.T) {
u := &user.User{ID: 1}
t.Run("Can Read", func(t *testing.T) {
t.Run("Allowed", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
ta := &TaskAttachment{TaskID: 1}
can, err := ta.CanRead(u)
assert.NoError(t, err)
assert.True(t, can)
})
t.Run("Forbidden", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
ta := &TaskAttachment{TaskID: 14}
can, err := ta.CanRead(u)
assert.NoError(t, err)
@ -170,18 +179,21 @@ func TestTaskAttachment_Rights(t *testing.T) {
})
t.Run("Can Delete", func(t *testing.T) {
t.Run("Allowed", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
ta := &TaskAttachment{TaskID: 1}
can, err := ta.CanDelete(u)
assert.NoError(t, err)
assert.True(t, can)
})
t.Run("Forbidden, no access", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
ta := &TaskAttachment{TaskID: 14}
can, err := ta.CanDelete(u)
assert.NoError(t, err)
assert.False(t, can)
})
t.Run("Forbidden, shared read only", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
ta := &TaskAttachment{TaskID: 15}
can, err := ta.CanDelete(u)
assert.NoError(t, err)
@ -190,18 +202,21 @@ func TestTaskAttachment_Rights(t *testing.T) {
})
t.Run("Can Create", func(t *testing.T) {
t.Run("Allowed", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
ta := &TaskAttachment{TaskID: 1}
can, err := ta.CanCreate(u)
assert.NoError(t, err)
assert.True(t, can)
})
t.Run("Forbidden, no access", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
ta := &TaskAttachment{TaskID: 14}
can, err := ta.CanCreate(u)
assert.NoError(t, err)
assert.False(t, can)
})
t.Run("Forbidden, shared read only", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
ta := &TaskAttachment{TaskID: 15}
can, err := ta.CanCreate(u)
assert.NoError(t, err)

View File

@ -24,61 +24,60 @@ import (
)
func TestTeamMember_Create(t *testing.T) {
db.LoadAndAssertFixtures(t)
// Dummy team member
dummyteammember := TeamMember{
TeamID: 1,
Username: "user3",
doer := &user.User{
ID: 1,
}
// Doer
doer, err := user.GetUserByID(1)
assert.NoError(t, err)
// Insert a new team member
allowed, _ := dummyteammember.CanCreate(doer)
assert.True(t, allowed)
err = dummyteammember.Create(doer)
assert.NoError(t, err)
// Check he's in there
team := Team{ID: 1}
err = team.ReadOne()
assert.NoError(t, err)
assert.Equal(t, 3, len(team.Members))
// Try inserting a user twice
err = dummyteammember.Create(doer)
assert.Error(t, err)
assert.True(t, IsErrUserIsMemberOfTeam(err))
// Delete it
allowed, _ = dummyteammember.CanDelete(doer)
assert.True(t, allowed)
err = dummyteammember.Delete()
assert.NoError(t, err)
// Delete the other one
tm := TeamMember{TeamID: 1, Username: "user2"}
err = tm.Delete()
assert.NoError(t, err)
// Try deleting the last one
tm = TeamMember{TeamID: 1, Username: "user1"}
err = tm.Delete()
assert.Error(t, err)
assert.True(t, IsErrCannotDeleteLastTeamMember(err))
// Try inserting a user which does not exist
dummyteammember.Username = "user9484"
err = dummyteammember.Create(doer)
assert.Error(t, err)
assert.True(t, user.IsErrUserDoesNotExist(err))
// Try adding a user to a team which does not exist
tm = TeamMember{TeamID: 94824, Username: "user1"}
err = tm.Create(doer)
assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err))
t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
tm := &TeamMember{
TeamID: 1,
Username: "user3",
}
err := tm.Create(doer)
assert.NoError(t, err)
})
t.Run("already existing", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
tm := &TeamMember{
TeamID: 1,
Username: "user1",
}
err := tm.Create(doer)
assert.Error(t, err)
assert.True(t, IsErrUserIsMemberOfTeam(err))
})
t.Run("nonexisting user", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
tm := &TeamMember{
TeamID: 1,
Username: "nonexistinguser",
}
err := tm.Create(doer)
assert.Error(t, err)
assert.True(t, user.IsErrUserDoesNotExist(err))
})
t.Run("nonexisting team", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
tm := &TeamMember{
TeamID: 9999999,
Username: "user1",
}
err := tm.Create(doer)
assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err))
})
}
func TestTeamMember_Delete(t *testing.T) {
t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
tm := &TeamMember{
TeamID: 1,
Username: "user1",
}
err := tm.Delete()
assert.NoError(t, err)
})
}

View File

@ -68,7 +68,7 @@ type TeamMember struct {
// Used under the hood to manage team members
UserID int64 `xorm:"int(11) not null INDEX" json:"-"`
// Whether or not the member is an admin of the team. See the docs for more about what a team admin can do
Admin bool `xorm:"tinyint(1) INDEX null" json:"admin"`
Admin bool `xorm:"null" json:"admin"`
// A timestamp when this relation was created. You cannot change this value.
Created timeutil.TimeStamp `xorm:"created not null" json:"created"`
@ -131,7 +131,7 @@ func addMoreInfoToTeams(teams []*Team) (err error) {
}
// Get all owners and team members
users := []*TeamUser{}
users := make(map[int64]*TeamUser)
err = x.Select("*").
Table("users").
Join("LEFT", "team_members", "team_members.user_id = users.id").
@ -153,9 +153,15 @@ func addMoreInfoToTeams(teams []*Team) (err error) {
continue
}
u.Email = ""
teamMap[u.TeamID].CreatedBy = &u.User
teamMap[u.TeamID].Members = append(teamMap[u.TeamID].Members, u)
}
// We need to do this in a second loop as owners might not be the last ones in the list
for _, team := range teamMap {
if teamUser, has := users[team.CreatedByID]; has {
team.CreatedBy = &teamUser.User
}
}
return
}

View File

@ -17,6 +17,7 @@
package models
import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
"reflect"
@ -24,79 +25,108 @@ import (
)
func TestTeam_Create(t *testing.T) {
//Dummyteam
dummyteam := Team{
Name: "Testteam293",
Description: "Lorem Ispum",
doer := &user.User{
ID: 1,
Username: "user1",
}
t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
team := &Team{
Name: "Testteam293",
Description: "Lorem Ispum",
}
err := team.Create(doer)
assert.NoError(t, err)
})
t.Run("empty name", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
team := &Team{}
err := team.Create(doer)
assert.Error(t, err)
assert.True(t, IsErrTeamNameCannotBeEmpty(err))
})
}
// Doer
doer, err := user.GetUserByID(1)
assert.NoError(t, err)
func TestTeam_ReadOne(t *testing.T) {
t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
team := &Team{ID: 1}
err := team.ReadOne()
assert.NoError(t, err)
assert.Equal(t, "testteam1", team.Name)
assert.Equal(t, "Lorem Ipsum", team.Description)
assert.Equal(t, int64(1), team.CreatedBy.ID)
assert.Equal(t, int64(1), team.CreatedByID)
})
t.Run("invalid id", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
team := &Team{ID: -1}
err := team.ReadOne()
assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err))
})
t.Run("nonexisting", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
team := &Team{ID: 99999}
err := team.ReadOne()
assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err))
})
}
// Insert it
allowed, _ := dummyteam.CanCreate(doer)
assert.True(t, allowed)
err = dummyteam.Create(doer)
assert.NoError(t, err)
func TestTeam_ReadAll(t *testing.T) {
doer := &user.User{ID: 1}
t.Run("normal", func(t *testing.T) {
team := &Team{}
ts, _, _, err := team.ReadAll(doer, "", 1, 50)
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice)
s := reflect.ValueOf(ts)
assert.Equal(t, 8, s.Len())
})
}
// Check if it was inserted and we're admin
tm := Team{ID: dummyteam.ID}
err = tm.ReadOne()
assert.NoError(t, err)
assert.Equal(t, 1, len(tm.Members))
assert.Equal(t, doer.ID, tm.Members[0].User.ID)
assert.True(t, tm.Members[0].Admin)
allowed, _ = dummyteam.CanRead(doer)
assert.True(t, allowed)
func TestTeam_Update(t *testing.T) {
t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
team := &Team{
ID: 1,
Name: "SomethingNew",
}
err := team.Update()
assert.NoError(t, err)
})
t.Run("empty name", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
team := &Team{
ID: 1,
Name: "",
}
err := team.Update()
assert.Error(t, err)
assert.True(t, IsErrTeamNameCannotBeEmpty(err))
})
t.Run("nonexisting", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
team := &Team{
ID: 9999,
Name: "SomethingNew",
}
err := team.Update()
assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err))
})
}
// Try getting a team with an ID < 0
_, err = GetTeamByID(-1)
assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err))
// Get all teams the user is part of
ts, _, _, err := tm.ReadAll(doer, "", 1, 50)
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice)
s := reflect.ValueOf(ts)
assert.Equal(t, 9, s.Len())
// Check inserting it with an empty name
dummyteam.Name = ""
err = dummyteam.Create(doer)
assert.Error(t, err)
assert.True(t, IsErrTeamNameCannotBeEmpty(err))
// update it (still no name, should fail)
allowed, _ = dummyteam.CanUpdate(doer)
assert.True(t, allowed)
err = dummyteam.Update()
assert.Error(t, err)
assert.True(t, IsErrTeamNameCannotBeEmpty(err))
// Update it, this time with a name
dummyteam.Name = "Lorem"
err = dummyteam.Update()
assert.NoError(t, err)
// Delete it
allowed, err = dummyteam.CanDelete(doer)
assert.NoError(t, err)
assert.True(t, allowed)
err = dummyteam.Delete()
assert.NoError(t, err)
// Try deleting a (now) nonexistant team
allowed, err = dummyteam.CanDelete(doer)
assert.False(t, allowed)
assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err))
// Try updating the (now) nonexistant team
err = dummyteam.Update()
assert.Error(t, err)
assert.True(t, IsErrTeamDoesNotExist(err))
func TestTeam_Delete(t *testing.T) {
t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
team := &Team{
ID: 1,
}
err := team.Delete()
assert.NoError(t, err)
})
}
func TestIsErrInvalidRight(t *testing.T) {

View File

@ -23,7 +23,7 @@ _testmain.go
*.test
*.prof
# SQLite databases
*.sqlite3
.env
/testfixtures
/dist

View 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}}"

View File

@ -2,4 +2,3 @@ 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"
ORACLE_CONN_STRING="testfixtures/testfixtures@localhost/XE"

View 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 😢.

View 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

View File

@ -0,0 +1,464 @@
# 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.