diff --git a/.drone.yml b/.drone.yml index 3d71d4a7e..3435e658a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,7 +5,7 @@ env: - PATH=$PWD/Godeps/_workspace/bin:/var/cache/drone/bin:$PATH script: - sudo apt-get -y install libsqlite3-dev sqlite3 1> /dev/null 2> /dev/null - - make + - make build-dist - make test - make dpkg notify: diff --git a/Makefile b/Makefile index a949f2f32..a1e3476ca 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +SELFPKG := github.com/drone/drone +VERSION := 0.2 SHA := $(shell git rev-parse --short HEAD) BRANCH := $(shell git rev-parse --abbrev-ref HEAD) PKGS := \ @@ -11,7 +13,7 @@ build/script \ channel \ database \ database/encrypt \ -database/migrate \ +database/migrate/testing \ database/testing \ mail \ model \ @@ -22,10 +24,25 @@ PKGS := $(addprefix github.com/drone/drone/pkg/,$(PKGS)) all: embed build +build: + go build -o bin/drone -ldflags "-X main.version $(VERSION)dev-$(SHA)" $(SELFPKG)/cmd/drone + go build -o bin/droned -ldflags "-X main.version $(VERSION)dev-$(SHA)" $(SELFPKG)/cmd/droned + +build-dist: godep + godep go build -o bin/drone -ldflags "-X main.version $(VERSION)-$(SHA)" $(SELFPKG)/cmd/drone + godep go build -o bin/droned -ldflags "-X main.version $(VERSION)-$(SHA)" $(SELFPKG)/cmd/droned + +bump-deps: deps vendor + +deps: + go get -u -t -v ./... + vendor: godep git submodule update --init --recursive godep save ./... + +# Embed static assets embed: js rice cd cmd/droned && rice embed cd pkg/template && rice embed @@ -33,14 +50,10 @@ embed: js rice js: cd cmd/droned/assets && find js -name "*.js" ! -name '.*' ! -name "main.js" -exec cat {} \; > js/main.js -build: - cd cmd/drone && go build -ldflags "-X main.version $(SHA)" -o ../../bin/drone - cd cmd/droned && go build -ldflags "-X main.version $(SHA)" -o ../../bin/droned - test: $(PKGS) -$(PKGS): - go test -v $@ +$(PKGS): godep + godep go test -v $@ install: cp deb/drone/etc/init/drone.conf /etc/init/drone.conf @@ -76,10 +89,6 @@ dpkg: run: bin/droned --port=":8080" --datasource="drone.sqlite" -go-gitlab-client: - rm -rf $$GOPATH/src/github.com/plouc/go-gitlab-client - git clone -b raw-request https://github.com/fudanchii/go-gitlab-client $$GOPATH/src/github.com/plouc/go-gitlab-client - godep: go get github.com/tools/godep diff --git a/pkg/database/migrate/sqlite_test.go b/pkg/database/migrate/sqlite_test.go deleted file mode 100644 index 37bc15b45..000000000 --- a/pkg/database/migrate/sqlite_test.go +++ /dev/null @@ -1,575 +0,0 @@ -package migrate_test - -import ( - "database/sql" - "os" - "strings" - "testing" - - . "github.com/drone/drone/pkg/database/migrate" - - _ "github.com/mattn/go-sqlite3" - "github.com/russross/meddler" -) - -type Sample struct { - ID int64 `meddler:"id,pk"` - Imel string `meddler:"imel"` - Name string `meddler:"name"` -} - -type RenameSample struct { - ID int64 `meddler:"id,pk"` - Email string `meddler:"email"` - Name string `meddler:"name"` -} - -type AddColumnSample struct { - ID int64 `meddler:"id,pk"` - Imel string `meddler:"imel"` - Name string `meddler:"name"` - Url string `meddler:"url"` - Num int64 `meddler:"num"` -} - -// ---------- revision 1 - -type revision1 struct{} - -func (r *revision1) Up(mg *MigrationDriver) error { - _, err := mg.CreateTable("samples", []string{ - "id INTEGER PRIMARY KEY AUTOINCREMENT", - "imel VARCHAR(255) UNIQUE", - "name VARCHAR(255)", - }) - return err -} - -func (r *revision1) Down(mg *MigrationDriver) error { - _, err := mg.DropTable("samples") - return err -} - -func (r *revision1) Revision() int64 { - return 1 -} - -// ---------- end of revision 1 - -// ---------- revision 2 - -type revision2 struct{} - -func (r *revision2) Up(mg *MigrationDriver) error { - _, err := mg.RenameTable("samples", "examples") - return err -} - -func (r *revision2) Down(mg *MigrationDriver) error { - _, err := mg.RenameTable("examples", "samples") - return err -} - -func (r *revision2) Revision() int64 { - return 2 -} - -// ---------- end of revision 2 - -// ---------- revision 3 - -type revision3 struct{} - -func (r *revision3) Up(mg *MigrationDriver) error { - if _, err := mg.AddColumn("samples", "url VARCHAR(255)"); err != nil { - return err - } - _, err := mg.AddColumn("samples", "num INTEGER") - return err -} - -func (r *revision3) Down(mg *MigrationDriver) error { - _, err := mg.DropColumns("samples", "num", "url") - return err -} - -func (r *revision3) Revision() int64 { - return 3 -} - -// ---------- end of revision 3 - -// ---------- revision 4 - -type revision4 struct{} - -func (r *revision4) Up(mg *MigrationDriver) error { - _, err := mg.RenameColumns("samples", map[string]string{ - "imel": "email", - }) - return err -} - -func (r *revision4) Down(mg *MigrationDriver) error { - _, err := mg.RenameColumns("samples", map[string]string{ - "email": "imel", - }) - return err -} - -func (r *revision4) Revision() int64 { - return 4 -} - -// ---------- end of revision 4 - -// ---------- revision 5 - -type revision5 struct{} - -func (r *revision5) Up(mg *MigrationDriver) error { - _, err := mg.AddIndex("samples", []string{"url", "name"}) - return err -} - -func (r *revision5) Down(mg *MigrationDriver) error { - _, err := mg.DropIndex("samples", []string{"url", "name"}) - return err -} - -func (r *revision5) Revision() int64 { - return 5 -} - -// ---------- end of revision 5 - -// ---------- revision 6 -type revision6 struct{} - -func (r *revision6) Up(mg *MigrationDriver) error { - _, err := mg.RenameColumns("samples", map[string]string{ - "url": "host", - }) - return err -} - -func (r *revision6) Down(mg *MigrationDriver) error { - _, err := mg.RenameColumns("samples", map[string]string{ - "host": "url", - }) - return err -} - -func (r *revision6) Revision() int64 { - return 6 -} - -// ---------- end of revision 6 - -// ---------- revision 7 -type revision7 struct{} - -func (r *revision7) Up(mg *MigrationDriver) error { - _, err := mg.DropColumns("samples", "host", "num") - return err -} - -func (r *revision7) Down(mg *MigrationDriver) error { - if _, err := mg.AddColumn("samples", "host VARCHAR(255)"); err != nil { - return err - } - _, err := mg.AddColumn("samples", "num INSTEGER") - return err -} - -func (r *revision7) Revision() int64 { - return 7 -} - -// ---------- end of revision 7 - -// ---------- revision 8 -type revision8 struct{} - -func (r *revision8) Up(mg *MigrationDriver) error { - if _, err := mg.AddColumn("samples", "repo_id INTEGER"); err != nil { - return err - } - _, err := mg.AddColumn("samples", "repo VARCHAR(255)") - return err -} - -func (r *revision8) Down(mg *MigrationDriver) error { - _, err := mg.DropColumns("samples", "repo", "repo_id") - return err -} - -func (r *revision8) Revision() int64 { - return 8 -} - -// ---------- end of revision 8 - -// ---------- revision 9 -type revision9 struct{} - -func (r *revision9) Up(mg *MigrationDriver) error { - _, err := mg.RenameColumns("samples", map[string]string{ - "repo": "repository", - }) - return err -} - -func (r *revision9) Down(mg *MigrationDriver) error { - _, err := mg.RenameColumns("samples", map[string]string{ - "repository": "repo", - }) - return err -} - -func (r *revision9) Revision() int64 { - return 9 -} - -// ---------- end of revision 9 - -// ---------- revision 10 - -type revision10 struct{} - -func (r *revision10) Revision() int64 { - return 10 -} - -func (r *revision10) Up(mg *MigrationDriver) error { - _, err := mg.ChangeColumn("samples", "email", "varchar(512) UNIQUE") - return err -} - -func (r *revision10) Down(mg *MigrationDriver) error { - _, err := mg.ChangeColumn("samples", "email", "varchar(255) unique") - return err -} - -// ---------- end of revision 10 - -var db *sql.DB - -var testSchema = ` -CREATE TABLE samples ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - imel VARCHAR(255) UNIQUE, - name VARCHAR(255) -); -` - -var dataDump = []string{ - `INSERT INTO samples (imel, name) VALUES ('test@example.com', 'Test Tester');`, - `INSERT INTO samples (imel, name) VALUES ('foo@bar.com', 'Foo Bar');`, - `INSERT INTO samples (imel, name) VALUES ('crash@bandicoot.io', 'Crash Bandicoot');`, -} - -func TestMigrateCreateTable(t *testing.T) { - defer tearDown() - if err := setUp(); err != nil { - t.Fatalf("Error preparing database: %q", err) - } - - mgr := New(db) - if err := mgr.Add(&revision1{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - sample := Sample{ - ID: 1, - Imel: "test@example.com", - Name: "Test Tester", - } - if err := meddler.Save(db, "samples", &sample); err != nil { - t.Fatalf("Can not save data: %q", err) - } -} - -func TestMigrateExistingCreateTable(t *testing.T) { - defer tearDown() - if err := setUp(); err != nil { - t.Fatalf("Error preparing database: %q", err) - } - - if _, err := db.Exec(testSchema); err != nil { - t.Fatalf("Can not create database: %q", err) - } - - mgr := New(db) - rev := &revision1{} - if err := mgr.Add(rev).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - var current int64 - db.QueryRow("SELECT max(revision) FROM migration").Scan(¤t) - if current != rev.Revision() { - t.Fatalf("Did not successfully migrate") - } -} - -func TestMigrateRenameTable(t *testing.T) { - defer tearDown() - if err := setUp(); err != nil { - t.Fatalf("Error preparing database: %q", err) - } - - mgr := New(db) - if err := mgr.Add(&revision1{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - loadFixture(t) - - if err := mgr.Add(&revision2{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - sample := Sample{} - if err := meddler.QueryRow(db, &sample, `SELECT * FROM examples WHERE id = ?`, 2); err != nil { - t.Fatalf("Can not fetch data: %q", err) - } - - if sample.Imel != "foo@bar.com" { - t.Errorf("Column doesn't match. Expect: %s, got: %s", "foo@bar.com", sample.Imel) - } -} - -type TableInfo struct { - CID int64 `meddler:"cid,pk"` - Name string `meddler:"name"` - Type string `meddler:"type"` - Notnull bool `meddler:"notnull"` - DfltValue interface{} `meddler:"dflt_value"` - PK bool `meddler:"pk"` -} - -func TestMigrateAddRemoveColumns(t *testing.T) { - defer tearDown() - if err := setUp(); err != nil { - t.Fatalf("Error preparing database: %q", err) - } - - mgr := New(db) - if err := mgr.Add(&revision1{}, &revision3{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - var columns []*TableInfo - if err := meddler.QueryAll(db, &columns, `PRAGMA table_info(samples);`); err != nil { - t.Fatalf("Can not access table info: %q", err) - } - - if len(columns) < 5 { - t.Errorf("Expect length columns: %d\nGot: %d", 5, len(columns)) - } - - var row = AddColumnSample{ - ID: 33, - Name: "Foo", - Imel: "foo@bar.com", - Url: "http://example.com", - Num: 42, - } - if err := meddler.Save(db, "samples", &row); err != nil { - t.Fatalf("Can not save into database: %q", err) - } - - if err := mgr.MigrateTo(1); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - var another_columns []*TableInfo - if err := meddler.QueryAll(db, &another_columns, `PRAGMA table_info(samples);`); err != nil { - t.Fatalf("Can not access table info: %q", err) - } - - if len(another_columns) != 3 { - t.Errorf("Expect length columns = %d, got: %d", 3, len(columns)) - } -} - -func TestRenameColumn(t *testing.T) { - defer tearDown() - if err := setUp(); err != nil { - t.Fatalf("Error preparing database: %q", err) - } - - mgr := New(db) - if err := mgr.Add(&revision1{}, &revision4{}).MigrateTo(1); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - loadFixture(t) - - if err := mgr.MigrateTo(4); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - row := RenameSample{} - if err := meddler.QueryRow(db, &row, `SELECT * FROM samples WHERE id = 3;`); err != nil { - t.Fatalf("Can not query database: %q", err) - } - - if row.Email != "crash@bandicoot.io" { - t.Errorf("Expect %s, got %s", "crash@bandicoot.io", row.Email) - } -} - -func TestMigrateExistingTable(t *testing.T) { - defer tearDown() - if err := setUp(); err != nil { - t.Fatalf("Error preparing database: %q", err) - } - - if _, err := db.Exec(testSchema); err != nil { - t.Fatalf("Can not create database: %q", err) - } - - loadFixture(t) - - mgr := New(db) - if err := mgr.Add(&revision4{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - var rows []*RenameSample - if err := meddler.QueryAll(db, &rows, `SELECT * from samples;`); err != nil { - t.Fatalf("Can not query database: %q", err) - } - - if len(rows) != 3 { - t.Errorf("Expect rows length = %d, got %d", 3, len(rows)) - } - - if rows[1].Email != "foo@bar.com" { - t.Errorf("Expect email = %s, got %s", "foo@bar.com", rows[1].Email) - } -} - -type sqliteMaster struct { - Sql interface{} `meddler:"sql"` -} - -func TestIndexOperations(t *testing.T) { - defer tearDown() - if err := setUp(); err != nil { - t.Fatalf("Error preparing database: %q", err) - } - - mgr := New(db) - - // Migrate, create index - if err := mgr.Add(&revision1{}, &revision3{}, &revision5{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - var esquel []*sqliteMaster - // Query sqlite_master, check if index is exists. - query := `SELECT sql FROM sqlite_master WHERE type='index' and tbl_name='samples'` - if err := meddler.QueryAll(db, &esquel, query); err != nil { - t.Fatalf("Can not find index: %q", err) - } - - indexStatement := `CREATE INDEX idx_samples_on_url_and_name ON samples (url, name)` - if string(esquel[1].Sql.([]byte)) != indexStatement { - t.Errorf("Can not find index, got: %q", esquel[1]) - } - - // Migrate, rename indexed columns - if err := mgr.Add(&revision6{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - var esquel1 []*sqliteMaster - if err := meddler.QueryAll(db, &esquel1, query); err != nil { - t.Fatalf("Can not find index: %q", err) - } - - indexStatement = `CREATE INDEX idx_samples_on_host_and_name ON samples (host, name)` - if string(esquel1[1].Sql.([]byte)) != indexStatement { - t.Errorf("Can not find index, got: %q", esquel1[1]) - } - - if err := mgr.Add(&revision7{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - var esquel2 []*sqliteMaster - if err := meddler.QueryAll(db, &esquel2, query); err != nil { - t.Fatalf("Can not find index: %q", err) - } - - if len(esquel2) != 1 { - t.Errorf("Expect row length equal to %d, got %d", 1, len(esquel2)) - } -} - -func TestColumnRedundancy(t *testing.T) { - defer tearDown() - if err := setUp(); err != nil { - t.Fatalf("Error preparing database: %q", err) - } - - migr := New(db) - if err := migr.Add(&revision1{}, &revision8{}, &revision9{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - var tableSql string - query := `SELECT sql FROM sqlite_master where type='table' and name='samples'` - if err := db.QueryRow(query).Scan(&tableSql); err != nil { - t.Fatalf("Can not query sqlite_master: %q", err) - } - - if !strings.Contains(tableSql, "repository ") { - t.Errorf("Expect column with name repository") - } -} - -func TestChangeColumnType(t *testing.T) { - defer tearDown() - if err := setUp(); err != nil { - t.Fatalf("Error preparing database: %q", err) - } - - migr := New(db) - if err := migr.Add(&revision1{}, &revision4{}, &revision10{}).Migrate(); err != nil { - t.Fatalf("Can not migrate: %q", err) - } - - var tableSql string - query := `SELECT sql FROM sqlite_master where type='table' and name='samples'` - if err := db.QueryRow(query).Scan(&tableSql); err != nil { - t.Fatalf("Can not query sqlite_master: %q", err) - } - - if !strings.Contains(tableSql, "email varchar(512) UNIQUE") { - t.Errorf("Expect email type to changed: %q", tableSql) - } -} - -func setUp() error { - var err error - Driver = SQLite - db, err = sql.Open("sqlite3", "migration_tests.sqlite") - return err -} - -func tearDown() { - db.Close() - os.Remove("migration_tests.sqlite") -} - -func loadFixture(t *testing.T) { - for _, sql := range dataDump { - if _, err := db.Exec(sql); err != nil { - t.Fatalf("Can not insert into database: %q", err) - } - } -} diff --git a/pkg/database/migrate/testing/migrate_test.go b/pkg/database/migrate/testing/migrate_test.go new file mode 100644 index 000000000..0dee1b98c --- /dev/null +++ b/pkg/database/migrate/testing/migrate_test.go @@ -0,0 +1,483 @@ +package migrate + +import ( + "database/sql" + "fmt" + "log" + "os" + "strings" + "testing" + + . "github.com/drone/drone/pkg/database/migrate" + + _ "github.com/go-sql-driver/mysql" + _ "github.com/mattn/go-sqlite3" + "github.com/russross/meddler" +) + +var ( + db *sql.DB + driver, dsn string + + dbname = "drone_test" +) + +var sqliteTestSchema = ` +CREATE TABLE samples ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + imel VARCHAR(255) UNIQUE, + name VARCHAR(255) +); +` + +var mysqlTestSchema = ` +CREATE TABLE samples ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + imel VARCHAR(255) UNIQUE, + name VARCHAR(255) +) +` + +var dataDump = []string{ + `INSERT INTO samples (imel, name) VALUES ('test@example.com', 'Test Tester');`, + `INSERT INTO samples (imel, name) VALUES ('foo@bar.com', 'Foo Bar');`, + `INSERT INTO samples (imel, name) VALUES ('crash@bandicoot.io', 'Crash Bandicoot');`, +} + +func TestMigrateCreateTable(t *testing.T) { + defer tearDown() + if err := setUp(); err != nil { + t.Fatalf("Error preparing database: %q", err) + } + + mgr := New(db) + if err := mgr.Add(&revision1{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + sample := Sample{ + ID: 1, + Imel: "test@example.com", + Name: "Test Tester", + } + if err := meddler.Save(db, "samples", &sample); err != nil { + t.Fatalf("Can not save data: %q", err) + } +} + +func TestMigrateExistingCreateTable(t *testing.T) { + defer tearDown() + if err := setUp(); err != nil { + t.Fatalf("Error preparing database: %q", err) + } + + var testSchema string + if driver == "mysql" { + testSchema = mysqlTestSchema + } else { + testSchema = sqliteTestSchema + } + + if _, err := db.Exec(testSchema); err != nil { + t.Fatalf("Can not create database: %q", err) + } + + mgr := New(db) + rev := &revision1{} + if err := mgr.Add(rev).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + var current int64 + db.QueryRow("SELECT max(revision) FROM migration").Scan(¤t) + if current != rev.Revision() { + t.Fatalf("Did not successfully migrate") + } +} + +func TestMigrateRenameTable(t *testing.T) { + defer tearDown() + if err := setUp(); err != nil { + t.Fatalf("Error preparing database: %q", err) + } + + mgr := New(db) + if err := mgr.Add(&revision1{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + loadFixture(t) + + if err := mgr.Add(&revision2{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + sample := Sample{} + if err := meddler.QueryRow(db, &sample, `SELECT * FROM examples WHERE id = ?`, 2); err != nil { + t.Fatalf("Can not fetch data: %q", err) + } + + if sample.Imel != "foo@bar.com" { + t.Errorf("Column doesn't match. Expect: %s, got: %s", "foo@bar.com", sample.Imel) + } +} + +type TableInfo struct { + CID int64 `meddler:"cid,pk"` + Name string `meddler:"name"` + Type string `meddler:"type"` + Notnull bool `meddler:"notnull"` + DfltValue interface{} `meddler:"dflt_value"` + PK bool `meddler:"pk"` +} + +type MysqlTableInfo struct { + Field string `meddler:"Field"` + Type string `meddler:"Type"` + Null string `meddler:"Null"` + Key interface{} `meddler:"Key"` + Default interface{} `meddler:"Default"` + Extra interface{} `meddler:"Extra"` +} + +func TestMigrateAddRemoveColumns(t *testing.T) { + defer tearDown() + if err := setUp(); err != nil { + t.Fatalf("Error preparing database: %q", err) + } + + mgr := New(db) + if err := mgr.Add(&revision1{}, &revision3{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + switch driver { + case "mysql": + var columns []*MysqlTableInfo + if err := meddler.QueryAll(db, &columns, `SHOW COLUMNS FROM samples`); err != nil { + t.Fatalf("Can not access table infor: %q", err) + } + + if len(columns) < 5 { + t.Errorf("Expect length columns: %d\nGot: %d", 5, len(columns)) + } + default: + var columns []*TableInfo + if err := meddler.QueryAll(db, &columns, `PRAGMA table_info(samples);`); err != nil { + t.Fatalf("Can not access table info: %q", err) + } + + if len(columns) < 5 { + t.Errorf("Expect length columns: %d\nGot: %d", 5, len(columns)) + } + } + + var row = AddColumnSample{ + ID: 33, + Name: "Foo", + Imel: "foo@bar.com", + Url: "http://example.com", + Num: 42, + } + if err := meddler.Save(db, "samples", &row); err != nil { + t.Fatalf("Can not save into database: %q", err) + } + + if err := mgr.MigrateTo(1); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + switch driver { + case "mysql": + var columns []*MysqlTableInfo + if err := meddler.QueryAll(db, &columns, `SHOW COLUMNS FROM samples`); err != nil { + t.Fatalf("Can not access table infor: %q", err) + } + + if len(columns) != 3 { + t.Errorf("Expect length columns: %d\nGot: %d", 3, len(columns)) + } + default: + var columns []*TableInfo + if err := meddler.QueryAll(db, &columns, `PRAGMA table_info(samples);`); err != nil { + t.Fatalf("Can not access table info: %q", err) + } + + if len(columns) != 3 { + t.Errorf("Expect length columns: %d\nGot: %d", 3, len(columns)) + } + } + +} + +func TestRenameColumn(t *testing.T) { + defer tearDown() + if err := setUp(); err != nil { + t.Fatalf("Error preparing database: %q", err) + } + + mgr := New(db) + if err := mgr.Add(&revision1{}, &revision4{}).MigrateTo(1); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + loadFixture(t) + + if err := mgr.MigrateTo(4); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + row := RenameSample{} + if err := meddler.QueryRow(db, &row, `SELECT * FROM samples WHERE id = 3;`); err != nil { + t.Fatalf("Can not query database: %q", err) + } + + if row.Email != "crash@bandicoot.io" { + t.Errorf("Expect %s, got %s", "crash@bandicoot.io", row.Email) + } +} + +func TestMigrateExistingTable(t *testing.T) { + defer tearDown() + if err := setUp(); err != nil { + t.Fatalf("Error preparing database: %q", err) + } + + var testSchema string + if driver == "mysql" { + testSchema = mysqlTestSchema + } else { + testSchema = sqliteTestSchema + } + + if _, err := db.Exec(testSchema); err != nil { + t.Fatalf("Can not create database: %q", err) + } + + loadFixture(t) + + mgr := New(db) + if err := mgr.Add(&revision4{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + var rows []*RenameSample + if err := meddler.QueryAll(db, &rows, `SELECT * from samples;`); err != nil { + t.Fatalf("Can not query database: %q", err) + } + + if len(rows) != 3 { + t.Errorf("Expect rows length = %d, got %d", 3, len(rows)) + } + + if rows[1].Email != "foo@bar.com" { + t.Errorf("Expect email = %s, got %s", "foo@bar.com", rows[1].Email) + } +} + +type sqliteMaster struct { + Sql interface{} `meddler:"sql"` +} + +func TestIndexOperations(t *testing.T) { + defer tearDown() + if err := setUp(); err != nil { + t.Fatalf("Error preparing database: %q", err) + } + + mgr := New(db) + + // Migrate, create index + if err := mgr.Add(&revision1{}, &revision3{}, &revision5{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + var esquel []*sqliteMaster + var mysquel struct { + Table string `meddler:"Table"` + CreateTable string `meddler:"Create Table"` + } + switch driver { + case "mysql": + query := `SHOW CREATE TABLE samples` + if err := meddler.QueryRow(db, &mysquel, query); err != nil { + t.Fatalf("Can not fetch table definition: %q", err) + } + + if !strings.Contains(mysquel.CreateTable, "KEY `idx_samples_on_url_and_name` (`url`,`name`)") { + t.Errorf("Can not find index, got: %q", mysquel.CreateTable) + } + + if err := mgr.Add(&revision6{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + if err := meddler.QueryRow(db, &mysquel, query); err != nil { + t.Fatalf("Can not find index: %q", err) + } + + if !strings.Contains(mysquel.CreateTable, "KEY `idx_samples_on_url_and_name` (`host`,`name`)") { + t.Errorf("Can not find index, got: %q", mysquel.CreateTable) + } + + if err := mgr.Add(&revision7{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + if err := meddler.QueryRow(db, &mysquel, query); err != nil { + t.Fatalf("Can not find index: %q", err) + } + + if strings.Contains(mysquel.CreateTable, "KEY `idx_samples_on_url_and_name` (`host`,`name`)") { + t.Errorf("Expect index to be deleted.") + } + + default: + // Query sqlite_master, check if index is exists. + query := `SELECT sql FROM sqlite_master WHERE type='index' and tbl_name='samples'` + if err := meddler.QueryAll(db, &esquel, query); err != nil { + t.Fatalf("Can not find index: %q", err) + } + + indexStatement := `CREATE INDEX idx_samples_on_url_and_name ON samples (url, name)` + if string(esquel[1].Sql.([]byte)) != indexStatement { + t.Errorf("Can not find index, got: %q", esquel[1]) + } + + // Migrate, rename indexed columns + if err := mgr.Add(&revision6{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + var esquel1 []*sqliteMaster + if err := meddler.QueryAll(db, &esquel1, query); err != nil { + t.Fatalf("Can not find index: %q", err) + } + + indexStatement = `CREATE INDEX idx_samples_on_host_and_name ON samples (host, name)` + if string(esquel1[1].Sql.([]byte)) != indexStatement { + t.Errorf("Can not find index, got: %q", esquel1[1]) + } + + if err := mgr.Add(&revision7{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + var esquel2 []*sqliteMaster + if err := meddler.QueryAll(db, &esquel2, query); err != nil { + t.Fatalf("Can not find index: %q", err) + } + + if len(esquel2) != 1 { + t.Errorf("Expect row length equal to %d, got %d", 1, len(esquel2)) + } + } +} + +func TestColumnRedundancy(t *testing.T) { + defer tearDown() + if err := setUp(); err != nil { + t.Fatalf("Error preparing database: %q", err) + } + + migr := New(db) + if err := migr.Add(&revision1{}, &revision8{}, &revision9{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + var dummy, query, tableSql string + switch driver { + case "mysql": + query = `SHOW CREATE TABLE samples` + if err := db.QueryRow(query).Scan(&dummy, &tableSql); err != nil { + t.Fatalf("Can not query table's definition: %q", err) + } + if !strings.Contains(tableSql, "`repository`") { + t.Errorf("Expect column with name repository") + } + default: + query = `SELECT sql FROM sqlite_master where type='table' and name='samples'` + if err := db.QueryRow(query).Scan(&tableSql); err != nil { + t.Fatalf("Can not query sqlite_master: %q", err) + } + if !strings.Contains(tableSql, "repository ") { + t.Errorf("Expect column with name repository") + } + } +} + +func TestChangeColumnType(t *testing.T) { + defer tearDown() + if err := setUp(); err != nil { + t.Fatalf("Error preparing database: %q", err) + } + + migr := New(db) + if err := migr.Add(&revision1{}, &revision4{}, &revision10{}).Migrate(); err != nil { + t.Fatalf("Can not migrate: %q", err) + } + + var dummy, tableSql, query string + switch driver { + case "mysql": + query = `SHOW CREATE TABLE samples` + if err := db.QueryRow(query).Scan(&dummy, &tableSql); err != nil { + t.Fatalf("Can not query table's definition: %q", err) + } + if !strings.Contains(tableSql, "`email` varchar(512)") { + t.Errorf("Expect email type to changed: %q", tableSql) + } + default: + query = `SELECT sql FROM sqlite_master where type='table' and name='samples'` + if err := db.QueryRow(query).Scan(&tableSql); err != nil { + t.Fatalf("Can not query sqlite_master: %q", err) + } + if !strings.Contains(tableSql, "email varchar(512) UNIQUE") { + t.Errorf("Expect email type to changed: %q", tableSql) + } + } +} + +func init() { + if driver = os.Getenv("DB_ENV"); len(driver) == 0 { + driver = "sqlite3" + } + if dsn = os.Getenv("MYSQL_LOGIN"); len(dsn) == 0 { + dsn = ":memory:" + } else { + dsn = fmt.Sprintf("%s@/?parseTime=true", dsn) + } +} + +func setUp() error { + var err error + Driver = SQLite + if db, err = sql.Open(driver, dsn); err != nil { + log.Fatalf("Can't connect to database: %q", err) + } + if driver == "mysql" { + Driver = MySQL + if _, err := db.Exec(fmt.Sprintf("CREATE DATABASE %s", dbname)); err != nil { + log.Fatalf("Can't create database: %q", err) + } + if _, err := db.Exec(fmt.Sprintf("USE %s", dbname)); err != nil { + log.Fatalf("Can't use database: %q", dbname) + } + } + return err +} + +func tearDown() { + if driver == "mysql" { + db.Exec(fmt.Sprintf("DROP DATABASE %s", dbname)) + } + db.Close() +} + +func loadFixture(t *testing.T) { + for _, sql := range dataDump { + if _, err := db.Exec(sql); err != nil { + t.Fatalf("Can not insert into database: %q", err) + } + } +} diff --git a/pkg/database/migrate/testing/revisions.go b/pkg/database/migrate/testing/revisions.go new file mode 100644 index 000000000..508af8437 --- /dev/null +++ b/pkg/database/migrate/testing/revisions.go @@ -0,0 +1,246 @@ +package migrate + +import ( + . "github.com/drone/drone/pkg/database/migrate" +) + +type Sample struct { + ID int64 `meddler:"id,pk"` + Imel string `meddler:"imel"` + Name string `meddler:"name"` +} + +type RenameSample struct { + ID int64 `meddler:"id,pk"` + Email string `meddler:"email"` + Name string `meddler:"name"` +} + +type AddColumnSample struct { + ID int64 `meddler:"id,pk"` + Imel string `meddler:"imel"` + Name string `meddler:"name"` + Url string `meddler:"url"` + Num int64 `meddler:"num"` +} + +// ---------- revision 1 + +type revision1 struct{} + +func (r *revision1) Up(mg *MigrationDriver) error { + _, err := mg.CreateTable("samples", []string{ + mg.T.Integer("id", PRIMARYKEY, AUTOINCREMENT), + mg.T.String("imel", UNIQUE), + mg.T.String("name"), + }) + return err +} + +func (r *revision1) Down(mg *MigrationDriver) error { + _, err := mg.DropTable("samples") + return err +} + +func (r *revision1) Revision() int64 { + return 1 +} + +// ---------- end of revision 1 + +// ---------- revision 2 + +type revision2 struct{} + +func (r *revision2) Up(mg *MigrationDriver) error { + _, err := mg.RenameTable("samples", "examples") + return err +} + +func (r *revision2) Down(mg *MigrationDriver) error { + _, err := mg.RenameTable("examples", "samples") + return err +} + +func (r *revision2) Revision() int64 { + return 2 +} + +// ---------- end of revision 2 + +// ---------- revision 3 + +type revision3 struct{} + +func (r *revision3) Up(mg *MigrationDriver) error { + if _, err := mg.AddColumn("samples", "url VARCHAR(255)"); err != nil { + return err + } + _, err := mg.AddColumn("samples", "num INTEGER") + return err +} + +func (r *revision3) Down(mg *MigrationDriver) error { + _, err := mg.DropColumns("samples", "num", "url") + return err +} + +func (r *revision3) Revision() int64 { + return 3 +} + +// ---------- end of revision 3 + +// ---------- revision 4 + +type revision4 struct{} + +func (r *revision4) Up(mg *MigrationDriver) error { + _, err := mg.RenameColumns("samples", map[string]string{ + "imel": "email", + }) + return err +} + +func (r *revision4) Down(mg *MigrationDriver) error { + _, err := mg.RenameColumns("samples", map[string]string{ + "email": "imel", + }) + return err +} + +func (r *revision4) Revision() int64 { + return 4 +} + +// ---------- end of revision 4 + +// ---------- revision 5 + +type revision5 struct{} + +func (r *revision5) Up(mg *MigrationDriver) error { + _, err := mg.AddIndex("samples", []string{"url", "name"}) + return err +} + +func (r *revision5) Down(mg *MigrationDriver) error { + _, err := mg.DropIndex("samples", []string{"url", "name"}) + return err +} + +func (r *revision5) Revision() int64 { + return 5 +} + +// ---------- end of revision 5 + +// ---------- revision 6 +type revision6 struct{} + +func (r *revision6) Up(mg *MigrationDriver) error { + _, err := mg.RenameColumns("samples", map[string]string{ + "url": "host", + }) + return err +} + +func (r *revision6) Down(mg *MigrationDriver) error { + _, err := mg.RenameColumns("samples", map[string]string{ + "host": "url", + }) + return err +} + +func (r *revision6) Revision() int64 { + return 6 +} + +// ---------- end of revision 6 + +// ---------- revision 7 +type revision7 struct{} + +func (r *revision7) Up(mg *MigrationDriver) error { + _, err := mg.DropColumns("samples", "host", "num") + return err +} + +func (r *revision7) Down(mg *MigrationDriver) error { + if _, err := mg.AddColumn("samples", "host VARCHAR(255)"); err != nil { + return err + } + _, err := mg.AddColumn("samples", "num INSTEGER") + return err +} + +func (r *revision7) Revision() int64 { + return 7 +} + +// ---------- end of revision 7 + +// ---------- revision 8 +type revision8 struct{} + +func (r *revision8) Up(mg *MigrationDriver) error { + if _, err := mg.AddColumn("samples", "repo_id INTEGER"); err != nil { + return err + } + _, err := mg.AddColumn("samples", "repo VARCHAR(255)") + return err +} + +func (r *revision8) Down(mg *MigrationDriver) error { + _, err := mg.DropColumns("samples", "repo", "repo_id") + return err +} + +func (r *revision8) Revision() int64 { + return 8 +} + +// ---------- end of revision 8 + +// ---------- revision 9 +type revision9 struct{} + +func (r *revision9) Up(mg *MigrationDriver) error { + _, err := mg.RenameColumns("samples", map[string]string{ + "repo": "repository", + }) + return err +} + +func (r *revision9) Down(mg *MigrationDriver) error { + _, err := mg.RenameColumns("samples", map[string]string{ + "repository": "repo", + }) + return err +} + +func (r *revision9) Revision() int64 { + return 9 +} + +// ---------- end of revision 9 + +// ---------- revision 10 + +type revision10 struct{} + +func (r *revision10) Revision() int64 { + return 10 +} + +func (r *revision10) Up(mg *MigrationDriver) error { + _, err := mg.ChangeColumn("samples", "email", "varchar(512) UNIQUE") + return err +} + +func (r *revision10) Down(mg *MigrationDriver) error { + _, err := mg.ChangeColumn("samples", "email", "varchar(255) unique") + return err +} + +// ---------- end of revision 10 diff --git a/pkg/database/testing/repos_test.go b/pkg/database/testing/repos_test.go index a6420184b..515b99b3b 100644 --- a/pkg/database/testing/repos_test.go +++ b/pkg/database/testing/repos_test.go @@ -63,11 +63,11 @@ func TestGetRepo(t *testing.T) { t.Errorf("Exepected Password %s, got %s", "no password", repo.Password) } - if repo.PublicKey != "public key" { + if repo.PublicKey != pubkey { t.Errorf("Exepected PublicKey %s, got %s", "public key", repo.PublicKey) } - if repo.PrivateKey != "private key" { + if repo.PrivateKey != privkey { t.Errorf("Exepected PrivateKey %s, got %s", "private key", repo.PrivateKey) } @@ -135,11 +135,11 @@ func TestGetRepoSlug(t *testing.T) { t.Errorf("Exepected Password %s, got %s", "no password", repo.Password) } - if repo.PublicKey != "public key" { + if repo.PublicKey != pubkey { t.Errorf("Exepected PublicKey %s, got %s", "public key", repo.PublicKey) } - if repo.PrivateKey != "private key" { + if repo.PrivateKey != privkey { t.Errorf("Exepected PrivateKey %s, got %s", "private key", repo.PrivateKey) } @@ -385,11 +385,11 @@ func TestListReposTeam(t *testing.T) { t.Errorf("Exepected Password %s, got %s", "no password", repo.Password) } - if repo.PublicKey != "public key" { + if repo.PublicKey != pubkey { t.Errorf("Exepected PublicKey %s, got %s", "public key", repo.PublicKey) } - if repo.PrivateKey != "private key" { + if repo.PrivateKey != privkey { t.Errorf("Exepected PrivateKey %s, got %s", "private key", repo.PrivateKey) } diff --git a/pkg/database/testing/testing.go b/pkg/database/testing/testing.go index 27d6b6bf5..59bd3263a 100644 --- a/pkg/database/testing/testing.go +++ b/pkg/database/testing/testing.go @@ -2,7 +2,11 @@ package database import ( "crypto/aes" + "database/sql" + "fmt" "log" + "os" + "strings" "github.com/drone/drone/pkg/database" "github.com/drone/drone/pkg/database/encrypt" @@ -12,6 +16,42 @@ import ( "github.com/russross/meddler" ) +const ( + pubkey = `sh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQClp9+xjhYj2Wz0nwLNhUiR1RkqfoVZwlJoxdubQy8GskZtY7C7YGa/PeKfdfaKOWtVgg37r/OYS3kc7bIKVup4sx/oW59FMwCZYQ2nxoaPZpPwUJs8D0Wy0b2VSP+vAnJ6jZQEIEiClrzyYafSfqN6L9T/BTkn28ktWalOHqWVKejKeD6M0uhlpyIZFsQ1K2wNt32ACwT/rbanx/r/jfczqxSkLzvIKXXs/RdKQgwRRUYnKkl4Lh6r22n9n3m2VwRor5wdsPK8sr57OsqdRpnvsFs3lxwM8w5ZiAZV3T0xTMGVs3W8Uy5HexAD6TgWBWFjSrgdXF1pE83wmUtJtVBf` + privkey = `-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEApaffsY4WI9ls9J8CzYVIkdUZKn6FWcJSaMXbm0MvBrJGbWOw +u2Bmvz3in3X2ijlrVYIN+6/zmEt5HO2yClbqeLMf6FufRTMAmWENp8aGj2aT8FCb +PA9FstG9lUj/rwJyeo2UBCBIgpa88mGn0n6jei/U/wU5J9vJLVmpTh6llSnoyng+ +jNLoZaciGRbENStsDbd9gAsE/622p8f6/433M6sUpC87yCl17P0XSkIMEUVGJypJ +eC4eq9tp/Z95tlcEaK+cHbDyvLK+ezrKnUaZ77BbN5ccDPMOWYgGVd09MUzBlbN1 +vFMuR3sQA+k4FgVhY0q4HVxdaRPN8JlLSbVQXwIDAQABAoIBAA3EqSPxwkdSf+rI ++IuqY0CzrHbKszylmQHaSAlciSEOWionWf4I4iFM/HPycv5EDXa663yawC1NQJC1 +9NFFLhHAGYvPaapvtcIJvf/O0UpD5VHY8T4JqupU4mPxAEdEdc1XzRCWulAYRTYE +BdXJ7r5uEU7s2TZF3y+kvxyeEXcXNWK1I4kGBSgH4KI5WIODtNJ6vaIk5Yugqt1N +cg5Sprk4bUMRTBH6GmSiJUleA0f/k6MCCmhETKXGt9mmfJ1PXpVlfDn5m26MX6vZ +XgaoIHUCy4sh1Fq6vbEI831JcO4kdvl4TtX90SzSadHjewNHy0V2gjAysvqbEDhw +Hn8D+MkCgYEA00tTKPp3AUTxT9ZgfPBD3DY7tk7+xp2R2lA6H9TUWe79TRfncFtS +8bCfEXd8xALL5cFyzi4q4YJ77mJjlWE7AMYdsFoAW1l3Q71JRRBSwsyIwp4hU8AV +K48SDjqecDzY42UvuKGp3opPWb0PzJixJNUgawU/ZGPxqN8jlr0o+K0CgYEAyLSO +rZqOvyE5wu8yadHLlQC4usoYtnyDC8PG2SgnZnlnZnkgNy3yLmHYvTvYSQsAv7rA +fFsKMt2MJhlclx+sTds/LLHKj/RfVDFenFf6ajBNZ1k+KRcwrV1A4iWinWmBxiEi +A8aM9rGs7WRBkqaCONSUQHcmLRRz7hqDtsBpkrsCgYBY2FJ2Z6LEmN2zCVx3DHws +S22eQeclUroyhwt5uP81daVy1jtN5kihMfgg2xJORTLBQC9q/MSxIDHGUf63oDO0 +JpnzPlTqFFtu01fMv4ldOa3Dz8QJuDnun/EipIlcfmlgbHq9ctS/q36kKDhNemL6 +Lte7yHAYYWIK9RC84Hsq3QKBgAfDbC1s6A6ek2Rl6jZLpitKTtryzEfqwwrmdL+b +nQKKuaQuFT/tKAwBPuf685/HrCy+ZYmp39gd17j1jC5QTFLqoyPwcJxm4HUaP8We +ZZJL8gKIYi4mtnxOOh9FQ2gBV8K5L16kBHnaX40DLsIkbK8UEfP4Z+Kggud34RZl +lO/XAoGAFFZdolsVbSieFhJt7ypzp/19dKJ8Sk6QGCk3uQpTuLJMvwcBT8X5XCTD +zFfYARarx87mbD2k5GZ7F0fmGYTUV14qlxJCGMythLM/xZ6EJuJWBz69puNj4yhn +exWM7t1BDHy2zIoPfIQLDH2h1zNTRjismMeErOCy0Uha7jrZhW8= +-----END RSA PRIVATE KEY-----` +) + +var ( + dbname, driver, dsn, login string + db *sql.DB +) + func init() { // create a cipher for ecnrypting and decrypting // database fields @@ -24,11 +64,31 @@ func init() { // decrypt database fields. meddler.Register("gobencrypt", &encrypt.EncryptedField{cipher}) + // Check for $DB_ENV + dbenv := os.Getenv("DB_ENV") + if dbenv == "mysql" { + driver = dbenv + dbname = "drone_test" + login = os.Getenv("MYSQL_LOGIN") + if len(login) == 0 { + login = "root" + } + log.Println("Using mysql database ...") + } else { + driver = "sqlite3" + dsn = ":memory:" + log.Println("Using sqlite3 database ...") + } + } func Setup() { // create an in-memory database - database.Init("sqlite3", ":memory:") + if driver == "mysql" { + idsn := fmt.Sprintf("%s@/?parseTime=true", login) + db, dsn = createDB(dbname, idsn) + } + database.Init(driver, dsn) // create dummy user data user1 := User{ @@ -102,8 +162,8 @@ func Setup() { URL: "git@github.com:drone/drone.git", Username: "no username", Password: "no password", - PublicKey: "public key", - PrivateKey: "private key", + PublicKey: pubkey, + PrivateKey: privkey, UserID: user1.ID, TeamID: team1.ID, } @@ -118,8 +178,8 @@ func Setup() { URL: "https://bitbucket.org/drone/test", Username: "no username", Password: "no password", - PublicKey: "public key", - PrivateKey: "private key", + PublicKey: pubkey, + PrivateKey: privkey, UserID: user1.ID, TeamID: team1.ID, } @@ -134,8 +194,8 @@ func Setup() { URL: "https://bitbucket.org/brydzewski/test", Username: "no username", Password: "no password", - PublicKey: "public key", - PrivateKey: "private key", + PublicKey: pubkey, + PrivateKey: privkey, UserID: user2.ID, } @@ -207,4 +267,19 @@ func Setup() { func Teardown() { database.Close() + if driver == "mysql" { + db.Exec(fmt.Sprintf("DROP DATABASE %s", dbname)) + } +} + +func createDB(name, datasource string) (*sql.DB, string) { + db, err := sql.Open(driver, datasource) + if err != nil { + panic("Can't connect to database") + } + if _, err := db.Exec(fmt.Sprintf("CREATE DATABASE %s", name)); err != nil { + panic("Can't create database") + } + dsn := strings.Replace(datasource, "/", fmt.Sprintf("/%s", name), 1) + return db, dsn }