mirror of https://github.com/docker/docs.git
Update Gotuf to the latest version (sqlite3 dependency changed) and clean up some old
Godep dependencies that are no longer needed. Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
parent
e786d62f0f
commit
f49b74675d
|
@ -1,24 +1,14 @@
|
|||
{
|
||||
"ImportPath": "github.com/docker/notary",
|
||||
"GoVersion": "go1.5.1",
|
||||
"GoVersion": "go1.4.2",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "code.google.com/p/gosqlite/sqlite3",
|
||||
"Comment": "null-16",
|
||||
"Rev": "74691fb6f83716190870cde1b658538dd4b18eb0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/BurntSushi/toml",
|
||||
"Rev": "bd2bdf7f18f849530ef7a1c29a4290217cab32a1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/DATA-DOG/go-sqlmock",
|
||||
"Comment": "0.1.0-8-ged4836e",
|
||||
"Rev": "ed4836e31d3e9e77420e442ed9b864df55370ee0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Sirupsen/logrus",
|
||||
"Comment": "v0.7.3",
|
||||
|
@ -106,7 +96,7 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/endophage/gotuf",
|
||||
"Rev": "338013a6720b3dfe4e4e9d1368109ea314c936d3"
|
||||
"Rev": "a8824e6565a316076fc738eaaccda6528c448a38"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||
|
|
|
@ -1,498 +0,0 @@
|
|||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package sqlite3 provides access to the SQLite library, version 3.
|
||||
//
|
||||
// The package has no exported API.
|
||||
// It registers a driver for the standard Go database/sql package.
|
||||
//
|
||||
// import _ "code.google.com/p/gosqlite/sqlite3"
|
||||
//
|
||||
// (For an alternate, earlier API, see the code.google.com/p/gosqlite/sqlite package.)
|
||||
package sqlite
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lsqlite3
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// These wrappers are necessary because SQLITE_TRANSIENT
|
||||
// is a pointer constant, and cgo doesn't translate them correctly.
|
||||
// The definition in sqlite3.h is:
|
||||
//
|
||||
// typedef void (*sqlite3_destructor_type)(void*);
|
||||
// #define SQLITE_STATIC ((sqlite3_destructor_type)0)
|
||||
// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
|
||||
|
||||
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
|
||||
return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||
}
|
||||
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
|
||||
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sql.Register("sqlite3", impl{})
|
||||
}
|
||||
|
||||
type errno int
|
||||
|
||||
func (e errno) Error() string {
|
||||
s := errText[e]
|
||||
if s == "" {
|
||||
return fmt.Sprintf("errno %d", int(e))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
errError error = errno(1) // /* SQL error or missing database */
|
||||
errInternal error = errno(2) // /* Internal logic error in SQLite */
|
||||
errPerm error = errno(3) // /* Access permission denied */
|
||||
errAbort error = errno(4) // /* Callback routine requested an abort */
|
||||
errBusy error = errno(5) // /* The database file is locked */
|
||||
errLocked error = errno(6) // /* A table in the database is locked */
|
||||
errNoMem error = errno(7) // /* A malloc() failed */
|
||||
errReadOnly error = errno(8) // /* Attempt to write a readonly database */
|
||||
errInterrupt error = errno(9) // /* Operation terminated by sqlite3_interrupt()*/
|
||||
errIOErr error = errno(10) // /* Some kind of disk I/O error occurred */
|
||||
errCorrupt error = errno(11) // /* The database disk image is malformed */
|
||||
errFull error = errno(13) // /* Insertion failed because database is full */
|
||||
errCantOpen error = errno(14) // /* Unable to open the database file */
|
||||
errEmpty error = errno(16) // /* Database is empty */
|
||||
errSchema error = errno(17) // /* The database schema changed */
|
||||
errTooBig error = errno(18) // /* String or BLOB exceeds size limit */
|
||||
errConstraint error = errno(19) // /* Abort due to constraint violation */
|
||||
errMismatch error = errno(20) // /* Data type mismatch */
|
||||
errMisuse error = errno(21) // /* Library used incorrectly */
|
||||
errNolfs error = errno(22) // /* Uses OS features not supported on host */
|
||||
errAuth error = errno(23) // /* Authorization denied */
|
||||
errFormat error = errno(24) // /* Auxiliary database format error */
|
||||
errRange error = errno(25) // /* 2nd parameter to sqlite3_bind out of range */
|
||||
errNotDB error = errno(26) // /* File opened that is not a database file */
|
||||
stepRow = errno(100) // /* sqlite3_step() has another row ready */
|
||||
stepDone = errno(101) // /* sqlite3_step() has finished executing */
|
||||
)
|
||||
|
||||
var errText = map[errno]string{
|
||||
1: "SQL error or missing database",
|
||||
2: "Internal logic error in SQLite",
|
||||
3: "Access permission denied",
|
||||
4: "Callback routine requested an abort",
|
||||
5: "The database file is locked",
|
||||
6: "A table in the database is locked",
|
||||
7: "A malloc() failed",
|
||||
8: "Attempt to write a readonly database",
|
||||
9: "Operation terminated by sqlite3_interrupt()*/",
|
||||
10: "Some kind of disk I/O error occurred",
|
||||
11: "The database disk image is malformed",
|
||||
12: "NOT USED. Table or record not found",
|
||||
13: "Insertion failed because database is full",
|
||||
14: "Unable to open the database file",
|
||||
15: "NOT USED. Database lock protocol error",
|
||||
16: "Database is empty",
|
||||
17: "The database schema changed",
|
||||
18: "String or BLOB exceeds size limit",
|
||||
19: "Abort due to constraint violation",
|
||||
20: "Data type mismatch",
|
||||
21: "Library used incorrectly",
|
||||
22: "Uses OS features not supported on host",
|
||||
23: "Authorization denied",
|
||||
24: "Auxiliary database format error",
|
||||
25: "2nd parameter to sqlite3_bind out of range",
|
||||
26: "File opened that is not a database file",
|
||||
100: "sqlite3_step() has another row ready",
|
||||
101: "sqlite3_step() has finished executing",
|
||||
}
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func (impl) Open(name string) (driver.Conn, error) {
|
||||
if C.sqlite3_threadsafe() == 0 {
|
||||
return nil, errors.New("sqlite library was not compiled for thread-safe operation")
|
||||
}
|
||||
|
||||
var db *C.sqlite3
|
||||
cname := C.CString(name)
|
||||
defer C.free(unsafe.Pointer(cname))
|
||||
rv := C.sqlite3_open_v2(cname, &db,
|
||||
C.SQLITE_OPEN_FULLMUTEX|
|
||||
C.SQLITE_OPEN_READWRITE|
|
||||
C.SQLITE_OPEN_CREATE,
|
||||
nil)
|
||||
if rv != 0 {
|
||||
return nil, errno(rv)
|
||||
}
|
||||
if db == nil {
|
||||
return nil, errors.New("sqlite succeeded without returning a database")
|
||||
}
|
||||
return &conn{db: db}, nil
|
||||
}
|
||||
|
||||
type conn struct {
|
||||
db *C.sqlite3
|
||||
closed bool
|
||||
tx bool
|
||||
}
|
||||
|
||||
func (c *conn) error(rv C.int) error {
|
||||
if rv == 0 {
|
||||
return nil
|
||||
}
|
||||
if rv == 21 || c.closed {
|
||||
return errno(rv)
|
||||
}
|
||||
return errors.New(errno(rv).Error() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
|
||||
}
|
||||
|
||||
func (c *conn) Prepare(cmd string) (driver.Stmt, error) {
|
||||
if c.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Prepare after Close")
|
||||
}
|
||||
cmdstr := C.CString(cmd)
|
||||
defer C.free(unsafe.Pointer(cmdstr))
|
||||
var s *C.sqlite3_stmt
|
||||
var tail *C.char
|
||||
rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &s, &tail)
|
||||
if rv != 0 {
|
||||
return nil, c.error(rv)
|
||||
}
|
||||
return &stmt{c: c, stmt: s, sql: cmd, t0: time.Now()}, nil
|
||||
}
|
||||
|
||||
func (c *conn) Close() error {
|
||||
if c.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: multiple Close")
|
||||
}
|
||||
c.closed = true
|
||||
rv := C.sqlite3_close(c.db)
|
||||
c.db = nil
|
||||
return c.error(rv)
|
||||
}
|
||||
|
||||
func (c *conn) exec(cmd string) error {
|
||||
cstring := C.CString(cmd)
|
||||
defer C.free(unsafe.Pointer(cstring))
|
||||
rv := C.sqlite3_exec(c.db, cstring, nil, nil, nil)
|
||||
return c.error(rv)
|
||||
}
|
||||
|
||||
func (c *conn) Begin() (driver.Tx, error) {
|
||||
if c.tx {
|
||||
panic("database/sql/driver: misuse of sqlite driver: multiple Tx")
|
||||
}
|
||||
if err := c.exec("BEGIN TRANSACTION"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.tx = true
|
||||
return &tx{c}, nil
|
||||
}
|
||||
|
||||
type tx struct {
|
||||
c *conn
|
||||
}
|
||||
|
||||
func (t *tx) Commit() error {
|
||||
if t.c == nil || !t.c.tx {
|
||||
panic("database/sql/driver: misuse of sqlite driver: extra Commit")
|
||||
}
|
||||
t.c.tx = false
|
||||
err := t.c.exec("COMMIT TRANSACTION")
|
||||
t.c = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *tx) Rollback() error {
|
||||
if t.c == nil || !t.c.tx {
|
||||
panic("database/sql/driver: misuse of sqlite driver: extra Rollback")
|
||||
}
|
||||
t.c.tx = false
|
||||
err := t.c.exec("ROLLBACK")
|
||||
t.c = nil
|
||||
return err
|
||||
}
|
||||
|
||||
type stmt struct {
|
||||
c *conn
|
||||
stmt *C.sqlite3_stmt
|
||||
err error
|
||||
t0 time.Time
|
||||
sql string
|
||||
args string
|
||||
closed bool
|
||||
rows bool
|
||||
colnames []string
|
||||
coltypes []string
|
||||
}
|
||||
|
||||
func (s *stmt) Close() error {
|
||||
if s.rows {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Close with active Rows")
|
||||
}
|
||||
if s.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: double Close of Stmt")
|
||||
}
|
||||
s.closed = true
|
||||
rv := C.sqlite3_finalize(s.stmt)
|
||||
if rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stmt) NumInput() int {
|
||||
if s.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: NumInput after Close")
|
||||
}
|
||||
return int(C.sqlite3_bind_parameter_count(s.stmt))
|
||||
}
|
||||
|
||||
func (s *stmt) reset() error {
|
||||
return s.c.error(C.sqlite3_reset(s.stmt))
|
||||
}
|
||||
|
||||
func (s *stmt) start(args []driver.Value) error {
|
||||
if err := s.reset(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n := int(C.sqlite3_bind_parameter_count(s.stmt))
|
||||
if n != len(args) {
|
||||
return fmt.Errorf("incorrect argument count for command: have %d want %d", len(args), n)
|
||||
}
|
||||
|
||||
for i, v := range args {
|
||||
var str string
|
||||
switch v := v.(type) {
|
||||
case nil:
|
||||
if rv := C.sqlite3_bind_null(s.stmt, C.int(i+1)); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case float64:
|
||||
if rv := C.sqlite3_bind_double(s.stmt, C.int(i+1), C.double(v)); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case int64:
|
||||
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(v)); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case []byte:
|
||||
var p *byte
|
||||
if len(v) > 0 {
|
||||
p = &v[0]
|
||||
}
|
||||
if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case bool:
|
||||
var vi int64
|
||||
if v {
|
||||
vi = 1
|
||||
}
|
||||
if rv := C.sqlite3_bind_int64(s.stmt, C.int(i+1), C.sqlite3_int64(vi)); rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
continue
|
||||
|
||||
case time.Time:
|
||||
str = v.UTC().Format(timefmt[0])
|
||||
|
||||
case string:
|
||||
str = v
|
||||
|
||||
default:
|
||||
str = fmt.Sprint(v)
|
||||
}
|
||||
|
||||
cstr := C.CString(str)
|
||||
rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
|
||||
C.free(unsafe.Pointer(cstr))
|
||||
if rv != 0 {
|
||||
return s.c.error(rv)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||
if s.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Exec after Close")
|
||||
}
|
||||
if s.rows {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Exec with active Rows")
|
||||
}
|
||||
|
||||
err := s.start(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rv := C.sqlite3_step(s.stmt)
|
||||
if errno(rv) != stepDone {
|
||||
if rv == 0 {
|
||||
rv = 21 // errMisuse
|
||||
}
|
||||
return nil, s.c.error(rv)
|
||||
}
|
||||
|
||||
id := int64(C.sqlite3_last_insert_rowid(s.c.db))
|
||||
rows := int64(C.sqlite3_changes(s.c.db))
|
||||
return &result{id, rows}, nil
|
||||
}
|
||||
|
||||
func (s *stmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||
if s.closed {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Query after Close")
|
||||
}
|
||||
if s.rows {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Query with active Rows")
|
||||
}
|
||||
|
||||
err := s.start(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.rows = true
|
||||
if s.colnames == nil {
|
||||
n := int64(C.sqlite3_column_count(s.stmt))
|
||||
s.colnames = make([]string, n)
|
||||
s.coltypes = make([]string, n)
|
||||
for i := range s.colnames {
|
||||
s.colnames[i] = C.GoString(C.sqlite3_column_name(s.stmt, C.int(i)))
|
||||
s.coltypes[i] = strings.ToLower(C.GoString(C.sqlite3_column_decltype(s.stmt, C.int(i))))
|
||||
}
|
||||
}
|
||||
return &rows{s}, nil
|
||||
}
|
||||
|
||||
type rows struct {
|
||||
s *stmt
|
||||
}
|
||||
|
||||
func (r *rows) Columns() []string {
|
||||
if r.s == nil {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Columns of closed Rows")
|
||||
}
|
||||
return r.s.colnames
|
||||
}
|
||||
|
||||
const maxslice = 1<<31 - 1
|
||||
|
||||
var timefmt = []string{
|
||||
"2006-01-02 15:04:05.999999999",
|
||||
"2006-01-02T15:04:05.999999999",
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02 15:04",
|
||||
"2006-01-02T15:04",
|
||||
"2006-01-02",
|
||||
}
|
||||
|
||||
func (r *rows) Next(dst []driver.Value) error {
|
||||
if r.s == nil {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Next of closed Rows")
|
||||
}
|
||||
|
||||
rv := C.sqlite3_step(r.s.stmt)
|
||||
if errno(rv) != stepRow {
|
||||
if errno(rv) == stepDone {
|
||||
return io.EOF
|
||||
}
|
||||
if rv == 0 {
|
||||
rv = 21
|
||||
}
|
||||
return r.s.c.error(rv)
|
||||
}
|
||||
|
||||
for i := range dst {
|
||||
switch typ := C.sqlite3_column_type(r.s.stmt, C.int(i)); typ {
|
||||
default:
|
||||
return fmt.Errorf("unexpected sqlite3 column type %d", typ)
|
||||
case C.SQLITE_INTEGER:
|
||||
val := int64(C.sqlite3_column_int64(r.s.stmt, C.int(i)))
|
||||
switch r.s.coltypes[i] {
|
||||
case "timestamp", "datetime":
|
||||
dst[i] = time.Unix(val, 0).UTC()
|
||||
case "boolean":
|
||||
dst[i] = val > 0
|
||||
default:
|
||||
dst[i] = val
|
||||
}
|
||||
|
||||
case C.SQLITE_FLOAT:
|
||||
dst[i] = float64(C.sqlite3_column_double(r.s.stmt, C.int(i)))
|
||||
|
||||
case C.SQLITE_BLOB, C.SQLITE_TEXT:
|
||||
n := int(C.sqlite3_column_bytes(r.s.stmt, C.int(i)))
|
||||
var b []byte
|
||||
if n > 0 {
|
||||
p := C.sqlite3_column_blob(r.s.stmt, C.int(i))
|
||||
b = (*[maxslice]byte)(unsafe.Pointer(p))[:n]
|
||||
}
|
||||
dst[i] = b
|
||||
switch r.s.coltypes[i] {
|
||||
case "timestamp", "datetime":
|
||||
dst[i] = time.Time{}
|
||||
s := string(b)
|
||||
for _, f := range timefmt {
|
||||
if t, err := time.Parse(f, s); err == nil {
|
||||
dst[i] = t
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case C.SQLITE_NULL:
|
||||
dst[i] = nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rows) Close() error {
|
||||
if r.s == nil {
|
||||
panic("database/sql/driver: misuse of sqlite driver: Close of closed Rows")
|
||||
}
|
||||
r.s.rows = false
|
||||
r.s = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
type result struct {
|
||||
id int64
|
||||
rows int64
|
||||
}
|
||||
|
||||
func (r *result) LastInsertId() (int64, error) {
|
||||
return r.id, nil
|
||||
}
|
||||
|
||||
func (r *result) RowsAffected() (int64, error) {
|
||||
return r.rows, nil
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
/*.test
|
|
@ -1,16 +0,0 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- release
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go get github.com/kisielk/errcheck
|
||||
- go get ./...
|
||||
|
||||
- go test -v ./...
|
||||
- go test -race ./...
|
||||
- errcheck github.com/DATA-DOG/go-sqlmock
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
The three clause BSD license (http://en.wikipedia.org/wiki/BSD_licenses)
|
||||
|
||||
Copyright (c) 2013, DataDog.lt team
|
||||
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.
|
||||
|
||||
* The name DataDog.lt may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
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 MICHAEL BOSTOCK 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.
|
|
@ -1,360 +0,0 @@
|
|||
[](https://travis-ci.org/DATA-DOG/go-sqlmock)
|
||||
[](https://godoc.org/github.com/DATA-DOG/go-sqlmock)
|
||||
|
||||
# Sql driver mock for Golang
|
||||
|
||||
This is a **mock** driver as **database/sql/driver** which is very flexible and pragmatic to
|
||||
manage and mock expected queries. All the expectations should be met and all queries and actions
|
||||
triggered should be mocked in order to pass a test.
|
||||
|
||||
## Install
|
||||
|
||||
go get github.com/DATA-DOG/go-sqlmock
|
||||
|
||||
## Use it with pleasure
|
||||
|
||||
An example of some database interaction which you may want to test:
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/kisielk/sqlstruct"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
const ORDER_PENDING = 0
|
||||
const ORDER_CANCELLED = 1
|
||||
|
||||
type User struct {
|
||||
Id int `sql:"id"`
|
||||
Username string `sql:"username"`
|
||||
Balance float64 `sql:"balance"`
|
||||
}
|
||||
|
||||
type Order struct {
|
||||
Id int `sql:"id"`
|
||||
Value float64 `sql:"value"`
|
||||
ReservedFee float64 `sql:"reserved_fee"`
|
||||
Status int `sql:"status"`
|
||||
}
|
||||
|
||||
func cancelOrder(id int, db *sql.DB) (err error) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var order Order
|
||||
var user User
|
||||
sql := fmt.Sprintf(`
|
||||
SELECT %s, %s
|
||||
FROM orders AS o
|
||||
INNER JOIN users AS u ON o.buyer_id = u.id
|
||||
WHERE o.id = ?
|
||||
FOR UPDATE`,
|
||||
sqlstruct.ColumnsAliased(order, "o"),
|
||||
sqlstruct.ColumnsAliased(user, "u"))
|
||||
|
||||
// fetch order to cancel
|
||||
rows, err := tx.Query(sql, id)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
// no rows, nothing to do
|
||||
if !rows.Next() {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// read order
|
||||
err = sqlstruct.ScanAliased(&order, rows, "o")
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// ensure order status
|
||||
if order.Status != ORDER_PENDING {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// read user
|
||||
err = sqlstruct.ScanAliased(&user, rows, "u")
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
rows.Close() // manually close before other prepared statements
|
||||
|
||||
// refund order value
|
||||
sql = "UPDATE users SET balance = balance + ? WHERE id = ?"
|
||||
refundStmt, err := tx.Prepare(sql)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
defer refundStmt.Close()
|
||||
_, err = refundStmt.Exec(order.Value + order.ReservedFee, user.Id)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// update order status
|
||||
order.Status = ORDER_CANCELLED
|
||||
sql = "UPDATE orders SET status = ?, updated = NOW() WHERE id = ?"
|
||||
orderUpdStmt, err := tx.Prepare(sql)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
defer orderUpdStmt.Close()
|
||||
_, err = orderUpdStmt.Exec(order.Status, order.Id)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func main() {
|
||||
db, err := sql.Open("mysql", "root:nimda@/test")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
err = cancelOrder(1, db)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And the clean nice test:
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"testing"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// will test that order with a different status, cannot be cancelled
|
||||
func TestShouldNotCancelOrderWithNonPendingStatus(t *testing.T) {
|
||||
// open database stub
|
||||
db, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Errorf("An error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
// columns are prefixed with "o" since we used sqlstruct to generate them
|
||||
columns := []string{"o_id", "o_status"}
|
||||
// expect transaction begin
|
||||
sqlmock.ExpectBegin()
|
||||
// expect query to fetch order and user, match it with regexp
|
||||
sqlmock.ExpectQuery("SELECT (.+) FROM orders AS o INNER JOIN users AS u (.+) FOR UPDATE").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows(columns).FromCSVString("1,1"))
|
||||
// expect transaction rollback, since order status is "cancelled"
|
||||
sqlmock.ExpectRollback()
|
||||
|
||||
// run the cancel order function
|
||||
err = cancelOrder(1, db)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, but got %s instead", err)
|
||||
}
|
||||
// db.Close() ensures that all expectations have been met
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("Error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
// will test order cancellation
|
||||
func TestShouldRefundUserWhenOrderIsCancelled(t *testing.T) {
|
||||
// open database stub
|
||||
db, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Errorf("An error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
// columns are prefixed with "o" since we used sqlstruct to generate them
|
||||
columns := []string{"o_id", "o_status", "o_value", "o_reserved_fee", "u_id", "u_balance"}
|
||||
// expect transaction begin
|
||||
sqlmock.ExpectBegin()
|
||||
// expect query to fetch order and user, match it with regexp
|
||||
sqlmock.ExpectQuery("SELECT (.+) FROM orders AS o INNER JOIN users AS u (.+) FOR UPDATE").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows(columns).AddRow(1, 0, 25.75, 3.25, 2, 10.00))
|
||||
// expect user balance update
|
||||
sqlmock.ExpectExec("UPDATE users SET balance").
|
||||
WithArgs(25.75 + 3.25, 2). // refund amount, user id
|
||||
WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row
|
||||
// expect order status update
|
||||
sqlmock.ExpectExec("UPDATE orders SET status").
|
||||
WithArgs(ORDER_CANCELLED, 1). // status, id
|
||||
WillReturnResult(sqlmock.NewResult(0, 1)) // no insert id, 1 affected row
|
||||
// expect a transaction commit
|
||||
sqlmock.ExpectCommit()
|
||||
|
||||
// run the cancel order function
|
||||
err = cancelOrder(1, db)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, but got %s instead", err)
|
||||
}
|
||||
// db.Close() ensures that all expectations have been met
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("Error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
// will test order cancellation
|
||||
func TestShouldRollbackOnError(t *testing.T) {
|
||||
// open database stub
|
||||
db, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Errorf("An error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
// expect transaction begin
|
||||
sqlmock.ExpectBegin()
|
||||
// expect query to fetch order and user, match it with regexp
|
||||
sqlmock.ExpectQuery("SELECT (.+) FROM orders AS o INNER JOIN users AS u (.+) FOR UPDATE").
|
||||
WithArgs(1).
|
||||
WillReturnError(fmt.Errorf("Some error"))
|
||||
// should rollback since error was returned from query execution
|
||||
sqlmock.ExpectRollback()
|
||||
|
||||
// run the cancel order function
|
||||
err = cancelOrder(1, db)
|
||||
// error should return back
|
||||
if err == nil {
|
||||
t.Error("Expected error, but got none")
|
||||
}
|
||||
// db.Close() ensures that all expectations have been met
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("Error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Expectations
|
||||
|
||||
All **Expect** methods return a **Mock** interface which allow you to describe
|
||||
expectations in more details: return an error, expect specific arguments, return rows and so on.
|
||||
**NOTE:** that if you call **WithArgs** on a non query based expectation, it will panic
|
||||
|
||||
A **Mock** interface:
|
||||
|
||||
``` go
|
||||
type Mock interface {
|
||||
WithArgs(...driver.Value) Mock
|
||||
WillReturnError(error) Mock
|
||||
WillReturnRows(driver.Rows) Mock
|
||||
WillReturnResult(driver.Result) Mock
|
||||
}
|
||||
```
|
||||
|
||||
As an example we can expect a transaction commit and simulate an error for it:
|
||||
|
||||
``` go
|
||||
sqlmock.ExpectCommit().WillReturnError(fmt.Errorf("Deadlock occured"))
|
||||
```
|
||||
|
||||
In same fashion, we can expect queries to match arguments. If there are any, it must be matched.
|
||||
Instead of result we can return error.
|
||||
|
||||
``` go
|
||||
sqlmock.ExpectQuery("SELECT (.*) FROM orders").
|
||||
WithArgs("string value").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"col"}).AddRow("val"))
|
||||
```
|
||||
|
||||
**NOTE:** it matches a regular expression. Some regex special characters must be escaped if you want to match them.
|
||||
For example if we want to match a subselect:
|
||||
|
||||
``` go
|
||||
sqlmock.ExpectQuery("SELECT (.*) FROM orders WHERE id IN \\(SELECT id FROM finished WHERE status = 1\\)").
|
||||
WithArgs("string value").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"col"}).AddRow("val"))
|
||||
```
|
||||
|
||||
**WithArgs** expectation, compares values based on their type, for usual values like **string, float, int**
|
||||
it matches the actual value. Types like **time** are compared only by type. Other types might require different ways
|
||||
to compare them correctly, this may be improved.
|
||||
|
||||
You can build rows either from CSV string or from interface values:
|
||||
|
||||
**Rows** interface, which satisfies sql driver.Rows:
|
||||
|
||||
``` go
|
||||
type Rows interface {
|
||||
AddRow(...driver.Value) Rows
|
||||
FromCSVString(s string) Rows
|
||||
Next([]driver.Value) error
|
||||
Columns() []string
|
||||
Close() error
|
||||
}
|
||||
```
|
||||
|
||||
Example for to build rows:
|
||||
|
||||
``` go
|
||||
rs := sqlmock.NewRows([]string{"column1", "column2"}).
|
||||
FromCSVString("one,1\ntwo,2").
|
||||
AddRow("three", 3)
|
||||
```
|
||||
|
||||
**Prepare** will ignore other expectations if ExpectPrepare not set. When set, can expect normal result or simulate an error:
|
||||
|
||||
``` go
|
||||
rs := sqlmock.ExpectPrepare().
|
||||
WillReturnError(fmt.Errorf("Query prepare failed"))
|
||||
```
|
||||
|
||||
## Run tests
|
||||
|
||||
go test
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit [godoc](http://godoc.org/github.com/DATA-DOG/go-sqlmock)
|
||||
See **.travis.yml** for supported **go** versions
|
||||
Different use case, is to functionally test with a real database - [go-txdb](https://github.com/DATA-DOG/go-txdb)
|
||||
all database related actions are isolated within a single transaction so the database can remain in the same state.
|
||||
|
||||
## Changes
|
||||
|
||||
- **2014-08-16** instead of **panic** during reflect type mismatch when comparing query arguments - now return error
|
||||
- **2014-08-14** added **sqlmock.NewErrorResult** which gives an option to return driver.Result with errors for
|
||||
interface methods, see [issue](https://github.com/DATA-DOG/go-sqlmock/issues/5)
|
||||
- **2014-05-29** allow to match arguments in more sophisticated ways, by providing an **sqlmock.Argument** interface
|
||||
- **2014-04-21** introduce **sqlmock.New()** to open a mock database connection for tests. This method
|
||||
calls sql.DB.Ping to ensure that connection is open, see [issue](https://github.com/DATA-DOG/go-sqlmock/issues/4).
|
||||
This way on Close it will surely assert if all expectations are met, even if database was not triggered at all.
|
||||
The old way is still available, but it is advisable to call db.Ping manually before asserting with db.Close.
|
||||
- **2014-02-14** RowsFromCSVString is now a part of Rows interface named as FromCSVString.
|
||||
It has changed to allow more ways to construct rows and to easily extend this API in future.
|
||||
See [issue 1](https://github.com/DATA-DOG/go-sqlmock/issues/1)
|
||||
**RowsFromCSVString** is deprecated and will be removed in future
|
||||
|
||||
## Contributions
|
||||
|
||||
Feel free to open a pull request. Note, if you wish to contribute an extension to public (exported methods or types) -
|
||||
please open an issue before, to discuss whether these changes can be accepted. All backward incompatible changes are
|
||||
and will be treated cautiously
|
||||
|
||||
## License
|
||||
|
||||
The [three clause BSD license](http://en.wikipedia.org/wiki/BSD_licenses)
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type conn struct {
|
||||
expectations []expectation
|
||||
active expectation
|
||||
}
|
||||
|
||||
// Close a mock database driver connection. It should
|
||||
// be always called to ensure that all expectations
|
||||
// were met successfully. Returns error if there is any
|
||||
func (c *conn) Close() (err error) {
|
||||
for _, e := range mock.conn.expectations {
|
||||
if !e.fulfilled() {
|
||||
err = fmt.Errorf("there is a remaining expectation %T which was not matched yet", e)
|
||||
break
|
||||
}
|
||||
}
|
||||
mock.conn.expectations = []expectation{}
|
||||
mock.conn.active = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *conn) Begin() (driver.Tx, error) {
|
||||
e := c.next()
|
||||
if e == nil {
|
||||
return nil, fmt.Errorf("all expectations were already fulfilled, call to begin transaction was not expected")
|
||||
}
|
||||
|
||||
etb, ok := e.(*expectedBegin)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("call to begin transaction, was not expected, next expectation is %T as %+v", e, e)
|
||||
}
|
||||
etb.triggered = true
|
||||
return &transaction{c}, etb.err
|
||||
}
|
||||
|
||||
// get next unfulfilled expectation
|
||||
func (c *conn) next() (e expectation) {
|
||||
for _, e = range c.expectations {
|
||||
if !e.fulfilled() {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil // all expectations were fulfilled
|
||||
}
|
||||
|
||||
func (c *conn) Exec(query string, args []driver.Value) (res driver.Result, err error) {
|
||||
e := c.next()
|
||||
query = stripQuery(query)
|
||||
if e == nil {
|
||||
return nil, fmt.Errorf("all expectations were already fulfilled, call to exec '%s' query with args %+v was not expected", query, args)
|
||||
}
|
||||
|
||||
eq, ok := e.(*expectedExec)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("call to exec query '%s' with args %+v, was not expected, next expectation is %T as %+v", query, args, e, e)
|
||||
}
|
||||
|
||||
eq.triggered = true
|
||||
|
||||
defer argMatcherErrorHandler(&err) // converts panic to error in case of reflect value type mismatch
|
||||
|
||||
if !eq.queryMatches(query) {
|
||||
return nil, fmt.Errorf("exec query '%s', does not match regex '%s'", query, eq.sqlRegex.String())
|
||||
}
|
||||
|
||||
if !eq.argsMatches(args) {
|
||||
return nil, fmt.Errorf("exec query '%s', args %+v does not match expected %+v", query, args, eq.args)
|
||||
}
|
||||
|
||||
if eq.err != nil {
|
||||
return nil, eq.err // mocked to return error
|
||||
}
|
||||
|
||||
if eq.result == nil {
|
||||
return nil, fmt.Errorf("exec query '%s' with args %+v, must return a database/sql/driver.result, but it was not set for expectation %T as %+v", query, args, eq, eq)
|
||||
}
|
||||
|
||||
return eq.result, err
|
||||
}
|
||||
|
||||
func (c *conn) Prepare(query string) (driver.Stmt, error) {
|
||||
e := c.next()
|
||||
|
||||
// for backwards compatibility, ignore when Prepare not expected
|
||||
if e == nil {
|
||||
return &statement{mock.conn, stripQuery(query)}, nil
|
||||
}
|
||||
eq, ok := e.(*expectedPrepare)
|
||||
if !ok {
|
||||
return &statement{mock.conn, stripQuery(query)}, nil
|
||||
}
|
||||
|
||||
eq.triggered = true
|
||||
if eq.err != nil {
|
||||
return nil, eq.err // mocked to return error
|
||||
}
|
||||
|
||||
return &statement{mock.conn, stripQuery(query)}, nil
|
||||
}
|
||||
|
||||
func (c *conn) Query(query string, args []driver.Value) (rw driver.Rows, err error) {
|
||||
e := c.next()
|
||||
query = stripQuery(query)
|
||||
if e == nil {
|
||||
return nil, fmt.Errorf("all expectations were already fulfilled, call to query '%s' with args %+v was not expected", query, args)
|
||||
}
|
||||
|
||||
eq, ok := e.(*expectedQuery)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("call to query '%s' with args %+v, was not expected, next expectation is %T as %+v", query, args, e, e)
|
||||
}
|
||||
|
||||
eq.triggered = true
|
||||
|
||||
defer argMatcherErrorHandler(&err) // converts panic to error in case of reflect value type mismatch
|
||||
|
||||
if !eq.queryMatches(query) {
|
||||
return nil, fmt.Errorf("query '%s', does not match regex [%s]", query, eq.sqlRegex.String())
|
||||
}
|
||||
|
||||
if !eq.argsMatches(args) {
|
||||
return nil, fmt.Errorf("query '%s', args %+v does not match expected %+v", query, args, eq.args)
|
||||
}
|
||||
|
||||
if eq.err != nil {
|
||||
return nil, eq.err // mocked to return error
|
||||
}
|
||||
|
||||
if eq.rows == nil {
|
||||
return nil, fmt.Errorf("query '%s' with args %+v, must return a database/sql/driver.rows, but it was not set for expectation %T as %+v", query, args, eq, eq)
|
||||
}
|
||||
|
||||
return eq.rows, err
|
||||
}
|
||||
|
||||
func argMatcherErrorHandler(errp *error) {
|
||||
if e := recover(); e != nil {
|
||||
if se, ok := e.(*reflect.ValueError); ok { // catch reflect error, failed type conversion
|
||||
*errp = fmt.Errorf("Failed to compare query arguments: %s", se)
|
||||
} else {
|
||||
panic(e) // overwise panic
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,378 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExecNoExpectations(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedExec{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
triggered: true,
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("otherquery")),
|
||||
args: []driver.Value{456},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Exec("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Result should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("all expectations were already fulfilled, call to exec"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecExpectationMismatch(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedQuery{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("otherquery")),
|
||||
args: []driver.Value{456},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Exec("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Result should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("was not expected, next expectation is"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecQueryMismatch(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedExec{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("otherquery")),
|
||||
args: []driver.Value{456},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Exec("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Result should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("does not match regex"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecArgsMismatch(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedExec{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("query")),
|
||||
args: []driver.Value{456},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Exec("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Result should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("does not match expected"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecWillReturnError(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedExec{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("query")),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Exec("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Result should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
if err.Error() != "WillReturnError" {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecMissingResult(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedExec{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("query")),
|
||||
args: []driver.Value{123},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Exec("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Result should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("must return a database/sql/driver.result, but it was not set for expectation"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExec(t *testing.T) {
|
||||
expectedResult := driver.Result(&result{})
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedExec{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("query")),
|
||||
args: []driver.Value{123},
|
||||
},
|
||||
result: expectedResult,
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Exec("query", []driver.Value{123})
|
||||
if res == nil {
|
||||
t.Error("Result should not be nil")
|
||||
}
|
||||
if res != expectedResult {
|
||||
t.Errorf("Result should match expected Result (actual %+v)", res)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("error should be nil (actual %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryNoExpectations(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedQuery{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
triggered: true,
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("otherquery")),
|
||||
args: []driver.Value{456},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Query("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Rows should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("all expectations were already fulfilled, call to query"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryExpectationMismatch(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedExec{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("otherquery")),
|
||||
args: []driver.Value{456},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Query("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Rows should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("was not expected, next expectation is"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryQueryMismatch(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedQuery{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("otherquery")),
|
||||
args: []driver.Value{456},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Query("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Rows should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("does not match regex"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryArgsMismatch(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedQuery{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("query")),
|
||||
args: []driver.Value{456},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Query("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Rows should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("does not match expected"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryWillReturnError(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedQuery{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{
|
||||
err: errors.New("WillReturnError"),
|
||||
},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("query")),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Query("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Rows should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
if err.Error() != "WillReturnError" {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryMissingRows(t *testing.T) {
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedQuery{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("query")),
|
||||
args: []driver.Value{123},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res, err := c.Query("query", []driver.Value{123})
|
||||
if res != nil {
|
||||
t.Error("Rows should be nil")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("error should not be nil")
|
||||
}
|
||||
pattern := regexp.MustCompile(regexp.QuoteMeta("must return a database/sql/driver.rows, but it was not set for expectation"))
|
||||
if !pattern.MatchString(err.Error()) {
|
||||
t.Errorf("error should match expected error message (actual: %s)", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuery(t *testing.T) {
|
||||
expectedRows := driver.Rows(&rows{})
|
||||
c := &conn{
|
||||
expectations: []expectation{
|
||||
&expectedQuery{
|
||||
queryBasedExpectation: queryBasedExpectation{
|
||||
commonExpectation: commonExpectation{},
|
||||
sqlRegex: regexp.MustCompile(regexp.QuoteMeta("query")),
|
||||
args: []driver.Value{123},
|
||||
},
|
||||
rows: expectedRows,
|
||||
},
|
||||
},
|
||||
}
|
||||
rows, err := c.Query("query", []driver.Value{123})
|
||||
if rows == nil {
|
||||
t.Error("Rows should not be nil")
|
||||
}
|
||||
if rows != expectedRows {
|
||||
t.Errorf("Rows should match expected Rows (actual %+v)", rows)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("error should be nil (actual %s)", err.Error())
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Argument interface allows to match
|
||||
// any argument in specific way
|
||||
type Argument interface {
|
||||
Match(driver.Value) bool
|
||||
}
|
||||
|
||||
// an expectation interface
|
||||
type expectation interface {
|
||||
fulfilled() bool
|
||||
setError(err error)
|
||||
}
|
||||
|
||||
// common expectation struct
|
||||
// satisfies the expectation interface
|
||||
type commonExpectation struct {
|
||||
triggered bool
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *commonExpectation) fulfilled() bool {
|
||||
return e.triggered
|
||||
}
|
||||
|
||||
func (e *commonExpectation) setError(err error) {
|
||||
e.err = err
|
||||
}
|
||||
|
||||
// query based expectation
|
||||
// adds a query matching logic
|
||||
type queryBasedExpectation struct {
|
||||
commonExpectation
|
||||
sqlRegex *regexp.Regexp
|
||||
args []driver.Value
|
||||
}
|
||||
|
||||
func (e *queryBasedExpectation) queryMatches(sql string) bool {
|
||||
return e.sqlRegex.MatchString(sql)
|
||||
}
|
||||
|
||||
func (e *queryBasedExpectation) argsMatches(args []driver.Value) bool {
|
||||
if nil == e.args {
|
||||
return true
|
||||
}
|
||||
if len(args) != len(e.args) {
|
||||
return false
|
||||
}
|
||||
for k, v := range args {
|
||||
matcher, ok := e.args[k].(Argument)
|
||||
if ok {
|
||||
if !matcher.Match(v) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
vi := reflect.ValueOf(v)
|
||||
ai := reflect.ValueOf(e.args[k])
|
||||
switch vi.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if vi.Int() != ai.Int() {
|
||||
return false
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if vi.Float() != ai.Float() {
|
||||
return false
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if vi.Uint() != ai.Uint() {
|
||||
return false
|
||||
}
|
||||
case reflect.String:
|
||||
if vi.String() != ai.String() {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
// compare types like time.Time based on type only
|
||||
if vi.Kind() != ai.Kind() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// begin transaction
|
||||
type expectedBegin struct {
|
||||
commonExpectation
|
||||
}
|
||||
|
||||
// tx commit
|
||||
type expectedCommit struct {
|
||||
commonExpectation
|
||||
}
|
||||
|
||||
// tx rollback
|
||||
type expectedRollback struct {
|
||||
commonExpectation
|
||||
}
|
||||
|
||||
// query expectation
|
||||
type expectedQuery struct {
|
||||
queryBasedExpectation
|
||||
|
||||
rows driver.Rows
|
||||
}
|
||||
|
||||
// exec query expectation
|
||||
type expectedExec struct {
|
||||
queryBasedExpectation
|
||||
|
||||
result driver.Result
|
||||
}
|
||||
|
||||
// Prepare expectation
|
||||
type expectedPrepare struct {
|
||||
commonExpectation
|
||||
|
||||
statement driver.Stmt
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type matcher struct {
|
||||
}
|
||||
|
||||
func (m matcher) Match(driver.Value) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestQueryExpectationArgComparison(t *testing.T) {
|
||||
e := &queryBasedExpectation{}
|
||||
against := []driver.Value{5}
|
||||
if !e.argsMatches(against) {
|
||||
t.Error("arguments should match, since the no expectation was set")
|
||||
}
|
||||
|
||||
e.args = []driver.Value{5, "str"}
|
||||
|
||||
against = []driver.Value{5}
|
||||
if e.argsMatches(against) {
|
||||
t.Error("arguments should not match, since the size is not the same")
|
||||
}
|
||||
|
||||
against = []driver.Value{3, "str"}
|
||||
if e.argsMatches(against) {
|
||||
t.Error("arguments should not match, since the first argument (int value) is different")
|
||||
}
|
||||
|
||||
against = []driver.Value{5, "st"}
|
||||
if e.argsMatches(against) {
|
||||
t.Error("arguments should not match, since the second argument (string value) is different")
|
||||
}
|
||||
|
||||
against = []driver.Value{5, "str"}
|
||||
if !e.argsMatches(against) {
|
||||
t.Error("arguments should match, but it did not")
|
||||
}
|
||||
|
||||
e.args = []driver.Value{5, time.Now()}
|
||||
|
||||
const longForm = "Jan 2, 2006 at 3:04pm (MST)"
|
||||
tm, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
|
||||
|
||||
against = []driver.Value{5, tm}
|
||||
if !e.argsMatches(against) {
|
||||
t.Error("arguments should match (time will be compared only by type), but it did not")
|
||||
}
|
||||
|
||||
against = []driver.Value{5, matcher{}}
|
||||
if !e.argsMatches(against) {
|
||||
t.Error("arguments should match, but it did not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryExpectationSqlMatch(t *testing.T) {
|
||||
e := &expectedExec{}
|
||||
e.sqlRegex = regexp.MustCompile("SELECT x FROM")
|
||||
if !e.queryMatches("SELECT x FROM someting") {
|
||||
t.Errorf("Sql must have matched the query")
|
||||
}
|
||||
|
||||
e.sqlRegex = regexp.MustCompile("SELECT COUNT\\(x\\) FROM")
|
||||
if !e.queryMatches("SELECT COUNT(x) FROM someting") {
|
||||
t.Errorf("Sql must have matched the query")
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
)
|
||||
|
||||
// Result satisfies sql driver Result, which
|
||||
// holds last insert id and rows affected
|
||||
// by Exec queries
|
||||
type result struct {
|
||||
insertID int64
|
||||
rowsAffected int64
|
||||
err error
|
||||
}
|
||||
|
||||
// NewResult creates a new sql driver Result
|
||||
// for Exec based query mocks.
|
||||
func NewResult(lastInsertID int64, rowsAffected int64) driver.Result {
|
||||
return &result{
|
||||
insertID: lastInsertID,
|
||||
rowsAffected: rowsAffected,
|
||||
}
|
||||
}
|
||||
|
||||
// NewErrorResult creates a new sql driver Result
|
||||
// which returns an error given for both interface methods
|
||||
func NewErrorResult(err error) driver.Result {
|
||||
return &result{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *result) LastInsertId() (int64, error) {
|
||||
return r.insertID, r.err
|
||||
}
|
||||
|
||||
func (r *result) RowsAffected() (int64, error) {
|
||||
return r.rowsAffected, r.err
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShouldReturnValidSqlDriverResult(t *testing.T) {
|
||||
result := NewResult(1, 2)
|
||||
id, err := result.LastInsertId()
|
||||
if 1 != id {
|
||||
t.Errorf("Expected last insert id to be 1, but got: %d", id)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, but got: %s", err)
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if 2 != affected {
|
||||
t.Errorf("Expected affected rows to be 2, but got: %d", affected)
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, but got: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldReturnErroeSqlDriverResult(t *testing.T) {
|
||||
result := NewErrorResult(fmt.Errorf("some error"))
|
||||
_, err := result.LastInsertId()
|
||||
if err == nil {
|
||||
t.Error("expected error, but got none")
|
||||
}
|
||||
_, err = result.RowsAffected()
|
||||
if err == nil {
|
||||
t.Error("expected error, but got none")
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Rows interface allows to construct rows
|
||||
// which also satisfies database/sql/driver.Rows interface
|
||||
type Rows interface {
|
||||
driver.Rows // composed interface, supports sql driver.Rows
|
||||
AddRow(...driver.Value) Rows
|
||||
FromCSVString(s string) Rows
|
||||
}
|
||||
|
||||
// a struct which implements database/sql/driver.Rows
|
||||
type rows struct {
|
||||
cols []string
|
||||
rows [][]driver.Value
|
||||
pos int
|
||||
}
|
||||
|
||||
func (r *rows) Columns() []string {
|
||||
return r.cols
|
||||
}
|
||||
|
||||
func (r *rows) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rows) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// advances to next row
|
||||
func (r *rows) Next(dest []driver.Value) error {
|
||||
r.pos++
|
||||
if r.pos > len(r.rows) {
|
||||
return io.EOF // per interface spec
|
||||
}
|
||||
|
||||
for i, col := range r.rows[r.pos-1] {
|
||||
dest[i] = col
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewRows allows Rows to be created from a group of
|
||||
// sql driver.Value or from the CSV string and
|
||||
// to be used as sql driver.Rows
|
||||
func NewRows(columns []string) Rows {
|
||||
return &rows{cols: columns}
|
||||
}
|
||||
|
||||
// AddRow adds a row which is built from arguments
|
||||
// in the same column order, returns sql driver.Rows
|
||||
// compatible interface
|
||||
func (r *rows) AddRow(values ...driver.Value) Rows {
|
||||
if len(values) != len(r.cols) {
|
||||
panic("Expected number of values to match number of columns")
|
||||
}
|
||||
|
||||
row := make([]driver.Value, len(r.cols))
|
||||
for i, v := range values {
|
||||
row[i] = v
|
||||
}
|
||||
|
||||
r.rows = append(r.rows, row)
|
||||
return r
|
||||
}
|
||||
|
||||
// FromCSVString adds rows from CSV string.
|
||||
// Returns sql driver.Rows compatible interface
|
||||
func (r *rows) FromCSVString(s string) Rows {
|
||||
res := strings.NewReader(strings.TrimSpace(s))
|
||||
csvReader := csv.NewReader(res)
|
||||
|
||||
for {
|
||||
res, err := csvReader.Read()
|
||||
if err != nil || res == nil {
|
||||
break
|
||||
}
|
||||
|
||||
row := make([]driver.Value, len(r.cols))
|
||||
for i, v := range res {
|
||||
row[i] = []byte(strings.TrimSpace(v))
|
||||
}
|
||||
r.rows = append(r.rows, row)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// RowsFromCSVString creates Rows from CSV string
|
||||
// to be used for mocked queries. Returns sql driver Rows interface
|
||||
// ** DEPRECATED ** will be removed in the future, use Rows.FromCSVString
|
||||
func RowsFromCSVString(columns []string, s string) driver.Rows {
|
||||
rs := &rows{}
|
||||
rs.cols = columns
|
||||
|
||||
r := strings.NewReader(strings.TrimSpace(s))
|
||||
csvReader := csv.NewReader(r)
|
||||
|
||||
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)
|
||||
row[i] = []byte(v)
|
||||
}
|
||||
rs.rows = append(rs.rows, row)
|
||||
}
|
||||
return rs
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
/*
|
||||
Package sqlmock provides sql driver mock connecection, which allows to test database,
|
||||
create expectations and ensure the correct execution flow of any database operations.
|
||||
It hooks into Go standard library's database/sql package.
|
||||
|
||||
The package provides convenient methods to mock database queries, transactions and
|
||||
expect the right execution flow, compare query arguments or even return error instead
|
||||
to simulate failures. See the example bellow, which illustrates how convenient it is
|
||||
to work with:
|
||||
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"testing"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// will test that order with a different status, cannot be cancelled
|
||||
func TestShouldNotCancelOrderWithNonPendingStatus(t *testing.T) {
|
||||
// open database stub
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("An error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
// columns to be used for result
|
||||
columns := []string{"id", "status"}
|
||||
// expect transaction begin
|
||||
sqlmock.ExpectBegin()
|
||||
// expect query to fetch order, match it with regexp
|
||||
sqlmock.ExpectQuery("SELECT (.+) FROM orders (.+) FOR UPDATE").
|
||||
WithArgs(1).
|
||||
WillReturnRows(sqlmock.NewRows(columns).FromCSVString("1,1"))
|
||||
// expect transaction rollback, since order status is "cancelled"
|
||||
sqlmock.ExpectRollback()
|
||||
|
||||
// run the cancel order function
|
||||
someOrderId := 1
|
||||
// call a function which executes expected database operations
|
||||
err = cancelOrder(someOrderId, db)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error, but got %s instead", err)
|
||||
}
|
||||
// db.Close() ensures that all expectations have been met
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("Error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var mock *mockDriver
|
||||
|
||||
// Mock interface defines a mock which is returned
|
||||
// by any expectation and can be detailed further
|
||||
// with the methods this interface provides
|
||||
type Mock interface {
|
||||
WithArgs(...driver.Value) Mock
|
||||
WillReturnError(error) Mock
|
||||
WillReturnRows(driver.Rows) Mock
|
||||
WillReturnResult(driver.Result) Mock
|
||||
}
|
||||
|
||||
type mockDriver struct {
|
||||
conn *conn
|
||||
}
|
||||
|
||||
func (d *mockDriver) Open(dsn string) (driver.Conn, error) {
|
||||
return mock.conn, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
mock = &mockDriver{&conn{}}
|
||||
sql.Register("mock", mock)
|
||||
}
|
||||
|
||||
// New creates sqlmock database connection
|
||||
// and pings it so that all expectations could be
|
||||
// asserted on Close.
|
||||
func New() (db *sql.DB, err error) {
|
||||
db, err = sql.Open("mock", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// ensure open connection, otherwise Close does not assert expectations
|
||||
return db, db.Ping()
|
||||
}
|
||||
|
||||
// ExpectBegin expects transaction to be started
|
||||
func ExpectBegin() Mock {
|
||||
e := &expectedBegin{}
|
||||
mock.conn.expectations = append(mock.conn.expectations, e)
|
||||
mock.conn.active = e
|
||||
return mock.conn
|
||||
}
|
||||
|
||||
// ExpectCommit expects transaction to be commited
|
||||
func ExpectCommit() Mock {
|
||||
e := &expectedCommit{}
|
||||
mock.conn.expectations = append(mock.conn.expectations, e)
|
||||
mock.conn.active = e
|
||||
return mock.conn
|
||||
}
|
||||
|
||||
// ExpectRollback expects transaction to be rolled back
|
||||
func ExpectRollback() Mock {
|
||||
e := &expectedRollback{}
|
||||
mock.conn.expectations = append(mock.conn.expectations, e)
|
||||
mock.conn.active = e
|
||||
return mock.conn
|
||||
}
|
||||
|
||||
// ExpectPrepare expects Query to be prepared
|
||||
func ExpectPrepare() Mock {
|
||||
e := &expectedPrepare{}
|
||||
mock.conn.expectations = append(mock.conn.expectations, e)
|
||||
mock.conn.active = e
|
||||
return mock.conn
|
||||
}
|
||||
|
||||
// WillReturnError the expectation will return an error
|
||||
func (c *conn) WillReturnError(err error) Mock {
|
||||
c.active.setError(err)
|
||||
return c
|
||||
}
|
||||
|
||||
// ExpectExec expects database Exec to be triggered, which will match
|
||||
// the given query string as a regular expression
|
||||
func ExpectExec(sqlRegexStr string) Mock {
|
||||
e := &expectedExec{}
|
||||
e.sqlRegex = regexp.MustCompile(sqlRegexStr)
|
||||
mock.conn.expectations = append(mock.conn.expectations, e)
|
||||
mock.conn.active = e
|
||||
return mock.conn
|
||||
}
|
||||
|
||||
// ExpectQuery database Query to be triggered, which will match
|
||||
// the given query string as a regular expression
|
||||
func ExpectQuery(sqlRegexStr string) Mock {
|
||||
e := &expectedQuery{}
|
||||
e.sqlRegex = regexp.MustCompile(sqlRegexStr)
|
||||
|
||||
mock.conn.expectations = append(mock.conn.expectations, e)
|
||||
mock.conn.active = e
|
||||
return mock.conn
|
||||
}
|
||||
|
||||
// WithArgs expectation should be called with given arguments.
|
||||
// Works with Exec and Query expectations
|
||||
func (c *conn) WithArgs(args ...driver.Value) Mock {
|
||||
eq, ok := c.active.(*expectedQuery)
|
||||
if !ok {
|
||||
ee, ok := c.active.(*expectedExec)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("arguments may be expected only with query based expectations, current is %T", c.active))
|
||||
}
|
||||
ee.args = args
|
||||
} else {
|
||||
eq.args = args
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// WillReturnResult expectation will return a Result.
|
||||
// Works only with Exec expectations
|
||||
func (c *conn) WillReturnResult(result driver.Result) Mock {
|
||||
eq, ok := c.active.(*expectedExec)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("driver.result may be returned only by exec expectations, current is %T", c.active))
|
||||
}
|
||||
eq.result = result
|
||||
return c
|
||||
}
|
||||
|
||||
// WillReturnRows expectation will return Rows.
|
||||
// Works only with Query expectations
|
||||
func (c *conn) WillReturnRows(rows driver.Rows) Mock {
|
||||
eq, ok := c.active.(*expectedQuery)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("driver.rows may be returned only by query expectations, current is %T", c.active))
|
||||
}
|
||||
eq.rows = rows
|
||||
return c
|
||||
}
|
|
@ -1,532 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIssue14EscapeSQL(t *testing.T) {
|
||||
db, err := New()
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
ExpectExec("INSERT INTO mytable\\(a, b\\)").
|
||||
WithArgs("A", "B").
|
||||
WillReturnResult(NewResult(1, 1))
|
||||
|
||||
_, err = db.Exec("INSERT INTO mytable(a, b) VALUES (?, ?)", "A", "B")
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected, while inserting a row", err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
// test the case when db is not triggered and expectations
|
||||
// are not asserted on close
|
||||
func TestIssue4(t *testing.T) {
|
||||
db, err := New()
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
ExpectQuery("some sql query which will not be called").
|
||||
WillReturnRows(NewRows([]string{"id"}))
|
||||
|
||||
err = db.Close()
|
||||
if err == nil {
|
||||
t.Errorf("Was expecting an error, since expected query was not matched")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockQuery(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
rs := NewRows([]string{"id", "title"}).FromCSVString("5,hello world")
|
||||
|
||||
ExpectQuery("SELECT (.+) FROM articles WHERE id = ?").
|
||||
WithArgs(5).
|
||||
WillReturnRows(rs)
|
||||
|
||||
rows, err := db.Query("SELECT (.+) FROM articles WHERE id = ?", 5)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while retrieving mock rows", err)
|
||||
}
|
||||
defer func() {
|
||||
if er := rows.Close(); er != nil {
|
||||
t.Error("Unexpected error while trying to close rows")
|
||||
}
|
||||
}()
|
||||
if !rows.Next() {
|
||||
t.Error("it must have had one row as result, but got empty result set instead")
|
||||
}
|
||||
|
||||
var id int
|
||||
var title string
|
||||
|
||||
err = rows.Scan(&id, &title)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while trying to scan row", err)
|
||||
}
|
||||
|
||||
if id != 5 {
|
||||
t.Errorf("expected mocked id to be 5, but got %d instead", id)
|
||||
}
|
||||
|
||||
if title != "hello world" {
|
||||
t.Errorf("expected mocked title to be 'hello world', but got '%s' instead", title)
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockQueryTypes(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
columns := []string{"id", "timestamp", "sold"}
|
||||
|
||||
timestamp := time.Now()
|
||||
rs := NewRows(columns)
|
||||
rs.AddRow(5, timestamp, true)
|
||||
|
||||
ExpectQuery("SELECT (.+) FROM sales WHERE id = ?").
|
||||
WithArgs(5).
|
||||
WillReturnRows(rs)
|
||||
|
||||
rows, err := db.Query("SELECT (.+) FROM sales WHERE id = ?", 5)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while retrieving mock rows", err)
|
||||
}
|
||||
defer func() {
|
||||
if er := rows.Close(); er != nil {
|
||||
t.Error("Unexpected error while trying to close rows")
|
||||
}
|
||||
}()
|
||||
if !rows.Next() {
|
||||
t.Error("it must have had one row as result, but got empty result set instead")
|
||||
}
|
||||
|
||||
var id int
|
||||
var time time.Time
|
||||
var sold bool
|
||||
|
||||
err = rows.Scan(&id, &time, &sold)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while trying to scan row", err)
|
||||
}
|
||||
|
||||
if id != 5 {
|
||||
t.Errorf("expected mocked id to be 5, but got %d instead", id)
|
||||
}
|
||||
|
||||
if time != timestamp {
|
||||
t.Errorf("expected mocked time to be %s, but got '%s' instead", timestamp, time)
|
||||
}
|
||||
|
||||
if sold != true {
|
||||
t.Errorf("expected mocked boolean to be true, but got %v instead", sold)
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionExpectations(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
// begin and commit
|
||||
ExpectBegin()
|
||||
ExpectCommit()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when beginning a transaction", err)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when commiting a transaction", err)
|
||||
}
|
||||
|
||||
// begin and rollback
|
||||
ExpectBegin()
|
||||
ExpectRollback()
|
||||
|
||||
tx, err = db.Begin()
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when beginning a transaction", err)
|
||||
}
|
||||
|
||||
err = tx.Rollback()
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when rolling back a transaction", err)
|
||||
}
|
||||
|
||||
// begin with an error
|
||||
ExpectBegin().WillReturnError(fmt.Errorf("some err"))
|
||||
|
||||
tx, err = db.Begin()
|
||||
if err == nil {
|
||||
t.Error("an error was expected when beginning a transaction, but got none")
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareExpectations(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
// no expectations, w/o ExpectPrepare()
|
||||
stmt, err := db.Prepare("SELECT (.+) FROM articles WHERE id = ?")
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while creating a prepared statement", err)
|
||||
}
|
||||
if stmt == nil {
|
||||
t.Errorf("stmt was expected while creating a prepared statement")
|
||||
}
|
||||
|
||||
// expect something else, w/o ExpectPrepare()
|
||||
var id int
|
||||
var title string
|
||||
rs := NewRows([]string{"id", "title"}).FromCSVString("5,hello world")
|
||||
|
||||
ExpectQuery("SELECT (.+) FROM articles WHERE id = ?").
|
||||
WithArgs(5).
|
||||
WillReturnRows(rs)
|
||||
|
||||
stmt, err = db.Prepare("SELECT (.+) FROM articles WHERE id = ?")
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while creating a prepared statement", err)
|
||||
}
|
||||
if stmt == nil {
|
||||
t.Errorf("stmt was expected while creating a prepared statement")
|
||||
}
|
||||
|
||||
err = stmt.QueryRow(5).Scan(&id, &title)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while retrieving mock rows", err)
|
||||
}
|
||||
|
||||
// expect normal result
|
||||
ExpectPrepare()
|
||||
stmt, err = db.Prepare("SELECT (.+) FROM articles WHERE id = ?")
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while creating a prepared statement", err)
|
||||
}
|
||||
if stmt == nil {
|
||||
t.Errorf("stmt was expected while creating a prepared statement")
|
||||
}
|
||||
|
||||
// expect error result
|
||||
ExpectPrepare().WillReturnError(fmt.Errorf("Some DB error occurred"))
|
||||
stmt, err = db.Prepare("SELECT (.+) FROM articles WHERE id = ?")
|
||||
if err == nil {
|
||||
t.Error("error was expected while creating a prepared statement")
|
||||
}
|
||||
if stmt != nil {
|
||||
t.Errorf("stmt was not expected while creating a prepared statement returning error")
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreparedQueryExecutions(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
rs1 := NewRows([]string{"id", "title"}).FromCSVString("5,hello world")
|
||||
ExpectQuery("SELECT (.+) FROM articles WHERE id = ?").
|
||||
WithArgs(5).
|
||||
WillReturnRows(rs1)
|
||||
|
||||
rs2 := NewRows([]string{"id", "title"}).FromCSVString("2,whoop")
|
||||
ExpectQuery("SELECT (.+) FROM articles WHERE id = ?").
|
||||
WithArgs(2).
|
||||
WillReturnRows(rs2)
|
||||
|
||||
stmt, err := db.Prepare("SELECT (.+) FROM articles WHERE id = ?")
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while creating a prepared statement", err)
|
||||
}
|
||||
|
||||
var id int
|
||||
var title string
|
||||
|
||||
err = stmt.QueryRow(5).Scan(&id, &title)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected querying row from statement and scanning", err)
|
||||
}
|
||||
|
||||
if id != 5 {
|
||||
t.Errorf("expected mocked id to be 5, but got %d instead", id)
|
||||
}
|
||||
|
||||
if title != "hello world" {
|
||||
t.Errorf("expected mocked title to be 'hello world', but got '%s' instead", title)
|
||||
}
|
||||
|
||||
err = stmt.QueryRow(2).Scan(&id, &title)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected querying row from statement and scanning", err)
|
||||
}
|
||||
|
||||
if id != 2 {
|
||||
t.Errorf("expected mocked id to be 2, but got %d instead", id)
|
||||
}
|
||||
|
||||
if title != "whoop" {
|
||||
t.Errorf("expected mocked title to be 'whoop', but got '%s' instead", title)
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnexpectedOperations(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
stmt, err := db.Prepare("SELECT (.+) FROM articles WHERE id = ?")
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while creating a prepared statement", err)
|
||||
}
|
||||
|
||||
var id int
|
||||
var title string
|
||||
|
||||
err = stmt.QueryRow(5).Scan(&id, &title)
|
||||
if err == nil {
|
||||
t.Error("error was expected querying row, since there was no such expectation")
|
||||
}
|
||||
|
||||
ExpectRollback()
|
||||
|
||||
err = db.Close()
|
||||
if err == nil {
|
||||
t.Error("error was expected while closing the database, expectation was not fulfilled", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrongExpectations(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
ExpectBegin()
|
||||
|
||||
rs1 := NewRows([]string{"id", "title"}).FromCSVString("5,hello world")
|
||||
ExpectQuery("SELECT (.+) FROM articles WHERE id = ?").
|
||||
WithArgs(5).
|
||||
WillReturnRows(rs1)
|
||||
|
||||
ExpectCommit().WillReturnError(fmt.Errorf("deadlock occured"))
|
||||
ExpectRollback() // won't be triggered
|
||||
|
||||
stmt, err := db.Prepare("SELECT (.+) FROM articles WHERE id = ? FOR UPDATE")
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while creating a prepared statement", err)
|
||||
}
|
||||
|
||||
var id int
|
||||
var title string
|
||||
|
||||
err = stmt.QueryRow(5).Scan(&id, &title)
|
||||
if err == nil {
|
||||
t.Error("error was expected while querying row, since there begin transaction expectation is not fulfilled")
|
||||
}
|
||||
|
||||
// lets go around and start transaction
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when beginning a transaction", err)
|
||||
}
|
||||
|
||||
err = stmt.QueryRow(5).Scan(&id, &title)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while querying row, since transaction was started", err)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err == nil {
|
||||
t.Error("a deadlock error was expected when commiting a transaction", err)
|
||||
}
|
||||
|
||||
err = db.Close()
|
||||
if err == nil {
|
||||
t.Error("error was expected while closing the database, expectation was not fulfilled", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecExpectations(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
result := NewResult(1, 1)
|
||||
ExpectExec("^INSERT INTO articles").
|
||||
WithArgs("hello").
|
||||
WillReturnResult(result)
|
||||
|
||||
res, err := db.Exec("INSERT INTO articles (title) VALUES (?)", "hello")
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected, while inserting a row", err)
|
||||
}
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected, while getting a last insert id", err)
|
||||
}
|
||||
|
||||
affected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected, while getting affected rows", err)
|
||||
}
|
||||
|
||||
if id != 1 {
|
||||
t.Errorf("expected last insert id to be 1, but got %d instead", id)
|
||||
}
|
||||
|
||||
if affected != 1 {
|
||||
t.Errorf("expected affected rows to be 1, but got %d instead", affected)
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRowBuilderAndNilTypes(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
rs := NewRows([]string{"id", "active", "created", "status"}).
|
||||
AddRow(1, true, time.Now(), 5).
|
||||
AddRow(2, false, nil, nil)
|
||||
|
||||
ExpectQuery("SELECT (.+) FROM sales").WillReturnRows(rs)
|
||||
|
||||
rows, err := db.Query("SELECT * FROM sales")
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while retrieving mock rows", err)
|
||||
}
|
||||
defer func() {
|
||||
if er := rows.Close(); er != nil {
|
||||
t.Error("Unexpected error while trying to close rows")
|
||||
}
|
||||
}()
|
||||
|
||||
// NullTime and NullInt are used from stubs_test.go
|
||||
var (
|
||||
id int
|
||||
active bool
|
||||
created NullTime
|
||||
status NullInt
|
||||
)
|
||||
|
||||
if !rows.Next() {
|
||||
t.Error("it must have had row in rows, but got empty result set instead")
|
||||
}
|
||||
|
||||
err = rows.Scan(&id, &active, &created, &status)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while trying to scan row", err)
|
||||
}
|
||||
|
||||
if id != 1 {
|
||||
t.Errorf("expected mocked id to be 1, but got %d instead", id)
|
||||
}
|
||||
|
||||
if !active {
|
||||
t.Errorf("expected 'active' to be 'true', but got '%v' instead", active)
|
||||
}
|
||||
|
||||
if !created.Valid {
|
||||
t.Errorf("expected 'created' to be valid, but it %+v is not", created)
|
||||
}
|
||||
|
||||
if !status.Valid {
|
||||
t.Errorf("expected 'status' to be valid, but it %+v is not", status)
|
||||
}
|
||||
|
||||
if status.Integer != 5 {
|
||||
t.Errorf("expected 'status' to be '5', but got '%d'", status.Integer)
|
||||
}
|
||||
|
||||
// test second row
|
||||
if !rows.Next() {
|
||||
t.Error("it must have had row in rows, but got empty result set instead")
|
||||
}
|
||||
|
||||
err = rows.Scan(&id, &active, &created, &status)
|
||||
if err != nil {
|
||||
t.Errorf("error '%s' was not expected while trying to scan row", err)
|
||||
}
|
||||
|
||||
if id != 2 {
|
||||
t.Errorf("expected mocked id to be 2, but got %d instead", id)
|
||||
}
|
||||
|
||||
if active {
|
||||
t.Errorf("expected 'active' to be 'false', but got '%v' instead", active)
|
||||
}
|
||||
|
||||
if created.Valid {
|
||||
t.Errorf("expected 'created' to be invalid, but it %+v is not", created)
|
||||
}
|
||||
|
||||
if status.Valid {
|
||||
t.Errorf("expected 'status' to be invalid, but it %+v is not", status)
|
||||
}
|
||||
|
||||
if err = db.Close(); err != nil {
|
||||
t.Errorf("error '%s' was not expected while closing the database", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgumentReflectValueTypeError(t *testing.T) {
|
||||
db, err := sql.Open("mock", "")
|
||||
if err != nil {
|
||||
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
|
||||
}
|
||||
|
||||
rs := NewRows([]string{"id"}).AddRow(1)
|
||||
|
||||
ExpectQuery("SELECT (.+) FROM sales").WithArgs(5.5).WillReturnRows(rs)
|
||||
|
||||
_, err = db.Query("SELECT * FROM sales WHERE x = ?", 5)
|
||||
if err == nil {
|
||||
t.Error("Expected error, but got none")
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
)
|
||||
|
||||
type statement struct {
|
||||
conn *conn
|
||||
query string
|
||||
}
|
||||
|
||||
func (stmt *statement) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (stmt *statement) NumInput() int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (stmt *statement) Exec(args []driver.Value) (driver.Result, error) {
|
||||
return stmt.conn.Exec(stmt.query, args)
|
||||
}
|
||||
|
||||
func (stmt *statement) Query(args []driver.Value) (driver.Rows, error) {
|
||||
return stmt.conn.Query(stmt.query, args)
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NullTime struct {
|
||||
Time time.Time
|
||||
Valid bool // Valid is true if Time is not NULL
|
||||
}
|
||||
|
||||
type NullInt struct {
|
||||
Integer int
|
||||
Valid bool
|
||||
}
|
||||
|
||||
// Satisfy sql.Scanner interface
|
||||
func (ni *NullInt) Scan(value interface{}) (err error) {
|
||||
if value == nil {
|
||||
ni.Integer, ni.Valid = 0, false
|
||||
return
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case int, int8, int16, int32, int64:
|
||||
ni.Integer, ni.Valid = v.(int), true
|
||||
return
|
||||
case []byte:
|
||||
ni.Integer, err = strconv.Atoi(string(v))
|
||||
ni.Valid = (err == nil)
|
||||
return
|
||||
case string:
|
||||
ni.Integer, err = strconv.Atoi(v)
|
||||
ni.Valid = (err == nil)
|
||||
return
|
||||
}
|
||||
|
||||
ni.Valid = false
|
||||
return fmt.Errorf("Can't convert %T to integer", value)
|
||||
}
|
||||
|
||||
// Satisfy sql.Valuer interface.
|
||||
func (ni NullInt) Value() (driver.Value, error) {
|
||||
if !ni.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return ni.Integer, nil
|
||||
}
|
||||
|
||||
// Satisfy sql.Scanner interface
|
||||
func (nt *NullTime) Scan(value interface{}) (err error) {
|
||||
if value == nil {
|
||||
nt.Time, nt.Valid = time.Time{}, false
|
||||
return
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
case time.Time:
|
||||
nt.Time, nt.Valid = v, true
|
||||
return
|
||||
}
|
||||
|
||||
nt.Valid = false
|
||||
return fmt.Errorf("Can't convert %T to time.Time", value)
|
||||
}
|
||||
|
||||
// Satisfy sql.Valuer interface.
|
||||
func (nt NullTime) Value() (driver.Value, error) {
|
||||
if !nt.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return nt.Time, nil
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type transaction struct {
|
||||
conn *conn
|
||||
}
|
||||
|
||||
func (tx *transaction) Commit() error {
|
||||
e := tx.conn.next()
|
||||
if e == nil {
|
||||
return fmt.Errorf("all expectations were already fulfilled, call to commit transaction was not expected")
|
||||
}
|
||||
|
||||
etc, ok := e.(*expectedCommit)
|
||||
if !ok {
|
||||
return fmt.Errorf("call to commit transaction, was not expected, next expectation was %v", e)
|
||||
}
|
||||
etc.triggered = true
|
||||
return etc.err
|
||||
}
|
||||
|
||||
func (tx *transaction) Rollback() error {
|
||||
e := tx.conn.next()
|
||||
if e == nil {
|
||||
return fmt.Errorf("all expectations were already fulfilled, call to rollback transaction was not expected")
|
||||
}
|
||||
|
||||
etr, ok := e.(*expectedRollback)
|
||||
if !ok {
|
||||
return fmt.Errorf("call to rollback transaction, was not expected, next expectation was %v", e)
|
||||
}
|
||||
etr.triggered = true
|
||||
return etr.err
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var re *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
re = regexp.MustCompile("\\s+")
|
||||
}
|
||||
|
||||
// strip out new lines and trim spaces
|
||||
func stripQuery(q string) (s string) {
|
||||
return strings.TrimSpace(re.ReplaceAllString(q, " "))
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package sqlmock
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQueryStringStripping(t *testing.T) {
|
||||
assert := func(actual, expected string) {
|
||||
if res := stripQuery(actual); res != expected {
|
||||
t.Errorf("Expected '%s' to be '%s', but got '%s'", actual, expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
assert(" SELECT 1", "SELECT 1")
|
||||
assert("SELECT 1 FROM d", "SELECT 1 FROM d")
|
||||
assert(`
|
||||
SELECT c
|
||||
FROM D
|
||||
`, "SELECT c FROM D")
|
||||
assert("UPDATE (.+) SET ", "UPDATE (.+) SET")
|
||||
}
|
|
@ -5,11 +5,6 @@
|
|||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "code.google.com/p/gosqlite/sqlite3",
|
||||
"Comment": "null-16",
|
||||
"Rev": "74691fb6f83716190870cde1b658538dd4b18eb0"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Sirupsen/logrus",
|
||||
"Comment": "v0.7.3",
|
||||
|
@ -27,6 +22,11 @@
|
|||
"ImportPath": "github.com/jfrazelle/go/canonical/json",
|
||||
"Rev": "6e461eb70cb4187b41a84e9a567d7137bdbe0f16"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-sqlite3",
|
||||
"Comment": "v1.0.0",
|
||||
"Rev": "b4142c444a8941d0d92b0b7103a24df9cd815e42"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/stretchr/testify/assert",
|
||||
"Comment": "v1.0-40-g7c2b1e5",
|
||||
|
|
|
@ -1,230 +0,0 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
tuf "github.com/endophage/gotuf"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/endophage/gotuf/keys"
|
||||
"github.com/endophage/gotuf/signed"
|
||||
"github.com/endophage/gotuf/store"
|
||||
)
|
||||
|
||||
func TestRotation(t *testing.T) {
|
||||
kdb := keys.NewDB()
|
||||
signer := signed.NewEd25519()
|
||||
repo := tuf.NewTufRepo(kdb, signer)
|
||||
remote := store.NewMemoryStore(nil, nil)
|
||||
cache := store.NewMemoryStore(nil, nil)
|
||||
|
||||
// Generate initial root key and role and add to key DB
|
||||
rootKey, err := signer.Create("root", data.ED25519Key)
|
||||
assert.NoError(t, err, "Error creating root key")
|
||||
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
|
||||
assert.NoError(t, err, "Error creating root role")
|
||||
|
||||
kdb.AddKey(rootKey)
|
||||
err = kdb.AddRole(rootRole)
|
||||
assert.NoError(t, err, "Error adding root role to db")
|
||||
|
||||
// Generate new key and role. These will appear in the root.json
|
||||
// but will not be added to the keyDB.
|
||||
replacementKey, err := signer.Create("root", data.ED25519Key)
|
||||
assert.NoError(t, err, "Error creating replacement root key")
|
||||
replacementRole, err := data.NewRole("root", 1, []string{replacementKey.ID()}, nil, nil)
|
||||
assert.NoError(t, err, "Error creating replacement root role")
|
||||
|
||||
// Generate a new root with the replacement key and role
|
||||
testRoot, err := data.NewRoot(
|
||||
map[string]data.PublicKey{replacementKey.ID(): replacementKey},
|
||||
map[string]*data.RootRole{"root": &replacementRole.RootRole},
|
||||
false,
|
||||
)
|
||||
assert.NoError(t, err, "Failed to create new root")
|
||||
|
||||
// Sign testRoot with both old and new keys
|
||||
signedRoot, err := testRoot.ToSigned()
|
||||
err = signed.Sign(signer, signedRoot, rootKey, replacementKey)
|
||||
assert.NoError(t, err, "Failed to sign root")
|
||||
var origKeySig bool
|
||||
var replKeySig bool
|
||||
for _, sig := range signedRoot.Signatures {
|
||||
if sig.KeyID == rootKey.ID() {
|
||||
origKeySig = true
|
||||
} else if sig.KeyID == replacementKey.ID() {
|
||||
replKeySig = true
|
||||
}
|
||||
}
|
||||
assert.True(t, origKeySig, "Original root key signature not present")
|
||||
assert.True(t, replKeySig, "Replacement root key signature not present")
|
||||
|
||||
client := NewClient(repo, remote, kdb, cache)
|
||||
|
||||
err = client.verifyRoot("root", signedRoot, 0)
|
||||
assert.NoError(t, err, "Failed to verify key rotated root")
|
||||
}
|
||||
|
||||
func TestRotationNewSigMissing(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
kdb := keys.NewDB()
|
||||
signer := signed.NewEd25519()
|
||||
repo := tuf.NewTufRepo(kdb, signer)
|
||||
remote := store.NewMemoryStore(nil, nil)
|
||||
cache := store.NewMemoryStore(nil, nil)
|
||||
|
||||
// Generate initial root key and role and add to key DB
|
||||
rootKey, err := signer.Create("root", data.ED25519Key)
|
||||
assert.NoError(t, err, "Error creating root key")
|
||||
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
|
||||
assert.NoError(t, err, "Error creating root role")
|
||||
|
||||
kdb.AddKey(rootKey)
|
||||
err = kdb.AddRole(rootRole)
|
||||
assert.NoError(t, err, "Error adding root role to db")
|
||||
|
||||
// Generate new key and role. These will appear in the root.json
|
||||
// but will not be added to the keyDB.
|
||||
replacementKey, err := signer.Create("root", data.ED25519Key)
|
||||
assert.NoError(t, err, "Error creating replacement root key")
|
||||
replacementRole, err := data.NewRole("root", 1, []string{replacementKey.ID()}, nil, nil)
|
||||
assert.NoError(t, err, "Error creating replacement root role")
|
||||
|
||||
assert.NotEqual(t, rootKey.ID(), replacementKey.ID(), "Key IDs are the same")
|
||||
|
||||
// Generate a new root with the replacement key and role
|
||||
testRoot, err := data.NewRoot(
|
||||
map[string]data.PublicKey{replacementKey.ID(): replacementKey},
|
||||
map[string]*data.RootRole{"root": &replacementRole.RootRole},
|
||||
false,
|
||||
)
|
||||
assert.NoError(t, err, "Failed to create new root")
|
||||
|
||||
_, ok := testRoot.Signed.Keys[rootKey.ID()]
|
||||
assert.False(t, ok, "Old root key appeared in test root")
|
||||
|
||||
// Sign testRoot with both old and new keys
|
||||
signedRoot, err := testRoot.ToSigned()
|
||||
err = signed.Sign(signer, signedRoot, rootKey)
|
||||
assert.NoError(t, err, "Failed to sign root")
|
||||
var origKeySig bool
|
||||
var replKeySig bool
|
||||
for _, sig := range signedRoot.Signatures {
|
||||
if sig.KeyID == rootKey.ID() {
|
||||
origKeySig = true
|
||||
} else if sig.KeyID == replacementKey.ID() {
|
||||
replKeySig = true
|
||||
}
|
||||
}
|
||||
assert.True(t, origKeySig, "Original root key signature not present")
|
||||
assert.False(t, replKeySig, "Replacement root key signature was present and shouldn't be")
|
||||
|
||||
client := NewClient(repo, remote, kdb, cache)
|
||||
|
||||
err = client.verifyRoot("root", signedRoot, 0)
|
||||
assert.Error(t, err, "Should have errored on verify as replacement signature was missing.")
|
||||
|
||||
}
|
||||
|
||||
func TestRotationOldSigMissing(t *testing.T) {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
kdb := keys.NewDB()
|
||||
signer := signed.NewEd25519()
|
||||
repo := tuf.NewTufRepo(kdb, signer)
|
||||
remote := store.NewMemoryStore(nil, nil)
|
||||
cache := store.NewMemoryStore(nil, nil)
|
||||
|
||||
// Generate initial root key and role and add to key DB
|
||||
rootKey, err := signer.Create("root", data.ED25519Key)
|
||||
assert.NoError(t, err, "Error creating root key")
|
||||
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
|
||||
assert.NoError(t, err, "Error creating root role")
|
||||
|
||||
kdb.AddKey(rootKey)
|
||||
err = kdb.AddRole(rootRole)
|
||||
assert.NoError(t, err, "Error adding root role to db")
|
||||
|
||||
// Generate new key and role. These will appear in the root.json
|
||||
// but will not be added to the keyDB.
|
||||
replacementKey, err := signer.Create("root", data.ED25519Key)
|
||||
assert.NoError(t, err, "Error creating replacement root key")
|
||||
replacementRole, err := data.NewRole("root", 1, []string{replacementKey.ID()}, nil, nil)
|
||||
assert.NoError(t, err, "Error creating replacement root role")
|
||||
|
||||
assert.NotEqual(t, rootKey.ID(), replacementKey.ID(), "Key IDs are the same")
|
||||
|
||||
// Generate a new root with the replacement key and role
|
||||
testRoot, err := data.NewRoot(
|
||||
map[string]data.PublicKey{replacementKey.ID(): replacementKey},
|
||||
map[string]*data.RootRole{"root": &replacementRole.RootRole},
|
||||
false,
|
||||
)
|
||||
assert.NoError(t, err, "Failed to create new root")
|
||||
|
||||
_, ok := testRoot.Signed.Keys[rootKey.ID()]
|
||||
assert.False(t, ok, "Old root key appeared in test root")
|
||||
|
||||
// Sign testRoot with both old and new keys
|
||||
signedRoot, err := testRoot.ToSigned()
|
||||
err = signed.Sign(signer, signedRoot, replacementKey)
|
||||
assert.NoError(t, err, "Failed to sign root")
|
||||
var origKeySig bool
|
||||
var replKeySig bool
|
||||
for _, sig := range signedRoot.Signatures {
|
||||
if sig.KeyID == rootKey.ID() {
|
||||
origKeySig = true
|
||||
} else if sig.KeyID == replacementKey.ID() {
|
||||
replKeySig = true
|
||||
}
|
||||
}
|
||||
assert.False(t, origKeySig, "Original root key signature was present and shouldn't be")
|
||||
assert.True(t, replKeySig, "Replacement root key signature was not present")
|
||||
|
||||
client := NewClient(repo, remote, kdb, cache)
|
||||
|
||||
err = client.verifyRoot("root", signedRoot, 0)
|
||||
assert.Error(t, err, "Should have errored on verify as replacement signature was missing.")
|
||||
|
||||
}
|
||||
|
||||
func TestCheckRootExpired(t *testing.T) {
|
||||
repo := tuf.NewTufRepo(nil, nil)
|
||||
storage := store.NewMemoryStore(nil, nil)
|
||||
client := NewClient(repo, storage, nil, storage)
|
||||
|
||||
root := &data.SignedRoot{}
|
||||
root.Signed.Expires = time.Now().AddDate(-1, 0, 0)
|
||||
|
||||
signedRoot, err := root.ToSigned()
|
||||
assert.NoError(t, err)
|
||||
rootJSON, err := json.Marshal(signedRoot)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rootHash := sha256.Sum256(rootJSON)
|
||||
|
||||
testSnap := &data.SignedSnapshot{
|
||||
Signed: data.Snapshot{
|
||||
Meta: map[string]data.FileMeta{
|
||||
"root": {
|
||||
Length: int64(len(rootJSON)),
|
||||
Hashes: map[string][]byte{
|
||||
"sha256": rootHash[:],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
repo.SetRoot(root)
|
||||
repo.SetSnapshot(testSnap)
|
||||
|
||||
storage.SetMeta("root", rootJSON)
|
||||
|
||||
err = client.checkRoot()
|
||||
assert.Error(t, err)
|
||||
assert.IsType(t, tuf.ErrLocalRootExpired{}, err)
|
||||
}
|
|
@ -24,7 +24,7 @@ var ValidRoles = map[string]string{
|
|||
|
||||
func SetValidRoles(rs map[string]string) {
|
||||
// iterate ValidRoles
|
||||
for k, _ := range ValidRoles {
|
||||
for k := range ValidRoles {
|
||||
if v, ok := rs[k]; ok {
|
||||
ValidRoles[k] = v
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCanonicalRole(t *testing.T) {
|
||||
|
||||
testRoles := map[string]string{
|
||||
CanonicalRootRole: "testRoot",
|
||||
CanonicalTargetsRole: "testTargets",
|
||||
CanonicalSnapshotRole: "testSnapshot",
|
||||
CanonicalTimestampRole: "testTimestamp",
|
||||
"garbageRole": "testGarbageRole",
|
||||
}
|
||||
|
||||
SetValidRoles(testRoles)
|
||||
|
||||
// make sure roles were set correctly
|
||||
assert.Equal(t, "testRoot", ValidRoles[CanonicalRootRole])
|
||||
assert.Equal(t, "testTargets", ValidRoles[CanonicalTargetsRole])
|
||||
assert.Equal(t, "testSnapshot", ValidRoles[CanonicalSnapshotRole])
|
||||
assert.Equal(t, "testTimestamp", ValidRoles[CanonicalTimestampRole])
|
||||
// check SetValidRoles doesn't allow non-valid roles in
|
||||
assert.Equal(t, "", ValidRoles["garbageRole"])
|
||||
|
||||
// check when looking up CanonicalRole from configured role
|
||||
assert.Equal(t, CanonicalRootRole, CanonicalRole("testRoot"))
|
||||
assert.Equal(t, CanonicalTargetsRole, CanonicalRole("testTargets"))
|
||||
assert.Equal(t, CanonicalSnapshotRole, CanonicalRole("testSnapshot"))
|
||||
assert.Equal(t, CanonicalTimestampRole, CanonicalRole("testTimestamp"))
|
||||
assert.Equal(t, "", CanonicalRole("testGarbageRole"))
|
||||
|
||||
// check when looking up CanonicalRole with canonical role
|
||||
assert.Equal(t, CanonicalRootRole, CanonicalRole(CanonicalRootRole))
|
||||
assert.Equal(t, CanonicalTargetsRole, CanonicalRole(CanonicalTargetsRole))
|
||||
assert.Equal(t, CanonicalSnapshotRole, CanonicalRole(CanonicalSnapshotRole))
|
||||
assert.Equal(t, CanonicalTimestampRole, CanonicalRole(CanonicalTimestampRole))
|
||||
assert.Equal(t, "", CanonicalRole("garbageRole"))
|
||||
|
||||
assert.Equal(t, "", CanonicalRole("not found"))
|
||||
|
||||
// reset ValidRoles so other tests aren't messed up
|
||||
ValidRoles = map[string]string{
|
||||
CanonicalRootRole: CanonicalRootRole,
|
||||
CanonicalTargetsRole: CanonicalTargetsRole,
|
||||
CanonicalSnapshotRole: CanonicalSnapshotRole,
|
||||
CanonicalTimestampRole: CanonicalTimestampRole,
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/jfrazelle/go/canonical/json"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGenerateFileMetaDefault(t *testing.T) {
|
||||
// default is sha512
|
||||
r := bytes.NewReader([]byte("foo"))
|
||||
meta, err := NewFileMeta(r, "sha512")
|
||||
assert.NoError(t, err, "Unexpected error.")
|
||||
assert.Equal(t, meta.Length, int64(3), "Meta did not have expected Length field value")
|
||||
hashes := meta.Hashes
|
||||
assert.Len(t, hashes, 1, "Only expected one hash to be present")
|
||||
hash, ok := hashes["sha512"]
|
||||
if !ok {
|
||||
t.Fatal("missing sha512 hash")
|
||||
}
|
||||
assert.Equal(t, "f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7", hex.EncodeToString(hash), "Hashes not equal")
|
||||
}
|
||||
|
||||
func TestGenerateFileMetaExplicit(t *testing.T) {
|
||||
r := bytes.NewReader([]byte("foo"))
|
||||
meta, err := NewFileMeta(r, "sha256", "sha512")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, meta.Length, int64(3))
|
||||
hashes := meta.Hashes
|
||||
assert.Len(t, hashes, 2)
|
||||
for name, val := range map[string]string{
|
||||
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
|
||||
"sha512": "f7fbba6e0636f890e56fbbf3283e524c6fa3204ae298382d624741d0dc6638326e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7",
|
||||
} {
|
||||
hash, ok := hashes[name]
|
||||
if !ok {
|
||||
t.Fatalf("missing %s hash", name)
|
||||
}
|
||||
assert.Equal(t, hex.EncodeToString(hash), val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureUnmarshalJSON(t *testing.T) {
|
||||
signatureJSON := `{"keyid":"97e8e1b51b6e7cf8720a56b5334bd8692ac5b28233c590b89fab0b0cd93eeedc","method":"RSA","sig":"2230cba525e4f5f8fc744f234221ca9a92924da4cc5faf69a778848882fcf7a20dbb57296add87f600891f2569a9c36706314c240f9361c60fd36f5a915a0e9712fc437b761e8f480868d7a4444724daa0d29a2669c0edbd4046046649a506b3d711d0aa5e70cb9d09dec7381e7de27a3168e77731e08f6ed56fcce2478855e837816fb69aff53412477748cd198dce783850080d37aeb929ad0f81460ebd31e61b772b6c7aa56977c787d4281fa45dbdefbb38d449eb5bccb2702964a52c78811545939712c8280dee0b23b2fa9fbbdd6a0c42476689ace655eba0745b4a21ba108bcd03ad00fdefff416dc74e08486a0538f8fd24989e1b9fc89e675141b7c"}`
|
||||
|
||||
var sig Signature
|
||||
err := json.Unmarshal([]byte(signatureJSON), &sig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the method string is lowercased
|
||||
assert.Equal(t, sig.Method.String(), "rsa")
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package encrypted
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var plaintext = []byte("reallyimportant")
|
||||
|
||||
func TestRoundtrip(t *testing.T) {
|
||||
passphrase := []byte("supersecret")
|
||||
|
||||
enc, err := Encrypt(plaintext, passphrase)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// successful decrypt
|
||||
dec, err := Decrypt(enc, passphrase)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dec, plaintext)
|
||||
|
||||
// wrong passphrase
|
||||
passphrase[0] = 0
|
||||
dec, err = Decrypt(enc, passphrase)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, dec)
|
||||
}
|
||||
|
||||
func TestTamperedRoundtrip(t *testing.T) {
|
||||
passphrase := []byte("supersecret")
|
||||
|
||||
enc, err := Encrypt(plaintext, passphrase)
|
||||
assert.NoError(t, err)
|
||||
|
||||
data := &data{}
|
||||
err = json.Unmarshal(enc, data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
data.Ciphertext[0] = 0
|
||||
data.Ciphertext[1] = 0
|
||||
|
||||
enc, _ = json.Marshal(data)
|
||||
|
||||
dec, err := Decrypt(enc, passphrase)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, dec)
|
||||
}
|
||||
|
||||
func TestDecrypt(t *testing.T) {
|
||||
enc := []byte(`{"kdf":{"name":"scrypt","params":{"N":32768,"r":8,"p":1},"salt":"N9a7x5JFGbrtB2uBR81jPwp0eiLR4A7FV3mjVAQrg1g="},"cipher":{"name":"nacl/secretbox","nonce":"2h8HxMmgRfuYdpswZBQaU3xJ1nkA/5Ik"},"ciphertext":"SEW6sUh0jf2wfdjJGPNS9+bkk2uB+Cxamf32zR8XkQ=="}`)
|
||||
passphrase := []byte("supersecret")
|
||||
|
||||
dec, err := Decrypt(enc, passphrase)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dec, plaintext)
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
package signed
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
)
|
||||
|
||||
const (
|
||||
testKeyPEM1 = "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnKuXZeefa2LmgxaL5NsM\nzKOHNe+x/nL6ik+lDBCTV6OdcwAhHQS+PONGhrChIUVR6Vth3hUCrreLzPO73Oo5\nVSCuRJ53UronENl6lsa5mFKP8StYLvIDITNvkoT3j52BJIjyNUK9UKY9As2TNqDf\nBEPIRp28ev/NViwGOEkBu2UAbwCIdnDXm8JQErCZA0Ydm7PKGgjLbFsFGrVzqXHK\n6pdzJXlhr9yap3UpgQ/iO9JtoEYB2EXsnSrPc9JRjR30bNHHtnVql3fvinXrAEwq\n3xmN4p+R4VGzfdQN+8Kl/IPjqWB535twhFYEG/B7Ze8IwbygBjK3co/KnOPqMUrM\nBI8ztvPiogz+MvXb8WvarZ6TMTh8ifZI96r7zzqyzjR1hJulEy3IsMGvz8XS2J0X\n7sXoaqszEtXdq5ef5zKVxkiyIQZcbPgmpHLq4MgfdryuVVc/RPASoRIXG4lKaTJj\n1ANMFPxDQpHudCLxwCzjCb+sVa20HBRPTnzo8LSZkI6jAgMBAAE=\n-----END PUBLIC KEY-----"
|
||||
testKeyID1 = "51324b59d4888faa91219ebbe5a3876bb4efb21f0602ddf363cd4c3996ded3d4"
|
||||
|
||||
testKeyPEM2 = "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArvqUPYb6JJROPJQglPTj\n5uDrsxQKl34Mo+3pSlBVuD6puE4lDnG649a2YksJy+C8ZIPJgokn5w+C3alh+dMe\nzbdWHHxrY1h9CLpYz5cbMlE16303ubkt1rvwDqEezG0HDBzPaKj4oP9YJ9x7wbsq\ndvFcy+Qc3wWd7UWcieo6E0ihbJkYcY8chRXVLg1rL7EfZ+e3bq5+ojA2ECM5JqzZ\nzgDpqCv5hTCYYZp72MZcG7dfSPAHrcSGIrwg7whzz2UsEtCOpsJTuCl96FPN7kAu\n4w/WyM3+SPzzr4/RQXuY1SrLCFD8ebM2zHt/3ATLhPnGmyG5I0RGYoegFaZ2AViw\nlqZDOYnBtgDvKP0zakMtFMbkh2XuNBUBO7Sjs0YcZMjLkh9gYUHL1yWS3Aqus1Lw\nlI0gHS22oyGObVBWkZEgk/Foy08sECLGao+5VvhmGpfVuiz9OKFUmtPVjWzRE4ng\niekEu4drSxpH41inLGSvdByDWLpcTvWQI9nkgclh3AT/AgMBAAE=\n-----END PUBLIC KEY-----"
|
||||
testKeyID2 = "26f2f5c0fbfa98823bf1ad39d5f3b32575895793baf80f1df675597d5b95dba8"
|
||||
)
|
||||
|
||||
type FailingCryptoService struct {
|
||||
testKey data.PublicKey
|
||||
}
|
||||
|
||||
func (mts *FailingCryptoService) Sign(keyIDs []string, _ []byte) ([]data.Signature, error) {
|
||||
sigs := make([]data.Signature, 0, len(keyIDs))
|
||||
return sigs, nil
|
||||
}
|
||||
|
||||
func (mts *FailingCryptoService) Create(_ string, _ data.KeyAlgorithm) (data.PublicKey, error) {
|
||||
return mts.testKey, nil
|
||||
}
|
||||
|
||||
func (mts *FailingCryptoService) GetKey(keyID string) data.PublicKey {
|
||||
if keyID == "testID" {
|
||||
return mts.testKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mts *FailingCryptoService) RemoveKey(keyID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
type MockCryptoService struct {
|
||||
testKey data.PublicKey
|
||||
}
|
||||
|
||||
func (mts *MockCryptoService) Sign(keyIDs []string, _ []byte) ([]data.Signature, error) {
|
||||
sigs := make([]data.Signature, 0, len(keyIDs))
|
||||
for _, keyID := range keyIDs {
|
||||
sigs = append(sigs, data.Signature{KeyID: keyID})
|
||||
}
|
||||
return sigs, nil
|
||||
}
|
||||
|
||||
func (mts *MockCryptoService) Create(_ string, _ data.KeyAlgorithm) (data.PublicKey, error) {
|
||||
return mts.testKey, nil
|
||||
}
|
||||
|
||||
func (mts *MockCryptoService) GetKey(keyID string) data.PublicKey {
|
||||
if keyID == "testID" {
|
||||
return mts.testKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mts *MockCryptoService) RemoveKey(keyID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ CryptoService = &MockCryptoService{}
|
||||
|
||||
// Test signing and ensure the expected signature is added
|
||||
func TestBasicSign(t *testing.T) {
|
||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||
k := data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||
mockCryptoService := &MockCryptoService{testKey: k}
|
||||
key, err := mockCryptoService.Create("root", data.ED25519Key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testData := data.Signed{}
|
||||
|
||||
Sign(mockCryptoService, &testData, key)
|
||||
|
||||
if len(testData.Signatures) != 1 {
|
||||
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
|
||||
}
|
||||
|
||||
if testData.Signatures[0].KeyID != testKeyID1 {
|
||||
t.Fatalf("Wrong signature ID returned: %s", testData.Signatures[0].KeyID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Test signing with the same key multiple times only registers a single signature
|
||||
// for the key (N.B. MockCryptoService.Sign will still be called again, but Signer.Sign
|
||||
// should be cleaning previous signatures by the KeyID when asked to sign again)
|
||||
func TestReSign(t *testing.T) {
|
||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||
k := data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||
mockCryptoService := &MockCryptoService{testKey: k}
|
||||
testData := data.Signed{}
|
||||
|
||||
Sign(mockCryptoService, &testData, k)
|
||||
Sign(mockCryptoService, &testData, k)
|
||||
|
||||
if len(testData.Signatures) != 1 {
|
||||
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
|
||||
}
|
||||
|
||||
if testData.Signatures[0].KeyID != testKeyID1 {
|
||||
t.Fatalf("Wrong signature ID returned: %s", testData.Signatures[0].KeyID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMultiSign(t *testing.T) {
|
||||
mockCryptoService := &MockCryptoService{}
|
||||
testData := data.Signed{}
|
||||
|
||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||
key := data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||
Sign(mockCryptoService, &testData, key)
|
||||
|
||||
testKey, _ = pem.Decode([]byte(testKeyPEM2))
|
||||
key = data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||
Sign(mockCryptoService, &testData, key)
|
||||
|
||||
if len(testData.Signatures) != 2 {
|
||||
t.Fatalf("Incorrect number of signatures: %d", len(testData.Signatures))
|
||||
}
|
||||
|
||||
keyIDs := map[string]struct{}{testKeyID1: struct{}{}, testKeyID2: struct{}{}}
|
||||
for _, sig := range testData.Signatures {
|
||||
if _, ok := keyIDs[sig.KeyID]; !ok {
|
||||
t.Fatalf("Got a signature we didn't expect: %s", sig.KeyID)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSignReturnsNoSigs(t *testing.T) {
|
||||
failingCryptoService := &FailingCryptoService{}
|
||||
testData := data.Signed{}
|
||||
|
||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||
key := data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||
err := Sign(failingCryptoService, &testData, key)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected failure due to no signature being returned by the crypto service")
|
||||
}
|
||||
if len(testData.Signatures) != 0 {
|
||||
t.Fatalf("Incorrect number of signatures, expected 0: %d", len(testData.Signatures))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
testKey, _ := pem.Decode([]byte(testKeyPEM1))
|
||||
k := data.NewPublicKey(data.RSAKey, testKey.Bytes)
|
||||
mockCryptoService := &MockCryptoService{testKey: k}
|
||||
|
||||
key, err := mockCryptoService.Create("root", data.ED25519Key)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if key.ID() != testKeyID1 {
|
||||
t.Fatalf("Expected key ID not found: %s", key.ID())
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,202 +0,0 @@
|
|||
package signed
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/endophage/gotuf/keys"
|
||||
"github.com/jfrazelle/go/canonical/json"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
cryptoService := NewEd25519()
|
||||
type test struct {
|
||||
name string
|
||||
keys []data.PublicKey
|
||||
roles map[string]*data.Role
|
||||
s *data.Signed
|
||||
ver int
|
||||
exp *time.Time
|
||||
typ string
|
||||
role string
|
||||
err error
|
||||
mut func(*test)
|
||||
}
|
||||
|
||||
expiredTime := time.Now().Add(-time.Hour)
|
||||
minVer := 10
|
||||
tests := []test{
|
||||
{
|
||||
name: "no signatures",
|
||||
mut: func(t *test) { t.s.Signatures = []data.Signature{} },
|
||||
err: ErrNoSignatures,
|
||||
},
|
||||
{
|
||||
name: "unknown role",
|
||||
role: "foo",
|
||||
err: ErrUnknownRole,
|
||||
},
|
||||
//{
|
||||
// name: "wrong signature method",
|
||||
// mut: func(t *test) { t.s.Signatures[0].Method = "foo" },
|
||||
// err: ErrWrongMethod,
|
||||
//},
|
||||
// {
|
||||
// name: "signature wrong length",
|
||||
// mut: func(t *test) { t.s.Signatures[0].Signature = []byte{0} },
|
||||
// err: ErrInvalid,
|
||||
// },
|
||||
{
|
||||
name: "key missing from role",
|
||||
mut: func(t *test) { t.roles["root"].KeyIDs = nil },
|
||||
err: ErrRoleThreshold{},
|
||||
},
|
||||
// {
|
||||
// name: "invalid signature",
|
||||
// mut: func(t *test) { t.s.Signatures[0].Signature = make([]byte, ed25519.SignatureSize) },
|
||||
// err: ErrInvalid,
|
||||
// },
|
||||
{
|
||||
name: "not enough signatures",
|
||||
mut: func(t *test) { t.roles["root"].Threshold = 2 },
|
||||
err: ErrRoleThreshold{},
|
||||
},
|
||||
{
|
||||
name: "exactly enough signatures",
|
||||
},
|
||||
{
|
||||
name: "more than enough signatures",
|
||||
mut: func(t *test) {
|
||||
k, _ := cryptoService.Create("root", data.ED25519Key)
|
||||
Sign(cryptoService, t.s, k)
|
||||
t.keys = append(t.keys, k)
|
||||
t.roles["root"].KeyIDs = append(t.roles["root"].KeyIDs, k.ID())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "duplicate key id",
|
||||
mut: func(t *test) {
|
||||
t.roles["root"].Threshold = 2
|
||||
t.s.Signatures = append(t.s.Signatures, t.s.Signatures[0])
|
||||
},
|
||||
err: ErrRoleThreshold{},
|
||||
},
|
||||
{
|
||||
name: "unknown key",
|
||||
mut: func(t *test) {
|
||||
k, _ := cryptoService.Create("root", data.ED25519Key)
|
||||
Sign(cryptoService, t.s, k)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown key below threshold",
|
||||
mut: func(t *test) {
|
||||
k, _ := cryptoService.Create("root", data.ED25519Key)
|
||||
Sign(cryptoService, t.s, k)
|
||||
t.roles["root"].Threshold = 2
|
||||
},
|
||||
err: ErrRoleThreshold{},
|
||||
},
|
||||
{
|
||||
name: "unknown keys in db",
|
||||
mut: func(t *test) {
|
||||
k, _ := cryptoService.Create("root", data.ED25519Key)
|
||||
Sign(cryptoService, t.s, k)
|
||||
t.keys = append(t.keys, k)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown keys in db below threshold",
|
||||
mut: func(t *test) {
|
||||
k, _ := cryptoService.Create("root", data.ED25519Key)
|
||||
Sign(cryptoService, t.s, k)
|
||||
t.keys = append(t.keys, k)
|
||||
t.roles["root"].Threshold = 2
|
||||
},
|
||||
err: ErrRoleThreshold{},
|
||||
},
|
||||
{
|
||||
name: "wrong type",
|
||||
typ: "bar",
|
||||
err: ErrWrongType,
|
||||
},
|
||||
{
|
||||
name: "low version",
|
||||
ver: minVer - 1,
|
||||
err: ErrLowVersion{minVer - 1, minVer},
|
||||
},
|
||||
{
|
||||
role: "root",
|
||||
name: "expired",
|
||||
exp: &expiredTime,
|
||||
err: ErrExpired{"root", expiredTime.Format("Mon Jan 2 15:04:05 MST 2006")},
|
||||
},
|
||||
}
|
||||
for _, run := range tests {
|
||||
if run.role == "" {
|
||||
run.role = "root"
|
||||
}
|
||||
if run.ver == 0 {
|
||||
run.ver = minVer
|
||||
}
|
||||
if run.exp == nil {
|
||||
expires := time.Now().Add(time.Hour)
|
||||
run.exp = &expires
|
||||
}
|
||||
if run.typ == "" {
|
||||
run.typ = data.TUFTypes[run.role]
|
||||
}
|
||||
if run.keys == nil && run.s == nil {
|
||||
k, _ := cryptoService.Create("root", data.ED25519Key)
|
||||
meta := &data.SignedCommon{Type: run.typ, Version: run.ver, Expires: *run.exp}
|
||||
|
||||
b, err := json.MarshalCanonical(meta)
|
||||
assert.NoError(t, err)
|
||||
s := &data.Signed{Signed: b}
|
||||
Sign(cryptoService, s, k)
|
||||
run.s = s
|
||||
run.keys = []data.PublicKey{k}
|
||||
}
|
||||
if run.roles == nil {
|
||||
run.roles = map[string]*data.Role{
|
||||
"root": &data.Role{
|
||||
RootRole: data.RootRole{
|
||||
KeyIDs: []string{run.keys[0].ID()},
|
||||
Threshold: 1,
|
||||
},
|
||||
Name: "root",
|
||||
},
|
||||
}
|
||||
}
|
||||
if run.mut != nil {
|
||||
run.mut(&run)
|
||||
}
|
||||
|
||||
db := keys.NewDB()
|
||||
for _, k := range run.keys {
|
||||
db.AddKey(k)
|
||||
}
|
||||
for _, r := range run.roles {
|
||||
err := db.AddRole(r)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
err := Verify(run.s, run.role, minVer, db)
|
||||
if e, ok := run.err.(ErrExpired); ok {
|
||||
assertErrExpired(t, err, e)
|
||||
} else {
|
||||
assert.Equal(t, run.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertErrExpired(t *testing.T, err error, expected ErrExpired) {
|
||||
actual, ok := err.(ErrExpired)
|
||||
if !ok {
|
||||
t.Fatalf("expected err to have type ErrExpired, got %T", err)
|
||||
}
|
||||
assert.Equal(t, actual.Expired, expected.Expired)
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/endophage/gotuf/testutils"
|
||||
)
|
||||
|
||||
// TestDBStore just ensures we can initialize an empty store.
|
||||
// Nothing to test, just ensure no crashes :-)
|
||||
func TestDBStore(t *testing.T) {
|
||||
db := testutils.GetSqliteDB()
|
||||
defer testutils.FlushDB(db)
|
||||
_ = DBStore(db, "")
|
||||
}
|
||||
|
||||
func TestLoadFiles(t *testing.T) {
|
||||
db := testutils.GetSqliteDB()
|
||||
defer testutils.FlushDB(db)
|
||||
store := DBStore(db, "docker.io/testImage")
|
||||
testmeta := testutils.SampleMeta()
|
||||
store.AddBlob("/foo.txt", testmeta)
|
||||
|
||||
called := false
|
||||
check := func(path string, meta data.FileMeta) error {
|
||||
if called {
|
||||
t.Fatal("Store only has one item but check called > once.")
|
||||
} else {
|
||||
called = true
|
||||
}
|
||||
|
||||
if path != "/foo.txt" {
|
||||
t.Fatal("Path is incorrect", path)
|
||||
}
|
||||
|
||||
if meta.Length != testmeta.Length {
|
||||
t.Fatal("Length is incorrect")
|
||||
}
|
||||
|
||||
if len(meta.Hashes) != len(testmeta.Hashes) {
|
||||
t.Fatal("Hashes map has been modified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
store.WalkStagedTargets([]string{}, check)
|
||||
if !called {
|
||||
t.Fatal("Walk func never called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddBlob(t *testing.T) {
|
||||
db := testutils.GetSqliteDB()
|
||||
defer testutils.FlushDB(db)
|
||||
store := DBStore(db, "docker.io/testImage")
|
||||
testmeta := testutils.SampleMeta()
|
||||
store.AddBlob("/foo.txt", testmeta)
|
||||
|
||||
called := false
|
||||
check := func(path string, meta data.FileMeta) error {
|
||||
if called {
|
||||
t.Fatal("Store only has one item but check called > once.")
|
||||
} else {
|
||||
called = true
|
||||
}
|
||||
|
||||
if path != "/foo.txt" {
|
||||
t.Fatal("Path is incorrect")
|
||||
}
|
||||
|
||||
if meta.Length != 1 {
|
||||
t.Fatal("Length is incorrect")
|
||||
}
|
||||
|
||||
sha256, ok256 := meta.Hashes["sha256"]
|
||||
sha512, ok512 := meta.Hashes["sha512"]
|
||||
if len(meta.Hashes) != 2 || !ok256 || !ok512 {
|
||||
t.Fatal("Hashes map has been modified")
|
||||
}
|
||||
|
||||
hash := []byte{0x01, 0x02}
|
||||
if sha256[0] != hash[0] || sha256[1] != hash[1] {
|
||||
t.Fatal("SHA256 has been modified")
|
||||
}
|
||||
hash = []byte{0x03, 0x04}
|
||||
if sha512[0] != hash[0] || sha512[1] != hash[1] {
|
||||
t.Fatal("SHA512 has been modified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
store.WalkStagedTargets([]string{}, check)
|
||||
|
||||
if !called {
|
||||
t.Fatal("Walk func never called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveBlob(t *testing.T) {
|
||||
testPath := "/foo.txt"
|
||||
db := testutils.GetSqliteDB()
|
||||
defer testutils.FlushDB(db)
|
||||
store := DBStore(db, "docker.io/testImage")
|
||||
meta := testutils.SampleMeta()
|
||||
|
||||
store.AddBlob(testPath, meta)
|
||||
|
||||
called := false
|
||||
check := func(path string, meta data.FileMeta) error {
|
||||
called = true
|
||||
return nil
|
||||
}
|
||||
|
||||
store.RemoveBlob(testPath)
|
||||
|
||||
store.WalkStagedTargets([]string{}, check)
|
||||
|
||||
if called {
|
||||
t.Fatal("Walk func called on empty db")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLoadFilesWithPath(t *testing.T) {
|
||||
db := testutils.GetSqliteDB()
|
||||
defer testutils.FlushDB(db)
|
||||
store := DBStore(db, "docker.io/testImage")
|
||||
meta := testutils.SampleMeta()
|
||||
|
||||
store.AddBlob("/foo.txt", meta)
|
||||
store.AddBlob("/bar.txt", meta)
|
||||
|
||||
called := false
|
||||
check := func(path string, meta data.FileMeta) error {
|
||||
if called {
|
||||
t.Fatal("Store only has one item but check called > once.")
|
||||
} else {
|
||||
called = true
|
||||
}
|
||||
|
||||
if path != "/foo.txt" {
|
||||
t.Fatal("Path is incorrect")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
store.WalkStagedTargets([]string{"/foo.txt"}, check)
|
||||
|
||||
if !called {
|
||||
t.Fatal("Walk func never called")
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const testDir = "/tmp/testFilesystemStore/"
|
||||
|
||||
func TestNewFilesystemStore(t *testing.T) {
|
||||
_, err := NewFilesystemStore(testDir, "metadata", "json", "targets")
|
||||
assert.Nil(t, err, "Initializing FilesystemStore returned unexpected error: %v", err)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
info, err := os.Stat(path.Join(testDir, "metadata"))
|
||||
assert.Nil(t, err, "Error attempting to stat metadata dir: %v", err)
|
||||
assert.NotNil(t, info, "Nil FileInfo from stat on metadata dir")
|
||||
assert.True(t, 0700&info.Mode() != 0, "Metadata directory is not writable")
|
||||
|
||||
info, err = os.Stat(path.Join(testDir, "targets"))
|
||||
assert.Nil(t, err, "Error attempting to stat targets dir: %v", err)
|
||||
assert.NotNil(t, info, "Nil FileInfo from stat on targets dir")
|
||||
assert.True(t, 0700&info.Mode() != 0, "Targets directory is not writable")
|
||||
}
|
||||
|
||||
func TestSetMeta(t *testing.T) {
|
||||
s, err := NewFilesystemStore(testDir, "metadata", "json", "targets")
|
||||
assert.Nil(t, err, "Initializing FilesystemStore returned unexpected error: %v", err)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
testContent := []byte("test data")
|
||||
|
||||
err = s.SetMeta("testMeta", testContent)
|
||||
assert.Nil(t, err, "SetMeta returned unexpected error: %v", err)
|
||||
|
||||
content, err := ioutil.ReadFile(path.Join(testDir, "metadata", "testMeta.json"))
|
||||
assert.Nil(t, err, "Error reading file: %v", err)
|
||||
assert.Equal(t, testContent, content, "Content written to file was corrupted.")
|
||||
}
|
||||
|
||||
func TestGetMeta(t *testing.T) {
|
||||
s, err := NewFilesystemStore(testDir, "metadata", "json", "targets")
|
||||
assert.Nil(t, err, "Initializing FilesystemStore returned unexpected error: %v", err)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
testContent := []byte("test data")
|
||||
|
||||
ioutil.WriteFile(path.Join(testDir, "metadata", "testMeta.json"), testContent, 0600)
|
||||
|
||||
content, err := s.GetMeta("testMeta", int64(len(testContent)))
|
||||
assert.Nil(t, err, "GetMeta returned unexpected error: %v", err)
|
||||
|
||||
assert.Equal(t, testContent, content, "Content read from file was corrupted.")
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/endophage/gotuf/signed"
|
||||
"github.com/jfrazelle/go/canonical/json"
|
||||
)
|
||||
|
||||
const testRoot = `{"signed":{"_type":"Root","consistent_snapshot":false,"expires":"2025-07-17T16:19:21.101698314-07:00","keys":{"1ca15c7f4b2b0c6efce202a545e7267152da28ab7c91590b3b60bdb4da723aad":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb0720c99Cj6ZmuDlznEZ52NA6YpeY9Sj45z51XvPnG63Bi2RSBezMJlPzbSfP39mXKXqOJyT+z9BZhi3FVWczg=="}},"b1d6813b55442ecbfb1f4b40eb1fcdb4290e53434cfc9ba2da24c26c9143873b":{"keytype":"ecdsa-x509","keyval":{"private":null,"public":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJVekNCKzZBREFnRUNBaEFCWDNKLzkzaW8zbHcrZUsvNFhvSHhNQW9HQ0NxR1NNNDlCQU1DTUJFeER6QU4KQmdOVkJBTVRCbVY0Y0dseVpUQWVGdzB4TlRBM01qQXlNekU1TVRkYUZ3MHlOVEEzTVRjeU16RTVNVGRhTUJFeApEekFOQmdOVkJBTVRCbVY0Y0dseVpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJFTDhOTFhQCitreUJZYzhYY0FTMXB2S2l5MXRQUDlCZHJ1dEdrWlR3Z0dEYTM1THMzSUFXaWlrUmlPbGRuWmxVVEE5cG5JekoKOFlRQThhTjQ1TDQvUlplak5UQXpNQTRHQTFVZER3RUIvd1FFQXdJQW9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRgpCUWNEQXpBTUJnTlZIUk1CQWY4RUFqQUFNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJRVJ1ZUVURG5xMlRqRFBmClhGRStqUFJqMEtqdXdEOG9HSmtoVGpMUDAycjhBaUI5cUNyL2ZqSXpJZ1NQcTJVSXZqR0hlYmZOYXh1QlpZZUUKYW8xNjd6dHNYZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}},"fbddae7f25a6c23ca735b017206a849d4c89304a4d8de4dcc4b3d6f3eb22ce3b":{"keytype":"ecdsa","keyval":{"private":null,"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE/xS5fBHK2HKmlGcvAr06vwPITvmxWP4P3CMDCgY25iSaIiM21OiXA1/Uvo3Pa3xh5G3cwCtDvi+4FpflW2iB/w=="}},"fd75751f010c3442e23b3e3e99a1442a112f2f21038603cb8609d8b17c9e912a":{"keytype":"ed25519","keyval":{"private":null,"public":"rc+glN01m+q8jmX8SolGsjTfk6NMhUQTWyj10hjmne0="}}},"roles":{"root":{"keyids":["b1d6813b55442ecbfb1f4b40eb1fcdb4290e53434cfc9ba2da24c26c9143873b"],"threshold":1},"snapshot":{"keyids":["1ca15c7f4b2b0c6efce202a545e7267152da28ab7c91590b3b60bdb4da723aad"],"threshold":1},"targets":{"keyids":["fbddae7f25a6c23ca735b017206a849d4c89304a4d8de4dcc4b3d6f3eb22ce3b"],"threshold":1},"timestamp":{"keyids":["fd75751f010c3442e23b3e3e99a1442a112f2f21038603cb8609d8b17c9e912a"],"threshold":1}},"version":2},"signatures":[{"keyid":"b1d6813b55442ecbfb1f4b40eb1fcdb4290e53434cfc9ba2da24c26c9143873b","method":"ecdsa","sig":"A2lNVwxHBnD9ViFtRre8r5oG6VvcvJnC6gdvvxv/Jyag40q/fNMjllCqyHrb+6z8XDZcrTTDsFU1R3/e+92d1A=="}]}`
|
||||
|
||||
const testRootKey = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJVekNCKzZBREFnRUNBaEFCWDNKLzkzaW8zbHcrZUsvNFhvSHhNQW9HQ0NxR1NNNDlCQU1DTUJFeER6QU4KQmdOVkJBTVRCbVY0Y0dseVpUQWVGdzB4TlRBM01qQXlNekU1TVRkYUZ3MHlOVEEzTVRjeU16RTVNVGRhTUJFeApEekFOQmdOVkJBTVRCbVY0Y0dseVpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJFTDhOTFhQCitreUJZYzhYY0FTMXB2S2l5MXRQUDlCZHJ1dEdrWlR3Z0dEYTM1THMzSUFXaWlrUmlPbGRuWmxVVEE5cG5JekoKOFlRQThhTjQ1TDQvUlplak5UQXpNQTRHQTFVZER3RUIvd1FFQXdJQW9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRgpCUWNEQXpBTUJnTlZIUk1CQWY4RUFqQUFNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJRVJ1ZUVURG5xMlRqRFBmClhGRStqUFJqMEtqdXdEOG9HSmtoVGpMUDAycjhBaUI5cUNyL2ZqSXpJZ1NQcTJVSXZqR0hlYmZOYXh1QlpZZUUKYW8xNjd6dHNYZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
|
||||
|
||||
type TestRoundTripper struct{}
|
||||
|
||||
func (rt *TestRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
func TestHTTPStoreGetMeta(t *testing.T) {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(testRoot))
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
store, err := NewHTTPStore(
|
||||
server.URL,
|
||||
"metadata",
|
||||
"txt",
|
||||
"targets",
|
||||
"key",
|
||||
&http.Transport{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
j, err := store.GetMeta("root", 4801)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p := &data.Signed{}
|
||||
err = json.Unmarshal(j, p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rootKey, err := base64.StdEncoding.DecodeString(testRootKey)
|
||||
assert.NoError(t, err)
|
||||
k := data.NewPublicKey("ecdsa-x509", rootKey)
|
||||
|
||||
sigBytes := p.Signatures[0].Signature
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var decoded map[string]interface{}
|
||||
if err := json.Unmarshal(p.Signed, &decoded); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
msg, err := json.MarshalCanonical(decoded)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
method := p.Signatures[0].Method
|
||||
err = signed.Verifiers[method].Verify(k, sigBytes, msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestSetMultiMeta(t *testing.T) {
|
||||
metas := map[string][]byte{
|
||||
"root": []byte("root data"),
|
||||
"targets": []byte("targets data"),
|
||||
}
|
||||
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
reader, err := r.MultipartReader()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
updates := make(map[string][]byte)
|
||||
for {
|
||||
part, err := reader.NextPart()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
role := strings.TrimSuffix(part.FileName(), ".json")
|
||||
updates[role], err = ioutil.ReadAll(part)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
rd, rok := updates["root"]
|
||||
assert.True(t, rok)
|
||||
assert.Equal(t, rd, metas["root"])
|
||||
|
||||
td, tok := updates["targets"]
|
||||
assert.True(t, tok)
|
||||
assert.Equal(t, td, metas["targets"])
|
||||
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
defer server.Close()
|
||||
store, err := NewHTTPStore(server.URL, "metadata", "json", "targets", "key", http.DefaultTransport)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
store.SetMultiMeta(metas)
|
||||
}
|
||||
|
||||
func TestPyCryptoRSAPSSCompat(t *testing.T) {
|
||||
pubPem := "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAnKuXZeefa2LmgxaL5NsM\nzKOHNe+x/nL6ik+lDBCTV6OdcwAhHQS+PONGhrChIUVR6Vth3hUCrreLzPO73Oo5\nVSCuRJ53UronENl6lsa5mFKP8StYLvIDITNvkoT3j52BJIjyNUK9UKY9As2TNqDf\nBEPIRp28ev/NViwGOEkBu2UAbwCIdnDXm8JQErCZA0Ydm7PKGgjLbFsFGrVzqXHK\n6pdzJXlhr9yap3UpgQ/iO9JtoEYB2EXsnSrPc9JRjR30bNHHtnVql3fvinXrAEwq\n3xmN4p+R4VGzfdQN+8Kl/IPjqWB535twhFYEG/B7Ze8IwbygBjK3co/KnOPqMUrM\nBI8ztvPiogz+MvXb8WvarZ6TMTh8ifZI96r7zzqyzjR1hJulEy3IsMGvz8XS2J0X\n7sXoaqszEtXdq5ef5zKVxkiyIQZcbPgmpHLq4MgfdryuVVc/RPASoRIXG4lKaTJj\n1ANMFPxDQpHudCLxwCzjCb+sVa20HBRPTnzo8LSZkI6jAgMBAAE=\n-----END PUBLIC KEY-----"
|
||||
//privPem := "-----BEGIN RSA PRIVATE KEY-----\nMIIG4wIBAAKCAYEAnKuXZeefa2LmgxaL5NsMzKOHNe+x/nL6ik+lDBCTV6OdcwAh\nHQS+PONGhrChIUVR6Vth3hUCrreLzPO73Oo5VSCuRJ53UronENl6lsa5mFKP8StY\nLvIDITNvkoT3j52BJIjyNUK9UKY9As2TNqDfBEPIRp28ev/NViwGOEkBu2UAbwCI\ndnDXm8JQErCZA0Ydm7PKGgjLbFsFGrVzqXHK6pdzJXlhr9yap3UpgQ/iO9JtoEYB\n2EXsnSrPc9JRjR30bNHHtnVql3fvinXrAEwq3xmN4p+R4VGzfdQN+8Kl/IPjqWB5\n35twhFYEG/B7Ze8IwbygBjK3co/KnOPqMUrMBI8ztvPiogz+MvXb8WvarZ6TMTh8\nifZI96r7zzqyzjR1hJulEy3IsMGvz8XS2J0X7sXoaqszEtXdq5ef5zKVxkiyIQZc\nbPgmpHLq4MgfdryuVVc/RPASoRIXG4lKaTJj1ANMFPxDQpHudCLxwCzjCb+sVa20\nHBRPTnzo8LSZkI6jAgMBAAECggGAdzyI7z/HLt2IfoAsXDLynNRgVYZluzgawiU3\ngeUjnnGhpSKWERXJC2IWDPBk0YOGgcnQxErNTdfXiFZ/xfRlSgqjVwob2lRe4w4B\npLr+CZXcgznv1VrPUvdolOSp3R2Mahfn7u0qVDUQ/g8jWVI6KW7FACmQhzQkPM8o\ntLGrpcmK+PA465uaHKtYccEB02ILqrK8v++tknv7eIZczrsSKlS1h/HHjSaidYxP\n2DAUiF7wnChrwwQEvuEUHhwVgQcoDMBoow0zwHdbFiFO2ZT54H2oiJWLhpR/x6RK\ngM1seqoPH2sYErPJACMcYsMtF4Tx7b5c4WSj3vDCGb+jeqnNS6nFC3aMnv75mUS2\nYDPU1heJFd8pNHVf0RDejLZZUiJSnXf3vpOxt9Xv2+4He0jeMfLV7zX0mO2Ni3MJ\nx6PiVy4xerHImOuuHzSla5crOq2ECiAxd1wEOFDRD2LRHzfhpk1ghiA5xA1qwc7Z\neRnkVfoy6PPZ4lZakZTm0p8YCQURAoHBAMUIC/7vnayLae7POmgy+np/ty7iMfyd\nV1eO6LTO21KAaGGlhaY26WD/5LcG2FUgc5jKKahprGrmiNLzLUeQPckJmuijSEVM\nl/4DlRvCo867l7fLaVqYzsQBBdeGIFNiT+FBOd8atff87ZBEfH/rXbDi7METD/VR\n4TdblnCsKYAXEJUdkw3IK7SUGERiQZIwKXrH/Map4ibDrljJ71iCgEureU0DBwcg\nwLftmjGMISoLscdRxeubX5uf/yxtHBJeRwKBwQDLjzHhb4gNGdBHUl4hZPAGCq1V\nLX/GpfoOVObW64Lud+tI6N9GNua5/vWduL7MWWOzDTMZysganhKwsJCY5SqAA9p0\nb6ohusf9i1nUnOa2F2j+weuYPXrTYm+ZrESBBdaEJPuj3R5YHVujrBA9Xe0kVOe3\nne151A+0xJOI3tX9CttIaQAsXR7cMDinkDITw6i7X4olRMPCSixHLW97cDsVDRGt\necO1d4dP3OGscN+vKCoL6tDKDotzWHYPwjH47sUCgcEAoVI8WCiipbKkMnaTsNsE\ngKXvO0DSgq3k5HjLCbdQldUzIbgfnH7bSKNcBYtiNxjR7OihgRW8qO5GWsnmafCs\n1dy6a/2835id3cnbHRaZflvUFhVDFn2E1bCsstFLyFn3Y0w/cO9yzC/X5sZcVXRF\nit3R0Selakv3JZckru4XMJwx5JWJYMBjIIAc+miknWg3niL+UT6pPun65xG3mXWI\nS+yC7c4rw+dKQ44UMLs2MDHRBoxqi8T0W/x9NkfDszpjAoHAclH7S4ZdvC3RIR0L\nLGoJuvroGbwx1JiGdOINuooNwGuswge2zTIsJi0gN/H3hcB2E6rIFiYid4BrMrwW\nmSeq1LZVS6siu0qw4p4OVy+/CmjfWKQD8j4k6u6PipiK6IMk1JYIlSCr2AS04JjT\njgNgGVVtxVt2cUM9huIXkXjEaRZdzK7boA60NCkIyGJdHWh3LLQdW4zg/A64C0lj\nIMoJBGuQkAKgfRuh7KI6Q6Qom7BM3OCFXdUJUEBQHc2MTyeZAoHAJdBQGBn1RFZ+\nn75AnbTMZJ6Twp2fVjzWUz/+rnXFlo87ynA18MR2BzaDST4Bvda29UBFGb32Mux9\nOHukqLgIE5jDuqWjy4B5eCoxZf/OvwlgXkX9+gprGR3axn/PZBFPbFB4ZmjbWLzn\nbocn7FJCXf+Cm0cMmv1jIIxej19MUU/duq9iq4RkHY2LG+KrSEQIUVmImCftXdN3\n/qNP5JetY0eH6C+KRc8JqDB0nvbqZNOgYXOfYXo/5Gk8XIHTFihm\n-----END RSA PRIVATE KEY-----"
|
||||
testStr := "The quick brown fox jumps over the lazy dog."
|
||||
sigHex := "4e05ee9e435653549ac4eddbc43e1a6868636e8ea6dbec2564435afcb0de47e0824cddbd88776ddb20728c53ecc90b5d543d5c37575fda8bd0317025fc07de62ee8084b1a75203b1a23d1ef4ac285da3d1fc63317d5b2cf1aafa3e522acedd366ccd5fe4a7f02a42922237426ca3dc154c57408638b9bfaf0d0213855d4e9ee621db204151bcb13d4dbb18f930ec601469c992c84b14e9e0b6f91ac9517bb3b749dd117e1cbac2e4acb0e549f44558a2005898a226d5b6c8b9291d7abae0d9e0a16858b89662a085f74a202deb867acab792bdbd2c36731217caea8b17bd210c29b890472f11e5afdd1dd7b69004db070e04201778f2c49f5758643881403d45a58d08f51b5c63910c6185892f0b590f191d760b669eff2464456f130239bba94acf54a0cb98f6939ff84ae26a37f9b890be259d9b5d636f6eb367b53e895227d7d79a3a88afd6d28c198ee80f6527437c5fbf63accb81709925c4e03d1c9eaee86f58e4bd1c669d6af042dbd412de0d13b98b1111e2fadbe34b45de52125e9a"
|
||||
k := data.NewPublicKey(data.RSAKey, []byte(pubPem))
|
||||
|
||||
sigBytes, err := hex.DecodeString(sigHex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v := signed.RSAPyCryptoVerifier{}
|
||||
err = v.Verify(k, sigBytes, []byte(testStr))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPyNaCled25519Compat(t *testing.T) {
|
||||
pubHex := "846612b43cef909a0e4ea9c818379bca4723a2020619f95e7a0ccc6f0850b7dc"
|
||||
//privHex := "bf3cdb9b2a664b0460e6755cb689ffca15b6e294f79f9f1fcf90b52e5b063a76"
|
||||
testStr := "The quick brown fox jumps over the lazy dog."
|
||||
sigHex := "166e7013e48f26dccb4e68fe4cf558d1cd3af902f8395534336a7f8b4c56588694aa3ac671767246298a59d5ef4224f02c854f41bfcfe70241db4be1546d6a00"
|
||||
|
||||
pub, _ := hex.DecodeString(pubHex)
|
||||
k := data.NewPublicKey(data.ED25519Key, pub)
|
||||
|
||||
sigBytes, _ := hex.DecodeString(sigHex)
|
||||
|
||||
err := signed.Verifiers[data.EDDSASignature].Verify(k, sigBytes, []byte(testStr))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -5,8 +5,8 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "code.google.com/p/gosqlite/sqlite3"
|
||||
"github.com/endophage/gotuf/data"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var counter int = 1
|
||||
|
|
|
@ -141,7 +141,7 @@ func (tr *TufRepo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
|||
}
|
||||
|
||||
// remove keys no longer in use by any roles
|
||||
for k, _ := range toDelete {
|
||||
for k := range toDelete {
|
||||
delete(tr.Root.Signed.Keys, k)
|
||||
}
|
||||
tr.Root.Dirty = true
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
package tuf
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/endophage/gotuf/keys"
|
||||
"github.com/endophage/gotuf/signed"
|
||||
)
|
||||
|
||||
func initRepo(t *testing.T, cryptoService signed.CryptoService, keyDB *keys.KeyDB) *TufRepo {
|
||||
rootKey, err := cryptoService.Create("root", data.ED25519Key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
targetsKey, err := cryptoService.Create("targets", data.ED25519Key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snapshotKey, err := cryptoService.Create("snapshot", data.ED25519Key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
timestampKey, err := cryptoService.Create("timestamp", data.ED25519Key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
keyDB.AddKey(rootKey)
|
||||
keyDB.AddKey(targetsKey)
|
||||
keyDB.AddKey(snapshotKey)
|
||||
keyDB.AddKey(timestampKey)
|
||||
|
||||
rootRole := &data.Role{
|
||||
Name: "root",
|
||||
RootRole: data.RootRole{
|
||||
KeyIDs: []string{rootKey.ID()},
|
||||
Threshold: 1,
|
||||
},
|
||||
}
|
||||
targetsRole := &data.Role{
|
||||
Name: "targets",
|
||||
RootRole: data.RootRole{
|
||||
KeyIDs: []string{targetsKey.ID()},
|
||||
Threshold: 1,
|
||||
},
|
||||
}
|
||||
snapshotRole := &data.Role{
|
||||
Name: "snapshot",
|
||||
RootRole: data.RootRole{
|
||||
KeyIDs: []string{snapshotKey.ID()},
|
||||
Threshold: 1,
|
||||
},
|
||||
}
|
||||
timestampRole := &data.Role{
|
||||
Name: "timestamp",
|
||||
RootRole: data.RootRole{
|
||||
KeyIDs: []string{timestampKey.ID()},
|
||||
Threshold: 1,
|
||||
},
|
||||
}
|
||||
|
||||
keyDB.AddRole(rootRole)
|
||||
keyDB.AddRole(targetsRole)
|
||||
keyDB.AddRole(snapshotRole)
|
||||
keyDB.AddRole(timestampRole)
|
||||
|
||||
repo := NewTufRepo(keyDB, cryptoService)
|
||||
err = repo.InitRepo(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return repo
|
||||
}
|
||||
|
||||
func writeRepo(t *testing.T, dir string, repo *TufRepo) {
|
||||
//err := os.Remove(dir)
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
signedRoot, err := repo.SignRoot(data.DefaultExpires("root"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rootJSON, _ := json.Marshal(signedRoot)
|
||||
ioutil.WriteFile(dir+"/root.json", rootJSON, 0755)
|
||||
|
||||
for r, _ := range repo.Targets {
|
||||
signedTargets, err := repo.SignTargets(r, data.DefaultExpires("targets"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
targetsJSON, _ := json.Marshal(signedTargets)
|
||||
p := path.Join(dir, r+".json")
|
||||
parentDir := filepath.Dir(p)
|
||||
os.MkdirAll(parentDir, 0755)
|
||||
ioutil.WriteFile(p, targetsJSON, 0755)
|
||||
}
|
||||
|
||||
signedSnapshot, err := repo.SignSnapshot(data.DefaultExpires("snapshot"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snapshotJSON, _ := json.Marshal(signedSnapshot)
|
||||
ioutil.WriteFile(dir+"/snapshot.json", snapshotJSON, 0755)
|
||||
|
||||
signedTimestamp, err := repo.SignTimestamp(data.DefaultExpires("timestamp"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
timestampJSON, _ := json.Marshal(signedTimestamp)
|
||||
ioutil.WriteFile(dir+"/timestamp.json", timestampJSON, 0755)
|
||||
}
|
||||
|
||||
func TestInitRepo(t *testing.T) {
|
||||
ed25519 := signed.NewEd25519()
|
||||
keyDB := keys.NewDB()
|
||||
repo := initRepo(t, ed25519, keyDB)
|
||||
writeRepo(t, "/tmp/tufrepo", repo)
|
||||
}
|
||||
|
||||
func TestUpdateDelegations(t *testing.T) {
|
||||
ed25519 := signed.NewEd25519()
|
||||
keyDB := keys.NewDB()
|
||||
repo := initRepo(t, ed25519, keyDB)
|
||||
|
||||
testKey, err := ed25519.Create("targets/test", data.ED25519Key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
role, err := data.NewRole("targets/test", 1, []string{testKey.ID()}, []string{"test"}, []string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = repo.UpdateDelegations(role, []data.Key{testKey}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testDeepKey, err := ed25519.Create("targets/test/deep", data.ED25519Key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
roleDeep, err := data.NewRole("targets/test/deep", 1, []string{testDeepKey.ID()}, []string{"test/deep"}, []string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = repo.UpdateDelegations(roleDeep, []data.Key{testDeepKey}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
writeRepo(t, "/tmp/tufdelegation", repo)
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/endophage/gotuf/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFileMetaEqual(t *testing.T) {
|
||||
type test struct {
|
||||
name string
|
||||
b data.FileMeta
|
||||
a data.FileMeta
|
||||
err func(test) error
|
||||
}
|
||||
fileMeta := func(length int64, hashes map[string]string) data.FileMeta {
|
||||
m := data.FileMeta{Length: length, Hashes: make(map[string][]byte, len(hashes))}
|
||||
for typ, hash := range hashes {
|
||||
v, err := hex.DecodeString(hash)
|
||||
assert.NoError(t, err, "hash not in hex")
|
||||
m.Hashes[typ] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
tests := []test{
|
||||
{
|
||||
name: "wrong length",
|
||||
a: data.FileMeta{Length: 1},
|
||||
b: data.FileMeta{Length: 2},
|
||||
err: func(test) error { return ErrWrongLength },
|
||||
},
|
||||
{
|
||||
name: "wrong sha512 hash",
|
||||
a: fileMeta(10, map[string]string{"sha512": "111111"}),
|
||||
b: fileMeta(10, map[string]string{"sha512": "222222"}),
|
||||
err: func(t test) error { return ErrWrongHash{"sha512", t.b.Hashes["sha512"], t.a.Hashes["sha512"]} },
|
||||
},
|
||||
{
|
||||
name: "intersecting hashes",
|
||||
a: fileMeta(10, map[string]string{"sha512": "111111", "md5": "222222"}),
|
||||
b: fileMeta(10, map[string]string{"sha512": "111111", "sha256": "333333"}),
|
||||
err: func(test) error { return nil },
|
||||
},
|
||||
{
|
||||
name: "no common hashes",
|
||||
a: fileMeta(10, map[string]string{"sha512": "111111"}),
|
||||
b: fileMeta(10, map[string]string{"sha256": "222222", "md5": "333333"}),
|
||||
err: func(t test) error { return ErrNoCommonHash{t.b.Hashes, t.a.Hashes} },
|
||||
},
|
||||
}
|
||||
for _, run := range tests {
|
||||
assert.Equal(t, FileMetaEqual(run.a, run.b), run.err(run), "Files not equivalent")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeTarget(t *testing.T) {
|
||||
for before, after := range map[string]string{
|
||||
"": "/",
|
||||
"foo.txt": "/foo.txt",
|
||||
"/bar.txt": "/bar.txt",
|
||||
"foo//bar.txt": "/foo/bar.txt",
|
||||
"/with/./a/dot": "/with/a/dot",
|
||||
"/with/double/../dot": "/with/dot",
|
||||
} {
|
||||
assert.Equal(t, NormalizeTarget(before), after, "Path normalization did not output expected.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashedPaths(t *testing.T) {
|
||||
hexBytes := func(s string) []byte {
|
||||
v, err := hex.DecodeString(s)
|
||||
assert.NoError(t, err, "String was not hex")
|
||||
return v
|
||||
}
|
||||
hashes := data.Hashes{
|
||||
"sha512": hexBytes("abc123"),
|
||||
"sha256": hexBytes("def456"),
|
||||
}
|
||||
paths := HashedPaths("foo/bar.txt", hashes)
|
||||
// cannot use DeepEquals as the returned order is non-deterministic
|
||||
assert.Len(t, paths, 2, "Expected 2 paths")
|
||||
expected := map[string]struct{}{"foo/abc123.bar.txt": {}, "foo/def456.bar.txt": {}}
|
||||
for _, path := range paths {
|
||||
if _, ok := expected[path]; !ok {
|
||||
t.Fatalf("unexpected path: %s", path)
|
||||
}
|
||||
delete(expected, path)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue