diff --git a/.gitignore b/.gitignore index 956ba27ba..dd54dc627 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ deb/drone/usr/local/bin/drone deb/drone/usr/local/bin/droned .vagrant +*.out \ No newline at end of file diff --git a/pkg/build/build.go b/pkg/build/build.go index 65f3a73eb..8b39698b1 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -474,7 +474,7 @@ func (b *Builder) writeBuildScript(dir string) error { f.WriteEnv("DRONE_PR", b.Repo.PR) f.WriteEnv("DRONE_BUILD_DIR", b.Repo.Dir) - // add /etc/hosts entries + // add /etc/hosts entries for _, mapping := range b.Build.Hosts { f.WriteHost(mapping) } diff --git a/pkg/build/build_test.go b/pkg/build/build_test.go new file mode 100644 index 000000000..34f2f3c50 --- /dev/null +++ b/pkg/build/build_test.go @@ -0,0 +1,380 @@ +package build + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/drone/drone/pkg/build/buildfile" + "github.com/drone/drone/pkg/build/docker" + "github.com/drone/drone/pkg/build/proxy" + "github.com/drone/drone/pkg/build/repo" + "github.com/drone/drone/pkg/build/script" +) + +var ( + // mux is the HTTP request multiplexer used with the test server. + mux *http.ServeMux + + // server is a test HTTP server used to provide mock API responses. + server *httptest.Server + + // docker client + client *docker.Client +) + +// setup a mock docker client for testing purposes. This will use +// a test http server that can return mock responses to the docker client. +func setup() { + mux = http.NewServeMux() + server = httptest.NewServer(mux) + + url, _ := url.Parse(server.URL) + url.Scheme = "tcp" + os.Setenv("DOCKER_HOST", url.String()) + client = docker.New() +} + +func teardown() { + server.Close() +} + +// TestSetup will test our ability to successfully create a Docker +// image for the build. +func TestSetup(t *testing.T) { + setup() + defer teardown() + + // Handles a request to inspect the Go 1.2 image + // This will return a dummy image ID, so that the system knows + // the build image exists, and doens't need to be downloaded. + mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) { + body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]` + w.Write([]byte(body)) + }) + + // Handles a request to create the build image, with the build + // script injected. This will return a dummy stream. + mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) { + body := `{"stream":"Step 1..."}` + w.Write([]byte(body)) + }) + + // Handles a request to inspect the newly created build image. Note + // that we are doing a "wildcard" url match here, since the name of + // the image will be random. This will return a dummy image ID + // to confirm the build image was created successfully. + mux.HandleFunc("/v1.9/images/", func(w http.ResponseWriter, r *http.Request) { + body := `{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }` + w.Write([]byte(body)) + }) + + b := Builder{} + b.Repo = &repo.Repo{} + b.Repo.Path = "git://github.com/drone/drone.git" + b.Build = &script.Build{} + b.Build.Image = "go1.2" + b.dockerClient = client + + if err := b.setup(); err != nil { + t.Errorf("Expected success, got %s", err) + } + + // verify the Image is being correctly set + if b.image == nil { + t.Errorf("Expected image not nil") + } + + expectedID := "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" + if b.image.ID != expectedID { + t.Errorf("Expected image.ID %s, got %s", expectedID, b.image.ID) + } +} + +// TestSetupEmptyImage will test our ability to handle a nil or +// blank Docker build image. We expect this to return an error. +func TestSetupEmptyImage(t *testing.T) { + b := Builder{Build: &script.Build{}} + var got, want = b.setup(), "Error: missing Docker image" + + if got == nil || got.Error() != want { + t.Errorf("Expected error %s, got %s", want, got) + } +} + +// TestSetupUnknownService will test our ability to handle an +// unknown or unsupported service (i.e. mysql). +func TestSetupUnknownService(t *testing.T) { + b := Builder{} + b.Repo = &repo.Repo{} + b.Repo.Path = "git://github.com/drone/drone.git" + b.Build = &script.Build{} + b.Build.Image = "go1.2" + b.Build.Services = append(b.Build.Services, "not-found") + + var got, want = b.setup(), "Error: Invalid or unknown service not-found" + if got == nil || got.Error() != want { + t.Errorf("Expected error %s, got %s", want, got) + } +} + +// TestSetupErrorRunDaemonPorts will test our ability to handle a +// failure when starting a service (i.e. mysql) as a daemon. +func TestSetupErrorRunDaemonPorts(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + }) + + b := Builder{} + b.Repo = &repo.Repo{} + b.Repo.Path = "git://github.com/drone/drone.git" + b.Build = &script.Build{} + b.Build.Image = "go1.2" + b.Build.Services = append(b.Build.Services, "mysql") + b.dockerClient = client + + var got, want = b.setup(), docker.ErrBadRequest + if got == nil || got != want { + t.Errorf("Expected error %s, got %s", want, got) + } +} + +// TestSetupErrorServiceInspect will test our ability to handle a +// failure when a service (i.e. mysql) is started successfully, +// but cannot be queried post-start with the Docker remote API. +func TestSetupErrorServiceInspect(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) { + body := `{ "Id":"e90e34656806", "Warnings":[] }` + w.Write([]byte(body)) + }) + + mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + }) + + mux.HandleFunc("/v1.9/containers/e90e34656806/json", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + }) + + b := Builder{} + b.Repo = &repo.Repo{} + b.Repo.Path = "git://github.com/drone/drone.git" + b.Build = &script.Build{} + b.Build.Image = "go1.2" + b.Build.Services = append(b.Build.Services, "mysql") + b.dockerClient = client + + var got, want = b.setup(), docker.ErrBadRequest + if got == nil || got != want { + t.Errorf("Expected error %s, got %s", want, got) + } +} + +// TestSetupErrorImagePull will test our ability to handle a +// failure when a the build image cannot be pulled from the index. +func TestSetupErrorImagePull(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + }) + + mux.HandleFunc("/v1.9/images/create", func(w http.ResponseWriter, r *http.Request) { + // validate ?fromImage=bradrydzewski/go&tag=1.2 + w.WriteHeader(http.StatusBadRequest) + }) + + b := Builder{} + b.Repo = &repo.Repo{} + b.Repo.Path = "git://github.com/drone/drone.git" + b.Build = &script.Build{} + b.Build.Image = "go1.2" + b.dockerClient = client + + var got, want = b.setup(), docker.ErrBadRequest + if got == nil || got != want { + t.Errorf("Expected error %s, got %s", want, got) + } +} + +// TestSetupErrorBuild will test our ability to handle a failure +// when creating a Docker image with the injected build script, +// ssh keys, etc. +func TestSetupErrorBuild(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) { + body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]` + w.Write([]byte(body)) + }) + + mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + }) + + b := Builder{} + b.Repo = &repo.Repo{} + b.Repo.Path = "git://github.com/drone/drone.git" + b.Build = &script.Build{} + b.Build.Image = "go1.2" + b.dockerClient = client + + var got, want = b.setup(), docker.ErrBadRequest + if got == nil || got != want { + t.Errorf("Expected error %s, got %s", want, got) + } +} + +// TestSetupErrorBuildInspect will test our ability to handle a failure +// when we successfully create a Docker image with the injected build script, +// ssh keys, etc, however, we cannot inspect it post-creation using +// the Docker remote API. +func TestSetupErrorBuildInspect(t *testing.T) { + setup() + defer teardown() + + mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) { + body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]` + w.Write([]byte(body)) + }) + + mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) { + body := `{"stream":"Step 1..."}` + w.Write([]byte(body)) + }) + + mux.HandleFunc("/v1.9/images/", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + }) + + b := Builder{} + b.Repo = &repo.Repo{} + b.Repo.Path = "git://github.com/drone/drone.git" + b.Build = &script.Build{} + b.Build.Image = "go1.2" + b.dockerClient = client + + var got, want = b.setup(), docker.ErrBadRequest + if got == nil || got != want { + t.Errorf("Expected error %s, got %s", want, got) + } +} + +// TestTeardown will test our ability to sucessfully teardown a +// Docker-based build environment. +func TestTeardown(t *testing.T) {} + +// TestTeardownContainerFail will test our ability to handle a +// failure to stop and remove the build container. +func TestTeardownContainerFail(t *testing.T) {} + +// TestTeardownImageFail will test our ability to handle a +// failure to stop and remove the build image. +func TestTeardownImageFail(t *testing.T) {} + +func TestWriteIdentifyFile(t *testing.T) { + // temporary directory to store file + dir, _ := ioutil.TempDir("", "drone-test-") + defer os.RemoveAll(dir) + + b := Builder{} + b.Key = []byte("ssh-rsa AAA...") + b.writeIdentifyFile(dir) + + // persist a dummy id_rsa keyfile to disk + keyfile, err := ioutil.ReadFile(filepath.Join(dir, "id_rsa")) + if err != nil { + t.Errorf("Expected id_rsa file saved to disk") + } + + if string(keyfile) != string(b.Key) { + t.Errorf("Expected id_rsa value saved as %s, got %s", b.Key, keyfile) + } +} + +func TestWriteProxyScript(t *testing.T) { + // temporary directory to store file + dir, _ := ioutil.TempDir("", "drone-test-") + defer os.RemoveAll(dir) + + // fake service container that we'll assume was part of the yaml + // and should be attached to the build container. + c := docker.Container{ + NetworkSettings: &docker.NetworkSettings{ + IPAddress: "172.1.4.5", + Ports: map[docker.Port][]docker.PortBinding{ + docker.NewPort("tcp", "3306"): nil, + }, + }, + } + + // this should generate the following proxy file + p := proxy.Proxy{} + p.Set("3306", "172.1.4.5") + want := p.String() + + b := Builder{} + b.services = append(b.services, &c) + b.writeProxyScript(dir) + + // persist a dummy proxy script to disk + got, err := ioutil.ReadFile(filepath.Join(dir, "proxy.sh")) + if err != nil { + t.Errorf("Expected proxy.sh file saved to disk") + } + + if string(got) != want { + t.Errorf("Expected proxy.sh value saved as %s, got %s", want, got) + } +} + +func TestWriteBuildScript(t *testing.T) { + // temporary directory to store file + dir, _ := ioutil.TempDir("", "drone-test-") + defer os.RemoveAll(dir) + + b := Builder{} + b.Build = &script.Build{ + Hosts: []string{"127.0.0.1"}} + b.Repo = &repo.Repo{ + Path: "git://github.com/drone/drone.git", + Branch: "master", + Commit: "e7e046b35", + PR: "123", + Dir: "/var/cache/drone/github.com/drone/drone"} + b.writeBuildScript(dir) + + // persist a dummy build script to disk + script, err := ioutil.ReadFile(filepath.Join(dir, "drone")) + if err != nil { + t.Errorf("Expected id_rsa file saved to disk") + } + + f := buildfile.New() + f.WriteEnv("CI", "true") + f.WriteEnv("DRONE", "true") + f.WriteEnv("DRONE_BRANCH", "master") + f.WriteEnv("DRONE_COMMIT", "e7e046b35") + f.WriteEnv("DRONE_PR", "123") + f.WriteEnv("DRONE_BUILD_DIR", "/var/cache/drone/github.com/drone/drone") + f.WriteHost("127.0.0.1") + f.WriteCmd("git clone --depth=0 --recursive --branch=master git://github.com/drone/drone.git /var/cache/drone/github.com/drone/drone") + f.WriteCmd("git fetch origin +refs/pull/123/head:refs/remotes/origin/pr/123") + f.WriteCmd("git checkout -qf -b pr/123 origin/pr/123") + + if string(script) != f.String() { + t.Errorf("Expected build script value saved as %s, got %s", f.String(), script) + } +} diff --git a/pkg/build/buildfile/buildfile_test.go b/pkg/build/buildfile/buildfile_test.go new file mode 100644 index 000000000..c4a48c993 --- /dev/null +++ b/pkg/build/buildfile/buildfile_test.go @@ -0,0 +1,49 @@ +package buildfile + +import ( + "testing" +) + +func TestWrite(t *testing.T) { + + var f = New() + var got, want = f.String(), base + if got != want { + t.Errorf("Exepected New() returned %s, got %s", want, got) + } + + f = &Buildfile{} + f.WriteCmd("echo hi") + got, want = f.String(), "echo '#DRONE:6563686f206869'\necho hi\n" + if got != want { + t.Errorf("Exepected WriteCmd returned %s, got %s", want, got) + } + + f = &Buildfile{} + f.WriteCmdSilent("echo hi") + got, want = f.String(), "echo hi\n" + if got != want { + t.Errorf("Exepected WriteCmdSilent returned %s, got %s", want, got) + } + + f = &Buildfile{} + f.WriteComment("this is a comment") + got, want = f.String(), "#this is a comment\n" + if got != want { + t.Errorf("Exepected WriteComment returned %s, got %s", want, got) + } + + f = &Buildfile{} + f.WriteEnv("FOO", "BAR") + got, want = f.String(), "export FOO=BAR\n" + if got != want { + t.Errorf("Exepected WriteEnv returned %s, got %s", want, got) + } + + f = &Buildfile{} + f.WriteHost("127.0.0.1") + got, want = f.String(), "[ -f /usr/bin/sudo ] || echo \"127.0.0.1\" | tee -a /etc/hosts\n[ -f /usr/bin/sudo ] && echo \"127.0.0.1\" | sudo tee -a /etc/hosts\n" + if got != want { + t.Errorf("Exepected WriteHost returned %s, got %s", want, got) + } +} diff --git a/pkg/build/dockerfile/dockerfile_test.go b/pkg/build/dockerfile/dockerfile_test.go new file mode 100644 index 000000000..cb9a63a22 --- /dev/null +++ b/pkg/build/dockerfile/dockerfile_test.go @@ -0,0 +1,63 @@ +package dockerfile + +import ( + "testing" +) + +func TestWrite(t *testing.T) { + + var f = New("ubuntu") + var got, want = f.String(), "FROM ubuntu\n" + if got != want { + t.Errorf("Exepected New() returned %s, got %s", want, got) + } + + f = &Dockerfile{} + f.WriteAdd("src", "target") + got, want = f.String(), "ADD src target\n" + if got != want { + t.Errorf("Exepected WriteAdd returned %s, got %s", want, got) + } + + f = &Dockerfile{} + f.WriteFrom("ubuntu") + got, want = f.String(), "FROM ubuntu\n" + if got != want { + t.Errorf("Exepected WriteFrom returned %s, got %s", want, got) + } + + f = &Dockerfile{} + f.WriteRun("whoami") + got, want = f.String(), "RUN whoami\n" + if got != want { + t.Errorf("Exepected WriteRun returned %s, got %s", want, got) + } + + f = &Dockerfile{} + f.WriteUser("root") + got, want = f.String(), "USER root\n" + if got != want { + t.Errorf("Exepected WriteUser returned %s, got %s", want, got) + } + + f = &Dockerfile{} + f.WriteEnv("FOO", "BAR") + got, want = f.String(), "ENV FOO BAR\n" + if got != want { + t.Errorf("Exepected WriteEnv returned %s, got %s", want, got) + } + + f = &Dockerfile{} + f.WriteWorkdir("/home/ubuntu") + got, want = f.String(), "WORKDIR /home/ubuntu\n" + if got != want { + t.Errorf("Exepected WriteWorkdir returned %s, got %s", want, got) + } + + f = &Dockerfile{} + f.WriteEntrypoint("/root") + got, want = f.String(), "ENTRYPOINT /root\n" + if got != want { + t.Errorf("Exepected WriteEntrypoint returned %s, got %s", want, got) + } +} diff --git a/pkg/database/testing/testing.go b/pkg/database/testing/testing.go index 69fde3787..e7deff75c 100644 --- a/pkg/database/testing/testing.go +++ b/pkg/database/testing/testing.go @@ -100,7 +100,7 @@ func Setup() { database.SaveMember(user1.ID, team2.ID, RoleOwner) database.SaveMember(user2.ID, team2.ID, RoleAdmin) database.SaveMember(user3.ID, team2.ID, RoleWrite) - database.SaveMember(user1.ID, team3.ID, RoleOwner) + database.SaveMember(user1.ID, team3.ID, RoleRead) // create dummy repo data repo1 := Repo{ diff --git a/pkg/handler/repos.go b/pkg/handler/repos.go index c49116b12..de232b8e5 100644 --- a/pkg/handler/repos.go +++ b/pkg/handler/repos.go @@ -2,7 +2,6 @@ package handler import ( "fmt" - "log" "net/http" "github.com/drone/drone/pkg/channel" @@ -87,7 +86,7 @@ func RepoCreateGithub(w http.ResponseWriter, r *http.Request, u *User) error { client.ApiUrl = settings.GitHubApiUrl githubRepo, err := client.Repos.Find(owner, name) if err != nil { - return err + return fmt.Errorf("Unable to find GitHub repository %s/%s.", owner, name) } repo, err := NewGitHubRepo(settings.GitHubDomain, owner, name, githubRepo.Private) @@ -104,13 +103,12 @@ func RepoCreateGithub(w http.ResponseWriter, r *http.Request, u *User) error { if len(teamName) > 0 { team, err := database.GetTeamSlug(teamName) if err != nil { - log.Printf("error retrieving team %s", teamName) - return err + return fmt.Errorf("Unable to find Team %s.", teamName) } // user must be an admin member of the team if ok, _ := database.IsMemberAdmin(u.ID, team.ID); !ok { - return fmt.Errorf("Forbidden") + return fmt.Errorf("Invalid permission to access Team %s.", teamName) } repo.TeamID = team.ID @@ -125,7 +123,7 @@ func RepoCreateGithub(w http.ResponseWriter, r *http.Request, u *User) error { // create the github key, or update if one already exists _, err := client.RepoKeys.CreateUpdate(owner, name, repo.PublicKey, keyName) if err != nil { - return fmt.Errorf("Unable to add Public Key to your GitHub repository") + return fmt.Errorf("Unable to add Public Key to your GitHub repository.") } } else { @@ -137,13 +135,12 @@ func RepoCreateGithub(w http.ResponseWriter, r *http.Request, u *User) error { // add the hook if _, err := client.Hooks.CreateUpdate(owner, name, link); err != nil { - return fmt.Errorf("Unable to add Hook to your GitHub repository. %s", err.Error()) + return fmt.Errorf("Unable to add Hook to your GitHub repository.") } // Save to the database if err := database.SaveRepo(repo); err != nil { - log.Print("error saving new repository to the database") - return err + return fmt.Errorf("Error saving repository to the database. %s", err) } return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) diff --git a/pkg/handler/testing/github_test.go b/pkg/handler/testing/github_test.go new file mode 100644 index 000000000..c2fb65182 --- /dev/null +++ b/pkg/handler/testing/github_test.go @@ -0,0 +1,396 @@ +package testing + +import ( + "database/sql" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/drone/drone/pkg/database" + "github.com/drone/drone/pkg/handler" + "github.com/drone/drone/pkg/model" + + dbtest "github.com/drone/drone/pkg/database/testing" + . "github.com/smartystreets/goconvey/convey" +) + +// Tests the ability to create GitHub repositories. +func Test_GitHubCreate(t *testing.T) { + // seed the database with values + SetupFixtures() + defer TeardownFixtures() + + // mock request + req := http.Request{} + req.Form = url.Values{} + + // get user that will add repositories + user, _ := database.GetUser(1) + settings := database.SettingsMust() + + Convey("Given request to setup github repo", t, func() { + + Convey("When repository is public", func() { + req.Form.Set("owner", "example") + req.Form.Set("name", "public") + req.Form.Set("team", "") + res := httptest.NewRecorder() + err := handler.RepoCreateGithub(res, &req, user) + repo, _ := database.GetRepoSlug(settings.GitHubDomain + "/example/public") + + Convey("The repository is created", func() { + So(err, ShouldBeNil) + So(repo, ShouldNotBeNil) + So(repo.ID, ShouldNotEqual, 0) + So(repo.Owner, ShouldEqual, "example") + So(repo.Name, ShouldEqual, "public") + So(repo.Host, ShouldEqual, settings.GitHubDomain) + So(repo.TeamID, ShouldEqual, 0) + So(repo.UserID, ShouldEqual, user.ID) + So(repo.Private, ShouldEqual, false) + So(repo.SCM, ShouldEqual, "git") + }) + Convey("The repository is public", func() { + So(repo.Private, ShouldEqual, false) + }) + }) + + Convey("When repository is private", func() { + req.Form.Set("owner", "example") + req.Form.Set("name", "private") + req.Form.Set("team", "") + res := httptest.NewRecorder() + err := handler.RepoCreateGithub(res, &req, user) + repo, _ := database.GetRepoSlug(settings.GitHubDomain + "/example/private") + + Convey("The repository is created", func() { + So(err, ShouldBeNil) + So(repo, ShouldNotBeNil) + So(repo.ID, ShouldNotEqual, 0) + }) + Convey("The repository is private", func() { + So(repo.Private, ShouldEqual, true) + }) + }) + + Convey("When repository is not found", func() { + req.Form.Set("owner", "example") + req.Form.Set("name", "notfound") + req.Form.Set("team", "") + res := httptest.NewRecorder() + err := handler.RepoCreateGithub(res, &req, user) + + Convey("The result is an error", func() { + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "Unable to find GitHub repository example/notfound.") + }) + + Convey("The repository is not created", func() { + _, err := database.GetRepoSlug("example/notfound") + So(err, ShouldNotBeNil) + So(err, ShouldEqual, sql.ErrNoRows) + }) + }) + + Convey("When repository hook is not writable", func() { + req.Form.Set("owner", "example") + req.Form.Set("name", "hookerr") + req.Form.Set("team", "") + res := httptest.NewRecorder() + err := handler.RepoCreateGithub(res, &req, user) + + Convey("The result is an error", func() { + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "Unable to add Hook to your GitHub repository.") + }) + + Convey("The repository is not created", func() { + _, err := database.GetRepoSlug("example/hookerr") + So(err, ShouldNotBeNil) + So(err, ShouldEqual, sql.ErrNoRows) + }) + }) + + Convey("When repository ssh key is not writable", func() { + req.Form.Set("owner", "example") + req.Form.Set("name", "keyerr") + req.Form.Set("team", "") + res := httptest.NewRecorder() + err := handler.RepoCreateGithub(res, &req, user) + + Convey("The result is an error", func() { + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "Unable to add Public Key to your GitHub repository.") + }) + + Convey("The repository is not created", func() { + _, err := database.GetRepoSlug("example/keyerr") + So(err, ShouldNotBeNil) + So(err, ShouldEqual, sql.ErrNoRows) + }) + }) + + Convey("When a team is provided", func() { + req.Form.Set("owner", "example") + req.Form.Set("name", "team") + req.Form.Set("team", "drone") + res := httptest.NewRecorder() + + // invoke handler + err := handler.RepoCreateGithub(res, &req, user) + team, _ := database.GetTeamSlug("drone") + repo, _ := database.GetRepoSlug(settings.GitHubDomain + "/example/team") + + Convey("The repository is created", func() { + So(err, ShouldBeNil) + So(repo, ShouldNotBeNil) + So(repo.ID, ShouldNotEqual, 0) + }) + + Convey("The team should be set", func() { + So(repo.TeamID, ShouldEqual, team.ID) + }) + }) + + Convey("When a team is not found", func() { + req.Form.Set("owner", "example") + req.Form.Set("name", "public") + req.Form.Set("team", "faketeam") + res := httptest.NewRecorder() + err := handler.RepoCreateGithub(res, &req, user) + + Convey("The result is an error", func() { + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "Unable to find Team faketeam.") + }) + }) + + Convey("When a team is forbidden", func() { + req.Form.Set("owner", "example") + req.Form.Set("name", "public") + req.Form.Set("team", "golang") + res := httptest.NewRecorder() + err := handler.RepoCreateGithub(res, &req, user) + + Convey("The result is an error", func() { + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "Invalid permission to access Team golang.") + }) + }) + }) +} + +// this code should be refactored and centralized, but for now +// it is just proof-of-concepting a testing strategy, so we'll +// revisit later. + +// mux is the HTTP request multiplexer used with the test server. +var mux *http.ServeMux + +// server is a test HTTP server used to provide mock API responses. +var server *httptest.Server + +func SetupFixtures() { + dbtest.Setup() + + // test server + mux = http.NewServeMux() + server = httptest.NewServer(mux) + url, _ := url.Parse(server.URL) + + // set database to use a localhost url for GitHub + settings := model.Settings{} + settings.GitHubKey = "123" + settings.GitHubSecret = "abc" + settings.GitHubApiUrl = url.String() // normall would be "https://api.github.com" + settings.GitHubDomain = url.Host // normally would be "github.com" + settings.Scheme = url.Scheme + settings.Domain = "localhost" + database.SaveSettings(&settings) + + // ----------------------------------------------------------------------------------- + // fixture to return a public repository and successfully + // create a commit hook. + + mux.HandleFunc("/repos/example/public", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{ + "name": "public", + "full_name": "example/public", + "private": false + }`) + }) + + mux.HandleFunc("/repos/example/public/hooks", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{ + "url": "https://api.github.com/repos/example/public/hooks/1", + "name": "web", + "events": [ "push", "pull_request" ], + "id": 1 + }`) + }) + + // ----------------------------------------------------------------------------------- + // fixture to return a private repository and successfully + // create a commit hook and ssh deploy key + + mux.HandleFunc("/repos/example/private", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{ + "name": "private", + "full_name": "example/private", + "private": true + }`) + }) + + mux.HandleFunc("/repos/example/private/hooks", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{ + "url": "https://api.github.com/repos/example/private/hooks/1", + "name": "web", + "events": [ "push", "pull_request" ], + "id": 1 + }`) + }) + + mux.HandleFunc("/repos/example/private/keys", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{ + "id": 1, + "key": "ssh-rsa AAA...", + "url": "https://api.github.com/user/keys/1", + "title": "octocat@octomac" + }`) + }) + + // ----------------------------------------------------------------------------------- + // fixture to return a not found when accessing a github repository. + + mux.HandleFunc("/repos/example/notfound", func(w http.ResponseWriter, r *http.Request) { + http.NotFound(w, r) + }) + + // ----------------------------------------------------------------------------------- + // fixture to return a public repository and successfully + // create a commit hook. + + mux.HandleFunc("/repos/example/hookerr", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{ + "name": "hookerr", + "full_name": "example/hookerr", + "private": false + }`) + }) + + mux.HandleFunc("/repos/example/hookerr/hooks", func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Forbidden", http.StatusForbidden) + }) + + // ----------------------------------------------------------------------------------- + // fixture to return a private repository and successfully + // create a commit hook and ssh deploy key + + mux.HandleFunc("/repos/example/keyerr", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{ + "name": "keyerr", + "full_name": "example/keyerr", + "private": true + }`) + }) + + mux.HandleFunc("/repos/example/keyerr/hooks", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{ + "url": "https://api.github.com/repos/example/keyerr/hooks/1", + "name": "web", + "events": [ "push", "pull_request" ], + "id": 1 + }`) + }) + + mux.HandleFunc("/repos/example/keyerr/keys", func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Forbidden", http.StatusForbidden) + }) + + // ----------------------------------------------------------------------------------- + // fixture to return a public repository and successfully to + // test adding a team. + + mux.HandleFunc("/repos/example/team", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, `{ + "name": "team", + "full_name": "example/team", + "private": false + }`) + }) + + mux.HandleFunc("/repos/example/team/hooks", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{ + "url": "https://api.github.com/repos/example/team/hooks/1", + "name": "web", + "events": [ "push", "pull_request" ], + "id": 1 + }`) + }) +} + +func TeardownFixtures() { + dbtest.Teardown() + server.Close() +} + +/* + +// response for querying a repo +var repoGet = `{ + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat" + }, + "private": false, + "git_url": "git://github.com/octocat/Hello-World.git", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "clone_url": "https://github.com/octocat/Hello-World.git" +}` + +// response for querying a private repo +var repoPrivateGet = `{ + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "owner": { + "login": "octocat" + }, + "private": true, + "git_url": "git://github.com/octocat/Hello-World.git", + "ssh_url": "git@github.com:octocat/Hello-World.git", + "clone_url": "https://github.com/octocat/Hello-World.git" +}` + +// response for creating a key +var keyAdd = ` +{ + "id": 1, + "key": "ssh-rsa AAA...", + "url": "https://api.github.com/user/keys/1", + "title": "octocat@octomac" +} +` + +// response for creating a hook +var hookAdd = ` +{ + "url": "https://api.github.com/repos/octocat/Hello-World/hooks/1", + "updated_at": "2011-09-06T20:39:23Z", + "created_at": "2011-09-06T17:26:27Z", + "name": "web", + "events": [ + "push", + "pull_request" + ], + "active": true, + "config": { + "url": "http://example.com", + "content_type": "json" + }, + "id": 1 +} +` +*/