diff --git a/datastore/datastore.go b/datastore/datastore.go index 1438ee3e0..0fd1b2a83 100644 --- a/datastore/datastore.go +++ b/datastore/datastore.go @@ -1,14 +1,15 @@ package datastore import ( + "errors" "io" "github.com/drone/drone/common" ) var ( - ErrConflict = "Key not unique" - ErrNotFound = "Key not found" + ErrConflict = errors.New("Key not unique") + ErrKeyNotFound = errors.New("Key not found") ) type Datastore interface { diff --git a/mocks/Datastore.go b/mocks/Datastore.go new file mode 100644 index 000000000..4d05f93db --- /dev/null +++ b/mocks/Datastore.go @@ -0,0 +1,304 @@ +package mocks + +import "github.com/stretchr/testify/mock" + +import "io" +import "github.com/drone/drone/common" + +type Datastore struct { + mock.Mock +} + +func (m *Datastore) User(_a0 string) (*common.User, error) { + ret := m.Called(_a0) + + var r0 *common.User + if ret.Get(0) != nil { + r0 = ret.Get(0).(*common.User) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) UserCount() (int, error) { + ret := m.Called() + + r0 := ret.Get(0).(int) + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) UserList() ([]*common.User, error) { + ret := m.Called() + + var r0 []*common.User + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*common.User) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) SetUser(_a0 *common.User) error { + ret := m.Called(_a0) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) SetUserNotExists(_a0 *common.User) error { + ret := m.Called(_a0) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) DelUser(_a0 *common.User) error { + ret := m.Called(_a0) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) Token(_a0 string, _a1 string) (*common.Token, error) { + ret := m.Called(_a0, _a1) + + var r0 *common.Token + if ret.Get(0) != nil { + r0 = ret.Get(0).(*common.Token) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) TokenList(_a0 string) ([]*common.Token, error) { + ret := m.Called(_a0) + + var r0 []*common.Token + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*common.Token) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) SetToken(_a0 *common.Token) error { + ret := m.Called(_a0) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) DelToken(_a0 *common.Token) error { + ret := m.Called(_a0) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) Subscribed(_a0 string, _a1 string) (bool, error) { + ret := m.Called(_a0, _a1) + + r0 := ret.Get(0).(bool) + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) SetSubscriber(_a0 string, _a1 string) error { + ret := m.Called(_a0, _a1) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) DelSubscriber(_a0 string, _a1 string) error { + ret := m.Called(_a0, _a1) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) Repo(_a0 string) (*common.Repo, error) { + ret := m.Called(_a0) + + var r0 *common.Repo + if ret.Get(0) != nil { + r0 = ret.Get(0).(*common.Repo) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) RepoList(_a0 string) ([]*common.Repo, error) { + ret := m.Called(_a0) + + var r0 []*common.Repo + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*common.Repo) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) RepoParams(_a0 string) (map[string]string, error) { + ret := m.Called(_a0) + + var r0 map[string]string + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]string) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) RepoKeypair(_a0 string) (*common.Keypair, error) { + ret := m.Called(_a0) + + var r0 *common.Keypair + if ret.Get(0) != nil { + r0 = ret.Get(0).(*common.Keypair) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) SetRepo(_a0 *common.Repo) error { + ret := m.Called(_a0) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) SetRepoNotExists(_a0 *common.User, _a1 *common.Repo) error { + ret := m.Called(_a0, _a1) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) SetRepoParams(_a0 string, _a1 map[string]string) error { + ret := m.Called(_a0, _a1) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) SetRepoKeypair(_a0 string, _a1 *common.Keypair) error { + ret := m.Called(_a0, _a1) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) DelRepo(_a0 *common.Repo) error { + ret := m.Called(_a0) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) Build(_a0 string, _a1 int) (*common.Build, error) { + ret := m.Called(_a0, _a1) + + var r0 *common.Build + if ret.Get(0) != nil { + r0 = ret.Get(0).(*common.Build) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) BuildList(_a0 string) ([]*common.Build, error) { + ret := m.Called(_a0) + + var r0 []*common.Build + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*common.Build) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) BuildLast(_a0 string) (*common.Build, error) { + ret := m.Called(_a0) + + var r0 *common.Build + if ret.Get(0) != nil { + r0 = ret.Get(0).(*common.Build) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) SetBuild(_a0 string, _a1 *common.Build) error { + ret := m.Called(_a0, _a1) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) Status(_a0 string, _a1 int, _a2 string) (*common.Status, error) { + ret := m.Called(_a0, _a1, _a2) + + var r0 *common.Status + if ret.Get(0) != nil { + r0 = ret.Get(0).(*common.Status) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) StatusList(_a0 string, _a1 int) ([]*common.Status, error) { + ret := m.Called(_a0, _a1) + + var r0 []*common.Status + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*common.Status) + } + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) SetStatus(_a0 string, _a1 int, _a2 *common.Status) error { + ret := m.Called(_a0, _a1, _a2) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) LogReader(_a0 string, _a1 int, _a2 int) (io.Reader, error) { + ret := m.Called(_a0, _a1, _a2) + + r0 := ret.Get(0).(io.Reader) + r1 := ret.Error(1) + + return r0, r1 +} +func (m *Datastore) SetLogs(_a0 string, _a1 int, _a2 int, _a3 []byte) error { + ret := m.Called(_a0, _a1, _a2, _a3) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) SetBuildState(_a0 string, _a1 *common.Build) error { + ret := m.Called(_a0, _a1) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) SetBuildStatus(_a0 string, _a1 int, _a2 *common.Status) error { + ret := m.Called(_a0, _a1, _a2) + + r0 := ret.Error(0) + + return r0 +} +func (m *Datastore) SetBuildTask(_a0 string, _a1 int, _a2 *common.Task) error { + ret := m.Called(_a0, _a1, _a2) + + r0 := ret.Error(0) + + return r0 +} diff --git a/server/badge_test.go b/server/badge_test.go new file mode 100644 index 000000000..b498218a2 --- /dev/null +++ b/server/badge_test.go @@ -0,0 +1,144 @@ +package server + +import ( + "encoding/xml" + "net/http" + "net/url" + "strings" + "testing" + "time" + + "github.com/drone/drone/common" + "github.com/drone/drone/common/ccmenu" + "github.com/drone/drone/datastore" + "github.com/drone/drone/mocks" + . "github.com/franela/goblin" + "github.com/gin-gonic/gin" +) + +func TestBadge(t *testing.T) { + g := Goblin(t) + g.Describe("Badge", func() { + var ctx gin.Context + owner := "Freya" + name := "Hello-World" + fullName := owner + "/" + name + repo := &common.Repo{Owner: owner, Name: name, FullName: fullName} + g.BeforeEach(func() { + ctx = gin.Context{Engine: gin.Default()} + url, _ := url.Parse("http://drone.local/badges/" + fullName) + ctx.Request = &http.Request{URL: url} + ctx.Set("repo", repo) + }) + + g.AfterEach(func() { + }) + + cycleStateTester := func(expector gin.HandlerFunc, handle gin.HandlerFunc, validator func(state string, w *ResponseRecorder)) { + for idx, state := range []string{"", common.StateError, common.StateFailure, common.StateKilled, common.StatePending, common.StateRunning, common.StateSuccess} { + w := NewResponseRecorder() + ctx.Writer = w + + repo.Last = &common.Build{ + Started: time.Now().UTC().Unix(), + Finished: time.Now().UTC().Unix(), + Number: idx, + State: state, + } + ctx.Set("repo", repo) + + if expector != nil { + expector(&ctx) + } + + handle(&ctx) + + validator(state, w) + } + } + + g.It("should provide SVG response", func() { + { + // 1. verify no "last" build + w := NewResponseRecorder() + ctx.Writer = w + ctx.Request.URL.Path += "/status.svg" + + GetBadge(&ctx) + + g.Assert(w.Status()).Equal(200) + g.Assert(w.HeaderMap.Get("content-type")).Equal("image/svg+xml") + g.Assert(strings.Contains(w.Body.String(), ">none")).IsTrue() + } + + // 2. verify a variety of "last" build states + cycleStateTester(nil, GetBadge, func(state string, w *ResponseRecorder) { + g.Assert(w.Status()).Equal(200) + g.Assert(w.HeaderMap.Get("content-type")).Equal("image/svg+xml") + + // this may be excessive, but does effectively verify behavior + switch state { + case common.StateSuccess: + g.Assert(strings.Contains(w.Body.String(), ">success")).IsTrue() + case common.StatePending, common.StateRunning: + g.Assert(strings.Contains(w.Body.String(), ">started")).IsTrue() + case common.StateError, common.StateKilled: + g.Assert(strings.Contains(w.Body.String(), ">error")).IsTrue() + case common.StateFailure: + g.Assert(strings.Contains(w.Body.String(), ">failure")).IsTrue() + default: + g.Assert(strings.Contains(w.Body.String(), ">none")).IsTrue() + } + }) + }) + + g.It("should provide CCTray response", func() { + { + // 1. verify no "last" build + w := NewResponseRecorder() + ctx.Writer = w + ctx.Request.URL.Path += "/cc.xml" + + ds := new(mocks.Datastore) + ctx.Set("datastore", ds) + + ds.On("BuildLast", fullName).Return(nil, datastore.ErrKeyNotFound).Once() + GetCC(&ctx) + + g.Assert(w.Status()).Equal(404) + } + + // 2. verify a variety of "last" build states + cycleStateTester(func(c *gin.Context) { + repo := ToRepo(c) + ds := new(mocks.Datastore) + ctx.Set("datastore", ds) + ds.On("BuildLast", fullName).Return(repo.Last, nil).Once() + }, + GetCC, + func(state string, w *ResponseRecorder) { + g.Assert(w.Status()).Equal(200) + + v := ccmenu.CCProjects{} + xml.Unmarshal(w.Body.Bytes(), &v) + switch state { + case common.StateSuccess: + g.Assert(v.Project.Activity).Equal("Sleeping") + g.Assert(v.Project.LastBuildStatus).Equal("Success") + case common.StatePending, common.StateRunning: + g.Assert(v.Project.Activity).Equal("Building") + g.Assert(v.Project.LastBuildStatus).Equal("Unknown") + case common.StateError, common.StateKilled: + g.Assert(v.Project.Activity).Equal("Sleeping") + g.Assert(v.Project.LastBuildStatus).Equal("Exception") + case common.StateFailure: + g.Assert(v.Project.Activity).Equal("Sleeping") + g.Assert(v.Project.LastBuildStatus).Equal("Failure") + default: + g.Assert(v.Project.Activity).Equal("Sleeping") + g.Assert(v.Project.LastBuildStatus).Equal("Unknown") + } + }) + }) + }) +} diff --git a/server/responserecorder_test.go b/server/responserecorder_test.go new file mode 100644 index 000000000..1ab5e1a9b --- /dev/null +++ b/server/responserecorder_test.go @@ -0,0 +1,33 @@ +package server + +import ( + "bufio" + "net" + "net/http" + "net/http/httptest" +) + +type ResponseRecorder struct { + *httptest.ResponseRecorder +} + +func NewResponseRecorder() *ResponseRecorder { + return &ResponseRecorder{httptest.NewRecorder()} +} + +func (rr *ResponseRecorder) reset() { + rr.ResponseRecorder = httptest.NewRecorder() +} + +func (rr *ResponseRecorder) CloseNotify() <-chan bool { + return http.ResponseWriter(rr).(http.CloseNotifier).CloseNotify() +} + +func (rr *ResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return http.ResponseWriter(rr).(http.Hijacker).Hijack() +} + +func (rr *ResponseRecorder) Size() int { return rr.Body.Len() } +func (rr *ResponseRecorder) Status() int { return rr.Code } +func (rr *ResponseRecorder) WriteHeaderNow() {} +func (rr *ResponseRecorder) Written() bool { return rr.Code != 0 }