Get rid of Iris and use julienschmidt/httprouter instead (#20)

* Replace iris with httprouter

* Linter fixes

* Finalize iris removal

* Vendor dependencies for reproducable builds

* Api tests are back
This commit is contained in:
Joona Hoikkala
2017-11-14 23:54:29 +02:00
committed by GitHub
parent 93871a7cec
commit fd9ce4606d
1541 changed files with 668170 additions and 259 deletions
+22
View File
@@ -0,0 +1,22 @@
# 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
+23
View File
@@ -0,0 +1,23 @@
Copyright (c) 2013, Erik St. Martin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+205
View File
@@ -0,0 +1,205 @@
go-testdb
=========
Framework for stubbing responses from go's driver.Driver interface.
This can be used to sit in place of your sql.Db so that you can stub responses for sql calls, and remove database dependencies for your test suite.
This project is in its infancy, but has worked well for all the use cases i've had so far, and continues to evolve as new scenarios are uncovered. Please feel free to send pull-requests, or submit feature requests if you have scenarios that are not accounted for yet.
## Setup
The only thing needed for setup is to include the go-testdb package, then you can create your db connection specifying "testdb" as your driver.
<pre>
import (
"database/sql"
_"github.com/erikstmartin/go-testdb"
)
db, _ := sql.Open("testdb", "")
</pre>
## Stubbing connection failure
You're able to set your own function to execute when the sql library calls sql.Open
<pre>
testdb.SetOpenFunc(func(dsn string) (driver.Conn, error) {
return c, errors.New("failed to connect")
})
</pre>
## Stubbing queries
You're able to stub responses to known queries, unknown queries will trigger log errors so that you can see that queries were executed that were not stubbed.
Differences in whitespace, and case are ignored.
For convenience a method has been created for you to take a CSV string and turn it into a database result object (RowsFromCSVString).
<pre>
db, _ := sql.Open("testdb", "")
sql := "select id, name, age from users"
columns := []string{"id", "name", "age", "created"}
result := `
1,tim,20,2012-10-01 01:00:01
2,joe,25,2012-10-02 02:00:02
3,bob,30,2012-10-03 03:00:03
`
testdb.StubQuery(sql, testdb.RowsFromCSVString(columns, result))
res, err := db.Query(sql)
</pre>
If for some reason you need to specify another rune to split the columns, you can do it passing the rune that you want to use as `Comma` character as third argument to RowsFromCSVString
<pre>
db, _ := sql.Open("testdb", "")
sql := "select id, name, age, data from users"
columns := []string{"id", "name", "age", "data", "created"}
result := `
1|tim|20|part_1,part_2,part_3|2014-10-16 15:01:00
2|joe|25|part_4,part_5,part_6|2014-10-17 15:01:01
3|bob|30|part_7,part_8,part_9|2014-10-18 15:01:02
`
testdb.StunQuery(sql, RowsFromCSVString(columns, result, '|'))
res, err := db.Query(sql)
</pre>
## Stubbing Query function
Some times you need more control over Query being run, maybe you need to assert whether or not a particular query is run.
You can return either a driver.Rows for response (your own or utilize RowsFromCSV) or an error to be returned
<pre>
testdb.SetQueryFunc(func(query string) (result driver.Rows, err error) {
columns := []string{"id", "name", "age", "created"}
rows := `
1,tim,20,2012-10-01 01:00:01
2,joe,25,2012-10-02 02:00:02
3,bob,30,2012-10-03 03:00:03`
// inspect query to ensure it matches a pattern, or anything else you want to do first
return RowsFromCSVString(columns, rows), nil
})
db, _ := sql.Open("testdb", "")
res, err := db.Query("SELECT foo FROM bar")
</pre>
## Stubbing Parameterized Query
Sometimes you need control over the results of a parameterized query.
<pre>
testdb.SetQueryWithArgsFunc(func(query string, args []driver.Value) (result driver.Rows, err error) {
columns := []string{"id", "name", "age", "created"}
rows := ""
if args[0] == "joe" {
rows = "2,joe,25,2012-10-02 02:00:02"
}
return testdb.RowsFromCSVString(columns, rows), nil
})
db, _ := sql.Open("testdb", "")
res, _ := db.Query("SELECT foo FROM bar WHERE name = $1", "joe")
</pre>
## Stubbing errors returned from queries
In case you need to stub errors returned from queries to ensure your code handles them properly
<pre>
db, _ := sql.Open("testdb", "")
sql := "select count(*) from error"
testdb.StubQueryError(sql, errors.New("test error"))
res, err := db.Query(sql)
</pre>
## Stubbing Parameterized Exec query
Sometimes you need control over the handling of a parameterized query that does not return any rows.
<pre>
type testResult struct{
lastId int64
affectedRows int64
}
func (r testResult) LastInsertId() (int64, error){
return r.lastId, nil
}
func (r testResult) RowsAffected() (int64, error) {
return r.affectedRows, nil
}
testdb.SetExecWithArgsFunc(func(query string, args []driver.Value) (result driver.Result, err error) {
if args[0] == "joe" {
return testResult{1, 1}, nil
}
return testResult{1, 0}, nil
})
db, _ := sql.Open("testdb", "")
res, _ := db.Exec("UPDATE bar SET name = 'foo' WHERE name = ?", "joe")
</pre>
## Stubbing Prepared Statements
You can use the same methods as `SetQueryFunc`, `SetQueryWithArgsFunc` for Prepared Statements
<pre>
testdb.SetQueryFunc(func(query string) (result driver.Rows, err error) {
columns := []string{"id", "name", "age", "created"}
rows := `
1,tim,20,2012-10-01 01:00:01
2,joe,25,2012-10-02 02:00:02
3,bob,30,2012-10-03 03:00:03`
// inspect query to ensure it matches a pattern, or anything else you want to do first
return RowsFromCSVString(columns, rows), nil
})
db, _ := sql.Open("testdb", "")
stmt, _ := db.Prepare("SELECT foo FROM bar")
res, err := stmt.Query("SELECT foo FROM bar")
</pre>
## Reset
At any point in your test, or as a defer you can remove all stubbed queries, errors, custom set Query or Open functions by calling the reset method.
<pre>
func TestMyDatabase(t *testing.T){
defer testdb.Reset()
}
</pre>
#### TODO
Feel free to contribute and send pull requests
- Transactions
#### License
Copyright (c) 2013, Erik St. Martin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+115
View File
@@ -0,0 +1,115 @@
package testdb
import (
"database/sql/driver"
"errors"
)
type conn struct {
queries map[string]query
queryFunc func(query string, args []driver.Value) (driver.Rows, error)
execFunc func(query string, args []driver.Value) (driver.Result, error)
beginFunc func() (driver.Tx, error)
commitFunc func() error
rollbackFunc func() error
}
func newConn() *conn {
return &conn{
queries: make(map[string]query),
}
}
func (c *conn) Prepare(query string) (driver.Stmt, error) {
s := new(stmt)
if c.queryFunc != nil {
s.queryFunc = func(args []driver.Value) (driver.Rows, error) {
return c.queryFunc(query, args)
}
}
if c.execFunc != nil {
s.execFunc = func(args []driver.Value) (driver.Result, error) {
return c.execFunc(query, args)
}
}
if q, ok := d.conn.queries[getQueryHash(query)]; ok {
if s.queryFunc == nil && q.rows != nil {
s.queryFunc = func(args []driver.Value) (driver.Rows, error) {
if q.rows != nil {
if rows, ok := q.rows.(*rows); ok {
return rows.clone(), nil
}
return q.rows, nil
}
return nil, q.err
}
}
if s.execFunc == nil && q.result != nil {
s.execFunc = func(args []driver.Value) (driver.Result, error) {
if q.result != nil {
return q.result, nil
}
return nil, q.err
}
}
}
if s.queryFunc == nil && s.execFunc == nil {
return new(stmt), errors.New("Query not stubbed: " + query)
}
return s, nil
}
func (*conn) Close() error {
return nil
}
func (c *conn) Begin() (driver.Tx, error) {
if c.beginFunc != nil {
return c.beginFunc()
}
t := &Tx{}
if c.commitFunc != nil {
t.SetCommitFunc(c.commitFunc)
}
if c.rollbackFunc != nil {
t.SetRollbackFunc(c.rollbackFunc)
}
return t, nil
}
func (c *conn) Query(query string, args []driver.Value) (driver.Rows, error) {
if c.queryFunc != nil {
return c.queryFunc(query, args)
}
if q, ok := d.conn.queries[getQueryHash(query)]; ok {
if rows, ok := q.rows.(*rows); ok {
return rows.clone(), q.err
}
return q.rows, q.err
}
return nil, errors.New("Query not stubbed: " + query)
}
func (c *conn) Exec(query string, args []driver.Value) (driver.Result, error) {
if c.execFunc != nil {
return c.execFunc(query, args)
}
if q, ok := d.conn.queries[getQueryHash(query)]; ok {
if q.result != nil {
return q.result, nil
} else if q.err != nil {
return nil, q.err
}
}
return nil, errors.New("Exec call not stubbed: " + query)
}
+297
View File
@@ -0,0 +1,297 @@
package testdb
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"strconv"
)
type user struct {
id int64
name string
age int64
created string
data string
}
func ExampleSetOpenFunc() {
defer Reset()
SetOpenFunc(func(dsn string) (driver.Conn, error) {
// Conn() will return the same internal driver.Conn being used by the driver
return Conn(), errors.New("test error")
})
// err only returns from this if it's an unknown driver, we are stubbing opening a connection
db, _ := sql.Open("testdb", "foo")
_, err := db.Driver().Open("foo")
if err != nil {
fmt.Println("Stubbed error returned as expected: " + err.Error())
}
// Output:
// Stubbed error returned as expected: test error
}
func ExampleRowsFromCSVString() {
columns := []string{"id", "name", "age", "created"}
result := `
1,tim,20,2012-10-01 01:00:01
2,joe,25,2012-10-02 02:00:02
3,bob,30,2012-10-03 03:00:03
`
rows := RowsFromCSVString(columns, result)
fmt.Println(rows.Columns())
// Output:
// [id name age created]
}
func ExampleStubQuery() {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select id, name, age from users"
columns := []string{"id", "name", "age", "created"}
result := `
1,tim,20,2012-10-01 01:00:01
2,joe,25,2012-10-02 02:00:02
3,bob,30,2012-10-03 03:00:03
`
StubQuery(sql, RowsFromCSVString(columns, result))
res, _ := db.Query(sql)
for res.Next() {
var u = new(user)
res.Scan(&u.id, &u.name, &u.age, &u.created)
fmt.Println(u.name + " - " + strconv.FormatInt(u.age, 10))
}
// Output:
// tim - 20
// joe - 25
// bob - 30
}
func ExampleStubQuery_queryRow() {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select id, name, age from users"
columns := []string{"id", "name", "age", "created"}
result := `
1,tim,20,2012-10-01 01:00:01
`
StubQuery(sql, RowsFromCSVString(columns, result))
row := db.QueryRow(sql)
u := new(user)
row.Scan(&u.id, &u.name, &u.age, &u.created)
fmt.Println(u.name + " - " + strconv.FormatInt(u.age, 10))
// Output:
// tim - 20
}
func ExampleStubQueryError() {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select count(*) from error"
StubQueryError(sql, errors.New("test error"))
_, err := db.Query(sql)
if err != nil {
fmt.Println("Error returned: " + err.Error())
}
// Output:
// Error returned: test error
}
func ExampleSetQueryFunc() {
defer Reset()
columns := []string{"id", "name", "age", "created"}
rows := "1,tim,20,2012-10-01 01:00:01\n2,joe,25,2012-10-02 02:00:02\n3,bob,30,2012-10-03 03:00:03"
SetQueryFunc(func(query string) (result driver.Rows, err error) {
return RowsFromCSVString(columns, rows), nil
})
db, _ := sql.Open("testdb", "")
res, _ := db.Query("SELECT foo FROM bar")
for res.Next() {
var u = new(user)
res.Scan(&u.id, &u.name, &u.age, &u.created)
fmt.Println(u.name + " - " + strconv.FormatInt(u.age, 10))
}
// Output:
// tim - 20
// joe - 25
// bob - 30
}
func ExampleSetQueryFunc_queryRow() {
defer Reset()
columns := []string{"id", "name", "age", "created"}
rows := "1,tim,20,2012-10-01 01:00:01"
SetQueryFunc(func(query string) (result driver.Rows, err error) {
return RowsFromCSVString(columns, rows), nil
})
db, _ := sql.Open("testdb", "")
row := db.QueryRow("SELECT foo FROM bar")
var u = new(user)
row.Scan(&u.id, &u.name, &u.age, &u.created)
fmt.Println(u.name + " - " + strconv.FormatInt(u.age, 10))
// Output:
// tim - 20
}
func ExampleSetQueryWithArgsFunc() {
defer Reset()
SetQueryWithArgsFunc(func(query string, args []driver.Value) (result driver.Rows, err error) {
columns := []string{"id", "name", "age", "created"}
rows := ""
if args[0] == "joe" {
rows = "2,joe,25,2012-10-02 02:00:02"
}
return RowsFromCSVString(columns, rows), nil
})
db, _ := sql.Open("testdb", "")
res, _ := db.Query("SELECT foo FROM bar WHERE name = $1", "joe")
for res.Next() {
var u = new(user)
res.Scan(&u.id, &u.name, &u.age, &u.created)
fmt.Println(u.name + " - " + strconv.FormatInt(u.age, 10))
}
// Output:
// joe - 25
}
type testResult struct {
lastId int64
affectedRows int64
}
func (r testResult) LastInsertId() (int64, error) {
return r.lastId, nil
}
func (r testResult) RowsAffected() (int64, error) {
return r.affectedRows, nil
}
func ExampleSetExecWithArgsFunc() {
defer Reset()
SetExecWithArgsFunc(func(query string, args []driver.Value) (result driver.Result, err error) {
if args[0] == "joe" {
return testResult{1, 1}, nil
}
return testResult{1, 0}, nil
})
db, _ := sql.Open("testdb", "")
res, _ := db.Exec("UPDATE bar SET name = 'foo' WHERE name = ?", "joe")
rowsAffected, _ := res.RowsAffected()
fmt.Println("RowsAffected =", rowsAffected)
// Output:
// RowsAffected = 1
}
func ExampleSetBeginFunc() {
defer Reset()
commitCalled := false
rollbackCalled := false
SetBeginFunc(func() (txn driver.Tx, err error) {
t := &Tx{}
t.SetCommitFunc(func() error {
commitCalled = true
return nil
})
t.SetRollbackFunc(func() error {
rollbackCalled = true
return nil
})
return t, nil
})
db, _ := sql.Open("testdb", "")
tx, _ := db.Begin()
tx.Commit()
fmt.Println("CommitCalled =", commitCalled)
fmt.Println("RollbackCalled =", rollbackCalled)
// Output:
// CommitCalled = true
// RollbackCalled = false
}
func ExampleSetCommitFunc() {
defer Reset()
SetCommitFunc(func() error {
return errors.New("commit failed")
})
db, _ := sql.Open("testdb", "")
tx, _ := db.Begin()
fmt.Println("CommitResult =", tx.Commit())
// Output:
// CommitResult = commit failed
}
func ExampleSetRollbackFunc() {
defer Reset()
SetRollbackFunc(func() error {
return errors.New("rollback failed")
})
db, _ := sql.Open("testdb", "")
tx, _ := db.Begin()
fmt.Println("RollbackResult =", tx.Rollback())
// Output:
// RollbackResult = rollback failed
}
+25
View File
@@ -0,0 +1,25 @@
package testdb
type Result struct {
lastInsertId int64
lastInsertIdError error
rowsAffected int64
rowsAffectedError error
}
func NewResult(lastId int64, lastIdError error, rowsAffected int64, rowsAffectedError error) (res *Result) {
return &Result{
lastInsertId: lastId,
lastInsertIdError: lastIdError,
rowsAffected: rowsAffected,
rowsAffectedError: rowsAffectedError,
}
}
func (res *Result) LastInsertId() (int64, error) {
return res.lastInsertId, res.lastInsertIdError
}
func (res *Result) RowsAffected() (int64, error) {
return res.rowsAffected, res.rowsAffectedError
}
+48
View File
@@ -0,0 +1,48 @@
package testdb
import (
"database/sql/driver"
"io"
)
type rows struct {
closed bool
columns []string
rows [][]driver.Value
pos int
}
func (rs *rows) clone() *rows {
if rs == nil {
return nil
}
return &rows{closed: false, columns: rs.columns, rows: rs.rows, pos: 0}
}
func (rs *rows) Next(dest []driver.Value) error {
rs.pos++
if rs.pos > len(rs.rows) {
rs.closed = true
return io.EOF // per interface spec
}
for i, col := range rs.rows[rs.pos-1] {
dest[i] = col
}
return nil
}
func (rs *rows) Err() error {
return nil
}
func (rs *rows) Columns() []string {
return rs.columns
}
func (rs *rows) Close() error {
return nil
}
+27
View File
@@ -0,0 +1,27 @@
package testdb
import (
"database/sql/driver"
)
type stmt struct {
queryFunc func(args []driver.Value) (driver.Rows, error)
execFunc func(args []driver.Value) (driver.Result, error)
}
func (s *stmt) Close() error {
return nil
}
func (s *stmt) NumInput() int {
// This prevents the sql package from validating the number of inputs
return -1
}
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
return s.execFunc(args)
}
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
return s.queryFunc(args)
}
+216
View File
@@ -0,0 +1,216 @@
package testdb
import (
"crypto/sha1"
"database/sql"
"database/sql/driver"
"encoding/csv"
"io"
"regexp"
"strings"
"time"
)
var d *testDriver
func init() {
d = newDriver()
sql.Register("testdb", d)
}
type testDriver struct {
openFunc func(dsn string) (driver.Conn, error)
conn *conn
enableTimeParsing bool
}
type query struct {
rows driver.Rows
result *Result
err error
}
func newDriver() *testDriver {
return &testDriver{
conn: newConn(),
}
}
func EnableTimeParsing(flag bool) {
d.enableTimeParsing = flag
}
func (d *testDriver) Open(dsn string) (driver.Conn, error) {
if d.openFunc != nil {
conn, err := d.openFunc(dsn)
return conn, err
}
if d.conn == nil {
d.conn = newConn()
}
return d.conn, nil
}
var whitespaceRegexp = regexp.MustCompile("\\s")
func getQueryHash(query string) string {
// Remove whitespace and lowercase to make stubbing less brittle
query = strings.ToLower(whitespaceRegexp.ReplaceAllString(query, ""))
h := sha1.New()
io.WriteString(h, query)
return string(h.Sum(nil))
}
// Set your own function to be executed when db.Query() is called. As with StubQuery() you can use the RowsFromCSVString() method to easily generate the driver.Rows, or you can return your own.
func SetQueryFunc(f func(query string) (result driver.Rows, err error)) {
SetQueryWithArgsFunc(func(query string, args []driver.Value) (result driver.Rows, err error) {
return f(query)
})
}
// Set your own function to be executed when db.Query() is called. As with StubQuery() you can use the RowsFromCSVString() method to easily generate the driver.Rows, or you can return your own.
func SetQueryWithArgsFunc(f func(query string, args []driver.Value) (result driver.Rows, err error)) {
d.conn.queryFunc = f
}
// Stubs the global driver.Conn to return the supplied driver.Rows when db.Query() is called, query stubbing is case insensitive, and whitespace is also ignored.
func StubQuery(q string, rows driver.Rows) {
d.conn.queries[getQueryHash(q)] = query{
rows: rows,
}
}
// Stubs the global driver.Conn to return the supplied error when db.Query() is called, query stubbing is case insensitive, and whitespace is also ignored.
func StubQueryError(q string, err error) {
d.conn.queries[getQueryHash(q)] = query{
err: err,
}
}
// Set your own function to be executed when db.Open() is called. You can either hand back a valid connection, or an error. Conn() can be used to grab the global Conn object containing stubbed queries.
func SetOpenFunc(f func(dsn string) (driver.Conn, error)) {
d.openFunc = f
}
// Set your own function to be executed when db.Exec is called. You can return an error or a Result object with the LastInsertId and RowsAffected
func SetExecFunc(f func(query string) (driver.Result, error)) {
SetExecWithArgsFunc(func(query string, args []driver.Value) (driver.Result, error) {
return f(query)
})
}
// Set your own function to be executed when db.Exec is called. You can return an error or a Result object with the LastInsertId and RowsAffected
func SetExecWithArgsFunc(f func(query string, args []driver.Value) (driver.Result, error)) {
d.conn.execFunc = f
}
// Stubs the global driver.Conn to return the supplied Result when db.Exec is called, query stubbing is case insensitive, and whitespace is also ignored.
func StubExec(q string, r *Result) {
d.conn.queries[getQueryHash(q)] = query{
result: r,
}
}
// Stubs the global driver.Conn to return the supplied error when db.Exec() is called, query stubbing is case insensitive, and whitespace is also ignored.
func StubExecError(q string, err error) {
StubQueryError(q, err)
}
// Set your own function to be executed when db.Begin() is called. You can either hand back a valid transaction, or an error. Conn() can be used to grab the global Conn object containing stubbed queries.
func SetBeginFunc(f func() (driver.Tx, error)) {
d.conn.beginFunc = f
}
// Stubs the global driver.Conn to return the supplied tx and error when db.Begin() is called.
func StubBegin(tx driver.Tx, err error) {
SetBeginFunc(func() (driver.Tx, error) {
return tx, err
})
}
// Set your own function to be executed when tx.Commit() is called on the default transcation. Conn() can be used to grab the global Conn object containing stubbed queries.
func SetCommitFunc(f func() error) {
d.conn.commitFunc = f
}
// Stubs the default transaction to return the supplied error when tx.Commit() is called.
func StubCommitError(err error) {
SetCommitFunc(func() error {
return err
})
}
// Set your own function to be executed when tx.Rollback() is called on the default transcation. Conn() can be used to grab the global Conn object containing stubbed queries.
func SetRollbackFunc(f func() error) {
d.conn.rollbackFunc = f
}
// Stubs the default transaction to return the supplied error when tx.Rollback() is called.
func StubRollbackError(err error) {
SetRollbackFunc(func() error {
return err
})
}
// Clears all stubbed queries, and replaced functions.
func Reset() {
d.conn = newConn()
d.openFunc = nil
}
// Returns a pointer to the global conn object associated with this driver.
func Conn() driver.Conn {
return d.conn
}
func RowsFromCSVString(columns []string, s string, c ...rune) driver.Rows {
r := strings.NewReader(strings.TrimSpace(s))
csvReader := csv.NewReader(r)
if len(c) > 0 {
csvReader.Comma = c[0]
}
rows := [][]driver.Value{}
for {
r, err := csvReader.Read()
if err != nil || r == nil {
break
}
row := make([]driver.Value, len(columns))
for i, v := range r {
v := strings.TrimSpace(v)
// If enableTimeParsing is on, check to see if this is a
// time in RFC33339 format
if d.enableTimeParsing {
if time, err := time.Parse(time.RFC3339, v); err == nil {
row[i] = time
} else {
row[i] = v
}
} else {
row[i] = v
}
}
rows = append(rows, row)
}
return RowsFromSlice(columns, rows)
}
func RowsFromSlice(columns []string, data [][]driver.Value) driver.Rows {
return &rows{
closed: false,
columns: columns,
rows: data,
pos: 0,
}
}
+727
View File
@@ -0,0 +1,727 @@
package testdb
import (
"database/sql"
"database/sql/driver"
"errors"
"reflect"
"testing"
)
func TestSetOpenFunc(t *testing.T) {
defer Reset()
SetOpenFunc(func(dsn string) (driver.Conn, error) {
return Conn(), errors.New("test error")
})
// err only returns from this if it's an unknown driver, we are stubbing opening a connection
db, _ := sql.Open("testdb", "foo")
conn, err := db.Driver().Open("foo")
if db == nil {
t.Fatal("driver.Open not properly set: db was nil")
}
if conn == nil {
t.Fatal("driver.Open not properly set: didn't connection")
}
if err.Error() != "test error" {
t.Fatal("driver.Open not properly set: err was not returned properly")
}
}
func TestStubQuery(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select count(*) from foo"
columns := []string{"count"}
result := `
5
`
StubQuery(sql, RowsFromCSVString(columns, result))
res, err := db.Query(sql)
if err != nil {
t.Fatal("stubbed query should not return error")
}
if res.Next() {
var count int64
err = res.Scan(&count)
if err != nil {
t.Fatal(err)
}
if count != 5 {
t.Fatal("failed to return count")
}
}
}
func TestStubQueryAdditionalWhitespace(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sqlWhitespace := "select count(*) from foo"
sql := "select count(*) from foo"
columns := []string{"count"}
result := `
5
`
StubQuery(sqlWhitespace, RowsFromCSVString(columns, result))
res, err := db.Query(sql)
if err != nil {
t.Fatal("stubbed query should not return error")
}
if res.Next() {
var count int64
err = res.Scan(&count)
if err != nil {
t.Fatal(err)
}
if count != 5 {
t.Fatal("failed to return count")
}
}
}
func TestStubQueryChangeCase(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sqlCase := "SELECT COUNT(*) FROM foo"
sql := "select count(*) from foo"
columns := []string{"count"}
result := `
5
`
StubQuery(sqlCase, RowsFromCSVString(columns, result))
res, err := db.Query(sql)
if err != nil {
t.Fatal("stubbed query should not return error")
}
if res.Next() {
var count int64
err = res.Scan(&count)
if err != nil {
t.Fatal(err)
}
if count != 5 {
t.Fatal("failed to return count")
}
}
}
func TestUnknownQuery(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select count(*) from foobar"
_, err := db.Query(sql)
if err == nil {
t.Fatal("Unknown queries should fail")
}
}
func TestStubQueryError(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select count(*) from error"
StubQueryError(sql, errors.New("test error"))
res, err := db.Query(sql)
if err == nil {
t.Fatal("failed to return error from stubbed query")
}
if res != nil {
t.Fatal("result should be nil on error")
}
}
func TestStubQueryRowError(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select count(*) from error"
StubQueryError(sql, errors.New("test error"))
row := db.QueryRow(sql)
var count int64
err := row.Scan(&count)
if err == nil {
t.Fatal("error not returned")
}
}
func TestStubQueryMultipleResult(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select id, name, age from users"
columns := []string{"id", "name", "age", "created"}
result := `
1,tim,20,2012-10-01 01:00:01
2,joe,25,2012-10-02 02:00:02
3,bob,30,2012-10-03 03:00:03
`
StubQuery(sql, RowsFromCSVString(columns, result))
res, err := db.Query(sql)
if err != nil {
t.Fatal("stubbed query should not return error")
}
i := 0
for res.Next() {
var u = user{}
err = res.Scan(&u.id, &u.name, &u.age, &u.created)
if err != nil {
t.Fatal(err)
}
if u.id == 0 || u.name == "" || u.age == 0 || u.created == "" {
t.Fatal("failed to populate object with result")
}
i++
}
if i != 3 {
t.Fatal("failed to return proper number of results")
}
}
func TestStubQueryMultipleResultWithCustomComma(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select id, name, age from users"
columns := []string{"id", "name", "age", "data", "created"}
result := `
1|tim|20|part_1,part_2,part_3|2014-10-16 15:01:00
2|joe|25|part_4,part_5,part_6|2014-10-17 15:01:01
3|bob|30|part_7,part_8,part_9|2014-10-18 15:01:02
`
StubQuery(sql, RowsFromCSVString(columns, result, '|'))
res, err := db.Query(sql)
if err != nil {
t.Fatal("stubbed query should not return error")
}
i := 0
for res.Next() {
var u = user{}
err = res.Scan(&u.id, &u.name, &u.age, &u.data, &u.created)
if err != nil {
t.Fatal(err)
}
if u.id == 0 || u.name == "" || u.age == 0 || u.data == "" || u.created == "" {
t.Fatal("failed to populate object with result")
}
i++
}
if i != 3 {
t.Fatal("failed to return proper number of results")
}
}
func TestStubQueryMultipleResultNewline(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select id, name, age from users"
columns := []string{"id", "name", "age", "created"}
result := "1,tim,20,2012-10-01 01:00:01\n2,joe,25,2012-10-02 02:00:02\n3,bob,30,2012-10-03 03:00:03"
StubQuery(sql, RowsFromCSVString(columns, result))
res, err := db.Query(sql)
if err != nil {
t.Fatal("stubbed query should not return error")
}
i := 0
for res.Next() {
var u = user{}
err = res.Scan(&u.id, &u.name, &u.age, &u.created)
if err != nil {
t.Fatal(err)
}
if u.id == 0 || u.name == "" || u.age == 0 || u.created == "" {
t.Fatal("failed to populate object with result")
}
i++
}
if i != 3 {
t.Fatal("failed to return proper number of results")
}
}
func TestSetQueryFunc(t *testing.T) {
defer Reset()
columns := []string{"id", "name", "age", "created"}
rows := "1,tim,20,2012-10-01 01:00:01\n2,joe,25,2012-10-02 02:00:02\n3,bob,30,2012-10-03 03:00:03"
SetQueryFunc(func(query string) (result driver.Rows, err error) {
return RowsFromCSVString(columns, rows), nil
})
if Conn().(*conn).queryFunc == nil {
t.Fatal("query function not stubbed")
}
db, _ := sql.Open("testdb", "")
res, err := db.Query("SELECT foo FROM bar")
if err != nil {
t.Fatal(err)
}
i := 0
for res.Next() {
var u = user{}
err = res.Scan(&u.id, &u.name, &u.age, &u.created)
if err != nil {
t.Fatal(err)
}
if u.id == 0 || u.name == "" || u.age == 0 || u.created == "" {
t.Fatal("failed to populate object with result")
}
i++
}
if i != 3 {
t.Fatal("failed to return proper number of results")
}
}
func TestSetQueryFuncError(t *testing.T) {
defer Reset()
SetQueryFunc(func(query string) (result driver.Rows, err error) {
return nil, errors.New("stubbed error")
})
db, _ := sql.Open("testdb", "")
_, err := db.Query("SELECT foo FROM bar")
if err == nil {
t.Fatal("failed to return error from QueryFunc")
}
}
func TestReset(t *testing.T) {
sql.Open("testdb", "")
sql := "select count(*) from error"
StubQueryError(sql, errors.New("test error"))
Reset()
if len(d.conn.queries) > 0 {
t.Fatal("failed to reset connection")
}
}
func TestStubQueryRow(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select count(*) from foo"
columns := []string{"count"}
result := `
5
`
StubQuery(sql, RowsFromCSVString(columns, result))
row := db.QueryRow(sql)
if row == nil {
t.Fatal("stub query should have returned row")
}
var count int64
err := row.Scan(&count)
if err != nil {
t.Fatal(err)
}
if count != 5 {
t.Fatal("failed to return count")
}
}
func TestStubQueryRowReuse(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "select count(*) from foo"
columns := []string{"count"}
result := `
5
`
StubQuery(sql, RowsFromCSVString(columns, result))
i := 0
rows, _ := db.Query(sql)
for rows.Next() {
i++
}
if i != 1 {
t.Fatal("stub query should have returned one row")
}
j := i
moreRows, _ := db.Query(sql)
for moreRows.Next() {
j++
}
if i == j {
t.Fatal("stub query did not return another set of rows")
}
}
func TestSetQueryFuncRow(t *testing.T) {
defer Reset()
columns := []string{"id", "name", "age", "created"}
rows := "1,tim,20,2012-10-01 01:00:01"
SetQueryFunc(func(query string) (result driver.Rows, err error) {
return RowsFromCSVString(columns, rows), nil
})
if Conn().(*conn).queryFunc == nil {
t.Fatal("query function not stubbed")
}
db, _ := sql.Open("testdb", "")
row := db.QueryRow("SELECT foo FROM bar")
var u = user{}
err := row.Scan(&u.id, &u.name, &u.age, &u.created)
if err != nil {
t.Fatal(err)
}
if u.id == 0 || u.name == "" || u.age == 0 || u.created == "" {
t.Fatal("failed to populate object with result")
}
}
func TestSetQueryFuncRowError(t *testing.T) {
defer Reset()
SetQueryFunc(func(query string) (result driver.Rows, err error) {
return nil, errors.New("Stubbed error")
})
if Conn().(*conn).queryFunc == nil {
t.Fatal("query function not stubbed")
}
db, _ := sql.Open("testdb", "")
row := db.QueryRow("SELECT foo FROM bar")
var u = user{}
err := row.Scan(&u.id, &u.name, &u.age, &u.created)
if err == nil {
t.Fatal("Did not return error")
}
}
func TestStubExec(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
sql := "INSERT INTO foo SET (foo) VALUES (bar)"
StubExec(sql, NewResult(5, errors.New("last insert error"), 3, errors.New("rows affected error")))
res, err := db.Exec(sql)
if err != nil {
t.Fatal("stubbed exec call returned unexpected error")
}
var insertId int64
insertId, err = res.LastInsertId()
if insertId != 5 || err.Error() != "last insert error" {
t.Fatal("stubbed exec did not return expected result")
}
var affected int64
affected, err = res.RowsAffected()
if affected != 3 || err.Error() != "rows affected error" {
t.Fatal("stubbed exec did not return expected result")
}
}
func TestStubExecError(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
query := "INSERT INTO foo SET (foo) VALUES (bar)"
StubExecError(query, errors.New("request failed"))
res, err := db.Exec(query)
if reflect.Indirect(reflect.ValueOf(res)).CanAddr() {
t.Fatal("stubbed exec returned unexpected result")
}
if err == nil || err.Error() != "request failed" {
t.Fatal("stubbed exec call did not return expected error")
}
}
func TestStubExecFunc(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
query := "INSERT INTO foo SET (foo) VALUES (bar)"
result := NewResult(5, errors.New("last insert error"), 3, errors.New("rows affected error"))
SetExecFunc(func(query string) (driver.Result, error) {
return result, nil
})
res, err := db.Exec(query)
if err != nil {
t.Fatal("stubbed exec returned unexpected error")
}
var insertId int64
insertId, err = res.LastInsertId()
if insertId != 5 || err.Error() != "last insert error" {
t.Fatal("stubbed exec did not return expected result")
}
var affected int64
affected, err = res.RowsAffected()
if affected != 3 || err.Error() != "rows affected error" {
t.Fatal("stubbed exec did not return expected result")
}
}
func TestStubExecFuncError(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
query := "INSERT INTO foo SET (foo) VALUES (bar)"
SetExecFunc(func(query string) (driver.Result, error) {
return nil, errors.New("request failed")
})
res, err := db.Exec(query)
if res != nil {
t.Fatal("stubbed exec unexpected result")
}
if err == nil || err.Error() != "request failed" {
t.Fatal("stubbed exec did not return expected error")
}
}
func TestSetBeginFunc(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
SetBeginFunc(func() (driver.Tx, error) {
return nil, errors.New("begin failed")
})
res, err := db.Begin()
if res != nil {
t.Fatal("stubbed begin unexpected result")
}
if err == nil || err.Error() != "begin failed" {
t.Fatal("stubbed begin did not return expected error")
}
}
func TestStubBegin(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
StubBegin(nil, errors.New("begin failed"))
res, err := db.Begin()
if res != nil {
t.Fatal("stubbed begin unexpected result")
}
if err == nil || err.Error() != "begin failed" {
t.Fatal("stubbed begin did not return expected error")
}
}
func TestSetCommitFunc(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
SetCommitFunc(func() error {
return errors.New("commit failed")
})
tx, err := db.Begin()
if tx == nil {
t.Fatal("begin expected result")
}
if err != nil {
t.Fatal("begin returned unexpected error")
}
err = tx.Commit()
if err == nil || err.Error() != "commit failed" {
t.Fatal("stubbed commit did not return expected error")
}
}
func TestStubCommitError(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
StubCommitError(errors.New("commit failed"))
tx, err := db.Begin()
if tx == nil {
t.Fatal("begin expected result")
}
if err != nil {
t.Fatal("begin returned unexpected error")
}
err = tx.Commit()
if err == nil || err.Error() != "commit failed" {
t.Fatal("stubbed commit did not return expected error")
}
}
func TestSetRollbackFunc(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
SetRollbackFunc(func() error {
return errors.New("rollback failed")
})
tx, err := db.Begin()
if tx == nil {
t.Fatal("begin expected result")
}
if err != nil {
t.Fatal("begin returned unexpected error")
}
err = tx.Rollback()
if err == nil || err.Error() != "rollback failed" {
t.Fatal("stubbed rollback did not return expected error")
}
}
func TestStubRollbackError(t *testing.T) {
defer Reset()
db, _ := sql.Open("testdb", "")
StubRollbackError(errors.New("rollback failed"))
tx, err := db.Begin()
if tx == nil {
t.Fatal("begin expected result")
}
if err != nil {
t.Fatal("begin returned unexpected error")
}
err = tx.Rollback()
if err == nil || err.Error() != "rollback failed" {
t.Fatal("stubbed rollback did not return expected error")
}
}
+40
View File
@@ -0,0 +1,40 @@
package testdb
type Tx struct {
commitFunc func() error
rollbackFunc func() error
}
func (t *Tx) Commit() error {
if t.commitFunc != nil {
return t.commitFunc()
}
return nil
}
func (t *Tx) Rollback() error {
if t.rollbackFunc != nil {
return t.rollbackFunc()
}
return nil
}
func (t *Tx) SetCommitFunc(f func() error) {
t.commitFunc = f
}
func (t *Tx) StubCommitError(err error) {
t.SetCommitFunc(func() error {
return err
})
}
func (t *Tx) SetRollbackFunc(f func() error) {
t.rollbackFunc = f
}
func (t *Tx) StubRollbackError(err error) {
t.SetRollbackFunc(func() error {
return err
})
}
+58
View File
@@ -0,0 +1,58 @@
package testdb
import (
"errors"
"testing"
)
func TestTxSetCommitFunc(t *testing.T) {
tx := &Tx{}
tx.SetCommitFunc(func() error {
return errors.New("commit failed")
})
err := tx.Commit()
if err == nil || err.Error() != "commit failed" {
t.Fatal("stubbed commit did not return expected error")
}
}
func TestTxStubCommitError(t *testing.T) {
tx := &Tx{}
tx.StubCommitError(errors.New("commit failed"))
err := tx.Commit()
if err == nil || err.Error() != "commit failed" {
t.Fatal("stubbed commit did not return expected error")
}
}
func TestTxSetRollbackFunc(t *testing.T) {
tx := &Tx{}
tx.SetRollbackFunc(func() error {
return errors.New("rollback failed")
})
err := tx.Rollback()
if err == nil || err.Error() != "rollback failed" {
t.Fatal("stubbed rollback did not return expected error")
}
}
func TestTxStubRollbackError(t *testing.T) {
tx := &Tx{}
tx.StubRollbackError(errors.New("rollback failed"))
err := tx.Rollback()
if err == nil || err.Error() != "rollback failed" {
t.Fatal("stubbed rollback did not return expected error")
}
}