diff options
Diffstat (limited to 'spec/ci')
80 files changed, 4935 insertions, 0 deletions
diff --git a/spec/ci/controllers/commits_controller_spec.rb b/spec/ci/controllers/commits_controller_spec.rb new file mode 100644 index 00000000000..f32d6f8c126 --- /dev/null +++ b/spec/ci/controllers/commits_controller_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +describe CommitsController do + before do + @project = FactoryGirl.create :project + end + + describe "GET /status" do + it "returns status of commit" do + commit = FactoryGirl.create :commit, project: @project + get :status, id: commit.sha, ref_id: commit.ref, project_id: @project.id + + expect(response).to be_success + expect(response.code).to eq('200') + JSON.parse(response.body)["status"] == "pending" + end + + it "returns not_found status" do + commit = FactoryGirl.create :commit, project: @project + get :status, id: commit.sha, ref_id: "deploy", project_id: @project.id + + expect(response).to be_success + expect(response.code).to eq('200') + JSON.parse(response.body)["status"] == "not_found" + end + end +end diff --git a/spec/ci/controllers/projects_controller_spec.rb b/spec/ci/controllers/projects_controller_spec.rb new file mode 100644 index 00000000000..0069a782511 --- /dev/null +++ b/spec/ci/controllers/projects_controller_spec.rb @@ -0,0 +1,108 @@ +require "spec_helper" + +describe ProjectsController do + before do + @project = FactoryGirl.create :project + end + + describe "POST #build" do + it 'should respond 200 if params is ok' do + post :build, id: @project.id, + ref: 'master', + before: '2aa371379db71ac89ae20843fcff3b3477cf1a1d', + after: '1c8a9df454ef68c22c2a33cca8232bb50849e5c5', + token: @project.token, + ci_yaml_file: gitlab_ci_yaml, + commits: [ { message: "Message" } ] + + + expect(response).to be_success + expect(response.code).to eq('201') + end + + it 'should respond 400 if push about removed branch' do + post :build, id: @project.id, + ref: 'master', + before: '2aa371379db71ac89ae20843fcff3b3477cf1a1d', + after: '0000000000000000000000000000000000000000', + token: @project.token, + ci_yaml_file: gitlab_ci_yaml + + expect(response).not_to be_success + expect(response.code).to eq('400') + end + + it 'should respond 400 if some params missed' do + post :build, id: @project.id, token: @project.token, ci_yaml_file: gitlab_ci_yaml + expect(response).not_to be_success + expect(response.code).to eq('400') + end + + it 'should respond 403 if token is wrong' do + post :build, id: @project.id, token: 'invalid-token' + expect(response).not_to be_success + expect(response.code).to eq('403') + end + end + + describe "POST /projects" do + let(:project_dump) { YAML.load File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } + let(:gitlab_url) { GitlabCi.config.gitlab_server.url } + + let (:user_data) do + data = JSON.parse File.read(Rails.root.join('spec/support/gitlab_stubs/user.json')) + data.merge("url" => gitlab_url) + end + + let(:user) do + User.new(user_data) + end + + it "creates project" do + allow(controller).to receive(:reset_cache) { true } + allow(controller).to receive(:current_user) { user } + Network.any_instance.stub(:enable_ci).and_return(true) + Network.any_instance.stub(:project_hooks).and_return(true) + + post :create, { project: JSON.dump(project_dump.to_h) }.with_indifferent_access + + expect(response.code).to eq('302') + expect(assigns(:project)).not_to be_a_new(Project) + end + + it "shows error" do + allow(controller).to receive(:reset_cache) { true } + allow(controller).to receive(:current_user) { user } + User.any_instance.stub(:can_manage_project?).and_return(false) + + post :create, { project: JSON.dump(project_dump.to_h) }.with_indifferent_access + + expect(response.code).to eq('302') + expect(flash[:alert]).to include("You have to have at least master role to enable CI for this project") + end + end + + describe "GET /gitlab" do + let(:gitlab_url) { GitlabCi.config.gitlab_server.url } + + let (:user_data) do + data = JSON.parse File.read(Rails.root.join('spec/support/gitlab_stubs/user.json')) + data.merge("url" => gitlab_url) + end + + let(:user) do + User.new(user_data) + end + + it "searches projects" do + allow(controller).to receive(:reset_cache) { true } + allow(controller).to receive(:current_user) { user } + Network.any_instance.should_receive(:projects).with(hash_including(search: 'str'), :authorized) + + xhr :get, :gitlab, { search: "str", format: "js" }.with_indifferent_access + + expect(response).to be_success + expect(response.code).to eq('200') + end + end +end diff --git a/spec/ci/factories/builds.rb b/spec/ci/factories/builds.rb new file mode 100644 index 00000000000..346e0002bf5 --- /dev/null +++ b/spec/ci/factories/builds.rb @@ -0,0 +1,45 @@ +# == Schema Information +# +# Table name: builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# commit_id :integer +# coverage :float +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# + +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :build do + started_at 'Di 29. Okt 09:51:28 CET 2013' + finished_at 'Di 29. Okt 09:53:28 CET 2013' + commands 'ls -a' + options do + { + image: "ruby:2.1", + services: ["postgres"] + } + end + + factory :not_started_build do + started_at nil + finished_at nil + end + end +end diff --git a/spec/ci/factories/commits.rb b/spec/ci/factories/commits.rb new file mode 100644 index 00000000000..6fdd46fa74b --- /dev/null +++ b/spec/ci/factories/commits.rb @@ -0,0 +1,75 @@ +# == Schema Information +# +# Table name: commits +# +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# + +# Read about factories at https://github.com/thoughtbot/factory_girl +FactoryGirl.define do + factory :commit do + ref 'master' + before_sha '76de212e80737a608d939f648d959671fb0a0142' + sha '97de212e80737a608d939f648d959671fb0a0142' + push_data do + { + ref: 'refs/heads/master', + before: '76de212e80737a608d939f648d959671fb0a0142', + after: '97de212e80737a608d939f648d959671fb0a0142', + user_name: 'Git User', + user_email: 'git@example.com', + repository: { + name: 'test-data', + url: 'ssh://git@gitlab.com/test/test-data.git', + description: '', + homepage: 'http://gitlab.com/test/test-data' + }, + commits: [ + { + id: '97de212e80737a608d939f648d959671fb0a0142', + message: 'Test commit message', + timestamp: '2014-09-23T13:12:25+02:00', + url: 'https://gitlab.com/test/test-data/commit/97de212e80737a608d939f648d959671fb0a0142', + author: { + name: 'Git User', + email: 'git@user.com' + } + } + ], + total_commits_count: 1, + ci_yaml_file: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + } + end + + factory :commit_without_jobs do + after(:create) do |commit, evaluator| + commit.push_data[:ci_yaml_file] = YAML.dump({}) + commit.save + end + end + + factory :commit_with_one_job do + after(:create) do |commit, evaluator| + commit.push_data[:ci_yaml_file] = YAML.dump({rspec: { script: "ls" }}) + commit.save + end + end + + factory :commit_with_two_jobs do + after(:create) do |commit, evaluator| + commit.push_data[:ci_yaml_file] = YAML.dump({rspec: { script: "ls" }, spinach: { script: "ls" }}) + commit.save + end + end + end +end diff --git a/spec/ci/factories/events.rb b/spec/ci/factories/events.rb new file mode 100644 index 00000000000..1dfa52e3529 --- /dev/null +++ b/spec/ci/factories/events.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: events +# +# id :integer not null, primary key +# project_id :integer +# user_id :integer +# is_admin :integer +# description :text +# created_at :datetime +# updated_at :datetime +# + +FactoryGirl.define do + factory :event, class: Event do + sequence :description do |n| + "updated project settings#{n}" + end + + factory :admin_event do + is_admin true + end + end +end diff --git a/spec/ci/factories/projects.rb b/spec/ci/factories/projects.rb new file mode 100644 index 00000000000..fb5b563f2f2 --- /dev/null +++ b/spec/ci/factories/projects.rb @@ -0,0 +1,56 @@ +# == Schema Information +# +# Table name: projects +# +# id :integer not null, primary key +# name :string(255) not null +# timeout :integer default(3600), not null +# created_at :datetime +# updated_at :datetime +# token :string(255) +# default_ref :string(255) +# path :string(255) +# always_build :boolean default(FALSE), not null +# polling_interval :integer +# public :boolean default(FALSE), not null +# ssh_url_to_repo :string(255) +# gitlab_id :integer +# allow_git_fetch :boolean default(TRUE), not null +# email_recipients :string(255) default(""), not null +# email_add_pusher :boolean default(TRUE), not null +# email_only_broken_builds :boolean default(TRUE), not null +# skip_refs :string(255) +# coverage_regex :string(255) +# shared_runners_enabled :boolean default(FALSE) +# generated_yaml_config :text +# + +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :project_without_token, class: Project do + sequence :name do |n| + "GitLab / gitlab-shell#{n}" + end + + default_ref 'master' + + sequence :path do |n| + "gitlab/gitlab-shell#{n}" + end + + sequence :ssh_url_to_repo do |n| + "git@demo.gitlab.com:gitlab/gitlab-shell#{n}.git" + end + + sequence :gitlab_id + + factory :project do + token 'iPWx6WM4lhHNedGfBpPJNP' + end + + factory :public_project do + public true + end + end +end diff --git a/spec/ci/factories/runner_projects.rb b/spec/ci/factories/runner_projects.rb new file mode 100644 index 00000000000..b27632b3429 --- /dev/null +++ b/spec/ci/factories/runner_projects.rb @@ -0,0 +1,19 @@ +# == Schema Information +# +# Table name: runner_projects +# +# id :integer not null, primary key +# runner_id :integer not null +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :runner_project do + runner_id 1 + project_id 1 + end +end diff --git a/spec/ci/factories/runners.rb b/spec/ci/factories/runners.rb new file mode 100644 index 00000000000..20a80f03268 --- /dev/null +++ b/spec/ci/factories/runners.rb @@ -0,0 +1,38 @@ +# == Schema Information +# +# Table name: runners +# +# id :integer not null, primary key +# token :string(255) +# created_at :datetime +# updated_at :datetime +# description :string(255) +# contacted_at :datetime +# active :boolean default(TRUE), not null +# is_shared :boolean default(FALSE) +# name :string(255) +# version :string(255) +# revision :string(255) +# platform :string(255) +# architecture :string(255) +# + +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :runner do + sequence :description do |n| + "My runner#{n}" + end + + platform "darwin" + + factory :shared_runner do + is_shared true + end + + factory :specific_runner do + is_shared false + end + end +end diff --git a/spec/ci/factories/trigger_requests.rb b/spec/ci/factories/trigger_requests.rb new file mode 100644 index 00000000000..c85d1027ce6 --- /dev/null +++ b/spec/ci/factories/trigger_requests.rb @@ -0,0 +1,13 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :trigger_request do + factory :trigger_request_with_variables do + variables do + { + TRIGGER_KEY: 'TRIGGER_VALUE' + } + end + end + end +end diff --git a/spec/ci/factories/triggers.rb b/spec/ci/factories/triggers.rb new file mode 100644 index 00000000000..a5af47b7d7f --- /dev/null +++ b/spec/ci/factories/triggers.rb @@ -0,0 +1,9 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :trigger_without_token, class: Trigger do + factory :trigger do + token 'token' + end + end +end diff --git a/spec/ci/factories/users.rb b/spec/ci/factories/users.rb new file mode 100644 index 00000000000..26b30eff0e6 --- /dev/null +++ b/spec/ci/factories/users.rb @@ -0,0 +1,6 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :user do + end +end diff --git a/spec/ci/factories/web_hook.rb b/spec/ci/factories/web_hook.rb new file mode 100644 index 00000000000..3c027fb4861 --- /dev/null +++ b/spec/ci/factories/web_hook.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :web_hook do + sequence(:url) { Faker::Internet.uri('http') } + project + end +end diff --git a/spec/ci/features/admin/builds_spec.rb b/spec/ci/features/admin/builds_spec.rb new file mode 100644 index 00000000000..e62e83692da --- /dev/null +++ b/spec/ci/features/admin/builds_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe "Admin Builds" do + let(:project) { FactoryGirl.create :project } + let(:commit) { FactoryGirl.create :commit, project: project } + let(:build) { FactoryGirl.create :build, commit: commit } + + before do + skip_admin_auth + login_as :user + end + + describe "GET /admin/builds" do + before do + build + visit admin_builds_path + end + + it { page.should have_content "All builds" } + it { page.should have_content build.short_sha } + end + + describe "Tabs" do + it "shows all builds" do + build = FactoryGirl.create :build, commit: commit, status: "pending" + build1 = FactoryGirl.create :build, commit: commit, status: "running" + build2 = FactoryGirl.create :build, commit: commit, status: "success" + build3 = FactoryGirl.create :build, commit: commit, status: "failed" + + visit admin_builds_path + + page.all(".build-link").size.should == 4 + end + + it "shows pending builds" do + build = FactoryGirl.create :build, commit: commit, status: "pending" + build1 = FactoryGirl.create :build, commit: commit, status: "running" + build2 = FactoryGirl.create :build, commit: commit, status: "success" + build3 = FactoryGirl.create :build, commit: commit, status: "failed" + + visit admin_builds_path + + within ".nav.nav-tabs" do + click_on "Pending" + end + + page.find(".build-link").should have_content(build.id) + page.find(".build-link").should_not have_content(build1.id) + page.find(".build-link").should_not have_content(build2.id) + page.find(".build-link").should_not have_content(build3.id) + end + + it "shows running builds" do + build = FactoryGirl.create :build, commit: commit, status: "pending" + build1 = FactoryGirl.create :build, commit: commit, status: "running" + build2 = FactoryGirl.create :build, commit: commit, status: "success" + build3 = FactoryGirl.create :build, commit: commit, status: "failed" + + visit admin_builds_path + + within ".nav.nav-tabs" do + click_on "Running" + end + + page.find(".build-link").should have_content(build1.id) + page.find(".build-link").should_not have_content(build.id) + page.find(".build-link").should_not have_content(build2.id) + page.find(".build-link").should_not have_content(build3.id) + end + end +end diff --git a/spec/ci/features/admin/events_spec.rb b/spec/ci/features/admin/events_spec.rb new file mode 100644 index 00000000000..469c6ed102d --- /dev/null +++ b/spec/ci/features/admin/events_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe "Admin Events" do + let(:event) { FactoryGirl.create :admin_event } + + before do + skip_admin_auth + login_as :user + end + + describe "GET /admin/events" do + before do + event + visit admin_events_path + end + + it { page.should have_content "Events" } + it { page.should have_content event.description } + end +end diff --git a/spec/ci/features/admin/projects_spec.rb b/spec/ci/features/admin/projects_spec.rb new file mode 100644 index 00000000000..6f87e368deb --- /dev/null +++ b/spec/ci/features/admin/projects_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe "Admin Projects" do + let(:project) { FactoryGirl.create :project } + + before do + skip_admin_auth + login_as :user + end + + describe "GET /admin/projects" do + before do + project + visit admin_projects_path + end + + it { page.should have_content "Projects" } + end +end diff --git a/spec/ci/features/admin/runners_spec.rb b/spec/ci/features/admin/runners_spec.rb new file mode 100644 index 00000000000..2827a7fc6e5 --- /dev/null +++ b/spec/ci/features/admin/runners_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe "Admin Runners" do + before do + skip_admin_auth + login_as :user + end + + describe "Runners page" do + before do + runner = FactoryGirl.create(:runner) + commit = FactoryGirl.create(:commit) + FactoryGirl.create(:build, commit: commit, runner_id: runner.id) + visit admin_runners_path + end + + it { page.has_text? "Manage Runners" } + it { page.has_text? "To register a new runner" } + it { page.has_text? "Runners with last contact less than a minute ago: 1" } + + describe 'search' do + before do + FactoryGirl.create :runner, description: 'foo' + FactoryGirl.create :runner, description: 'bar' + + fill_in 'search', with: 'foo' + click_button 'Search' + end + + it { page.should have_content("foo") } + it { page.should_not have_content("bar") } + end + end + + describe "Runner show page" do + let(:runner) { FactoryGirl.create :runner } + + before do + FactoryGirl.create(:project, name: "foo") + FactoryGirl.create(:project, name: "bar") + visit admin_runner_path(runner) + end + + describe 'runner info' do + it { find_field('runner_token').value.should eq runner.token } + end + + describe 'projects' do + it { page.should have_content("foo") } + it { page.should have_content("bar") } + end + + describe 'search' do + before do + fill_in 'search', with: 'foo' + click_button 'Search' + end + + it { page.should have_content("foo") } + it { page.should_not have_content("bar") } + end + end +end diff --git a/spec/ci/features/builds_spec.rb b/spec/ci/features/builds_spec.rb new file mode 100644 index 00000000000..fcd7996efd7 --- /dev/null +++ b/spec/ci/features/builds_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe "Builds" do + before do + @project = FactoryGirl.create :project + @commit = FactoryGirl.create :commit, project: @project + @build = FactoryGirl.create :build, commit: @commit + end + + describe "GET /:project/builds/:id" do + before do + login_as :user + visit project_build_path(@project, @build) + end + + it { page.should have_content @commit.sha[0..7] } + it { page.should have_content @commit.git_commit_message } + it { page.should have_content @commit.git_author_name } + end + + describe "GET /:project/builds/:id/cancel" do + before do + login_as :user + @build.run! + visit cancel_project_build_path(@project, @build) + end + + it { page.should have_content 'canceled' } + it { page.should have_content 'Retry' } + end + + describe "POST /:project/builds/:id/retry" do + before do + login_as :user + @build.cancel! + visit project_build_path(@project, @build) + click_link 'Retry' + end + + it { page.should have_content 'pending' } + it { page.should have_content 'Cancel' } + end + + describe "Show page public accessible" do + before do + @project = FactoryGirl.create :public_project + @commit = FactoryGirl.create :commit, project: @project + @runner = FactoryGirl.create :specific_runner + @build = FactoryGirl.create :build, commit: @commit, runner: @runner + + stub_gitlab_calls + visit project_build_path(@project, @build) + end + + it { page.should have_content @commit.sha[0..7] } + end +end diff --git a/spec/ci/features/commits_spec.rb b/spec/ci/features/commits_spec.rb new file mode 100644 index 00000000000..202f05c516f --- /dev/null +++ b/spec/ci/features/commits_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +describe "Commits" do + context "Authenticated user" do + before do + login_as :user + @project = FactoryGirl.create :project + @commit = FactoryGirl.create :commit, project: @project + @build = FactoryGirl.create :build, commit: @commit + end + + describe "GET /:project/commits/:sha" do + before do + visit project_ref_commit_path(@project, @commit.ref, @commit.sha) + end + + it { page.should have_content @commit.sha[0..7] } + it { page.should have_content @commit.git_commit_message } + it { page.should have_content @commit.git_author_name } + end + + describe "Cancel commit" do + it "cancels commit" do + visit project_ref_commit_path(@project, @commit.ref, @commit.sha) + click_on "Cancel" + + page.should have_content "canceled" + end + end + + describe ".gitlab-ci.yml not found warning" do + it "does not show warning" do + visit project_ref_commit_path(@project, @commit.ref, @commit.sha) + + page.should_not have_content ".gitlab-ci.yml not found in this commit" + end + + it "shows warning" do + @commit.push_data[:ci_yaml_file] = nil + @commit.save + + visit project_ref_commit_path(@project, @commit.ref, @commit.sha) + + page.should have_content ".gitlab-ci.yml not found in this commit" + end + end + end + + context "Public pages" do + before do + @project = FactoryGirl.create :public_project + @commit = FactoryGirl.create :commit, project: @project + @build = FactoryGirl.create :build, commit: @commit + end + + describe "GET /:project/commits/:sha" do + before do + visit project_ref_commit_path(@project, @commit.ref, @commit.sha) + end + + it { page.should have_content @commit.sha[0..7] } + it { page.should have_content @commit.git_commit_message } + it { page.should have_content @commit.git_author_name } + end + end +end diff --git a/spec/ci/features/events_spec.rb b/spec/ci/features/events_spec.rb new file mode 100644 index 00000000000..77d1fba5769 --- /dev/null +++ b/spec/ci/features/events_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe "Events" do + let(:project) { FactoryGirl.create :project } + let(:event) { FactoryGirl.create :admin_event, project: project } + + before do + login_as :user + end + + describe "GET /project/:id/events" do + before do + event + visit project_events_path(project) + end + + it { page.should have_content "Events" } + it { page.should have_content event.description } + end +end diff --git a/spec/ci/features/lint_spec.rb b/spec/ci/features/lint_spec.rb new file mode 100644 index 00000000000..0b3d4e099fb --- /dev/null +++ b/spec/ci/features/lint_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe "Lint" do + before do + login_as :user + end + + it "Yaml parsing", js: true do + content = File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + visit lint_path + fill_in "content", with: content + click_on "Validate" + within "table" do + page.should have_content("Job - rspec") + page.should have_content("Job - spinach") + page.should have_content("Deploy Job - staging") + page.should have_content("Deploy Job - production") + end + end + + it "Yaml parsing with error", js: true do + visit lint_path + fill_in "content", with: "" + click_on "Validate" + page.should have_content("Status: syntax is incorrect") + page.should have_content("Error: Please provide content of .gitlab-ci.yml") + end +end diff --git a/spec/ci/features/projects_spec.rb b/spec/ci/features/projects_spec.rb new file mode 100644 index 00000000000..3f21af92a2b --- /dev/null +++ b/spec/ci/features/projects_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe "Projects" do + before do + login_as :user + @project = FactoryGirl.create :project, name: "GitLab / gitlab-shell" + end + + describe "GET /projects", js: true do + before do + stub_js_gitlab_calls + visit projects_path + end + + it { page.should have_content "GitLab / gitlab-shell" } + it { page.should have_selector ".search input#search" } + end + + describe "GET /projects/:id" do + before do + visit project_path(@project) + end + + it { page.should have_content @project.name } + it { page.should have_content 'All commits' } + end + + describe "GET /projects/:id/edit" do + before do + visit edit_project_path(@project) + end + + it { page.should have_content @project.name } + it { page.should have_content 'Build Schedule' } + + it "updates configuration" do + fill_in 'Timeout', with: '70' + click_button 'Save changes' + + page.should have_content 'was successfully updated' + + find_field('Timeout').value.should eq '70' + end + end + + describe "GET /projects/:id/charts" do + before do + visit project_charts_path(@project) + end + + it { page.should have_content 'Overall' } + it { page.should have_content 'Builds chart for last week' } + it { page.should have_content 'Builds chart for last month' } + it { page.should have_content 'Builds chart for last year' } + it { page.should have_content 'Commit duration in minutes for last 30 commits' } + end +end diff --git a/spec/ci/features/runners_spec.rb b/spec/ci/features/runners_spec.rb new file mode 100644 index 00000000000..c41dc5b2e2e --- /dev/null +++ b/spec/ci/features/runners_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe "Runners" do + before do + login_as :user + end + + describe "specific runners" do + before do + @project = FactoryGirl.create :project + @project2 = FactoryGirl.create :project + stub_js_gitlab_calls + + # all projects should be authorized for user + Network.any_instance.stub(:projects).and_return([ + OpenStruct.new({id: @project.gitlab_id}), + OpenStruct.new({id: @project2.gitlab_id}) + ]) + + @shared_runner = FactoryGirl.create :shared_runner + @specific_runner = FactoryGirl.create :specific_runner + @specific_runner2 = FactoryGirl.create :specific_runner + @project.runners << @specific_runner + @project2.runners << @specific_runner2 + end + + it "places runners in right places" do + visit project_runners_path(@project) + page.find(".available-specific-runners").should have_content(@specific_runner2.display_name) + page.find(".activated-specific-runners").should have_content(@specific_runner.display_name) + page.find(".available-shared-runners").should have_content(@shared_runner.display_name) + end + + it "enables specific runner for project" do + visit project_runners_path(@project) + + within ".available-specific-runners" do + click_on "Enable for this project" + end + + page.find(".activated-specific-runners").should have_content(@specific_runner2.display_name) + end + + it "disables specific runner for project" do + @project2.runners << @specific_runner + + visit project_runners_path(@project) + + within ".activated-specific-runners" do + click_on "Disable for this project" + end + + page.find(".available-specific-runners").should have_content(@specific_runner.display_name) + end + + it "removes specific runner for project if this is last project for that runners" do + visit project_runners_path(@project) + + within ".activated-specific-runners" do + click_on "Remove runner" + end + + Runner.exists?(id: @specific_runner).should be_false + end + end + + describe "shared runners" do + before do + @project = FactoryGirl.create :project + stub_js_gitlab_calls + end + + it "enables shared runners" do + visit project_runners_path(@project) + + click_on "Enable shared runners" + + @project.reload.shared_runners_enabled.should be_true + end + end + + describe "show page" do + before do + @project = FactoryGirl.create :project + stub_js_gitlab_calls + @specific_runner = FactoryGirl.create :specific_runner + @project.runners << @specific_runner + end + + it "shows runner information" do + visit project_runners_path(@project) + + click_on @specific_runner.short_sha + + page.should have_content(@specific_runner.platform) + end + end +end diff --git a/spec/ci/features/triggers_spec.rb b/spec/ci/features/triggers_spec.rb new file mode 100644 index 00000000000..2076429383d --- /dev/null +++ b/spec/ci/features/triggers_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'Variables' do + before do + login_as :user + @project = FactoryGirl.create :project + stub_js_gitlab_calls + visit project_triggers_path(@project) + end + + context 'create a trigger' do + before do + click_on 'Add Trigger' + @project.triggers.count.should == 1 + end + + it 'contains trigger token' do + page.should have_content(@project.triggers.first.token) + end + + it 'revokes the trigger' do + click_on 'Revoke' + @project.triggers.count.should == 0 + end + end +end diff --git a/spec/ci/features/variables_spec.rb b/spec/ci/features/variables_spec.rb new file mode 100644 index 00000000000..2bb0d9dedde --- /dev/null +++ b/spec/ci/features/variables_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe "Variables" do + before do + login_as :user + end + + describe "specific runners" do + before do + @project = FactoryGirl.create :project + stub_js_gitlab_calls + end + + it "creates variable", js: true do + visit project_variables_path(@project) + click_on "Add a variable" + fill_in "Key", with: "SECRET_KEY" + fill_in "Value", with: "SECRET_VALUE" + click_on "Save changes" + + page.should have_content("Variables were successfully updated.") + @project.variables.count.should == 1 + end + + end +end diff --git a/spec/ci/helpers/application_helper_spec.rb b/spec/ci/helpers/application_helper_spec.rb new file mode 100644 index 00000000000..c2b1058a8fa --- /dev/null +++ b/spec/ci/helpers/application_helper_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe ApplicationHelper do + describe "#duration_in_words" do + it "returns minutes and seconds" do + intervals_in_words = { + 100 => "1 minute 40 seconds", + 121 => "2 minutes 1 second", + 3721 => "62 minutes 1 second", + 0 => "0 seconds" + } + + intervals_in_words.each do |interval, expectation| + duration_in_words(Time.now + interval, Time.now).should == expectation + end + end + + it "calculates interval from now if there is no finished_at" do + duration_in_words(nil, Time.now - 5).should == "5 seconds" + end + end + + describe "#time_interval_in_words" do + it "returns minutes and seconds" do + intervals_in_words = { + 100 => "1 minute 40 seconds", + 121 => "2 minutes 1 second", + 3721 => "62 minutes 1 second", + 0 => "0 seconds" + } + + intervals_in_words.each do |interval, expectation| + time_interval_in_words(interval).should == expectation + end + end + end +end diff --git a/spec/ci/helpers/runners_helper_spec.rb b/spec/ci/helpers/runners_helper_spec.rb new file mode 100644 index 00000000000..02d497b40d2 --- /dev/null +++ b/spec/ci/helpers/runners_helper_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe RunnersHelper do + it "returns - not contacted yet" do + runner = FactoryGirl.build :runner + runner_status_icon(runner).should include("not connected yet") + end + + it "returns offline text" do + runner = FactoryGirl.build(:runner, contacted_at: 1.day.ago, active: true) + runner_status_icon(runner).should include("Runner is offline") + end + + it "returns online text" do + runner = FactoryGirl.build(:runner, contacted_at: 1.hour.ago, active: true) + runner_status_icon(runner).should include("Runner is online") + end +end diff --git a/spec/ci/helpers/user_helper_spec.rb b/spec/ci/helpers/user_helper_spec.rb new file mode 100644 index 00000000000..7215dc41a85 --- /dev/null +++ b/spec/ci/helpers/user_helper_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe UserHelper do + describe :user_avatar_url do + let (:user) { User.new({'avatar_url' => avatar_url}) } + + context 'no avatar' do + let (:avatar_url) { nil } + + it 'should return a generic avatar' do + user_avatar_url(user).should == 'ci/no_avatar.png' + end + end + + context 'plain gravatar' do + let (:base_url) { 'http://www.gravatar.com/avatar/abcdefgh' } + let (:avatar_url) { "#{base_url}?s=40&d=mm" } + + it 'should return gravatar with default size' do + user_avatar_url(user).should == "#{base_url}?s=40&d=identicon" + end + + it 'should return gravatar with custom size' do + user_avatar_url(user, 120).should == "#{base_url}?s=120&d=identicon" + end + end + + context 'secure gravatar' do + let (:base_url) { 'https://secure.gravatar.com/avatar/abcdefgh' } + let (:avatar_url) { "#{base_url}?s=40&d=mm" } + + it 'should return gravatar with default size' do + user_avatar_url(user).should == "#{base_url}?s=40&d=identicon" + end + + it 'should return gravatar with custom size' do + user_avatar_url(user, 120).should == "#{base_url}?s=120&d=identicon" + end + end + + context 'custom avatar' do + let (:avatar_url) { 'http://example.local/avatar.png' } + + it 'should return custom avatar' do + user_avatar_url(user).should == avatar_url + end + end + end +end diff --git a/spec/ci/helpers/user_sessions_helper_spec.rb b/spec/ci/helpers/user_sessions_helper_spec.rb new file mode 100644 index 00000000000..a2ab1f1e023 --- /dev/null +++ b/spec/ci/helpers/user_sessions_helper_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe UserSessionsHelper do + describe :generate_oauth_hmac do + let (:salt) { 'a' } + let (:salt2) { 'b' } + let (:return_to) { 'b' } + + it 'should return null if return_to is also null' do + generate_oauth_hmac(salt, nil).should be_nil + end + + it 'should return not null if return_to is also not null' do + generate_oauth_hmac(salt, return_to).should_not be_nil + end + + it 'should return different hmacs for different salts' do + secret1 = generate_oauth_hmac(salt, return_to) + secret2 = generate_oauth_hmac(salt2, return_to) + secret1.should_not eq(secret2) + end + end + + describe :generate_oauth_state do + let (:return_to) { 'b' } + + it 'should return null if return_to is also null' do + generate_oauth_state(nil).should be_nil + end + + it 'should return two different states for same return_to' do + state1 = generate_oauth_state(return_to) + state2 = generate_oauth_state(return_to) + state1.should_not eq(state2) + end + end + + describe :get_ouath_state_return_to do + let (:return_to) { 'a' } + let (:state) { generate_oauth_state(return_to) } + + it 'should return return_to' do + get_ouath_state_return_to(state).should eq(return_to) + end + end + + describe :is_oauth_state_valid? do + let (:return_to) { 'a' } + let (:state) { generate_oauth_state(return_to) } + let (:forged) { "forged#{state}" } + let (:invalid) { 'aa' } + let (:invalid2) { 'aa:bb' } + let (:invalid3) { 'aa:bb:' } + + it 'should validate oauth state' do + is_oauth_state_valid?(state).should be_true + end + + it 'should not validate forged state' do + is_oauth_state_valid?(forged).should be_false + end + + it 'should not validate invalid state' do + is_oauth_state_valid?(invalid).should be_false + is_oauth_state_valid?(invalid2).should be_false + is_oauth_state_valid?(invalid3).should be_false + end + end +end diff --git a/spec/ci/lib/ansi2html_spec.rb b/spec/ci/lib/ansi2html_spec.rb new file mode 100644 index 00000000000..aa60011685b --- /dev/null +++ b/spec/ci/lib/ansi2html_spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +describe Ansi2html do + + it "prints non-ansi as-is" do + Ansi2html::convert("Hello").should == 'Hello' + end + + it "strips non-color-changing controll sequences" do + Ansi2html::convert("Hello \e[2Kworld").should == 'Hello world' + end + + it "prints simply red" do + Ansi2html::convert("\e[31mHello\e[0m").should == '<span class="term-fg-red">Hello</span>' + end + + it "prints simply red without trailing reset" do + Ansi2html::convert("\e[31mHello").should == '<span class="term-fg-red">Hello</span>' + end + + it "prints simply yellow" do + Ansi2html::convert("\e[33mHello\e[0m").should == '<span class="term-fg-yellow">Hello</span>' + end + + it "prints default on blue" do + Ansi2html::convert("\e[39;44mHello").should == '<span class="term-bg-blue">Hello</span>' + end + + it "prints red on blue" do + Ansi2html::convert("\e[31;44mHello").should == '<span class="term-fg-red term-bg-blue">Hello</span>' + end + + it "resets colors after red on blue" do + Ansi2html::convert("\e[31;44mHello\e[0m world").should == '<span class="term-fg-red term-bg-blue">Hello</span> world' + end + + it "performs color change from red/blue to yellow/blue" do + Ansi2html::convert("\e[31;44mHello \e[33mworld").should == '<span class="term-fg-red term-bg-blue">Hello </span><span class="term-fg-yellow term-bg-blue">world</span>' + end + + it "performs color change from red/blue to yellow/green" do + Ansi2html::convert("\e[31;44mHello \e[33;42mworld").should == '<span class="term-fg-red term-bg-blue">Hello </span><span class="term-fg-yellow term-bg-green">world</span>' + end + + it "performs color change from red/blue to reset to yellow/green" do + Ansi2html::convert("\e[31;44mHello\e[0m \e[33;42mworld").should == '<span class="term-fg-red term-bg-blue">Hello</span> <span class="term-fg-yellow term-bg-green">world</span>' + end + + it "ignores unsupported codes" do + Ansi2html::convert("\e[51mHello\e[0m").should == 'Hello' + end + + it "prints light red" do + Ansi2html::convert("\e[91mHello\e[0m").should == '<span class="term-fg-l-red">Hello</span>' + end + + it "prints default on light red" do + Ansi2html::convert("\e[101mHello\e[0m").should == '<span class="term-bg-l-red">Hello</span>' + end + + it "performs color change from red/blue to default/blue" do + Ansi2html::convert("\e[31;44mHello \e[39mworld").should == '<span class="term-fg-red term-bg-blue">Hello </span><span class="term-bg-blue">world</span>' + end + + it "performs color change from light red/blue to default/blue" do + Ansi2html::convert("\e[91;44mHello \e[39mworld").should == '<span class="term-fg-l-red term-bg-blue">Hello </span><span class="term-bg-blue">world</span>' + end + + it "prints bold text" do + Ansi2html::convert("\e[1mHello").should == '<span class="term-bold">Hello</span>' + end + + it "resets bold text" do + Ansi2html::convert("\e[1mHello\e[21m world").should == '<span class="term-bold">Hello</span> world' + Ansi2html::convert("\e[1mHello\e[22m world").should == '<span class="term-bold">Hello</span> world' + end + + it "prints italic text" do + Ansi2html::convert("\e[3mHello").should == '<span class="term-italic">Hello</span>' + end + + it "resets italic text" do + Ansi2html::convert("\e[3mHello\e[23m world").should == '<span class="term-italic">Hello</span> world' + end + + it "prints underlined text" do + Ansi2html::convert("\e[4mHello").should == '<span class="term-underline">Hello</span>' + end + + it "resets underlined text" do + Ansi2html::convert("\e[4mHello\e[24m world").should == '<span class="term-underline">Hello</span> world' + end + + it "prints concealed text" do + Ansi2html::convert("\e[8mHello").should == '<span class="term-conceal">Hello</span>' + end + + it "resets concealed text" do + Ansi2html::convert("\e[8mHello\e[28m world").should == '<span class="term-conceal">Hello</span> world' + end + + it "prints crossed-out text" do + Ansi2html::convert("\e[9mHello").should == '<span class="term-cross">Hello</span>' + end + + it "resets crossed-out text" do + Ansi2html::convert("\e[9mHello\e[29m world").should == '<span class="term-cross">Hello</span> world' + end + + it "can print 256 xterm fg colors" do + Ansi2html::convert("\e[38;5;16mHello").should == '<span class="xterm-fg-16">Hello</span>' + end + + it "can print 256 xterm fg colors on normal magenta background" do + Ansi2html::convert("\e[38;5;16;45mHello").should == '<span class="xterm-fg-16 term-bg-magenta">Hello</span>' + end + + it "can print 256 xterm bg colors" do + Ansi2html::convert("\e[48;5;240mHello").should == '<span class="xterm-bg-240">Hello</span>' + end + + it "can print 256 xterm bg colors on normal magenta foreground" do + Ansi2html::convert("\e[48;5;16;35mHello").should == '<span class="term-fg-magenta xterm-bg-16">Hello</span>' + end + + it "prints bold colored text vividly" do + Ansi2html::convert("\e[1;31mHello\e[0m").should == '<span class="term-fg-l-red term-bold">Hello</span>' + end + + it "prints bold light colored text correctly" do + Ansi2html::convert("\e[1;91mHello\e[0m").should == '<span class="term-fg-l-red term-bold">Hello</span>' + end +end diff --git a/spec/ci/lib/charts_spec.rb b/spec/ci/lib/charts_spec.rb new file mode 100644 index 00000000000..236cfc2a1f6 --- /dev/null +++ b/spec/ci/lib/charts_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe "Charts" do + + context "build_times" do + before do + @project = FactoryGirl.create(:project) + @commit = FactoryGirl.create(:commit, project: @project) + FactoryGirl.create(:build, commit: @commit) + end + + it 'should return build times in minutes' do + chart = Charts::BuildTime.new(@project) + chart.build_times.should == [2] + end + end +end diff --git a/spec/ci/lib/gitlab_ci_yaml_processor_spec.rb b/spec/ci/lib/gitlab_ci_yaml_processor_spec.rb new file mode 100644 index 00000000000..ed3d4e84054 --- /dev/null +++ b/spec/ci/lib/gitlab_ci_yaml_processor_spec.rb @@ -0,0 +1,311 @@ +require 'spec_helper' + +describe GitlabCiYamlProcessor do + + describe "#builds_for_ref" do + let (:type) { 'test' } + + it "returns builds if no branch specified" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: {script: "rspec"} + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds_for_stage_and_ref(type, "master").size.should == 1 + config_processor.builds_for_stage_and_ref(type, "master").first.should == { + stage: "test", + except: nil, + name: :rspec, + only: nil, + script: "pwd\nrspec", + tags: [], + options: {}, + allow_failure: false + } + end + + it "does not return builds if only has another branch" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: {script: "rspec", only: ["deploy"]} + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds_for_stage_and_ref(type, "master").size.should == 0 + end + + it "does not return builds if only has regexp with another branch" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: {script: "rspec", only: ["/^deploy$/"]} + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds_for_stage_and_ref(type, "master").size.should == 0 + end + + it "returns builds if only has specified this branch" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: {script: "rspec", only: ["master"]} + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds_for_stage_and_ref(type, "master").size.should == 1 + end + + it "does not build tags" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: {script: "rspec", except: ["tags"]} + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds_for_stage_and_ref(type, "0-1", true).size.should == 0 + end + + it "returns builds if only has a list of branches including specified" do + config = YAML.dump({ + before_script: ["pwd"], + rspec: {script: "rspec", type: type, only: ["master", "deploy"]} + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds_for_stage_and_ref(type, "deploy").size.should == 1 + end + + it "returns build only for specified type" do + + config = YAML.dump({ + before_script: ["pwd"], + build: {script: "build", type: "build", only: ["master", "deploy"]}, + rspec: {script: "rspec", type: type, only: ["master", "deploy"]}, + staging: {script: "deploy", type: "deploy", only: ["master", "deploy"]}, + production: {script: "deploy", type: "deploy", only: ["master", "deploy"]}, + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds_for_stage_and_ref("production", "deploy").size.should == 0 + config_processor.builds_for_stage_and_ref(type, "deploy").size.should == 1 + config_processor.builds_for_stage_and_ref("deploy", "deploy").size.should == 2 + end + end + + describe "Image and service handling" do + it "returns image and service when defined" do + config = YAML.dump({ + image: "ruby:2.1", + services: ["mysql"], + before_script: ["pwd"], + rspec: {script: "rspec"} + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds_for_stage_and_ref("test", "master").size.should == 1 + config_processor.builds_for_stage_and_ref("test", "master").first.should == { + except: nil, + stage: "test", + name: :rspec, + only: nil, + script: "pwd\nrspec", + tags: [], + options: { + image: "ruby:2.1", + services: ["mysql"] + }, + allow_failure: false + } + end + + it "returns image and service when overridden for job" do + config = YAML.dump({ + image: "ruby:2.1", + services: ["mysql"], + before_script: ["pwd"], + rspec: {image: "ruby:2.5", services: ["postgresql"], script: "rspec"} + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds_for_stage_and_ref("test", "master").size.should == 1 + config_processor.builds_for_stage_and_ref("test", "master").first.should == { + except: nil, + stage: "test", + name: :rspec, + only: nil, + script: "pwd\nrspec", + tags: [], + options: { + image: "ruby:2.5", + services: ["postgresql"] + }, + allow_failure: false + } + end + end + + describe "Variables" do + it "returns variables when defined" do + variables = { + var1: "value1", + var2: "value2", + } + config = YAML.dump({ + variables: variables, + before_script: ["pwd"], + rspec: {script: "rspec"} + }) + + config_processor = GitlabCiYamlProcessor.new(config) + config_processor.variables.should == variables + end + end + + describe "Error handling" do + it "indicates that object is invalid" do + expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError) + end + + it "returns errors if tags parameter is invalid" do + config = YAML.dump({rspec: {script: "test", tags: "mysql"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: tags parameter should be an array of strings") + end + + it "returns errors if before_script parameter is invalid" do + config = YAML.dump({before_script: "bundle update", rspec: {script: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "before_script should be an array of strings") + end + + it "returns errors if image parameter is invalid" do + config = YAML.dump({image: ["test"], rspec: {script: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image should be a string") + end + + it "returns errors if job image parameter is invalid" do + config = YAML.dump({rspec: {script: "test", image: ["test"]}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: image should be a string") + end + + it "returns errors if services parameter is not an array" do + config = YAML.dump({services: "test", rspec: {script: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services should be an array of strings") + end + + it "returns errors if services parameter is not an array of strings" do + config = YAML.dump({services: [10, "test"], rspec: {script: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services should be an array of strings") + end + + it "returns errors if job services parameter is not an array" do + config = YAML.dump({rspec: {script: "test", services: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings") + end + + it "returns errors if job services parameter is not an array of strings" do + config = YAML.dump({rspec: {script: "test", services: [10, "test"]}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings") + end + + it "returns errors if there are unknown parameters" do + config = YAML.dump({extra: "bundle update"}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra") + end + + it "returns errors if there are unknown parameters that are hashes, but doesn't have a script" do + config = YAML.dump({extra: {services: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra") + end + + it "returns errors if there is no any jobs defined" do + config = YAML.dump({before_script: ["bundle update"]}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Please define at least one job") + end + + it "returns errors if job allow_failure parameter is not an boolean" do + config = YAML.dump({rspec: {script: "test", allow_failure: "string"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: allow_failure parameter should be an boolean") + end + + it "returns errors if job stage is not a string" do + config = YAML.dump({rspec: {script: "test", type: 1, allow_failure: "string"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy") + end + + it "returns errors if job stage is not a pre-defined stage" do + config = YAML.dump({rspec: {script: "test", type: "acceptance", allow_failure: "string"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy") + end + + it "returns errors if job stage is not a defined stage" do + config = YAML.dump({types: ["build", "test"], rspec: {script: "test", type: "acceptance", allow_failure: "string"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test") + end + + it "returns errors if stages is not an array" do + config = YAML.dump({types: "test", rspec: {script: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages should be an array of strings") + end + + it "returns errors if stages is not an array of strings" do + config = YAML.dump({types: [true, "test"], rspec: {script: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "stages should be an array of strings") + end + + it "returns errors if variables is not a map" do + config = YAML.dump({variables: "test", rspec: {script: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") + end + + it "returns errors if variables is not a map of key-valued strings" do + config = YAML.dump({variables: {test: false}, rspec: {script: "test"}}) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "variables should be a map of key-valued strings") + end + end +end diff --git a/spec/ci/lib/upgrader_spec.rb b/spec/ci/lib/upgrader_spec.rb new file mode 100644 index 00000000000..40a98307ad2 --- /dev/null +++ b/spec/ci/lib/upgrader_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Upgrader do + let(:upgrader) { Upgrader.new } + let(:current_version) { GitlabCi::VERSION } + + describe 'current_version_raw' do + it { upgrader.current_version_raw.should == current_version } + end + + describe 'latest_version?' do + it 'should be true if newest version' do + upgrader.stub(latest_version_raw: current_version) + upgrader.latest_version?.should be_true + end + end + + describe 'latest_version_raw' do + it 'should be latest version for GitlabCI 3' do + allow(upgrader).to receive(:current_version_raw).and_return('3.0.0') + expect(upgrader.latest_version_raw).to eq('v3.2.0') + end + + it 'should get the latest version from tags' do + allow(upgrader).to receive(:fetch_git_tags).and_return([ + '1b5bee25b51724214c7a3307ef94027ab93ec982 refs/tags/v7.8.1', + '424cb42e35947fa304ef83eb211ffc657e31aef3 refs/tags/v7.8.1^{}', + '498e5ba63be1bb99e30c6e720902d864aac4413c refs/tags/v7.9.0.rc1', + '96aaf45ae93bd43e8b3f5d4d353d64d3cbe1e63b refs/tags/v7.9.0.rc1^{}', + '94aaf45ae93bd43e8b3fad4a353d64d3cbe1e62b refs/tags/v7.1.0', + '96aaf45ae93ba13e8b3f5d4d353d64d3cbe1e251 refs/tags/v7.1.0^{}', + '29359d64442bf54b4ca1d8b439fd9e5f9cd83252 refs/tags/v7.10.0', + '4d9213a6378bff43a69ae099702fb81e29335e7a refs/tags/v7.10.0^{}', + '1d93e1626bda93622ca7a2ae2825e2e94dabf3c6 refs/tags/v7.12.0', + '0188a9d1c2efdc52bfad36ad303686be997de713 refs/tags/v7.12.0^{}']) + expect(upgrader.latest_version_raw).to eq("v7.12.0") + end + end +end diff --git a/spec/ci/mailers/notify_spec.rb b/spec/ci/mailers/notify_spec.rb new file mode 100644 index 00000000000..6a2c845cd0e --- /dev/null +++ b/spec/ci/mailers/notify_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Notify do + include EmailSpec::Helpers + include EmailSpec::Matchers + + before do + @project = FactoryGirl.create :project + @commit = FactoryGirl.create :commit, project: @project + @build = FactoryGirl.create :build, commit: @commit + end + + describe 'build success' do + subject { Notify.build_success_email(@build.id, 'wow@example.com') } + + it 'has the correct subject' do + should have_subject /Build success for/ + end + + it 'contains name of project' do + should have_body_text /build successful/ + end + end + + describe 'build fail' do + subject { Notify.build_fail_email(@build.id, 'wow@example.com') } + + it 'has the correct subject' do + should have_subject /Build failed for/ + end + + it 'contains name of project' do + should have_body_text /build failed/ + end + end +end diff --git a/spec/ci/models/build_spec.rb b/spec/ci/models/build_spec.rb new file mode 100644 index 00000000000..733398176bf --- /dev/null +++ b/spec/ci/models/build_spec.rb @@ -0,0 +1,350 @@ +# == Schema Information +# +# Table name: builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# commit_id :integer +# coverage :float +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# + +require 'spec_helper' + +describe Build do + let(:project) { FactoryGirl.create :project } + let(:commit) { FactoryGirl.create :commit, project: project } + let(:build) { FactoryGirl.create :build, commit: commit } + + it { should belong_to(:commit) } + it { should validate_presence_of :status } + + it { should respond_to :success? } + it { should respond_to :failed? } + it { should respond_to :running? } + it { should respond_to :pending? } + it { should respond_to :trace_html } + + describe :first_pending do + let(:first) { FactoryGirl.create :build, commit: commit, status: 'pending', created_at: Date.yesterday } + let(:second) { FactoryGirl.create :build, commit: commit, status: 'pending' } + before { first; second } + subject { Build.first_pending } + + it { should be_a(Build) } + it('returns with the first pending build') { should eq(first) } + end + + describe :create_from do + before do + build.status = 'success' + build.save + end + let(:create_from_build) { Build.create_from build } + + it ('there should be a pending task') do + expect(Build.pending.count(:all)).to eq 0 + create_from_build + expect(Build.pending.count(:all)).to be > 0 + end + end + + describe :started? do + subject { build.started? } + + context 'without started_at' do + before { build.started_at = nil } + + it { should be_false } + end + + %w(running success failed).each do |status| + context "if build status is #{status}" do + before { build.status = status } + + it { should be_true } + end + end + + %w(pending canceled).each do |status| + context "if build status is #{status}" do + before { build.status = status } + + it { should be_false } + end + end + end + + describe :active? do + subject { build.active? } + + %w(pending running).each do |state| + context "if build.status is #{state}" do + before { build.status = state } + + it { should be_true } + end + end + + %w(success failed canceled).each do |state| + context "if build.status is #{state}" do + before { build.status = state } + + it { should be_false } + end + end + end + + describe :complete? do + subject { build.complete? } + + %w(success failed canceled).each do |state| + context "if build.status is #{state}" do + before { build.status = state } + + it { should be_true } + end + end + + %w(pending running).each do |state| + context "if build.status is #{state}" do + before { build.status = state } + + it { should be_false } + end + end + end + + describe :ignored? do + subject { build.ignored? } + + context 'if build is not allowed to fail' do + before { build.allow_failure = false } + + context 'and build.status is success' do + before { build.status = 'success' } + + it { should be_false } + end + + context 'and build.status is failed' do + before { build.status = 'failed' } + + it { should be_false } + end + end + + context 'if build is allowed to fail' do + before { build.allow_failure = true } + + context 'and build.status is success' do + before { build.status = 'success' } + + it { should be_false } + end + + context 'and build.status is failed' do + before { build.status = 'failed' } + + it { should be_true } + end + end + end + + describe :trace do + subject { build.trace_html } + + it { should be_empty } + + context 'if build.trace contains text' do + let(:text) { 'example output' } + before { build.trace = text } + + it { should include(text) } + it { should have_at_least(text.length).items } + end + end + + describe :timeout do + subject { build.timeout } + + it { should eq(commit.project.timeout) } + end + + describe :duration do + subject { build.duration } + + it { should eq(120.0) } + + context 'if the building process has not started yet' do + before do + build.started_at = nil + build.finished_at = nil + end + + it { should be_nil } + end + + context 'if the building process has started' do + before do + build.started_at = Time.now - 1.minute + build.finished_at = nil + end + + it { should be_a(Float) } + it { should > 0.0 } + end + end + + describe :options do + let(:options) { + { + :image => "ruby:2.1", + :services => [ + "postgres" + ] + } + } + + subject { build.options } + it { should eq(options) } + end + + describe :ref do + subject { build.ref } + + it { should eq(commit.ref) } + end + + describe :sha do + subject { build.sha } + + it { should eq(commit.sha) } + end + + describe :short_sha do + subject { build.short_sha } + + it { should eq(commit.short_sha) } + end + + describe :before_sha do + subject { build.before_sha } + + it { should eq(commit.before_sha) } + end + + describe :allow_git_fetch do + subject { build.allow_git_fetch } + + it { should eq(project.allow_git_fetch) } + end + + describe :project do + subject { build.project } + + it { should eq(commit.project) } + end + + describe :project_id do + subject { build.project_id } + + it { should eq(commit.project_id) } + end + + describe :project_name do + subject { build.project_name } + + it { should eq(project.name) } + end + + describe :repo_url do + subject { build.repo_url } + + it { should eq(project.repo_url_with_auth) } + end + + describe :extract_coverage do + context 'valid content & regex' do + subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') } + + it { should eq(98.29) } + end + + context 'valid content & bad regex' do + subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') } + + it { should be_nil } + end + + context 'no coverage content & regex' do + subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') } + + it { should be_nil } + end + + context 'multiple results in content & regex' do + subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') } + + it { should eq(98.29) } + end + end + + describe :variables do + context 'returns variables' do + subject { build.variables } + + let(:variables) { + [ + {key: :DB_NAME, value: 'postgres', public: true} + ] + } + + it { should eq(variables) } + + context 'and secure variables' do + let(:secure_variables) { + [ + {key: 'SECRET_KEY', value: 'secret_value', public: false} + ] + } + + before do + build.project.variables << Variable.new(key: 'SECRET_KEY', value: 'secret_value') + end + + it { should eq(variables + secure_variables) } + + context 'and trigger variables' do + let(:trigger) { FactoryGirl.create :trigger, project: project } + let(:trigger_request) { FactoryGirl.create :trigger_request_with_variables, commit: commit, trigger: trigger } + let(:trigger_variables) { + [ + {key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false} + ] + } + + before do + build.trigger_request = trigger_request + end + + it { should eq(variables + secure_variables + trigger_variables) } + end + end + end + end +end diff --git a/spec/ci/models/commit_spec.rb b/spec/ci/models/commit_spec.rb new file mode 100644 index 00000000000..6f644d20aaf --- /dev/null +++ b/spec/ci/models/commit_spec.rb @@ -0,0 +1,264 @@ +# == Schema Information +# +# Table name: commits +# +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# + +require 'spec_helper' + +describe Commit do + let(:project) { FactoryGirl.create :project } + let(:commit) { FactoryGirl.create :commit, project: project } + let(:commit_with_project) { FactoryGirl.create :commit, project: project } + let(:config_processor) { GitlabCiYamlProcessor.new(gitlab_ci_yaml) } + + it { should belong_to(:project) } + it { should have_many(:builds) } + it { should validate_presence_of :before_sha } + it { should validate_presence_of :sha } + it { should validate_presence_of :ref } + it { should validate_presence_of :push_data } + + it { should respond_to :git_author_name } + it { should respond_to :git_author_email } + it { should respond_to :short_sha } + + describe :last_build do + subject { commit.last_build } + before do + @first = FactoryGirl.create :build, commit: commit, created_at: Date.yesterday + @second = FactoryGirl.create :build, commit: commit + end + + it { should be_a(Build) } + it('returns with the most recently created build') { should eq(@second) } + end + + describe :retry do + before do + @first = FactoryGirl.create :build, commit: commit, created_at: Date.yesterday + @second = FactoryGirl.create :build, commit: commit + end + + it "creates new build" do + expect(commit.builds.count(:all)).to eq 2 + commit.retry + expect(commit.builds.count(:all)).to eq 3 + end + end + + describe :project_recipients do + + context 'always sending notification' do + it 'should return commit_pusher_email as only recipient when no additional recipients are given' do + project = FactoryGirl.create :project, + email_add_pusher: true, + email_recipients: '' + commit = FactoryGirl.create :commit, project: project + expected = 'commit_pusher_email' + commit.stub(:push_data) { { user_email: expected } } + commit.project_recipients.should == [expected] + end + + it 'should return commit_pusher_email and additional recipients' do + project = FactoryGirl.create :project, + email_add_pusher: true, + email_recipients: 'rec1 rec2' + commit = FactoryGirl.create :commit, project: project + expected = 'commit_pusher_email' + commit.stub(:push_data) { { user_email: expected } } + commit.project_recipients.should == ['rec1', 'rec2', expected] + end + + it 'should return recipients' do + project = FactoryGirl.create :project, + email_add_pusher: false, + email_recipients: 'rec1 rec2' + commit = FactoryGirl.create :commit, project: project + commit.project_recipients.should == ['rec1', 'rec2'] + end + + it 'should return unique recipients only' do + project = FactoryGirl.create :project, + email_add_pusher: true, + email_recipients: 'rec1 rec1 rec2' + commit = FactoryGirl.create :commit, project: project + expected = 'rec2' + commit.stub(:push_data) { { user_email: expected } } + commit.project_recipients.should == ['rec1', 'rec2'] + end + end + end + + describe :valid_commit_sha do + context 'commit.sha can not start with 00000000' do + before do + commit.sha = '0' * 40 + commit.valid_commit_sha + end + + it('commit errors should not be empty') { commit.errors.should_not be_empty } + end + end + + describe :compare? do + subject { commit_with_project.compare? } + + context 'if commit.before_sha are not nil' do + it { should be_true } + end + end + + describe :short_sha do + subject { commit.short_before_sha } + + it { should have(8).items } + it { commit.before_sha.should start_with(subject) } + end + + describe :short_sha do + subject { commit.short_sha } + + it { should have(8).items } + it { commit.sha.should start_with(subject) } + end + + describe :create_next_builds do + before do + commit.stub(:config_processor).and_return(config_processor) + end + + it "creates builds for next type" do + commit.create_builds.should be_true + commit.builds.reload + commit.builds.size.should == 2 + + commit.create_next_builds(nil).should be_true + commit.builds.reload + commit.builds.size.should == 4 + + commit.create_next_builds(nil).should be_true + commit.builds.reload + commit.builds.size.should == 5 + + commit.create_next_builds(nil).should be_false + end + end + + describe :create_builds do + before do + commit.stub(:config_processor).and_return(config_processor) + end + + it 'creates builds' do + commit.create_builds.should be_true + commit.builds.reload + commit.builds.size.should == 2 + end + + context 'for build triggers' do + let(:trigger) { FactoryGirl.create :trigger, project: project } + let(:trigger_request) { FactoryGirl.create :trigger_request, commit: commit, trigger: trigger } + + it 'creates builds' do + commit.create_builds(trigger_request).should be_true + commit.builds.reload + commit.builds.size.should == 2 + end + + it 'rebuilds commit' do + commit.create_builds.should be_true + commit.builds.reload + commit.builds.size.should == 2 + + commit.create_builds(trigger_request).should be_true + commit.builds.reload + commit.builds.size.should == 4 + end + + it 'creates next builds' do + commit.create_builds(trigger_request).should be_true + commit.builds.reload + commit.builds.size.should == 2 + + commit.create_next_builds(trigger_request).should be_true + commit.builds.reload + commit.builds.size.should == 4 + end + + context 'for [ci skip]' do + before do + commit.push_data[:commits][0][:message] = 'skip this commit [ci skip]' + commit.save + end + + it 'rebuilds commit' do + commit.status.should == 'skipped' + commit.create_builds(trigger_request).should be_true + commit.builds.reload + commit.builds.size.should == 2 + commit.status.should == 'pending' + end + end + end + end + + describe "#finished_at" do + let(:project) { FactoryGirl.create :project } + let(:commit) { FactoryGirl.create :commit, project: project } + + it "returns finished_at of latest build" do + build = FactoryGirl.create :build, commit: commit, finished_at: Time.now - 60 + build1 = FactoryGirl.create :build, commit: commit, finished_at: Time.now - 120 + + commit.finished_at.to_i.should == build.finished_at.to_i + end + + it "returns nil if there is no finished build" do + build = FactoryGirl.create :not_started_build, commit: commit + + commit.finished_at.should be_nil + end + end + + describe "coverage" do + let(:project) { FactoryGirl.create :project, coverage_regex: "/.*/" } + let(:commit) { FactoryGirl.create :commit, project: project } + + it "calculates average when there are two builds with coverage" do + FactoryGirl.create :build, name: "rspec", coverage: 30, commit: commit + FactoryGirl.create :build, name: "rubocop", coverage: 40, commit: commit + commit.coverage.should == "35.00" + end + + it "calculates average when there are two builds with coverage and one with nil" do + FactoryGirl.create :build, name: "rspec", coverage: 30, commit: commit + FactoryGirl.create :build, name: "rubocop", coverage: 40, commit: commit + FactoryGirl.create :build, commit: commit + commit.coverage.should == "35.00" + end + + it "calculates average when there are two builds with coverage and one is retried" do + FactoryGirl.create :build, name: "rspec", coverage: 30, commit: commit + FactoryGirl.create :build, name: "rubocop", coverage: 30, commit: commit + FactoryGirl.create :build, name: "rubocop", coverage: 40, commit: commit + commit.coverage.should == "35.00" + end + + it "calculates average when there is one build without coverage" do + FactoryGirl.create :build, commit: commit + commit.coverage.should be_nil + end + end +end diff --git a/spec/ci/models/mail_service_spec.rb b/spec/ci/models/mail_service_spec.rb new file mode 100644 index 00000000000..d66a6591f8f --- /dev/null +++ b/spec/ci/models/mail_service_spec.rb @@ -0,0 +1,184 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +require 'spec_helper' + +describe MailService do + describe "Associations" do + it { should belong_to :project } + end + + describe "Validations" do + context "active" do + before do + subject.active = true + end + end + end + + describe 'Sends email for' do + let(:mail) { MailService.new } + + describe 'failed build' do + let(:project) { FactoryGirl.create(:project, email_add_pusher: true) } + let(:commit) { FactoryGirl.create(:commit, project: project) } + let(:build) { FactoryGirl.create(:build, status: :failed, commit: commit) } + + before do + mail.stub( + project: project + ) + end + + it do + should_email("git@example.com") + mail.execute(build) + end + + def should_email(email) + Notify.should_receive(:build_fail_email).with(build.id, email) + Notify.should_not_receive(:build_success_email).with(build.id, email) + end + end + + describe 'successfull build' do + let(:project) { FactoryGirl.create(:project, email_add_pusher: true, email_only_broken_builds: false) } + let(:commit) { FactoryGirl.create(:commit, project: project) } + let(:build) { FactoryGirl.create(:build, status: :success, commit: commit) } + + before do + mail.stub( + project: project + ) + end + + it do + should_email("git@example.com") + mail.execute(build) + end + + def should_email(email) + Notify.should_receive(:build_success_email).with(build.id, email) + Notify.should_not_receive(:build_fail_email).with(build.id, email) + end + end + + describe 'successfull build and project has email_recipients' do + let(:project) { + FactoryGirl.create(:project, + email_add_pusher: true, + email_only_broken_builds: false, + email_recipients: "jeroen@example.com") + } + let(:commit) { FactoryGirl.create(:commit, project: project) } + let(:build) { FactoryGirl.create(:build, status: :success, commit: commit) } + + before do + mail.stub( + project: project + ) + end + + it do + should_email("git@example.com") + should_email("jeroen@example.com") + mail.execute(build) + end + + def should_email(email) + Notify.should_receive(:build_success_email).with(build.id, email) + Notify.should_not_receive(:build_fail_email).with(build.id, email) + end + end + + describe 'successful build and notify only broken builds' do + let(:project) { + FactoryGirl.create(:project, + email_add_pusher: true, + email_only_broken_builds: true, + email_recipients: "jeroen@example.com") + } + let(:commit) { FactoryGirl.create(:commit, project: project) } + let(:build) { FactoryGirl.create(:build, status: :success, commit: commit) } + + before do + mail.stub( + project: project + ) + end + + it do + should_email(commit.git_author_email) + should_email("jeroen@example.com") + mail.execute(build) if mail.can_execute?(build) + end + + def should_email(email) + Notify.should_not_receive(:build_success_email).with(build.id, email) + Notify.should_not_receive(:build_fail_email).with(build.id, email) + end + end + + describe 'successful build and can test service' do + let(:project) { + FactoryGirl.create(:project, + email_add_pusher: true, + email_only_broken_builds: false, + email_recipients: "jeroen@example.com") + } + let(:commit) { FactoryGirl.create(:commit, project: project) } + let(:build) { FactoryGirl.create(:build, status: :success, commit: commit) } + + before do + mail.stub( + project: project + ) + build + end + + it do + mail.can_test?.should == true + end + end + + describe 'retried build should not receive email' do + let(:project) { + FactoryGirl.create(:project, + email_add_pusher: true, + email_only_broken_builds: true, + email_recipients: "jeroen@example.com") + } + let(:commit) { FactoryGirl.create(:commit, project: project) } + let(:build) { FactoryGirl.create(:build, status: :failed, commit: commit) } + + before do + mail.stub( + project: project + ) + end + + it do + Build.retry(build) + should_email(commit.git_author_email) + should_email("jeroen@example.com") + mail.execute(build) if mail.can_execute?(build) + end + + def should_email(email) + Notify.should_not_receive(:build_success_email).with(build.id, email) + Notify.should_not_receive(:build_fail_email).with(build.id, email) + end + end + end +end diff --git a/spec/ci/models/network_spec.rb b/spec/ci/models/network_spec.rb new file mode 100644 index 00000000000..b80adba5b08 --- /dev/null +++ b/spec/ci/models/network_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Network do + let(:network) { Network.new } + + describe :enable_ci do + subject { network.enable_ci '', '', '' } + + context 'on success' do + before do + response = double + response.stub(:code) { 200 } + network.class.stub(:put) { response } + end + + it { should be_true } + end + + context 'on failure' do + before do + response = double + response.stub(:code) { 404 } + network.class.stub(:put) { response } + end + + it { should be_nil } + end + end + + describe :disable_ci do + let(:response) { double } + subject { network.disable_ci '', '' } + + context 'on success' do + let(:parsed_response) { 'parsed' } + before do + response.stub(:code) { 200 } + response.stub(:parsed_response) { parsed_response } + network.class.stub(:delete) { response } + end + + it { should equal(parsed_response) } + end + + context 'on failure' do + before do + response.stub(:code) { 404 } + network.class.stub(:delete) { response } + end + + it { should be_nil } + end + end +end diff --git a/spec/ci/models/project_services/hip_chat_message_spec.rb b/spec/ci/models/project_services/hip_chat_message_spec.rb new file mode 100644 index 00000000000..f1ad875ebcf --- /dev/null +++ b/spec/ci/models/project_services/hip_chat_message_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe HipChatMessage do + subject { HipChatMessage.new(build) } + + let(:project) { FactoryGirl.create(:project) } + + context "One build" do + let(:commit) { FactoryGirl.create(:commit_with_one_job, project: project) } + + let(:build) do + commit.create_builds + commit.builds.first + end + + context 'when build succeeds' do + it 'returns a successful message' do + build.update(status: "success") + + expect( subject.status_color ).to eq 'green' + expect( subject.notify? ).to be_false + expect( subject.to_s ).to match(/Build '[^']+' #\d+/) + expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./) + end + end + + context 'when build fails' do + it 'returns a failure message' do + build.update(status: "failed") + + expect( subject.status_color ).to eq 'red' + expect( subject.notify? ).to be_true + expect( subject.to_s ).to match(/Build '[^']+' #\d+/) + expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./) + end + end + end + + context "Several builds" do + let(:commit) { FactoryGirl.create(:commit_with_two_jobs, project: project) } + + let(:build) do + commit.builds.first + end + + context 'when all matrix builds succeed' do + it 'returns a successful message' do + commit.create_builds + commit.builds.update_all(status: "success") + commit.reload + + expect( subject.status_color ).to eq 'green' + expect( subject.notify? ).to be_false + expect( subject.to_s ).to match(/Commit #\d+/) + expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./) + end + end + + context 'when at least one matrix build fails' do + it 'returns a failure message' do + commit.create_builds + first_build = commit.builds.first + second_build = commit.builds.last + first_build.update(status: "success") + second_build.update(status: "failed") + + expect( subject.status_color ).to eq 'red' + expect( subject.notify? ).to be_true + expect( subject.to_s ).to match(/Commit #\d+/) + expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./) + end + end + end +end diff --git a/spec/ci/models/project_services/hip_chat_service_spec.rb b/spec/ci/models/project_services/hip_chat_service_spec.rb new file mode 100644 index 00000000000..37ce4905af8 --- /dev/null +++ b/spec/ci/models/project_services/hip_chat_service_spec.rb @@ -0,0 +1,75 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + + +require 'spec_helper' + +describe HipChatService do + + describe "Validations" do + + context "active" do + before do + subject.active = true + end + + it { should validate_presence_of :hipchat_room } + it { should validate_presence_of :hipchat_token } + + end + end + + describe "Execute" do + + let(:service) { HipChatService.new } + let(:project) { FactoryGirl.create :project } + let(:commit) { FactoryGirl.create :commit, project: project } + let(:build) { FactoryGirl.create :build, commit: commit, status: 'failed' } + let(:api_url) { 'https://api.hipchat.com/v2/room/123/notification?auth_token=a1b2c3d4e5f6' } + + before do + service.stub( + project: project, + project_id: project.id, + notify_only_broken_builds: false, + hipchat_room: 123, + hipchat_token: 'a1b2c3d4e5f6' + ) + + WebMock.stub_request(:post, api_url) + end + + + it "should call the HipChat API" do + service.execute(build) + HipChatNotifierWorker.drain + + expect( WebMock ).to have_requested(:post, api_url).once + end + + it "calls the worker with expected arguments" do + expect( HipChatNotifierWorker ).to receive(:perform_async) \ + .with(an_instance_of(String), hash_including( + token: 'a1b2c3d4e5f6', + room: 123, + server: 'https://api.hipchat.com', + color: 'red', + notify: true + )) + + service.execute(build) + end + end +end + diff --git a/spec/ci/models/project_services/slack_message_spec.rb b/spec/ci/models/project_services/slack_message_spec.rb new file mode 100644 index 00000000000..88e0f373206 --- /dev/null +++ b/spec/ci/models/project_services/slack_message_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +describe SlackMessage do + subject { SlackMessage.new(commit) } + + let(:project) { FactoryGirl.create :project } + + context "One build" do + let(:commit) { FactoryGirl.create(:commit_with_one_job, project: project) } + + let(:build) do + commit.create_builds + commit.builds.first + end + + context 'when build succeeded' do + let(:color) { 'good' } + + it 'returns a message with succeeded build' do + build.update(status: "success") + + subject.color.should == color + subject.fallback.should include('Build') + subject.fallback.should include("\##{build.id}") + subject.fallback.should include('succeeded') + subject.attachments.first[:fields].should be_empty + end + end + + context 'when build failed' do + let(:color) { 'danger' } + + it 'returns a message with failed build' do + build.update(status: "failed") + + subject.color.should == color + subject.fallback.should include('Build') + subject.fallback.should include("\##{build.id}") + subject.fallback.should include('failed') + subject.attachments.first[:fields].should be_empty + end + end + end + + context "Several builds" do + let(:commit) { FactoryGirl.create(:commit_with_two_jobs, project: project) } + + context 'when all matrix builds succeeded' do + let(:color) { 'good' } + + it 'returns a message with success' do + commit.create_builds + commit.builds.update_all(status: "success") + commit.reload + + subject.color.should == color + subject.fallback.should include('Commit') + subject.fallback.should include("\##{commit.id}") + subject.fallback.should include('succeeded') + subject.attachments.first[:fields].should be_empty + end + end + + context 'when one of matrix builds failed' do + let(:color) { 'danger' } + + it 'returns a message with information about failed build' do + commit.create_builds + first_build = commit.builds.first + second_build = commit.builds.last + first_build.update(status: "success") + second_build.update(status: "failed") + + subject.color.should == color + subject.fallback.should include('Commit') + subject.fallback.should include("\##{commit.id}") + subject.fallback.should include('failed') + subject.attachments.first[:fields].size.should == 1 + subject.attachments.first[:fields].first[:title].should == second_build.name + subject.attachments.first[:fields].first[:value].should include("\##{second_build.id}") + end + end + end +end diff --git a/spec/ci/models/project_services/slack_service_spec.rb b/spec/ci/models/project_services/slack_service_spec.rb new file mode 100644 index 00000000000..e1c14281274 --- /dev/null +++ b/spec/ci/models/project_services/slack_service_spec.rb @@ -0,0 +1,58 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +require 'spec_helper' + +describe SlackService do + describe "Associations" do + it { should belong_to :project } + end + + describe "Validations" do + context "active" do + before do + subject.active = true + end + + it { should validate_presence_of :webhook } + end + end + + describe "Execute" do + let(:slack) { SlackService.new } + let(:project) { FactoryGirl.create :project } + let(:commit) { FactoryGirl.create :commit, project: project } + let(:build) { FactoryGirl.create :build, commit: commit, status: 'failed' } + let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } + let(:notify_only_broken_builds) { false } + + before do + slack.stub( + project: project, + project_id: project.id, + webhook: webhook_url, + notify_only_broken_builds: notify_only_broken_builds + ) + + WebMock.stub_request(:post, webhook_url) + end + + it "should call Slack API" do + slack.execute(build) + SlackNotifierWorker.drain + + WebMock.should have_requested(:post, webhook_url).once + end + end +end diff --git a/spec/ci/models/project_spec.rb b/spec/ci/models/project_spec.rb new file mode 100644 index 00000000000..aa76b99154b --- /dev/null +++ b/spec/ci/models/project_spec.rb @@ -0,0 +1,185 @@ +# == Schema Information +# +# Table name: projects +# +# id :integer not null, primary key +# name :string(255) not null +# timeout :integer default(3600), not null +# created_at :datetime +# updated_at :datetime +# token :string(255) +# default_ref :string(255) +# path :string(255) +# always_build :boolean default(FALSE), not null +# polling_interval :integer +# public :boolean default(FALSE), not null +# ssh_url_to_repo :string(255) +# gitlab_id :integer +# allow_git_fetch :boolean default(TRUE), not null +# email_recipients :string(255) default(""), not null +# email_add_pusher :boolean default(TRUE), not null +# email_only_broken_builds :boolean default(TRUE), not null +# skip_refs :string(255) +# coverage_regex :string(255) +# shared_runners_enabled :boolean default(FALSE) +# generated_yaml_config :text +# + +require 'spec_helper' + +describe Project do + subject { FactoryGirl.build :project } + + it { should have_many(:commits) } + + it { should validate_presence_of :name } + it { should validate_presence_of :timeout } + it { should validate_presence_of :default_ref } + + describe 'before_validation' do + it 'should set an random token if none provided' do + project = FactoryGirl.create :project_without_token + project.token.should_not == "" + end + + it 'should not set an random toke if one provided' do + project = FactoryGirl.create :project + project.token.should == "iPWx6WM4lhHNedGfBpPJNP" + end + end + + describe "ordered_by_last_commit_date" do + it "returns ordered projects" do + newest_project = FactoryGirl.create :project + oldest_project = FactoryGirl.create :project + project_without_commits = FactoryGirl.create :project + + FactoryGirl.create :commit, committed_at: 1.hour.ago, project: newest_project + FactoryGirl.create :commit, committed_at: 2.hour.ago, project: oldest_project + + Project.ordered_by_last_commit_date.should == [newest_project, oldest_project, project_without_commits] + end + end + + context :valid_project do + let(:project) { FactoryGirl.create :project } + + context :project_with_commit_and_builds do + before do + commit = FactoryGirl.create(:commit, project: project) + FactoryGirl.create(:build, commit: commit) + end + + it { project.status.should == 'pending' } + it { project.last_commit.should be_kind_of(Commit) } + it { project.human_status.should == 'pending' } + end + end + + describe '#email_notification?' do + it do + project = FactoryGirl.create :project, email_add_pusher: true + project.email_notification?.should == true + end + + it do + project = FactoryGirl.create :project, email_add_pusher: false, email_recipients: 'test tesft' + project.email_notification?.should == true + end + + it do + project = FactoryGirl.create :project, email_add_pusher: false, email_recipients: '' + project.email_notification?.should == false + end + end + + describe '#broken_or_success?' do + it { + project = FactoryGirl.create :project, email_add_pusher: true + project.stub(:broken?).and_return(true) + project.stub(:success?).and_return(true) + project.broken_or_success?.should == true + } + + it { + project = FactoryGirl.create :project, email_add_pusher: true + project.stub(:broken?).and_return(true) + project.stub(:success?).and_return(false) + project.broken_or_success?.should == true + } + + it { + project = FactoryGirl.create :project, email_add_pusher: true + project.stub(:broken?).and_return(false) + project.stub(:success?).and_return(true) + project.broken_or_success?.should == true + } + + it { + project = FactoryGirl.create :project, email_add_pusher: true + project.stub(:broken?).and_return(false) + project.stub(:success?).and_return(false) + project.broken_or_success?.should == false + } + end + + describe 'Project.parse' do + let(:project_dump) { YAML.load File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } + let(:parsed_project) { Project.parse(project_dump) } + + + it { parsed_project.should be_valid } + it { parsed_project.should be_kind_of(Project) } + it { parsed_project.name.should eq("GitLab / api.gitlab.org") } + it { parsed_project.gitlab_id.should eq(189) } + it { parsed_project.gitlab_url.should eq("http://demo.gitlab.com/gitlab/api-gitlab-org") } + + it "parses plain hash" do + Project.parse(project_dump).name.should eq("GitLab / api.gitlab.org") + end + end + + describe :repo_url_with_auth do + let(:project) { FactoryGirl.create :project } + subject { project.repo_url_with_auth } + + it { should be_a(String) } + it { should end_with(".git") } + it { should start_with(project.gitlab_url[0..6]) } + it { should include(project.token) } + it { should include('gitlab-ci-token') } + it { should include(project.gitlab_url[7..-1]) } + end + + describe :search do + let!(:project) { FactoryGirl.create(:project, name: "foo") } + + it { Project.search('fo').should include(project) } + it { Project.search('bar').should be_empty } + end + + describe :any_runners do + it "there are no runners available" do + project = FactoryGirl.create(:project) + project.any_runners?.should be_false + end + + it "there is a specific runner" do + project = FactoryGirl.create(:project) + project.runners << FactoryGirl.create(:specific_runner) + project.any_runners?.should be_true + end + + it "there is a shared runner" do + project = FactoryGirl.create(:project, shared_runners_enabled: true) + FactoryGirl.create(:shared_runner) + project.any_runners?.should be_true + end + + it "there is a shared runner, but they are prohibited to use" do + project = FactoryGirl.create(:project) + FactoryGirl.create(:shared_runner) + project.any_runners?.should be_false + end + end +end diff --git a/spec/ci/models/runner_project_spec.rb b/spec/ci/models/runner_project_spec.rb new file mode 100644 index 00000000000..cbefb24705a --- /dev/null +++ b/spec/ci/models/runner_project_spec.rb @@ -0,0 +1,16 @@ +# == Schema Information +# +# Table name: runner_projects +# +# id :integer not null, primary key +# runner_id :integer not null +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +require 'spec_helper' + +describe RunnerProject do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/ci/models/runner_spec.rb b/spec/ci/models/runner_spec.rb new file mode 100644 index 00000000000..6902c0a94e6 --- /dev/null +++ b/spec/ci/models/runner_spec.rb @@ -0,0 +1,70 @@ +# == Schema Information +# +# Table name: runners +# +# id :integer not null, primary key +# token :string(255) +# created_at :datetime +# updated_at :datetime +# description :string(255) +# contacted_at :datetime +# active :boolean default(TRUE), not null +# is_shared :boolean default(FALSE) +# name :string(255) +# version :string(255) +# revision :string(255) +# platform :string(255) +# architecture :string(255) +# + +require 'spec_helper' + +describe Runner do + describe '#display_name' do + it 'should return the description if it has a value' do + runner = FactoryGirl.build(:runner, description: 'Linux/Ruby-1.9.3-p448') + expect(runner.display_name).to eq 'Linux/Ruby-1.9.3-p448' + end + + it 'should return the token if it does not have a description' do + runner = FactoryGirl.create(:runner) + expect(runner.display_name).to eq runner.description + end + + it 'should return the token if the description is an empty string' do + runner = FactoryGirl.build(:runner, description: '') + expect(runner.display_name).to eq runner.token + end + end + + describe :assign_to do + let!(:project) { FactoryGirl.create :project } + let!(:shared_runner) { FactoryGirl.create(:shared_runner) } + + before { shared_runner.assign_to(project) } + + it { shared_runner.should be_specific } + it { shared_runner.projects.should == [project] } + it { shared_runner.only_for?(project).should be_true } + end + + describe "belongs_to_one_project?" do + it "returns false if there are two projects runner assigned to" do + runner = FactoryGirl.create(:specific_runner) + project = FactoryGirl.create(:project) + project1 = FactoryGirl.create(:project) + project.runners << runner + project1.runners << runner + + runner.belongs_to_one_project?.should be_false + end + + it "returns true" do + runner = FactoryGirl.create(:specific_runner) + project = FactoryGirl.create(:project) + project.runners << runner + + runner.belongs_to_one_project?.should be_true + end + end +end diff --git a/spec/ci/models/service_spec.rb b/spec/ci/models/service_spec.rb new file mode 100644 index 00000000000..22a49e10a6c --- /dev/null +++ b/spec/ci/models/service_spec.rb @@ -0,0 +1,49 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +require 'spec_helper' + +describe Service do + + describe "Associations" do + it { should belong_to :project } + end + + describe "Mass assignment" do + end + + describe "Test Button" do + before do + @service = Service.new + end + + describe "Testable" do + let (:project) { FactoryGirl.create :project } + let (:commit) { FactoryGirl.create :commit, project: project } + let (:build) { FactoryGirl.create :build, commit: commit } + + before do + @service.stub( + project: project + ) + build + @testable = @service.can_test? + end + + describe :can_test do + it { @testable.should == true } + end + end + end +end diff --git a/spec/ci/models/trigger_spec.rb b/spec/ci/models/trigger_spec.rb new file mode 100644 index 00000000000..bba638e7817 --- /dev/null +++ b/spec/ci/models/trigger_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Trigger do + let(:project) { FactoryGirl.create :project } + + describe 'before_validation' do + it 'should set an random token if none provided' do + trigger = FactoryGirl.create :trigger_without_token, project: project + trigger.token.should_not be_nil + end + + it 'should not set an random token if one provided' do + trigger = FactoryGirl.create :trigger, project: project + trigger.token.should == 'token' + end + end +end diff --git a/spec/ci/models/user_spec.rb b/spec/ci/models/user_spec.rb new file mode 100644 index 00000000000..73a7a7d5fbc --- /dev/null +++ b/spec/ci/models/user_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +describe User do + + describe "has_developer_access?" do + before do + @user = User.new({}) + end + + let(:project_with_owner_access) do + { + "name" => "gitlab-shell", + "permissions" => { + "project_access" => { + "access_level"=> 10, + "notification_level" => 3 + }, + "group_access" => { + "access_level" => 50, + "notification_level" => 3 + } + } + } + end + + let(:project_with_reporter_access) do + { + "name" => "gitlab-shell", + "permissions" => { + "project_access" => { + "access_level" => 20, + "notification_level" => 3 + }, + "group_access" => { + "access_level" => 10, + "notification_level" => 3 + } + } + } + end + + it "returns false for reporter" do + @user.stub(:project_info).and_return(project_with_reporter_access) + + @user.has_developer_access?(1).should be_false + end + + it "returns true for owner" do + @user.stub(:project_info).and_return(project_with_owner_access) + + @user.has_developer_access?(1).should be_true + end + end + + describe "authorized_projects" do + let (:user) { User.new({}) } + + before do + FactoryGirl.create :project, gitlab_id: 1 + FactoryGirl.create :project, gitlab_id: 2 + gitlab_project = OpenStruct.new({id: 1}) + gitlab_project1 = OpenStruct.new({id: 2}) + User.any_instance.stub(:gitlab_projects).and_return([gitlab_project, gitlab_project1]) + end + + it "returns projects" do + User.any_instance.stub(:can_manage_project?).and_return(true) + + user.authorized_projects.count.should == 2 + end + + it "empty list if user miss manage permission" do + User.any_instance.stub(:can_manage_project?).and_return(false) + + user.authorized_projects.count.should == 0 + end + end + + describe "authorized_runners" do + it "returns authorized runners" do + project = FactoryGirl.create :project, gitlab_id: 1 + project1 = FactoryGirl.create :project, gitlab_id: 2 + gitlab_project = OpenStruct.new({id: 1}) + gitlab_project1 = OpenStruct.new({id: 2}) + User.any_instance.stub(:gitlab_projects).and_return([gitlab_project, gitlab_project1]) + User.any_instance.stub(:can_manage_project?).and_return(true) + user = User.new({}) + + runner = FactoryGirl.create :specific_runner + runner1 = FactoryGirl.create :specific_runner + runner2 = FactoryGirl.create :specific_runner + + project.runners << runner + project1.runners << runner1 + + user.authorized_runners.should include(runner, runner1) + user.authorized_runners.should_not include(runner2) + end + end +end diff --git a/spec/ci/models/variable_spec.rb b/spec/ci/models/variable_spec.rb new file mode 100644 index 00000000000..4575115ccfb --- /dev/null +++ b/spec/ci/models/variable_spec.rb @@ -0,0 +1,44 @@ +# == Schema Information +# +# Table name: variables +# +# id :integer not null, primary key +# project_id :integer not null +# key :string(255) +# value :text +# encrypted_value :text +# encrypted_value_salt :string(255) +# encrypted_value_iv :string(255) +# + +require 'spec_helper' + +describe Variable do + subject { Variable.new } + + let(:secret_value) { 'secret' } + + before :each do + subject.value = secret_value + end + + describe :value do + it 'stores the encrypted value' do + subject.encrypted_value.should_not be_nil + end + + it 'stores an iv for value' do + subject.encrypted_value_iv.should_not be_nil + end + + it 'stores a salt for value' do + subject.encrypted_value_salt.should_not be_nil + end + + it 'fails to decrypt if iv is incorrect' do + subject.encrypted_value_iv = nil + subject.instance_variable_set(:@value, nil) + expect { subject.value }.to raise_error + end + end +end diff --git a/spec/ci/models/web_hook_spec.rb b/spec/ci/models/web_hook_spec.rb new file mode 100644 index 00000000000..0f0f175a7a3 --- /dev/null +++ b/spec/ci/models/web_hook_spec.rb @@ -0,0 +1,64 @@ +# == Schema Information +# +# Table name: web_hooks +# +# id :integer not null, primary key +# url :string(255) not null +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# + +require 'spec_helper' + +describe WebHook do + describe "Associations" do + it { should belong_to :project } + end + + describe "Validations" do + it { should validate_presence_of(:url) } + + context "url format" do + it { should allow_value("http://example.com").for(:url) } + it { should allow_value("https://excample.com").for(:url) } + it { should allow_value("http://test.com/api").for(:url) } + it { should allow_value("http://test.com/api?key=abc").for(:url) } + it { should allow_value("http://test.com/api?key=abc&type=def").for(:url) } + + it { should_not allow_value("example.com").for(:url) } + it { should_not allow_value("ftp://example.com").for(:url) } + it { should_not allow_value("herp-and-derp").for(:url) } + end + end + + describe "execute" do + before(:each) do + @web_hook = FactoryGirl.create(:web_hook) + @project = @web_hook.project + @data = { before: 'oldrev', after: 'newrev', ref: 'ref'} + + WebMock.stub_request(:post, @web_hook.url) + end + + it "POSTs to the web hook URL" do + @web_hook.execute(@data) + WebMock.should have_requested(:post, @web_hook.url).once + end + + it "POSTs the data as JSON" do + json = @data.to_json + + @web_hook.execute(@data) + WebMock.should have_requested(:post, @web_hook.url).with(body: json).once + end + + it "catches exceptions" do + WebHook.should_receive(:post).and_raise("Some HTTP Post error") + + lambda { + @web_hook.execute(@data) + }.should raise_error + end + end +end diff --git a/spec/ci/requests/api/builds_spec.rb b/spec/ci/requests/api/builds_spec.rb new file mode 100644 index 00000000000..be55e9ff479 --- /dev/null +++ b/spec/ci/requests/api/builds_spec.rb @@ -0,0 +1,115 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + + let(:runner) { FactoryGirl.create(:runner, tag_list: ["mysql", "ruby"]) } + let(:project) { FactoryGirl.create(:project) } + + describe "Builds API for runners" do + let(:shared_runner) { FactoryGirl.create(:runner, token: "SharedRunner") } + let(:shared_project) { FactoryGirl.create(:project, name: "SharedProject") } + + before do + FactoryGirl.create :runner_project, project_id: project.id, runner_id: runner.id + end + + describe "POST /builds/register" do + it "should start a build" do + commit = FactoryGirl.create(:commit, project: project) + commit.create_builds + build = commit.builds.first + + post api("/builds/register"), token: runner.token, info: {platform: :darwin} + + response.status.should == 201 + json_response['sha'].should == build.sha + runner.reload.platform.should == "darwin" + end + + it "should return 404 error if no pending build found" do + post api("/builds/register"), token: runner.token + + response.status.should == 404 + end + + it "should return 404 error if no builds for specific runner" do + commit = FactoryGirl.create(:commit, project: shared_project) + FactoryGirl.create(:build, commit: commit, status: 'pending' ) + + post api("/builds/register"), token: runner.token + + response.status.should == 404 + end + + it "should return 404 error if no builds for shared runner" do + commit = FactoryGirl.create(:commit, project: project) + FactoryGirl.create(:build, commit: commit, status: 'pending' ) + + post api("/builds/register"), token: shared_runner.token + + response.status.should == 404 + end + + it "returns options" do + commit = FactoryGirl.create(:commit, project: project) + commit.create_builds + + post api("/builds/register"), token: runner.token, info: {platform: :darwin} + + response.status.should == 201 + json_response["options"].should == {"image" => "ruby:2.1", "services" => ["postgres"]} + end + + it "returns variables" do + commit = FactoryGirl.create(:commit, project: project) + commit.create_builds + project.variables << Variable.new(key: "SECRET_KEY", value: "secret_value") + + post api("/builds/register"), token: runner.token, info: {platform: :darwin} + + response.status.should == 201 + json_response["variables"].should == [ + {"key" => "DB_NAME", "value" => "postgres", "public" => true}, + {"key" => "SECRET_KEY", "value" => "secret_value", "public" => false}, + ] + end + + it "returns variables for triggers" do + trigger = FactoryGirl.create(:trigger, project: project) + commit = FactoryGirl.create(:commit, project: project) + + trigger_request = FactoryGirl.create(:trigger_request_with_variables, commit: commit, trigger: trigger) + commit.create_builds(trigger_request) + project.variables << Variable.new(key: "SECRET_KEY", value: "secret_value") + + post api("/builds/register"), token: runner.token, info: {platform: :darwin} + + response.status.should == 201 + json_response["variables"].should == [ + {"key" => "DB_NAME", "value" => "postgres", "public" => true}, + {"key" => "SECRET_KEY", "value" => "secret_value", "public" => false}, + {"key" => "TRIGGER_KEY", "value" => "TRIGGER_VALUE", "public" => false}, + ] + end + end + + describe "PUT /builds/:id" do + let(:commit) { FactoryGirl.create(:commit, project: project)} + let(:build) { FactoryGirl.create(:build, commit: commit, runner_id: runner.id) } + + it "should update a running build" do + build.run! + put api("/builds/#{build.id}"), token: runner.token + response.status.should == 200 + end + + it 'Should not override trace information when no trace is given' do + build.run! + build.update!(trace: 'hello_world') + put api("/builds/#{build.id}"), token: runner.token + expect(build.reload.trace).to eq 'hello_world' + end + end + end +end diff --git a/spec/ci/requests/api/commits_spec.rb b/spec/ci/requests/api/commits_spec.rb new file mode 100644 index 00000000000..190df70c1a5 --- /dev/null +++ b/spec/ci/requests/api/commits_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe API::API, 'Commits' do + include ApiHelpers + + let(:project) { FactoryGirl.create(:project) } + let(:commit) { FactoryGirl.create(:commit, project: project) } + + let(:options) { + { + project_token: project.token, + project_id: project.id + } + } + + describe "GET /commits" do + before { commit } + + it "should return commits per project" do + get api("/commits"), options + + response.status.should == 200 + json_response.count.should == 1 + json_response.first["project_id"].should == project.id + json_response.first["sha"].should == commit.sha + end + end + + describe "POST /commits" do + let(:data) { + { + "before" => "95790bf891e76fee5e1747ab589903a6a1f80f22", + "after" => "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "ref" => "refs/heads/master", + "commits" => [ + { + "id" => "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "message" => "Update Catalan translation to e38cb41.", + "timestamp" => "2011-12-12T14:27:31+02:00", + "url" => "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "author" => { + "name" => "Jordi Mallach", + "email" => "jordi@softcatala.org", + } + } + ], + ci_yaml_file: gitlab_ci_yaml + } + } + + it "should create a build" do + post api("/commits"), options.merge(data: data) + + response.status.should == 201 + json_response['sha'].should == "da1560886d4f094c3e6c9ef40349f7d38b5d27d7" + end + + it "should return 400 error if no data passed" do + post api("/commits"), options + + response.status.should == 400 + json_response['message'].should == "400 (Bad request) \"data\" not given" + end + end +end diff --git a/spec/ci/requests/api/forks_spec.rb b/spec/ci/requests/api/forks_spec.rb new file mode 100644 index 00000000000..af523421c65 --- /dev/null +++ b/spec/ci/requests/api/forks_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + + let(:project) { FactoryGirl.create(:project) } + let(:gitlab_url) { GitlabCi.config.gitlab_server.url } + let(:private_token) { Network.new.authenticate(access_token: "some_token")["private_token"] } + + let(:options) { + { + private_token: private_token, + url: gitlab_url + } + } + + before { + stub_gitlab_calls + } + + + describe "POST /forks" do + let(:project_info) { + { + project_id: project.gitlab_id, + project_token: project.token, + data: { + id: 2, + name_with_namespace: "Gitlab.org / Underscore", + path_with_namespace: "gitlab-org/underscore", + default_branch: "master", + ssh_url_to_repo: "git@example.com:gitlab-org/underscore" + } + } + } + + context "with valid info" do + before do + options.merge!(project_info) + end + + it "should create a project with valid data" do + post api("/forks"), options + response.status.should == 201 + json_response['name'].should == "Gitlab.org / Underscore" + end + end + + context "with invalid project info" do + before do + options.merge!({}) + end + + it "should error with invalid data" do + post api("/forks"), options + response.status.should == 400 + end + end + end +end diff --git a/spec/ci/requests/api/projects_spec.rb b/spec/ci/requests/api/projects_spec.rb new file mode 100644 index 00000000000..014a9efc617 --- /dev/null +++ b/spec/ci/requests/api/projects_spec.rb @@ -0,0 +1,251 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + + let(:gitlab_url) { GitlabCi.config.gitlab_server.url } + let(:private_token) { Network.new.authenticate(access_token: "some_token")["private_token"] } + + let(:options) { + { + private_token: private_token, + url: gitlab_url + } + } + + before { + stub_gitlab_calls + } + + context "requests for scoped projects" do + # NOTE: These ids are tied to the actual projects on demo.gitlab.com + describe "GET /projects" do + let!(:project1) { FactoryGirl.create(:project, name: "gitlabhq", gitlab_id: 3) } + let!(:project2) { FactoryGirl.create(:project, name: "gitlab-ci", gitlab_id: 4) } + + it "should return all projects on the CI instance" do + get api("/projects"), options + response.status.should == 200 + json_response.count.should == 2 + json_response.first["id"].should == project1.id + json_response.last["id"].should == project2.id + end + end + + describe "GET /projects/owned" do + # NOTE: This user doesn't own any of these projects on demo.gitlab.com + let!(:project1) { FactoryGirl.create(:project, name: "gitlabhq", gitlab_id: 3) } + let!(:project2) { FactoryGirl.create(:project, name: "random-project", gitlab_id: 9898) } + + it "should return all projects on the CI instance" do + get api("/projects/owned"), options + + response.status.should == 200 + json_response.count.should == 0 + end + end + end + + describe "POST /projects/:project_id/webhooks" do + let!(:project) { FactoryGirl.create(:project) } + + context "Valid Webhook URL" do + let!(:webhook) { {web_hook: "http://example.com/sth/1/ala_ma_kota" } } + + before do + options.merge!(webhook) + end + + it "should create webhook for specified project" do + post api("/projects/#{project.id}/webhooks"), options + response.status.should == 201 + json_response["url"].should == webhook[:web_hook] + end + + it "fails to create webhook for non existsing project" do + post api("/projects/non-existant-id/webhooks"), options + response.status.should == 404 + end + + it "non-manager is not authorized" do + User.any_instance.stub(:can_manage_project?).and_return(false) + post api("/projects/#{project.id}/webhooks"), options + response.status.should == 401 + end + end + + context "Invalid Webhook URL" do + let!(:webhook) { {web_hook: "ala_ma_kota" } } + + before do + options.merge!(webhook) + end + + it "fails to create webhook for not valid url" do + post api("/projects/#{project.id}/webhooks"), options + response.status.should == 400 + end + end + + context "Missed web_hook parameter" do + it "fails to create webhook for not provided url" do + post api("/projects/#{project.id}/webhooks"), options + response.status.should == 400 + end + end + end + + describe "GET /projects/:id" do + let!(:project) { FactoryGirl.create(:project) } + + context "with an existing project" do + it "should retrieve the project info" do + get api("/projects/#{project.id}"), options + response.status.should == 200 + json_response['id'].should == project.id + end + end + + context "with a non-existing project" do + it "should return 404 error if project not found" do + get api("/projects/non_existent_id"), options + response.status.should == 404 + end + end + end + + describe "PUT /projects/:id" do + let!(:project) { FactoryGirl.create(:project) } + let!(:project_info) { {name: "An updated name!" } } + + before do + options.merge!(project_info) + end + + it "should update a specific project's information" do + put api("/projects/#{project.id}"), options + response.status.should == 200 + json_response["name"].should == project_info[:name] + end + + it "fails to update a non-existing project" do + put api("/projects/non-existant-id"), options + response.status.should == 404 + end + + it "non-manager is not authorized" do + User.any_instance.stub(:can_manage_project?).and_return(false) + put api("/projects/#{project.id}"), options + response.status.should == 401 + end + end + + describe "DELETE /projects/:id" do + let!(:project) { FactoryGirl.create(:project) } + + it "should delete a specific project" do + delete api("/projects/#{project.id}"), options + response.status.should == 200 + + expect { project.reload }.to raise_error + end + + it "non-manager is not authorized" do + User.any_instance.stub(:can_manage_project?).and_return(false) + delete api("/projects/#{project.id}"), options + response.status.should == 401 + end + + it "is getting not found error" do + delete api("/projects/not-existing_id"), options + response.status.should == 404 + end + end + + describe "POST /projects" do + let(:project_info) { + { + name: "My project", + gitlab_id: 1, + path: "testing/testing", + ssh_url_to_repo: "ssh://example.com/testing/testing.git" + } + } + + let(:invalid_project_info) { {} } + + context "with valid project info" do + before do + options.merge!(project_info) + end + + it "should create a project with valid data" do + post api("/projects"), options + response.status.should == 201 + json_response['name'].should == project_info[:name] + end + end + + context "with invalid project info" do + before do + options.merge!(invalid_project_info) + end + + it "should error with invalid data" do + post api("/projects"), options + response.status.should == 400 + end + end + + describe "POST /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:project) } + let(:runner) { FactoryGirl.create(:runner) } + + it "should add the project to the runner" do + post api("/projects/#{project.id}/runners/#{runner.id}"), options + response.status.should == 201 + + project.reload + project.runners.first.id.should == runner.id + end + + it "should fail if it tries to link a non-existing project or runner" do + post api("/projects/#{project.id}/runners/non-existing"), options + response.status.should == 404 + + post api("/projects/non-existing/runners/#{runner.id}"), options + response.status.should == 404 + end + + it "non-manager is not authorized" do + User.any_instance.stub(:can_manage_project?).and_return(false) + post api("/projects/#{project.id}/runners/#{runner.id}"), options + response.status.should == 401 + end + end + + describe "DELETE /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:project) } + let(:runner) { FactoryGirl.create(:runner) } + + before do + post api("/projects/#{project.id}/runners/#{runner.id}"), options + end + + it "should remove the project from the runner" do + project.runners.should be_present + delete api("/projects/#{project.id}/runners/#{runner.id}"), options + response.status.should == 200 + + project.reload + project.runners.should be_empty + end + + it "non-manager is not authorized" do + User.any_instance.stub(:can_manage_project?).and_return(false) + post api("/projects/#{project.id}/runners/#{runner.id}"), options + response.status.should == 401 + end + end + end +end diff --git a/spec/ci/requests/api/runners_spec.rb b/spec/ci/requests/api/runners_spec.rb new file mode 100644 index 00000000000..47de3c2a95c --- /dev/null +++ b/spec/ci/requests/api/runners_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + include StubGitlabCalls + + before { + stub_gitlab_calls + } + + describe "GET /runners" do + let(:gitlab_url) { GitlabCi.config.gitlab_server.url } + let(:private_token) { Network.new.authenticate(access_token: "some_token")["private_token"] } + let(:options) { + { + :private_token => private_token, + :url => gitlab_url + } + } + + before do + 5.times { FactoryGirl.create(:runner) } + end + + it "should retrieve a list of all runners" do + get api("/runners"), options + response.status.should == 200 + json_response.count.should == 5 + json_response.last.should have_key("id") + json_response.last.should have_key("token") + end + end + + describe "POST /runners/register" do + describe "should create a runner if token provided" do + before { post api("/runners/register"), token: GitlabCi::REGISTRATION_TOKEN } + + it { response.status.should == 201 } + end + + describe "should create a runner with description" do + before { post api("/runners/register"), token: GitlabCi::REGISTRATION_TOKEN, description: "server.hostname" } + + it { response.status.should == 201 } + it { Runner.first.description.should == "server.hostname" } + end + + describe "should create a runner with tags" do + before { post api("/runners/register"), token: GitlabCi::REGISTRATION_TOKEN, tag_list: "tag1, tag2" } + + it { response.status.should == 201 } + it { Runner.first.tag_list.sort.should == ["tag1", "tag2"] } + end + + describe "should create a runner if project token provided" do + let(:project) { FactoryGirl.create(:project) } + before { post api("/runners/register"), token: project.token } + + it { response.status.should == 201 } + it { project.runners.size.should == 1 } + end + + it "should return 403 error if token is invalid" do + post api("/runners/register"), token: 'invalid' + + response.status.should == 403 + end + + it "should return 400 error if no token" do + post api("/runners/register") + + response.status.should == 400 + end + end + + describe "DELETE /runners/delete" do + let!(:runner) { FactoryGirl.create(:runner) } + before { delete api("/runners/delete"), token: runner.token } + + it { response.status.should == 200 } + it { Runner.count.should == 0 } + end +end diff --git a/spec/ci/requests/api/triggers_spec.rb b/spec/ci/requests/api/triggers_spec.rb new file mode 100644 index 00000000000..6e56c4b3b22 --- /dev/null +++ b/spec/ci/requests/api/triggers_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + + describe 'POST /projects/:project_id/refs/:ref/trigger' do + let!(:trigger_token) { 'secure token' } + let!(:project) { FactoryGirl.create(:project) } + let!(:project2) { FactoryGirl.create(:project) } + let!(:trigger) { FactoryGirl.create(:trigger, project: project, token: trigger_token) } + let(:options) { + { + token: trigger_token + } + } + + context 'Handles errors' do + it 'should return bad request if token is missing' do + post api("/projects/#{project.id}/refs/master/trigger") + response.status.should == 400 + end + + it 'should return not found if project is not found' do + post api('/projects/0/refs/master/trigger'), options + response.status.should == 404 + end + + it 'should return unauthorized if token is for different project' do + post api("/projects/#{project2.id}/refs/master/trigger"), options + response.status.should == 401 + end + end + + context 'Have a commit' do + before do + @commit = FactoryGirl.create(:commit, project: project) + end + + it 'should create builds' do + post api("/projects/#{project.id}/refs/master/trigger"), options + response.status.should == 201 + @commit.builds.reload + @commit.builds.size.should == 2 + end + + it 'should return bad request with no builds created if there\'s no commit for that ref' do + post api("/projects/#{project.id}/refs/other-branch/trigger"), options + response.status.should == 400 + json_response['message'].should == 'No builds created' + end + + context 'Validates variables' do + let(:variables) { + {'TRIGGER_KEY' => 'TRIGGER_VALUE'} + } + + it 'should validate variables to be a hash' do + post api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: 'value') + response.status.should == 400 + json_response['message'].should == 'variables needs to be a hash' + end + + it 'should validate variables needs to be a map of key-valued strings' do + post api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: {key: %w(1 2)}) + response.status.should == 400 + json_response['message'].should == 'variables needs to be a map of key-valued strings' + end + + it 'create trigger request with variables' do + post api("/projects/#{project.id}/refs/master/trigger"), options.merge(variables: variables) + response.status.should == 201 + @commit.builds.reload + @commit.builds.first.trigger_request.variables.should == variables + end + end + end + end +end diff --git a/spec/ci/requests/builds_spec.rb b/spec/ci/requests/builds_spec.rb new file mode 100644 index 00000000000..73d540e372a --- /dev/null +++ b/spec/ci/requests/builds_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe "Builds" do + before do + @project = FactoryGirl.create :project + @commit = FactoryGirl.create :commit, project: @project + @build = FactoryGirl.create :build, commit: @commit + end + + describe "GET /:project/builds/:id/status.json" do + before do + get status_project_build_path(@project, @build), format: :json + end + + it { response.status.should == 200 } + it { response.body.should include(@build.sha) } + end +end diff --git a/spec/ci/requests/commits_spec.rb b/spec/ci/requests/commits_spec.rb new file mode 100644 index 00000000000..e9d8366c41a --- /dev/null +++ b/spec/ci/requests/commits_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe "Commits" do + before do + @project = FactoryGirl.create :project + @commit = FactoryGirl.create :commit, project: @project + end + + describe "GET /:project/refs/:ref_name/commits/:id/status.json" do + before do + get status_project_ref_commit_path(@project, @commit.ref, @commit.sha), format: :json + end + + it { response.status.should == 200 } + it { response.body.should include(@commit.sha) } + end +end diff --git a/spec/ci/services/create_commit_service_spec.rb b/spec/ci/services/create_commit_service_spec.rb new file mode 100644 index 00000000000..34e00d5b3c0 --- /dev/null +++ b/spec/ci/services/create_commit_service_spec.rb @@ -0,0 +1,130 @@ +require 'spec_helper' + +describe CreateCommitService do + let(:service) { CreateCommitService.new } + let(:project) { FactoryGirl.create(:project) } + + describe :execute do + context 'valid params' do + let(:commit) do + service.execute(project, + ref: 'refs/heads/master', + before: '00000000', + after: '31das312', + ci_yaml_file: gitlab_ci_yaml, + commits: [ { message: "Message" } ] + ) + end + + it { commit.should be_kind_of(Commit) } + it { commit.should be_valid } + it { commit.should be_persisted } + it { commit.should == project.commits.last } + it { commit.builds.first.should be_kind_of(Build) } + end + + context "skip tag if there is no build for it" do + it "creates commit if there is appropriate job" do + result = service.execute(project, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + ci_yaml_file: gitlab_ci_yaml, + commits: [ { message: "Message" } ] + ) + result.should be_persisted + end + + it "creates commit if there is no appropriate job but deploy job has right ref setting" do + config = YAML.dump({deploy: {deploy: "ls", only: ["0_1"]}}) + + result = service.execute(project, + ref: 'refs/heads/0_1', + before: '00000000', + after: '31das312', + ci_yaml_file: config, + commits: [ { message: "Message" } ] + ) + result.should be_persisted + end + end + + describe :ci_skip? do + it "skips builds creation if there is [ci skip] tag in commit message" do + commits = [{message: "some message[ci skip]"}] + commit = service.execute(project, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits, + ci_yaml_file: gitlab_ci_yaml + ) + commit.builds.any?.should be_false + commit.status.should == "skipped" + end + + it "does not skips builds creation if there is no [ci skip] tag in commit message" do + commits = [{message: "some message"}] + + commit = service.execute(project, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits, + ci_yaml_file: gitlab_ci_yaml + ) + + commit.builds.first.name.should == "staging" + end + + it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do + commits = [{message: "some message[ci skip]"}] + commit = service.execute(project, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits, + ci_yaml_file: "invalid: file" + ) + commit.builds.any?.should be_false + commit.status.should == "skipped" + end + end + + it "skips build creation if there are already builds" do + commits = [{message: "message"}] + commit = service.execute(project, + ref: 'refs/heads/master', + before: '00000000', + after: '31das312', + commits: commits, + ci_yaml_file: gitlab_ci_yaml + ) + commit.builds.count(:all).should == 2 + + commit = service.execute(project, + ref: 'refs/heads/master', + before: '00000000', + after: '31das312', + commits: commits, + ci_yaml_file: gitlab_ci_yaml + ) + commit.builds.count(:all).should == 2 + end + + it "creates commit with failed status if yaml is invalid" do + commits = [{message: "some message"}] + + commit = service.execute(project, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits, + ci_yaml_file: "invalid: file" + ) + + commit.status.should == "failed" + commit.builds.any?.should be_false + end + end +end diff --git a/spec/ci/services/create_project_service_spec.rb b/spec/ci/services/create_project_service_spec.rb new file mode 100644 index 00000000000..31614968d55 --- /dev/null +++ b/spec/ci/services/create_project_service_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe CreateProjectService do + let(:service) { CreateProjectService.new } + let(:current_user) { double.as_null_object } + let(:project_dump) { YAML.load File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } + + before { Network.any_instance.stub(enable_ci: true) } + + describe :execute do + context 'valid params' do + let(:project) { service.execute(current_user, project_dump, 'http://localhost/projects/:project_id') } + + it { project.should be_kind_of(Project) } + it { project.should be_persisted } + end + + context 'without project dump' do + it 'should raise exception' do + expect { service.execute(current_user, '', '') }.to raise_error + end + end + + context "forking" do + it "uses project as a template for settings and jobs" do + origin_project = FactoryGirl.create(:project) + origin_project.shared_runners_enabled = true + origin_project.public = true + origin_project.allow_git_fetch = true + origin_project.save! + + project = service.execute(current_user, project_dump, 'http://localhost/projects/:project_id', origin_project) + + project.shared_runners_enabled.should be_true + project.public.should be_true + project.allow_git_fetch.should be_true + end + end + end +end diff --git a/spec/ci/services/create_trigger_request_service_spec.rb b/spec/ci/services/create_trigger_request_service_spec.rb new file mode 100644 index 00000000000..41db01c2235 --- /dev/null +++ b/spec/ci/services/create_trigger_request_service_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe CreateTriggerRequestService do + let(:service) { CreateTriggerRequestService.new } + let(:project) { FactoryGirl.create :project } + let(:trigger) { FactoryGirl.create :trigger, project: project } + + describe :execute do + context 'valid params' do + subject { service.execute(project, trigger, 'master') } + + before do + @commit = FactoryGirl.create :commit, project: project + end + + it { subject.should be_kind_of(TriggerRequest) } + it { subject.commit.should == @commit } + end + + context 'no commit for ref' do + subject { service.execute(project, trigger, 'other-branch') } + + it { subject.should be_nil } + end + + context 'no builds created' do + subject { service.execute(project, trigger, 'master') } + + before do + FactoryGirl.create :commit_without_jobs, project: project + end + + it { subject.should be_nil } + end + + context 'for multiple commits' do + subject { service.execute(project, trigger, 'master') } + + before do + @commit1 = FactoryGirl.create :commit, committed_at: 2.hour.ago, project: project + @commit2 = FactoryGirl.create :commit, committed_at: 1.hour.ago, project: project + @commit3 = FactoryGirl.create :commit, committed_at: 3.hour.ago, project: project + end + + context 'retries latest one' do + it { subject.should be_kind_of(TriggerRequest) } + it { subject.should be_persisted } + it { subject.commit.should == @commit2 } + end + end + end +end diff --git a/spec/ci/services/event_service_spec.rb b/spec/ci/services/event_service_spec.rb new file mode 100644 index 00000000000..f7b9bf58127 --- /dev/null +++ b/spec/ci/services/event_service_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe EventService do + let (:project) { FactoryGirl.create :project, name: "GitLab / gitlab-shell" } + let (:user) { double(username: "root", id: 1) } + + before do + Event.destroy_all + end + + describe :remove_project do + it "creates event" do + EventService.new.remove_project(user, project) + + Event.admin.last.description.should == "Project \"GitLab / gitlab-shell\" has been removed by root" + end + end + + describe :create_project do + it "creates event" do + EventService.new.create_project(user, project) + + Event.admin.last.description.should == "Project \"GitLab / gitlab-shell\" has been created by root" + end + end + + describe :change_project_settings do + it "creates event" do + EventService.new.change_project_settings(user, project) + + Event.last.description.should == "User \"root\" updated projects settings" + end + end +end
\ No newline at end of file diff --git a/spec/ci/services/image_for_build_service_spec.rb b/spec/ci/services/image_for_build_service_spec.rb new file mode 100644 index 00000000000..4c7094146bb --- /dev/null +++ b/spec/ci/services/image_for_build_service_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe ImageForBuildService do + let(:service) { ImageForBuildService.new } + let(:project) { FactoryGirl.create(:project) } + let(:commit) { FactoryGirl.create(:commit, project: project, ref: 'master') } + let(:build) { FactoryGirl.create(:build, commit: commit) } + + describe :execute do + before { build } + + context 'branch name' do + before { build.run! } + let(:image) { service.execute(project, ref: 'master') } + + it { image.should be_kind_of(OpenStruct) } + it { image.path.to_s.should include('public/build-running.svg') } + it { image.name.should == 'build-running.svg' } + end + + context 'unknown branch name' do + let(:image) { service.execute(project, ref: 'feature') } + + it { image.should be_kind_of(OpenStruct) } + it { image.path.to_s.should include('public/build-unknown.svg') } + it { image.name.should == 'build-unknown.svg' } + end + + context 'commit sha' do + before { build.run! } + let(:image) { service.execute(project, sha: build.sha) } + + it { image.should be_kind_of(OpenStruct) } + it { image.path.to_s.should include('public/build-running.svg') } + it { image.name.should == 'build-running.svg' } + end + + context 'unknown commit sha' do + let(:image) { service.execute(project, sha: '0000000') } + + it { image.should be_kind_of(OpenStruct) } + it { image.path.to_s.should include('public/build-unknown.svg') } + it { image.name.should == 'build-unknown.svg' } + end + end +end diff --git a/spec/ci/services/register_build_service_spec.rb b/spec/ci/services/register_build_service_spec.rb new file mode 100644 index 00000000000..b5af777dd1d --- /dev/null +++ b/spec/ci/services/register_build_service_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe RegisterBuildService do + let!(:service) { RegisterBuildService.new } + let!(:project) { FactoryGirl.create :project } + let!(:commit) { FactoryGirl.create :commit, project: project } + let!(:pending_build) { FactoryGirl.create :build, project: project, commit: commit } + let!(:shared_runner) { FactoryGirl.create(:runner, is_shared: true) } + let!(:specific_runner) { FactoryGirl.create(:runner, is_shared: false) } + + before do + specific_runner.assign_to(project) + end + + describe :execute do + context 'runner follow tag list' do + it "picks build with the same tag" do + pending_build.tag_list = ["linux"] + pending_build.save + specific_runner.tag_list = ["linux"] + service.execute(specific_runner).should == pending_build + end + + it "does not pick build with different tag" do + pending_build.tag_list = ["linux"] + pending_build.save + specific_runner.tag_list = ["win32"] + service.execute(specific_runner).should be_false + end + + it "picks build without tag" do + service.execute(specific_runner).should == pending_build + end + + it "does not pick build with tag" do + pending_build.tag_list = ["linux"] + pending_build.save + service.execute(specific_runner).should be_false + end + + it "pick build without tag" do + specific_runner.tag_list = ["win32"] + service.execute(specific_runner).should == pending_build + end + end + + context 'allow shared runners' do + before do + project.shared_runners_enabled = true + project.save + end + + context 'shared runner' do + let(:build) { service.execute(shared_runner) } + + it { build.should be_kind_of(Build) } + it { build.should be_valid } + it { build.should be_running } + it { build.runner.should == shared_runner } + end + + context 'specific runner' do + let(:build) { service.execute(specific_runner) } + + it { build.should be_kind_of(Build) } + it { build.should be_valid } + it { build.should be_running } + it { build.runner.should == specific_runner } + end + end + + context 'disallow shared runners' do + context 'shared runner' do + let(:build) { service.execute(shared_runner) } + + it { build.should be_nil } + end + + context 'specific runner' do + let(:build) { service.execute(specific_runner) } + + it { build.should be_kind_of(Build) } + it { build.should be_valid } + it { build.should be_running } + it { build.runner.should == specific_runner } + end + end + end +end diff --git a/spec/ci/services/web_hook_service_spec.rb b/spec/ci/services/web_hook_service_spec.rb new file mode 100644 index 00000000000..2bb153942e8 --- /dev/null +++ b/spec/ci/services/web_hook_service_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe WebHookService do + let (:project) { FactoryGirl.create :project } + let (:commit) { FactoryGirl.create :commit, project: project } + let (:build) { FactoryGirl.create :build, commit: commit } + let (:hook) { FactoryGirl.create :web_hook, project: project } + + describe :execute do + it "should execute successfully" do + stub_request(:post, hook.url).to_return(status: 200) + WebHookService.new.build_end(build).should be_true + end + end + + context 'build_data' do + it "contains all needed fields" do + build_data(build).should include( + :build_id, + :project_id, + :ref, + :build_status, + :build_started_at, + :build_finished_at, + :before_sha, + :project_name, + :gitlab_url, + :build_name + ) + end + end + + def build_data(build) + WebHookService.new.send :build_data, build + end +end diff --git a/spec/ci/six.tar.gz b/spec/ci/six.tar.gz Binary files differnew file mode 100644 index 00000000000..80a8c6644e4 --- /dev/null +++ b/spec/ci/six.tar.gz diff --git a/spec/ci/spec_helper.rb b/spec/ci/spec_helper.rb new file mode 100644 index 00000000000..54d3068845d --- /dev/null +++ b/spec/ci/spec_helper.rb @@ -0,0 +1,60 @@ +if ENV['SIMPLECOV'] + require 'simplecov' + SimpleCov.start +end + +if ENV['COVERALLS'] + require 'coveralls' + Coveralls.wear!('rails') +end + +ENV["RAILS_ENV"] ||= 'test' +require File.expand_path("../../config/environment", __FILE__) +require 'rspec/rails' +require 'rspec/autorun' +require 'sidekiq/testing/inline' +require 'capybara/poltergeist' + +Capybara.javascript_driver = :poltergeist +Capybara.default_wait_time = 10 + +# Requires supporting ruby files with custom matchers and macros, etc, +# in spec/support/ and its subdirectories. +Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} + +require 'webmock/rspec' +WebMock.disable_net_connect!(allow_localhost: true) + +RSpec.configure do |config| + config.include LoginHelpers, type: :feature + + config.include StubGitlabCalls + config.include StubGitlabData + + # ## Mock Framework + # + # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: + # + # config.mock_with :mocha + # config.mock_with :flexmock + # config.mock_with :rr + + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = false + + # If true, the base class of anonymous controllers will be inferred + # automatically. This will be the default behavior in future versions of + # rspec-rails. + config.infer_base_class_for_anonymous_controllers = false + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = "random" +end diff --git a/spec/ci/support/api_helpers.rb b/spec/ci/support/api_helpers.rb new file mode 100644 index 00000000000..555980f2ea7 --- /dev/null +++ b/spec/ci/support/api_helpers.rb @@ -0,0 +1,35 @@ +module ApiHelpers + # Public: Prepend a request path with the path to the API + # + # path - Path to append + # user - User object - If provided, automatically appends private_token query + # string for authenticated requests + # + # Examples + # + # >> api('/issues') + # => "/api/v2/issues" + # + # >> api('/issues', User.last) + # => "/api/v2/issues?private_token=..." + # + # >> api('/issues?foo=bar', User.last) + # => "/api/v2/issues?foo=bar&private_token=..." + # + # Returns the relative path to the requested API resource + def api(path, user = nil) + "/api/#{API::API.version}#{path}" + + + # Normalize query string + (path.index('?') ? '' : '?') + + + # Append private_token if given a User object + (user.respond_to?(:private_token) ? + "&private_token=#{user.private_token}" : "") + end + + def json_response + JSON.parse(response.body) + end + +end diff --git a/spec/ci/support/db_cleaner.rb b/spec/ci/support/db_cleaner.rb new file mode 100644 index 00000000000..d2d532d9738 --- /dev/null +++ b/spec/ci/support/db_cleaner.rb @@ -0,0 +1,39 @@ +# RSpec.configure do |config| + +# config.around(:each) do |example| +# DatabaseCleaner.strategy = :transaction +# DatabaseCleaner.clean_with(:truncation) +# DatabaseCleaner.cleaning do +# example.run +# end +# end + +# config.around(:each, js: true) do |example| +# DatabaseCleaner.strategy = :truncation +# DatabaseCleaner.clean_with(:truncation) +# DatabaseCleaner.cleaning do +# example.run +# end +# end +# end +RSpec.configure do |config| + config.before(:suite) do + DatabaseCleaner.clean_with(:truncation) + end + + config.before(:each) do + DatabaseCleaner.strategy = :transaction + end + + config.before(:each, :js => true) do + DatabaseCleaner.strategy = :truncation + end + + config.before(:each) do + DatabaseCleaner.start + end + + config.after(:each) do + DatabaseCleaner.clean + end +end diff --git a/spec/ci/support/gitlab_stubs/gitlab_ci.yml b/spec/ci/support/gitlab_stubs/gitlab_ci.yml new file mode 100644 index 00000000000..3482145404e --- /dev/null +++ b/spec/ci/support/gitlab_stubs/gitlab_ci.yml @@ -0,0 +1,63 @@ +image: ruby:2.1 +services: + - postgres + +before_script: + - gem install bundler + - bundle install + - bundle exec rake db:create + +variables: + DB_NAME: postgres + +types: + - test + - deploy + - notify + +rspec: + script: "rake spec" + tags: + - ruby + - postgres + only: + - branches + +spinach: + script: "rake spinach" + allow_failure: true + tags: + - ruby + - mysql + except: + - tags + +staging: + script: "cap deploy stating" + type: deploy + tags: + - capistrano + - debian + except: + - stable + +production: + type: deploy + script: + - cap deploy production + - cap notify + tags: + - capistrano + - debian + only: + - master + - /^deploy-.*$/ + +dockerhub: + type: notify + script: "curl http://dockerhub/URL" + tags: + - ruby + - postgres + only: + - branches diff --git a/spec/ci/support/gitlab_stubs/project_8.json b/spec/ci/support/gitlab_stubs/project_8.json new file mode 100644 index 00000000000..f0a9fce859c --- /dev/null +++ b/spec/ci/support/gitlab_stubs/project_8.json @@ -0,0 +1,45 @@ +{ + "id":8, + "description":"ssh access and repository management app for GitLab", + "default_branch":"master", + "public":false, + "visibility_level":0, + "ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab-shell.git", + "http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab-shell.git", + "web_url":"http://demo.gitlab.com/gitlab/gitlab-shell", + "owner": { + "id":4, + "name":"GitLab", + "created_at":"2012-12-21T13:03:05Z" + }, + "name":"gitlab-shell", + "name_with_namespace":"GitLab / gitlab-shell", + "path":"gitlab-shell", + "path_with_namespace":"gitlab/gitlab-shell", + "issues_enabled":true, + "merge_requests_enabled":true, + "wall_enabled":false, + "wiki_enabled":true, + "snippets_enabled":false, + "created_at":"2013-03-20T13:28:53Z", + "last_activity_at":"2013-11-30T00:11:17Z", + "namespace":{ + "created_at":"2012-12-21T13:03:05Z", + "description":"Self hosted Git management software", + "id":4, + "name":"GitLab", + "owner_id":1, + "path":"gitlab", + "updated_at":"2013-03-20T13:29:13Z" + }, + "permissions":{ + "project_access": { + "access_level": 10, + "notification_level": 3 + }, + "group_access": { + "access_level": 50, + "notification_level": 3 + } + } +}
\ No newline at end of file diff --git a/spec/ci/support/gitlab_stubs/project_8_hooks.json b/spec/ci/support/gitlab_stubs/project_8_hooks.json new file mode 100644 index 00000000000..93d51406d63 --- /dev/null +++ b/spec/ci/support/gitlab_stubs/project_8_hooks.json @@ -0,0 +1 @@ +[{}] diff --git a/spec/ci/support/gitlab_stubs/projects.json b/spec/ci/support/gitlab_stubs/projects.json new file mode 100644 index 00000000000..ca42c14c5d8 --- /dev/null +++ b/spec/ci/support/gitlab_stubs/projects.json @@ -0,0 +1 @@ +[{"id":3,"description":"GitLab is open source software to collaborate on code. Create projects and repositories, manage access and do code reviews.","default_branch":"master","public":true,"visibility_level":20,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlabhq.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlabhq.git","web_url":"http://demo.gitlab.com/gitlab/gitlabhq","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlabhq","name_with_namespace":"GitLab / gitlabhq","path":"gitlabhq","path_with_namespace":"gitlab/gitlabhq","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"snippets_enabled":true,"created_at":"2012-12-21T13:06:34Z","last_activity_at":"2013-12-02T19:10:10Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":4,"description":"Component of GitLab CI. Web application","default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab-ci.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab-ci.git","web_url":"http://demo.gitlab.com/gitlab/gitlab-ci","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlab-ci","name_with_namespace":"GitLab / gitlab-ci","path":"gitlab-ci","path_with_namespace":"gitlab/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"snippets_enabled":true,"created_at":"2012-12-21T13:06:50Z","last_activity_at":"2013-11-28T19:26:54Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":5,"description":"","default_branch":"master","public":true,"visibility_level":20,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab-recipes.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab-recipes.git","web_url":"http://demo.gitlab.com/gitlab/gitlab-recipes","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlab-recipes","name_with_namespace":"GitLab / gitlab-recipes","path":"gitlab-recipes","path_with_namespace":"gitlab/gitlab-recipes","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":true,"wiki_enabled":true,"snippets_enabled":true,"created_at":"2012-12-21T13:07:02Z","last_activity_at":"2013-12-02T13:54:10Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":8,"description":"ssh access and repository management app for GitLab","default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab-shell.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab-shell.git","web_url":"http://demo.gitlab.com/gitlab/gitlab-shell","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlab-shell","name_with_namespace":"GitLab / gitlab-shell","path":"gitlab-shell","path_with_namespace":"gitlab/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-03-20T13:28:53Z","last_activity_at":"2013-11-30T00:11:17Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":9,"description":null,"default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:gitlab/gitlab_git.git","http_url_to_repo":"http://demo.gitlab.com/gitlab/gitlab_git.git","web_url":"http://demo.gitlab.com/gitlab/gitlab_git","owner":{"id":4,"name":"GitLab","created_at":"2012-12-21T13:03:05Z"},"name":"gitlab_git","name_with_namespace":"GitLab / gitlab_git","path":"gitlab_git","path_with_namespace":"gitlab/gitlab_git","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-04-28T19:15:08Z","last_activity_at":"2013-12-02T13:07:13Z","namespace":{"created_at":"2012-12-21T13:03:05Z","description":"Self hosted Git management software","id":4,"name":"GitLab","owner_id":1,"path":"gitlab","updated_at":"2013-03-20T13:29:13Z"}},{"id":10,"description":"ultra lite authorization library http://randx.github.com/six/\\r\\n ","default_branch":"master","public":true,"visibility_level":20,"ssh_url_to_repo":"git@demo.gitlab.com:sandbox/six.git","http_url_to_repo":"http://demo.gitlab.com/sandbox/six.git","web_url":"http://demo.gitlab.com/sandbox/six","owner":{"id":8,"name":"Sandbox","created_at":"2013-08-01T16:44:17Z"},"name":"Six","name_with_namespace":"Sandbox / Six","path":"six","path_with_namespace":"sandbox/six","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-08-01T16:45:02Z","last_activity_at":"2013-11-29T11:30:56Z","namespace":{"created_at":"2013-08-01T16:44:17Z","description":"","id":8,"name":"Sandbox","owner_id":1,"path":"sandbox","updated_at":"2013-08-01T16:44:17Z"}},{"id":11,"description":"Simple HTML5 Charts using the <canvas> tag ","default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:sandbox/charts-js.git","http_url_to_repo":"http://demo.gitlab.com/sandbox/charts-js.git","web_url":"http://demo.gitlab.com/sandbox/charts-js","owner":{"id":8,"name":"Sandbox","created_at":"2013-08-01T16:44:17Z"},"name":"Charts.js","name_with_namespace":"Sandbox / Charts.js","path":"charts-js","path_with_namespace":"sandbox/charts-js","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-08-01T16:47:29Z","last_activity_at":"2013-12-02T15:18:11Z","namespace":{"created_at":"2013-08-01T16:44:17Z","description":"","id":8,"name":"Sandbox","owner_id":1,"path":"sandbox","updated_at":"2013-08-01T16:44:17Z"}},{"id":13,"description":"","default_branch":"master","public":false,"visibility_level":0,"ssh_url_to_repo":"git@demo.gitlab.com:sandbox/afro.git","http_url_to_repo":"http://demo.gitlab.com/sandbox/afro.git","web_url":"http://demo.gitlab.com/sandbox/afro","owner":{"id":8,"name":"Sandbox","created_at":"2013-08-01T16:44:17Z"},"name":"Afro","name_with_namespace":"Sandbox / Afro","path":"afro","path_with_namespace":"sandbox/afro","issues_enabled":true,"merge_requests_enabled":true,"wall_enabled":false,"wiki_enabled":true,"snippets_enabled":false,"created_at":"2013-11-14T17:45:19Z","last_activity_at":"2013-12-02T17:41:45Z","namespace":{"created_at":"2013-08-01T16:44:17Z","description":"","id":8,"name":"Sandbox","owner_id":1,"path":"sandbox","updated_at":"2013-08-01T16:44:17Z"}}]
\ No newline at end of file diff --git a/spec/ci/support/gitlab_stubs/raw_project.yml b/spec/ci/support/gitlab_stubs/raw_project.yml new file mode 100644 index 00000000000..df2ce223d1f --- /dev/null +++ b/spec/ci/support/gitlab_stubs/raw_project.yml @@ -0,0 +1,36 @@ +--- !ruby/object:OpenStruct +table: + :id: 189 + :description: Website at http://api.gitlab.org/ + :default_branch: master + :public: false + :visibility_level: 0 + :ssh_url_to_repo: dzaporozhets@localhost:gitlab/api-gitlab-org.git + :http_url_to_repo: http://localhost:3000/gitlab/api-gitlab-org.git + :web_url: http://localhost:3000/gitlab/api-gitlab-org + :owner: + id: 1 + name: GitLab + created_at: '2012-10-03T09:59:57.000Z' + :name: api.gitlab.org + :name_with_namespace: GitLab / api.gitlab.org + :path: api-gitlab-org + :path_with_namespace: gitlab/api-gitlab-org + :issues_enabled: true + :merge_requests_enabled: true + :wall_enabled: false + :wiki_enabled: false + :snippets_enabled: false + :created_at: '2013-06-06T12:29:39.000Z' + :last_activity_at: '2013-12-06T20:29:42.000Z' + :namespace: + id: 1 + name: GitLab + path: gitlab + owner_id: 1 + created_at: '2012-10-03T09:59:57.000Z' + updated_at: '2014-01-28T08:49:53.000Z' + description: Self hosted Git management software + avatar: + url: /uploads/group/avatar/1/0-vader-profile.jpg + diff --git a/spec/ci/support/gitlab_stubs/session.json b/spec/ci/support/gitlab_stubs/session.json new file mode 100644 index 00000000000..ce8dfe5ae75 --- /dev/null +++ b/spec/ci/support/gitlab_stubs/session.json @@ -0,0 +1,20 @@ +{ + "id":2, + "username":"jsmith", + "email":"test@test.com", + "name":"John Smith", + "bio":"", + "skype":"aertert", + "linkedin":"", + "twitter":"", + "theme_id":2,"color_scheme_id":2, + "state":"active", + "created_at":"2012-12-21T13:02:20Z", + "extern_uid":null, + "provider":null, + "is_admin":false, + "can_create_group":false, + "can_create_project":false, + "private_token":"Wvjy2Krpb7y8xi93owUz", + "access_token":"Wvjy2Krpb7y8xi93owUz" +}
\ No newline at end of file diff --git a/spec/ci/support/gitlab_stubs/user.json b/spec/ci/support/gitlab_stubs/user.json new file mode 100644 index 00000000000..ce8dfe5ae75 --- /dev/null +++ b/spec/ci/support/gitlab_stubs/user.json @@ -0,0 +1,20 @@ +{ + "id":2, + "username":"jsmith", + "email":"test@test.com", + "name":"John Smith", + "bio":"", + "skype":"aertert", + "linkedin":"", + "twitter":"", + "theme_id":2,"color_scheme_id":2, + "state":"active", + "created_at":"2012-12-21T13:02:20Z", + "extern_uid":null, + "provider":null, + "is_admin":false, + "can_create_group":false, + "can_create_project":false, + "private_token":"Wvjy2Krpb7y8xi93owUz", + "access_token":"Wvjy2Krpb7y8xi93owUz" +}
\ No newline at end of file diff --git a/spec/ci/support/login_helpers.rb b/spec/ci/support/login_helpers.rb new file mode 100644 index 00000000000..ebd9693f8a4 --- /dev/null +++ b/spec/ci/support/login_helpers.rb @@ -0,0 +1,22 @@ +module LoginHelpers + def login_as(role) + raise 'Only :user allowed' unless role == :user + stub_gitlab_calls + login_with(:user) + end + + # Internal: Login as the specified user + # + # user - User instance to login with + def login_with(user) + visit callback_user_sessions_path(code: "some_auth_code_here") + end + + def logout + click_link "Logout" rescue nil + end + + def skip_admin_auth + ApplicationController.any_instance.stub(authenticate_admin!: true) + end +end diff --git a/spec/ci/support/monkey_patches/oauth2.rb b/spec/ci/support/monkey_patches/oauth2.rb new file mode 100644 index 00000000000..dfd5e319f00 --- /dev/null +++ b/spec/ci/support/monkey_patches/oauth2.rb @@ -0,0 +1,7 @@ +module OAuth2 + class Client + def get_token(params, access_token_opts = {}, access_token_class = AccessToken) + OpenStruct.new(token: "some_token") + end + end +end
\ No newline at end of file diff --git a/spec/ci/support/setup_builds_storage.rb b/spec/ci/support/setup_builds_storage.rb new file mode 100644 index 00000000000..cafc8dee918 --- /dev/null +++ b/spec/ci/support/setup_builds_storage.rb @@ -0,0 +1,16 @@ +RSpec.configure do |config| + def builds_path + Rails.root.join('tmp/builds_test') + end + + config.before(:each) do + FileUtils.mkdir_p(builds_path) + Ci::Settings.gitlab_ci['builds_path'] = builds_path + end + + config.after(:suite) do + Dir.chdir(builds_path) do + `ls | grep -v .gitkeep | xargs rm -r` + end + end +end diff --git a/spec/ci/support/stub_gitlab_calls.rb b/spec/ci/support/stub_gitlab_calls.rb new file mode 100644 index 00000000000..931ef963c0f --- /dev/null +++ b/spec/ci/support/stub_gitlab_calls.rb @@ -0,0 +1,77 @@ +module StubGitlabCalls + def stub_gitlab_calls + stub_session + stub_user + stub_project_8 + stub_project_8_hooks + stub_projects + stub_projects_owned + stub_ci_enable + end + + def stub_js_gitlab_calls + Network.any_instance.stub(:projects) { project_hash_array } + end + + private + + def gitlab_url + GitlabCi.config.gitlab_server.url + end + + def stub_session + f = File.read(Rails.root.join('spec/support/gitlab_stubs/session.json')) + + stub_request(:post, "#{gitlab_url}api/v3/session.json"). + with(:body => "{\"email\":\"test@test.com\",\"password\":\"123456\"}", + :headers => {'Content-Type'=>'application/json'}). + to_return(:status => 201, :body => f, :headers => {'Content-Type'=>'application/json'}) + end + + def stub_user + f = File.read(Rails.root.join('spec/support/gitlab_stubs/user.json')) + + stub_request(:get, "#{gitlab_url}api/v3/user?private_token=Wvjy2Krpb7y8xi93owUz"). + with(:headers => {'Content-Type'=>'application/json'}). + to_return(:status => 200, :body => f, :headers => {'Content-Type'=>'application/json'}) + + stub_request(:get, "#{gitlab_url}api/v3/user?access_token=some_token"). + with(:headers => {'Content-Type'=>'application/json'}). + to_return(:status => 200, :body => f, :headers => {'Content-Type'=>'application/json'}) + end + + def stub_project_8 + data = File.read(Rails.root.join('spec/support/gitlab_stubs/project_8.json')) + Network.any_instance.stub(:project).and_return(JSON.parse(data)) + end + + def stub_project_8_hooks + data = File.read(Rails.root.join('spec/support/gitlab_stubs/project_8_hooks.json')) + Network.any_instance.stub(:project_hooks).and_return(JSON.parse(data)) + end + + def stub_projects + f = File.read(Rails.root.join('spec/support/gitlab_stubs/projects.json')) + + stub_request(:get, "#{gitlab_url}api/v3/projects.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz"). + with(:headers => {'Content-Type'=>'application/json'}). + to_return(:status => 200, :body => f, :headers => {'Content-Type'=>'application/json'}) + end + + def stub_projects_owned + stub_request(:get, "#{gitlab_url}api/v3/projects/owned.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz"). + with(:headers => {'Content-Type'=>'application/json'}). + to_return(:status => 200, :body => "", :headers => {}) + end + + def stub_ci_enable + stub_request(:put, "#{gitlab_url}api/v3/projects/2/services/gitlab-ci.json?private_token=Wvjy2Krpb7y8xi93owUz"). + with(:headers => {'Content-Type'=>'application/json'}). + to_return(:status => 200, :body => "", :headers => {}) + end + + def project_hash_array + f = File.read(Rails.root.join('spec/support/gitlab_stubs/projects.json')) + return JSON.parse f + end +end diff --git a/spec/ci/support/stub_gitlab_data.rb b/spec/ci/support/stub_gitlab_data.rb new file mode 100644 index 00000000000..fa402f35b95 --- /dev/null +++ b/spec/ci/support/stub_gitlab_data.rb @@ -0,0 +1,5 @@ +module StubGitlabData + def gitlab_ci_yaml + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end +end |