github.com/tobgu/qframe@v0.4.0/qframe_sql_test.go (about)

     1  package qframe_test
     2  
     3  import (
     4  	"database/sql"
     5  	"database/sql/driver"
     6  	"io"
     7  	"testing"
     8  
     9  	"github.com/tobgu/qframe"
    10  	qsql "github.com/tobgu/qframe/config/sql"
    11  )
    12  
    13  // MockDriver implements a fake SQL driver for testing.
    14  type MockDriver struct {
    15  	t *testing.T
    16  	// expected SQL query
    17  	query string
    18  	// results holds values that are
    19  	// returned from a database query
    20  	results struct {
    21  		// column names for each row of values
    22  		columns []string
    23  		// each value for each row
    24  		values [][]driver.Value
    25  	}
    26  	// args holds expected values
    27  	args struct {
    28  		// values we expect to be given
    29  		// to the database
    30  		values [][]driver.Value
    31  	}
    32  
    33  	// optional statement Query implementation
    34  	mockQuery MockQuery
    35  }
    36  
    37  func (m MockDriver) Open(name string) (driver.Conn, error) {
    38  	stmt := &MockStmt{
    39  		t:      m.t,
    40  		values: m.args.values,
    41  		rows: &MockRows{
    42  			t:       m.t,
    43  			columns: m.results.columns,
    44  			values:  m.results.values,
    45  		},
    46  		mockQuery: m.mockQuery,
    47  	}
    48  	return &MockConn{
    49  		t:     m.t,
    50  		stmt:  stmt,
    51  		query: m.query,
    52  	}, nil
    53  }
    54  
    55  type MockRows struct {
    56  	t       *testing.T
    57  	idx     int
    58  	columns []string
    59  	values  [][]driver.Value
    60  }
    61  
    62  func (m *MockRows) Next(dest []driver.Value) error {
    63  	if m.idx == len(m.values) {
    64  		return io.EOF
    65  	}
    66  	for i := 0; i < len(dest); i++ {
    67  		dest[i] = m.values[m.idx][i]
    68  	}
    69  	m.idx++
    70  	return nil
    71  }
    72  
    73  func (m MockRows) Close() error { return nil }
    74  
    75  func (m MockRows) Columns() []string { return m.columns }
    76  
    77  type MockTx struct{}
    78  
    79  func (m MockTx) Commit() error { return nil }
    80  
    81  func (m MockTx) Rollback() error { return nil }
    82  
    83  type MockStmt struct {
    84  	t         *testing.T
    85  	rows      *MockRows
    86  	idx       int
    87  	values    [][]driver.Value
    88  	mockQuery MockQuery
    89  }
    90  
    91  func (s MockStmt) Close() error { return nil }
    92  
    93  func (s MockStmt) NumInput() int {
    94  	if len(s.values) > 0 {
    95  		return len(s.values[0])
    96  	}
    97  	return 0
    98  }
    99  
   100  func (s *MockStmt) Exec(args []driver.Value) (driver.Result, error) {
   101  	for i, arg := range args {
   102  		if s.values[s.idx][i] != arg {
   103  			s.t.Errorf("arg %t != %t", arg, s.values[s.idx][i])
   104  		}
   105  	}
   106  	s.idx++
   107  	return nil, nil
   108  }
   109  
   110  func (s MockStmt) Query(args []driver.Value) (driver.Rows, error) {
   111  	// use the mock query implementation if supplied by the test
   112  	if s.mockQuery != nil {
   113  		return s.mockQuery(args)
   114  	}
   115  	return s.rows, nil
   116  }
   117  
   118  type MockQuery func(args []driver.Value) (driver.Rows, error)
   119  
   120  type MockConn struct {
   121  	t     *testing.T
   122  	query string
   123  	stmt  *MockStmt
   124  }
   125  
   126  func (m MockConn) Prepare(query string) (driver.Stmt, error) {
   127  	if query != m.query {
   128  		m.t.Errorf("invalid query: %s != %s", query, m.query)
   129  	}
   130  	return m.stmt, nil
   131  }
   132  
   133  func (c MockConn) Close() error { return nil }
   134  func (c MockConn) Begin() (driver.Tx, error) {
   135  	return &MockTx{}, nil
   136  }
   137  
   138  var (
   139  	_ driver.Conn = (*MockConn)(nil)
   140  	_ driver.Rows = (*MockRows)(nil)
   141  	_ driver.Tx   = (*MockTx)(nil)
   142  	_ driver.Stmt = (*MockStmt)(nil)
   143  	_ driver.Conn = (*MockConn)(nil)
   144  )
   145  
   146  func TestQFrame_ToSQL(t *testing.T) {
   147  	dvr := MockDriver{t: t}
   148  	dvr.query = "INSERT INTO test (COL1,COL2,COL3,COL4) VALUES (?,?,?,?);"
   149  	dvr.args.values = [][]driver.Value{
   150  		{int64(1), 1.1, "one", true},
   151  		{int64(2), 2.2, "two", true},
   152  		{int64(3), 3.3, "three", false},
   153  	}
   154  	sql.Register("TestToSQL", dvr)
   155  	db, _ := sql.Open("TestToSQL", "")
   156  	tx, _ := db.Begin()
   157  	qf := qframe.New(map[string]interface{}{
   158  		"COL1": []int{1, 2, 3},
   159  		"COL2": []float64{1.1, 2.2, 3.3},
   160  		"COL3": []string{"one", "two", "three"},
   161  		"COL4": []bool{true, true, false},
   162  	})
   163  	assertNotErr(t, qf.ToSQL(tx, qsql.Table("test")))
   164  }
   165  
   166  func TestQFrame_ReadSQL(t *testing.T) {
   167  	dvr := MockDriver{t: t}
   168  	dvr.results.columns = []string{"COL1", "COL2", "COL3", "COL4"}
   169  	dvr.results.values = [][]driver.Value{
   170  		{int64(1), 1.1, "one", true},
   171  		{int64(2), 2.2, "two", true},
   172  		{int64(3), 3.3, "three", false},
   173  	}
   174  	sql.Register("TestReadSQL", dvr)
   175  	db, _ := sql.Open("TestReadSQL", "")
   176  	tx, _ := db.Begin()
   177  	qf := qframe.ReadSQL(tx)
   178  	assertNotErr(t, qf.Err)
   179  	expected := qframe.New(map[string]interface{}{
   180  		"COL1": []int{1, 2, 3},
   181  		"COL2": []float64{1.1, 2.2, 3.3},
   182  		"COL3": []string{"one", "two", "three"},
   183  		"COL4": []bool{true, true, false},
   184  	})
   185  	assertEquals(t, expected, qf)
   186  }
   187  
   188  func TestQFrame_ReadSQLCoercion(t *testing.T) {
   189  	dvr := MockDriver{t: t}
   190  	dvr.results.columns = []string{"COL1", "COL2"}
   191  	dvr.results.values = [][]driver.Value{
   192  		{int64(1), int64(0)},
   193  		{int64(1), int64(0)},
   194  		{int64(0), int64(1)},
   195  	}
   196  	sql.Register("TestReadSQLCoercion", dvr)
   197  	db, _ := sql.Open("TestReadSQLCoercion", "")
   198  	tx, _ := db.Begin()
   199  	qf := qframe.ReadSQL(tx, qsql.Coerce(
   200  		qsql.CoercePair{Column: "COL1", Type: qsql.Int64ToBool},
   201  		qsql.CoercePair{Column: "COL2", Type: qsql.Int64ToBool},
   202  	))
   203  	assertNotErr(t, qf.Err)
   204  	expected := qframe.New(map[string]interface{}{
   205  		"COL1": []bool{true, true, false},
   206  		"COL2": []bool{false, false, true},
   207  	})
   208  	assertEquals(t, expected, qf)
   209  }
   210  
   211  func TestQFrame_ReadWithArgs(t *testing.T) {
   212  	dvr := MockDriver{t: t}
   213  	dvr.args.values = [][]driver.Value{{""}}
   214  
   215  	// mock rows indexed by string COL3
   216  	indexRows := map[string][][]driver.Value{
   217  		"one":   {{int64(1), 1.1, "one", true}},
   218  		"two":   {{int64(2), 1.2, "two", false}},
   219  		"three": {{int64(3), 1.3, "three", true}},
   220  	}
   221  	dvr.mockQuery = func(args []driver.Value) (driver.Rows, error) {
   222  		// to confirm our argument made it through to the db driver as expected
   223  		if len(args) != 1 {
   224  			t.Error("expecting one argument in query invocation")
   225  		}
   226  		val, valid := args[0].(string)
   227  		if !valid {
   228  			t.Error("expecting argument in query invocation to be a string")
   229  		}
   230  		matching, has := indexRows[val]
   231  		if !has {
   232  			matching = [][]driver.Value{}
   233  		}
   234  		return &MockRows{
   235  			t:       t,
   236  			columns: []string{"COL1", "COL2", "COL3", "COL4"},
   237  			values:  matching,
   238  		}, nil
   239  	}
   240  	stmt := "SELECT * FROM mock_table WHERE COL3=$1"
   241  	dvr.query = stmt
   242  	sql.Register("TestReadPrepared", dvr)
   243  	db, _ := sql.Open("TestReadPrepared", "")
   244  	tx, _ := db.Begin()
   245  	qf := qframe.ReadSQLWithArgs(tx, []interface{}{"one"}, qsql.Query(stmt))
   246  	assertNotErr(t, qf.Err)
   247  	expected := qframe.New(map[string]interface{}{
   248  		"COL1": []int{1},
   249  		"COL2": []float64{1.1},
   250  		"COL3": []string{"one"},
   251  		"COL4": []bool{true},
   252  	})
   253  	assertEquals(t, expected, qf)
   254  }