Этот коммит содержится в:
gedi 2015-07-09 14:22:42 +03:00
родитель 0d5e51f091
коммит f748d3cb70
6 изменённых файлов: 26 добавлений и 162 удалений

Просмотреть файл

@ -1,8 +1,22 @@
.PHONY: test
define DB_SQL
CREATE TABLE users (
`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL,
`username` VARCHAR(32) NOT NULL,
`email` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `uniq_email` (`email`)
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
endef
export DB_SQL
SQL := "$$DB_SQL"
test: test:
mysql -u root -e 'DROP DATABASE IF EXISTS `godog_test`' mysql -u root -e 'DROP DATABASE IF EXISTS `godog_test`'
mysql -u root -e 'CREATE DATABASE IF NOT EXISTS `godog_test`' mysql -u root -e 'CREATE DATABASE IF NOT EXISTS `godog_test`'
mysql -u root godog_test < db.sql @mysql -u root godog_test -e $(SQL)
godog users.feature godog users.feature
.PHONY: test

Просмотреть файл

@ -4,9 +4,10 @@ The following example demonstrates steps how we describe and test our API with D
To start with, see [API example](https://github.com/DATA-DOG/godog/tree/master/examples/api) before. To start with, see [API example](https://github.com/DATA-DOG/godog/tree/master/examples/api) before.
We have extended it to be used with database. We have extended it to be used with database.
The interesting point is, that we have **txdb.go** which has an implementation of custom sql.driver The interesting point is, that we have [go-txdb](https://github.com/DATA-DOG/go-txdb) library,
to allow execute every and each scenario within a **transaction**. After it completes, transaction which has an implementation of custom sql.driver to allow execute every and each scenario
is rolled back so the state could be clean for the next scenario. within a **transaction**. After it completes, transaction is rolled back so the state could
be clean for the next scenario.
To run **users.feature** you need MySQL installed on your system with an anonymous root password. To run **users.feature** you need MySQL installed on your system with an anonymous root password.
Then run: Then run:

Просмотреть файл

@ -29,8 +29,6 @@ func (s *server) users(w http.ResponseWriter, r *http.Request) {
rows, err := s.db.Query("SELECT id, email, username FROM users") rows, err := s.db.Query("SELECT id, email, username FROM users")
defer rows.Close() defer rows.Close()
switch err { switch err {
case sql.ErrNoRows:
users = make([]*user, 0) // an empty array in this case
case nil: case nil:
for rows.Next() { for rows.Next() {
user := &user{} user := &user{}
@ -40,6 +38,9 @@ func (s *server) users(w http.ResponseWriter, r *http.Request) {
} }
users = append(users, user) users = append(users, user)
} }
if len(users) == 0 {
users = make([]*user, 0) // an empty array in this case
}
default: default:
fail(w, fmt.Sprintf("failed to fetch users: %s", err), http.StatusInternalServerError) fail(w, fmt.Sprintf("failed to fetch users: %s", err), http.StatusInternalServerError)
return return

Просмотреть файл

@ -8,13 +8,14 @@ import (
"net/http/httptest" "net/http/httptest"
"strings" "strings"
"github.com/DATA-DOG/go-txdb"
"github.com/DATA-DOG/godog" "github.com/DATA-DOG/godog"
"github.com/cucumber/gherkin-go" "github.com/cucumber/gherkin-go"
) )
func init() { func init() {
// we register an sql driver txdb // we register an sql driver txdb
Register("mysql", "root@/godog_test") txdb.Register("txdb", "mysql", "root@/godog_test")
} }
type apiFeature struct { type apiFeature struct {
@ -27,7 +28,7 @@ func (a *apiFeature) resetResponse(interface{}) {
if a.db != nil { if a.db != nil {
a.db.Close() a.db.Close()
} }
db, err := sql.Open("txdb", "") db, err := sql.Open("txdb", "api")
if err != nil { if err != nil {
panic(err) panic(err)
} }

Просмотреть файл

@ -1,7 +0,0 @@
CREATE TABLE users (
`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL,
`username` VARCHAR(32) NOT NULL,
`email` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `uniq_email` (`email`)
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;

Просмотреть файл

@ -1,146 +0,0 @@
package main
import (
"database/sql"
"database/sql/driver"
"io"
"sync"
)
// Register a txdb sql driver which can be used to open
// a single transaction based database connection pool
func Register(drv, dsn string) {
sql.Register("txdb", &txDriver{dsn: dsn, drv: drv})
}
// txDriver is an sql driver which runs on single transaction
// when the Close is called, transaction is rolled back
type txDriver struct {
sync.Mutex
tx *sql.Tx
drv string
dsn string
db *sql.DB
}
func (d *txDriver) Open(dsn string) (driver.Conn, error) {
// first open a real database connection
var err error
if d.db == nil {
db, err := sql.Open(d.drv, d.dsn)
if err != nil {
return d, err
}
d.db = db
}
if d.tx == nil {
d.tx, err = d.db.Begin()
}
return d, err
}
func (d *txDriver) Close() error {
err := d.tx.Rollback()
d.tx = nil
return err
}
func (d *txDriver) Begin() (driver.Tx, error) {
return d, nil
}
func (d *txDriver) Commit() error {
return nil
}
func (d *txDriver) Rollback() error {
return nil
}
func (d *txDriver) Prepare(query string) (driver.Stmt, error) {
return &stmt{drv: d, query: query}, nil
}
type stmt struct {
query string
drv *txDriver
}
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
s.drv.Lock()
defer s.drv.Unlock()
st, err := s.drv.tx.Prepare(s.query)
if err != nil {
return nil, err
}
defer st.Close()
var iargs []interface{}
for _, arg := range args {
iargs = append(iargs, arg)
}
return st.Exec(iargs...)
}
func (s *stmt) NumInput() int {
return -1
}
func (s *stmt) Close() error {
return nil
}
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
s.drv.Lock()
defer s.drv.Unlock()
st, err := s.drv.tx.Prepare(s.query)
if err != nil {
return nil, err
}
// do not close the statement here, Rows need it
var iargs []interface{}
for _, arg := range args {
iargs = append(iargs, arg)
}
rs, err := st.Query(iargs...)
return &rows{rs: rs}, err
}
type rows struct {
err error
rs *sql.Rows
}
func (r *rows) Columns() (cols []string) {
cols, r.err = r.rs.Columns()
return
}
func (r *rows) Next(dest []driver.Value) error {
if r.err != nil {
return r.err
}
if r.rs.Err() != nil {
return r.rs.Err()
}
if !r.rs.Next() {
return io.EOF
}
values := make([]interface{}, len(dest))
for i := range values {
values[i] = new(interface{})
}
if err := r.rs.Scan(values...); err != nil {
return err
}
for i, val := range values {
dest[i] = *(val.(*interface{}))
}
return r.rs.Err()
}
func (r *rows) Close() error {
return r.rs.Close()
}