summaryrefslogtreecommitdiff
path: root/qa
diff options
context:
space:
mode:
authorLin Jen-Shin <godfat@godfat.org>2018-01-24 23:13:56 +0800
committerLin Jen-Shin <godfat@godfat.org>2018-01-24 23:13:56 +0800
commitada39be67aa3805f0c2f45d51f47b478735836d4 (patch)
tree207f11fccc49b2f9c5687b3d582322e672a40fd4 /qa
parent4b1da8502f4625116447014b18adc7e1986e038e (diff)
parent9f5390d81d63ae4186554aced85094a4b08e1aff (diff)
downloadgitlab-ce-ada39be67aa3805f0c2f45d51f47b478735836d4.tar.gz
Merge remote-tracking branch 'upstream/master' into qa-deploy-key-scenario
* upstream/master: (106 commits) Remove callback as we already update accordingly on services added missing imports correctly imports performance bar Added imports for dispatcher routes Make the exposing of the Application secret more explicit Add documentation about when the application API was added Add application create API Return a blank JSON response for a missing .js file to prevent Rails CSRF errors add CHANGELOG.md entry for !15804 disable CopyAsGFM on iOS due to bug in webkit Update changelog GitalyClient::ConflictsService#conflicts? should return true for conflicts with missing side Return more consistent values for merge_status on MR API Fix a migration spec messing up the MergeRequestDiff DB schema Backport changes to Gitlab::Checks::ChangeAccess from EE remove webpack bundle tag for monitoring:environments:metrics Set timezone for karma to UTC Upgrade jasmine + raven to newer versions Ensure Gitaly Ruby gems are installed using the correct Gemfile and at the correct location Clarify that a feature that isn't in review by the 1st or 3rd doesn't necessarily miss the freeze ...
Diffstat (limited to 'qa')
-rw-r--r--qa/Gemfile1
-rw-r--r--qa/Gemfile.lock36
-rw-r--r--qa/qa.rb8
-rw-r--r--qa/qa/factory/resource/personal_access_token.rb27
-rw-r--r--qa/qa/page/README.md12
-rw-r--r--qa/qa/page/menu/main.rb9
-rw-r--r--qa/qa/page/menu/profile.rb27
-rw-r--r--qa/qa/page/profile/personal_access_tokens.rb33
-rw-r--r--qa/qa/runtime/address.rb20
-rw-r--r--qa/qa/runtime/api.rb82
-rw-r--r--qa/qa/runtime/browser.rb19
-rw-r--r--qa/qa/runtime/env.rb6
-rw-r--r--qa/qa/specs/features/api/users_spec.rb42
-rw-r--r--qa/spec/runtime/api_client_spec.rb30
-rw-r--r--qa/spec/runtime/api_request_spec.rb42
-rw-r--r--qa/spec/runtime/env_spec.rb8
-rw-r--r--qa/spec/spec_helper.rb2
-rw-r--r--qa/spec/support/stub_env.rb38
18 files changed, 419 insertions, 23 deletions
diff --git a/qa/Gemfile b/qa/Gemfile
index 1cc40f2d2d0..c3e61568f3d 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -7,3 +7,4 @@ gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.8.0'
gem 'net-ssh', require: false
+gem 'airborne', '~> 0.2.13'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 7aa442b8c8e..51d2e4d7a10 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -1,8 +1,19 @@
GEM
remote: https://rubygems.org/
specs:
+ activesupport (5.1.4)
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ i18n (~> 0.7)
+ minitest (~> 5.1)
+ tzinfo (~> 1.1)
addressable (2.5.2)
public_suffix (>= 2.0.2, < 4.0)
+ airborne (0.2.13)
+ activesupport
+ rack
+ rack-test (~> 0.6, >= 0.6.2)
+ rest-client (>= 1.7.3, < 3.0)
+ rspec (~> 3.1)
byebug (9.1.0)
capybara (2.16.1)
addressable
@@ -17,14 +28,26 @@ GEM
childprocess (0.8.0)
ffi (~> 1.0, >= 1.0.11)
coderay (1.1.2)
+ concurrent-ruby (1.0.5)
diff-lcs (1.3)
+ domain_name (0.5.20170404)
+ unf (>= 0.0.5, < 1.0.0)
ffi (1.9.18)
+ http-cookie (1.0.3)
+ domain_name (~> 0.5)
+ i18n (0.9.1)
+ concurrent-ruby (~> 1.0)
launchy (2.4.3)
addressable (~> 2.3)
method_source (0.9.0)
+ mime-types (3.1)
+ mime-types-data (~> 3.2015)
+ mime-types-data (3.2016.0521)
mini_mime (1.0.0)
mini_portile2 (2.3.0)
+ minitest (5.11.1)
net-ssh (4.1.0)
+ netrc (0.11.0)
nokogiri (1.8.1)
mini_portile2 (~> 2.3.0)
pry (0.11.3)
@@ -38,11 +61,15 @@ GEM
rack-test (0.8.2)
rack (>= 1.0, < 3)
rake (12.3.0)
+ rest-client (2.0.2)
+ http-cookie (>= 1.0.2, < 2.0)
+ mime-types (>= 1.16, < 4.0)
+ netrc (~> 0.8)
rspec (3.7.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
- rspec-core (3.7.0)
+ rspec-core (3.7.1)
rspec-support (~> 3.7.0)
rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
@@ -55,6 +82,12 @@ GEM
selenium-webdriver (3.8.0)
childprocess (~> 0.5)
rubyzip (~> 1.0)
+ thread_safe (0.3.6)
+ tzinfo (1.2.4)
+ thread_safe (~> 0.1)
+ unf (0.1.4)
+ unf_ext
+ unf_ext (0.0.7.4)
xpath (2.1.0)
nokogiri (~> 1.3)
@@ -62,6 +95,7 @@ PLATFORMS
ruby
DEPENDENCIES
+ airborne (~> 0.2.13)
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
net-ssh
diff --git a/qa/qa.rb b/qa/qa.rb
index b8821f2c164..d2777345e31 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -12,6 +12,8 @@ module QA
autoload :Browser, 'qa/runtime/browser'
autoload :Env, 'qa/runtime/env'
autoload :RSAKey, 'qa/runtime/rsa_key'
+ autoload :Address, 'qa/runtime/address'
+ autoload :API, 'qa/runtime/api'
end
##
@@ -27,6 +29,7 @@ module QA
autoload :Group, 'qa/factory/resource/group'
autoload :Project, 'qa/factory/resource/project'
autoload :DeployKey, 'qa/factory/resource/deploy_key'
+ autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
end
module Repository
@@ -86,6 +89,7 @@ module QA
autoload :Main, 'qa/page/menu/main'
autoload :Side, 'qa/page/menu/side'
autoload :Admin, 'qa/page/menu/admin'
+ autoload :Profile, 'qa/page/menu/profile'
end
module Dashboard
@@ -109,6 +113,10 @@ module QA
end
end
+ module Profile
+ autoload :PersonalAccessTokens, 'qa/page/profile/personal_access_tokens'
+ end
+
module Admin
autoload :Settings, 'qa/page/admin/settings'
end
diff --git a/qa/qa/factory/resource/personal_access_token.rb b/qa/qa/factory/resource/personal_access_token.rb
new file mode 100644
index 00000000000..514e3615d18
--- /dev/null
+++ b/qa/qa/factory/resource/personal_access_token.rb
@@ -0,0 +1,27 @@
+module QA
+ module Factory
+ module Resource
+ ##
+ # Create a personal access token that can be used by the api
+ #
+ class PersonalAccessToken < Factory::Base
+ attr_accessor :name
+
+ product :access_token do
+ Page::Profile::PersonalAccessTokens.act { created_access_token }
+ end
+
+ def fabricate!
+ Page::Menu::Main.act { go_to_profile_settings }
+ Page::Menu::Profile.act { click_access_tokens }
+
+ Page::Profile::PersonalAccessTokens.perform do |page|
+ page.fill_token_name(name || 'api-test-token')
+ page.check_api
+ page.create_token
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/README.md b/qa/qa/page/README.md
index f72fbfeafca..83710606d7c 100644
--- a/qa/qa/page/README.md
+++ b/qa/qa/page/README.md
@@ -77,7 +77,7 @@ module Page
view 'app/views/devise/sessions/_new_base.html.haml' do
element :login_field, 'text_field :login'
- element :passowrd_field, 'password_field :password'
+ element :password_field, 'password_field :password'
element :sign_in_button, 'submit "Sign in"'
end
@@ -103,6 +103,16 @@ view 'app/views/my/view.html.haml' do
end
```
+## Running the test locally
+
+During development, you can run the `qa:selectors` test by running
+
+```shell
+bin/qa Test::Sanity::Selectors
+```
+
+from within the `qa` directory.
+
## Where to ask for help?
If you need more information, ask for help on `#qa` channel on Slack (GitLab
diff --git a/qa/qa/page/menu/main.rb b/qa/qa/page/menu/main.rb
index f8978b8a5f7..df93a5fa2d2 100644
--- a/qa/qa/page/menu/main.rb
+++ b/qa/qa/page/menu/main.rb
@@ -7,6 +7,7 @@ module QA
element :user_avatar
element :user_menu, '.dropdown-menu-nav'
element :user_sign_out_link, 'link_to "Sign out"'
+ element :settings_link, 'link_to "Settings"'
end
view 'app/views/layouts/nav/_dashboard.html.haml' do
@@ -40,7 +41,13 @@ module QA
def sign_out
within_user_menu do
- click_link('Sign out')
+ click_link 'Sign out'
+ end
+ end
+
+ def go_to_profile_settings
+ within_user_menu do
+ click_link 'Settings'
end
end
diff --git a/qa/qa/page/menu/profile.rb b/qa/qa/page/menu/profile.rb
new file mode 100644
index 00000000000..95e88d863e4
--- /dev/null
+++ b/qa/qa/page/menu/profile.rb
@@ -0,0 +1,27 @@
+module QA
+ module Page
+ module Menu
+ class Profile < Page::Base
+ view 'app/views/layouts/nav/sidebar/_profile.html.haml' do
+ element :access_token_link, 'link_to profile_personal_access_tokens_path'
+ element :access_token_title, 'Access Tokens'
+ element :top_level_items, '.sidebar-top-level-items'
+ end
+
+ def click_access_tokens
+ within_sidebar do
+ click_link('Access Tokens')
+ end
+ end
+
+ private
+
+ def within_sidebar
+ page.within('.sidebar-top-level-items') do
+ yield
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb
new file mode 100644
index 00000000000..f5ae47dadd0
--- /dev/null
+++ b/qa/qa/page/profile/personal_access_tokens.rb
@@ -0,0 +1,33 @@
+module QA
+ module Page
+ module Profile
+ class PersonalAccessTokens < Page::Base
+ view 'app/views/shared/_personal_access_tokens_form.html.haml' do
+ element :personal_access_token_name_field, 'text_field :name'
+ element :create_token_button, 'submit "Create #{type} token"' # rubocop:disable Lint/InterpolationCheck
+ element :scopes_api_radios, "label :scopes"
+ end
+
+ view 'app/views/profiles/personal_access_tokens/index.html.haml' do
+ element :create_token_field, "text_field_tag 'created-personal-access-token'"
+ end
+
+ def fill_token_name(name)
+ fill_in 'personal_access_token_name', with: name
+ end
+
+ def check_api
+ check 'personal_access_token_scopes_api'
+ end
+
+ def create_token
+ click_on 'Create personal access token'
+ end
+
+ def created_access_token
+ page.find('#created-personal-access-token').value
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/address.rb b/qa/qa/runtime/address.rb
new file mode 100644
index 00000000000..ffad3974b02
--- /dev/null
+++ b/qa/qa/runtime/address.rb
@@ -0,0 +1,20 @@
+module QA
+ module Runtime
+ class Address
+ attr_reader :address
+
+ def initialize(instance, page = nil)
+ @instance = instance
+ @address = host + (page.is_a?(String) ? page : page&.path)
+ end
+
+ def host
+ if @instance.is_a?(Symbol)
+ Runtime::Scenario.send("#{@instance}_address")
+ else
+ @instance.to_s
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/api.rb b/qa/qa/runtime/api.rb
new file mode 100644
index 00000000000..e2a096b971d
--- /dev/null
+++ b/qa/qa/runtime/api.rb
@@ -0,0 +1,82 @@
+require 'airborne'
+
+module QA
+ module Runtime
+ module API
+ class Client
+ attr_reader :address
+
+ def initialize(address = :gitlab)
+ @address = address
+ end
+
+ def personal_access_token
+ @personal_access_token ||= get_personal_access_token
+ end
+
+ def get_personal_access_token
+ # you can set the environment variable PERSONAL_ACCESS_TOKEN
+ # to use a specific access token rather than create one from the UI
+ if Runtime::Env.personal_access_token
+ Runtime::Env.personal_access_token
+ else
+ create_personal_access_token
+ end
+ end
+
+ private
+
+ def create_personal_access_token
+ Runtime::Browser.visit(@address, Page::Main::Login) do
+ Page::Main::Login.act { sign_in_using_credentials }
+ Factory::Resource::PersonalAccessToken.fabricate!.access_token
+ end
+ end
+ end
+
+ class Request
+ API_VERSION = 'v4'.freeze
+
+ def initialize(api_client, path, personal_access_token: nil)
+ personal_access_token ||= api_client.personal_access_token
+ request_path = request_path(path, personal_access_token: personal_access_token)
+ @session_address = Runtime::Address.new(api_client.address, request_path)
+ end
+
+ def url
+ @session_address.address
+ end
+
+ # Prepend a request path with the path to the API
+ #
+ # path - Path to append
+ #
+ # Examples
+ #
+ # >> request_path('/issues')
+ # => "/api/v4/issues"
+ #
+ # >> request_path('/issues', personal_access_token: 'sometoken)
+ # => "/api/v4/issues?private_token=..."
+ #
+ # Returns the relative path to the requested API resource
+ def request_path(path, version: API_VERSION, personal_access_token: nil, oauth_access_token: nil)
+ full_path = File.join('/api', version, path)
+
+ if oauth_access_token
+ query_string = "access_token=#{oauth_access_token}"
+ elsif personal_access_token
+ query_string = "private_token=#{personal_access_token}"
+ end
+
+ if query_string
+ full_path << (path.include?('?') ? '&' : '?')
+ full_path << query_string
+ end
+
+ full_path
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 14b2a488760..7b1be3d5ef3 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -24,9 +24,7 @@ module QA
# based on `Runtime::Scenario#something_address`.
#
def visit(address, page, &block)
- Browser::Session.new(address, page).tap do |session|
- session.perform(&block)
- end
+ Browser::Session.new(address, page).perform(&block)
end
def self.visit(address, page, &block)
@@ -94,20 +92,15 @@ module QA
include Capybara::DSL
def initialize(instance, page = nil)
- @instance = instance
- @address = host + page&.path
+ @session_address = Runtime::Address.new(instance, page)
end
- def host
- if @instance.is_a?(Symbol)
- Runtime::Scenario.send("#{@instance}_address")
- else
- @instance.to_s
- end
+ def url
+ @session_address.address
end
def perform(&block)
- visit(@address)
+ visit(url)
yield if block_given?
rescue
@@ -130,7 +123,7 @@ module QA
# See gitlab-org/gitlab-qa#102
#
def clear!
- visit(@address)
+ visit(url)
reset_session!
end
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index d5c28e9a7db..56944e8b641 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -3,6 +3,7 @@ module QA
module Env
extend self
+ # set to 'false' to have Chrome run visibly instead of headless
def chrome_headless?
(ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i) != 0
end
@@ -10,6 +11,11 @@ module QA
def running_in_ci?
ENV['CI'] || ENV['CI_SERVER']
end
+
+ # specifies token that can be used for the api
+ def personal_access_token
+ ENV['PERSONAL_ACCESS_TOKEN']
+ end
end
end
end
diff --git a/qa/qa/specs/features/api/users_spec.rb b/qa/qa/specs/features/api/users_spec.rb
new file mode 100644
index 00000000000..9d039590a0e
--- /dev/null
+++ b/qa/qa/specs/features/api/users_spec.rb
@@ -0,0 +1,42 @@
+module QA
+ feature 'API users', :core do
+ before(:context) do
+ @api_client = Runtime::API::Client.new(:gitlab)
+ end
+
+ context 'when authenticated' do
+ let(:request) { Runtime::API::Request.new(@api_client, '/users') }
+
+ scenario 'get list of users' do
+ get request.url
+
+ expect_status(200)
+ end
+
+ scenario 'submit request with a valid user name' do
+ get request.url, { params: { username: 'root' } }
+
+ expect_status(200)
+ expect(json_body).to be_an Array
+ expect(json_body.size).to eq(1)
+ expect(json_body.first[:username]).to eq Runtime::User.name
+ end
+
+ scenario 'submit request with an invalid user name' do
+ get request.url, { params: { username: 'invalid' } }
+
+ expect_status(200)
+ expect(json_body).to be_an Array
+ expect(json_body.size).to eq(0)
+ end
+ end
+
+ scenario 'submit request with an invalid token' do
+ request = Runtime::API::Request.new(@api_client, '/users', personal_access_token: 'invalid')
+
+ get request.url
+
+ expect_status(401)
+ end
+ end
+end
diff --git a/qa/spec/runtime/api_client_spec.rb b/qa/spec/runtime/api_client_spec.rb
new file mode 100644
index 00000000000..d497d8839b8
--- /dev/null
+++ b/qa/spec/runtime/api_client_spec.rb
@@ -0,0 +1,30 @@
+describe QA::Runtime::API::Client do
+ include Support::StubENV
+
+ describe 'initialization' do
+ it 'defaults to :gitlab address' do
+ expect(described_class.new.address).to eq :gitlab
+ end
+
+ it 'uses specified address' do
+ client = described_class.new('http:///example.com')
+
+ expect(client.address).to eq 'http:///example.com'
+ end
+ end
+
+ describe '#get_personal_access_token' do
+ it 'returns specified token from env' do
+ stub_env('PERSONAL_ACCESS_TOKEN', 'a_token')
+
+ expect(described_class.new.get_personal_access_token).to eq 'a_token'
+ end
+
+ it 'returns a created token' do
+ allow_any_instance_of(described_class)
+ .to receive(:create_personal_access_token).and_return('created_token')
+
+ expect(described_class.new.get_personal_access_token).to eq 'created_token'
+ end
+ end
+end
diff --git a/qa/spec/runtime/api_request_spec.rb b/qa/spec/runtime/api_request_spec.rb
new file mode 100644
index 00000000000..9a1ed8a7a46
--- /dev/null
+++ b/qa/spec/runtime/api_request_spec.rb
@@ -0,0 +1,42 @@
+describe QA::Runtime::API::Request do
+ include Support::StubENV
+
+ before do
+ stub_env('PERSONAL_ACCESS_TOKEN', 'a_token')
+ end
+
+ let(:client) { QA::Runtime::API::Client.new('http://example.com') }
+ let(:request) { described_class.new(client, '/users') }
+
+ describe '#url' do
+ it 'returns the full api request url' do
+ expect(request.url).to eq 'http://example.com/api/v4/users?private_token=a_token'
+ end
+ end
+
+ describe '#request_path' do
+ it 'prepends the api path' do
+ expect(request.request_path('/users')).to eq '/api/v4/users'
+ end
+
+ it 'adds the personal access token' do
+ expect(request.request_path('/users', personal_access_token: 'token'))
+ .to eq '/api/v4/users?private_token=token'
+ end
+
+ it 'adds the oauth access token' do
+ expect(request.request_path('/users', oauth_access_token: 'otoken'))
+ .to eq '/api/v4/users?access_token=otoken'
+ end
+
+ it 'respects query parameters' do
+ expect(request.request_path('/users?page=1')).to eq '/api/v4/users?page=1'
+ expect(request.request_path('/users?page=1', personal_access_token: 'token'))
+ .to eq '/api/v4/users?page=1&private_token=token'
+ end
+
+ it 'uses a different api version' do
+ expect(request.request_path('/users', version: 'v3')).to eq '/api/v3/users'
+ end
+ end
+end
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index 57a72a04507..103573db6be 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -1,7 +1,5 @@
describe QA::Runtime::Env do
- before do
- allow(ENV).to receive(:[]).and_call_original
- end
+ include Support::StubENV
describe '.chrome_headless?' do
context 'when there is an env variable set' do
@@ -57,8 +55,4 @@ describe QA::Runtime::Env do
end
end
end
-
- def stub_env(name, value)
- allow(ENV).to receive(:[]).with(name).and_return(value)
- end
end
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 64d06ef6558..c2c6cf95406 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -1,5 +1,7 @@
require_relative '../qa'
+Dir[File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
+
RSpec.configure do |config|
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
diff --git a/qa/spec/support/stub_env.rb b/qa/spec/support/stub_env.rb
new file mode 100644
index 00000000000..bc8f3a5e22e
--- /dev/null
+++ b/qa/spec/support/stub_env.rb
@@ -0,0 +1,38 @@
+# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
+module Support
+ module StubENV
+ def stub_env(key_or_hash, value = nil)
+ init_stub unless env_stubbed?
+
+ if key_or_hash.is_a? Hash
+ key_or_hash.each { |k, v| add_stubbed_value(k, v) }
+ else
+ add_stubbed_value key_or_hash, value
+ end
+ end
+
+ private
+
+ STUBBED_KEY = '__STUBBED__'.freeze
+
+ def add_stubbed_value(key, value)
+ allow(ENV).to receive(:[]).with(key).and_return(value)
+ allow(ENV).to receive(:key?).with(key).and_return(true)
+ allow(ENV).to receive(:fetch).with(key).and_return(value)
+ allow(ENV).to receive(:fetch).with(key, anything()) do |_, default_val|
+ value || default_val
+ end
+ end
+
+ def env_stubbed?
+ ENV[STUBBED_KEY]
+ end
+
+ def init_stub
+ allow(ENV).to receive(:[]).and_call_original
+ allow(ENV).to receive(:key?).and_call_original
+ allow(ENV).to receive(:fetch).and_call_original
+ add_stubbed_value(STUBBED_KEY, true)
+ end
+ end
+end