summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2016-12-22 10:15:49 +0000
committerFilipa Lacerda <filipa@gitlab.com>2016-12-22 10:15:49 +0000
commit7fc64dd18d9b2b6e3a2a01dab0007f7dd25c37ed (patch)
tree428602d5265cd981a2e33ace8aed6fc9594dd37c /spec
parentfd3ab00cf90ddf081c61fb701721ca9180378bba (diff)
parent6d9c1d3efce00da95832feaaf36227bcbffecadf (diff)
downloadgitlab-ce-pipeline-ui-updates.tar.gz
Merge branch 'master' into pipeline-ui-updatespipeline-ui-updates
* master: (259 commits) Exclude non existent repository storages. fixed minor animation glitch in mini pipeline graph animation Update Bitbucket callback URL documentation Update build step for KaTeX. Add KaTeX fonts to assets paths and precompile Replace url('...') to url(font-path('...')) Rname katex.css to katex.scss Revert conflicting EE changes Added Autodeploy script for OpenShift Whitelist next project names: notes, services Put back progress bar CSS Remove unneeded bundle refs. Adds entry to changelog Reduce MR widget title by one pixel Use same font size for all items in issue title Adds background color for disabled state to merge when succeeds dropdown Filter protocol-relative URLs in ExternalLinkFilter. Fixes issue #22742. Move javascript for widget check to ci_bundle. Introduce "Set up autodeploy" button to help configure GitLab CI for deployment Whitelist next project names: help, ci, admin, search ...
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/groups_controller_spec.rb21
-rw-r--r--spec/controllers/import/gitea_controller_spec.rb43
-rw-r--r--spec/controllers/import/github_controller_spec.rb216
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb69
-rw-r--r--spec/controllers/projects/mattermosts_controller_spec.rb58
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb4
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb47
-rw-r--r--spec/factories/projects.rb12
-rw-r--r--spec/features/admin/admin_projects_spec.rb99
-rw-r--r--spec/features/admin/admin_settings_spec.rb6
-rw-r--r--spec/features/auto_deploy_spec.rb64
-rw-r--r--spec/features/environment_spec.rb32
-rw-r--r--spec/features/environments_spec.rb28
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb27
-rw-r--r--spec/features/merge_requests/created_from_fork_spec.rb8
-rw-r--r--spec/features/milestones/show_spec.rb26
-rw-r--r--spec/features/projects/commit/builds_spec.rb12
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin679415 -> 682154 bytes
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb4
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb72
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb34
-rw-r--r--spec/features/projects/services/slack_service_spec.rb4
-rw-r--r--spec/features/projects/services/slack_slash_command_spec.rb40
-rw-r--r--spec/helpers/import_helper_spec.rb33
-rw-r--r--spec/javascripts/fixtures/issuable_filter.html.haml8
-rw-r--r--spec/javascripts/fixtures/mini_dropdown_graph.html.haml8
-rw-r--r--spec/javascripts/issuable_spec.js.es681
-rw-r--r--spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es651
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb14
-rw-r--r--spec/lib/gitlab/chat_commands/command_spec.rb28
-rw-r--r--spec/lib/gitlab/git/rev_list_spec.rb7
-rw-r--r--spec/lib/gitlab/github_import/client_spec.rb46
-rw-r--r--spec/lib/gitlab/github_import/importer_spec.rb370
-rw-r--r--spec/lib/gitlab/github_import/issuable_formatter_spec.rb21
-rw-r--r--spec/lib/gitlab/github_import/issue_formatter_spec.rb38
-rw-r--r--spec/lib/gitlab/github_import/milestone_formatter_spec.rb27
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb34
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml5
-rw-r--r--spec/lib/gitlab/import_export/project.json22
-rw-r--r--spec/lib/gitlab/import_sources_spec.rb94
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb39
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb4
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb6
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb36
-rw-r--r--spec/lib/mattermost/client_spec.rb24
-rw-r--r--spec/lib/mattermost/command_spec.rb61
-rw-r--r--spec/lib/mattermost/session_spec.rb24
-rw-r--r--spec/lib/mattermost/team_spec.rb66
-rw-r--r--spec/models/ci/pipeline_spec.rb24
-rw-r--r--spec/models/ci/stage_spec.rb13
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb145
-rw-r--r--spec/models/environment_spec.rb60
-rw-r--r--spec/models/project_authorization_spec.rb25
-rw-r--r--spec/models/project_services/chat_message/build_message_spec.rb8
-rw-r--r--spec/models/project_services/chat_message/issue_message_spec.rb10
-rw-r--r--spec/models/project_services/chat_message/merge_message_spec.rb12
-rw-r--r--spec/models/project_services/chat_message/note_message_spec.rb22
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb8
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb26
-rw-r--r--spec/models/project_services/chat_message/wiki_page_message_spec.rb8
-rw-r--r--spec/models/project_services/chat_service_spec.rb15
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb109
-rw-r--r--spec/models/project_services/mattermost_notification_service_spec.rb5
-rw-r--r--spec/models/project_services/mattermost_service_spec.rb5
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb163
-rw-r--r--spec/models/project_services/slack_notification_service_spec.rb5
-rw-r--r--spec/models/project_services/slack_service_spec.rb5
-rw-r--r--spec/models/project_services/slack_slash_commands_service.rb40
-rw-r--r--spec/models/project_spec.rb18
-rw-r--r--spec/models/route_spec.rb8
-rw-r--r--spec/requests/api/files_spec.rb87
-rw-r--r--spec/requests/api/repositories_spec.rb473
-rw-r--r--spec/requests/ci/api/builds_spec.rb25
-rw-r--r--spec/routing/import_routing_spec.rb165
-rw-r--r--spec/services/groups/update_service_spec.rb51
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb185
-rw-r--r--spec/support/api/repositories_shared_context.rb10
-rw-r--r--spec/support/api/status_shared_examples.rb42
-rw-r--r--spec/support/chat_slash_commands_shared_examples.rb97
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_context.rb10
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb232
-rw-r--r--spec/support/kubernetes_helpers.rb52
-rw-r--r--spec/support/query_recorder.rb40
-rw-r--r--spec/support/reactive_caching_helpers.rb38
-rw-r--r--spec/support/slack_mattermost_notifications_shared_examples.rb (renamed from spec/support/slack_mattermost_shared_examples.rb)2
-rw-r--r--spec/views/projects/pipelines/_stage.html.haml_spec.rb21
-rw-r--r--spec/workers/authorized_projects_worker_spec.rb14
-rw-r--r--spec/workers/reactive_caching_worker_spec.rb15
89 files changed, 3543 insertions, 825 deletions
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index a763e2c5ba8..98dfb3e5216 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -105,4 +105,25 @@ describe GroupsController do
end
end
end
+
+ describe 'PUT update' do
+ before do
+ sign_in(user)
+ end
+
+ it 'updates the path succesfully' do
+ post :update, id: group.to_param, group: { path: 'new_path' }
+
+ expect(response).to have_http_status(302)
+ expect(controller).to set_flash[:notice]
+ end
+
+ it 'does not update the path on error' do
+ allow_any_instance_of(Group).to receive(:move_dir).and_raise(Gitlab::UpdatePathError)
+ post :update, id: group.to_param, group: { path: 'new_path' }
+
+ expect(assigns(:group).errors).not_to be_empty
+ expect(assigns(:group).path).not_to eq('new_path')
+ end
+ end
end
diff --git a/spec/controllers/import/gitea_controller_spec.rb b/spec/controllers/import/gitea_controller_spec.rb
new file mode 100644
index 00000000000..5ba64ab3eed
--- /dev/null
+++ b/spec/controllers/import/gitea_controller_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Import::GiteaController do
+ include ImportSpecHelper
+
+ let(:provider) { :gitea }
+ let(:host_url) { 'https://try.gitea.io' }
+
+ include_context 'a GitHub-ish import controller'
+
+ def assign_host_url
+ session[:gitea_host_url] = host_url
+ end
+
+ describe "GET new" do
+ it_behaves_like 'a GitHub-ish import controller: GET new' do
+ before do
+ assign_host_url
+ end
+ end
+ end
+
+ describe "POST personal_access_token" do
+ it_behaves_like 'a GitHub-ish import controller: POST personal_access_token'
+ end
+
+ describe "GET status" do
+ it_behaves_like 'a GitHub-ish import controller: GET status' do
+ before do
+ assign_host_url
+ end
+ let(:extra_assign_expectations) { { gitea_host_url: host_url } }
+ end
+ end
+
+ describe 'POST create' do
+ it_behaves_like 'a GitHub-ish import controller: POST create' do
+ before do
+ assign_host_url
+ end
+ end
+ end
+end
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 4f96567192d..95696e14b6c 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -3,34 +3,18 @@ require 'spec_helper'
describe Import::GithubController do
include ImportSpecHelper
- let(:user) { create(:user) }
- let(:token) { "asdasd12345" }
- let(:access_params) { { github_access_token: token } }
+ let(:provider) { :github }
- def assign_session_token
- session[:github_access_token] = token
- end
-
- before do
- sign_in(user)
- allow(controller).to receive(:github_import_enabled?).and_return(true)
- end
+ include_context 'a GitHub-ish import controller'
describe "GET new" do
- it "redirects to GitHub for an access token if logged in with GitHub" do
- allow(controller).to receive(:logged_in_with_github?).and_return(true)
- expect(controller).to receive(:go_to_github_for_permissions)
+ it_behaves_like 'a GitHub-ish import controller: GET new'
- get :new
- end
-
- it "redirects to status if we already have a token" do
- assign_session_token
- allow(controller).to receive(:logged_in_with_github?).and_return(false)
+ it "redirects to GitHub for an access token if logged in with GitHub" do
+ allow(controller).to receive(:logged_in_with_provider?).and_return(true)
+ expect(controller).to receive(:go_to_provider_for_permissions)
get :new
-
- expect(controller).to redirect_to(status_import_github_url)
end
end
@@ -51,196 +35,14 @@ describe Import::GithubController do
end
describe "POST personal_access_token" do
- it "updates access token" do
- token = "asdfasdf9876"
-
- allow_any_instance_of(Gitlab::GithubImport::Client).
- to receive(:user).and_return(true)
-
- post :personal_access_token, personal_access_token: token
-
- expect(session[:github_access_token]).to eq(token)
- expect(controller).to redirect_to(status_import_github_url)
- end
+ it_behaves_like 'a GitHub-ish import controller: POST personal_access_token'
end
describe "GET status" do
- before do
- @repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim')
- @org = OpenStruct.new(login: 'company')
- @org_repo = OpenStruct.new(login: 'company', full_name: 'company/repo')
- assign_session_token
- end
-
- it "assigns variables" do
- @project = create(:project, import_type: 'github', creator_id: user.id)
- stub_client(repos: [@repo, @org_repo], orgs: [@org], org_repos: [@org_repo])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([@repo, @org_repo])
- end
-
- it "does not show already added project" do
- @project = create(:project, import_type: 'github', creator_id: user.id, import_source: 'asd/vim')
- stub_client(repos: [@repo], orgs: [])
-
- get :status
-
- expect(assigns(:already_added_projects)).to eq([@project])
- expect(assigns(:repos)).to eq([])
- end
-
- it "handles an invalid access token" do
- allow_any_instance_of(Gitlab::GithubImport::Client).
- to receive(:repos).and_raise(Octokit::Unauthorized)
-
- get :status
-
- expect(session[:github_access_token]).to eq(nil)
- expect(controller).to redirect_to(new_import_github_url)
- expect(flash[:alert]).to eq('Access denied to your GitHub account.')
- end
+ it_behaves_like 'a GitHub-ish import controller: GET status'
end
describe "POST create" do
- let(:github_username) { user.username }
- let(:github_user) { OpenStruct.new(login: github_username) }
- let(:github_repo) do
- OpenStruct.new(
- name: 'vim',
- full_name: "#{github_username}/vim",
- owner: OpenStruct.new(login: github_username)
- )
- end
-
- before do
- stub_client(user: github_user, repo: github_repo)
- assign_session_token
- end
-
- context "when the repository owner is the GitHub user" do
- context "when the GitHub user and GitLab user's usernames match" do
- it "takes the current user's namespace" do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
- and_return(double(execute: true))
-
- post :create, format: :js
- end
- end
-
- context "when the GitHub user and GitLab user's usernames don't match" do
- let(:github_username) { "someone_else" }
-
- it "takes the current user's namespace" do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
- and_return(double(execute: true))
-
- post :create, format: :js
- end
- end
- end
-
- context "when the repository owner is not the GitHub user" do
- let(:other_username) { "someone_else" }
-
- before do
- github_repo.owner = OpenStruct.new(login: other_username)
- assign_session_token
- end
-
- context "when a namespace with the GitHub user's username already exists" do
- let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
-
- context "when the namespace is owned by the GitLab user" do
- it "takes the existing namespace" do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).with(github_repo, github_repo.name, existing_namespace, user, access_params).
- and_return(double(execute: true))
-
- post :create, format: :js
- end
- end
-
- context "when the namespace is not owned by the GitLab user" do
- before do
- existing_namespace.owner = create(:user)
- existing_namespace.save
- end
-
- it "creates a project using user's namespace" do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
- and_return(double(execute: true))
-
- post :create, format: :js
- end
- end
- end
-
- context "when a namespace with the GitHub user's username doesn't exist" do
- context "when current user can create namespaces" do
- it "creates the namespace" do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).and_return(double(execute: true))
-
- expect { post :create, target_namespace: github_repo.name, format: :js }.to change(Namespace, :count).by(1)
- end
-
- it "takes the new namespace" do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).with(github_repo, github_repo.name, an_instance_of(Group), user, access_params).
- and_return(double(execute: true))
-
- post :create, target_namespace: github_repo.name, format: :js
- end
- end
-
- context "when current user can't create namespaces" do
- before do
- user.update_attribute(:can_create_group, false)
- end
-
- it "doesn't create the namespace" do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).and_return(double(execute: true))
-
- expect { post :create, format: :js }.not_to change(Namespace, :count)
- end
-
- it "takes the current user's namespace" do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).with(github_repo, github_repo.name, user.namespace, user, access_params).
- and_return(double(execute: true))
-
- post :create, format: :js
- end
- end
- end
-
- context 'user has chosen a namespace and name for the project' do
- let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) }
- let(:test_name) { 'test_name' }
-
- it 'takes the selected namespace and name' do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).with(github_repo, test_name, test_namespace, user, access_params).
- and_return(double(execute: true))
-
- post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :js }
- end
-
- it 'takes the selected name and default namespace' do
- expect(Gitlab::GithubImport::ProjectCreator).
- to receive(:new).with(github_repo, test_name, user.namespace, user, access_params).
- and_return(double(execute: true))
-
- post :create, { new_name: test_name, format: :js }
- end
- end
- end
+ it_behaves_like 'a GitHub-ish import controller: POST create'
end
end
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index bc5e2711125..7ac1d62d1b1 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -71,6 +71,75 @@ describe Projects::EnvironmentsController do
end
end
+ describe 'GET #terminal' do
+ context 'with valid id' do
+ it 'responds with a status code 200' do
+ get :terminal, environment_params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'loads the terminals for the enviroment' do
+ expect_any_instance_of(Environment).to receive(:terminals)
+
+ get :terminal, environment_params
+ end
+ end
+
+ context 'with invalid id' do
+ it 'responds with a status code 404' do
+ get :terminal, environment_params(id: 666)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'GET #terminal_websocket_authorize' do
+ context 'with valid workhorse signature' do
+ before do
+ allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_return(nil)
+ end
+
+ context 'and valid id' do
+ it 'returns the first terminal for the environment' do
+ expect_any_instance_of(Environment).
+ to receive(:terminals).
+ and_return([:fake_terminal])
+
+ expect(Gitlab::Workhorse).
+ to receive(:terminal_websocket).
+ with(:fake_terminal).
+ and_return(workhorse: :response)
+
+ get :terminal_websocket_authorize, environment_params
+
+ expect(response).to have_http_status(200)
+ expect(response.headers["Content-Type"]).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(response.body).to eq('{"workhorse":"response"}')
+ end
+ end
+
+ context 'and invalid id' do
+ it 'returns 404' do
+ get :terminal_websocket_authorize, environment_params(id: 666)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ context 'with invalid workhorse signature' do
+ it 'aborts with an exception' do
+ allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_raise(JWT::DecodeError)
+
+ expect { get :terminal_websocket_authorize, environment_params }.to raise_error(JWT::DecodeError)
+ # controller tests don't set the response status correctly. It's enough
+ # to check that the action raised an exception
+ end
+ end
+ end
+
def environment_params(opts = {})
opts.reverse_merge(namespace_id: project.namespace,
project_id: project,
diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb
new file mode 100644
index 00000000000..2ae635a1244
--- /dev/null
+++ b/spec/controllers/projects/mattermosts_controller_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe Projects::MattermostsController do
+ let!(:project) { create(:empty_project) }
+ let!(:user) { create(:user) }
+
+ before do
+ project.team << [user, :master]
+ sign_in(user)
+ end
+
+ describe 'GET #new' do
+ before do
+ allow_any_instance_of(MattermostSlashCommandsService).
+ to receive(:list_teams).and_return([])
+
+ get(:new,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param)
+ end
+
+ it 'accepts the request' do
+ expect(response).to have_http_status(200)
+ end
+ end
+
+ describe 'POST #create' do
+ let(:mattermost_params) { { trigger: 'http://localhost:3000/trigger', team_id: 'abc' } }
+
+ subject do
+ post(:create,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ mattermost: mattermost_params)
+ end
+
+ context 'no request can be made to mattermost' do
+ it 'shows the error' do
+ allow_any_instance_of(MattermostSlashCommandsService).to receive(:configure).and_return([false, "error message"])
+
+ expect(subject).to redirect_to(new_namespace_project_mattermost_url(project.namespace, project))
+ end
+ end
+
+ context 'the request is succesull' do
+ before do
+ allow_any_instance_of(Mattermost::Command).to receive(:create).and_return('token')
+ end
+
+ it 'redirects to the new page' do
+ subject
+ service = project.services.last
+
+ expect(subject).to redirect_to(edit_namespace_project_service_url(project.namespace, project, service))
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 9e0b80205d8..440b897ddc6 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -649,10 +649,6 @@ describe Projects::MergeRequestsController do
end
end
- describe 'GET builds' do
- it_behaves_like "loads labels", :builds
- end
-
describe 'GET pipelines' do
it_behaves_like "loads labels", :pipelines
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
new file mode 100644
index 00000000000..5fe7e6407cc
--- /dev/null
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Projects::PipelinesController do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project, :public) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET stages.json' do
+ context 'when accessing existing stage' do
+ before do
+ create(:ci_build, pipeline: pipeline, stage: 'build')
+
+ get_stage('build')
+ end
+
+ it 'returns html source for stage dropdown' do
+ expect(response).to have_http_status(:ok)
+ expect(response).to render_template('projects/pipelines/_stage')
+ expect(json_response).to include('html')
+ end
+ end
+
+ context 'when accessing unknown stage' do
+ before do
+ get_stage('test')
+ end
+
+ it 'responds with not found' do
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+
+ def get_stage(name)
+ get :stage, namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: pipeline.id,
+ stage: name,
+ format: :json
+ end
+ end
+end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 0d072d6a690..f7fa834d7a2 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -42,6 +42,12 @@ FactoryGirl.define do
end
end
+ trait :test_repo do
+ after :create do |project|
+ TestEnv.copy_repo(project)
+ end
+ end
+
# Nest Project Feature attributes
transient do
wiki_access_level ProjectFeature::ENABLED
@@ -91,9 +97,7 @@ FactoryGirl.define do
factory :project, parent: :empty_project do
path { 'gitlabhq' }
- after :create do |project|
- TestEnv.copy_repo(project)
- end
+ test_repo
end
factory :forked_project_with_submodules, parent: :empty_project do
@@ -140,7 +144,7 @@ FactoryGirl.define do
active: true,
properties: {
namespace: project.path,
- api_url: 'https://kubernetes.example.com/api',
+ api_url: 'https://kubernetes.example.com',
token: 'a' * 40,
}
)
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index a36bfd574cb..a5b88812b75 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -1,12 +1,17 @@
require 'spec_helper'
describe "Admin::Projects", feature: true do
- before do
- @project = create(:project)
+ include Select2Helper
+
+ let(:user) { create :user }
+ let!(:project) { create(:project) }
+ let!(:current_user) do
login_as :admin
end
describe "GET /admin/projects" do
+ let!(:archived_project) { create :project, :public, archived: true }
+
before do
visit admin_projects_path
end
@@ -15,20 +20,98 @@ describe "Admin::Projects", feature: true do
expect(current_path).to eq(admin_projects_path)
end
- it "has projects list" do
- expect(page).to have_content(@project.name)
+ it 'renders projects list without archived project' do
+ expect(page).to have_content(project.name)
+ expect(page).not_to have_content(archived_project.name)
+ end
+
+ it 'renders all projects', js: true do
+ find(:css, '#sort-projects-dropdown').click
+ click_link 'Show archived projects'
+
+ expect(page).to have_content(project.name)
+ expect(page).to have_content(archived_project.name)
+ expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived')
end
end
- describe "GET /admin/projects/:id" do
+ describe "GET /admin/projects/:namespace_id/:id" do
before do
visit admin_projects_path
- click_link "#{@project.name}"
+ click_link "#{project.name}"
+ end
+
+ it do
+ expect(current_path).to eq admin_namespace_project_path(project.namespace, project)
end
it "has project info" do
- expect(page).to have_content(@project.path)
- expect(page).to have_content(@project.name)
+ expect(page).to have_content(project.path)
+ expect(page).to have_content(project.name)
+ expect(page).to have_content(project.name_with_namespace)
+ expect(page).to have_content(project.creator.name)
+ end
+ end
+
+ describe 'transfer project' do
+ before do
+ create(:group, name: 'Web')
+
+ allow_any_instance_of(Projects::TransferService).
+ to receive(:move_uploads_to_new_namespace).and_return(true)
+ end
+
+ it 'transfers project to group web', js: true do
+ visit admin_namespace_project_path(project.namespace, project)
+
+ click_button 'Search for Namespace'
+ click_link 'group: web'
+ click_button 'Transfer'
+
+ expect(page).to have_content("Web / #{project.name}")
+ expect(page).to have_content('Namespace: Web')
+ end
+ end
+
+ describe 'add admin himself to a project' do
+ before do
+ project.team << [user, :master]
+ end
+
+ it 'adds admin a to a project as developer', js: true do
+ visit namespace_project_project_members_path(project.namespace, project)
+
+ page.within '.users-project-form' do
+ select2(current_user.id, from: '#user_ids', multiple: true)
+ select 'Developer', from: 'access_level'
+ end
+
+ click_button 'Add to project'
+
+ page.within '.content-list' do
+ expect(page).to have_content(current_user.name)
+ expect(page).to have_content('Developer')
+ end
+ end
+ end
+
+ describe 'admin remove himself from a project' do
+ before do
+ project.team << [user, :master]
+ project.team << [current_user, :developer]
+ end
+
+ it 'removes admin from the project' do
+ visit namespace_project_project_members_path(project.namespace, project)
+
+ page.within '.content-list' do
+ expect(page).to have_content(current_user.name)
+ expect(page).to have_content('Developer')
+ end
+
+ find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click
+
+ expect(page).not_to have_selector(:css, '.content-list')
end
end
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 8cd66f189be..47fa2f14307 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -17,9 +17,9 @@ feature 'Admin updates settings', feature: true do
expect(page).to have_content "Application settings saved successfully"
end
- scenario 'Change Slack Service template settings' do
+ scenario 'Change Slack Notifications Service template settings' do
click_link 'Service Templates'
- click_link 'Slack'
+ click_link 'Slack notifications'
fill_in 'Webhook', with: 'http://localhost'
fill_in 'Username', with: 'test_user'
fill_in 'service_push_channel', with: '#test_channel'
@@ -30,7 +30,7 @@ feature 'Admin updates settings', feature: true do
expect(page).to have_content 'Application settings saved successfully'
- click_link 'Slack'
+ click_link 'Slack notifications'
page.all('input[type=checkbox]').each do |checkbox|
expect(checkbox).to be_checked
diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb
new file mode 100644
index 00000000000..92f1ab90881
--- /dev/null
+++ b/spec/features/auto_deploy_spec.rb
@@ -0,0 +1,64 @@
+require 'spec_helper'
+
+describe 'Auto deploy' do
+ include WaitForAjax
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+
+ before do
+ project.create_kubernetes_service(
+ active: true,
+ properties: {
+ namespace: project.path,
+ api_url: 'https://kubernetes.example.com',
+ token: 'a' * 40,
+ }
+ )
+ project.team << [user, :master]
+ login_as user
+ end
+
+ context 'when no deployment service is active' do
+ before do
+ project.kubernetes_service.update!(active: false)
+ end
+
+ it 'does not show a button to set up auto deploy' do
+ visit namespace_project_path(project.namespace, project)
+ expect(page).to have_no_content('Set up autodeploy')
+ end
+ end
+
+ context 'when a deployment service is active' do
+ before do
+ project.kubernetes_service.update!(active: true)
+ visit namespace_project_path(project.namespace, project)
+ end
+
+ it 'shows a button to set up auto deploy' do
+ expect(page).to have_link('Set up autodeploy')
+ end
+
+ it 'includes Kubernetes as an available template', js: true do
+ click_link 'Set up autodeploy'
+ click_button 'Choose a GitLab CI Yaml template'
+
+ within '.gitlab-ci-yml-selector' do
+ expect(page).to have_content('OpenShift')
+ end
+ end
+
+ it 'creates a merge request using "autodeploy" branch', js: true do
+ click_link 'Set up autodeploy'
+ click_button 'Choose a GitLab CI Yaml template'
+ within '.gitlab-ci-yml-selector' do
+ click_on 'OpenShift'
+ end
+ wait_for_ajax
+ click_button 'Commit Changes'
+
+ expect(page).to have_content('New Merge Request From autodeploy into master')
+ end
+ end
+end
diff --git a/spec/features/environment_spec.rb b/spec/features/environment_spec.rb
index 0c1939fd885..56f6cd2e095 100644
--- a/spec/features/environment_spec.rb
+++ b/spec/features/environment_spec.rb
@@ -38,6 +38,10 @@ feature 'Environment', :feature do
scenario 'does not show a re-deploy button for deployment without build' do
expect(page).not_to have_link('Re-deploy')
end
+
+ scenario 'does not show terminal button' do
+ expect(page).not_to have_terminal_button
+ end
end
context 'with related deployable present' do
@@ -60,6 +64,10 @@ feature 'Environment', :feature do
expect(page).not_to have_link('Stop')
end
+ scenario 'does not show terminal button' do
+ expect(page).not_to have_terminal_button
+ end
+
context 'with manual action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy to production') }
@@ -84,6 +92,26 @@ feature 'Environment', :feature do
end
end
+ context 'with terminal' do
+ let(:project) { create(:kubernetes_project, :test_repo) }
+
+ context 'for project master' do
+ let(:role) { :master }
+
+ scenario 'it shows the terminal button' do
+ expect(page).to have_terminal_button
+ end
+ end
+
+ context 'for developer' do
+ let(:role) { :developer }
+
+ scenario 'does not show terminal button' do
+ expect(page).not_to have_terminal_button
+ end
+ end
+ end
+
context 'with stop action' do
given(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'close_app') }
given(:deployment) { create(:deployment, environment: environment, deployable: build, on_stop: 'close_app') }
@@ -158,4 +186,8 @@ feature 'Environment', :feature do
environment.project,
environment)
end
+
+ def have_terminal_button
+ have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
+ end
end
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index e1b97b31e5d..72b984cfab8 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -113,6 +113,10 @@ feature 'Environments page', :feature, :js do
expect(page).not_to have_css('external-url')
end
+ scenario 'does not show terminal button' do
+ expect(page).not_to have_terminal_button
+ end
+
context 'with external_url' do
given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') }
given(:build) { create(:ci_build, pipeline: pipeline) }
@@ -145,6 +149,26 @@ feature 'Environments page', :feature, :js do
end
end
end
+
+ context 'with terminal' do
+ let(:project) { create(:kubernetes_project, :test_repo) }
+
+ context 'for project master' do
+ let(:role) { :master }
+
+ scenario 'it shows the terminal button' do
+ expect(page).to have_terminal_button
+ end
+ end
+
+ context 'for developer' do
+ let(:role) { :developer }
+
+ scenario 'does not show terminal button' do
+ expect(page).not_to have_terminal_button
+ end
+ end
+ end
end
end
end
@@ -195,6 +219,10 @@ feature 'Environments page', :feature, :js do
end
end
+ def have_terminal_button
+ have_link(nil, href: terminal_namespace_project_environment_path(project.namespace, project, environment))
+ end
+
def visit_environments(project)
visit namespace_project_environments_path(project.namespace, project)
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index da64827b377..df3a467cbb7 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -39,7 +39,6 @@ feature 'GFM autocomplete', feature: true, js: true do
page.within '.timeline-content-form' do
note.native.send_keys('')
note.native.send_keys("~#{label.title[0]}")
- sleep 1
note.click
end
@@ -53,7 +52,6 @@ feature 'GFM autocomplete', feature: true, js: true do
page.within '.timeline-content-form' do
note.native.send_keys('')
note.native.send_keys("@#{user.username[0]}")
- sleep 1
note.click
end
@@ -67,7 +65,6 @@ feature 'GFM autocomplete', feature: true, js: true do
page.within '.timeline-content-form' do
note.native.send_keys('')
note.native.send_keys(":cartwheel")
- sleep 1
note.click
end
@@ -76,6 +73,22 @@ feature 'GFM autocomplete', feature: true, js: true do
expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1')
end
+ it 'doesn\'t open autocomplete after non-word character' do
+ page.within '.timeline-content-form' do
+ find('#note_note').native.send_keys("@#{user.username[0..2]}!")
+ end
+
+ expect(page).not_to have_selector('.atwho-view')
+ end
+
+ it 'doesn\'t open autocomplete if there is no space before' do
+ page.within '.timeline-content-form' do
+ find('#note_note').native.send_keys("hello:#{user.username[0..2]}")
+ end
+
+ expect(page).not_to have_selector('.atwho-view')
+ end
+
def expect_to_wrap(should_wrap, item, note, value)
expect(item).to have_content(value)
expect(item).not_to have_content("\"#{value}\"")
@@ -89,12 +102,4 @@ feature 'GFM autocomplete', feature: true, js: true do
end
end
end
-
- it 'doesnt open autocomplete after non-word character' do
- page.within '.timeline-content-form' do
- find('#note_note').native.send_keys("@#{user.username[0..2]}!")
- end
-
- expect(page).not_to have_selector('.atwho-view')
- end
end
diff --git a/spec/features/merge_requests/created_from_fork_spec.rb b/spec/features/merge_requests/created_from_fork_spec.rb
index 142649297cc..73c5ef31edc 100644
--- a/spec/features/merge_requests/created_from_fork_spec.rb
+++ b/spec/features/merge_requests/created_from_fork_spec.rb
@@ -54,14 +54,14 @@ feature 'Merge request created from fork' do
scenario 'user visits a pipelines page', js: true do
visit_merge_request(merge_request)
- page.within('.merge-request-tabs') { click_link 'Builds' }
+ page.within('.merge-request-tabs') { click_link 'Pipelines' }
page.within('table.ci-table') do
- expect(page).to have_content 'rspec'
- expect(page).to have_content 'spinach'
+ expect(page).to have_content pipeline.status
+ expect(page).to have_content pipeline.id
end
- expect(find_link('Cancel running')[:href])
+ expect(page.find('a.btn-remove')[:href])
.to include fork_project.path_with_namespace
end
end
diff --git a/spec/features/milestones/show_spec.rb b/spec/features/milestones/show_spec.rb
new file mode 100644
index 00000000000..40b4dc63697
--- /dev/null
+++ b/spec/features/milestones/show_spec.rb
@@ -0,0 +1,26 @@
+require 'rails_helper'
+
+describe 'Milestone show', feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:empty_project) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:labels) { create_list(:label, 2, project: project) }
+ let(:issue_params) { { project: project, assignee: user, author: user, milestone: milestone, labels: labels } }
+
+ before do
+ project.add_user(user, :developer)
+ login_as(user)
+ end
+
+ def visit_milestone
+ visit namespace_project_milestone_path(project.namespace, project, milestone)
+ end
+
+ it 'avoids N+1 database queries' do
+ create(:labeled_issue, issue_params)
+ control_count = ActiveRecord::QueryRecorder.new { visit_milestone }.count
+ create_list(:labeled_issue, 10, issue_params)
+
+ expect { visit_milestone }.not_to exceed_query_limit(control_count)
+ end
+end
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index fcdf7870f34..33f1c323af1 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'project commit builds' do
+feature 'project commit pipelines' do
given(:project) { create(:project) }
background do
@@ -16,11 +16,13 @@ feature 'project commit builds' do
ref: 'master')
end
- scenario 'user views commit builds page' do
- visit builds_namespace_project_commit_path(project.namespace,
- project, project.commit.sha)
+ scenario 'user views commit pipelines page' do
+ visit pipelines_namespace_project_commit_path(project.namespace, project, project.commit.sha)
- expect(page).to have_content('Builds')
+ page.within('.table-holder') do
+ expect(page).to have_content project.pipelines[0].status # pipeline status
+ expect(page).to have_content project.pipelines[0].id # pipeline ids
+ end
end
end
end
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index d3165d07d7b..7655c2b351f 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index 27a83fdcd1f..b7273021c95 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -24,7 +24,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
click_on 'Add to project'
end
- page.within '.project_member:first-child' do
+ page.within "#project_member_#{new_member.project_members.first.id}" do
expect(page).to have_content('Expires in 4 days')
end
end
@@ -35,7 +35,7 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
project.team.add_users([new_member.id], :developer, expires_at: '2016-09-06')
visit namespace_project_project_members_path(project.namespace, project)
- page.within '.project_member:first-child' do
+ page.within "#project_member_#{new_member.project_members.first.id}" do
find('.js-access-expiration-date').set '2016-08-09'
wait_for_ajax
expect(page).to have_content('Expires in 3 days')
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 1210e2745db..14e009daba8 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Pipelines", feature: true, js: true do
+describe 'Pipeline', :feature, :js do
include GitlabRoutingHelper
let(:project) { create(:empty_project) }
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index f3731698a18..1ff57f92c4c 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
-describe "Pipelines" do
+describe 'Pipelines', :feature, :js do
include GitlabRoutingHelper
+ include WaitForAjax
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
@@ -69,16 +70,32 @@ describe "Pipelines" do
end
context 'with manual actions' do
- let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'manual build', stage: 'test', commands: 'test') }
+ let!(:manual) do
+ create(:ci_build, :manual, pipeline: pipeline,
+ name: 'manual build',
+ stage: 'test',
+ commands: 'test')
+ end
- before { visit namespace_project_pipelines_path(project.namespace, project) }
+ before do
+ visit namespace_project_pipelines_path(project.namespace, project)
+ end
- it { expect(page).to have_link('Manual build') }
+ it 'has link to the manual action' do
+ find('.js-pipeline-dropdown-manual-actions').click
- context 'when playing' do
- before { click_link('Manual build') }
+ expect(page).to have_link('Manual build')
+ end
- it { expect(manual.reload).to be_pending }
+ context 'when manual action was played' do
+ before do
+ find('.js-pipeline-dropdown-manual-actions').click
+ click_link('Manual build')
+ end
+
+ it 'enqueues manual action job' do
+ expect(manual.reload).to be_pending
+ end
end
end
@@ -131,7 +148,10 @@ describe "Pipelines" do
before { visit namespace_project_pipelines_path(project.namespace, project) }
it { expect(page).to have_selector('.build-artifacts') }
- it { expect(page).to have_link(with_artifacts.name) }
+ it do
+ find('.js-pipeline-dropdown-download').click
+ expect(page).to have_link(with_artifacts.name)
+ end
end
context 'with artifacts expired' do
@@ -150,6 +170,42 @@ describe "Pipelines" do
it { expect(page).not_to have_selector('.build-artifacts') }
end
end
+
+ context 'mini pipleine graph' do
+ let!(:build) do
+ create(:ci_build, pipeline: pipeline, stage: 'build', name: 'build')
+ end
+
+ before do
+ visit namespace_project_pipelines_path(project.namespace, project)
+ end
+
+ it 'should render a mini pipeline graph' do
+ endpoint = stage_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: build.name)
+
+ expect(page).to have_selector('.mini-pipeline-graph')
+ expect(page).to have_selector(".js-builds-dropdown-button[data-stage-endpoint='#{endpoint}']")
+ end
+
+ context 'when clicking a graph stage' do
+ it 'should open a dropdown' do
+ find('.js-builds-dropdown-button').trigger('click')
+
+ wait_for_ajax
+
+ expect(page).to have_link build.name
+ end
+
+ it 'should be possible to retry the failed build' do
+ find('.js-builds-dropdown-button').trigger('click')
+
+ wait_for_ajax
+
+ find('a.ci-action-icon-container').trigger('click')
+ expect(page).not_to have_content('Cancel running')
+ end
+ end
+ end
end
describe 'POST /:project/pipelines' do
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index f474e7e891b..274d50e7ce4 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -4,29 +4,26 @@ feature 'Setup Mattermost slash commands', feature: true do
include WaitForAjax
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:empty_project) }
let(:service) { project.create_mattermost_slash_commands_service }
+ let(:mattermost_enabled) { true }
before do
+ Settings.mattermost['enabled'] = mattermost_enabled
project.team << [user, :master]
login_as(user)
+ visit edit_namespace_project_service_path(project.namespace, project, service)
end
- describe 'user visites the mattermost slash command config page', js: true do
+ describe 'user visits the mattermost slash command config page', js: true do
it 'shows a help message' do
- visit edit_namespace_project_service_path(project.namespace, project, service)
-
wait_for_ajax
expect(page).to have_content("This service allows GitLab users to perform common")
end
- end
-
- describe 'saving a token' do
- let(:token) { ('a'..'z').to_a.join }
it 'shows the token after saving' do
- visit edit_namespace_project_service_path(project.namespace, project, service)
+ token = ('a'..'z').to_a.join
fill_in 'service_token', with: token
click_on 'Save'
@@ -35,14 +32,21 @@ feature 'Setup Mattermost slash commands', feature: true do
expect(value).to eq(token)
end
- end
- describe 'the trigger url' do
- it 'shows the correct url' do
- visit edit_namespace_project_service_path(project.namespace, project, service)
+ describe 'mattermost service is enabled' do
+ it 'shows the add to mattermost button' do
+ expect(page).to have_link 'Add to Mattermost'
+ end
+ end
+
+ describe 'mattermost service is not enabled' do
+ let(:mattermost_enabled) { false }
+
+ it 'shows the correct trigger url' do
+ value = find_field('request_url').value
- value = find_field('request_url').value
- expect(value).to match("api/v3/projects/#{project.id}/services/mattermost_slash_commands/trigger")
+ expect(value).to match("api/v3/projects/#{project.id}/services/mattermost_slash_commands/trigger")
+ end
end
end
end
diff --git a/spec/features/projects/services/slack_service_spec.rb b/spec/features/projects/services/slack_service_spec.rb
index 320ed13a01d..16541f51d98 100644
--- a/spec/features/projects/services/slack_service_spec.rb
+++ b/spec/features/projects/services/slack_service_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
feature 'Projects > Slack service > Setup events', feature: true do
let(:user) { create(:user) }
- let(:service) { SlackNotificationService.new }
- let(:project) { create(:project, slack_notification_service: service) }
+ let(:service) { SlackService.new }
+ let(:project) { create(:project, slack_service: service) }
background do
service.fields
diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb
new file mode 100644
index 00000000000..32b32f7ae8e
--- /dev/null
+++ b/spec/features/projects/services/slack_slash_command_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+feature 'Slack slash commands', feature: true do
+ include WaitForAjax
+
+ given(:user) { create(:user) }
+ given(:project) { create(:project) }
+ given(:service) { project.create_slack_slash_commands_service }
+
+ background do
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ scenario 'user visits the slack slash command config page and shows a help message', js: true do
+ visit edit_namespace_project_service_path(project.namespace, project, service)
+
+ wait_for_ajax
+
+ expect(page).to have_content('This service allows GitLab users to perform common')
+ end
+
+ scenario 'shows the token after saving' do
+ visit edit_namespace_project_service_path(project.namespace, project, service)
+
+ fill_in 'service_token', with: 'token'
+ click_on 'Save'
+
+ value = find_field('service_token').value
+
+ expect(value).to eq('token')
+ end
+
+ scenario 'shows the correct trigger url' do
+ visit edit_namespace_project_service_path(project.namespace, project, service)
+
+ value = find_field('url').value
+ expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger")
+ end
+end
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index 187b891b927..10f293cddf5 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -25,24 +25,37 @@ describe ImportHelper do
end
end
- describe '#github_project_link' do
- context 'when provider does not specify a custom URL' do
- it 'uses default GitHub URL' do
- allow(Gitlab.config.omniauth).to receive(:providers).
+ describe '#provider_project_link' do
+ context 'when provider is "github"' do
+ context 'when provider does not specify a custom URL' do
+ it 'uses default GitHub URL' do
+ allow(Gitlab.config.omniauth).to receive(:providers).
and_return([Settingslogic.new('name' => 'github')])
- expect(helper.github_project_link('octocat/Hello-World')).
+ expect(helper.provider_project_link('github', 'octocat/Hello-World')).
to include('href="https://github.com/octocat/Hello-World"')
+ end
end
- end
- context 'when provider specify a custom URL' do
- it 'uses custom URL' do
- allow(Gitlab.config.omniauth).to receive(:providers).
+ context 'when provider specify a custom URL' do
+ it 'uses custom URL' do
+ allow(Gitlab.config.omniauth).to receive(:providers).
and_return([Settingslogic.new('name' => 'github', 'url' => 'https://github.company.com')])
- expect(helper.github_project_link('octocat/Hello-World')).
+ expect(helper.provider_project_link('github', 'octocat/Hello-World')).
to include('href="https://github.company.com/octocat/Hello-World"')
+ end
+ end
+ end
+
+ context 'when provider is "gitea"' do
+ before do
+ assign(:gitea_host_url, 'https://try.gitea.io/')
+ end
+
+ it 'uses given host' do
+ expect(helper.provider_project_link('gitea', 'octocat/Hello-World')).
+ to include('href="https://try.gitea.io/octocat/Hello-World"')
end
end
end
diff --git a/spec/javascripts/fixtures/issuable_filter.html.haml b/spec/javascripts/fixtures/issuable_filter.html.haml
new file mode 100644
index 00000000000..ae745b292e6
--- /dev/null
+++ b/spec/javascripts/fixtures/issuable_filter.html.haml
@@ -0,0 +1,8 @@
+%form.js-filter-form{action: '/user/project/issues?scope=all&state=closed'}
+ %input{id: 'utf8', name: 'utf8', value: '✓'}
+ %input{id: 'check_all_issues', name: 'check_all_issues'}
+ %input{id: 'search', name: 'search'}
+ %input{id: 'author_id', name: 'author_id'}
+ %input{id: 'assignee_id', name: 'assignee_id'}
+ %input{id: 'milestone_title', name: 'milestone_title'}
+ %input{id: 'label_name', name: 'label_name'}
diff --git a/spec/javascripts/fixtures/mini_dropdown_graph.html.haml b/spec/javascripts/fixtures/mini_dropdown_graph.html.haml
new file mode 100644
index 00000000000..e9bf7568e95
--- /dev/null
+++ b/spec/javascripts/fixtures/mini_dropdown_graph.html.haml
@@ -0,0 +1,8 @@
+%div.js-builds-dropdown-tests
+ %button.dropdown.js-builds-dropdown-button{'data-stage-endpoint' => 'foobar'}
+ Dropdown
+ %div.js-builds-dropdown-container
+ %div.js-builds-dropdown-list
+
+ %div.js-builds-dropdown-loading.builds-dropdown-loading.hidden
+ %span.fa.fa-spinner.fa-spin
diff --git a/spec/javascripts/issuable_spec.js.es6 b/spec/javascripts/issuable_spec.js.es6
new file mode 100644
index 00000000000..d61601ee4fb
--- /dev/null
+++ b/spec/javascripts/issuable_spec.js.es6
@@ -0,0 +1,81 @@
+/* global Issuable */
+/* global Turbolinks */
+
+//= require issuable
+//= require turbolinks
+
+(() => {
+ const BASE_URL = '/user/project/issues?scope=all&state=closed';
+ const DEFAULT_PARAMS = '&utf8=%E2%9C%93';
+
+ function updateForm(formValues, form) {
+ $.each(formValues, (id, value) => {
+ $(`#${id}`, form).val(value);
+ });
+ }
+
+ function resetForm(form) {
+ $('input[name!="utf8"]', form).each((index, input) => {
+ input.setAttribute('value', '');
+ });
+ }
+
+ describe('Issuable', () => {
+ fixture.preload('issuable_filter');
+
+ beforeEach(() => {
+ fixture.load('issuable_filter');
+ Issuable.init();
+ });
+
+ it('should be defined', () => {
+ expect(window.Issuable).toBeDefined();
+ });
+
+ describe('filtering', () => {
+ let $filtersForm;
+
+ beforeEach(() => {
+ $filtersForm = $('.js-filter-form');
+ fixture.load('issuable_filter');
+ resetForm($filtersForm);
+ });
+
+ it('should contain only the default parameters', () => {
+ spyOn(Turbolinks, 'visit');
+
+ Issuable.filterResults($filtersForm);
+
+ expect(Turbolinks.visit).toHaveBeenCalledWith(BASE_URL + DEFAULT_PARAMS);
+ });
+
+ it('should filter for the phrase "broken"', () => {
+ spyOn(Turbolinks, 'visit');
+
+ updateForm({ search: 'broken' }, $filtersForm);
+ Issuable.filterResults($filtersForm);
+ const params = `${DEFAULT_PARAMS}&search=broken`;
+
+ expect(Turbolinks.visit).toHaveBeenCalledWith(BASE_URL + params);
+ });
+
+ it('should keep query parameters after modifying filter', () => {
+ spyOn(Turbolinks, 'visit');
+
+ // initial filter
+ updateForm({ milestone_title: 'v1.0' }, $filtersForm);
+
+ Issuable.filterResults($filtersForm);
+ let params = `${DEFAULT_PARAMS}&milestone_title=v1.0`;
+ expect(Turbolinks.visit).toHaveBeenCalledWith(BASE_URL + params);
+
+ // update filter
+ updateForm({ label_name: 'Frontend' }, $filtersForm);
+
+ Issuable.filterResults($filtersForm);
+ params = `${DEFAULT_PARAMS}&milestone_title=v1.0&label_name=Frontend`;
+ expect(Turbolinks.visit).toHaveBeenCalledWith(BASE_URL + params);
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6 b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6
new file mode 100644
index 00000000000..d1793e9308e
--- /dev/null
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6
@@ -0,0 +1,51 @@
+/* eslint-disable no-new */
+
+//= require flash
+//= require mini_pipeline_graph_dropdown
+
+(() => {
+ describe('Mini Pipeline Graph Dropdown', () => {
+ fixture.preload('mini_dropdown_graph');
+
+ beforeEach(() => {
+ fixture.load('mini_dropdown_graph');
+ });
+
+ describe('When is initialized', () => {
+ it('should initialize without errors when no options are given', () => {
+ const miniPipelineGraph = new window.gl.MiniPipelineGraph();
+
+ expect(miniPipelineGraph.dropdownListSelector).toEqual('.js-builds-dropdown-container');
+ });
+
+ it('should set the container as the given prop', () => {
+ const container = '.foo';
+
+ const miniPipelineGraph = new window.gl.MiniPipelineGraph({ container });
+
+ expect(miniPipelineGraph.container).toEqual(container);
+ });
+ });
+
+ describe('When dropdown is clicked', () => {
+ it('should call getBuildsList', () => {
+ const getBuildsListSpy = spyOn(gl.MiniPipelineGraph.prototype, 'getBuildsList').and.callFake(function () {});
+
+ new gl.MiniPipelineGraph({ container: '.js-builds-dropdown-tests' });
+
+ document.querySelector('.js-builds-dropdown-button').click();
+
+ expect(getBuildsListSpy).toHaveBeenCalled();
+ });
+
+ it('should make a request to the endpoint provided in the html', () => {
+ const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {});
+
+ new gl.MiniPipelineGraph({ container: '.js-builds-dropdown-tests' });
+
+ document.querySelector('.js-builds-dropdown-button').click();
+ expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar');
+ });
+ });
+ });
+})();
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 167397c736b..d9e4525cb28 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -80,4 +80,18 @@ describe Banzai::Filter::ExternalLinkFilter, lib: true do
expect(filter(act).to_html).to eq(exp)
end
end
+
+ context 'for protocol-relative links' do
+ let(:doc) { filter %q(<p><a href="//google.com/">Google</a></p>) }
+
+ it 'adds rel="nofollow" to external links' do
+ expect(doc.at_css('a')).to have_attribute('rel')
+ expect(doc.at_css('a')['rel']).to include 'nofollow'
+ end
+
+ it 'adds rel="noreferrer" to external links' do
+ expect(doc.at_css('a')).to have_attribute('rel')
+ expect(doc.at_css('a')['rel']).to include 'noreferrer'
+ end
+ end
end
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index bfc6818ac08..a0ec8884635 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -5,7 +5,9 @@ describe Gitlab::ChatCommands::Command, service: true do
let(:user) { create(:user) }
describe '#execute' do
- subject { described_class.new(project, user, params).execute }
+ subject do
+ described_class.new(project, user, params).execute
+ end
context 'when no command is available' do
let(:params) { { text: 'issue show 1' } }
@@ -74,7 +76,7 @@ describe Gitlab::ChatCommands::Command, service: true do
end
it 'returns action' do
- expect(subject[:text]).to include('Deployment from staging to production started')
+ expect(subject[:text]).to include('Deployment from staging to production started.')
expect(subject[:response_type]).to be(:in_channel)
end
@@ -91,4 +93,26 @@ describe Gitlab::ChatCommands::Command, service: true do
end
end
end
+
+ describe '#match_command' do
+ subject { described_class.new(project, user, params).match_command.first }
+
+ context 'IssueShow is triggered' do
+ let(:params) { { text: 'issue show 123' } }
+
+ it { is_expected.to eq(Gitlab::ChatCommands::IssueShow) }
+ end
+
+ context 'IssueCreate is triggered' do
+ let(:params) { { text: 'issue create my title' } }
+
+ it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) }
+ end
+
+ context 'IssueSearch is triggered' do
+ let(:params) { { text: 'issue search my query' } }
+
+ it { is_expected.to eq(Gitlab::ChatCommands::IssueSearch) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb
index 444639acbaa..1f9c987be0b 100644
--- a/spec/lib/gitlab/git/rev_list_spec.rb
+++ b/spec/lib/gitlab/git/rev_list_spec.rb
@@ -26,6 +26,13 @@ describe Gitlab::Git::RevList, lib: true do
expect(rev_list).not_to be_valid
end
+
+ it "ignores nil values" do
+ env = { var => nil }
+ rev_list = described_class.new('oldrev', 'newrev', project: project, env: env)
+
+ expect(rev_list).to be_valid
+ end
end
end
end
diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb
index e829b936343..21f2a9e225b 100644
--- a/spec/lib/gitlab/github_import/client_spec.rb
+++ b/spec/lib/gitlab/github_import/client_spec.rb
@@ -45,20 +45,46 @@ describe Gitlab::GithubImport::Client, lib: true do
end
end
- context 'when provider does not specity an API endpoint' do
- it 'uses GitHub root API endpoint' do
- expect(client.api.api_endpoint).to eq 'https://api.github.com/'
+ describe '#api_endpoint' do
+ context 'when provider does not specity an API endpoint' do
+ it 'uses GitHub root API endpoint' do
+ expect(client.api.api_endpoint).to eq 'https://api.github.com/'
+ end
end
- end
- context 'when provider specify a custom API endpoint' do
- before do
- github_provider['args']['client_options']['site'] = 'https://github.company.com/'
+ context 'when provider specify a custom API endpoint' do
+ before do
+ github_provider['args']['client_options']['site'] = 'https://github.company.com/'
+ end
+
+ it 'uses the custom API endpoint' do
+ expect(OmniAuth::Strategies::GitHub).not_to receive(:default_options)
+ expect(client.api.api_endpoint).to eq 'https://github.company.com/'
+ end
+ end
+
+ context 'when given a host' do
+ subject(:client) { described_class.new(token, host: 'https://try.gitea.io/') }
+
+ it 'builds a endpoint with the given host and the default API version' do
+ expect(client.api.api_endpoint).to eq 'https://try.gitea.io/api/v3/'
+ end
end
- it 'uses the custom API endpoint' do
- expect(OmniAuth::Strategies::GitHub).not_to receive(:default_options)
- expect(client.api.api_endpoint).to eq 'https://github.company.com/'
+ context 'when given an API version' do
+ subject(:client) { described_class.new(token, api_version: 'v3') }
+
+ it 'does not use the API version without a host' do
+ expect(client.api.api_endpoint).to eq 'https://api.github.com/'
+ end
+ end
+
+ context 'when given a host and version' do
+ subject(:client) { described_class.new(token, host: 'https://try.gitea.io/', api_version: 'v3') }
+
+ it 'builds a endpoint with the given options' do
+ expect(client.api.api_endpoint).to eq 'https://try.gitea.io/api/v3/'
+ end
end
end
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index 9e027839f59..72421832ffc 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -1,169 +1,251 @@
require 'spec_helper'
describe Gitlab::GithubImport::Importer, lib: true do
- describe '#execute' do
+ shared_examples 'Gitlab::GithubImport::Importer#execute' do
+ let(:expected_not_called) { [] }
+
before do
- allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
+ allow(project).to receive(:import_data).and_return(double.as_null_object)
end
- context 'when an error occurs' do
- let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_access_level: ProjectFeature::DISABLED) }
- let(:octocat) { double(id: 123456, login: 'octocat') }
- let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
- let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
- let(:repository) { double(id: 1, fork: false) }
- let(:source_sha) { create(:commit, project: project).id }
- let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) }
- let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
- let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
-
- let(:label1) do
- double(
- name: 'Bug',
- color: 'ff0000',
- url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug'
- )
- end
+ it 'calls import methods' do
+ importer = described_class.new(project)
- let(:label2) do
- double(
- name: nil,
- color: 'ff0000',
- url: 'https://api.github.com/repos/octocat/Hello-World/labels/bug'
- )
- end
+ expected_called = [
+ :import_labels, :import_milestones, :import_pull_requests, :import_issues,
+ :import_wiki, :import_releases, :handle_errors
+ ]
- let(:milestone) do
- double(
- number: 1347,
- state: 'open',
- title: '1.0',
- description: 'Version 1.0',
- due_on: nil,
- created_at: created_at,
- updated_at: updated_at,
- closed_at: nil,
- url: 'https://api.github.com/repos/octocat/Hello-World/milestones/1'
- )
- end
+ expected_called -= expected_not_called
- let(:issue1) do
- double(
- number: 1347,
- milestone: nil,
- state: 'open',
- title: 'Found a bug',
- body: "I'm having a problem with this.",
- assignee: nil,
- user: octocat,
- comments: 0,
- pull_request: nil,
- created_at: created_at,
- updated_at: updated_at,
- closed_at: nil,
- url: 'https://api.github.com/repos/octocat/Hello-World/issues/1347',
- labels: [double(name: 'Label #1')],
- )
- end
+ aggregate_failures do
+ expected_called.each do |method_name|
+ expect(importer).to receive(method_name)
+ end
- let(:issue2) do
- double(
- number: 1348,
- milestone: nil,
- state: 'open',
- title: nil,
- body: "I'm having a problem with this.",
- assignee: nil,
- user: octocat,
- comments: 0,
- pull_request: nil,
- created_at: created_at,
- updated_at: updated_at,
- closed_at: nil,
- url: 'https://api.github.com/repos/octocat/Hello-World/issues/1348',
- labels: [double(name: 'Label #2')],
- )
- end
+ expect(importer).to receive(:import_comments).with(:issues)
+ expect(importer).to receive(:import_comments).with(:pull_requests)
- let(:pull_request) do
- double(
- number: 1347,
- milestone: nil,
- state: 'open',
- title: 'New feature',
- body: 'Please pull these awesome changes',
- head: source_branch,
- base: target_branch,
- assignee: nil,
- user: octocat,
- created_at: created_at,
- updated_at: updated_at,
- closed_at: nil,
- merged_at: nil,
- url: 'https://api.github.com/repos/octocat/Hello-World/pulls/1347',
- )
+ expected_not_called.each do |method_name|
+ expect(importer).not_to receive(method_name)
+ end
end
- let(:release1) do
- double(
- tag_name: 'v1.0.0',
- name: 'First release',
- body: 'Release v1.0.0',
- draft: false,
- created_at: created_at,
- updated_at: updated_at,
- url: 'https://api.github.com/repos/octocat/Hello-World/releases/1'
- )
- end
+ importer.execute
+ end
+ end
- let(:release2) do
- double(
- tag_name: 'v2.0.0',
- name: 'Second release',
- body: nil,
- draft: false,
- created_at: created_at,
- updated_at: updated_at,
- url: 'https://api.github.com/repos/octocat/Hello-World/releases/2'
- )
- end
+ shared_examples 'Gitlab::GithubImport::Importer#execute an error occurs' do
+ before do
+ allow(project).to receive(:import_data).and_return(double.as_null_object)
- before do
- allow(project).to receive(:import_data).and_return(double.as_null_object)
- allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
- allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
- allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
- allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
- allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
- allow_any_instance_of(Octokit::Client).to receive(:issues_comments).and_return([])
- allow_any_instance_of(Octokit::Client).to receive(:pull_requests_comments).and_return([])
- allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
- allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
- allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
- end
+ allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
+
+ allow_any_instance_of(Octokit::Client).to receive(:rate_limit!).and_raise(Octokit::NotFound)
+ allow_any_instance_of(Gitlab::Shell).to receive(:import_repository).and_raise(Gitlab::Shell::Error)
+
+ allow_any_instance_of(Octokit::Client).to receive(:labels).and_return([label1, label2])
+ allow_any_instance_of(Octokit::Client).to receive(:milestones).and_return([milestone, milestone])
+ allow_any_instance_of(Octokit::Client).to receive(:issues).and_return([issue1, issue2])
+ allow_any_instance_of(Octokit::Client).to receive(:pull_requests).and_return([pull_request, pull_request])
+ allow_any_instance_of(Octokit::Client).to receive(:issues_comments).and_return([])
+ allow_any_instance_of(Octokit::Client).to receive(:pull_requests_comments).and_return([])
+ allow_any_instance_of(Octokit::Client).to receive(:last_response).and_return(double(rels: { next: nil }))
+ allow_any_instance_of(Octokit::Client).to receive(:releases).and_return([release1, release2])
+ end
+ let(:octocat) { double(id: 123456, login: 'octocat') }
+ let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+ let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
+ let(:label1) do
+ double(
+ name: 'Bug',
+ color: 'ff0000',
+ url: "#{api_root}/repos/octocat/Hello-World/labels/bug"
+ )
+ end
+
+ let(:label2) do
+ double(
+ name: nil,
+ color: 'ff0000',
+ url: "#{api_root}/repos/octocat/Hello-World/labels/bug"
+ )
+ end
+
+ let(:milestone) do
+ double(
+ id: 1347, # For Gitea
+ number: 1347,
+ state: 'open',
+ title: '1.0',
+ description: 'Version 1.0',
+ due_on: nil,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ url: "#{api_root}/repos/octocat/Hello-World/milestones/1"
+ )
+ end
- it 'returns true' do
- expect(described_class.new(project).execute).to eq true
+ let(:issue1) do
+ double(
+ number: 1347,
+ milestone: nil,
+ state: 'open',
+ title: 'Found a bug',
+ body: "I'm having a problem with this.",
+ assignee: nil,
+ user: octocat,
+ comments: 0,
+ pull_request: nil,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ url: "#{api_root}/repos/octocat/Hello-World/issues/1347",
+ labels: [double(name: 'Label #1')]
+ )
+ end
+
+ let(:issue2) do
+ double(
+ number: 1348,
+ milestone: nil,
+ state: 'open',
+ title: nil,
+ body: "I'm having a problem with this.",
+ assignee: nil,
+ user: octocat,
+ comments: 0,
+ pull_request: nil,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ url: "#{api_root}/repos/octocat/Hello-World/issues/1348",
+ labels: [double(name: 'Label #2')]
+ )
+ end
+
+ let(:repository) { double(id: 1, fork: false) }
+ let(:source_sha) { create(:commit, project: project).id }
+ let(:source_branch) { double(ref: 'feature', repo: repository, sha: source_sha) }
+ let(:target_sha) { create(:commit, project: project, git_commit: RepoHelpers.another_sample_commit).id }
+ let(:target_branch) { double(ref: 'master', repo: repository, sha: target_sha) }
+ let(:pull_request) do
+ double(
+ number: 1347,
+ milestone: nil,
+ state: 'open',
+ title: 'New feature',
+ body: 'Please pull these awesome changes',
+ head: source_branch,
+ base: target_branch,
+ assignee: nil,
+ user: octocat,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ merged_at: nil,
+ url: "#{api_root}/repos/octocat/Hello-World/pulls/1347",
+ labels: [double(name: 'Label #2')]
+ )
+ end
+
+ let(:release1) do
+ double(
+ tag_name: 'v1.0.0',
+ name: 'First release',
+ body: 'Release v1.0.0',
+ draft: false,
+ created_at: created_at,
+ updated_at: updated_at,
+ url: "#{api_root}/repos/octocat/Hello-World/releases/1"
+ )
+ end
+
+ let(:release2) do
+ double(
+ tag_name: 'v2.0.0',
+ name: 'Second release',
+ body: nil,
+ draft: false,
+ created_at: created_at,
+ updated_at: updated_at,
+ url: "#{api_root}/repos/octocat/Hello-World/releases/2"
+ )
+ end
+
+ it 'returns true' do
+ expect(described_class.new(project).execute).to eq true
+ end
+
+ it 'does not raise an error' do
+ expect { described_class.new(project).execute }.not_to raise_error
+ end
+
+ it 'stores error messages' do
+ error = {
+ message: 'The remote data could not be fully imported.',
+ errors: [
+ { type: :label, url: "#{api_root}/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
+ { type: :issue, url: "#{api_root}/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank" },
+ { type: :wiki, errors: "Gitlab::Shell::Error" }
+ ]
+ }
+
+ unless project.gitea_import?
+ error[:errors] << { type: :release, url: "#{api_root}/repos/octocat/Hello-World/releases/2", errors: "Validation failed: Description can't be blank" }
end
- it 'does not raise an error' do
- expect { described_class.new(project).execute }.not_to raise_error
+ described_class.new(project).execute
+
+ expect(project.import_error).to eq error.to_json
+ end
+ end
+
+ let(:project) { create(:project, import_url: "#{repo_root}/octocat/Hello-World.git", wiki_access_level: ProjectFeature::DISABLED) }
+ let(:credentials) { { user: 'joe' } }
+
+ context 'when importing a GitHub project' do
+ let(:api_root) { 'https://api.github.com' }
+ let(:repo_root) { 'https://github.com' }
+
+ it_behaves_like 'Gitlab::GithubImport::Importer#execute'
+ it_behaves_like 'Gitlab::GithubImport::Importer#execute an error occurs'
+
+ describe '#client' do
+ it 'instantiates a Client' do
+ allow(project).to receive(:import_data).and_return(double(credentials: credentials))
+ expect(Gitlab::GithubImport::Client).to receive(:new).with(
+ credentials[:user],
+ {}
+ )
+
+ described_class.new(project).client
end
+ end
+ end
- it 'stores error messages' do
- error = {
- message: 'The remote data could not be fully imported.',
- errors: [
- { type: :label, url: "https://api.github.com/repos/octocat/Hello-World/labels/bug", errors: "Validation failed: Title can't be blank, Title is invalid" },
- { type: :issue, url: "https://api.github.com/repos/octocat/Hello-World/issues/1348", errors: "Validation failed: Title can't be blank" },
- { type: :wiki, errors: "Gitlab::Shell::Error" },
- { type: :release, url: 'https://api.github.com/repos/octocat/Hello-World/releases/2', errors: "Validation failed: Description can't be blank" }
- ]
- }
+ context 'when importing a Gitea project' do
+ let(:api_root) { 'https://try.gitea.io/api/v1' }
+ let(:repo_root) { 'https://try.gitea.io' }
+ before do
+ project.update(import_type: 'gitea', import_url: "#{repo_root}/foo/group/project.git")
+ end
- described_class.new(project).execute
+ it_behaves_like 'Gitlab::GithubImport::Importer#execute' do
+ let(:expected_not_called) { [:import_releases] }
+ end
+ it_behaves_like 'Gitlab::GithubImport::Importer#execute an error occurs'
+
+ describe '#client' do
+ it 'instantiates a Client' do
+ allow(project).to receive(:import_data).and_return(double(credentials: credentials))
+ expect(Gitlab::GithubImport::Client).to receive(:new).with(
+ credentials[:user],
+ { host: "#{repo_root}:443/foo", api_version: 'v1' }
+ )
- expect(project.import_error).to eq error.to_json
+ described_class.new(project).client
end
end
end
diff --git a/spec/lib/gitlab/github_import/issuable_formatter_spec.rb b/spec/lib/gitlab/github_import/issuable_formatter_spec.rb
new file mode 100644
index 00000000000..6bc5f98ed2c
--- /dev/null
+++ b/spec/lib/gitlab/github_import/issuable_formatter_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::IssuableFormatter, lib: true do
+ let(:raw_data) do
+ double(number: 42)
+ end
+ let(:project) { double(import_type: 'github') }
+ let(:issuable_formatter) { described_class.new(project, raw_data) }
+
+ describe '#project_association' do
+ it { expect { issuable_formatter.project_association }.to raise_error(NotImplementedError) }
+ end
+
+ describe '#number' do
+ it { expect(issuable_formatter.number).to eq(42) }
+ end
+
+ describe '#find_condition' do
+ it { expect(issuable_formatter.find_condition).to eq({ iid: 42 }) }
+ end
+end
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
index 95339e2f128..e31ed9c1fa0 100644
--- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -23,9 +23,9 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
}
end
- subject(:issue) { described_class.new(project, raw_data)}
+ subject(:issue) { described_class.new(project, raw_data) }
- describe '#attributes' do
+ shared_examples 'Gitlab::GithubImport::IssueFormatter#attributes' do
context 'when issue is open' do
let(:raw_data) { double(base_data.merge(state: 'open')) }
@@ -83,7 +83,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
context 'when it has a milestone' do
- let(:milestone) { double(number: 45) }
+ let(:milestone) { double(id: 42, number: 42) }
let(:raw_data) { double(base_data.merge(milestone: milestone)) }
it 'returns nil when milestone does not exist' do
@@ -91,7 +91,7 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
it 'returns milestone when it exists' do
- milestone = create(:milestone, project: project, iid: 45)
+ milestone = create(:milestone, project: project, iid: 42)
expect(issue.attributes.fetch(:milestone)).to eq milestone
end
@@ -118,6 +118,28 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
end
+ shared_examples 'Gitlab::GithubImport::IssueFormatter#number' do
+ let(:raw_data) { double(base_data.merge(number: 1347)) }
+
+ it 'returns issue number' do
+ expect(issue.number).to eq 1347
+ end
+ end
+
+ context 'when importing a GitHub project' do
+ it_behaves_like 'Gitlab::GithubImport::IssueFormatter#attributes'
+ it_behaves_like 'Gitlab::GithubImport::IssueFormatter#number'
+ end
+
+ context 'when importing a Gitea project' do
+ before do
+ project.update(import_type: 'gitea')
+ end
+
+ it_behaves_like 'Gitlab::GithubImport::IssueFormatter#attributes'
+ it_behaves_like 'Gitlab::GithubImport::IssueFormatter#number'
+ end
+
describe '#has_comments?' do
context 'when number of comments is greater than zero' do
let(:raw_data) { double(base_data.merge(comments: 1)) }
@@ -136,14 +158,6 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do
end
end
- describe '#number' do
- let(:raw_data) { double(base_data.merge(number: 1347)) }
-
- it 'returns pull request number' do
- expect(issue.number).to eq 1347
- end
- end
-
describe '#pull_request?' do
context 'when mention a pull request' do
let(:raw_data) { double(base_data.merge(pull_request: double)) }
diff --git a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
index 09337c99a07..6d38041c468 100644
--- a/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/milestone_formatter_spec.rb
@@ -6,7 +6,6 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
let(:base_data) do
{
- number: 1347,
state: 'open',
title: '1.0',
description: 'Version 1.0',
@@ -16,12 +15,15 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
closed_at: nil
}
end
+ let(:iid_attr) { :number }
- subject(:formatter) { described_class.new(project, raw_data)}
+ subject(:formatter) { described_class.new(project, raw_data) }
+
+ shared_examples 'Gitlab::GithubImport::MilestoneFormatter#attributes' do
+ let(:data) { base_data.merge(iid_attr => 1347) }
- describe '#attributes' do
context 'when milestone is open' do
- let(:raw_data) { double(base_data.merge(state: 'open')) }
+ let(:raw_data) { double(data.merge(state: 'open')) }
it 'returns formatted attributes' do
expected = {
@@ -40,7 +42,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
end
context 'when milestone is closed' do
- let(:raw_data) { double(base_data.merge(state: 'closed')) }
+ let(:raw_data) { double(data.merge(state: 'closed')) }
it 'returns formatted attributes' do
expected = {
@@ -60,7 +62,7 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
context 'when milestone has a due date' do
let(:due_date) { DateTime.strptime('2011-01-28T19:01:12Z') }
- let(:raw_data) { double(base_data.merge(due_on: due_date)) }
+ let(:raw_data) { double(data.merge(due_on: due_date)) }
it 'returns formatted attributes' do
expected = {
@@ -78,4 +80,17 @@ describe Gitlab::GithubImport::MilestoneFormatter, lib: true do
end
end
end
+
+ context 'when importing a GitHub project' do
+ it_behaves_like 'Gitlab::GithubImport::MilestoneFormatter#attributes'
+ end
+
+ context 'when importing a Gitea project' do
+ let(:iid_attr) { :id }
+ before do
+ project.update(import_type: 'gitea')
+ end
+
+ it_behaves_like 'Gitlab::GithubImport::MilestoneFormatter#attributes'
+ end
end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
index 302f0fc0623..2b3256edcb2 100644
--- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -32,9 +32,9 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
}
end
- subject(:pull_request) { described_class.new(project, raw_data)}
+ subject(:pull_request) { described_class.new(project, raw_data) }
- describe '#attributes' do
+ shared_examples 'Gitlab::GithubImport::PullRequestFormatter#attributes' do
context 'when pull request is open' do
let(:raw_data) { double(base_data.merge(state: 'open')) }
@@ -149,7 +149,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
context 'when it has a milestone' do
- let(:milestone) { double(number: 45) }
+ let(:milestone) { double(id: 42, number: 42) }
let(:raw_data) { double(base_data.merge(milestone: milestone)) }
it 'returns nil when milestone does not exist' do
@@ -157,22 +157,22 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
it 'returns milestone when it exists' do
- milestone = create(:milestone, project: project, iid: 45)
+ milestone = create(:milestone, project: project, iid: 42)
expect(pull_request.attributes.fetch(:milestone)).to eq milestone
end
end
end
- describe '#number' do
- let(:raw_data) { double(base_data.merge(number: 1347)) }
+ shared_examples 'Gitlab::GithubImport::PullRequestFormatter#number' do
+ let(:raw_data) { double(base_data) }
it 'returns pull request number' do
expect(pull_request.number).to eq 1347
end
end
- describe '#source_branch_name' do
+ shared_examples 'Gitlab::GithubImport::PullRequestFormatter#source_branch_name' do
context 'when source branch exists' do
let(:raw_data) { double(base_data) }
@@ -190,7 +190,7 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
end
- describe '#target_branch_name' do
+ shared_examples 'Gitlab::GithubImport::PullRequestFormatter#target_branch_name' do
context 'when source branch exists' do
let(:raw_data) { double(base_data) }
@@ -208,6 +208,24 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
end
end
+ context 'when importing a GitHub project' do
+ it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#attributes'
+ it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#number'
+ it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#source_branch_name'
+ it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#target_branch_name'
+ end
+
+ context 'when importing a Gitea project' do
+ before do
+ project.update(import_type: 'gitea')
+ end
+
+ it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#attributes'
+ it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#number'
+ it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#source_branch_name'
+ it_behaves_like 'Gitlab::GithubImport::PullRequestFormatter#target_branch_name'
+ end
+
describe '#valid?' do
context 'when source, and target repos are not a fork' do
let(:raw_data) { double(base_data) }
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 9b49d6837c3..f420d71dee2 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -129,6 +129,7 @@ project:
- builds_email_service
- pipelines_email_service
- mattermost_slash_commands_service
+- slack_slash_commands_service
- irker_service
- pivotaltracker_service
- hipchat_service
@@ -136,8 +137,8 @@ project:
- assembla_service
- asana_service
- gemnasium_service
-- slack_notification_service
-- mattermost_notification_service
+- slack_service
+- mattermost_service
- buildkite_service
- bamboo_service
- teamcity_service
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 931d426c87f..2c0750c3377 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -15,6 +15,28 @@
"type": "ProjectLabel",
"priorities": [
]
+ },
+ {
+ "id": 3,
+ "title": "test3",
+ "color": "#428bca",
+ "group_id": 8,
+ "created_at": "2016-07-22T08:55:44.161Z",
+ "updated_at": "2016-07-22T08:55:44.161Z",
+ "template": false,
+ "description": "",
+ "project_id": null,
+ "type": "GroupLabel",
+ "priorities": [
+ {
+ "id": 1,
+ "project_id": 5,
+ "label_id": 1,
+ "priority": 1,
+ "created_at": "2016-10-18T09:35:43.338Z",
+ "updated_at": "2016-10-18T09:35:43.338Z"
+ }
+ ]
}
],
"issues": [
diff --git a/spec/lib/gitlab/import_sources_spec.rb b/spec/lib/gitlab/import_sources_spec.rb
new file mode 100644
index 00000000000..8cea38e9ff8
--- /dev/null
+++ b/spec/lib/gitlab/import_sources_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+describe Gitlab::ImportSources do
+ describe '.options' do
+ it 'returns a hash' do
+ expected =
+ {
+ 'GitHub' => 'github',
+ 'Bitbucket' => 'bitbucket',
+ 'GitLab.com' => 'gitlab',
+ 'Google Code' => 'google_code',
+ 'FogBugz' => 'fogbugz',
+ 'Repo by URL' => 'git',
+ 'GitLab export' => 'gitlab_project',
+ 'Gitea' => 'gitea'
+ }
+
+ expect(described_class.options).to eq(expected)
+ end
+ end
+
+ describe '.values' do
+ it 'returns an array' do
+ expected =
+ [
+ 'github',
+ 'bitbucket',
+ 'gitlab',
+ 'google_code',
+ 'fogbugz',
+ 'git',
+ 'gitlab_project',
+ 'gitea'
+ ]
+
+ expect(described_class.values).to eq(expected)
+ end
+ end
+
+ describe '.importer_names' do
+ it 'returns an array of importer names' do
+ expected =
+ [
+ 'github',
+ 'bitbucket',
+ 'gitlab',
+ 'google_code',
+ 'fogbugz',
+ 'gitlab_project',
+ 'gitea'
+ ]
+
+ expect(described_class.importer_names).to eq(expected)
+ end
+ end
+
+ describe '.importer' do
+ import_sources = {
+ 'github' => Gitlab::GithubImport::Importer,
+ 'bitbucket' => Gitlab::BitbucketImport::Importer,
+ 'gitlab' => Gitlab::GitlabImport::Importer,
+ 'google_code' => Gitlab::GoogleCodeImport::Importer,
+ 'fogbugz' => Gitlab::FogbugzImport::Importer,
+ 'git' => nil,
+ 'gitlab_project' => Gitlab::ImportExport::Importer,
+ 'gitea' => Gitlab::GithubImport::Importer
+ }
+
+ import_sources.each do |name, klass|
+ it "returns #{klass} when given #{name}" do
+ expect(described_class.importer(name)).to eq(klass)
+ end
+ end
+ end
+
+ describe '.title' do
+ import_sources = {
+ 'github' => 'GitHub',
+ 'bitbucket' => 'Bitbucket',
+ 'gitlab' => 'GitLab.com',
+ 'google_code' => 'Google Code',
+ 'fogbugz' => 'FogBugz',
+ 'git' => 'Repo by URL',
+ 'gitlab_project' => 'GitLab export',
+ 'gitea' => 'Gitea'
+ }
+
+ import_sources.each do |name, title|
+ it "returns #{title} when given #{name}" do
+ expect(described_class.title(name)).to eq(title)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
new file mode 100644
index 00000000000..c9bd52a3b8f
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe Gitlab::Kubernetes do
+ include described_class
+
+ describe '#container_exec_url' do
+ let(:api_url) { 'https://example.com' }
+ let(:namespace) { 'default' }
+ let(:pod_name) { 'pod1' }
+ let(:container_name) { 'container1' }
+
+ subject(:result) { URI::parse(container_exec_url(api_url, namespace, pod_name, container_name)) }
+
+ it { expect(result.scheme).to eq('wss') }
+ it { expect(result.host).to eq('example.com') }
+ it { expect(result.path).to eq('/api/v1/namespaces/default/pods/pod1/exec') }
+ it { expect(result.query).to eq('container=container1&stderr=true&stdin=true&stdout=true&tty=true&command=sh&command=-c&command=bash+%7C%7C+sh') }
+
+ context 'with a HTTP API URL' do
+ let(:api_url) { 'http://example.com' }
+
+ it { expect(result.scheme).to eq('ws') }
+ end
+
+ context 'with a path prefix in the API URL' do
+ let(:api_url) { 'https://example.com/prefix/' }
+ it { expect(result.path).to eq('/prefix/api/v1/namespaces/default/pods/pod1/exec') }
+ end
+
+ context 'with arguments that need urlencoding' do
+ let(:namespace) { 'default namespace' }
+ let(:pod_name) { 'pod 1' }
+ let(:container_name) { 'container 1' }
+
+ it { expect(result.path).to eq('/api/v1/namespaces/default%20namespace/pods/pod%201/exec') }
+ it { expect(result.query).to match(/\Acontainer=container\+1&/) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index bcaffd27909..7371b578a48 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -33,7 +33,7 @@ describe Gitlab::Metrics::RackMiddleware do
end
it 'tags a transaction with the method and path of the route in the grape endpoint' do
- route = double(:route, route_method: "GET", route_path: "/:version/projects/:id/archive(.:format)")
+ route = double(:route, request_method: "GET", path: "/:version/projects/:id/archive(.:format)")
endpoint = double(:endpoint, route: route)
env['api.endpoint'] = endpoint
@@ -117,7 +117,7 @@ describe Gitlab::Metrics::RackMiddleware do
let(:transaction) { middleware.transaction_from_env(env) }
it 'tags a transaction with the method and path of the route in the grape endpount' do
- route = double(:route, route_method: "GET", route_path: "/:version/projects/:id/archive(.:format)")
+ route = double(:route, request_method: "GET", path: "/:version/projects/:id/archive(.:format)")
endpoint = double(:endpoint, route: route)
env['api.endpoint'] = endpoint
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index ab1ab22795c..8d925460f01 100644
--- a/spec/lib/gitlab/middleware/multipart_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Middleware::Multipart do
expect(app).to receive(:call) do |env|
file = Rack::Request.new(env).params['file']
- expect(file).to be_a(File)
+ expect(file).to be_a(::UploadedFile)
expect(file.path).to eq(tempfile.path)
end
@@ -39,7 +39,7 @@ describe Gitlab::Middleware::Multipart do
expect(app).to receive(:call) do |env|
file = Rack::Request.new(env).params['user']['avatar']
- expect(file).to be_a(File)
+ expect(file).to be_a(::UploadedFile)
expect(file.path).to eq(tempfile.path)
end
@@ -54,7 +54,7 @@ describe Gitlab::Middleware::Multipart do
expect(app).to receive(:call) do |env|
file = Rack::Request.new(env).params['project']['milestone']['themesong']
- expect(file).to be_a(File)
+ expect(file).to be_a(::UploadedFile)
expect(file.path).to eq(tempfile.path)
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index b5b685da904..61da91dcbd3 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -37,6 +37,42 @@ describe Gitlab::Workhorse, lib: true do
end
end
+ describe '.terminal_websocket' do
+ def terminal(ca_pem: nil)
+ out = {
+ subprotocols: ['foo'],
+ url: 'wss://example.com/terminal.ws',
+ headers: { 'Authorization' => ['Token x'] }
+ }
+ out[:ca_pem] = ca_pem if ca_pem
+ out
+ end
+
+ def workhorse(ca_pem: nil)
+ out = {
+ 'Terminal' => {
+ 'Subprotocols' => ['foo'],
+ 'Url' => 'wss://example.com/terminal.ws',
+ 'Header' => { 'Authorization' => ['Token x'] }
+ }
+ }
+ out['Terminal']['CAPem'] = ca_pem if ca_pem
+ out
+ end
+
+ context 'without ca_pem' do
+ subject { Gitlab::Workhorse.terminal_websocket(terminal) }
+
+ it { is_expected.to eq(workhorse) }
+ end
+
+ context 'with ca_pem' do
+ subject { Gitlab::Workhorse.terminal_websocket(terminal(ca_pem: "foo")) }
+
+ it { is_expected.to eq(workhorse(ca_pem: "foo")) }
+ end
+ end
+
describe '.send_git_diff' do
let(:diff_refs) { double(base_sha: "base", head_sha: "head") }
subject { described_class.send_git_patch(repository, diff_refs) }
diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb
new file mode 100644
index 00000000000..dc11a414717
--- /dev/null
+++ b/spec/lib/mattermost/client_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Mattermost::Client do
+ let(:user) { build(:user) }
+
+ subject { described_class.new(user) }
+
+ context 'JSON parse error' do
+ before do
+ Struct.new("Request", :body, :success?)
+ end
+
+ it 'yields an error on malformed JSON' do
+ bad_json = Struct::Request.new("I'm not json", true)
+ expect { subject.send(:json_response, bad_json) }.to raise_error(Mattermost::ClientError)
+ end
+
+ it 'shows a client error if the request was unsuccessful' do
+ bad_request = Struct::Request.new("true", false)
+
+ expect { subject.send(:json_response, bad_request) }.to raise_error(Mattermost::ClientError)
+ end
+ end
+end
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
new file mode 100644
index 00000000000..5ccf1100898
--- /dev/null
+++ b/spec/lib/mattermost/command_spec.rb
@@ -0,0 +1,61 @@
+require 'spec_helper'
+
+describe Mattermost::Command do
+ let(:params) { { 'token' => 'token', team_id: 'abc' } }
+
+ before do
+ Mattermost::Session.base_uri('http://mattermost.example.com')
+
+ allow_any_instance_of(Mattermost::Client).to receive(:with_session).
+ and_yield(Mattermost::Session.new(nil))
+ end
+
+ describe '#create' do
+ let(:params) do
+ { team_id: 'abc',
+ trigger: 'gitlab'
+ }
+ end
+
+ subject { described_class.new(nil).create(params) }
+
+ context 'for valid trigger word' do
+ before do
+ stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
+ with(body: {
+ team_id: 'abc',
+ trigger: 'gitlab' }.to_json).
+ to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: { token: 'token' }.to_json
+ )
+ end
+
+ it 'returns a token' do
+ is_expected.to eq('token')
+ end
+ end
+
+ context 'for error message' do
+ before do
+ stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
+ to_return(
+ status: 500,
+ headers: { 'Content-Type' => 'application/json' },
+ body: {
+ id: 'api.command.duplicate_trigger.app_error',
+ message: 'This trigger word is already in use. Please choose another word.',
+ detailed_error: '',
+ request_id: 'obc374man7bx5r3dbc1q5qhf3r',
+ status_code: 500
+ }.to_json
+ )
+ end
+
+ it 'raises an error with message' do
+ expect { subject }.to raise_error(Mattermost::Error, 'This trigger word is already in use. Please choose another word.')
+ end
+ end
+ end
+end
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index 3c2eddbd221..74d12e37181 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -95,5 +95,29 @@ describe Mattermost::Session, type: :request do
end
end
end
+
+ context 'with lease' do
+ before do
+ allow(subject).to receive(:lease_try_obtain).and_return('aldkfjsldfk')
+ end
+
+ it 'tries to obtain a lease' do
+ expect(subject).to receive(:lease_try_obtain)
+ expect(Gitlab::ExclusiveLease).to receive(:cancel)
+
+ # Cannot setup a session, but we should still cancel the lease
+ expect { subject.with_session }.to raise_error(Mattermost::NoSessionError)
+ end
+ end
+
+ context 'without lease' do
+ before do
+ allow(subject).to receive(:lease_try_obtain).and_return(nil)
+ end
+
+ it 'returns a NoSessionError error' do
+ expect { subject.with_session }.to raise_error(Mattermost::NoSessionError)
+ end
+ end
end
end
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
new file mode 100644
index 00000000000..2d14be6bcc2
--- /dev/null
+++ b/spec/lib/mattermost/team_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Mattermost::Team do
+ before do
+ Mattermost::Session.base_uri('http://mattermost.example.com')
+
+ allow_any_instance_of(Mattermost::Client).to receive(:with_session).
+ and_yield(Mattermost::Session.new(nil))
+ end
+
+ describe '#all' do
+ subject { described_class.new(nil).all }
+
+ context 'for valid request' do
+ let(:response) do
+ [{
+ "id" => "xiyro8huptfhdndadpz8r3wnbo",
+ "create_at" => 1482174222155,
+ "update_at" => 1482174222155,
+ "delete_at" => 0,
+ "display_name" => "chatops",
+ "name" => "chatops",
+ "email" => "admin@example.com",
+ "type" => "O",
+ "company_name" => "",
+ "allowed_domains" => "",
+ "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro",
+ "allow_open_invite" => false }]
+ end
+
+ before do
+ stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
+ to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: response.to_json
+ )
+ end
+
+ it 'returns a token' do
+ is_expected.to eq(response)
+ end
+ end
+
+ context 'for error message' do
+ before do
+ stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
+ to_return(
+ status: 500,
+ headers: { 'Content-Type' => 'application/json' },
+ body: {
+ id: 'api.team.list.app_error',
+ message: 'Cannot list teams.',
+ detailed_error: '',
+ request_id: 'obc374man7bx5r3dbc1q5qhf3r',
+ status_code: 500
+ }.to_json
+ )
+ end
+
+ it 'raises an error with message' do
+ expect { subject }.to raise_error(Mattermost::Error, 'Cannot list teams.')
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 52dd41065e9..dc377d15f15 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -175,6 +175,30 @@ describe Ci::Pipeline, models: true do
end
end
+ describe '#stage' do
+ subject { pipeline.stage('test') }
+
+ context 'with status in stage' do
+ before do
+ create(:commit_status, pipeline: pipeline, stage: 'test')
+ end
+
+ it { expect(subject).to be_a Ci::Stage }
+ it { expect(subject.name).to eq 'test' }
+ it { expect(subject.statuses).not_to be_empty }
+ end
+
+ context 'without status in stage' do
+ before do
+ create(:commit_status, pipeline: pipeline, stage: 'build')
+ end
+
+ it 'return stage object' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
describe 'state machine' do
let(:current) { Time.now.change(usec: 0) }
let(:build) { create_build('build1', 0) }
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index 8fff38f7cda..742bedb37e4 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -28,6 +28,19 @@ describe Ci::Stage, models: true do
end
end
+ describe '#statuses_count' do
+ before do
+ create_job(:ci_build)
+ create_job(:ci_build, stage: 'other stage')
+ end
+
+ subject { stage.statuses_count }
+
+ it "counts statuses only from current stage" do
+ is_expected.to eq(1)
+ end
+ end
+
describe '#builds' do
let!(:stage_build) { create_job(:ci_build) }
let!(:commit_status) { create_job(:commit_status) }
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
new file mode 100644
index 00000000000..a0765a264cf
--- /dev/null
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -0,0 +1,145 @@
+require 'spec_helper'
+
+describe ReactiveCaching, caching: true do
+ include ReactiveCachingHelpers
+
+ class CacheTest
+ include ReactiveCaching
+
+ self.reactive_cache_key = ->(thing) { ["foo", thing.id] }
+
+ self.reactive_cache_lifetime = 5.minutes
+ self.reactive_cache_refresh_interval = 15.seconds
+
+ attr_reader :id
+
+ def initialize(id, &blk)
+ @id = id
+ @calculator = blk
+ end
+
+ def calculate_reactive_cache
+ @calculator.call
+ end
+
+ def result
+ with_reactive_cache do |data|
+ data / 2
+ end
+ end
+ end
+
+ let(:now) { Time.now.utc }
+
+ around(:each) do |example|
+ Timecop.freeze(now) { example.run }
+ end
+
+ let(:calculation) { -> { 2 + 2 } }
+ let(:cache_key) { "foo:666" }
+ let(:instance) { CacheTest.new(666, &calculation) }
+
+ describe '#with_reactive_cache' do
+ before { stub_reactive_cache }
+ subject(:go!) { instance.result }
+
+ context 'when cache is empty' do
+ it { is_expected.to be_nil }
+
+ it 'queues a background worker' do
+ expect(ReactiveCachingWorker).to receive(:perform_async).with(CacheTest, 666)
+
+ go!
+ end
+
+ it 'updates the cache lifespan' do
+ go!
+
+ expect(reactive_cache_alive?(instance)).to be_truthy
+ end
+ end
+
+ context 'when the cache is full' do
+ before { stub_reactive_cache(instance, 4) }
+
+ it { is_expected.to eq(2) }
+
+ context 'and expired' do
+ before { invalidate_reactive_cache(instance) }
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
+ describe '#clear_reactive_cache!' do
+ before do
+ stub_reactive_cache(instance, 4)
+ instance.clear_reactive_cache!
+ end
+
+ it { expect(instance.result).to be_nil }
+ end
+
+ describe '#exclusively_update_reactive_cache!' do
+ subject(:go!) { instance.exclusively_update_reactive_cache! }
+
+ context 'when the lease is free and lifetime is not exceeded' do
+ before { stub_reactive_cache(instance, "preexisting") }
+
+ it 'takes and releases the lease' do
+ expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return("000000")
+ expect(Gitlab::ExclusiveLease).to receive(:cancel).with(cache_key, "000000")
+
+ go!
+ end
+
+ it 'caches the result of #calculate_reactive_cache' do
+ go!
+
+ expect(read_reactive_cache(instance)).to eq(calculation.call)
+ end
+
+ it "enqueues a repeat worker" do
+ expect_reactive_cache_update_queued(instance)
+
+ go!
+ end
+
+ context 'and #calculate_reactive_cache raises an exception' do
+ before { stub_reactive_cache(instance, "preexisting") }
+ let(:calculation) { -> { raise "foo"} }
+
+ it 'leaves the cache untouched' do
+ expect { go! }.to raise_error("foo")
+ expect(read_reactive_cache(instance)).to eq("preexisting")
+ end
+
+ it 'enqueues a repeat worker' do
+ expect_reactive_cache_update_queued(instance)
+
+ expect { go! }.to raise_error("foo")
+ end
+ end
+ end
+
+ context 'when lifetime is exceeded' do
+ it 'skips the calculation' do
+ expect(instance).to receive(:calculate_reactive_cache).never
+
+ go!
+ end
+ end
+
+ context 'when the lease is already taken' do
+ before do
+ expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(nil)
+ end
+
+ it 'skips the calculation' do
+ expect(instance).to receive(:calculate_reactive_cache).never
+
+ go!
+ end
+ end
+ end
+end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 97cbb093ed2..93eb402e060 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
describe Environment, models: true do
- subject(:environment) { create(:environment) }
+ let(:project) { create(:empty_project) }
+ subject(:environment) { create(:environment, project: project) }
it { is_expected.to belong_to(:project) }
it { is_expected.to have_many(:deployments) }
@@ -31,6 +32,8 @@ describe Environment, models: true do
end
describe '#includes_commit?' do
+ let(:project) { create(:project) }
+
context 'without a last deployment' do
it "returns false" do
expect(environment.includes_commit?('HEAD')).to be false
@@ -38,9 +41,6 @@ describe Environment, models: true do
end
context 'with a last deployment' do
- let(:project) { create(:project) }
- let(:environment) { create(:environment, project: project) }
-
let!(:deployment) do
create(:deployment, environment: environment, sha: project.commit('master').id)
end
@@ -65,7 +65,6 @@ describe Environment, models: true do
describe '#first_deployment_for' do
let(:project) { create(:project) }
- let!(:environment) { create(:environment, project: project) }
let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) }
let!(:deployment1) { create(:deployment, environment: environment, ref: commit.id) }
let(:head_commit) { project.commit }
@@ -196,6 +195,57 @@ describe Environment, models: true do
end
end
+ describe '#has_terminals?' do
+ subject { environment.has_terminals? }
+
+ context 'when the enviroment is available' do
+ context 'with a deployment service' do
+ let(:project) { create(:kubernetes_project) }
+
+ context 'and a deployment' do
+ let!(:deployment) { create(:deployment, environment: environment) }
+ it { is_expected.to be_truthy }
+ end
+
+ context 'but no deployments' do
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'without a deployment service' do
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'when the environment is unavailable' do
+ let(:project) { create(:kubernetes_project) }
+ before { environment.stop }
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#terminals' do
+ let(:project) { create(:kubernetes_project) }
+ subject { environment.terminals }
+
+ context 'when the environment has terminals' do
+ before { allow(environment).to receive(:has_terminals?).and_return(true) }
+
+ it 'returns the terminals from the deployment service' do
+ expect(project.deployment_service).
+ to receive(:terminals).with(environment).
+ and_return(:fake_terminals)
+
+ is_expected.to eq(:fake_terminals)
+ end
+ end
+
+ context 'when the environment does not have terminals' do
+ before { allow(environment).to receive(:has_terminals?).and_return(false) }
+ it { is_expected.to eq(nil) }
+ end
+ end
+
describe '#slug' do
it "is automatically generated" do
expect(environment.slug).not_to be_nil
diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb
new file mode 100644
index 00000000000..33ef67f97a7
--- /dev/null
+++ b/spec/models/project_authorization_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe ProjectAuthorization do
+ let(:user) { create(:user) }
+ let(:project1) { create(:empty_project) }
+ let(:project2) { create(:empty_project) }
+
+ describe '.insert_authorizations' do
+ it 'inserts the authorizations' do
+ described_class.
+ insert_authorizations([[user.id, project1.id, Gitlab::Access::MASTER]])
+
+ expect(user.project_authorizations.count).to eq(1)
+ end
+
+ it 'inserts rows in batches' do
+ described_class.insert_authorizations([
+ [user.id, project1.id, Gitlab::Access::MASTER],
+ [user.id, project2.id, Gitlab::Access::MASTER],
+ ], 1)
+
+ expect(user.project_authorizations.count).to eq(2)
+ end
+ end
+end
diff --git a/spec/models/project_services/chat_message/build_message_spec.rb b/spec/models/project_services/chat_message/build_message_spec.rb
index b71d153f814..50ad5013df9 100644
--- a/spec/models/project_services/chat_message/build_message_spec.rb
+++ b/spec/models/project_services/chat_message/build_message_spec.rb
@@ -10,7 +10,7 @@ describe ChatMessage::BuildMessage do
tag: false,
project_name: 'project_name',
- project_url: 'example.gitlab.com',
+ project_url: 'http://example.gitlab.com',
commit: {
status: status,
@@ -48,10 +48,10 @@ describe ChatMessage::BuildMessage do
end
def build_message(status_text = status)
- "<example.gitlab.com|project_name>:" \
- " Commit <example.gitlab.com/commit/" \
+ "<http://example.gitlab.com|project_name>:" \
+ " Commit <http://example.gitlab.com/commit/" \
"97de212e80737a608d939f648d959671fb0a0142/builds|97de212e>" \
- " of <example.gitlab.com/commits/develop|develop> branch" \
+ " of <http://example.gitlab.com/commits/develop|develop> branch" \
" by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}"
end
end
diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb
index ebe0ead4408..190ff4c535d 100644
--- a/spec/models/project_services/chat_message/issue_message_spec.rb
+++ b/spec/models/project_services/chat_message/issue_message_spec.rb
@@ -10,14 +10,14 @@ describe ChatMessage::IssueMessage, models: true do
username: 'test.user'
},
project_name: 'project_name',
- project_url: 'somewhere.com',
+ project_url: 'http://somewhere.com',
object_attributes: {
title: 'Issue title',
id: 10,
iid: 100,
assignee_id: 1,
- url: 'url',
+ url: 'http://url.com',
action: 'open',
state: 'opened',
description: 'issue description'
@@ -40,11 +40,11 @@ describe ChatMessage::IssueMessage, models: true do
context 'open' do
it 'returns a message regarding opening of issues' do
expect(subject.pretext).to eq(
- '<somewhere.com|[project_name>] Issue opened by test.user')
+ '[<http://somewhere.com|project_name>] Issue opened by test.user')
expect(subject.attachments).to eq([
{
title: "#100 Issue title",
- title_link: "url",
+ title_link: "http://url.com",
text: "issue description",
color: color,
}
@@ -60,7 +60,7 @@ describe ChatMessage::IssueMessage, models: true do
it 'returns a message regarding closing of issues' do
expect(subject.pretext). to eq(
- '<somewhere.com|[project_name>] Issue <url|#100 Issue title> closed by test.user')
+ '[<http://somewhere.com|project_name>] Issue <http://url.com|#100 Issue title> closed by test.user')
expect(subject.attachments).to be_empty
end
end
diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb
index 07c414c6ca4..cc154112e90 100644
--- a/spec/models/project_services/chat_message/merge_message_spec.rb
+++ b/spec/models/project_services/chat_message/merge_message_spec.rb
@@ -10,14 +10,14 @@ describe ChatMessage::MergeMessage, models: true do
username: 'test.user'
},
project_name: 'project_name',
- project_url: 'somewhere.com',
+ project_url: 'http://somewhere.com',
object_attributes: {
title: "Issue title\nSecond line",
id: 10,
iid: 100,
assignee_id: 1,
- url: 'url',
+ url: 'http://url.com',
state: 'opened',
description: 'issue description',
source_branch: 'source_branch',
@@ -31,8 +31,8 @@ describe ChatMessage::MergeMessage, models: true do
context 'open' do
it 'returns a message regarding opening of merge requests' do
expect(subject.pretext).to eq(
- 'test.user opened <somewhere.com/merge_requests/100|merge request !100> '\
- 'in <somewhere.com|project_name>: *Issue title*')
+ 'test.user opened <http://somewhere.com/merge_requests/100|merge request !100> '\
+ 'in <http://somewhere.com|project_name>: *Issue title*')
expect(subject.attachments).to be_empty
end
end
@@ -43,8 +43,8 @@ describe ChatMessage::MergeMessage, models: true do
end
it 'returns a message regarding closing of merge requests' do
expect(subject.pretext).to eq(
- 'test.user closed <somewhere.com/merge_requests/100|merge request !100> '\
- 'in <somewhere.com|project_name>: *Issue title*')
+ 'test.user closed <http://somewhere.com/merge_requests/100|merge request !100> '\
+ 'in <http://somewhere.com|project_name>: *Issue title*')
expect(subject.attachments).to be_empty
end
end
diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb
index 31936da40a2..da700a08e57 100644
--- a/spec/models/project_services/chat_message/note_message_spec.rb
+++ b/spec/models/project_services/chat_message/note_message_spec.rb
@@ -11,15 +11,15 @@ describe ChatMessage::NoteMessage, models: true do
avatar_url: 'http://fakeavatar'
},
project_name: 'project_name',
- project_url: 'somewhere.com',
+ project_url: 'http://somewhere.com',
repository: {
name: 'project_name',
- url: 'somewhere.com',
+ url: 'http://somewhere.com',
},
object_attributes: {
id: 10,
note: 'comment on a commit',
- url: 'url',
+ url: 'http://url.com',
noteable_type: 'Commit'
}
}
@@ -37,8 +37,8 @@ describe ChatMessage::NoteMessage, models: true do
it 'returns a message regarding notes on commits' do
message = described_class.new(@args)
- expect(message.pretext).to eq("test.user <url|commented on " \
- "commit 5f163b2b> in <somewhere.com|project_name>: " \
+ expect(message.pretext).to eq("test.user <http://url.com|commented on " \
+ "commit 5f163b2b> in <http://somewhere.com|project_name>: " \
"*Added a commit message*")
expected_attachments = [
{
@@ -63,8 +63,8 @@ describe ChatMessage::NoteMessage, models: true do
it 'returns a message regarding notes on a merge request' do
message = described_class.new(@args)
- expect(message.pretext).to eq("test.user <url|commented on " \
- "merge request !30> in <somewhere.com|project_name>: " \
+ expect(message.pretext).to eq("test.user <http://url.com|commented on " \
+ "merge request !30> in <http://somewhere.com|project_name>: " \
"*merge request title*")
expected_attachments = [
{
@@ -90,8 +90,8 @@ describe ChatMessage::NoteMessage, models: true do
it 'returns a message regarding notes on an issue' do
message = described_class.new(@args)
expect(message.pretext).to eq(
- "test.user <url|commented on " \
- "issue #20> in <somewhere.com|project_name>: " \
+ "test.user <http://url.com|commented on " \
+ "issue #20> in <http://somewhere.com|project_name>: " \
"*issue title*")
expected_attachments = [
{
@@ -115,8 +115,8 @@ describe ChatMessage::NoteMessage, models: true do
it 'returns a message regarding notes on a project snippet' do
message = described_class.new(@args)
- expect(message.pretext).to eq("test.user <url|commented on " \
- "snippet #5> in <somewhere.com|project_name>: " \
+ expect(message.pretext).to eq("test.user <http://url.com|commented on " \
+ "snippet #5> in <http://somewhere.com|project_name>: " \
"*snippet title*")
expected_attachments = [
{
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index eca71db07b6..bf2a9616455 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -15,7 +15,7 @@ describe ChatMessage::PipelineMessage do
duration: duration
},
project: { path_with_namespace: 'project_name',
- web_url: 'example.gitlab.com' },
+ web_url: 'http://example.gitlab.com' },
user: user
}
end
@@ -59,9 +59,9 @@ describe ChatMessage::PipelineMessage do
end
def build_message(status_text = status, name = user[:name])
- "<example.gitlab.com|project_name>:" \
- " Pipeline <example.gitlab.com/pipelines/123|#123>" \
- " of <example.gitlab.com/commits/develop|develop> branch" \
+ "<http://example.gitlab.com|project_name>:" \
+ " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
+ " of <http://example.gitlab.com/commits/develop|develop> branch" \
" by #{name} #{status_text} in #{duration} #{'second'.pluralize(duration)}"
end
end
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index b781c4505db..24928873bad 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -10,7 +10,7 @@ describe ChatMessage::PushMessage, models: true do
project_name: 'project_name',
ref: 'refs/heads/master',
user_name: 'test.user',
- project_url: 'url'
+ project_url: 'http://url.com'
}
end
@@ -19,20 +19,20 @@ describe ChatMessage::PushMessage, models: true do
context 'push' do
before do
args[:commits] = [
- { message: 'message1', url: 'url1', id: 'abcdefghijkl', author: { name: 'author1' } },
- { message: 'message2', url: 'url2', id: '123456789012', author: { name: 'author2' } },
+ { message: 'message1', url: 'http://url1.com', id: 'abcdefghijkl', author: { name: 'author1' } },
+ { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } },
]
end
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq(
- 'test.user pushed to branch <url/commits/master|master> of '\
- '<url|project_name> (<url/compare/before...after|Compare changes>)'
+ 'test.user pushed to branch <http://url.com/commits/master|master> of '\
+ '<http://url.com|project_name> (<http://url.com/compare/before...after|Compare changes>)'
)
expect(subject.attachments).to eq([
{
- text: "<url1|abcdefgh>: message1 - author1\n"\
- "<url2|12345678>: message2 - author2",
+ text: "<http://url1.com|abcdefgh>: message1 - author1\n"\
+ "<http://url2.com|12345678>: message2 - author2",
color: color,
}
])
@@ -47,14 +47,14 @@ describe ChatMessage::PushMessage, models: true do
project_name: 'project_name',
ref: 'refs/tags/new_tag',
user_name: 'test.user',
- project_url: 'url'
+ project_url: 'http://url.com'
}
end
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq('test.user pushed new tag ' \
- '<url/commits/new_tag|new_tag> to ' \
- '<url|project_name>')
+ '<http://url.com/commits/new_tag|new_tag> to ' \
+ '<http://url.com|project_name>')
expect(subject.attachments).to be_empty
end
end
@@ -66,8 +66,8 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding a new branch' do
expect(subject.pretext).to eq(
- 'test.user pushed new branch <url/commits/master|master> to '\
- '<url|project_name>'
+ 'test.user pushed new branch <http://url.com/commits/master|master> to '\
+ '<http://url.com|project_name>'
)
expect(subject.attachments).to be_empty
end
@@ -80,7 +80,7 @@ describe ChatMessage::PushMessage, models: true do
it 'returns a message regarding a removed branch' do
expect(subject.pretext).to eq(
- 'test.user removed branch master from <url|project_name>'
+ 'test.user removed branch master from <http://url.com|project_name>'
)
expect(subject.attachments).to be_empty
end
diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
index 94c04dc0865..a2ad61e38e7 100644
--- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb
+++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
@@ -10,10 +10,10 @@ describe ChatMessage::WikiPageMessage, models: true do
username: 'test.user'
},
project_name: 'project_name',
- project_url: 'somewhere.com',
+ project_url: 'http://somewhere.com',
object_attributes: {
title: 'Wiki page title',
- url: 'url',
+ url: 'http://url.com',
content: 'Wiki page description'
}
}
@@ -25,7 +25,7 @@ describe ChatMessage::WikiPageMessage, models: true do
it 'returns a message that a new wiki page was created' do
expect(subject.pretext).to eq(
- 'test.user created <url|wiki page> in <somewhere.com|project_name>: '\
+ 'test.user created <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\
'*Wiki page title*')
end
end
@@ -35,7 +35,7 @@ describe ChatMessage::WikiPageMessage, models: true do
it 'returns a message that a wiki page was updated' do
expect(subject.pretext).to eq(
- 'test.user edited <url|wiki page> in <somewhere.com|project_name>: '\
+ 'test.user edited <http://url.com|wiki page> in <http://somewhere.com|project_name>: '\
'*Wiki page title*')
end
end
diff --git a/spec/models/project_services/chat_service_spec.rb b/spec/models/project_services/chat_service_spec.rb
deleted file mode 100644
index c6a45a3e1be..00000000000
--- a/spec/models/project_services/chat_service_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'spec_helper'
-
-describe ChatService, models: true do
- describe "Associations" do
- it { is_expected.to have_many :chat_names }
- end
-
- describe '#valid_token?' do
- subject { described_class.new }
-
- it 'is false as it has no token' do
- expect(subject.valid_token?('wer')).to be_falsey
- end
- end
-end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 3603602e41d..4f3cd14e941 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -1,7 +1,29 @@
require 'spec_helper'
-describe KubernetesService, models: true do
- let(:project) { create(:empty_project) }
+describe KubernetesService, models: true, caching: true do
+ include KubernetesHelpers
+ include ReactiveCachingHelpers
+
+ let(:project) { create(:kubernetes_project) }
+ let(:service) { project.kubernetes_service }
+
+ # We use Kubeclient to interactive with the Kubernetes API. It will
+ # GET /api/v1 for a list of resources the API supports. This must be stubbed
+ # in addition to any other HTTP requests we expect it to perform.
+ let(:discovery_url) { service.api_url + '/api/v1' }
+ let(:discovery_response) { { body: kube_discovery_body.to_json } }
+
+ let(:pods_url) { service.api_url + "/api/v1/namespaces/#{service.namespace}/pods" }
+ let(:pods_response) { { body: kube_pods_body(kube_pod).to_json } }
+
+ def stub_kubeclient_discover
+ WebMock.stub_request(:get, discovery_url).to_return(discovery_response)
+ end
+
+ def stub_kubeclient_pods
+ stub_kubeclient_discover
+ WebMock.stub_request(:get, pods_url).to_return(pods_response)
+ end
describe "Associations" do
it { is_expected.to belong_to :project }
@@ -65,22 +87,15 @@ describe KubernetesService, models: true do
end
describe '#test' do
- let(:project) { create(:kubernetes_project) }
- let(:service) { project.kubernetes_service }
- let(:discovery_url) { service.api_url + '/api/v1' }
-
- # JSON response body from Kubernetes GET /api/v1 request
- let(:discovery_response) { { "kind" => "APIResourceList", "groupVersion" => "v1", "resources" => [] }.to_json }
+ before do
+ stub_kubeclient_discover
+ end
context 'with path prefix in api_url' do
let(:discovery_url) { 'https://kubernetes.example.com/prefix/api/v1' }
- before do
- service.api_url = 'https://kubernetes.example.com/prefix/'
- end
-
it 'tests with the prefix' do
- WebMock.stub_request(:get, discovery_url).to_return(body: discovery_response)
+ service.api_url = 'https://kubernetes.example.com/prefix/'
expect(service.test[:success]).to be_truthy
expect(WebMock).to have_requested(:get, discovery_url).once
@@ -88,17 +103,12 @@ describe KubernetesService, models: true do
end
context 'with custom CA certificate' do
- let(:certificate) { "CA PEM DATA" }
- before do
- service.update_attributes!(ca_pem: certificate)
- end
-
it 'is added to the certificate store' do
- cert = double("certificate")
+ service.ca_pem = "CA PEM DATA"
- expect(OpenSSL::X509::Certificate).to receive(:new).with(certificate).and_return(cert)
+ cert = double("certificate")
+ expect(OpenSSL::X509::Certificate).to receive(:new).with(service.ca_pem).and_return(cert)
expect_any_instance_of(OpenSSL::X509::Store).to receive(:add_cert).with(cert)
- WebMock.stub_request(:get, discovery_url).to_return(body: discovery_response)
expect(service.test[:success]).to be_truthy
expect(WebMock).to have_requested(:get, discovery_url).once
@@ -107,17 +117,15 @@ describe KubernetesService, models: true do
context 'success' do
it 'reads the discovery endpoint' do
- WebMock.stub_request(:get, discovery_url).to_return(body: discovery_response)
-
expect(service.test[:success]).to be_truthy
expect(WebMock).to have_requested(:get, discovery_url).once
end
end
context 'failure' do
- it 'fails to read the discovery endpoint' do
- WebMock.stub_request(:get, discovery_url).to_return(status: 404)
+ let(:discovery_response) { { status: 404 } }
+ it 'fails to read the discovery endpoint' do
expect(service.test[:success]).to be_falsy
expect(WebMock).to have_requested(:get, discovery_url).once
end
@@ -156,4 +164,55 @@ describe KubernetesService, models: true do
)
end
end
+
+ describe '#terminals' do
+ let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
+ subject { service.terminals(environment) }
+
+ context 'with invalid pods' do
+ it 'returns no terminals' do
+ stub_reactive_cache(service, pods: [ { "bad" => "pod" } ])
+
+ is_expected.to be_empty
+ end
+ end
+
+ context 'with valid pods' do
+ let(:pod) { kube_pod(app: environment.slug) }
+ let(:terminals) { kube_terminals(service, pod) }
+
+ it 'returns terminals' do
+ stub_reactive_cache(service, pods: [ pod, pod, kube_pod(app: "should-be-filtered-out") ])
+
+ is_expected.to eq(terminals + terminals)
+ end
+ end
+ end
+
+ describe '#calculate_reactive_cache' do
+ before { stub_kubeclient_pods }
+ subject { service.calculate_reactive_cache }
+
+ context 'when service is inactive' do
+ before { service.active = false }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when kubernetes responds with valid pods' do
+ it { is_expected.to eq(pods: [kube_pod]) }
+ end
+
+ context 'when kubernetes responds with 500' do
+ let(:pods_response) { { status: 500 } }
+
+ it { expect { subject }.to raise_error(KubeException) }
+ end
+
+ context 'when kubernetes responds with 404' do
+ let(:pods_response) { { status: 404 } }
+
+ it { is_expected.to eq(pods: []) }
+ end
+ end
end
diff --git a/spec/models/project_services/mattermost_notification_service_spec.rb b/spec/models/project_services/mattermost_notification_service_spec.rb
deleted file mode 100644
index c01e64b4c8e..00000000000
--- a/spec/models/project_services/mattermost_notification_service_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'spec_helper'
-
-describe MattermostNotificationService, models: true do
- it_behaves_like "slack or mattermost"
-end
diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb
new file mode 100644
index 00000000000..490d6aedffc
--- /dev/null
+++ b/spec/models/project_services/mattermost_service_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe MattermostService, models: true do
+ it_behaves_like "slack or mattermost notifications"
+end
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 4a1037e950b..d6f4fbd7265 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -1,97 +1,122 @@
require 'spec_helper'
-describe MattermostSlashCommandsService, models: true do
- describe "Associations" do
- it { is_expected.to respond_to :token }
- end
+describe MattermostSlashCommandsService, :models do
+ it_behaves_like "chat slash commands service"
- describe '#valid_token?' do
- subject { described_class.new }
+ context 'Mattermost API' do
+ let(:project) { create(:empty_project) }
+ let(:service) { project.build_mattermost_slash_commands_service }
+ let(:user) { create(:user)}
- context 'when the token is empty' do
- it 'is false' do
- expect(subject.valid_token?('wer')).to be_falsey
- end
+ before do
+ Mattermost::Session.base_uri("http://mattermost.example.com")
+
+ allow_any_instance_of(Mattermost::Client).to receive(:with_session).
+ and_yield(Mattermost::Session.new(nil))
end
- context 'when there is a token' do
- before do
- subject.token = '123'
+ describe '#configure' do
+ subject do
+ service.configure(user, team_id: 'abc',
+ trigger: 'gitlab', url: 'http://trigger.url',
+ icon_url: 'http://icon.url/icon.png')
end
- it 'accepts equal tokens' do
- expect(subject.valid_token?('123')).to be_truthy
- end
- end
- end
+ context 'the requests succeeds' do
+ before do
+ stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
+ with(body: {
+ team_id: 'abc',
+ trigger: 'gitlab',
+ url: 'http://trigger.url',
+ icon_url: 'http://icon.url/icon.png',
+ auto_complete: true,
+ auto_complete_desc: "Perform common operations on: #{project.name_with_namespace}",
+ auto_complete_hint: '[help]',
+ description: "Perform common operations on: #{project.name_with_namespace}",
+ display_name: "GitLab / #{project.name_with_namespace}",
+ method: 'P',
+ user_name: 'GitLab' }.to_json).
+ to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: { token: 'token' }.to_json
+ )
+ end
- describe '#trigger' do
- subject { described_class.new }
+ it 'saves the service' do
+ expect { subject }.to change { project.services.count }.by(1)
+ end
- context 'no token is passed' do
- let(:params) { Hash.new }
+ it 'saves the token' do
+ subject
- it 'returns nil' do
- expect(subject.trigger(params)).to be_nil
+ expect(service.reload.token).to eq('token')
+ end
end
- end
- context 'with a token passed' do
- let(:project) { create(:empty_project) }
- let(:params) { { token: 'token' } }
-
- before do
- allow(subject).to receive(:token).and_return('token')
- end
+ context 'an error is received' do
+ before do
+ stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
+ to_return(
+ status: 500,
+ headers: { 'Content-Type' => 'application/json' },
+ body: {
+ id: 'api.command.duplicate_trigger.app_error',
+ message: 'This trigger word is already in use. Please choose another word.',
+ detailed_error: '',
+ request_id: 'obc374man7bx5r3dbc1q5qhf3r',
+ status_code: 500
+ }.to_json
+ )
+ end
- context 'no user can be found' do
- context 'when no url can be generated' do
- it 'responds with the authorize url' do
- response = subject.trigger(params)
+ it 'shows error messages' do
+ succeeded, message = subject
- expect(response[:response_type]).to eq :ephemeral
- expect(response[:text]).to start_with ":sweat_smile: Couldn't identify you"
- end
+ expect(succeeded).to be(false)
+ expect(message).to eq('This trigger word is already in use. Please choose another word.')
end
+ end
+ end
- context 'when an auth url can be generated' do
- let(:params) do
- {
- team_domain: 'http://domain.tld',
- team_id: 'T3423423',
- user_id: 'U234234',
- user_name: 'mepmep',
- token: 'token'
- }
- end
-
- let(:service) do
- project.create_mattermost_slash_commands_service(
- properties: { token: 'token' }
- )
- end
+ describe '#list_teams' do
+ subject do
+ service.list_teams(user)
+ end
- it 'generates the url' do
- response = service.trigger(params)
+ context 'the requests succeeds' do
+ before do
+ stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
+ to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: ['list'].to_json
+ )
+ end
- expect(response[:text]).to start_with(':wave: Hi there!')
- end
+ it 'returns a list of teams' do
+ expect(subject).not_to be_empty
end
end
- context 'when the user is authenticated' do
- let!(:chat_name) { create(:chat_name, service: service) }
- let(:service) do
- project.create_mattermost_slash_commands_service(
- properties: { token: 'token' }
- )
+ context 'an error is received' do
+ before do
+ stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
+ to_return(
+ status: 500,
+ headers: { 'Content-Type' => 'application/json' },
+ body: {
+ message: 'Failed to get team list.'
+ }.to_json
+ )
end
- let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } }
- it 'triggers the command' do
- expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute)
+ it 'shows error messages' do
+ teams, message = subject
- service.trigger(params)
+ expect(teams).to be_empty
+ expect(message).to eq('Failed to get team list.')
end
end
end
diff --git a/spec/models/project_services/slack_notification_service_spec.rb b/spec/models/project_services/slack_notification_service_spec.rb
deleted file mode 100644
index 59ddddf7454..00000000000
--- a/spec/models/project_services/slack_notification_service_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'spec_helper'
-
-describe SlackNotificationService, models: true do
- it_behaves_like "slack or mattermost"
-end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
new file mode 100644
index 00000000000..9a3ecc66d83
--- /dev/null
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe SlackService, models: true do
+ it_behaves_like "slack or mattermost notifications"
+end
diff --git a/spec/models/project_services/slack_slash_commands_service.rb b/spec/models/project_services/slack_slash_commands_service.rb
new file mode 100644
index 00000000000..5775e439906
--- /dev/null
+++ b/spec/models/project_services/slack_slash_commands_service.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+describe SlackSlashCommandsService, :models do
+ it_behaves_like "chat slash commands service"
+
+ describe '#trigger' do
+ context 'when an auth url is generated' do
+ let(:project) { create(:empty_project) }
+ let(:params) do
+ {
+ team_domain: 'http://domain.tld',
+ team_id: 'T3423423',
+ user_id: 'U234234',
+ user_name: 'mepmep',
+ token: 'token'
+ }
+ end
+
+ let(:service) do
+ project.create_slack_slash_commands_service(
+ properties: { token: 'token' }
+ )
+ end
+
+ let(:authorize_url) do
+ 'http://authorize.example.com/'
+ end
+
+ before do
+ allow(service).to receive(:authorize_chat_name_url).and_return(authorize_url)
+ end
+
+ it 'uses slack compatible links' do
+ response = service.trigger(params)
+
+ expect(response[:text]).to include("<#{authorize_url}|connect your GitLab account>")
+ end
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index ed6b2c6a22b..88d5d14f855 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -20,10 +20,9 @@ describe Project, models: true do
it { is_expected.to have_many(:deploy_keys) }
it { is_expected.to have_many(:hooks).dependent(:destroy) }
it { is_expected.to have_many(:protected_branches).dependent(:destroy) }
- it { is_expected.to have_many(:chat_services) }
it { is_expected.to have_one(:forked_project_link).dependent(:destroy) }
- it { is_expected.to have_one(:slack_notification_service).dependent(:destroy) }
- it { is_expected.to have_one(:mattermost_notification_service).dependent(:destroy) }
+ it { is_expected.to have_one(:slack_service).dependent(:destroy) }
+ it { is_expected.to have_one(:mattermost_service).dependent(:destroy) }
it { is_expected.to have_one(:pushover_service).dependent(:destroy) }
it { is_expected.to have_one(:asana_service).dependent(:destroy) }
it { is_expected.to have_many(:boards).dependent(:destroy) }
@@ -37,6 +36,7 @@ describe Project, models: true do
it { is_expected.to have_one(:hipchat_service).dependent(:destroy) }
it { is_expected.to have_one(:flowdock_service).dependent(:destroy) }
it { is_expected.to have_one(:assembla_service).dependent(:destroy) }
+ it { is_expected.to have_one(:slack_slash_commands_service).dependent(:destroy) }
it { is_expected.to have_one(:mattermost_slash_commands_service).dependent(:destroy) }
it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) }
it { is_expected.to have_one(:buildkite_service).dependent(:destroy) }
@@ -1458,6 +1458,18 @@ describe Project, models: true do
end
end
+ describe '#gitlab_project_import?' do
+ subject(:project) { build(:project, import_type: 'gitlab_project') }
+
+ it { expect(project.gitlab_project_import?).to be true }
+ end
+
+ describe '#gitea_import?' do
+ subject(:project) { build(:project, import_type: 'gitea') }
+
+ it { expect(project.gitea_import?).to be true }
+ end
+
describe '#lfs_enabled?' do
let(:project) { create(:project) }
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 6f491fdf9a0..8481a9bef16 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Route, models: true do
- let!(:group) { create(:group) }
+ let!(:group) { create(:group, path: 'gitlab') }
let!(:route) { group.route }
describe 'relationships' do
@@ -17,13 +17,15 @@ describe Route, models: true do
describe '#rename_children' do
let!(:nested_group) { create(:group, path: "test", parent: group) }
let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) }
+ let!(:similar_group) { create(:group, path: 'gitlab-org') }
- it "updates children routes with new path" do
- route.update_attributes(path: 'bar')
+ before { route.update_attributes(path: 'bar') }
+ it "updates children routes with new path" do
expect(described_class.exists?(path: 'bar')).to be_truthy
expect(described_class.exists?(path: 'bar/test')).to be_truthy
expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy
+ expect(described_class.exists?(path: 'gitlab-org')).to be_truthy
end
end
end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 2081f80ccc1..685da28c673 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -4,7 +4,14 @@ describe API::Files, api: true do
include ApiHelpers
let(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) }
+ let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
let(:file_path) { 'files/ruby/popen.rb' }
+ let(:params) do
+ {
+ file_path: file_path,
+ ref: 'master'
+ }
+ end
let(:author_email) { FFaker::Internet.email }
# I have to remove periods from the end of the name
@@ -24,36 +31,72 @@ describe API::Files, api: true do
before { project.team << [user, :developer] }
describe "GET /projects/:id/repository/files" do
- it "returns file info" do
- params = {
- file_path: file_path,
- ref: 'master',
- }
+ let(:route) { "/projects/#{project.id}/repository/files" }
- get api("/projects/#{project.id}/repository/files", user), params
+ shared_examples_for 'repository files' do
+ it "returns file info" do
+ get api(route, current_user), params
- expect(response).to have_http_status(200)
- expect(json_response['file_path']).to eq(file_path)
- expect(json_response['file_name']).to eq('popen.rb')
- expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
- expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n")
- end
+ expect(response).to have_http_status(200)
+ expect(json_response['file_path']).to eq(file_path)
+ expect(json_response['file_name']).to eq('popen.rb')
+ expect(json_response['last_commit_id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
+ expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n")
+ end
- it "returns a 400 bad request if no params given" do
- get api("/projects/#{project.id}/repository/files", user)
+ context 'when no params are given' do
+ it_behaves_like '400 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
- expect(response).to have_http_status(400)
+ context 'when file_path does not exist' do
+ let(:params) do
+ {
+ file_path: 'app/models/application.rb',
+ ref: 'master',
+ }
+ end
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route, current_user), params }
+ let(:message) { '404 File Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user), params }
+ end
+ end
end
- it "returns a 404 if such file does not exist" do
- params = {
- file_path: 'app/models/application.rb',
- ref: 'master',
- }
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository files' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
+ end
+ end
- get api("/projects/#{project.id}/repository/files", user), params
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route), params }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository files' do
+ let(:current_user) { user }
+ end
+ end
- expect(response).to have_http_status(404)
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest), params }
+ end
end
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index c90b69e8ebb..0b19fa38c55 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -7,204 +7,415 @@ describe API::Repositories, api: true do
include WorkhorseHelpers
let(:user) { create(:user) }
- let(:user2) { create(:user) }
+ let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } }
let!(:project) { create(:project, creator_id: user.id) }
let!(:master) { create(:project_member, :master, user: user, project: project) }
- let!(:guest) { create(:project_member, :guest, user: user2, project: project) }
describe "GET /projects/:id/repository/tree" do
- context "authorized user" do
- before { project.team << [user2, :reporter] }
+ let(:route) { "/projects/#{project.id}/repository/tree" }
- it "returns project commits" do
- get api("/projects/#{project.id}/repository/tree", user)
+ shared_examples_for 'repository tree' do
+ it 'returns the repository tree' do
+ get api(route, current_user)
expect(response).to have_http_status(200)
+ first_commit = json_response.first
+
expect(json_response).to be_an Array
- expect(json_response.first['name']).to eq('bar')
- expect(json_response.first['type']).to eq('tree')
- expect(json_response.first['mode']).to eq('040000')
+ expect(first_commit['name']).to eq('bar')
+ expect(first_commit['type']).to eq('tree')
+ expect(first_commit['mode']).to eq('040000')
+ end
+
+ context 'when ref does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get api("#{route}?ref_name=foo", current_user) }
+ let(:message) { '404 Tree Not Found' }
+ end
end
- it 'returns a 404 for unknown ref' do
- get api("/projects/#{project.id}/repository/tree?ref_name=foo", user)
- expect(response).to have_http_status(404)
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
- expect(json_response).to be_an Object
- json_response['message'] == '404 Tree Not Found'
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
+
+ context 'with recursive=1' do
+ it 'returns recursive project paths tree' do
+ get api("#{route}?recursive=1", current_user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+ expect(json_response[4]['name']).to eq('html')
+ expect(json_response[4]['path']).to eq('files/html')
+ expect(json_response[4]['type']).to eq('tree')
+ expect(json_response[4]['mode']).to eq('040000')
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
+
+ context 'when ref does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get api("#{route}?recursive=1&ref_name=foo", current_user) }
+ let(:message) { '404 Tree Not Found' }
+ end
+ end
end
end
- context "unauthorized user" do
- it "does not return project commits" do
- get api("/projects/#{project.id}/repository/tree")
- expect(response).to have_http_status(401)
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository tree' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
end
end
- end
- describe 'GET /projects/:id/repository/tree?recursive=1' do
- context 'authorized user' do
- before { project.team << [user2, :reporter] }
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository tree' do
+ let(:current_user) { user }
+ end
+ end
- it 'should return recursive project paths tree' do
- get api("/projects/#{project.id}/repository/tree?recursive=1", user)
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest) }
+ end
+ end
+ end
- expect(response.status).to eq(200)
+ {
+ 'blobs/:sha' => 'blobs/master',
+ 'commits/:sha/blob' => 'commits/master/blob'
+ }.each do |desc_path, example_path|
+ describe "GET /projects/:id/repository/#{desc_path}" do
+ let(:route) { "/projects/#{project.id}/repository/#{example_path}?filepath=README.md" }
+
+ shared_examples_for 'repository blob' do
+ it 'returns the repository blob' do
+ get api(route, current_user)
+
+ expect(response).to have_http_status(200)
+ end
+
+ context 'when sha does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route.sub('master', 'invalid_branch_name'), current_user) }
+ let(:message) { '404 Commit Not Found' }
+ end
+ end
+
+ context 'when filepath does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route.sub('README.md', 'README.invalid'), current_user) }
+ let(:message) { '404 File Not Found' }
+ end
+ end
+
+ context 'when no filepath is given' do
+ it_behaves_like '400 response' do
+ let(:request) { get api(route.sub('?filepath=README.md', ''), current_user) }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
+ end
- expect(json_response).to be_an Array
- expect(json_response[4]['name']).to eq('html')
- expect(json_response[4]['path']).to eq('files/html')
- expect(json_response[4]['type']).to eq('tree')
- expect(json_response[4]['mode']).to eq('040000')
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository blob' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
+ end
end
- it 'returns a 404 for unknown ref' do
- get api("/projects/#{project.id}/repository/tree?ref_name=foo&recursive=1", user)
- expect(response).to have_http_status(404)
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
- expect(json_response).to be_an Object
- json_response['message'] == '404 Tree Not Found'
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository blob' do
+ let(:current_user) { user }
+ end
end
- end
- context "unauthorized user" do
- it "does not return project commits" do
- get api("/projects/#{project.id}/repository/tree?recursive=1")
- expect(response).to have_http_status(401)
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest) }
+ end
end
end
end
- describe "GET /projects/:id/repository/blobs/:sha" do
- it "gets the raw file contents" do
- get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user)
- expect(response).to have_http_status(200)
+ describe "GET /projects/:id/repository/raw_blobs/:sha" do
+ let(:route) { "/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}" }
+
+ shared_examples_for 'repository raw blob' do
+ it 'returns the repository raw blob' do
+ get api(route, current_user)
+
+ expect(response).to have_http_status(200)
+ end
+
+ context 'when sha does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route.sub(sample_blob.oid, '123456'), current_user) }
+ let(:message) { '404 Blob Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, current_user) }
+ end
+ end
end
- it "returns 404 for invalid branch_name" do
- get api("/projects/#{project.id}/repository/blobs/invalid_branch_name?filepath=README.md", user)
- expect(response).to have_http_status(404)
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository raw blob' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
+ end
end
- it "returns 404 for invalid file" do
- get api("/projects/#{project.id}/repository/blobs/master?filepath=README.invalid", user)
- expect(response).to have_http_status(404)
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
end
- it "returns a 400 error if filepath is missing" do
- get api("/projects/#{project.id}/repository/blobs/master", user)
- expect(response).to have_http_status(400)
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository raw blob' do
+ let(:current_user) { user }
+ end
end
- end
- describe "GET /projects/:id/repository/commits/:sha/blob" do
- it "gets the raw file contents" do
- get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user)
- expect(response).to have_http_status(200)
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest) }
+ end
end
end
- describe "GET /projects/:id/repository/raw_blobs/:sha" do
- it "gets the raw file contents" do
- get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user)
- expect(response).to have_http_status(200)
- end
+ describe "GET /projects/:id/repository/archive(.:format)?:sha" do
+ let(:route) { "/projects/#{project.id}/repository/archive" }
+
+ shared_examples_for 'repository archive' do
+ it 'returns the repository archive' do
+ get api(route, current_user)
+
+ expect(response).to have_http_status(200)
- it 'returns a 404 for unknown blob' do
- get api("/projects/#{project.id}/repository/raw_blobs/123456", user)
- expect(response).to have_http_status(404)
+ repo_name = project.repository.name.gsub("\.git", "")
+ type, params = workhorse_send_data
- expect(json_response).to be_an Object
- json_response['message'] == '404 Blob Not Found'
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
+ end
+
+ it 'returns the repository archive archive.zip' do
+ get api("/projects/#{project.id}/repository/archive.zip", user)
+
+ expect(response).to have_http_status(200)
+
+ repo_name = project.repository.name.gsub("\.git", "")
+ type, params = workhorse_send_data
+
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
+ end
+
+ it 'returns the repository archive archive.tar.bz2' do
+ get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
+
+ expect(response).to have_http_status(200)
+
+ repo_name = project.repository.name.gsub("\.git", "")
+ type, params = workhorse_send_data
+
+ expect(type).to eq('git-archive')
+ expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
+ end
+
+ context 'when sha does not exist' do
+ it_behaves_like '404 response' do
+ let(:request) { get api("#{route}?sha=xxx", current_user) }
+ let(:message) { '404 File Not Found' }
+ end
+ end
end
- end
- describe "GET /projects/:id/repository/archive(.:format)?:sha" do
- it "gets the archive" do
- get api("/projects/#{project.id}/repository/archive", user)
- repo_name = project.repository.name.gsub("\.git", "")
- expect(response).to have_http_status(200)
- type, params = workhorse_send_data
- expect(type).to eq('git-archive')
- expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.gz/)
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository archive' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
+ end
end
- it "gets the archive.zip" do
- get api("/projects/#{project.id}/repository/archive.zip", user)
- repo_name = project.repository.name.gsub("\.git", "")
- expect(response).to have_http_status(200)
- type, params = workhorse_send_data
- expect(type).to eq('git-archive')
- expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.zip/)
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
end
- it "gets the archive.tar.bz2" do
- get api("/projects/#{project.id}/repository/archive.tar.bz2", user)
- repo_name = project.repository.name.gsub("\.git", "")
- expect(response).to have_http_status(200)
- type, params = workhorse_send_data
- expect(type).to eq('git-archive')
- expect(params['ArchivePath']).to match(/#{repo_name}\-[^\.]+\.tar.bz2/)
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository archive' do
+ let(:current_user) { user }
+ end
end
- it "returns 404 for invalid sha" do
- get api("/projects/#{project.id}/repository/archive/?sha=xxx", user)
- expect(response).to have_http_status(404)
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest) }
+ end
end
end
describe 'GET /projects/:id/repository/compare' do
- it "compares branches" do
- get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'feature'
- expect(response).to have_http_status(200)
- expect(json_response['commits']).to be_present
- expect(json_response['diffs']).to be_present
+ let(:route) { "/projects/#{project.id}/repository/compare" }
+
+ shared_examples_for 'repository compare' do
+ it "compares branches" do
+ get api(route, current_user), from: 'master', to: 'feature'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_present
+ expect(json_response['diffs']).to be_present
+ end
+
+ it "compares tags" do
+ get api(route, current_user), from: 'v1.0.0', to: 'v1.1.0'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_present
+ expect(json_response['diffs']).to be_present
+ end
+
+ it "compares commits" do
+ get api(route, current_user), from: sample_commit.id, to: sample_commit.parent_id
+
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_empty
+ expect(json_response['diffs']).to be_empty
+ expect(json_response['compare_same_ref']).to be_falsey
+ end
+
+ it "compares commits in reverse order" do
+ get api(route, current_user), from: sample_commit.parent_id, to: sample_commit.id
+
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_present
+ expect(json_response['diffs']).to be_present
+ end
+
+ it "compares same refs" do
+ get api(route, current_user), from: 'master', to: 'master'
+
+ expect(response).to have_http_status(200)
+ expect(json_response['commits']).to be_empty
+ expect(json_response['diffs']).to be_empty
+ expect(json_response['compare_same_ref']).to be_truthy
+ end
end
- it "compares tags" do
- get api("/projects/#{project.id}/repository/compare", user), from: 'v1.0.0', to: 'v1.1.0'
- expect(response).to have_http_status(200)
- expect(json_response['commits']).to be_present
- expect(json_response['diffs']).to be_present
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository compare' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
+ end
end
- it "compares commits" do
- get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.id, to: sample_commit.parent_id
- expect(response).to have_http_status(200)
- expect(json_response['commits']).to be_empty
- expect(json_response['diffs']).to be_empty
- expect(json_response['compare_same_ref']).to be_falsey
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
end
- it "compares commits in reverse order" do
- get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.parent_id, to: sample_commit.id
- expect(response).to have_http_status(200)
- expect(json_response['commits']).to be_present
- expect(json_response['diffs']).to be_present
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository compare' do
+ let(:current_user) { user }
+ end
end
- it "compares same refs" do
- get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'master'
- expect(response).to have_http_status(200)
- expect(json_response['commits']).to be_empty
- expect(json_response['diffs']).to be_empty
- expect(json_response['compare_same_ref']).to be_truthy
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest) }
+ end
end
end
describe 'GET /projects/:id/repository/contributors' do
- it 'returns valid data' do
- get api("/projects/#{project.id}/repository/contributors", user)
- expect(response).to have_http_status(200)
- expect(json_response).to be_an Array
- contributor = json_response.first
- expect(contributor['email']).to eq('tiagonbotelho@hotmail.com')
- expect(contributor['name']).to eq('tiagonbotelho')
- expect(contributor['commits']).to eq(1)
- expect(contributor['additions']).to eq(0)
- expect(contributor['deletions']).to eq(0)
+ let(:route) { "/projects/#{project.id}/repository/contributors" }
+
+ shared_examples_for 'repository contributors' do
+ it 'returns valid data' do
+ get api(route, current_user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+
+ first_contributor = json_response.first
+
+ expect(first_contributor['email']).to eq('tiagonbotelho@hotmail.com')
+ expect(first_contributor['name']).to eq('tiagonbotelho')
+ expect(first_contributor['commits']).to eq(1)
+ expect(first_contributor['additions']).to eq(0)
+ expect(first_contributor['deletions']).to eq(0)
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository contributors' do
+ let(:project) { create(:project, :public) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route) }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository contributors' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route, guest) }
+ end
end
end
end
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 80652129928..3b5dc98e4d5 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -37,6 +37,11 @@ describe Ci::API::Builds do
let(:user_agent) { 'Go-http-client/1.1' }
it { expect(response).to have_http_status(404) }
end
+
+ context "when runner doesn't have a User-Agent" do
+ let(:user_agent) { nil }
+ it { expect(response).to have_http_status(404) }
+ end
end
context 'when there is a pending build' do
@@ -249,7 +254,11 @@ describe Ci::API::Builds do
end
describe 'PATCH /builds/:id/trace.txt' do
- let(:build) { create(:ci_build, :pending, :trace, runner_id: runner.id) }
+ let(:build) do
+ attributes = { runner_id: runner.id, pipeline: pipeline }
+ create(:ci_build, :running, :trace, attributes)
+ end
+
let(:headers) { { Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token, 'Content-Type' => 'text/plain' } }
let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) }
let(:update_interval) { 10.seconds.to_i }
@@ -276,7 +285,6 @@ describe Ci::API::Builds do
end
before do
- build.run!
initial_patch_the_trace
end
@@ -329,6 +337,19 @@ describe Ci::API::Builds do
end
end
end
+
+ context 'when project for the build has been deleted' do
+ let(:build) do
+ attributes = { runner_id: runner.id, pipeline: pipeline }
+ create(:ci_build, :running, :trace, attributes) do |build|
+ build.project.update(pending_delete: true)
+ end
+ end
+
+ it 'responds with forbidden' do
+ expect(response.status).to eq(403)
+ end
+ end
end
context 'when Runner makes a force-patch' do
diff --git a/spec/routing/import_routing_spec.rb b/spec/routing/import_routing_spec.rb
new file mode 100644
index 00000000000..78ff9c6e6fd
--- /dev/null
+++ b/spec/routing/import_routing_spec.rb
@@ -0,0 +1,165 @@
+require 'spec_helper'
+
+# Shared examples for a resource inside a Project
+#
+# By default it tests all the default REST actions: index, create, new, edit,
+# show, update, and destroy. You can remove actions by customizing the
+# `actions` variable.
+#
+# It also expects a `controller` variable to be available which defines both
+# the path to the resource as well as the controller name.
+#
+# Examples
+#
+# # Default behavior
+# it_behaves_like 'RESTful project resources' do
+# let(:controller) { 'issues' }
+# end
+#
+# # Customizing actions
+# it_behaves_like 'RESTful project resources' do
+# let(:actions) { [:index] }
+# let(:controller) { 'issues' }
+# end
+shared_examples 'importer routing' do
+ let(:except_actions) { [] }
+
+ it 'to #create' do
+ expect(post("/import/#{provider}")).to route_to("import/#{provider}#create") unless except_actions.include?(:create)
+ end
+
+ it 'to #new' do
+ expect(get("/import/#{provider}/new")).to route_to("import/#{provider}#new") unless except_actions.include?(:new)
+ end
+
+ it 'to #status' do
+ expect(get("/import/#{provider}/status")).to route_to("import/#{provider}#status") unless except_actions.include?(:status)
+ end
+
+ it 'to #callback' do
+ expect(get("/import/#{provider}/callback")).to route_to("import/#{provider}#callback") unless except_actions.include?(:callback)
+ end
+
+ it 'to #jobs' do
+ expect(get("/import/#{provider}/jobs")).to route_to("import/#{provider}#jobs") unless except_actions.include?(:jobs)
+ end
+end
+
+# personal_access_token_import_github POST /import/github/personal_access_token(.:format) import/github#personal_access_token
+# status_import_github GET /import/github/status(.:format) import/github#status
+# callback_import_github GET /import/github/callback(.:format) import/github#callback
+# jobs_import_github GET /import/github/jobs(.:format) import/github#jobs
+# import_github POST /import/github(.:format) import/github#create
+# new_import_github GET /import/github/new(.:format) import/github#new
+describe Import::GithubController, 'routing' do
+ it_behaves_like 'importer routing' do
+ let(:provider) { 'github' }
+ end
+
+ it 'to #personal_access_token' do
+ expect(post('/import/github/personal_access_token')).to route_to('import/github#personal_access_token')
+ end
+end
+
+# personal_access_token_import_gitea POST /import/gitea/personal_access_token(.:format) import/gitea#personal_access_token
+# status_import_gitea GET /import/gitea/status(.:format) import/gitea#status
+# jobs_import_gitea GET /import/gitea/jobs(.:format) import/gitea#jobs
+# import_gitea POST /import/gitea(.:format) import/gitea#create
+# new_import_gitea GET /import/gitea/new(.:format) import/gitea#new
+describe Import::GiteaController, 'routing' do
+ it_behaves_like 'importer routing' do
+ let(:except_actions) { [:callback] }
+ let(:provider) { 'gitea' }
+ end
+
+ it 'to #personal_access_token' do
+ expect(post('/import/gitea/personal_access_token')).to route_to('import/gitea#personal_access_token')
+ end
+end
+
+# status_import_gitlab GET /import/gitlab/status(.:format) import/gitlab#status
+# callback_import_gitlab GET /import/gitlab/callback(.:format) import/gitlab#callback
+# jobs_import_gitlab GET /import/gitlab/jobs(.:format) import/gitlab#jobs
+# import_gitlab POST /import/gitlab(.:format) import/gitlab#create
+describe Import::GitlabController, 'routing' do
+ it_behaves_like 'importer routing' do
+ let(:except_actions) { [:new] }
+ let(:provider) { 'gitlab' }
+ end
+end
+
+# status_import_bitbucket GET /import/bitbucket/status(.:format) import/bitbucket#status
+# callback_import_bitbucket GET /import/bitbucket/callback(.:format) import/bitbucket#callback
+# jobs_import_bitbucket GET /import/bitbucket/jobs(.:format) import/bitbucket#jobs
+# import_bitbucket POST /import/bitbucket(.:format) import/bitbucket#create
+describe Import::BitbucketController, 'routing' do
+ it_behaves_like 'importer routing' do
+ let(:except_actions) { [:new] }
+ let(:provider) { 'bitbucket' }
+ end
+end
+
+# status_import_google_code GET /import/google_code/status(.:format) import/google_code#status
+# callback_import_google_code POST /import/google_code/callback(.:format) import/google_code#callback
+# jobs_import_google_code GET /import/google_code/jobs(.:format) import/google_code#jobs
+# new_user_map_import_google_code GET /import/google_code/user_map(.:format) import/google_code#new_user_map
+# create_user_map_import_google_code POST /import/google_code/user_map(.:format) import/google_code#create_user_map
+# import_google_code POST /import/google_code(.:format) import/google_code#create
+# new_import_google_code GET /import/google_code/new(.:format) import/google_code#new
+describe Import::GoogleCodeController, 'routing' do
+ it_behaves_like 'importer routing' do
+ let(:except_actions) { [:callback] }
+ let(:provider) { 'google_code' }
+ end
+
+ it 'to #callback' do
+ expect(post("/import/google_code/callback")).to route_to("import/google_code#callback")
+ end
+
+ it 'to #new_user_map' do
+ expect(get('/import/google_code/user_map')).to route_to('import/google_code#new_user_map')
+ end
+
+ it 'to #create_user_map' do
+ expect(post('/import/google_code/user_map')).to route_to('import/google_code#create_user_map')
+ end
+end
+
+# status_import_fogbugz GET /import/fogbugz/status(.:format) import/fogbugz#status
+# callback_import_fogbugz POST /import/fogbugz/callback(.:format) import/fogbugz#callback
+# jobs_import_fogbugz GET /import/fogbugz/jobs(.:format) import/fogbugz#jobs
+# new_user_map_import_fogbugz GET /import/fogbugz/user_map(.:format) import/fogbugz#new_user_map
+# create_user_map_import_fogbugz POST /import/fogbugz/user_map(.:format) import/fogbugz#create_user_map
+# import_fogbugz POST /import/fogbugz(.:format) import/fogbugz#create
+# new_import_fogbugz GET /import/fogbugz/new(.:format) import/fogbugz#new
+describe Import::FogbugzController, 'routing' do
+ it_behaves_like 'importer routing' do
+ let(:except_actions) { [:callback] }
+ let(:provider) { 'fogbugz' }
+ end
+
+ it 'to #callback' do
+ expect(post("/import/fogbugz/callback")).to route_to("import/fogbugz#callback")
+ end
+
+ it 'to #new_user_map' do
+ expect(get('/import/fogbugz/user_map')).to route_to('import/fogbugz#new_user_map')
+ end
+
+ it 'to #create_user_map' do
+ expect(post('/import/fogbugz/user_map')).to route_to('import/fogbugz#create_user_map')
+ end
+end
+
+# import_gitlab_project POST /import/gitlab_project(.:format) import/gitlab_projects#create
+# POST /import/gitlab_project(.:format) import/gitlab_projects#create
+# new_import_gitlab_project GET /import/gitlab_project/new(.:format) import/gitlab_projects#new
+describe Import::GitlabProjectsController, 'routing' do
+ it 'to #create' do
+ expect(post('/import/gitlab_project')).to route_to('import/gitlab_projects#create')
+ end
+
+ it 'to #new' do
+ expect(get('/import/gitlab_project/new')).to route_to('import/gitlab_projects#new')
+ end
+end
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index 9c2331144a0..531180e48a1 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
describe Groups::UpdateService, services: true do
- let!(:user) { create(:user) }
- let!(:private_group) { create(:group, :private) }
- let!(:internal_group) { create(:group, :internal) }
- let!(:public_group) { create(:group, :public) }
+ let!(:user) { create(:user) }
+ let!(:private_group) { create(:group, :private) }
+ let!(:internal_group) { create(:group, :internal) }
+ let!(:public_group) { create(:group, :public) }
describe "#execute" do
context "project visibility_level validation" do
context "public group with public projects" do
- let!(:service) { described_class.new(public_group, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL ) }
+ let!(:service) { described_class.new(public_group, user, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
before do
public_group.add_user(user, Gitlab::Access::MASTER)
@@ -23,7 +23,7 @@ describe Groups::UpdateService, services: true do
end
context "internal group with internal project" do
- let!(:service) { described_class.new(internal_group, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE ) }
+ let!(:service) { described_class.new(internal_group, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
before do
internal_group.add_user(user, Gitlab::Access::MASTER)
@@ -39,7 +39,7 @@ describe Groups::UpdateService, services: true do
end
context "unauthorized visibility_level validation" do
- let!(:service) { described_class.new(internal_group, user, visibility_level: 99 ) }
+ let!(:service) { described_class.new(internal_group, user, visibility_level: 99) }
before do
internal_group.add_user(user, Gitlab::Access::MASTER)
end
@@ -49,4 +49,41 @@ describe Groups::UpdateService, services: true do
expect(internal_group.errors.count).to eq(1)
end
end
+
+ context 'rename group' do
+ let!(:service) { described_class.new(internal_group, user, path: 'new_path') }
+
+ before do
+ internal_group.add_user(user, Gitlab::Access::MASTER)
+ create(:project, :internal, group: internal_group)
+ end
+
+ it 'returns true' do
+ expect(service.execute).to eq(true)
+ end
+
+ context 'error moving group' do
+ before do
+ allow(internal_group).to receive(:move_dir).and_raise(Gitlab::UpdatePathError)
+ end
+
+ it 'does not raise an error' do
+ expect { service.execute }.not_to raise_error
+ end
+
+ it 'returns false' do
+ expect(service.execute).to eq(false)
+ end
+
+ it 'has the right error' do
+ service.execute
+
+ expect(internal_group.errors.full_messages.first).to eq('Gitlab::UpdatePathError')
+ end
+
+ it "hasn't changed the path" do
+ expect { service.execute}.not_to change { internal_group.reload.path}
+ end
+ end
+ end
end
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
new file mode 100644
index 00000000000..72c8f7cd8ec
--- /dev/null
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -0,0 +1,185 @@
+require 'spec_helper'
+
+describe Users::RefreshAuthorizedProjectsService do
+ let(:project) { create(:empty_project) }
+ let(:user) { project.namespace.owner }
+ let(:service) { described_class.new(user) }
+
+ def create_authorization(project, user, access_level = Gitlab::Access::MASTER)
+ ProjectAuthorization.
+ create!(project: project, user: user, access_level: access_level)
+ end
+
+ describe '#execute' do
+ before do
+ user.project_authorizations.delete_all
+ end
+
+ it 'updates the authorized projects of the user' do
+ project2 = create(:empty_project)
+ to_remove = create_authorization(project2, user)
+
+ expect(service).to receive(:update_with_lease).
+ with([to_remove.id], [[user.id, project.id, Gitlab::Access::MASTER]])
+
+ service.execute
+ end
+
+ it 'sets the access level of a project to the highest available level' do
+ to_remove = create_authorization(project, user, Gitlab::Access::DEVELOPER)
+
+ expect(service).to receive(:update_with_lease).
+ with([to_remove.id], [[user.id, project.id, Gitlab::Access::MASTER]])
+
+ service.execute
+ end
+
+ it 'returns a User' do
+ expect(service.execute).to be_an_instance_of(User)
+ end
+ end
+
+ describe '#update_with_lease', :redis do
+ it 'refreshes the authorizations using a lease' do
+ expect_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).
+ and_return('foo')
+
+ expect(Gitlab::ExclusiveLease).to receive(:cancel).
+ with(an_instance_of(String), 'foo')
+
+ expect(service).to receive(:update_authorizations).with([1], [])
+
+ service.update_with_lease([1])
+ end
+ end
+
+ describe '#update_authorizations' do
+ it 'does nothing when there are no rows to add and remove' do
+ expect(user).not_to receive(:remove_project_authorizations)
+ expect(ProjectAuthorization).not_to receive(:insert_authorizations)
+ expect(user).not_to receive(:set_authorized_projects_column)
+
+ service.update_authorizations([], [])
+ end
+
+ it 'removes authorizations that should be removed' do
+ authorization = create_authorization(project, user)
+
+ service.update_authorizations([authorization.id])
+
+ expect(user.project_authorizations).to be_empty
+ end
+
+ it 'inserts authorizations that should be added' do
+ service.update_authorizations([], [[user.id, project.id, Gitlab::Access::MASTER]])
+
+ authorizations = user.project_authorizations
+
+ expect(authorizations.length).to eq(1)
+ expect(authorizations[0].user_id).to eq(user.id)
+ expect(authorizations[0].project_id).to eq(project.id)
+ expect(authorizations[0].access_level).to eq(Gitlab::Access::MASTER)
+ end
+
+ it 'populates the authorized projects column' do
+ # make sure we start with a nil value no matter what the default in the
+ # factory may be.
+ user.update(authorized_projects_populated: nil)
+
+ service.update_authorizations([], [[user.id, project.id, Gitlab::Access::MASTER]])
+
+ expect(user.authorized_projects_populated).to eq(true)
+ end
+ end
+
+ describe '#fresh_access_levels_per_project' do
+ let(:hash) { service.fresh_access_levels_per_project }
+
+ it 'returns a Hash' do
+ expect(hash).to be_an_instance_of(Hash)
+ end
+
+ it 'sets the keys to the project IDs' do
+ expect(hash.keys).to eq([project.id])
+ end
+
+ it 'sets the values to the access levels' do
+ expect(hash.values).to eq([Gitlab::Access::MASTER])
+ end
+ end
+
+ describe '#current_authorizations_per_project' do
+ before { create_authorization(project, user) }
+
+ let(:hash) { service.current_authorizations_per_project }
+
+ it 'returns a Hash' do
+ expect(hash).to be_an_instance_of(Hash)
+ end
+
+ it 'sets the keys to the project IDs' do
+ expect(hash.keys).to eq([project.id])
+ end
+
+ it 'sets the values to the project authorization rows' do
+ expect(hash.values).to eq([ProjectAuthorization.first])
+ end
+ end
+
+ describe '#current_authorizations' do
+ context 'without authorizations' do
+ it 'returns an empty list' do
+ expect(service.current_authorizations.empty?).to eq(true)
+ end
+ end
+
+ context 'with an authorization' do
+ before { create_authorization(project, user) }
+
+ let(:row) { service.current_authorizations.take }
+
+ it 'returns the currently authorized projects' do
+ expect(service.current_authorizations.length).to eq(1)
+ end
+
+ it 'includes the row ID for every row' do
+ expect(row.id).to be_a_kind_of(Numeric)
+ end
+
+ it 'includes the project ID for every row' do
+ expect(row.project_id).to eq(project.id)
+ end
+
+ it 'includes the access level for every row' do
+ expect(row.access_level).to eq(Gitlab::Access::MASTER)
+ end
+ end
+ end
+
+ describe '#fresh_authorizations' do
+ it 'returns the new authorized projects' do
+ expect(service.fresh_authorizations.length).to eq(1)
+ end
+
+ it 'returns the highest access level' do
+ project.team.add_guest(user)
+
+ rows = service.fresh_authorizations.to_a
+
+ expect(rows.length).to eq(1)
+ expect(rows.first.access_level).to eq(Gitlab::Access::MASTER)
+ end
+
+ context 'every returned row' do
+ let(:row) { service.fresh_authorizations.take }
+
+ it 'includes the project ID' do
+ expect(row.project_id).to eq(project.id)
+ end
+
+ it 'includes the access level' do
+ expect(row.access_level).to eq(Gitlab::Access::MASTER)
+ end
+ end
+ end
+end
diff --git a/spec/support/api/repositories_shared_context.rb b/spec/support/api/repositories_shared_context.rb
new file mode 100644
index 00000000000..ea38fe4f5b8
--- /dev/null
+++ b/spec/support/api/repositories_shared_context.rb
@@ -0,0 +1,10 @@
+shared_context 'disabled repository' do
+ before do
+ project.project_feature.update_attributes!(
+ repository_access_level: ProjectFeature::DISABLED,
+ merge_requests_access_level: ProjectFeature::DISABLED,
+ builds_access_level: ProjectFeature::DISABLED
+ )
+ expect(project.feature_available?(:repository, current_user)).to be false
+ end
+end
diff --git a/spec/support/api/status_shared_examples.rb b/spec/support/api/status_shared_examples.rb
new file mode 100644
index 00000000000..3481749a7f0
--- /dev/null
+++ b/spec/support/api/status_shared_examples.rb
@@ -0,0 +1,42 @@
+# Specs for status checking.
+#
+# Requires an API request:
+# let(:request) { get api("/projects/#{project.id}/repository/branches", user) }
+shared_examples_for '400 response' do
+ before do
+ # Fires the request
+ request
+ end
+
+ it 'returns 400' do
+ expect(response).to have_http_status(400)
+ end
+end
+
+shared_examples_for '403 response' do
+ before do
+ # Fires the request
+ request
+ end
+
+ it 'returns 403' do
+ expect(response).to have_http_status(403)
+ end
+end
+
+shared_examples_for '404 response' do
+ let(:message) { nil }
+ before do
+ # Fires the request
+ request
+ end
+
+ it 'returns 404' do
+ expect(response).to have_http_status(404)
+ expect(json_response).to be_an Object
+
+ if message.present?
+ expect(json_response['message']).to eq(message)
+ end
+ end
+end
diff --git a/spec/support/chat_slash_commands_shared_examples.rb b/spec/support/chat_slash_commands_shared_examples.rb
new file mode 100644
index 00000000000..4dfa29849ee
--- /dev/null
+++ b/spec/support/chat_slash_commands_shared_examples.rb
@@ -0,0 +1,97 @@
+RSpec.shared_examples 'chat slash commands service' do
+ describe "Associations" do
+ it { is_expected.to respond_to :token }
+ it { is_expected.to have_many :chat_names }
+ end
+
+ describe '#valid_token?' do
+ subject { described_class.new }
+
+ context 'when the token is empty' do
+ it 'is false' do
+ expect(subject.valid_token?('wer')).to be_falsey
+ end
+ end
+
+ context 'when there is a token' do
+ before do
+ subject.token = '123'
+ end
+
+ it 'accepts equal tokens' do
+ expect(subject.valid_token?('123')).to be_truthy
+ end
+ end
+ end
+
+ describe '#trigger' do
+ subject { described_class.new }
+
+ context 'no token is passed' do
+ let(:params) { Hash.new }
+
+ it 'returns nil' do
+ expect(subject.trigger(params)).to be_nil
+ end
+ end
+
+ context 'with a token passed' do
+ let(:project) { create(:empty_project) }
+ let(:params) { { token: 'token' } }
+
+ before do
+ allow(subject).to receive(:token).and_return('token')
+ end
+
+ context 'no user can be found' do
+ context 'when no url can be generated' do
+ it 'responds with the authorize url' do
+ response = subject.trigger(params)
+
+ expect(response[:response_type]).to eq :ephemeral
+ expect(response[:text]).to start_with ":sweat_smile: Couldn't identify you"
+ end
+ end
+
+ context 'when an auth url can be generated' do
+ let(:params) do
+ {
+ team_domain: 'http://domain.tld',
+ team_id: 'T3423423',
+ user_id: 'U234234',
+ user_name: 'mepmep',
+ token: 'token'
+ }
+ end
+
+ let(:service) do
+ project.create_mattermost_slash_commands_service(
+ properties: { token: 'token' }
+ )
+ end
+
+ it 'generates the url' do
+ response = service.trigger(params)
+
+ expect(response[:text]).to start_with(':wave: Hi there!')
+ end
+ end
+ end
+
+ context 'when the user is authenticated' do
+ let!(:chat_name) { create(:chat_name, service: subject) }
+ let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } }
+
+ subject do
+ described_class.create(project: project, properties: { token: 'token' })
+ end
+
+ it 'triggers the command' do
+ expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute)
+
+ subject.trigger(params)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/controllers/githubish_import_controller_shared_context.rb b/spec/support/controllers/githubish_import_controller_shared_context.rb
new file mode 100644
index 00000000000..e71994edec6
--- /dev/null
+++ b/spec/support/controllers/githubish_import_controller_shared_context.rb
@@ -0,0 +1,10 @@
+shared_context 'a GitHub-ish import controller' do
+ let(:user) { create(:user) }
+ let(:token) { "asdasd12345" }
+ let(:access_params) { { github_access_token: token } }
+
+ before do
+ sign_in(user)
+ allow(controller).to receive(:"#{provider}_import_enabled?").and_return(true)
+ end
+end
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
new file mode 100644
index 00000000000..d0fd2d52004
--- /dev/null
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -0,0 +1,232 @@
+# Specifications for behavior common to all objects with an email attribute.
+# Takes a list of email-format attributes and requires:
+# - subject { "the object with a attribute= setter" }
+# Note: You have access to `email_value` which is the email address value
+# being currently tested).
+
+def assign_session_token(provider)
+ session[:"#{provider}_access_token"] = 'asdasd12345'
+end
+
+shared_examples 'a GitHub-ish import controller: POST personal_access_token' do
+ let(:status_import_url) { public_send("status_import_#{provider}_url") }
+
+ it "updates access token" do
+ token = 'asdfasdf9876'
+
+ allow_any_instance_of(Gitlab::GithubImport::Client).
+ to receive(:user).and_return(true)
+
+ post :personal_access_token, personal_access_token: token
+
+ expect(session[:"#{provider}_access_token"]).to eq(token)
+ expect(controller).to redirect_to(status_import_url)
+ end
+end
+
+shared_examples 'a GitHub-ish import controller: GET new' do
+ let(:status_import_url) { public_send("status_import_#{provider}_url") }
+
+ it "redirects to status if we already have a token" do
+ assign_session_token(provider)
+ allow(controller).to receive(:logged_in_with_provider?).and_return(false)
+
+ get :new
+
+ expect(controller).to redirect_to(status_import_url)
+ end
+
+ it "renders the :new page if no token is present in session" do
+ get :new
+
+ expect(response).to render_template(:new)
+ end
+end
+
+shared_examples 'a GitHub-ish import controller: GET status' do
+ let(:new_import_url) { public_send("new_import_#{provider}_url") }
+ let(:user) { create(:user) }
+ let(:repo) { OpenStruct.new(login: 'vim', full_name: 'asd/vim') }
+ let(:org) { OpenStruct.new(login: 'company') }
+ let(:org_repo) { OpenStruct.new(login: 'company', full_name: 'company/repo') }
+ let(:extra_assign_expectations) { {} }
+
+ before do
+ assign_session_token(provider)
+ end
+
+ it "assigns variables" do
+ project = create(:empty_project, import_type: provider, creator_id: user.id)
+ stub_client(repos: [repo, org_repo], orgs: [org], org_repos: [org_repo])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([project])
+ expect(assigns(:repos)).to eq([repo, org_repo])
+ extra_assign_expectations.each do |key, value|
+ expect(assigns(key)).to eq(value)
+ end
+ end
+
+ it "does not show already added project" do
+ project = create(:empty_project, import_type: provider, creator_id: user.id, import_source: 'asd/vim')
+ stub_client(repos: [repo], orgs: [])
+
+ get :status
+
+ expect(assigns(:already_added_projects)).to eq([project])
+ expect(assigns(:repos)).to eq([])
+ end
+
+ it "handles an invalid access token" do
+ allow_any_instance_of(Gitlab::GithubImport::Client).
+ to receive(:repos).and_raise(Octokit::Unauthorized)
+
+ get :status
+
+ expect(session[:"#{provider}_access_token"]).to be_nil
+ expect(controller).to redirect_to(new_import_url)
+ expect(flash[:alert]).to eq("Access denied to your #{Gitlab::ImportSources.title(provider.to_s)} account.")
+ end
+end
+
+shared_examples 'a GitHub-ish import controller: POST create' do
+ let(:user) { create(:user) }
+ let(:provider_username) { user.username }
+ let(:provider_user) { OpenStruct.new(login: provider_username) }
+ let(:provider_repo) do
+ OpenStruct.new(
+ name: 'vim',
+ full_name: "#{provider_username}/vim",
+ owner: OpenStruct.new(login: provider_username)
+ )
+ end
+
+ before do
+ stub_client(user: provider_user, repo: provider_repo)
+ assign_session_token(provider)
+ end
+
+ context "when the repository owner is the provider user" do
+ context "when the provider user and GitLab user's usernames match" do
+ it "takes the current user's namespace" do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
+ and_return(double(execute: true))
+
+ post :create, format: :js
+ end
+ end
+
+ context "when the provider user and GitLab user's usernames don't match" do
+ let(:provider_username) { "someone_else" }
+
+ it "takes the current user's namespace" do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
+ and_return(double(execute: true))
+
+ post :create, format: :js
+ end
+ end
+ end
+
+ context "when the repository owner is not the provider user" do
+ let(:other_username) { "someone_else" }
+
+ before do
+ provider_repo.owner = OpenStruct.new(login: other_username)
+ assign_session_token(provider)
+ end
+
+ context "when a namespace with the provider user's username already exists" do
+ let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
+
+ context "when the namespace is owned by the GitLab user" do
+ it "takes the existing namespace" do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, access_params, type: provider).
+ and_return(double(execute: true))
+
+ post :create, format: :js
+ end
+ end
+
+ context "when the namespace is not owned by the GitLab user" do
+ before do
+ existing_namespace.owner = create(:user)
+ existing_namespace.save
+ end
+
+ it "creates a project using user's namespace" do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
+ and_return(double(execute: true))
+
+ post :create, format: :js
+ end
+ end
+ end
+
+ context "when a namespace with the provider user's username doesn't exist" do
+ context "when current user can create namespaces" do
+ it "creates the namespace" do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).and_return(double(execute: true))
+
+ expect { post :create, target_namespace: provider_repo.name, format: :js }.to change(Namespace, :count).by(1)
+ end
+
+ it "takes the new namespace" do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, access_params, type: provider).
+ and_return(double(execute: true))
+
+ post :create, target_namespace: provider_repo.name, format: :js
+ end
+ end
+
+ context "when current user can't create namespaces" do
+ before do
+ user.update_attribute(:can_create_group, false)
+ end
+
+ it "doesn't create the namespace" do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).and_return(double(execute: true))
+
+ expect { post :create, format: :js }.not_to change(Namespace, :count)
+ end
+
+ it "takes the current user's namespace" do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider).
+ and_return(double(execute: true))
+
+ post :create, format: :js
+ end
+ end
+ end
+
+ context 'user has chosen a namespace and name for the project' do
+ let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) }
+ let(:test_name) { 'test_name' }
+
+ it 'takes the selected namespace and name' do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider).
+ and_return(double(execute: true))
+
+ post :create, { target_namespace: test_namespace.name, new_name: test_name, format: :js }
+ end
+
+ it 'takes the selected name and default namespace' do
+ expect(Gitlab::GithubImport::ProjectCreator).
+ to receive(:new).with(provider_repo, test_name, user.namespace, user, access_params, type: provider).
+ and_return(double(execute: true))
+
+ post :create, { new_name: test_name, format: :js }
+ end
+ end
+ end
+end
diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/kubernetes_helpers.rb
new file mode 100644
index 00000000000..6c4c246a68b
--- /dev/null
+++ b/spec/support/kubernetes_helpers.rb
@@ -0,0 +1,52 @@
+module KubernetesHelpers
+ include Gitlab::Kubernetes
+
+ def kube_discovery_body
+ { "kind" => "APIResourceList",
+ "resources" => [
+ { "name" => "pods", "namespaced" => true, "kind" => "Pod" },
+ ],
+ }
+ end
+
+ def kube_pods_body(*pods)
+ { "kind" => "PodList",
+ "items" => [ kube_pod ],
+ }
+ end
+
+ # This is a partial response, it will have many more elements in reality but
+ # these are the ones we care about at the moment
+ def kube_pod(app: "valid-pod-label")
+ { "metadata" => {
+ "name" => "kube-pod",
+ "creationTimestamp" => "2016-11-25T19:55:19Z",
+ "labels" => { "app" => app },
+ },
+ "spec" => {
+ "containers" => [
+ { "name" => "container-0" },
+ { "name" => "container-1" },
+ ],
+ },
+ "status" => { "phase" => "Running" },
+ }
+ end
+
+ def kube_terminals(service, pod)
+ pod_name = pod['metadata']['name']
+ containers = pod['spec']['containers']
+
+ containers.map do |container|
+ terminal = {
+ selectors: { pod: pod_name, container: container['name'] },
+ url: container_exec_url(service.api_url, service.namespace, pod_name, container['name']),
+ subprotocols: ['channel.k8s.io'],
+ headers: { 'Authorization' => ["Bearer #{service.token}"] },
+ created_at: DateTime.parse(pod['metadata']['creationTimestamp'])
+ }
+ terminal[:ca_pem] = service.ca_pem if service.ca_pem.present?
+ terminal
+ end
+ end
+end
diff --git a/spec/support/query_recorder.rb b/spec/support/query_recorder.rb
new file mode 100644
index 00000000000..e40d5ebd9a8
--- /dev/null
+++ b/spec/support/query_recorder.rb
@@ -0,0 +1,40 @@
+module ActiveRecord
+ class QueryRecorder
+ attr_reader :log
+
+ def initialize(&block)
+ @log = []
+ ActiveSupport::Notifications.subscribed(method(:callback), 'sql.active_record', &block)
+ end
+
+ def callback(name, start, finish, message_id, values)
+ return if %w(CACHE SCHEMA).include?(values[:name])
+ @log << values[:sql]
+ end
+
+ def count
+ @log.count
+ end
+
+ def log_message
+ @log.join("\n\n")
+ end
+ end
+end
+
+RSpec::Matchers.define :exceed_query_limit do |expected|
+ supports_block_expectations
+
+ match do |block|
+ query_count(&block) > expected
+ end
+
+ failure_message_when_negated do |actual|
+ "Expected a maximum of #{expected} queries, got #{@recorder.count}:\n\n#{@recorder.log_message}"
+ end
+
+ def query_count(&block)
+ @recorder = ActiveRecord::QueryRecorder.new(&block)
+ @recorder.count
+ end
+end
diff --git a/spec/support/reactive_caching_helpers.rb b/spec/support/reactive_caching_helpers.rb
new file mode 100644
index 00000000000..279db3c5748
--- /dev/null
+++ b/spec/support/reactive_caching_helpers.rb
@@ -0,0 +1,38 @@
+module ReactiveCachingHelpers
+ def reactive_cache_key(subject, *qualifiers)
+ ([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':')
+ end
+
+ def stub_reactive_cache(subject = nil, data = nil)
+ allow(ReactiveCachingWorker).to receive(:perform_async)
+ allow(ReactiveCachingWorker).to receive(:perform_in)
+ write_reactive_cache(subject, data) if data
+ end
+
+ def read_reactive_cache(subject)
+ Rails.cache.read(reactive_cache_key(subject))
+ end
+
+ def write_reactive_cache(subject, data)
+ start_reactive_cache_lifetime(subject)
+ Rails.cache.write(reactive_cache_key(subject), data)
+ end
+
+ def reactive_cache_alive?(subject)
+ Rails.cache.read(reactive_cache_key(subject, 'alive'))
+ end
+
+ def invalidate_reactive_cache(subject)
+ Rails.cache.delete(reactive_cache_key(subject, 'alive'))
+ end
+
+ def start_reactive_cache_lifetime(subject)
+ Rails.cache.write(reactive_cache_key(subject, 'alive'), true)
+ end
+
+ def expect_reactive_cache_update_queued(subject)
+ expect(ReactiveCachingWorker).
+ to receive(:perform_in).
+ with(subject.class.reactive_cache_refresh_interval, subject.class, subject.id)
+ end
+end
diff --git a/spec/support/slack_mattermost_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb
index 56d4965f74d..8582aea5fe5 100644
--- a/spec/support/slack_mattermost_shared_examples.rb
+++ b/spec/support/slack_mattermost_notifications_shared_examples.rb
@@ -1,6 +1,6 @@
Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f }
-RSpec.shared_examples 'slack or mattermost' do
+RSpec.shared_examples 'slack or mattermost notifications' do
let(:chat_service) { described_class.new }
let(:webhook_url) { 'https://example.gitlab.com/' }
diff --git a/spec/views/projects/pipelines/_stage.html.haml_spec.rb b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
new file mode 100644
index 00000000000..eb7f7ca4a1a
--- /dev/null
+++ b/spec/views/projects/pipelines/_stage.html.haml_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe 'projects/pipelines/_stage', :view do
+ let(:project) { create(:project) }
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:stage) { build(:ci_stage, pipeline: pipeline) }
+
+ before do
+ assign :stage, stage
+
+ create(:ci_build, name: 'test:build',
+ stage: stage.name,
+ pipeline: pipeline)
+ end
+
+ it 'shows the builds in the stage' do
+ render
+
+ expect(rendered).to have_text 'test:build'
+ end
+end
diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb
index 95e2458da35..b6591f272f6 100644
--- a/spec/workers/authorized_projects_worker_spec.rb
+++ b/spec/workers/authorized_projects_worker_spec.rb
@@ -7,27 +7,17 @@ describe AuthorizedProjectsWorker do
it "refreshes user's authorized projects" do
user = create(:user)
- expect(worker).to receive(:refresh).with(an_instance_of(User))
+ expect_any_instance_of(User).to receive(:refresh_authorized_projects)
worker.perform(user.id)
end
context "when the user is not found" do
it "does nothing" do
- expect(worker).not_to receive(:refresh)
+ expect_any_instance_of(User).not_to receive(:refresh_authorized_projects)
described_class.new.perform(-1)
end
end
end
-
- describe '#refresh', redis: true do
- it 'refreshes the authorized projects of the user' do
- user = create(:user)
-
- expect(user).to receive(:refresh_authorized_projects)
-
- worker.refresh(user)
- end
- end
end
diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb
new file mode 100644
index 00000000000..5f4453c15d6
--- /dev/null
+++ b/spec/workers/reactive_caching_worker_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe ReactiveCachingWorker do
+ let(:project) { create(:kubernetes_project) }
+ let(:service) { project.deployment_service }
+ subject { described_class.new.perform("KubernetesService", service.id) }
+
+ describe '#perform' do
+ it 'calls #exclusively_update_reactive_cache!' do
+ expect_any_instance_of(KubernetesService).to receive(:exclusively_update_reactive_cache!)
+
+ subject
+ end
+ end
+end