Этот коммит содержится в:
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:
mysql -u root -e 'DROP DATABASE IF 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
.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.
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
to allow execute every and each scenario within a **transaction**. After it completes, transaction
is rolled back so the state could be clean for the next scenario.
The interesting point is, that we have [go-txdb](https://github.com/DATA-DOG/go-txdb) library,
which has an implementation of custom sql.driver to allow execute every and each 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.
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")
defer rows.Close()
switch err {
case sql.ErrNoRows:
users = make([]*user, 0) // an empty array in this case
case nil:
for rows.Next() {
user := &user{}
@ -40,6 +38,9 @@ func (s *server) users(w http.ResponseWriter, r *http.Request) {
}
users = append(users, user)
}
if len(users) == 0 {
users = make([]*user, 0) // an empty array in this case
}
default:
fail(w, fmt.Sprintf("failed to fetch users: %s", err), http.StatusInternalServerError)
return

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

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