Commit 2faf1563 authored by Stan Hu's avatar Stan Hu

Merge branch 'master' into ce-to-ee-2018-08-28

parents d50dd3e8 da338f4e
...@@ -160,7 +160,8 @@ stages: ...@@ -160,7 +160,8 @@ stages:
.single-script-job: &single-script-job .single-script-job: &single-script-job
image: ruby:2.4-alpine image: ruby:2.4-alpine
stage: build before_script: []
stage: test
cache: {} cache: {}
dependencies: [] dependencies: []
variables: &single-script-job-variables variables: &single-script-job-variables
......
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 11.2.3 (2018-08-28)
- No changes.
## 11.2.2 (2018-08-27)
### Security (1 change)
- Prevent regular users from moving projects to different storage shards.
## 11.2.1 (2018-08-22) ## 11.2.1 (2018-08-22)
- No changes. - No changes.
...@@ -96,6 +107,18 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -96,6 +107,18 @@ Please view this file on the master branch, on stable branches it's out of date.
- Geo: Log to geo.log when the Log Cursor skips an event. - Geo: Log to geo.log when the Log Cursor skips an event.
## 11.1.6 (2018-08-28)
- No changes.
## 11.1.5 (2018-08-27)
- No changes.
### Security (1 change)
- Prevent regular users from moving projects to different storage shards.
## 11.1.4 (2018-07-30) ## 11.1.4 (2018-07-30)
- No changes. - No changes.
...@@ -189,6 +212,13 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -189,6 +212,13 @@ Please view this file on the master branch, on stable branches it's out of date.
- Geo - Make Geo repository verification flag opt-out by default. !6369 - Geo - Make Geo repository verification flag opt-out by default. !6369
## 11.0.6 (2018-08-27)
### Security (1 change)
- Prevent regular users from moving projects to different storage shards.
## 11.0.5 (2018-07-26) ## 11.0.5 (2018-07-26)
### Security (1 change) ### Security (1 change)
......
...@@ -2,6 +2,19 @@ ...@@ -2,6 +2,19 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 11.2.3 (2018-08-28)
- No changes.
## 11.2.2 (2018-08-27)
### Security (3 changes)
- Fixed persistent XSS rendering/escaping of diff location lines.
- Adding CSRF protection to Hooks resend action.
- Block link-local addresses in URLBlocker.
## 11.2.1 (2018-08-22) ## 11.2.1 (2018-08-22)
### Fixed (2 changes) ### Fixed (2 changes)
...@@ -256,6 +269,24 @@ entry. ...@@ -256,6 +269,24 @@ entry.
- Moves help_popover component to a common location. - Moves help_popover component to a common location.
## 11.1.6 (2018-08-28)
- No changes.
## 11.1.5 (2018-08-27)
- No changes.
### Security (3 changes)
- Fixed persistent XSS rendering/escaping of diff location lines.
- Adding CSRF protection to Hooks resend action.
- Block link-local addresses in URLBlocker.
### Fixed (1 change, 1 of them is from the community)
- Sanitize git URL in import errors. (Jamie Schembri)
## 11.1.4 (2018-07-30) ## 11.1.4 (2018-07-30)
### Fixed (4 changes, 1 of them is from the community) ### Fixed (4 changes, 1 of them is from the community)
...@@ -538,6 +569,19 @@ entry. ...@@ -538,6 +569,19 @@ entry.
- Use monospaced font for MR diff commit link ref on GFM. - Use monospaced font for MR diff commit link ref on GFM.
## 11.0.6 (2018-08-27)
### Security (3 changes)
- Fixed persistent XSS rendering/escaping of diff location lines.
- Adding CSRF protection to Hooks resend action.
- Block link-local addresses in URLBlocker.
### Fixed (1 change, 1 of them is from the community)
- Sanitize git URL in import errors. (Jamie Schembri)
## 11.0.5 (2018-07-26) ## 11.0.5 (2018-07-26)
### Security (4 changes) ### Security (4 changes)
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
%hr %hr
= link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), class: "btn btn-default float-right prepend-left-10" = link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), method: :post, class: "btn btn-default float-right prepend-left-10"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log } = render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
...@@ -4,6 +4,6 @@ ...@@ -4,6 +4,6 @@
Request details Request details
.col-lg-9 .col-lg-9
= link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), class: "btn btn-default float-right prepend-left-10" = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), method: :post, class: "btn btn-default float-right prepend-left-10"
= render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log } = render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log }
---
title: Adding CSRF protection to Hooks resend action
merge_request:
author:
type: security
...@@ -65,7 +65,7 @@ namespace :admin do ...@@ -65,7 +65,7 @@ namespace :admin do
resources :hook_logs, only: [:show] do resources :hook_logs, only: [:show] do
member do member do
get :retry post :retry
end end
end end
end end
......
...@@ -362,7 +362,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -362,7 +362,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :hook_logs, only: [:show] do resources :hook_logs, only: [:show] do
member do member do
get :retry post :retry
end end
end end
end end
......
...@@ -8,7 +8,7 @@ from source, follow the ...@@ -8,7 +8,7 @@ from source, follow the
NOTE: **Note:** NOTE: **Note:**
If your GitLab installation uses external PostgreSQL, the Omnibus roles If your GitLab installation uses external PostgreSQL, the Omnibus roles
will not be able to perform all necessary configuration steps. Refer to the will not be able to perform all necessary configuration steps. Refer to the
section on [External PostreSQL][external postgresql] for additional instructions. section on [External PostgreSQL][external postgresql] for additional instructions.
NOTE: **Note:** NOTE: **Note:**
The stages of the setup process must be completed in the documented order. The stages of the setup process must be completed in the documented order.
......
---
comments: false comments: false
description: "Learn how to use GitLab CI/CD, the GitLab built-in Continuous Integration, Continuous Deployment, and Continuous Delivery toolset to build, test, and deploy your application." description: "Learn how to use GitLab CI/CD, the GitLab built-in Continuous Integration, Continuous Deployment, and Continuous Delivery toolset to build, test, and deploy your application."
--- ---
......
## Custom project templates **[PREMIUM ONLY]** # Custom project templates **[PREMIUM ONLY]**
> **Notes:** > **Notes:**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in [GitLab Premium](https://about.gitlab.com/pricing) 11.2 > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in [GitLab Premium](https://about.gitlab.com/pricing) 11.2
......
...@@ -52,4 +52,4 @@ ...@@ -52,4 +52,4 @@
%a{ href: 'https://about.gitlab.com/license-faq/' } true-up model %a{ href: 'https://about.gitlab.com/license-faq/' } true-up model
has a retroactive charge for these users at the next renewal. If you want to update your has a retroactive charge for these users at the next renewal. If you want to update your
license sooner to prevent this, please contact license sooner to prevent this, please contact
#{mail_to 'sales@gitlab.com'}. #{mail_to 'renewals@gitlab.com'}.
---
title: Prevent regular users from moving projects to different storage shards
merge_request:
author:
type: security
...@@ -33,9 +33,16 @@ module EE ...@@ -33,9 +33,16 @@ module EE
def verify_update_project_attrs!(project, attrs) def verify_update_project_attrs!(project, attrs)
super super
verify_storage_attrs!(attrs)
verify_mirror_attrs!(project, attrs) verify_mirror_attrs!(project, attrs)
end end
def verify_storage_attrs!(attrs)
unless current_user.admin?
attrs.delete(:repository_storage)
end
end
def verify_mirror_attrs!(project, attrs) def verify_mirror_attrs!(project, attrs)
unless can?(current_user, :admin_mirror, project) unless can?(current_user, :admin_mirror, project)
attrs.delete(:mirror) attrs.delete(:mirror)
......
...@@ -77,16 +77,60 @@ describe API::Projects do ...@@ -77,16 +77,60 @@ describe API::Projects do
describe 'PUT /projects/:id' do describe 'PUT /projects/:id' do
let(:project) { create(:project, namespace: user.namespace) } let(:project) { create(:project, namespace: user.namespace) }
before do context 'when updating external classification' do
enable_external_authorization_service_check before do
enable_external_authorization_service_check
end
it 'updates the classification label' do
put(api("/projects/#{project.id}", user), external_authorization_classification_label: 'new label')
expect(response).to have_gitlab_http_status(200)
expect(project.reload.external_authorization_classification_label).to eq('new label')
end
end end
it 'updates the classification label when enabled' do context 'when updating repository storage' do
put(api("/projects/#{project.id}", user), external_authorization_classification_label: 'new label') let(:unknown_storage) { 'new-storage' }
let(:new_project) { create(:project, :repository, namespace: user.namespace) }
context 'as a user' do
it 'returns 200 but does not change repository_storage' do
expect do
Sidekiq::Testing.fake! do
put(api("/projects/#{new_project.id}", user), repository_storage: unknown_storage, issues_enabled: false)
end
end.not_to change(ProjectUpdateRepositoryStorageWorker.jobs, :size)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response['issues_enabled']).to eq(false)
expect(new_project.reload.repository.storage).to eq('default')
end
end
expect(project.reload.external_authorization_classification_label).to eq('new label') context 'as an admin' do
let(:admin) { create(:admin) }
it 'returns 500 when repository storage is unknown' do
put(api("/projects/#{new_project.id}", admin), repository_storage: unknown_storage)
expect(response).to have_gitlab_http_status(500)
expect(json_response['message']).to match('ArgumentError')
end
it 'returns 200 when repository storage has changed' do
stub_storage_settings('extra' => { 'path' => 'tmp/tests/extra_storage' })
expect do
Sidekiq::Testing.fake! do
put(api("/projects/#{new_project.id}", admin), repository_storage: 'extra')
end
end.to change(ProjectUpdateRepositoryStorageWorker.jobs, :size).by(1)
expect(response).to have_gitlab_http_status(200)
end
end
end end
context 'when updating mirror related attributes' do context 'when updating mirror related attributes' do
......
...@@ -21,14 +21,18 @@ module QA ...@@ -21,14 +21,18 @@ module QA
def perform(address, *rspec_options) def perform(address, *rspec_options)
Runtime::Scenario.define(:gitlab_address, address) Runtime::Scenario.define(:gitlab_address, address)
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
Specs::Runner.perform do |specs| Specs::Runner.perform do |specs|
specs.tty = true specs.tty = true
specs.tags = self.class.focus
specs.options = specs.options =
if rspec_options.any? if rspec_options.any?
rspec_options rspec_options
else else
::File.expand_path('../specs/features', __dir__) ['--tag', self.class.focus.join(','), '--', ::File.expand_path('../specs/features', __dir__)]
end end
end end
end end
......
...@@ -20,13 +20,18 @@ module QA ...@@ -20,13 +20,18 @@ module QA
def self.do_perform(address, *rspec_options) def self.do_perform(address, *rspec_options)
Runtime::Scenario.define(:gitlab_address, address) Runtime::Scenario.define(:gitlab_address, address)
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
Specs::Runner.perform do |specs| Specs::Runner.perform do |specs|
specs.tty = true specs.tty = true
specs.options = specs.options =
if rspec_options.any? if rspec_options.any?
rspec_options rspec_options
else else
::File.expand_path('../../specs/features', __dir__) ['--', ::File.expand_path('../../specs/features', __dir__)]
end end
end end
end end
......
# frozen_string_literal: true
module QA module QA
describe 'API users' do context :manage do
before(:context) do describe 'Users API' do
@api_client = Runtime::API::Client.new(:gitlab) before(:context) do
end @api_client = Runtime::API::Client.new(:gitlab)
end
context 'when authenticated' do
let(:request) { Runtime::API::Request.new(@api_client, '/users') } let(:request) { Runtime::API::Request.new(@api_client, '/users') }
it 'get list of users' do it 'GET /users' do
get request.url get request.url
expect_status(200) expect_status(200)
end end
it 'submit request with a valid user name' do it 'GET /users/:username with a valid username' do
get request.url, { params: { username: Runtime::User.username } } get request.url, { params: { username: Runtime::User.username } }
expect_status(200) expect_status(200)
...@@ -22,20 +24,12 @@ module QA ...@@ -22,20 +24,12 @@ module QA
) )
end end
it 'submit request with an invalid user name' do it 'GET /users/:username with an invalid username' do
get request.url, { params: { username: SecureRandom.hex(10) } } get request.url, { params: { username: SecureRandom.hex(10) } }
expect_status(200) expect_status(200)
expect(json_body).to eq([]) expect(json_body).to eq([])
end end
end end
it 'submit request with an invalid token' do
request = Runtime::API::Request.new(@api_client, '/users', private_token: 'invalid')
get request.url
expect_status(401)
end
end end
end end
# frozen_string_literal: true
module QA
context :geo, :orchestrated, :geo do
describe 'Geo Nodes API' do
before(:all) do
get_personal_access_token
end
shared_examples 'retrieving configuration about Geo nodes' do
it 'GET /geo_nodes' do
get api_endpoint('/geo_nodes')
expect_status(200)
expect(json_body.size).to be >= 2
expect_json('?', primary: true)
expect_json_types('*', primary: :boolean, current: :boolean,
files_max_capacity: :integer, repos_max_capacity: :integer,
clone_protocol: :string, _links: :object)
end
it 'GET /geo_nodes/:id' do
get api_endpoint("/geo_nodes/#{geo_node[:id]}")
expect_status(200)
expect(json_body).to eq geo_node
end
end
shared_examples 'retrieving status about all Geo nodes' do
it 'GET /geo_nodes/status' do
get api_endpoint('/geo_nodes/status')
expect_status(200)
expect(json_body.size).to be >= 2
# only need to check that some of the key values are there
expect_json_types('*', health: :string,
attachments_count: :integer,
db_replication_lag_seconds: :integer_or_null,
lfs_objects_count: :integer,
job_artifacts_count: :integer,
projects_count: :integer,
repositories_count: :integer,
wikis_count: :integer,
replication_slots_count: :integer_or_null,
version: :string_or_null)
end
end
shared_examples 'retrieving status about a specific Geo node' do
it 'GET /geo_nodes/:id/status of primary node' do
get api_endpoint("/geo_nodes/#{@primary_node[:id]}/status")
expect_status(200)
expect_json(geo_node_id: @primary_node[:id])
end
it 'GET /geo_nodes/:id/status of secondary node' do
get api_endpoint("/geo_nodes/#{@secondary_node[:id]}/status")
expect_status(200)
expect_json(geo_node_id: @secondary_node[:id])
end
it 'GET /geo_nodes/:id/status of an invalid node' do
get api_endpoint("/geo_nodes/1000/status")
expect_status(404)
end
end
shared_examples 'retrieving project sync failures ocurred on the current node' do
it 'GET /geo_nodes/current/failures' do
get api_endpoint("/geo_nodes/current/failures")
expect_status(200)
expect(json_body).to be_an Array
end
end
describe 'Geo Nodes API on primary node', :geo do
before(:context) do
fetch_nodes(:geo_primary)
end
include_examples 'retrieving configuration about Geo nodes' do
let(:geo_node) { @primary_node }
end
include_examples 'retrieving status about all Geo nodes'
include_examples 'retrieving status about a specific Geo node'
describe 'editing a Geo node' do
it 'PUT /geo_nodes/:id for primary node' do
put api_endpoint("/geo_nodes/#{@primary_node[:id]}"),
{ params: { files_max_capacity: 1000 } }
expect_status(403)
end
it 'PUT /geo_nodes/:id for secondary node' do
endpoint = api_endpoint("/geo_nodes/#{@secondary_node[:id]}")
new_attributes = { enabled: false, files_max_capacity: 1000, repos_max_capacity: 2000 }
put endpoint, new_attributes
expect_status(200)
expect_json(new_attributes)
# restore the original values
put endpoint, { enabled: @secondary_node[:enabled],
files_max_capacity: @secondary_node[:files_max_capacity],
repos_max_capacity: @secondary_node[:repos_max_capacity] }
expect_status(200)
end
it 'PUT /geo_nodes/:id for an invalid node' do
put api_endpoint("/geo_nodes/1000"),
{ params: { files_max_capacity: 1000 } }
expect_status(404)
end
end
describe 'repairing a Geo node' do
it 'POST /geo_nodes/:id/repair for primary node' do
post api_endpoint("/geo_nodes/#{@primary_node[:id]}/repair")
expect_status(200)
expect_json(geo_node_id: @primary_node[:id])
end
it 'POST /geo_nodes/:id/repair for secondary node' do
post api_endpoint("/geo_nodes/#{@secondary_node[:id]}/repair")
expect_status(200)
expect_json(geo_node_id: @secondary_node[:id])
end
it 'POST /geo_nodes/:id/repair for an invalid node' do
post api_endpoint("/geo_nodes/1000/repair")
expect_status(404)
end
end
end
describe 'Geo Nodes API on secondary node', :geo do
before(:context) do
fetch_nodes(:geo_secondary)
end
include_examples 'retrieving configuration about Geo nodes' do
let(:geo_node) { @nodes.first }
end
include_examples 'retrieving status about all Geo nodes'
include_examples 'retrieving status about a specific Geo node'
include_examples 'retrieving project sync failures ocurred on the current node'
it 'GET /geo_nodes is not current' do
get api_endpoint('/geo_nodes')
expect_status(200)
expect_json('?', current: false)
end
describe 'editing a Geo node' do
it 'PUT /geo_nodes/:id for primary node' do
put api_endpoint("/geo_nodes/#{@primary_node[:id]}"),
{ params: { files_max_capacity: 1000 } }
expect_status(403)
end
it 'PUT /geo_nodes/:id for secondary node' do
put api_endpoint("/geo_nodes/#{@secondary_node[:id]}"),
{ params: { files_max_capacity: 1000 } }
expect_status(403)
end
it 'PUT /geo_nodes/:id for an invalid node' do
put api_endpoint('/geo_nodes/1000'),
{ params: { files_max_capacity: 1000 } }
expect_status(403)
end
end
describe 'repairing a Geo node' do
it 'POST /geo_nodes/:id/repair for primary node' do
post api_endpoint("/geo_nodes/#{@primary_node[:id]}/repair")
expect_status(403)
end
it 'POST /geo_nodes/:id/repair for secondary node' do
post api_endpoint("/geo_nodes/#{@secondary_node[:id]}/repair")
expect_status(403)
end
it 'POST /geo_nodes/:id/repair for an invalid node' do
post api_endpoint('/geo_nodes/1000/repair')
expect_status(403)
end
end
end
def api_endpoint(endpoint)
QA::Runtime::API::Request.new(@api_client, endpoint).url
end
def fetch_nodes(node_type)
@api_client = Runtime::API::Client.new(node_type, personal_access_token: @personal_access_token)
get api_endpoint('/geo_nodes')
@nodes = json_body
@primary_node = @nodes.detect { |node| node[:primary] == true }
@secondary_node = @nodes.detect { |node| node[:primary] == false }
end
# go to the primary and create a personal_access_token, which will be used
# for accessing both the primary and secondary
def get_personal_access_token
api_client = Runtime::API::Client.new(:geo_primary)
@personal_access_token = api_client.personal_access_token
end
end
end
end
module QA
context :manage, :smoke do
describe 'basic user login' do
it 'user logs in using basic credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
# TODO, since `Signed in successfully` message was removed
# this is the only way to tell if user is signed in correctly.
#
Page::Menu::Main.perform do |menu|
expect(menu).to have_personal_area
end
end
end
end
end
# frozen_string_literal: true
module QA
context :manage, :orchestrated, :ldap do
describe 'LDAP login' do
before do
Runtime::Env.user_type = 'ldap'
end
it 'user logs into GitLab using LDAP credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
# TODO, since `Signed in successfully` message was removed
# this is the only way to tell if user is signed in correctly.
#
Page::Menu::Main.perform do |menu|
expect(menu).to have_personal_area
end
end
end
end
end
# frozen_string_literal: true
module QA
context :manage, :orchestrated, :mattermost do
describe 'Mattermost login' do
it 'user logs into Mattermost using GitLab OAuth' do
Runtime::Browser.visit(:gitlab, Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
Runtime::Browser.visit(:mattermost, Page::Mattermost::Login) do
Page::Mattermost::Login.act { sign_in_using_oauth }
Page::Mattermost::Main.perform do |page|
expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
end
end
end
end
end
end
end
# frozen_string_literal: true
module QA
context :manage, :smoke do
describe 'Project creation' do
it 'user creates a new project' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
created_project = Factory::Resource::Project.fabricate! do |project|
project.name = 'awesome-project'
project.description = 'create awesome project test'
end
expect(created_project.name).to match /^awesome-project-\h{16}$/
expect(page).to have_content(
/Project \S?awesome-project\S+ was successfully created/
)
expect(page).to have_content('create awesome project test')
expect(page).to have_content('The repository for this project is empty')
end
end
end
end
# frozen_string_literal: true
module QA
context :manage, :orchestrated, :github do
describe 'Project import from GitHub' do
let(:imported_project) do
Factory::Resource::ProjectImportedFromGithub.fabricate! do |project|
project.name = 'imported-project'
project.personal_access_token = Runtime::Env.github_access_token
project.github_repository_path = 'gitlab-qa/test-project'
end
end
after do
# We need to delete the imported project because it's impossible to import
# the same GitHub project twice for a given user.
api_client = Runtime::API::Client.new(:gitlab)
delete_project_request = Runtime::API::Request.new(api_client, "/projects/#{CGI.escape("#{Runtime::Namespace.path}/#{imported_project.name}")}")
delete delete_project_request.url
expect_status(202)
end
it 'user imports a GitHub repo' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
imported_project # import the project
Page::Menu::Main.act { go_to_projects }
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(imported_project.name)
end
Page::Project::Show.act { wait_for_import }
verify_repository_import
verify_issues_import
verify_merge_requests_import
verify_labels_import
verify_milestones_import
verify_wiki_import
end
def verify_repository_import
expect(page).to have_content('This test project is used for automated GitHub import by GitLab QA.')
expect(page).to have_content(imported_project.name)
end
def verify_issues_import
Page::Menu::Side.act { click_issues }
expect(page).to have_content('This is a sample issue')
click_link 'This is a sample issue'
expect(page).to have_content('We should populate this project with issues, pull requests and wiki pages.')
# Comments
expect(page).to have_content('This is a comment from @rymai.')
Page::Issuable::Sidebar.perform do |issuable|
expect(issuable).to have_label('enhancement')
expect(issuable).to have_label('help wanted')
expect(issuable).to have_label('good first issue')
end
end
def verify_merge_requests_import
Page::Menu::Side.act { click_merge_requests }
expect(page).to have_content('Improve README.md')
click_link 'Improve README.md'
expect(page).to have_content('This improves the README file a bit.')
# Review comment are not supported yet
expect(page).not_to have_content('Really nice change.')
# Comments
expect(page).to have_content('Nice work! This is a comment from @rymai.')
# Diff comments
expect(page).to have_content('[Review comment] I like that!')
expect(page).to have_content('[Review comment] Nice blank line.')
expect(page).to have_content('[Single diff comment] Much better without this line!')
Page::Issuable::Sidebar.perform do |issuable|
expect(issuable).to have_label('bug')
expect(issuable).to have_label('enhancement')
end
end
def verify_labels_import
# TODO: Waiting on https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19228
# to build upon it.
end
def verify_milestones_import
# TODO: Waiting on https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18727
# to build upon it.
end
def verify_wiki_import
Page::Menu::Side.act { click_wiki }
expect(page).to have_content('Welcome to the test-project wiki!')
end
end
end
end
# frozen_string_literal: true
module QA
context :manage do
describe 'Project activity' do
it 'user creates an event in the activity page upon Git push' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Repository::ProjectPush.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is a test project'
push.commit_message = 'Add README.md'
end
Page::Menu::Side.act { go_to_activity }
Page::Project::Activity.act { go_to_push_events }
expect(page).to have_content('pushed new branch master')
end
end
end
end
# frozen_string_literal: true
module QA
context :plan, :smoke do
describe 'Issue creation' do
let(:issue_title) { 'issue title' }
it 'user creates an issue' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Issue.fabricate! do |issue|
issue.title = issue_title
end
Page::Menu::Side.act { click_issues }
expect(page).to have_content(issue_title)
end
end
end
end
module QA # frozen_string_literal: true
describe 'creates a merge request with milestone' do
it 'user creates a new merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
current_project = Factory::Resource::Project.fabricate! do |project|
project.name = 'project-with-merge-request-and-milestone'
end
current_milestone = Factory::Resource::ProjectMilestone.fabricate! do |milestone|
milestone.title = 'unique-milestone'
milestone.project = current_project
end
Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request.title = 'This is a merge request with a milestone'
merge_request.description = 'Great feature with milestone'
merge_request.project = current_project
merge_request.milestone = current_milestone
end
expect(page).to have_content('This is a merge request with a milestone')
expect(page).to have_content('Great feature with milestone')
expect(page).to have_content(/Opened [\w\s]+ ago/)
Page::Issuable::Sidebar.perform do |sidebar| module QA
expect(sidebar).to have_milestone(current_milestone.title) context :create do
describe 'Merge request creation' do
it 'user creates a new merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
current_project = Factory::Resource::Project.fabricate! do |project|
project.name = 'project-with-merge-request-and-milestone'
end
current_milestone = Factory::Resource::ProjectMilestone.fabricate! do |milestone|
milestone.title = 'unique-milestone'
milestone.project = current_project
end
Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request.title = 'This is a merge request with a milestone'
merge_request.description = 'Great feature with milestone'
merge_request.project = current_project
merge_request.milestone = current_milestone
end
expect(page).to have_content('This is a merge request with a milestone')
expect(page).to have_content('Great feature with milestone')
expect(page).to have_content(/Opened [\w\s]+ ago/)
Page::Issuable::Sidebar.perform do |sidebar|
expect(sidebar).to have_milestone(current_milestone.title)
end
end end
end end
end end
......
# frozen_string_literal: true
module QA
context :create do
describe 'Merge request creation from fork' do
it 'user forks a project, submits a merge request and maintainer merges it' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
merge_request = Factory::Resource::MergeRequestFromFork.fabricate! do |merge_request|
merge_request.fork_branch = 'feature-branch'
end
Page::Menu::Main.perform { |main| main.sign_out }
Page::Main::Login.perform { |login| login.sign_in_using_credentials }
merge_request.visit!
Page::MergeRequest::Show.perform { |show| show.merge! }
expect(page).to have_content('The changes were merged')
end
end
end
end
# frozen_string_literal: true
module QA
context :create do
describe 'Merge request rebasing' do
it 'user rebases source branch of merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = "only-fast-forward"
end
Page::Menu::Side.act { go_to_settings }
Page::Project::Settings::MergeRequest.act { enable_ff_only }
merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request.project = project
merge_request.title = 'Needs rebasing'
end
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.file_name = "other.txt"
push.file_content = "New file added!"
push.branch_name = "master"
push.new_branch = false
end
merge_request.visit!
Page::MergeRequest::Show.perform do |merge_request|
expect(merge_request).to have_content('Needs rebasing')
expect(merge_request).not_to be_fast_forward_possible
expect(merge_request).not_to have_merge_button
merge_request.rebase!
expect(merge_request).to have_merge_button
expect(merge_request.fast_forward_possible?).to be_truthy
end
end
end
end
end
# frozen_string_literal: true
module QA
context :create do
describe 'Merge request squashing' do
it 'user squashes commits while merging' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = "squash-before-merge"
end
merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request.project = project
merge_request.title = 'Squashing commits'
end
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.commit_message = 'to be squashed'
push.branch_name = merge_request.source_branch
push.new_branch = false
push.file_name = 'other.txt'
push.file_content = "Test with unicode characters ❤✓€❄"
end
merge_request.visit!
expect(page).to have_text('to be squashed')
Page::MergeRequest::Show.perform do |merge_request_page|
merge_request_page.mark_to_squash
merge_request_page.merge!
merge_request.project.visit!
Git::Repository.perform do |repository|
repository.uri = Page::Project::Show.act do
choose_repository_clone_http
repository_location.uri
end
repository.use_default_credentials
repository.act { clone }
expect(repository.commits.size).to eq 3
end
end
end
end
end
end
# frozen_string_literal: true
module QA module QA
describe 'clone code from the repository' do context :create do
context 'with regular account over http' do describe 'Git clone over HTTP' do
let(:location) do let(:location) do
Page::Project::Show.act do Page::Project::Show.act do
choose_repository_clone_http choose_repository_clone_http
......
# frozen_string_literal: true
module QA
context :create do
describe 'Files management' do
it 'user creates, edits and deletes a file via the Web' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
# Create
file_name = 'QA Test - File name'
file_content = 'QA Test - File content'
commit_message_for_create = 'QA Test - Create new file'
Factory::Resource::File.fabricate! do |file|
file.name = file_name
file.content = file_content
file.commit_message = commit_message_for_create
end
expect(page).to have_content('The file has been successfully created.')
expect(page).to have_content(file_name)
expect(page).to have_content(file_content)
expect(page).to have_content(commit_message_for_create)
# Edit
updated_file_content = 'QA Test - Updated file content'
commit_message_for_update = 'QA Test - Update file'
Page::File::Show.act { click_edit }
Page::File::Form.act do
remove_content
add_content(updated_file_content)
add_commit_message(commit_message_for_update)
commit_changes
end
expect(page).to have_content('Your changes have been successfully committed.')
expect(page).to have_content(updated_file_content)
expect(page).to have_content(commit_message_for_update)
# Delete
commit_message_for_delete = 'QA Test - Delete file'
Page::File::Show.act do
click_delete
add_commit_message(commit_message_for_delete)
click_delete_file
end
expect(page).to have_content('The file has been successfully deleted.')
expect(page).to have_content(commit_message_for_delete)
expect(page).to have_no_content(file_name)
end
end
end
end
# frozen_string_literal: true
module QA module QA
describe 'push code to repository' do context :create do
context 'with regular account over http' do describe 'Git push over HTTP' do
it 'user pushes code to the repository' do it 'user pushes code to the repository' do
Runtime::Browser.visit(:gitlab, Page::Main::Login) Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials } Page::Main::Login.act { sign_in_using_credentials }
......
# frozen_string_literal: true
module QA
context :create do
describe 'Protected branch support' do
let(:branch_name) { 'protected-branch' }
let(:commit_message) { 'Protected push commit message' }
let(:project) do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'protected-branch-project'
end
end
before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
end
after do
# We need to clear localStorage because we're using it for the dropdown,
# and capybara doesn't do this for us.
# https://github.com/teamcapybara/capybara/issues/1702
Capybara.execute_script 'localStorage.clear()'
end
context 'when developers and maintainers are allowed to push to a protected branch' do
it 'user with push rights successfully pushes to the protected branch' do
create_protected_branch(allow_to_push: true)
push = push_new_file(branch_name)
expect(push.output).to match(/remote: To create a merge request for protected-branch, visit/)
end
end
context 'when developers and maintainers are not allowed to push to a protected branch' do
it 'user without push rights fails to push to the protected branch' do
create_protected_branch(allow_to_push: false)
push = push_new_file(branch_name)
expect(push.output)
.to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/)
expect(push.output)
.to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
end
def create_protected_branch(allow_to_push:)
Factory::Resource::Branch.fabricate! do |resource|
resource.branch_name = branch_name
resource.project = project
resource.allow_to_push = allow_to_push
resource.protected = true
end
end
def push_new_file(branch)
Factory::Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.file_name = 'new_file.md'
resource.file_content = '# This is a new file'
resource.commit_message = 'Add new_file.md'
resource.branch_name = branch_name
resource.new_branch = false
end
end
end
end
end
# frozen_string_literal: true
module QA
context :create do
describe 'Wiki management' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
end
def validate_content(content)
expect(page).to have_content('Wiki was successfully updated')
expect(page).to have_content(/#{content}/)
end
before do
login
end
it 'user creates, edits, clones, and pushes to the wiki' do
wiki = Factory::Resource::Wiki.fabricate! do |resource|
resource.title = 'Home'
resource.content = '# My First Wiki Content'
resource.message = 'Update home'
end
validate_content('My First Wiki Content')
Page::Project::Wiki::Edit.act { go_to_edit_page }
Page::Project::Wiki::New.perform do |page|
page.set_content("My Second Wiki Content")
page.save_changes
end
validate_content('My Second Wiki Content')
Factory::Repository::WikiPush.fabricate! do |push|
push.wiki = wiki
push.file_name = 'Home.md'
push.file_content = '# My Third Wiki Content'
push.commit_message = 'Update Home.md'
end
Page::Menu::Side.act { click_wiki }
expect(page).to have_content('My Third Wiki Content')
end
end
end
end
# frozen_string_literal: true
module QA
context :verify, :docker do
describe 'Pipeline creation and processing' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
after do
Service::Runner.new(executor).remove!
end
it 'users creates a pipeline which gets processed' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'project-with-pipelines'
project.description = 'Project with CI/CD Pipelines.'
end
Factory::Resource::Runner.fabricate! do |runner|
runner.project = project
runner.name = executor
runner.tags = %w[qa test]
end
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.file_name = '.gitlab-ci.yml'
push.commit_message = 'Add .gitlab-ci.yml'
push.file_content = <<~EOF
test-success:
tags:
- qa
- test
script: echo 'OK'
test-failure:
tags:
- qa
- test
script:
- echo 'FAILURE'
- exit 1
test-tags:
tags:
- qa
- docker
script: echo 'NOOP'
test-artifacts:
tags:
- qa
- test
script: mkdir my-artifacts; echo "CONTENTS" > my-artifacts/artifact.txt
artifacts:
paths:
- my-artifacts/
EOF
end
Page::Project::Show.act { wait_for_push }
expect(page).to have_content('Add .gitlab-ci.yml')
Page::Menu::Side.act { click_ci_cd_pipelines }
expect(page).to have_content('All 1')
expect(page).to have_content('Add .gitlab-ci.yml')
puts 'Waiting for the runner to process the pipeline'
sleep 15 # Runner should process all jobs within 15 seconds.
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to be_running
expect(pipeline).to have_build('test-success', status: :success)
expect(pipeline).to have_build('test-failure', status: :failed)
expect(pipeline).to have_build('test-tags', status: :pending)
expect(pipeline).to have_build('test-artifacts', status: :success)
end
end
end
end
end
# frozen_string_literal: true
module QA
context :verify, :docker do
describe 'Runner registration' do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
after do
Service::Runner.new(executor).remove!
end
it 'user registers a new specific runner' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Runner.fabricate! do |runner|
runner.name = executor
end
Page::Project::Settings::CICD.perform do |settings|
sleep 5 # Runner should register within 5 seconds
settings.refresh
settings.expand_runners_settings do |page|
expect(page).to have_content(executor)
expect(page).to have_online_runner
end
end
end
end
end
end
# frozen_string_literal: true
module QA
context :verify do
describe 'Secret variable support' do
it 'user adds a secret variable' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::SecretVariable.fabricate! do |resource|
resource.key = 'VARIABLE_KEY'
resource.value = 'some secret variable'
end
Page::Project::Settings::CICD.perform do |settings|
settings.expand_secret_variables do |page|
expect(page).to have_field(with: 'VARIABLE_KEY')
expect(page).not_to have_field(with: 'some secret variable')
page.reveal_variables
expect(page).to have_field(with: 'some secret variable')
end
end
end
end
end
end
# frozen_string_literal: true
module QA
context :release do
describe 'Deploy key creation' do
it 'user adds a deploy key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
key = Runtime::Key::RSA.new
deploy_key_title = 'deploy key title'
deploy_key_value = key.public_key
deploy_key = Factory::Resource::DeployKey.fabricate! do |resource|
resource.title = deploy_key_title
resource.key = deploy_key_value
end
expect(deploy_key.fingerprint).to eq(key.fingerprint)
end
end
end
end
# frozen_string_literal: true
require 'digest/sha1'
module QA
context :release, :docker do
describe 'Git clone using a deploy key' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
end
before(:all) do
login
@runner_name = "qa-runner-#{Time.now.to_i}"
@project = Factory::Resource::Project.fabricate! do |resource|
resource.name = 'deploy-key-clone-project'
end
@repository_location = @project.repository_ssh_location
Factory::Resource::Runner.fabricate! do |resource|
resource.project = @project
resource.name = @runner_name
resource.tags = %w[qa docker]
resource.image = 'gitlab/gitlab-runner:ubuntu'
end
Page::Menu::Main.act { sign_out }
end
after(:all) do
Service::Runner.new(@runner_name).remove!
end
keys = [
[Runtime::Key::RSA, 8192],
[Runtime::Key::ECDSA, 521],
[Runtime::Key::ED25519]
]
keys.each do |(key_class, bits)|
it "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines" do
key = key_class.new(*bits)
login
Factory::Resource::DeployKey.fabricate! do |resource|
resource.project = @project
resource.title = "deploy key #{key.name}(#{key.bits})"
resource.key = key.public_key
end
deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}"
Factory::Resource::SecretVariable.fabricate! do |resource|
resource.project = @project
resource.key = deploy_key_name
resource.value = key.private_key
end
gitlab_ci = <<~YAML
cat-config:
script:
- mkdir -p ~/.ssh
- ssh-keyscan -p #{@repository_location.port} #{@repository_location.host} >> ~/.ssh/known_hosts
- eval $(ssh-agent -s)
- ssh-add -D
- echo "$#{deploy_key_name}" | ssh-add -
- git clone #{@repository_location.git_uri}
- cd #{@project.name}
- git checkout #{deploy_key_name}
- sha1sum .gitlab-ci.yml
tags:
- qa
- docker
YAML
Factory::Repository::ProjectPush.fabricate! do |resource|
resource.project = @project
resource.file_name = '.gitlab-ci.yml'
resource.commit_message = 'Add .gitlab-ci.yml'
resource.file_content = gitlab_ci
resource.branch_name = deploy_key_name
resource.new_branch = true
end
sha1sum = Digest::SHA1.hexdigest(gitlab_ci)
Page::Project::Show.act { wait_for_push }
Page::Menu::Side.act { click_ci_cd_pipelines }
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
Page::Project::Pipeline::Show.act { go_to_first_job }
Page::Project::Job::Show.perform do |job|
job.wait(reload: false) do
job.completed? && !job.trace_loading?
end
expect(job.passed?).to be_truthy, "Job status did not become \"passed\"."
expect(job.output).to include(sha1sum)
end
end
end
end
end
end
# frozen_string_literal: true
require 'pathname'
module QA
context :configure, :orchestrated, :kubernetes do
describe 'Auto DevOps support' do
after do
@cluster&.remove!
end
it 'user creates a new project and runs auto devops' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |p|
p.name = 'project-with-autodevops'
p.description = 'Project with Auto Devops'
end
# Disable code_quality check in Auto DevOps pipeline as it takes
# too long and times out the test
Factory::Resource::SecretVariable.fabricate! do |resource|
resource.key = 'CODE_QUALITY_DISABLED'
resource.value = '1'
end
# Create Auto Devops compatible repo
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.directory = Pathname
.new(__dir__)
.join('../../../fixtures/auto_devops_rack')
push.commit_message = 'Create Auto DevOps compatible rack application'
end
Page::Project::Show.act { wait_for_push }
# Create and connect K8s cluster
@cluster = Service::KubernetesCluster.new.create!
kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster|
cluster.project = project
cluster.cluster = @cluster
cluster.install_helm_tiller = true
cluster.install_ingress = true
cluster.install_prometheus = true
cluster.install_runner = true
end
project.visit!
Page::Menu::Side.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p|
p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
end
project.visit!
Page::Menu::Side.act { click_ci_cd_pipelines }
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_build('build', status: :success, wait: 600)
expect(pipeline).to have_build('test', status: :success, wait: 600)
expect(pipeline).to have_build('production', status: :success, wait: 1200)
end
end
end
end
end
# frozen_string_literal: true
module QA
context :configure, :orchestrated, :mattermost do
describe 'Mattermost support' do
it 'user creates a group with a mattermost team' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Page::Menu::Main.act { go_to_groups }
Page::Dashboard::Groups.perform do |page|
page.go_to_new_group
expect(page).to have_content(
/Create a Mattermost team for this group/
)
end
end
end
end
end
# frozen_string_literal: true
module QA
context :geo, :orchestrated, :geo do
describe 'GitLab Geo attachment replication' do
let(:file_to_attach) { File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif')) }
it 'user uploads attachment to the primary node' do
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'project-for-issues'
project.description = 'project for adding issues'
end
issue = Factory::Resource::Issue.fabricate! do |issue|
issue.title = 'My geo issue'
issue.project = project
end
Page::Project::Issue::Show.perform do |show|
show.comment('See attached banana for scale', attachment: file_to_attach)
end
Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do |session|
Page::Main::OAuth.act do
authorize! if needs_authorization?
end
EE::Page::Main::Banner.perform do |banner|
expect(banner).to have_secondary_read_only_banner
end
expect(page).to have_content 'You are on a secondary, read-only Geo node'
Page::Menu::Main.perform do |menu|
menu.go_to_projects
end
Page::Dashboard::Projects.perform do |dashboard|
dashboard.wait_for_project_replication(project.name)
dashboard.go_to_project(project.name)
end
Page::Menu::Side.act { click_issues }
Page::Project::Issue::Index.perform do |index|
index.wait_for_issue_replication(issue)
end
image_url = find('a[href$="banana_sample.gif"]')[:href]
Page::Project::Issue::Show.perform do |show|
# Wait for attachment replication
found = show.wait(reload: false) do
show.asset_exists?(image_url)
end
expect(found).to be_truthy
end
end
end
end
end
end
end
# frozen_string_literal: true
module QA
context :geo, :orchestrated, :geo do
describe 'GitLab Geo project rename replication' do
it 'user renames project' do
# create the project and push code
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'geo-before-rename'
project.description = 'Geo project to be renamed'
end
geo_project_name = project.name
expect(project.name).to include 'geo-before-rename'
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.file_name = 'README.md'
push.file_content = '# This is Geo project!'
push.commit_message = 'Add README.md'
end
# rename the project
Page::Menu::Main.act { go_to_projects }
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(geo_project_name)
end
Page::Menu::Side.act { go_to_settings }
geo_project_renamed = "geo-after-rename-#{SecureRandom.hex(8)}"
Page::Project::Settings::Main.perform do |settings|
settings.expand_advanced_settings do |page|
page.rename_to(geo_project_renamed)
end
end
# check renamed project exist on secondary node
Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
Page::Main::OAuth.act do
authorize! if needs_authorization?
end
EE::Page::Main::Banner.perform do |banner|
expect(banner).to have_secondary_read_only_banner
end
Page::Menu::Main.perform do |menu|
menu.go_to_projects
end
Page::Dashboard::Projects.perform do |dashboard|
dashboard.wait_for_project_replication(geo_project_renamed)
dashboard.go_to_project(geo_project_renamed)
end
Page::Project::Show.perform do |show|
show.wait_for_repository_replication
expect(page).to have_content 'README.md'
expect(page).to have_content 'This is Geo project!'
end
end
end
end
end
end
end
# frozen_string_literal: true
module QA
context :geo, :orchestrated, :geo do
describe 'GitLab Geo repository replication' do
it 'users pushes code to the primary node' do
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'geo-project'
project.description = 'Geo test project'
end
geo_project_name = Page::Project::Show.act { project_name }
expect(geo_project_name).to include 'geo-project'
Factory::Repository::ProjectPush.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is Geo project!'
push.commit_message = 'Add README.md'
push.project = project
end
Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
Page::Main::OAuth.act do
authorize! if needs_authorization?
end
EE::Page::Main::Banner.perform do |banner|
expect(banner).to have_secondary_read_only_banner
end
Page::Menu::Main.perform do |menu|
menu.go_to_projects
end
Page::Dashboard::Projects.perform do |dashboard|
dashboard.wait_for_project_replication(geo_project_name)
dashboard.go_to_project(geo_project_name)
end
Page::Project::Show.perform do |show|
show.wait_for_repository_replication
expect(page).to have_content 'README.md'
expect(page).to have_content 'This is Geo project!'
end
end
end
end
end
end
end
module QA
describe 'Geo Nodes API' do
before(:all) do
get_personal_access_token
end
shared_examples 'retrieving configuration about Geo nodes' do
it 'GET /geo_nodes' do
get api_endpoint('/geo_nodes')
expect_status(200)
expect(json_body.size).to be >= 2
expect_json('?', primary: true)
expect_json_types('*', primary: :boolean, current: :boolean,
files_max_capacity: :integer, repos_max_capacity: :integer,
clone_protocol: :string, _links: :object)
end
it 'GET /geo_nodes/:id' do
get api_endpoint("/geo_nodes/#{geo_node[:id]}")
expect_status(200)
expect(json_body).to eq geo_node
end
end
shared_examples 'retrieving status about all Geo nodes' do
it 'GET /geo_nodes/status' do
get api_endpoint('/geo_nodes/status')
expect_status(200)
expect(json_body.size).to be >= 2
# only need to check that some of the key values are there
expect_json_types('*', health: :string,
attachments_count: :integer,
db_replication_lag_seconds: :integer_or_null,
lfs_objects_count: :integer,
job_artifacts_count: :integer,
projects_count: :integer,
repositories_count: :integer,
wikis_count: :integer,
replication_slots_count: :integer_or_null,
version: :string_or_null)
end
end
shared_examples 'retrieving status about a specific Geo node' do
it 'GET /geo_nodes/:id/status of primary node' do
get api_endpoint("/geo_nodes/#{@primary_node[:id]}/status")
expect_status(200)
expect_json(geo_node_id: @primary_node[:id])
end
it 'GET /geo_nodes/:id/status of secondary node' do
get api_endpoint("/geo_nodes/#{@secondary_node[:id]}/status")
expect_status(200)
expect_json(geo_node_id: @secondary_node[:id])
end
it 'GET /geo_nodes/:id/status of an invalid node' do
get api_endpoint("/geo_nodes/1000/status")
expect_status(404)
end
end
shared_examples 'retrieving project sync failures ocurred on the current node' do
it 'GET /geo_nodes/current/failures' do
get api_endpoint("/geo_nodes/current/failures")
expect_status(200)
expect(json_body).to be_an Array
end
end
describe 'Geo Nodes API on primary node', :geo do
before(:context) do
fetch_nodes(:geo_primary)
end
include_examples 'retrieving configuration about Geo nodes' do
let(:geo_node) { @primary_node }
end
include_examples 'retrieving status about all Geo nodes'
include_examples 'retrieving status about a specific Geo node'
describe 'editing a Geo node' do
it 'PUT /geo_nodes/:id for primary node' do
put api_endpoint("/geo_nodes/#{@primary_node[:id]}"),
{ params: { files_max_capacity: 1000 } }
expect_status(403)
end
it 'PUT /geo_nodes/:id for secondary node' do
endpoint = api_endpoint("/geo_nodes/#{@secondary_node[:id]}")
new_attributes = { enabled: false, files_max_capacity: 1000, repos_max_capacity: 2000 }
put endpoint, new_attributes
expect_status(200)
expect_json(new_attributes)
# restore the original values
put endpoint, { enabled: @secondary_node[:enabled],
files_max_capacity: @secondary_node[:files_max_capacity],
repos_max_capacity: @secondary_node[:repos_max_capacity] }
expect_status(200)
end
it 'PUT /geo_nodes/:id for an invalid node' do
put api_endpoint("/geo_nodes/1000"),
{ params: { files_max_capacity: 1000 } }
expect_status(404)
end
end
describe 'repairing a Geo node' do
it 'POST /geo_nodes/:id/repair for primary node' do
post api_endpoint("/geo_nodes/#{@primary_node[:id]}/repair")
expect_status(200)
expect_json(geo_node_id: @primary_node[:id])
end
it 'POST /geo_nodes/:id/repair for secondary node' do
post api_endpoint("/geo_nodes/#{@secondary_node[:id]}/repair")
expect_status(200)
expect_json(geo_node_id: @secondary_node[:id])
end
it 'POST /geo_nodes/:id/repair for an invalid node' do
post api_endpoint("/geo_nodes/1000/repair")
expect_status(404)
end
end
end
describe 'Geo Nodes API on secondary node', :geo do
before(:context) do
fetch_nodes(:geo_secondary)
end
include_examples 'retrieving configuration about Geo nodes' do
let(:geo_node) { @nodes.first }
end
include_examples 'retrieving status about all Geo nodes'
include_examples 'retrieving status about a specific Geo node'
include_examples 'retrieving project sync failures ocurred on the current node'
it 'GET /geo_nodes is not current' do
get api_endpoint('/geo_nodes')
expect_status(200)
expect_json('?', current: false)
end
describe 'editing a Geo node' do
it 'PUT /geo_nodes/:id for primary node' do
put api_endpoint("/geo_nodes/#{@primary_node[:id]}"),
{ params: { files_max_capacity: 1000 } }
expect_status(403)
end
it 'PUT /geo_nodes/:id for secondary node' do
put api_endpoint("/geo_nodes/#{@secondary_node[:id]}"),
{ params: { files_max_capacity: 1000 } }
expect_status(403)
end
it 'PUT /geo_nodes/:id for an invalid node' do
put api_endpoint('/geo_nodes/1000'),
{ params: { files_max_capacity: 1000 } }
expect_status(403)
end
end
describe 'repairing a Geo node' do
it 'POST /geo_nodes/:id/repair for primary node' do
post api_endpoint("/geo_nodes/#{@primary_node[:id]}/repair")
expect_status(403)
end
it 'POST /geo_nodes/:id/repair for secondary node' do
post api_endpoint("/geo_nodes/#{@secondary_node[:id]}/repair")
expect_status(403)
end
it 'POST /geo_nodes/:id/repair for an invalid node' do
post api_endpoint('/geo_nodes/1000/repair')
expect_status(403)
end
end
end
def api_endpoint(endpoint)
QA::Runtime::API::Request.new(@api_client, endpoint).url
end
def fetch_nodes(node_type)
@api_client = Runtime::API::Client.new(node_type, personal_access_token: @personal_access_token)
get api_endpoint('/geo_nodes')
@nodes = json_body
@primary_node = @nodes.detect { |node| node[:primary] == true }
@secondary_node = @nodes.detect { |node| node[:primary] == false }
end
# go to the primary and create a personal_access_token, which will be used
# for accessing both the primary and secondary
def get_personal_access_token
api_client = Runtime::API::Client.new(:geo_primary)
@personal_access_token = api_client.personal_access_token
end
end
end
module QA
describe 'GitLab Geo attachment replication', :geo do
let(:file_to_attach) { File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif')) }
it 'user uploads attachment to the primary node' do
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'project-for-issues'
project.description = 'project for adding issues'
end
issue = Factory::Resource::Issue.fabricate! do |issue|
issue.title = 'My geo issue'
issue.project = project
end
Page::Project::Issue::Show.perform do |show|
show.comment('See attached banana for scale', attachment: file_to_attach)
end
Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do |session|
Page::Main::OAuth.act do
authorize! if needs_authorization?
end
EE::Page::Main::Banner.perform do |banner|
expect(banner).to have_secondary_read_only_banner
end
expect(page).to have_content 'You are on a secondary, read-only Geo node'
Page::Menu::Main.perform do |menu|
menu.go_to_projects
end
Page::Dashboard::Projects.perform do |dashboard|
dashboard.wait_for_project_replication(project.name)
dashboard.go_to_project(project.name)
end
Page::Menu::Side.act { click_issues }
Page::Project::Issue::Index.perform do |index|
index.wait_for_issue_replication(issue)
end
image_url = find('a[href$="banana_sample.gif"]')[:href]
Page::Project::Issue::Show.perform do |show|
# Wait for attachment replication
found = show.wait(reload: false) do
show.asset_exists?(image_url)
end
expect(found).to be_truthy
end
end
end
end
end
end
module QA
describe 'GitLab Geo project rename replication', :geo do
it 'user renames project' do
# create the project and push code
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'geo-before-rename'
project.description = 'Geo project to be renamed'
end
geo_project_name = project.name
expect(project.name).to include 'geo-before-rename'
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.file_name = 'README.md'
push.file_content = '# This is Geo project!'
push.commit_message = 'Add README.md'
end
# rename the project
Page::Menu::Main.act { go_to_projects }
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(geo_project_name)
end
Page::Menu::Side.act { go_to_settings }
geo_project_renamed = "geo-after-rename-#{SecureRandom.hex(8)}"
Page::Project::Settings::Main.perform do |settings|
settings.expand_advanced_settings do |page|
page.rename_to(geo_project_renamed)
end
end
# check renamed project exist on secondary node
Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
Page::Main::OAuth.act do
authorize! if needs_authorization?
end
EE::Page::Main::Banner.perform do |banner|
expect(banner).to have_secondary_read_only_banner
end
Page::Menu::Main.perform do |menu|
menu.go_to_projects
end
Page::Dashboard::Projects.perform do |dashboard|
dashboard.wait_for_project_replication(geo_project_renamed)
dashboard.go_to_project(geo_project_renamed)
end
Page::Project::Show.perform do |show|
show.wait_for_repository_replication
expect(page).to have_content 'README.md'
expect(page).to have_content 'This is Geo project!'
end
end
end
end
end
end
module QA
describe 'GitLab Geo repository replication', :geo do
it 'users pushes code to the primary node' do
Runtime::Browser.visit(:geo_primary, QA::Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'geo-project'
project.description = 'Geo test project'
end
geo_project_name = Page::Project::Show.act { project_name }
expect(geo_project_name).to include 'geo-project'
Factory::Repository::ProjectPush.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is Geo project!'
push.commit_message = 'Add README.md'
push.project = project
end
Runtime::Browser.visit(:geo_secondary, QA::Page::Main::Login) do
Page::Main::OAuth.act do
authorize! if needs_authorization?
end
EE::Page::Main::Banner.perform do |banner|
expect(banner).to have_secondary_read_only_banner
end
Page::Menu::Main.perform do |menu|
menu.go_to_projects
end
Page::Dashboard::Projects.perform do |dashboard|
dashboard.wait_for_project_replication(geo_project_name)
dashboard.go_to_project(geo_project_name)
end
Page::Project::Show.perform do |show|
show.wait_for_repository_replication
expect(page).to have_content 'README.md'
expect(page).to have_content 'This is Geo project!'
end
end
end
end
end
end
module QA
describe 'basic user login', :smoke do
it 'user logs in using basic credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
# TODO, since `Signed in successfully` message was removed
# this is the only way to tell if user is signed in correctly.
#
Page::Menu::Main.perform do |menu|
expect(menu).to have_personal_area
end
end
end
end
module QA
describe 'LDAP user login', :orchestrated, :ldap do
before do
Runtime::Env.user_type = 'ldap'
end
it 'user logs in using LDAP credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
# TODO, since `Signed in successfully` message was removed
# this is the only way to tell if user is signed in correctly.
#
Page::Menu::Main.perform do |menu|
expect(menu).to have_personal_area
end
end
end
end
module QA
describe 'create a new group', :orchestrated, :mattermost do
it 'creating a group with a mattermost team' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Page::Menu::Main.act { go_to_groups }
Page::Dashboard::Groups.perform do |page|
page.go_to_new_group
expect(page).to have_content(
/Create a Mattermost team for this group/
)
end
end
end
end
module QA
describe 'logging in to Mattermost', :orchestrated, :mattermost do
it 'can use gitlab oauth' do
Runtime::Browser.visit(:gitlab, Page::Main::Login) do
Page::Main::Login.act { sign_in_using_credentials }
Runtime::Browser.visit(:mattermost, Page::Mattermost::Login) do
Page::Mattermost::Login.act { sign_in_using_oauth }
Page::Mattermost::Main.perform do |page|
expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
end
end
end
end
end
end
module QA
describe 'merge request rebase' do
it 'rebases source branch of merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = "only-fast-forward"
end
Page::Menu::Side.act { go_to_settings }
Page::Project::Settings::MergeRequest.act { enable_ff_only }
merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request.project = project
merge_request.title = 'Needs rebasing'
end
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.file_name = "other.txt"
push.file_content = "New file added!"
push.branch_name = "master"
push.new_branch = false
end
merge_request.visit!
Page::MergeRequest::Show.perform do |merge_request|
expect(merge_request).to have_content('Needs rebasing')
expect(merge_request).not_to be_fast_forward_possible
expect(merge_request).not_to have_merge_button
merge_request.rebase!
expect(merge_request).to have_merge_button
expect(merge_request.fast_forward_possible?).to be_truthy
end
end
end
end
module QA
describe 'merge request squash commits' do
it 'when squash commits is marked before merge' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = "squash-before-merge"
end
merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request|
merge_request.project = project
merge_request.title = 'Squashing commits'
end
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.commit_message = 'to be squashed'
push.branch_name = merge_request.source_branch
push.new_branch = false
push.file_name = 'other.txt'
push.file_content = "Test with unicode characters ❤✓€❄"
end
merge_request.visit!
expect(page).to have_text('to be squashed')
Page::MergeRequest::Show.perform do |merge_request_page|
merge_request_page.mark_to_squash
merge_request_page.merge!
merge_request.project.visit!
Git::Repository.perform do |repository|
repository.uri = Page::Project::Show.act do
choose_repository_clone_http
repository_location.uri
end
repository.use_default_credentials
repository.act { clone }
expect(repository.commits.size).to eq 3
end
end
end
end
end
module QA
describe 'activity page' do
it 'push creates an event in the activity page' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Repository::ProjectPush.fabricate! do |push|
push.file_name = 'README.md'
push.file_content = '# This is a test project'
push.commit_message = 'Add README.md'
end
Page::Menu::Side.act { go_to_activity }
Page::Project::Activity.act { go_to_push_events }
expect(page).to have_content('pushed new branch master')
end
end
end
module QA
describe 'deploy keys support' do
it 'user adds a deploy key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
key = Runtime::Key::RSA.new
deploy_key_title = 'deploy key title'
deploy_key_value = key.public_key
deploy_key = Factory::Resource::DeployKey.fabricate! do |resource|
resource.title = deploy_key_title
resource.key = deploy_key_value
end
expect(deploy_key.fingerprint).to eq(key.fingerprint)
end
end
end
module QA
describe 'secret variables support' do
it 'user adds a secret variable' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::SecretVariable.fabricate! do |resource|
resource.key = 'VARIABLE_KEY'
resource.value = 'some secret variable'
end
Page::Project::Settings::CICD.perform do |settings|
settings.expand_secret_variables do |page|
expect(page).to have_field(with: 'VARIABLE_KEY')
expect(page).not_to have_field(with: 'some secret variable')
page.reveal_variables
expect(page).to have_field(with: 'some secret variable')
end
end
end
end
end
require 'pathname'
module QA
describe 'Auto Devops', :orchestrated, :kubernetes do
after do
@cluster&.remove!
end
it 'user creates a new project and runs auto devops' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |p|
p.name = 'project-with-autodevops'
p.description = 'Project with Auto Devops'
end
# Disable code_quality check in Auto DevOps pipeline as it takes
# too long and times out the test
Factory::Resource::SecretVariable.fabricate! do |resource|
resource.key = 'CODE_QUALITY_DISABLED'
resource.value = '1'
end
# Create Auto Devops compatible repo
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.directory = Pathname
.new(__dir__)
.join('../../../fixtures/auto_devops_rack')
push.commit_message = 'Create Auto DevOps compatible rack application'
end
Page::Project::Show.act { wait_for_push }
# Create and connect K8s cluster
@cluster = Service::KubernetesCluster.new.create!
kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster|
cluster.project = project
cluster.cluster = @cluster
cluster.install_helm_tiller = true
cluster.install_ingress = true
cluster.install_prometheus = true
cluster.install_runner = true
end
project.visit!
Page::Menu::Side.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p|
p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io")
end
project.visit!
Page::Menu::Side.act { click_ci_cd_pipelines }
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_build('build', status: :success, wait: 600)
expect(pipeline).to have_build('test', status: :success, wait: 600)
expect(pipeline).to have_build('production', status: :success, wait: 1200)
end
end
end
end
module QA
describe 'creates issue', :smoke do
let(:issue_title) { 'issue title' }
it 'user creates issue' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Issue.fabricate! do |issue|
issue.title = issue_title
end
Page::Menu::Side.act { click_issues }
expect(page).to have_content(issue_title)
end
end
end
module QA
describe 'create a new project', :smoke do
it 'user creates a new project' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
created_project = Factory::Resource::Project.fabricate! do |project|
project.name = 'awesome-project'
project.description = 'create awesome project test'
end
expect(created_project.name).to match /^awesome-project-\h{16}$/
expect(page).to have_content(
/Project \S?awesome-project\S+ was successfully created/
)
expect(page).to have_content('create awesome project test')
expect(page).to have_content('The repository for this project is empty')
end
end
end
require 'digest/sha1'
module QA
describe 'cloning code using a deploy key', :docker do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
end
before(:all) do
login
@runner_name = "qa-runner-#{Time.now.to_i}"
@project = Factory::Resource::Project.fabricate! do |resource|
resource.name = 'deploy-key-clone-project'
end
@repository_location = @project.repository_ssh_location
Factory::Resource::Runner.fabricate! do |resource|
resource.project = @project
resource.name = @runner_name
resource.tags = %w[qa docker]
resource.image = 'gitlab/gitlab-runner:ubuntu'
end
Page::Menu::Main.act { sign_out }
end
after(:all) do
Service::Runner.new(@runner_name).remove!
end
keys = [
[Runtime::Key::RSA, 8192],
[Runtime::Key::ECDSA, 521],
[Runtime::Key::ED25519]
]
keys.each do |(key_class, bits)|
it "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines" do
key = key_class.new(*bits)
login
Factory::Resource::DeployKey.fabricate! do |resource|
resource.project = @project
resource.title = "deploy key #{key.name}(#{key.bits})"
resource.key = key.public_key
end
deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}"
Factory::Resource::SecretVariable.fabricate! do |resource|
resource.project = @project
resource.key = deploy_key_name
resource.value = key.private_key
end
gitlab_ci = <<~YAML
cat-config:
script:
- mkdir -p ~/.ssh
- ssh-keyscan -p #{@repository_location.port} #{@repository_location.host} >> ~/.ssh/known_hosts
- eval $(ssh-agent -s)
- ssh-add -D
- echo "$#{deploy_key_name}" | ssh-add -
- git clone #{@repository_location.git_uri}
- cd #{@project.name}
- git checkout #{deploy_key_name}
- sha1sum .gitlab-ci.yml
tags:
- qa
- docker
YAML
Factory::Repository::ProjectPush.fabricate! do |resource|
resource.project = @project
resource.file_name = '.gitlab-ci.yml'
resource.commit_message = 'Add .gitlab-ci.yml'
resource.file_content = gitlab_ci
resource.branch_name = deploy_key_name
resource.new_branch = true
end
sha1sum = Digest::SHA1.hexdigest(gitlab_ci)
Page::Project::Show.act { wait_for_push }
Page::Menu::Side.act { click_ci_cd_pipelines }
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
Page::Project::Pipeline::Show.act { go_to_first_job }
Page::Project::Job::Show.perform do |job|
job.wait(reload: false) do
job.completed? && !job.trace_loading?
end
expect(job.passed?).to be_truthy, "Job status did not become \"passed\"."
expect(job.output).to include(sha1sum)
end
end
end
end
end
module QA
describe 'File Functionality', :core do
it 'lets a user create, edit and delete a file via WebUI' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
# Create
file_name = 'QA Test - File name'
file_content = 'QA Test - File content'
commit_message_for_create = 'QA Test - Create new file'
Factory::Resource::File.fabricate! do |file|
file.name = file_name
file.content = file_content
file.commit_message = commit_message_for_create
end
expect(page).to have_content('The file has been successfully created.')
expect(page).to have_content(file_name)
expect(page).to have_content(file_content)
expect(page).to have_content(commit_message_for_create)
# Edit
updated_file_content = 'QA Test - Updated file content'
commit_message_for_update = 'QA Test - Update file'
Page::File::Show.act { click_edit }
Page::File::Form.act do
remove_content
add_content(updated_file_content)
add_commit_message(commit_message_for_update)
commit_changes
end
expect(page).to have_content('Your changes have been successfully committed.')
expect(page).to have_content(updated_file_content)
expect(page).to have_content(commit_message_for_update)
# Delete
commit_message_for_delete = 'QA Test - Delete file'
Page::File::Show.act do
click_delete
add_commit_message(commit_message_for_delete)
click_delete_file
end
expect(page).to have_content('The file has been successfully deleted.')
expect(page).to have_content(commit_message_for_delete)
expect(page).to have_no_content(file_name)
end
end
end
module QA
describe 'Project fork' do
it 'can submit merge requests to upstream master' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
merge_request = Factory::Resource::MergeRequestFromFork.fabricate! do |merge_request|
merge_request.fork_branch = 'feature-branch'
end
Page::Menu::Main.perform { |main| main.sign_out }
Page::Main::Login.perform { |login| login.sign_in_using_credentials }
merge_request.visit!
Page::MergeRequest::Show.perform { |show| show.merge! }
expect(page).to have_content('The changes were merged')
end
end
end
module QA
describe 'user imports a GitHub repo', :orchestrated, :github do
let(:imported_project) do
Factory::Resource::ProjectImportedFromGithub.fabricate! do |project|
project.name = 'imported-project'
project.personal_access_token = Runtime::Env.github_access_token
project.github_repository_path = 'gitlab-qa/test-project'
end
end
after do
# We need to delete the imported project because it's impossible to import
# the same GitHub project twice for a given user.
api_client = Runtime::API::Client.new(:gitlab)
delete_project_request = Runtime::API::Request.new(api_client, "/projects/#{CGI.escape("#{Runtime::Namespace.path}/#{imported_project.name}")}")
delete delete_project_request.url
expect_status(202)
end
it 'user imports a GitHub repo' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
imported_project # import the project
Page::Menu::Main.act { go_to_projects }
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(imported_project.name)
end
Page::Project::Show.act { wait_for_import }
verify_repository_import
verify_issues_import
verify_merge_requests_import
verify_labels_import
verify_milestones_import
verify_wiki_import
end
def verify_repository_import
expect(page).to have_content('This test project is used for automated GitHub import by GitLab QA.')
expect(page).to have_content(imported_project.name)
end
def verify_issues_import
Page::Menu::Side.act { click_issues }
expect(page).to have_content('This is a sample issue')
click_link 'This is a sample issue'
expect(page).to have_content('We should populate this project with issues, pull requests and wiki pages.')
# Comments
expect(page).to have_content('This is a comment from @rymai.')
Page::Issuable::Sidebar.perform do |issuable|
expect(issuable).to have_label('enhancement')
expect(issuable).to have_label('help wanted')
expect(issuable).to have_label('good first issue')
end
end
def verify_merge_requests_import
Page::Menu::Side.act { click_merge_requests }
expect(page).to have_content('Improve README.md')
click_link 'Improve README.md'
expect(page).to have_content('This improves the README file a bit.')
# Review comment are not supported yet
expect(page).not_to have_content('Really nice change.')
# Comments
expect(page).to have_content('Nice work! This is a comment from @rymai.')
# Diff comments
expect(page).to have_content('[Review comment] I like that!')
expect(page).to have_content('[Review comment] Nice blank line.')
expect(page).to have_content('[Single diff comment] Much better without this line!')
Page::Issuable::Sidebar.perform do |issuable|
expect(issuable).to have_label('bug')
expect(issuable).to have_label('enhancement')
end
end
def verify_labels_import
# TODO: Waiting on https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19228
# to build upon it.
end
def verify_milestones_import
# TODO: Waiting on https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18727
# to build upon it.
end
def verify_wiki_import
Page::Menu::Side.act { click_wiki }
expect(page).to have_content('Welcome to the test-project wiki!')
end
end
end
module QA
describe 'CI/CD Pipelines', :orchestrated, :docker do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
after do
Service::Runner.new(executor).remove!
end
it 'user registers a new specific runner' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
Factory::Resource::Runner.fabricate! do |runner|
runner.name = executor
end
Page::Project::Settings::CICD.perform do |settings|
sleep 5 # Runner should register within 5 seconds
settings.refresh
settings.expand_runners_settings do |page|
expect(page).to have_content(executor)
expect(page).to have_online_runner
end
end
end
it 'users creates a new pipeline' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
project = Factory::Resource::Project.fabricate! do |project|
project.name = 'project-with-pipelines'
project.description = 'Project with CI/CD Pipelines.'
end
Factory::Resource::Runner.fabricate! do |runner|
runner.project = project
runner.name = executor
runner.tags = %w[qa test]
end
Factory::Repository::ProjectPush.fabricate! do |push|
push.project = project
push.file_name = '.gitlab-ci.yml'
push.commit_message = 'Add .gitlab-ci.yml'
push.file_content = <<~EOF
test-success:
tags:
- qa
- test
script: echo 'OK'
test-failure:
tags:
- qa
- test
script:
- echo 'FAILURE'
- exit 1
test-tags:
tags:
- qa
- docker
script: echo 'NOOP'
test-artifacts:
tags:
- qa
- test
script: mkdir my-artifacts; echo "CONTENTS" > my-artifacts/artifact.txt
artifacts:
paths:
- my-artifacts/
EOF
end
Page::Project::Show.act { wait_for_push }
expect(page).to have_content('Add .gitlab-ci.yml')
Page::Menu::Side.act { click_ci_cd_pipelines }
expect(page).to have_content('All 1')
expect(page).to have_content('Add .gitlab-ci.yml')
puts 'Waiting for the runner to process the pipeline'
sleep 15 # Runner should process all jobs within 15 seconds.
Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to be_running
expect(pipeline).to have_build('test-success', status: :success)
expect(pipeline).to have_build('test-failure', status: :failed)
expect(pipeline).to have_build('test-tags', status: :pending)
expect(pipeline).to have_build('test-artifacts', status: :success)
end
end
end
end
module QA
describe 'Wiki Functionality' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
end
def validate_content(content)
expect(page).to have_content('Wiki was successfully updated')
expect(page).to have_content(/#{content}/)
end
before do
login
end
it 'User creates, edits, clones, and pushes to the wiki' do
wiki = Factory::Resource::Wiki.fabricate! do |resource|
resource.title = 'Home'
resource.content = '# My First Wiki Content'
resource.message = 'Update home'
end
validate_content('My First Wiki Content')
Page::Project::Wiki::Edit.act { go_to_edit_page }
Page::Project::Wiki::New.perform do |page|
page.set_content("My Second Wiki Content")
page.save_changes
end
validate_content('My Second Wiki Content')
Factory::Repository::WikiPush.fabricate! do |push|
push.wiki = wiki
push.file_name = 'Home.md'
push.file_content = '# My Third Wiki Content'
push.commit_message = 'Update Home.md'
end
Page::Menu::Side.act { click_wiki }
expect(page).to have_content('My Third Wiki Content')
end
end
end
module QA
describe 'branch protection support' do
let(:branch_name) { 'protected-branch' }
let(:commit_message) { 'Protected push commit message' }
let(:project) do
Factory::Resource::Project.fabricate! do |resource|
resource.name = 'protected-branch-project'
end
end
before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
end
after do
# We need to clear localStorage because we're using it for the dropdown,
# and capybara doesn't do this for us.
# https://github.com/teamcapybara/capybara/issues/1702
Capybara.execute_script 'localStorage.clear()'
end
context 'when developers and maintainers are allowed to push to a protected branch' do
it 'user with push rights successfully pushes to the protected branch' do
create_protected_branch(allow_to_push: true)
push = push_new_file(branch_name)
expect(push.output).to match(/remote: To create a merge request for protected-branch, visit/)
end
end
context 'when developers and maintainers are not allowed to push to a protected branch' do
it 'user without push rights fails to push to the protected branch' do
create_protected_branch(allow_to_push: false)
push = push_new_file(branch_name)
expect(push.output)
.to match(/remote\: GitLab\: You are not allowed to push code to protected branches on this project/)
expect(push.output)
.to match(/\[remote rejected\] #{branch_name} -> #{branch_name} \(pre-receive hook declined\)/)
end
end
def create_protected_branch(allow_to_push:)
Factory::Resource::Branch.fabricate! do |resource|
resource.branch_name = branch_name
resource.project = project
resource.allow_to_push = allow_to_push
resource.protected = true
end
end
def push_new_file(branch)
Factory::Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.file_name = 'new_file.md'
resource.file_content = '# This is a new file'
resource.commit_message = 'Add new_file.md'
resource.branch_name = branch_name
resource.new_branch = false
end
end
end
end
...@@ -104,6 +104,8 @@ describe QA::Runtime::Env do ...@@ -104,6 +104,8 @@ describe QA::Runtime::Env do
describe '.github_access_token' do describe '.github_access_token' do
it 'returns "" if GITHUB_ACCESS_TOKEN is not defined' do it 'returns "" if GITHUB_ACCESS_TOKEN is not defined' do
stub_env('GITHUB_ACCESS_TOKEN', nil)
expect(described_class.github_access_token).to eq('') expect(described_class.github_access_token).to eq('')
end end
...@@ -115,6 +117,8 @@ describe QA::Runtime::Env do ...@@ -115,6 +117,8 @@ describe QA::Runtime::Env do
describe '.require_github_access_token!' do describe '.require_github_access_token!' do
it 'raises ArgumentError if GITHUB_ACCESS_TOKEN is not defined' do it 'raises ArgumentError if GITHUB_ACCESS_TOKEN is not defined' do
stub_env('GITHUB_ACCESS_TOKEN', nil)
expect { described_class.require_github_access_token! }.to raise_error(ArgumentError) expect { described_class.require_github_access_token! }.to raise_error(ArgumentError)
end end
......
describe QA::Scenario::Test::Instance::All do describe QA::Scenario::Test::Instance::All do
subject do
Class.new(described_class) do
tags :rspec, :foo
end
end
context '#perform' do context '#perform' do
let(:arguments) { spy('Runtime::Scenario') } let(:arguments) { spy('Runtime::Scenario') }
let(:release) { spy('Runtime::Release') } let(:release) { spy('Runtime::Release') }
...@@ -24,7 +30,7 @@ describe QA::Scenario::Test::Instance::All do ...@@ -24,7 +30,7 @@ describe QA::Scenario::Test::Instance::All do
subject.perform("test") subject.perform("test")
expect(runner).to have_received(:options=) expect(runner).to have_received(:options=)
.with(::File.expand_path('../../../../qa/specs/features', __dir__)) .with(['--tag', 'rspec,foo', '--', ::File.expand_path('../../../../qa/specs/features', __dir__)])
end end
end end
......
...@@ -30,7 +30,7 @@ describe QA::Scenario::Test::Instance::Smoke do ...@@ -30,7 +30,7 @@ describe QA::Scenario::Test::Instance::Smoke do
subject.perform("test") subject.perform("test")
expect(runner).to have_received(:options=) expect(runner).to have_received(:options=)
.with(::File.expand_path('../../../../qa/specs/features', __dir__)) .with(['--tag', 'smoke', '--', ::File.expand_path('../../../../qa/specs/features', __dir__)])
end end
end end
......
...@@ -103,11 +103,11 @@ describe Admin::HooksController, "routing" do ...@@ -103,11 +103,11 @@ describe Admin::HooksController, "routing" do
end end
end end
# admin_hook_hook_log_retry GET /admin/hooks/:hook_id/hook_logs/:id/retry(.:format) admin/hook_logs#retry # admin_hook_hook_log_retry POST /admin/hooks/:hook_id/hook_logs/:id/retry(.:format) admin/hook_logs#retry
# admin_hook_hook_log GET /admin/hooks/:hook_id/hook_logs/:id(.:format) admin/hook_logs#show # admin_hook_hook_log GET /admin/hooks/:hook_id/hook_logs/:id(.:format) admin/hook_logs#show
describe Admin::HookLogsController, 'routing' do describe Admin::HookLogsController, 'routing' do
it 'to #retry' do it 'to #retry' do
expect(get('/admin/hooks/1/hook_logs/1/retry')).to route_to('admin/hook_logs#retry', hook_id: '1', id: '1') expect(post('/admin/hooks/1/hook_logs/1/retry')).to route_to('admin/hook_logs#retry', hook_id: '1', id: '1')
end end
it 'to #show' do it 'to #show' do
......
...@@ -381,7 +381,7 @@ describe 'project routing' do ...@@ -381,7 +381,7 @@ describe 'project routing' do
end end
end end
# test_project_hook GET /:project_id/hooks/:id/test(.:format) hooks#test # test_project_hook POST /:project_id/hooks/:id/test(.:format) hooks#test
# project_hooks GET /:project_id/hooks(.:format) hooks#index # project_hooks GET /:project_id/hooks(.:format) hooks#index
# POST /:project_id/hooks(.:format) hooks#create # POST /:project_id/hooks(.:format) hooks#create
# edit_project_hook GET /:project_id/hooks/:id/edit(.:format) hooks#edit # edit_project_hook GET /:project_id/hooks/:id/edit(.:format) hooks#edit
...@@ -398,11 +398,11 @@ describe 'project routing' do ...@@ -398,11 +398,11 @@ describe 'project routing' do
end end
end end
# retry_namespace_project_hook_hook_log GET /:project_id/hooks/:hook_id/hook_logs/:id/retry(.:format) projects/hook_logs#retry # retry_namespace_project_hook_hook_log POST /:project_id/hooks/:hook_id/hook_logs/:id/retry(.:format) projects/hook_logs#retry
# namespace_project_hook_hook_log GET /:project_id/hooks/:hook_id/hook_logs/:id(.:format) projects/hook_logs#show # namespace_project_hook_hook_log GET /:project_id/hooks/:hook_id/hook_logs/:id(.:format) projects/hook_logs#show
describe Projects::HookLogsController, 'routing' do describe Projects::HookLogsController, 'routing' do
it 'to #retry' do it 'to #retry' do
expect(get('/gitlab/gitlabhq/hooks/1/hook_logs/1/retry')).to route_to('projects/hook_logs#retry', namespace_id: 'gitlab', project_id: 'gitlabhq', hook_id: '1', id: '1') expect(post('/gitlab/gitlabhq/hooks/1/hook_logs/1/retry')).to route_to('projects/hook_logs#retry', namespace_id: 'gitlab', project_id: 'gitlabhq', hook_id: '1', id: '1')
end end
it 'to #show' do it 'to #show' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment