From f748d3cb707842b9b5cd461d5d554b5a90c6a351 Mon Sep 17 00:00:00 2001 From: gedi Date: Thu, 9 Jul 2015 14:22:42 +0300 Subject: [PATCH] update database test example --- examples/db/Makefile | 18 ++++- examples/db/README.md | 7 +- examples/db/api.go | 5 +- examples/db/api_test.go | 5 +- examples/db/db.sql | 7 -- examples/db/txdb.go | 146 ---------------------------------------- 6 files changed, 26 insertions(+), 162 deletions(-) delete mode 100644 examples/db/db.sql delete mode 100644 examples/db/txdb.go diff --git a/examples/db/Makefile b/examples/db/Makefile index 4d5aa00..2b77086 100644 --- a/examples/db/Makefile +++ b/examples/db/Makefile @@ -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 diff --git a/examples/db/README.md b/examples/db/README.md index f6fb538..42db787 100644 --- a/examples/db/README.md +++ b/examples/db/README.md @@ -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: diff --git a/examples/db/api.go b/examples/db/api.go index 2b28c7c..8395ee7 100644 --- a/examples/db/api.go +++ b/examples/db/api.go @@ -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 diff --git a/examples/db/api_test.go b/examples/db/api_test.go index 4ac359b..6eb1809 100644 --- a/examples/db/api_test.go +++ b/examples/db/api_test.go @@ -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) } diff --git a/examples/db/db.sql b/examples/db/db.sql deleted file mode 100644 index 9ae3f74..0000000 --- a/examples/db/db.sql +++ /dev/null @@ -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; diff --git a/examples/db/txdb.go b/examples/db/txdb.go deleted file mode 100644 index b01234c..0000000 --- a/examples/db/txdb.go +++ /dev/null @@ -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() -}