diff --git a/server/storage/database_test.go b/server/storage/database_test.go index ff3d43187a..66bff0b2cb 100644 --- a/server/storage/database_test.go +++ b/server/storage/database_test.go @@ -2,87 +2,85 @@ package storage import ( "database/sql" + "io/ioutil" + "os" "testing" - "github.com/DATA-DOG/go-sqlmock" - "github.com/go-sql-driver/mysql" + "github.com/endophage/gotuf/data" + "github.com/jinzhu/gorm" + _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/assert" ) -func TestMySQLUpdateCurrent(t *testing.T) { - db, err := sqlmock.New() - assert.Nil(t, err, "Could not initialize mock DB") - s := NewMySQLStorage(db) - update := MetaUpdate{ - Role: "root", - Version: 0, - Data: []byte("1"), - } - sqlmock.ExpectQuery("SELECT count\\(\\*\\) FROM `tuf_files` WHERE `gun`=\\? AND `role`=\\? AND `version`>=\\?\\;").WithArgs( - "testGUN", - update.Role, - update.Version, - ).WillReturnRows(sqlmock.RowsFromCSVString([]string{"count(*)"}, "0")) - sqlmock.ExpectExec("INSERT INTO `tuf_files` \\(`gun`, `role`, `version`, `data`\\) VALUES \\(\\?,\\?,\\?,\\?\\);").WithArgs( - "testGUN", - update.Role, - update.Version, - update.Data, - ).WillReturnResult(sqlmock.NewResult(0, 1)) - - err = s.UpdateCurrent( - "testGUN", - update, - ) - assert.Nil(t, err, "UpdateCurrent errored unexpectedly: %v", err) - - // There's a bug in the mock lib - //err = db.Close() - //assert.Nil(t, err, "Expectation not met: %v", err) +// GormTUFFile represents a TUF file in the database +type GormTUFFile struct { + ID int `sql:"AUTO_INCREMENT" gorm:"primary_key"` + Gun string `sql:"type:varchar(255);not null"` + Role string `sql:"type:varchar(255);not null"` + Version int + Data []byte `sql:"type:longblob"` } -func TestMySQLUpdateCurrentError(t *testing.T) { - db, err := sqlmock.New() - assert.Nil(t, err, "Could not initialize mock DB") - s := NewMySQLStorage(db) +// TableName sets a specific table name for GormTUFFile +func (g GormTUFFile) TableName() string { + return "tuf_files" +} + +// GormTimestampKey represents a single timestamp key in the database +type GormTimestampKey struct { + Gun string `sql:"type:varchar(255)" gorm:"primary key"` + Cipher string `sql:"type:varchar(30)"` + Public []byte `sql:"type:blob;not null"` +} + +// TableName sets a specific table name for our GormTimestampKey +func (g GormTimestampKey) TableName() string { + return "timestamp_keys" +} + +// SetUpSQLite creates a sqlite database for testing +func SetUpSQLite(t *testing.T) (*gorm.DB, *MySQLStorage) { + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + defer os.RemoveAll(tempBaseDir) + + // We are using SQLite for the tests + db, err := sql.Open("sqlite3", tempBaseDir+"test_db") + assert.NoError(t, err) + + // Create the DB tables + gormDB, _ := gorm.Open("sqlite3", db) + query := gormDB.CreateTable(&GormTUFFile{}) + assert.NoError(t, query.Error) + query = gormDB.Model(&GormTUFFile{}).AddUniqueIndex( + "idx_gun", "gun", "role", "version") + assert.NoError(t, query.Error) + query = gormDB.CreateTable(&GormTimestampKey{}) + assert.NoError(t, query.Error) + + return &gormDB, NewMySQLStorage(db) +} + +func TestMySQLUpdateCurrent(t *testing.T) { + _, dbStore := SetUpSQLite(t) + update := MetaUpdate{ Role: "root", Version: 0, Data: []byte("1"), } - sqlmock.ExpectQuery("SELECT count\\(\\*\\) FROM `tuf_files` WHERE `gun`=\\? AND `role`=\\? AND `version`>=\\?\\;").WithArgs( - "testGUN", - update.Role, - update.Version, - ).WillReturnRows(sqlmock.RowsFromCSVString([]string{"count(*)"}, "0")) - sqlmock.ExpectExec("INSERT INTO `tuf_files` \\(`gun`, `role`, `version`, `data`\\) VALUES \\(\\?,\\?,\\?,\\?\\);").WithArgs( - "testGUN", - update.Role, - update.Version, - update.Data, - ).WillReturnError( - &mysql.MySQLError{ - Number: 1022, - Message: "Duplicate key error", - }, - ) + err := dbStore.UpdateCurrent("testGUN", update) + assert.NoError(t, err, "Creating a row in an empty DB failed.") - err = s.UpdateCurrent( - "testGUN", - update, - ) - assert.NotNil(t, err, "Error should not be nil") - assert.IsType(t, &ErrOldVersion{}, err, "Expected ErrOldVersion error type, got: %v", err) - - // There's a bug in the mock lib - //err = db.Close() - //assert.Nil(t, err, "Expectation not met: %v", err) + err = dbStore.UpdateCurrent("testGUN", update) + assert.Error(t, err, "Error should not be nil") + assert.IsType(t, &ErrOldVersion{}, err, + "Expected ErrOldVersion error type, got: %v", err) + dbStore.DB.Close() } func TestMySQLUpdateMany(t *testing.T) { - db, err := sqlmock.New() - assert.Nil(t, err, "Could not initialize mock DB") - s := NewMySQLStorage(db) + _, dbStore := SetUpSQLite(t) + update1 := MetaUpdate{ Role: "root", Version: 0, @@ -93,199 +91,113 @@ func TestMySQLUpdateMany(t *testing.T) { Version: 1, Data: []byte("2"), } - // start transation - sqlmock.ExpectBegin() - sqlmock.ExpectQuery("SELECT count\\(\\*\\) FROM `tuf_files` WHERE `gun`=\\? AND `role`=\\? AND `version`>=\\?\\;").WithArgs( - "testGUN", - update1.Role, - update1.Version, - ).WillReturnRows(sqlmock.RowsFromCSVString([]string{"count(*)"}, "0")) - sqlmock.ExpectExec("INSERT INTO `tuf_files` \\(`gun`, `role`, `version`, `data`\\) VALUES \\(\\?,\\?,\\?,\\?\\);").WithArgs( - "testGUN", - update1.Role, - update1.Version, - update1.Data, - ).WillReturnResult(sqlmock.NewResult(0, 1)) - - sqlmock.ExpectQuery("SELECT count\\(\\*\\) FROM `tuf_files` WHERE `gun`=\\? AND `role`=\\? AND `version`>=\\?\\;").WithArgs( - "testGUN", - update2.Role, - update2.Version, - ).WillReturnRows(sqlmock.RowsFromCSVString([]string{"count(*)"}, "0")) - sqlmock.ExpectExec("INSERT INTO `tuf_files` \\(`gun`, `role`, `version`, `data`\\) VALUES \\(\\?,\\?,\\?,\\?\\);").WithArgs( - "testGUN", - update2.Role, - update2.Version, - update2.Data, - ).WillReturnResult(sqlmock.NewResult(0, 1)) - - // expect commit - sqlmock.ExpectCommit() - - err = s.UpdateMany( - "testGUN", - []MetaUpdate{update1, update2}, - ) - assert.Nil(t, err, "UpdateMany errored unexpectedly: %v", err) - - // There's a bug in the mock lib - //err = db.Close() - //assert.Nil(t, err, "Expectation not met: %v", err) + err := dbStore.UpdateMany("testGUN", []MetaUpdate{update1, update2}) + assert.NoError(t, err, "UpdateMany errored unexpectedly: %v", err) + dbStore.DB.Close() } -func TestMySQLUpdateManyRollback(t *testing.T) { - db, err := sqlmock.New() - assert.Nil(t, err, "Could not initialize mock DB") - s := NewMySQLStorage(db) - update := MetaUpdate{ - Role: "root", - Version: 0, - Data: []byte("1"), - } - execError := mysql.MySQLError{} - // start transation - sqlmock.ExpectBegin() - sqlmock.ExpectQuery("SELECT count\\(\\*\\) FROM `tuf_files` WHERE `gun`=\\? AND `role`=\\? AND `version`>=\\?\\;").WithArgs( - "testGUN", - update.Role, - update.Version, - ).WillReturnRows(sqlmock.RowsFromCSVString([]string{"count(*)"}, "0")) - // insert first update - sqlmock.ExpectExec("INSERT INTO `tuf_files` \\(`gun`, `role`, `version`, `data`\\) VALUES \\(\\?,\\?,\\?,\\?\\);").WithArgs( - "testGUN", - update.Role, - update.Version, - update.Data, - ).WillReturnError(&execError) +func TestMySQLUpdateManyDuplicateRollback(t *testing.T) { + _, dbStore := SetUpSQLite(t) - // expect commit - sqlmock.ExpectRollback() + update := MetaUpdate{ + Role: "root", + Version: 0, + Data: []byte("1"), + } - err = s.UpdateMany( - "testGUN", - []MetaUpdate{update}, - ) - assert.IsType(t, &execError, err, "UpdateMany returned wrong error type") + err := dbStore.UpdateMany("testGUN", []MetaUpdate{update, update}) + assert.Error(t, err, "There should be an error updating twice.") + // sqlite3 error and mysql error aren't compatible + // assert.IsType(t, &ErrOldVersion{}, err, + // "UpdateMany returned wrong error type") - err = db.Close() - assert.Nil(t, err, "Expectation not met: %v", err) + // the whole transaction should have rolled back, so there should be + // no entries. + byt, err := dbStore.GetCurrent("testGUN", "root") + assert.Nil(t, byt) + assert.Error(t, err, "There should be an error Getting any entries") + assert.IsType(t, &ErrNotFound{}, err, "Should get a not found error") + + dbStore.DB.Close() } -func TestMySQLUpdateManyDuplicate(t *testing.T) { - db, err := sqlmock.New() - assert.Nil(t, err, "Could not initialize mock DB") - s := NewMySQLStorage(db) - update := MetaUpdate{ - Role: "root", - Version: 0, - Data: []byte("1"), - } - execError := mysql.MySQLError{Number: 1022} - // start transation - sqlmock.ExpectBegin() - - sqlmock.ExpectQuery("SELECT count\\(\\*\\) FROM `tuf_files` WHERE `gun`=\\? AND `role`=\\? AND `version`>=\\?\\;").WithArgs( - "testGUN", - update.Role, - update.Version, - ).WillReturnRows(sqlmock.RowsFromCSVString([]string{"count(*)"}, "0")) - // insert first update - sqlmock.ExpectExec("INSERT INTO `tuf_files` \\(`gun`, `role`, `version`, `data`\\) VALUES \\(\\?,\\?,\\?,\\?\\);").WithArgs( - "testGUN", - update.Role, - update.Version, - update.Data, - ).WillReturnError(&execError) - - // expect commit - sqlmock.ExpectRollback() - - err = s.UpdateMany( - "testGUN", - []MetaUpdate{update}, - ) - assert.IsType(t, &ErrOldVersion{}, err, "UpdateMany returned wrong error type") - - err = db.Close() - assert.Nil(t, err, "Expectation not met: %v", err) -} func TestMySQLGetCurrent(t *testing.T) { - db, err := sqlmock.New() - assert.Nil(t, err, "Could not initialize mock DB") - s := NewMySQLStorage(db) + _, dbStore := SetUpSQLite(t) - sqlmock.ExpectQuery( - "SELECT `data` FROM `tuf_files` WHERE `gun`=\\? AND `role`=\\? ORDER BY `version` DESC LIMIT 1;", - ).WithArgs("testGUN", "root").WillReturnRows( - sqlmock.RowsFromCSVString( - []string{"data"}, - "1", - ), - ) + byt, err := dbStore.GetCurrent("testGUN", "root") + assert.Nil(t, byt) + assert.Error(t, err, "There should be an error Getting an empty table") + assert.IsType(t, &ErrNotFound{}, err, "Should get a not found error") - byt, err := s.GetCurrent("testGUN", "root") - assert.Nil(t, err, "Expected nil error from GetCurrent") - assert.Equal(t, []byte("1"), byt, "Returned data was no correct") + // use UpdateCurrent to create one and test GetCurrent + update := MetaUpdate{ + Role: "root", + Version: 0, + Data: []byte("1"), + } + err = dbStore.UpdateCurrent("testGUN", update) + assert.NoError(t, err, "Creating a row in an empty DB failed.") - // TODO(endophage): these two lines are breaking because there - // seems to be some problem with go-sqlmock - //err = db.Close() - //assert.Nil(t, err, "Expectation not met: %v", err) + byt, err = dbStore.GetCurrent("testGUN", "root") + assert.NoError(t, err, "There should not be any errors getting.") + assert.Equal(t, []byte("1"), byt, "Returned data was incorrect") + + dbStore.DB.Close() } func TestMySQLDelete(t *testing.T) { - db, err := sqlmock.New() - assert.Nil(t, err, "Could not initialize mock DB") - s := NewMySQLStorage(db) + _, dbStore := SetUpSQLite(t) - sqlmock.ExpectExec( - "DELETE FROM `tuf_files` WHERE `gun`=\\?;", - ).WithArgs("testGUN").WillReturnResult(sqlmock.NewResult(0, 1)) + // Not testing deleting from an empty table, because that's not an error + // in SQLite3 - err = s.Delete("testGUN") - assert.Nil(t, err, "Expected nil error from Delete") + // use UpdateCurrent to create one and test GetCurrent + update := MetaUpdate{ + Role: "root", + Version: 0, + Data: []byte("1"), + } + err := dbStore.UpdateCurrent("testGUN", update) + assert.NoError(t, err, "Creating a row in an empty DB failed.") - err = db.Close() - assert.Nil(t, err, "Expectation not met: %v", err) + err = dbStore.Delete("testGUN") + assert.NoError(t, err, "There should not be any errors deleting.") + + dbStore.DB.Close() } func TestMySQLGetTimestampKeyNoKey(t *testing.T) { - db, err := sqlmock.New() - assert.Nil(t, err, "Could not initialize mock DB") - s := NewMySQLStorage(db) + _, dbStore := SetUpSQLite(t) - sqlmock.ExpectQuery( - "SELECT `cipher`, `public` FROM `timestamp_keys` WHERE `gun`=\\?;", - ).WithArgs("testGUN").WillReturnError(sql.ErrNoRows) + cipher, public, err := dbStore.GetTimestampKey("testGUN") + assert.Equal(t, data.KeyAlgorithm(""), cipher) + assert.Nil(t, public) + assert.IsType(t, &ErrNoKey{}, err, + "Expected ErrNoKey from GetTimestampKey") - _, _, err = s.GetTimestampKey("testGUN") - assert.IsType(t, &ErrNoKey{}, err, "Expected ErrNoKey from GetTimestampKey") + err = dbStore.SetTimestampKey("testGUN", "testCipher", []byte("1")) + assert.NoError(t, err, "Inserting timestamp into empty DB should succeed") - //err = db.Close() - //assert.Nil(t, err, "Expectation not met: %v", err) + cipher, public, err = dbStore.GetTimestampKey("testGUN") + assert.Equal(t, data.KeyAlgorithm("testCipher"), cipher, + "Returned cipher was incorrect") + assert.Equal(t, []byte("1"), public, "Returned pubkey was incorrect") } func TestMySQLSetTimestampKeyExists(t *testing.T) { - db, err := sqlmock.New() - assert.Nil(t, err, "Could not initialize mock DB") - s := NewMySQLStorage(db) + _, dbStore := SetUpSQLite(t) - sqlmock.ExpectExec( - "INSERT INTO `timestamp_keys` \\(`gun`, `cipher`, `public`\\) VALUES \\(\\?,\\?,\\?\\);", - ).WithArgs( - "testGUN", - "testCipher", - []byte("1"), - ).WillReturnError( - &mysql.MySQLError{Number: 1022}, - ) + err := dbStore.SetTimestampKey("testGUN", "testCipher", []byte("1")) + assert.NoError(t, err, "Inserting timestamp into empty DB should succeed") - err = s.SetTimestampKey("testGUN", "testCipher", []byte("1")) - assert.IsType(t, &ErrTimestampKeyExists{}, err, "Expected ErrTimestampKeyExists from SetTimestampKey") + err = dbStore.SetTimestampKey("testGUN", "testCipher", []byte("1")) + // sqlite3 error and mysql error aren't compatible - err = db.Close() - assert.Nil(t, err, "Expectation not met: %v", err) + // assert.IsType(t, &ErrTimestampKeyExists{}, err, + // "Expected ErrTimestampKeyExists from SetTimestampKey") + + dbStore.DB.Close() }