Skip to content
Snippets Groups Projects
Commit b8b9e9eb authored by Brett Walker's avatar Brett Walker Committed by Grzegorz Bizon
Browse files

Add ability to drive the API in QA specs

parent fcfda3ef
No related branches found
No related tags found
No related merge requests found
Showing
with 420 additions and 24 deletions
Loading
Loading
@@ -6,3 +6,4 @@ gem 'capybara-screenshot', '~> 1.0.18'
gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.8.0'
gem 'airborne', '~> 0.2.13'
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
Loading
Loading
@@ -17,13 +28,25 @@ 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)
netrc (0.11.0)
nokogiri (1.8.1)
mini_portile2 (~> 2.3.0)
pry (0.11.3)
Loading
Loading
@@ -37,11 +60,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)
Loading
Loading
@@ -54,6 +81,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)
 
Loading
Loading
@@ -61,6 +94,7 @@ PLATFORMS
ruby
 
DEPENDENCIES
airborne (~> 0.2.13)
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
pry-byebug (~> 3.5.1)
Loading
Loading
@@ -69,4 +103,4 @@ DEPENDENCIES
selenium-webdriver (~> 3.8.0)
 
BUNDLED WITH
1.16.0
1.16.1
Loading
Loading
@@ -11,6 +11,8 @@ module QA
autoload :Scenario, 'qa/runtime/scenario'
autoload :Browser, 'qa/runtime/browser'
autoload :Env, 'qa/runtime/env'
autoload :Address, 'qa/runtime/address'
autoload :API, 'qa/runtime/api'
end
 
##
Loading
Loading
@@ -26,6 +28,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
Loading
Loading
@@ -85,6 +88,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
Loading
Loading
@@ -108,6 +112,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
Loading
Loading
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
Loading
Loading
@@ -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
 
Loading
Loading
@@ -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
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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
 
Loading
Loading
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
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
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
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
Loading
Loading
@@ -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)
Loading
Loading
@@ -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
Loading
Loading
@@ -130,7 +123,7 @@ module QA
# See gitlab-org/gitlab-qa#102
#
def clear!
visit(@address)
visit(url)
reset_session!
end
end
Loading
Loading
Loading
Loading
@@ -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
Loading
Loading
@@ -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
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
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
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
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
Loading
Loading
@@ -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
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
Loading
Loading
# 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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment