summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/ci/controllers/commits_controller_spec.rb27
-rw-r--r--spec/ci/controllers/projects_controller_spec.rb108
-rw-r--r--spec/ci/factories/builds.rb45
-rw-r--r--spec/ci/factories/commits.rb75
-rw-r--r--spec/ci/factories/events.rb24
-rw-r--r--spec/ci/factories/projects.rb56
-rw-r--r--spec/ci/factories/runner_projects.rb19
-rw-r--r--spec/ci/factories/runners.rb38
-rw-r--r--spec/ci/factories/trigger_requests.rb13
-rw-r--r--spec/ci/factories/triggers.rb9
-rw-r--r--spec/ci/factories/users.rb6
-rw-r--r--spec/ci/factories/web_hook.rb6
-rw-r--r--spec/ci/features/admin/builds_spec.rb71
-rw-r--r--spec/ci/features/admin/events_spec.rb20
-rw-r--r--spec/ci/features/admin/projects_spec.rb19
-rw-r--r--spec/ci/features/admin/runners_spec.rb63
-rw-r--r--spec/ci/features/builds_spec.rb57
-rw-r--r--spec/ci/features/commits_spec.rb66
-rw-r--r--spec/ci/features/events_spec.rb20
-rw-r--r--spec/ci/features/lint_spec.rb28
-rw-r--r--spec/ci/features/projects_spec.rb57
-rw-r--r--spec/ci/features/runners_spec.rb98
-rw-r--r--spec/ci/features/triggers_spec.rb26
-rw-r--r--spec/ci/features/variables_spec.rb26
-rw-r--r--spec/ci/helpers/application_helper_spec.rb37
-rw-r--r--spec/ci/helpers/runners_helper_spec.rb18
-rw-r--r--spec/ci/helpers/user_helper_spec.rb49
-rw-r--r--spec/ci/helpers/user_sessions_helper_spec.rb69
-rw-r--r--spec/ci/lib/ansi2html_spec.rb133
-rw-r--r--spec/ci/lib/charts_spec.rb17
-rw-r--r--spec/ci/lib/gitlab_ci_yaml_processor_spec.rb311
-rw-r--r--spec/ci/lib/upgrader_spec.rb39
-rw-r--r--spec/ci/mailers/notify_spec.rb36
-rw-r--r--spec/ci/models/build_spec.rb350
-rw-r--r--spec/ci/models/commit_spec.rb264
-rw-r--r--spec/ci/models/mail_service_spec.rb184
-rw-r--r--spec/ci/models/network_spec.rb54
-rw-r--r--spec/ci/models/project_services/hip_chat_message_spec.rb74
-rw-r--r--spec/ci/models/project_services/hip_chat_service_spec.rb75
-rw-r--r--spec/ci/models/project_services/slack_message_spec.rb84
-rw-r--r--spec/ci/models/project_services/slack_service_spec.rb58
-rw-r--r--spec/ci/models/project_spec.rb185
-rw-r--r--spec/ci/models/runner_project_spec.rb16
-rw-r--r--spec/ci/models/runner_spec.rb70
-rw-r--r--spec/ci/models/service_spec.rb49
-rw-r--r--spec/ci/models/trigger_spec.rb17
-rw-r--r--spec/ci/models/user_spec.rb100
-rw-r--r--spec/ci/models/variable_spec.rb44
-rw-r--r--spec/ci/models/web_hook_spec.rb64
-rw-r--r--spec/ci/requests/api/builds_spec.rb115
-rw-r--r--spec/ci/requests/api/commits_spec.rb65
-rw-r--r--spec/ci/requests/api/forks_spec.rb60
-rw-r--r--spec/ci/requests/api/projects_spec.rb251
-rw-r--r--spec/ci/requests/api/runners_spec.rb83
-rw-r--r--spec/ci/requests/api/triggers_spec.rb78
-rw-r--r--spec/ci/requests/builds_spec.rb18
-rw-r--r--spec/ci/requests/commits_spec.rb17
-rw-r--r--spec/ci/services/create_commit_service_spec.rb130
-rw-r--r--spec/ci/services/create_project_service_spec.rb40
-rw-r--r--spec/ci/services/create_trigger_request_service_spec.rb52
-rw-r--r--spec/ci/services/event_service_spec.rb34
-rw-r--r--spec/ci/services/image_for_build_service_spec.rb46
-rw-r--r--spec/ci/services/register_build_service_spec.rb89
-rw-r--r--spec/ci/services/web_hook_service_spec.rb36
-rw-r--r--spec/ci/six.tar.gzbin0 -> 61937 bytes
-rw-r--r--spec/ci/spec_helper.rb60
-rw-r--r--spec/ci/support/api_helpers.rb35
-rw-r--r--spec/ci/support/db_cleaner.rb39
-rw-r--r--spec/ci/support/gitlab_stubs/gitlab_ci.yml63
-rw-r--r--spec/ci/support/gitlab_stubs/project_8.json45
-rw-r--r--spec/ci/support/gitlab_stubs/project_8_hooks.json1
-rw-r--r--spec/ci/support/gitlab_stubs/projects.json1
-rw-r--r--spec/ci/support/gitlab_stubs/raw_project.yml36
-rw-r--r--spec/ci/support/gitlab_stubs/session.json20
-rw-r--r--spec/ci/support/gitlab_stubs/user.json20
-rw-r--r--spec/ci/support/login_helpers.rb22
-rw-r--r--spec/ci/support/monkey_patches/oauth2.rb7
-rw-r--r--spec/ci/support/setup_builds_storage.rb16
-rw-r--r--spec/ci/support/stub_gitlab_calls.rb77
-rw-r--r--spec/ci/support/stub_gitlab_data.rb5
-rw-r--r--spec/lib/extracts_path_spec.rb2
-rw-r--r--spec/support/filter_spec_helper.rb2
82 files changed, 4937 insertions, 2 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
new file mode 100644
index 00000000000..80a8c6644e4
--- /dev/null
+++ b/spec/ci/six.tar.gz
Binary files differ
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
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 9c115bbfc6a..48bc60eed16 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe ExtractsPath do
include ExtractsPath
include RepoHelpers
- include Rails.application.routes.url_helpers
+ include Gitlab::Application.routes.url_helpers
let(:project) { double('project') }
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index 755964e9a3d..203117aee70 100644
--- a/spec/support/filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -72,6 +72,6 @@ module FilterSpecHelper
# Shortcut to Rails' auto-generated routes helpers, to avoid including the
# module
def urls
- Rails.application.routes.url_helpers
+ Gitlab::Application.routes.url_helpers
end
end