Commit caab4e03 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-02-15

# Conflicts:
#	app/assets/javascripts/vue_merge_request_widget/dependencies.js
#	config/webpack.config.js
#	doc/api/namespaces.md
#	lib/gitlab/git_access.rb
#	lib/gitlab/ldap/config.rb

[ci skip]
parents 40ae4b80 62a60045
...@@ -175,7 +175,7 @@ Assigning a team label makes sure issues get the attention of the appropriate ...@@ -175,7 +175,7 @@ Assigning a team label makes sure issues get the attention of the appropriate
people. people.
The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge, The current team labels are ~Build, ~"CI/CD", ~Discussion, ~Documentation, ~Edge,
~Geo, ~Gitaly, ~Platform, ~Monitoring, ~Release, and ~"UX". ~Geo, ~Gitaly, ~Monitoring, ~Platform, ~Release, ~"Security Products" and ~"UX".
The descriptions on the [labels page][labels-page] explain what falls under the The descriptions on the [labels page][labels-page] explain what falls under the
responsibility of each team. responsibility of each team.
......
...@@ -50,10 +50,8 @@ class AwardsHandler { ...@@ -50,10 +50,8 @@ class AwardsHandler {
this.registerEventListener('on', $('html'), 'click', (e) => { this.registerEventListener('on', $('html'), 'click', (e) => {
const $target = $(e.target); const $target = $(e.target);
if (!$target.closest('.emoji-menu-content').length) {
$('.js-awards-block.current').removeClass('current');
}
if (!$target.closest('.emoji-menu').length) { if (!$target.closest('.emoji-menu').length) {
$('.js-awards-block.current').removeClass('current');
if ($('.emoji-menu').is(':visible')) { if ($('.emoji-menu').is(':visible')) {
$('.js-add-award.is-active').removeClass('is-active'); $('.js-add-award.is-active').removeClass('is-active');
this.hideMenuElement($('.emoji-menu')); this.hideMenuElement($('.emoji-menu'));
......
...@@ -26,7 +26,11 @@ export { default as ConflictsState } from './components/states/mr_widget_conflic ...@@ -26,7 +26,11 @@ export { default as ConflictsState } from './components/states/mr_widget_conflic
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge'; export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue'; export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
<<<<<<< HEAD
export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge'; export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge';
=======
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
>>>>>>> upstream/master
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch'; export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions'; export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue'; export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
......
...@@ -149,6 +149,7 @@ export default { ...@@ -149,6 +149,7 @@ export default {
}, },
handleNotification(data) { handleNotification(data) {
if (data.ci_status === this.mr.ciStatus) return; if (data.ci_status === this.mr.ciStatus) return;
if (!data.pipeline) return;
const label = data.pipeline.details.status.label; const label = data.pipeline.details.status.label;
const title = `Pipeline ${label}`; const title = `Pipeline ${label}`;
......
...@@ -261,8 +261,6 @@ ul.controls { ...@@ -261,8 +261,6 @@ ul.controls {
} }
.author_link { .author_link {
display: inline-block;
.avatar-inline { .avatar-inline {
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
......
...@@ -39,12 +39,12 @@ class Projects::Clusters::GcpController < Projects::ApplicationController ...@@ -39,12 +39,12 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
def verify_billing def verify_billing
case google_project_billing_status case google_project_billing_status
when 'true' when nil
return
when 'false'
flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" }
else
flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') flash[:alert] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.')
when false
flash[:alert] = _('Please <a href=%{link_to_billing} target="_blank" rel="noopener noreferrer">enable billing for one of your projects to be able to create a Kubernetes cluster</a>, then try again.').html_safe % { link_to_billing: "https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral" }
when true
return
end end
@cluster = ::Clusters::Cluster.new(create_params) @cluster = ::Clusters::Cluster.new(create_params)
...@@ -81,9 +81,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController ...@@ -81,9 +81,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController
end end
def google_project_billing_status def google_project_billing_status
Gitlab::Redis::SharedState.with do |redis| CheckGcpProjectBillingWorker.get_billing_state(token_in_session)
redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session))
end
end end
def token_in_session def token_in_session
......
...@@ -39,7 +39,7 @@ class ExternalIssue ...@@ -39,7 +39,7 @@ class ExternalIssue
end end
def to_reference(_from = nil, full: nil) def to_reference(_from = nil, full: nil)
id reference_link_text
end end
def reference_link_text(from = nil) def reference_link_text(from = nil)
......
...@@ -11,6 +11,7 @@ class Identity < ActiveRecord::Base ...@@ -11,6 +11,7 @@ class Identity < ActiveRecord::Base
validates :user_id, uniqueness: { scope: :provider } validates :user_id, uniqueness: { scope: :provider }
before_save :ensure_normalized_extern_uid, if: :extern_uid_changed? before_save :ensure_normalized_extern_uid, if: :extern_uid_changed?
after_destroy :clear_user_synced_attributes, if: :user_synced_attributes_metadata_from_provider?
scope :with_provider, ->(provider) { where(provider: provider) } scope :with_provider, ->(provider) { where(provider: provider) }
scope :with_extern_uid, ->(provider, extern_uid) do scope :with_extern_uid, ->(provider, extern_uid) do
...@@ -36,4 +37,12 @@ class Identity < ActiveRecord::Base ...@@ -36,4 +37,12 @@ class Identity < ActiveRecord::Base
self.extern_uid = Identity.normalize_uid(self.provider, self.extern_uid) self.extern_uid = Identity.normalize_uid(self.provider, self.extern_uid)
end end
def user_synced_attributes_metadata_from_provider?
user.user_synced_attributes_metadata&.provider == provider
end
def clear_user_synced_attributes
user.user_synced_attributes_metadata&.destroy
end
end end
...@@ -600,7 +600,15 @@ class Repository ...@@ -600,7 +600,15 @@ class Repository
def license_key def license_key
return unless exists? return unless exists?
Licensee.license(path).try(:key) # The licensee gem creates a Rugged object from the path:
# https://github.com/benbalter/licensee/blob/v8.7.0/lib/licensee/projects/git_project.rb
begin
Licensee.license(path).try(:key)
# Normally we would rescue Rugged::Error, but that is banned by lint-rugged
# and we need to migrate this endpoint to Gitaly:
# https://gitlab.com/gitlab-org/gitaly/issues/1026
rescue
end
end end
cache_method :license_key cache_method :license_key
......
...@@ -258,7 +258,7 @@ class User < ActiveRecord::Base ...@@ -258,7 +258,7 @@ class User < ActiveRecord::Base
def find_for_database_authentication(warden_conditions) def find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup conditions = warden_conditions.dup
if login = conditions.delete(:login) if login = conditions.delete(:login)
where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase) where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip)
else else
find_by(conditions) find_by(conditions)
end end
......
...@@ -135,7 +135,7 @@ module MergeRequests ...@@ -135,7 +135,7 @@ module MergeRequests
end end
def append_closes_description def append_closes_description
return unless issue return unless issue&.to_reference.present?
closes_issue = "Closes #{issue.to_reference}" closes_issue = "Closes #{issue.to_reference}"
...@@ -164,7 +164,7 @@ module MergeRequests ...@@ -164,7 +164,7 @@ module MergeRequests
return if merge_request.title.present? return if merge_request.title.present?
if issue_iid.present? if issue_iid.present?
merge_request.title = "Resolve #{issue_iid}" merge_request.title = "Resolve #{issue.to_reference}"
branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize branch_title = source_branch.downcase.remove(issue_iid.downcase).titleize.humanize
merge_request.title += " \"#{branch_title}\"" if branch_title.present? merge_request.title += " \"#{branch_title}\"" if branch_title.present?
end end
......
...@@ -7,6 +7,7 @@ class CheckGcpProjectBillingWorker ...@@ -7,6 +7,7 @@ class CheckGcpProjectBillingWorker
LEASE_TIMEOUT = 3.seconds.to_i LEASE_TIMEOUT = 3.seconds.to_i
SESSION_KEY_TIMEOUT = 5.minutes SESSION_KEY_TIMEOUT = 5.minutes
BILLING_TIMEOUT = 1.hour BILLING_TIMEOUT = 1.hour
BILLING_CHANGED_LABELS = { state_transition: nil }.freeze
def self.get_session_token(token_key) def self.get_session_token(token_key)
Gitlab::Redis::SharedState.with do |redis| Gitlab::Redis::SharedState.with do |redis|
...@@ -22,8 +23,11 @@ class CheckGcpProjectBillingWorker ...@@ -22,8 +23,11 @@ class CheckGcpProjectBillingWorker
end end
end end
def self.redis_shared_state_key_for(token) def self.get_billing_state(token)
"gitlab:gcp:#{Digest::SHA1.hexdigest(token)}:billing_enabled" Gitlab::Redis::SharedState.with do |redis|
value = redis.get(redis_shared_state_key_for(token))
ActiveRecord::Type::Boolean.new.type_cast_from_user(value)
end
end end
def perform(token_key) def perform(token_key)
...@@ -33,12 +37,9 @@ class CheckGcpProjectBillingWorker ...@@ -33,12 +37,9 @@ class CheckGcpProjectBillingWorker
return unless token return unless token
return unless try_obtain_lease_for(token) return unless try_obtain_lease_for(token)
billing_enabled_projects = CheckGcpProjectBillingService.new.execute(token) billing_enabled_state = !CheckGcpProjectBillingService.new.execute(token).empty?
Gitlab::Redis::SharedState.with do |redis| update_billing_change_counter(self.class.get_billing_state(token), billing_enabled_state)
redis.set(self.class.redis_shared_state_key_for(token), self.class.set_billing_state(token, billing_enabled_state)
!billing_enabled_projects.empty?,
ex: BILLING_TIMEOUT)
end
end end
private private
...@@ -51,9 +52,41 @@ class CheckGcpProjectBillingWorker ...@@ -51,9 +52,41 @@ class CheckGcpProjectBillingWorker
"gitlab:gcp:session:#{token_key}" "gitlab:gcp:session:#{token_key}"
end end
def self.redis_shared_state_key_for(token)
"gitlab:gcp:#{Digest::SHA1.hexdigest(token)}:billing_enabled"
end
def self.set_billing_state(token, value)
Gitlab::Redis::SharedState.with do |redis|
redis.set(redis_shared_state_key_for(token), value, ex: BILLING_TIMEOUT)
end
end
def try_obtain_lease_for(token) def try_obtain_lease_for(token)
Gitlab::ExclusiveLease Gitlab::ExclusiveLease
.new("check_gcp_project_billing_worker:#{token.hash}", timeout: LEASE_TIMEOUT) .new("check_gcp_project_billing_worker:#{token.hash}", timeout: LEASE_TIMEOUT)
.try_obtain .try_obtain
end end
def billing_changed_counter
@billing_changed_counter ||= Gitlab::Metrics.counter(
:gcp_billing_change_count,
"Counts the number of times a GCP project changed billing_enabled state from false to true",
BILLING_CHANGED_LABELS
)
end
def state_transition(previous_state, current_state)
if previous_state.nil? && !current_state
'no_billing'
elsif previous_state.nil? && current_state
'with_billing'
elsif !previous_state && current_state
'billing_configured'
end
end
def update_billing_change_counter(previous_state, current_state)
billing_changed_counter.increment(state_transition: state_transition(previous_state, current_state))
end
end end
---
title: Prevent MR Widget error when no CI configured
merge_request:
author:
type: fixed
---
title: Fix Teleporting Emoji
merge_request: 16963
author: Jared Deckard <jared.deckard@gmail.com>
type: fixed
---
title: "Fix user avatar's vertical align on the issues and merge requests pages"
merge_request: 17072
author: Laszlo Karpati
type: fixed
---
title: Remove whitespace from the username/email sign in form field
merge_request: 17020
author: Peter lauck
type: changed
---
title: Fix validation of environment scope of variables
merge_request:
author:
type: fixed
---
title: Fixed error 500 when removing an identity with synced attributes and visiting
the profile page
merge_request: 17054
author:
type: fixed
---
title: 'Ensure users cannot create environments with leading or trailing slashes (Fixes #39885)'
merge_request: 15273
author:
type: fixed
...@@ -104,7 +104,10 @@ var config = { ...@@ -104,7 +104,10 @@ var config = {
roadmap: 'ee/roadmap', roadmap: 'ee/roadmap',
ide: './ide/index.js', ide: './ide/index.js',
sidebar: './sidebar/sidebar_bundle.js', sidebar: './sidebar/sidebar_bundle.js',
<<<<<<< HEAD
ee_sidebar: 'ee/sidebar/sidebar_bundle.js', ee_sidebar: 'ee/sidebar/sidebar_bundle.js',
=======
>>>>>>> upstream/master
snippet: './snippet/snippet_bundle.js', snippet: './snippet/snippet_bundle.js',
sketch_viewer: './blob/sketch_viewer.js', sketch_viewer: './blob/sketch_viewer.js',
stl_viewer: './blob/stl_viewer.js', stl_viewer: './blob/stl_viewer.js',
......
...@@ -9,7 +9,6 @@ Gitlab::Seeder.quiet do ...@@ -9,7 +9,6 @@ Gitlab::Seeder.quiet do
s.username = 'root' s.username = 'root'
s.password = '5iveL!fe' s.password = '5iveL!fe'
s.admin = true s.admin = true
s.projects_limit = 100
s.confirmed_at = DateTime.now s.confirmed_at = DateTime.now
end end
end end
...@@ -9,7 +9,19 @@ created in snippets, wikis, and repos. ...@@ -9,7 +9,19 @@ created in snippets, wikis, and repos.
## PlantUML Server ## PlantUML Server
Before you can enable PlantUML in GitLab; you need to set up your own PlantUML Before you can enable PlantUML in GitLab; you need to set up your own PlantUML
server that will generate the diagrams. Installing and configuring your server that will generate the diagrams.
### Docker
With Docker, you can just run a container like this:
`docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat`
The **PlantUML URL** will be the hostname of the server running the container.
### Debian/Ubuntu
Installing and configuring your
own PlantUML server is easy in Debian/Ubuntu distributions using Tomcat. own PlantUML server is easy in Debian/Ubuntu distributions using Tomcat.
First you need to create a `plantuml.war` file from the source code: First you need to create a `plantuml.war` file from the source code:
......
...@@ -69,7 +69,7 @@ new one, and attempting to pull a repo. ...@@ -69,7 +69,7 @@ new one, and attempting to pull a repo.
> **Warning:** Do not disable writes until SSH is confirmed to be working > **Warning:** Do not disable writes until SSH is confirmed to be working
perfectly, because the file will quickly become out-of-date. perfectly, because the file will quickly become out-of-date.
In the case of lookup failures (which are not uncommon), the `authorized_keys` In the case of lookup failures (which are common), the `authorized_keys`
file will still be scanned. So git SSH performance will still be slow for many file will still be scanned. So git SSH performance will still be slow for many
users as long as a large file exists. users as long as a large file exists.
......
...@@ -61,6 +61,21 @@ Before proceeding with the Pages configuration, you will need to: ...@@ -61,6 +61,21 @@ Before proceeding with the Pages configuration, you will need to:
NOTE: **Note:** NOTE: **Note:**
If your GitLab instance and the Pages daemon are deployed in a private network or behind a firewall, your GitLab Pages websites will only be accessible to devices/users that have access to the private network. If your GitLab instance and the Pages daemon are deployed in a private network or behind a firewall, your GitLab Pages websites will only be accessible to devices/users that have access to the private network.
### Add the domain to the Public Suffix List
The [Public Suffix List](https://publicsuffix.org) is used by browsers to
decide how to treat subdomains. If your GitLab instance allows members of the
public to create GitLab Pages sites, it also allows those users to create
subdomains on the pages domain (`example.io`). Adding the domain to the Public
Suffix List prevents browsers from accepting
[supercookies](https://en.wikipedia.org/wiki/HTTP_cookie#Supercookie),
among other things.
Follow [these instructions](https://publicsuffix.org/submit/) to submit your
GitLab Pages subdomain. For instance, if your domain is `example.io`, you should
request that `*.example.io` is added to the Public Suffix List. GitLab.com
added `*.gitlab.io` [in 2016](https://gitlab.com/gitlab-com/infrastructure/issues/230).
### DNS configuration ### DNS configuration
GitLab Pages expect to run on their own virtual host. In your DNS server/provider GitLab Pages expect to run on their own virtual host. In your DNS server/provider
......
...@@ -40,8 +40,12 @@ Example response: ...@@ -40,8 +40,12 @@ Example response:
"kind": "group" "kind": "group"
"full_path": "group1", "full_path": "group1",
"parent_id": null, "parent_id": null,
<<<<<<< HEAD
"members_count_with_descendants": 2, "members_count_with_descendants": 2,
"plan": "bronze" "plan": "bronze"
=======
"members_count_with_descendants": 2
>>>>>>> upstream/master
}, },
{ {
"id": 3, "id": 3,
......
...@@ -95,7 +95,9 @@ Auto Deploy, and Auto Monitoring will be silently skipped. ...@@ -95,7 +95,9 @@ Auto Deploy, and Auto Monitoring will be silently skipped.
The Auto DevOps base domain is required if you want to make use of [Auto The Auto DevOps base domain is required if you want to make use of [Auto
Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It is defined Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It is defined
under the project's CI/CD settings while [enabling Auto DevOps](#enabling-auto-devops). either under the project's CI/CD settings while
[enabling Auto DevOps](#enabling-auto-devops) or in instance-wide settings in
the CI/CD section.
It can also be set at the project or group level as a variable, `AUTO_DEVOPS_DOMAIN`. It can also be set at the project or group level as a variable, `AUTO_DEVOPS_DOMAIN`.
A wildcard DNS A record matching the base domain is required, for example, A wildcard DNS A record matching the base domain is required, for example,
......
...@@ -18,7 +18,7 @@ documentation. ...@@ -18,7 +18,7 @@ documentation.
> **Important:** > **Important:**
For security reasons, when using the command line, we strongly recommend For security reasons, when using the command line, we strongly recommend
you to [connect with GitLab via SSH](../../../ssh/README.md). that you [connect with GitLab via SSH](../../../ssh/README.md).
## Files ## Files
......
Feature: Group Members
Background:
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest"
Scenario: Search member by name
Given "Mary Jane" is guest of group "Guest"
And I visit group "Guest" members page
When I search for 'Mary' member
Then I should see user "Mary Jane" in team list
Then I should not see user "John Doe" in team list
Feature: Group Milestones
Background:
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
Scenario: I should see group "Owned" milestone index page with no milestones
When I visit group "Owned" page
And I click on group milestones
Then I should see group milestones index page has no milestones
Scenario: I should see group "Owned" milestone index page with milestones
Given Group has projects with milestones
When I visit group "Owned" page
And I click on group milestones
Then I should see group milestones index page with milestones
Scenario: I should see group "Owned" milestone show page
Given Group has projects with milestones
When I visit group "Owned" page
And I click on group milestones
And I click on one group milestone
Then I should see group milestone with descriptions and expiry date
And I should see group milestone with all issues and MRs assigned to that milestone
Scenario: Create group milestones
Given I visit group "Owned" milestones page
And I click new milestone button
And I fill milestone name
When I press create mileston button
Then group milestone should be created
Scenario: I should see Issues listed with labels
Given Group has projects with milestones
When I visit group "Owned" page
And I click on group milestones
And I click on one group milestone
Then I should see the "bug" label
And I should see the "feature" label
And I should see the project name in the Issue row
@javascript
Scenario: I should see the Labels tab
Given Group has projects with milestones
When I visit group "Owned" page
And I click on group milestones
And I click on one group milestone
And I click on the "Labels" tab
Then I should see the list of labels
@profile
Feature: Profile
Background:
Given I sign in as a user
Scenario: I look at my profile
Given I visit profile page
Then I should see my profile info
@javascript
Scenario: I can see groups I belong to
Given I have group with projects
When I visit profile page
And I click on my profile picture
Then I should see my user page
And I should see groups I belong to
Scenario: I edit profile
Given I visit profile page
Then I change my profile info
And I should see new profile info
Scenario: I change my password without old one
Given I visit profile password page
When I try change my password w/o old one
Then I should see a missing password error message
And I should be redirected to password page
Scenario: I change my password
Given I visit profile password page
Then I change my password
And I should be redirected to sign in page
Scenario: I edit my avatar
Given I visit profile page
Then I change my avatar
And I should see new avatar
And I should see the "Remove avatar" button
And I should see the gravatar host link
Scenario: I remove my avatar
Given I visit profile page
And I have an avatar
When I remove my avatar
Then I should see my gravatar
And I should not see the "Remove avatar" button
And I should see the gravatar host link
Scenario: My password is expired
Given my password is expired
And I am not an ldap user
Given I visit profile password page
Then I redirected to expired password page
And I submit new password
And I redirected to sign in page
Scenario: I unsuccessfully change my password
Given I visit profile password page
When I unsuccessfully change my password
Then I should see a password error message
Scenario: I visit history tab
Given I logout
And I sign in via the UI
And I have activity
When I visit Authentication log page
Then I should see my activity
Scenario: I visit my user page
When I visit profile page
And I click on my profile picture
Then I should see my user page
Scenario: I can manage application
Given I visit profile applications page
Then I should see application form
Then I fill application form out and submit
And I see application
Then I click edit
And I see edit application form
Then I change name of application and submit
And I see that application was changed
Then I visit profile applications page
And I click to remove application
Then I see that application is removed
...@@ -9,14 +9,6 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps ...@@ -9,14 +9,6 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
expect(group_members_list).to have_content("John Doe") expect(group_members_list).to have_content("John Doe")
end end
step 'I should not see user "John Doe" in team list' do
expect(group_members_list).not_to have_content("John Doe")
end
step 'I should see user "Mary Jane" in team list' do
expect(group_members_list).to have_content("Mary Jane")
end
step 'I should not see user "Mary Jane" in team list' do step 'I should not see user "Mary Jane" in team list' do
expect(group_members_list).not_to have_content("Mary Jane") expect(group_members_list).not_to have_content("Mary Jane")
end end
...@@ -41,13 +33,6 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps ...@@ -41,13 +33,6 @@ class Spinach::Features::GroupMembers < Spinach::FeatureSteps
# poltergeist always confirms popups. # poltergeist always confirms popups.
end end
step 'I search for \'Mary\' member' do
page.within '.member-search-form' do
fill_in 'search', with: 'Mary'
find('.member-search-btn').click
end
end
step 'I change the "Mary Jane" role to "Developer"' do step 'I change the "Mary Jane" role to "Developer"' do
member = mary_jane_member member = mary_jane_member
......
class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
include WaitForRequests
include SharedAuthentication
include SharedPaths
include SharedGroup
include SharedUser
step 'I click on group milestones' do
visit group_milestones_path('owned')
end
step 'I should see group milestones index page has no milestones' do
expect(page).to have_content('No milestones to show')
end
step 'Group has projects with milestones' do
group_milestone
end
step 'I should see group milestones index page with milestones' do
expect(page).to have_content('Version 7.2')
expect(page).to have_content('GL-113')
expect(page).to have_link('3 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2"))
expect(page).to have_link('0 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113"))
end
step 'I click on one group milestone' do
milestones = Milestone.where(title: 'GL-113')
@global_milestone = GlobalMilestone.new('GL-113', milestones)
click_link 'GL-113'
end
step 'I should see group milestone with descriptions and expiry date' do
expect(page).to have_content('expires on Aug 20, 2114')
end
step 'I should see group milestone with all issues and MRs assigned to that milestone' do
expect(page).to have_content('Milestone GL-113')
expect(page).to have_content('Issues 3 Open: 3 Closed: 0')
issue = Milestone.find_by(name: 'GL-113').issues.first
expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue))
end
step 'I fill milestone name' do
fill_in 'milestone_title', with: 'v2.9.0'
end
step 'I click new milestone button' do
page.within('.nav-controls') do
click_link "New milestone"
end
end
step 'I press create mileston button' do
click_button "Create milestone"
end
step 'group milestone should be created' do
group = Group.find_by(name: 'Owned')
expect(page).to have_content group.milestones.find_by_title('v2.9.0').title
end
step 'I should see the "bug" label' do
page.within('#tab-issues') do
expect(page).to have_content 'bug'
end
end
step 'I should see the "feature" label' do
page.within('#tab-issues') do
expect(page).to have_content 'bug'
end
end
step 'I should see the project name in the Issue row' do
page.within('#tab-issues') do
@global_milestone.projects.each do |project|
expect(page).to have_content project.name
end
end
end
step 'I click on the "Labels" tab' do
page.within('.content .nav-links') do
page.find(:xpath, "//a[@href='#tab-labels']").click
end
end
step 'I should see the list of labels' do
wait_for_requests
page.within('#tab-labels') do
expect(page).to have_content 'bug'
expect(page).to have_content 'feature'
end
end
private
def group_milestone
group = owned_group
%w(gitlabhq gitlab-ci cookbook-gitlab).each do |path|
project = create(:project, path: path, group: group)
milestone = create :milestone, title: "Version 7.2", project: project
create(:label, project: project, title: 'bug')
create(:label, project: project, title: 'feature')
create :issue,
project: project,
assignees: [current_user],
author: current_user,
milestone: milestone
milestone = create :milestone,
title: "GL-113",
project: project,
due_date: '2114-08-20',
description: 'Lorem Ipsum is simply dummy text'
issue = create :issue,
project: project,
assignees: [current_user],
author: current_user,
milestone: milestone
issue.labels << project.labels.find_by(title: 'bug')
issue.labels << project.labels.find_by(title: 'feature')
end
current_user.refresh_authorized_projects
end
end
class Spinach::Features::Profile < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
step 'I should see my profile info' do
expect(page).to have_content "This information will appear on your profile"
end
step 'I change my profile info' do
fill_in 'user_skype', with: 'testskype'
fill_in 'user_linkedin', with: 'testlinkedin'
fill_in 'user_twitter', with: 'testtwitter'
fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab'
fill_in 'user_organization', with: 'GitLab'
click_button 'Update profile settings'
@user.reload
end
step 'I should see new profile info' do
expect(@user.skype).to eq 'testskype'
expect(@user.linkedin).to eq 'testlinkedin'
expect(@user.twitter).to eq 'testtwitter'
expect(@user.website_url).to eq 'testurl'
expect(@user.bio).to eq 'I <3 GitLab'
expect(@user.organization).to eq 'GitLab'
expect(find('#user_location').value).to eq 'Ukraine'
end
step 'I change my avatar' do
attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
click_button "Update profile settings"
@user.reload
end
step 'I should see new avatar' do
expect(@user.avatar).to be_instance_of AvatarUploader
expect(@user.avatar.url).to eq "/uploads/-/system/user/avatar/#{@user.id}/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
expect(page).to have_link("Remove avatar")
end
step 'I have an avatar' do
attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
click_button "Update profile settings"
@user.reload
end
step 'I remove my avatar' do
click_link "Remove avatar"
@user.reload
end
step 'I should see my gravatar' do
expect(@user.avatar?).to eq false
end
step 'I should not see the "Remove avatar" button' do
expect(page).not_to have_link("Remove avatar")
end
step 'I should see the gravatar host link' do
expect(page).to have_link("gravatar.com")
end
step 'I try change my password w/o old one' do
page.within '.update-password' do
fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
click_button "Save password"
end
end
step 'I change my password' do
page.within '.update-password' do
fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
click_button "Save password"
end
end
step 'I unsuccessfully change my password' do
page.within '.update-password' do
fill_in "user_current_password", with: "12345678"
fill_in "user_password", with: "password"
fill_in "user_password_confirmation", with: "confirmation"
click_button "Save password"
end
end
step "I should see a missing password error message" do
page.within ".flash-container" do
expect(page).to have_content "You must provide a valid current password"
end
end
step "I should see a password error message" do
page.within '.alert-danger' do
expect(page).to have_content "Password confirmation doesn't match"
end
end
step 'I have activity' do
create(:closed_issue_event, author: current_user)
end
step 'I should see my activity' do
expect(page).to have_content "Signed in with standard authentication"
end
step 'my password is expired' do
current_user.update_attributes(password_expires_at: Time.now - 1.hour)
end
step "I am not an ldap user" do
current_user.identities.delete
expect(current_user.ldap_user?).to eq false
end
step 'I redirected to expired password page' do
expect(current_path).to eq new_profile_password_path
end
step 'I submit new password' do
fill_in :user_current_password, with: '12345678'
fill_in :user_password, with: '12345678'
fill_in :user_password_confirmation, with: '12345678'
click_button "Set new password"
end
step 'I redirected to sign in page' do
expect(current_path).to eq new_user_session_path
end
step 'I should be redirected to password page' do
expect(current_path).to eq edit_profile_password_path
end
step 'I should be redirected to account page' do
expect(current_path).to eq profile_account_path
end
step 'I click on my profile picture' do
find(:css, '.header-user-dropdown-toggle').click
page.within ".header-user" do
click_link "Profile"
end
end
step 'I should see my user page' do
page.within ".cover-block" do
expect(page).to have_content current_user.name
expect(page).to have_content current_user.username
end
end
step 'I have group with projects' do
@group = create(:group)
@group.add_owner(current_user)
@project = create(:project, :repository, namespace: @group)
@event = create(:closed_issue_event, project: @project)
@project.add_master(current_user)
end
step 'I should see groups I belong to' do
page.within ".content" do
click_link "Groups"
end
page.within "#groups" do
expect(page).to have_content @group.name
end
end
step 'I should see application form' do
expect(page).to have_content "Add new application"
end
step 'I fill application form out and submit' do
fill_in :doorkeeper_application_name, with: 'test'
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
click_on "Save application"
end
step 'I see application' do
expect(page).to have_content "Application: test"
expect(page).to have_content "Application Id"
expect(page).to have_content "Secret"
end
step 'I click edit' do
click_on "Edit"
end
step 'I see edit application form' do
expect(page).to have_content "Edit application"
end
step 'I change name of application and submit' do
expect(page).to have_content "Edit application"
fill_in :doorkeeper_application_name, with: 'test_changed'
click_on "Save application"
end
step 'I see that application was changed' do
expect(page).to have_content "test_changed"
expect(page).to have_content "Application Id"
expect(page).to have_content "Secret"
end
step 'I click to remove application' do
page.within '.oauth-applications' do
click_on "Destroy"
end
end
step "I see that application is removed" do
expect(page.find(".oauth-applications")).not_to have_content "test_changed"
end
end
...@@ -259,6 +259,7 @@ module Gitlab ...@@ -259,6 +259,7 @@ module Gitlab
# If user does not have access to make at least one change, cancel all # If user does not have access to make at least one change, cancel all
# push by allowing the exception to bubble up # push by allowing the exception to bubble up
check_single_change_access(change, skip_lfs_integrity_check: !first_change) check_single_change_access(change, skip_lfs_integrity_check: !first_change)
<<<<<<< HEAD
if project.size_limit_enabled? if project.size_limit_enabled?
push_size_in_bytes += EE::Gitlab::Deltas.delta_size_check(change, project.repository) push_size_in_bytes += EE::Gitlab::Deltas.delta_size_check(change, project.repository)
...@@ -267,6 +268,8 @@ module Gitlab ...@@ -267,6 +268,8 @@ module Gitlab
if project.changes_will_exceed_size_limit?(push_size_in_bytes) if project.changes_will_exceed_size_limit?(push_size_in_bytes)
raise UnauthorizedError, Gitlab::RepositorySizeError.new(project).new_changes_error raise UnauthorizedError, Gitlab::RepositorySizeError.new(project).new_changes_error
=======
>>>>>>> upstream/master
end end
end end
......
...@@ -19,9 +19,13 @@ module Gitlab ...@@ -19,9 +19,13 @@ module Gitlab
end end
def self.servers def self.servers
<<<<<<< HEAD
Gitlab.config.ldap.servers.values Gitlab.config.ldap.servers.values
rescue Settingslogic::MissingSetting rescue Settingslogic::MissingSetting
[] []
=======
Gitlab.config.ldap['servers']&.values || []
>>>>>>> upstream/master
end end
def self.available_servers def self.available_servers
......
...@@ -200,9 +200,11 @@ module Gitlab ...@@ -200,9 +200,11 @@ module Gitlab
end end
def update_profile def update_profile
clear_user_synced_attributes_metadata
return unless sync_profile_from_provider? || creating_linked_ldap_user? return unless sync_profile_from_provider? || creating_linked_ldap_user?
metadata = gl_user.user_synced_attributes_metadata || gl_user.build_user_synced_attributes_metadata metadata = gl_user.build_user_synced_attributes_metadata
if sync_profile_from_provider? if sync_profile_from_provider?
UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key| UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES.each do |key|
...@@ -223,6 +225,10 @@ module Gitlab ...@@ -223,6 +225,10 @@ module Gitlab
end end
end end
def clear_user_synced_attributes_metadata
gl_user.user_synced_attributes_metadata&.destroy
end
def log def log
Gitlab::AppLogger Gitlab::AppLogger
end end
......
...@@ -41,12 +41,16 @@ module Gitlab ...@@ -41,12 +41,16 @@ module Gitlab
'a-zA-Z0-9_/\\$\\{\\}\\. \\-' 'a-zA-Z0-9_/\\$\\{\\}\\. \\-'
end end
def environment_name_regex_chars_without_slash
'a-zA-Z0-9_\\$\\{\\}\\. -'
end
def environment_name_regex def environment_name_regex
@environment_name_regex ||= /\A[#{environment_name_regex_chars}]+\z/.freeze @environment_name_regex ||= /\A[#{environment_name_regex_chars_without_slash}]([#{environment_name_regex_chars}]*[#{environment_name_regex_chars_without_slash}])?\z/.freeze
end end
def environment_name_regex_message def environment_name_regex_message
"can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', and spaces" "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', and spaces, but it cannot start or end with '/'"
end end
def kubernetes_namespace_regex def kubernetes_namespace_regex
......
...@@ -33,7 +33,7 @@ module QA ...@@ -33,7 +33,7 @@ module QA
end end
def clone(opts = '') def clone(opts = '')
`git clone #{opts} #{@uri.to_s} ./` `git clone #{opts} #{@uri.to_s} ./ #{suppress_output}`
end end
def shallow_clone def shallow_clone
...@@ -61,12 +61,22 @@ module QA ...@@ -61,12 +61,22 @@ module QA
end end
def push_changes(branch = 'master') def push_changes(branch = 'master')
`git push #{@uri.to_s} #{branch}` `git push #{@uri.to_s} #{branch} #{suppress_output}`
end end
def commits def commits
`git log --oneline`.split("\n") `git log --oneline`.split("\n")
end end
private
def suppress_output
# If we're running as the default user, it's probably a temporary
# instance and output can be useful for debugging
return if @username == Runtime::User.default_name
"&> #{File::NULL}"
end
end end
end end
end end
...@@ -53,8 +53,8 @@ module QA ...@@ -53,8 +53,8 @@ module QA
click_link 'LDAP' click_link 'LDAP'
fill_in :username, with: Runtime::User.name fill_in :username, with: Runtime::User.ldap_username
fill_in :password, with: Runtime::User.password fill_in :password, with: Runtime::User.ldap_password
click_button 'Sign in' click_button 'Sign in'
end end
end end
......
...@@ -45,6 +45,10 @@ module QA ...@@ -45,6 +45,10 @@ module QA
end end
def new_merge_request def new_merge_request
wait(reload: true) do
has_css?(element_selector_css(:create_merge_request))
end
click_element :create_merge_request click_element :create_merge_request
end end
......
...@@ -35,6 +35,14 @@ module QA ...@@ -35,6 +35,14 @@ module QA
ENV['GITLAB_PASSWORD'] ENV['GITLAB_PASSWORD']
end end
def ldap_username
ENV['GITLAB_LDAP_USERNAME']
end
def ldap_password
ENV['GITLAB_LDAP_PASSWORD']
end
def sandbox_name def sandbox_name
ENV['GITLAB_SANDBOX_NAME'] ENV['GITLAB_SANDBOX_NAME']
end end
......
...@@ -3,8 +3,12 @@ module QA ...@@ -3,8 +3,12 @@ module QA
module User module User
extend self extend self
def default_name
'root'
end
def name def name
Runtime::Env.user_username || 'root' Runtime::Env.user_username || default_name
end end
def password def password
...@@ -14,6 +18,14 @@ module QA ...@@ -14,6 +18,14 @@ module QA
def ldap_user? def ldap_user?
Runtime::Env.user_type == 'ldap' Runtime::Env.user_type == 'ldap'
end end
def ldap_username
Runtime::Env.ldap_username || name
end
def ldap_password
Runtime::Env.ldap_password || password
end
end end
end end
end end
require 'spec_helper'
describe 'Search group member' do
let(:user) { create :user }
let(:member) { create :user }
let!(:guest_group) do
create(:group) do |group|
group.add_guest(user)
group.add_guest(member)
end
end
before do
sign_in(user)
visit group_group_members_path(guest_group)
end
it 'renders member users' do
page.within '.member-search-form' do
fill_in 'search', with: member.name
find('.member-search-btn').click
end
group_members_list = find(".panel .content-list")
expect(group_members_list).to have_content(member.name)
expect(group_members_list).not_to have_content(user.name)
end
end
require 'rails_helper' require 'rails_helper'
feature 'Group milestones', :js do feature 'Group milestones' do
let(:group) { create(:group) } let(:group) { create(:group) }
let!(:project) { create(:project_empty_repo, group: group) } let!(:project) { create(:project_empty_repo, group: group) }
let(:user) { create(:group_member, :master, user: create(:user), group: group ).user } let(:user) { create(:group_member, :master, user: create(:user), group: group ).user }
...@@ -13,7 +13,7 @@ feature 'Group milestones', :js do ...@@ -13,7 +13,7 @@ feature 'Group milestones', :js do
sign_in(user) sign_in(user)
end end
context 'create a milestone' do context 'create a milestone', :js do
before do before do
visit new_group_milestone_path(group) visit new_group_milestone_path(group)
end end
...@@ -61,55 +61,132 @@ feature 'Group milestones', :js do ...@@ -61,55 +61,132 @@ feature 'Group milestones', :js do
end end
context 'milestones list' do context 'milestones list' do
let!(:other_project) { create(:project_empty_repo, group: group) } context 'when no milestones' do
it 'renders no milestones text' do
let!(:active_project_milestone1) { create(:milestone, project: project, state: 'active', title: 'v1.0') } visit group_milestones_path(group)
let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') } expect(page).to have_content('No milestones to show')
let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') } end
let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') }
let!(:active_group_milestone) { create(:milestone, group: group, state: 'active') }
let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
before do
visit group_milestones_path(group)
end end
it 'counts milestones correctly' do context 'when milestones exists' do
expect(find('.top-area .active .badge').text).to eq("2") let!(:other_project) { create(:project_empty_repo, group: group) }
expect(find('.top-area .closed .badge').text).to eq("2")
expect(find('.top-area .all .badge').text).to eq("4") let!(:active_project_milestone1) do
end create(
:milestone,
project: project,
state: 'active',
title: 'v1.0',
due_date: '2114-08-20',
description: 'Lorem Ipsum is simply dummy text'
)
end
let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') }
let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') }
let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') }
let!(:active_group_milestone) { create(:milestone, group: group, state: 'active', title: 'GL-113') }
let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
let!(:issue) do
create :issue, project: project, assignees: [user], author: user, milestone: active_project_milestone1
end
it 'lists legacy group milestones and group milestones' do before do
legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first visit group_milestones_path(group)
end
expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1) it 'counts milestones correctly' do
expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1) expect(find('.top-area .active .badge').text).to eq("2")
end expect(find('.top-area .closed .badge').text).to eq("2")
expect(find('.top-area .all .badge').text).to eq("4")
end
it 'updates milestone' do it 'lists legacy group milestones and group milestones' do
page.within(".milestones #milestone_#{active_group_milestone.id}") do legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first
click_link('Edit')
expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1)
expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1)
end end
page.within('.milestone-form') do it 'updates milestone' do
fill_in 'milestone_title', with: 'new title' page.within(".milestones #milestone_#{active_group_milestone.id}") do
click_button('Update milestone') click_link('Edit')
end
page.within('.milestone-form') do
fill_in 'milestone_title', with: 'new title'
click_button('Update milestone')
end
expect(find('#content-body h2')).to have_content('new title')
end end
expect(find('#content-body h2')).to have_content('new title') it 'shows milestone detail and supports its edit' do
end page.within(".milestones #milestone_#{active_group_milestone.id}") do
click_link(active_group_milestone.title)
end
page.within('.detail-page-header') do
click_link('Edit')
end
it 'shows milestone detail and supports its edit' do expect(page).to have_selector('.milestone-form')
page.within(".milestones #milestone_#{active_group_milestone.id}") do
click_link(active_group_milestone.title)
end end
page.within('.detail-page-header') do it 'renders milestones' do
click_link('Edit') expect(page).to have_content('v1.0')
expect(page).to have_content('GL-113')
expect(page).to have_link(
'1 Issue',
href: issues_group_path(group, milestone_title: 'v1.0')
)
expect(page).to have_link(
'0 Merge Requests',
href: merge_requests_group_path(group, milestone_title: 'v1.0')
)
end end
expect(page).to have_selector('.milestone-form') it 'renders group milestone details' do
click_link 'v1.0'
expect(page).to have_content('expires on Aug 20, 2114')
expect(page).to have_content('v1.0')
expect(page).to have_content('Issues 1 Open: 1 Closed: 0')
expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue))
end
describe 'labels' do
before do
create(:label, project: project, title: 'bug') do |label|
issue.labels << label
end
create(:label, project: project, title: 'feature') do |label|
issue.labels << label
end
end
it 'renders labels' do
click_link 'v1.0'
page.within('#tab-issues') do
expect(page).to have_content 'bug'
expect(page).to have_content 'feature'
end
end
it 'renders labels list', :js do
click_link 'v1.0'
page.within('.content .nav-links') do
page.find(:xpath, "//a[@href='#tab-labels']").click
end
page.within('#tab-labels') do
expect(page).to have_content 'bug'
expect(page).to have_content 'feature'
end
end
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
describe 'Profile > Password' do describe 'Profile > Password' do
let(:user) { create(:user) }
def fill_passwords(password, confirmation)
fill_in 'New password', with: password
fill_in 'Password confirmation', with: confirmation
click_button 'Save password'
end
context 'Password authentication enabled' do context 'Password authentication enabled' do
let(:user) { create(:user, password_automatically_set: true) } let(:user) { create(:user, password_automatically_set: true) }
...@@ -9,13 +18,6 @@ describe 'Profile > Password' do ...@@ -9,13 +18,6 @@ describe 'Profile > Password' do
visit edit_profile_password_path visit edit_profile_password_path
end end
def fill_passwords(password, confirmation)
fill_in 'New password', with: password
fill_in 'Password confirmation', with: confirmation
click_button 'Save password'
end
context 'User with password automatically set' do context 'User with password automatically set' do
describe 'User puts different passwords in the field and in the confirmation' do describe 'User puts different passwords in the field and in the confirmation' do
it 'shows an error message' do it 'shows an error message' do
...@@ -73,4 +75,64 @@ describe 'Profile > Password' do ...@@ -73,4 +75,64 @@ describe 'Profile > Password' do
end end
end end
end end
context 'Change passowrd' do
before do
sign_in(user)
visit(edit_profile_password_path)
end
it 'does not change user passowrd without old one' do
page.within '.update-password' do
fill_passwords('22233344', '22233344')
end
page.within '.flash-container' do
expect(page).to have_content 'You must provide a valid current password'
end
end
it 'does not change password with invalid old password' do
page.within '.update-password' do
fill_in 'user_current_password', with: 'invalid'
fill_passwords('password', 'confirmation')
end
page.within '.flash-container' do
expect(page).to have_content 'You must provide a valid current password'
end
end
it 'changes user password' do
page.within '.update-password' do
fill_in "user_current_password", with: user.password
fill_passwords('22233344', '22233344')
end
expect(current_path).to eq new_user_session_path
end
end
context 'when password is expired' do
before do
sign_in(user)
user.update_attributes(password_expires_at: 1.hour.ago)
user.identities.delete
expect(user.ldap_user?).to eq false
end
it 'needs change user password' do
visit edit_profile_password_path
expect(current_path).to eq new_profile_password_path
fill_in :user_current_password, with: user.password
fill_in :user_password, with: '12345678'
fill_in :user_password_confirmation, with: '12345678'
click_button 'Set new password'
expect(current_path).to eq new_user_session_path
end
end
end end
require 'spec_helper'
describe 'User edit profile' do
let(:user) { create(:user) }
before do
sign_in(user)
visit(profile_path)
end
it 'changes user profile' do
fill_in 'user_skype', with: 'testskype'
fill_in 'user_linkedin', with: 'testlinkedin'
fill_in 'user_twitter', with: 'testtwitter'
fill_in 'user_website_url', with: 'testurl'
fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab'
fill_in 'user_organization', with: 'GitLab'
click_button 'Update profile settings'
expect(user.reload).to have_attributes(
skype: 'testskype',
linkedin: 'testlinkedin',
twitter: 'testtwitter',
website_url: 'testurl',
bio: 'I <3 GitLab',
organization: 'GitLab'
)
expect(find('#user_location').value).to eq 'Ukraine'
expect(page).to have_content('Profile was successfully updated')
end
context 'user avatar' do
before do
attach_file(:user_avatar, Rails.root.join('spec', 'fixtures', 'banana_sample.gif'))
click_button 'Update profile settings'
end
it 'changes user avatar' do
expect(page).to have_link('Remove avatar')
user.reload
expect(user.avatar).to be_instance_of AvatarUploader
expect(user.avatar.url).to eq "/uploads/-/system/user/avatar/#{user.id}/banana_sample.gif"
end
it 'removes user avatar' do
click_link 'Remove avatar'
user.reload
expect(user.avatar?).to eq false
expect(page).not_to have_link('Remove avatar')
expect(page).to have_link('gravatar.com')
end
end
end
require 'spec_helper'
describe 'User manages applications' do
let(:user) { create(:user) }
before do
sign_in(user)
visit applications_profile_path
end
it 'manages applications' do
expect(page).to have_content 'Add new application'
fill_in :doorkeeper_application_name, with: 'test'
fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com'
click_on 'Save application'
expect(page).to have_content 'Application: test'
expect(page).to have_content 'Application Id'
expect(page).to have_content 'Secret'
click_on 'Edit'
expect(page).to have_content 'Edit application'
fill_in :doorkeeper_application_name, with: 'test_changed'
click_on 'Save application'
expect(page).to have_content 'test_changed'
expect(page).to have_content 'Application Id'
expect(page).to have_content 'Secret'
visit applications_profile_path
page.within '.oauth-applications' do
click_on 'Destroy'
end
expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
end
end
...@@ -3,13 +3,28 @@ require 'spec_helper' ...@@ -3,13 +3,28 @@ require 'spec_helper'
describe 'User visits the authentication log' do describe 'User visits the authentication log' do
let(:user) { create(:user) } let(:user) { create(:user) }
before do context 'when user signed in' do
sign_in(user) before do
sign_in(user)
end
visit(audit_log_profile_path) it 'shows correct menu item' do
visit(audit_log_profile_path)
expect(page).to have_active_navigation('Authentication log')
end
end end
it 'shows correct menu item' do context 'when user has activity' do
expect(page).to have_active_navigation('Authentication log') before do
create(:closed_issue_event, author: user)
gitlab_sign_in(user)
end
it 'shows user activity' do
visit(audit_log_profile_path)
expect(page).to have_content 'Signed in with standard authentication'
end
end end
end end
...@@ -5,20 +5,58 @@ describe 'User visits their profile' do ...@@ -5,20 +5,58 @@ describe 'User visits their profile' do
before do before do
sign_in(user) sign_in(user)
visit(profile_path)
end end
it 'shows correct menu item' do it 'shows correct menu item' do
visit(profile_path)
expect(page).to have_active_navigation('Profile') expect(page).to have_active_navigation('Profile')
end end
describe 'profile settings', :js do it 'shows profile info' do
it 'saves updates' do visit(profile_path)
fill_in 'user_bio', with: 'bio'
click_button 'Update profile settings' expect(page).to have_content "This information will appear on your profile"
end
context 'when user has groups' do
let(:group) do
create :group do |group|
group.add_owner(user)
end
end
let!(:project) do
create(:project, :repository, namespace: group) do |project|
create(:closed_issue_event, project: project)
project.add_master(user)
end
end
def click_on_profile_picture
find(:css, '.header-user-dropdown-toggle').click
page.within ".header-user" do
click_link "Profile"
end
end
it 'shows user groups', :js do
visit(profile_path)
click_on_profile_picture
page.within ".cover-block" do
expect(page).to have_content user.name
expect(page).to have_content user.username
end
page.within ".content" do
click_link "Groups"
end
expect(page).to have_content('Profile was successfully updated') page.within "#groups" do
expect(page).to have_content group.name
end
end end
end end
end end
...@@ -25,7 +25,7 @@ feature 'Gcp Cluster', :js do ...@@ -25,7 +25,7 @@ feature 'Gcp Cluster', :js do
context 'when user has a GCP project with billing enabled' do context 'when user has a GCP project with billing enabled' do
before do before do
allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing) allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing)
allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return('true') allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return(true)
end end
context 'when user does not have a cluster and visits cluster index page' do context 'when user does not have a cluster and visits cluster index page' do
...@@ -134,7 +134,7 @@ feature 'Gcp Cluster', :js do ...@@ -134,7 +134,7 @@ feature 'Gcp Cluster', :js do
context 'when user does not have a GCP project with billing enabled' do context 'when user does not have a GCP project with billing enabled' do
before do before do
allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing) allow_any_instance_of(Projects::Clusters::GcpController).to receive(:authorize_google_project_billing)
allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return('false') allow_any_instance_of(Projects::Clusters::GcpController).to receive(:google_project_billing_status).and_return(false)
visit project_clusters_path(project) visit project_clusters_path(project)
......
...@@ -79,7 +79,7 @@ import '~/lib/utils/common_utils'; ...@@ -79,7 +79,7 @@ import '~/lib/utils/common_utils';
return expect($emojiMenu.length).toBe(1); return expect($emojiMenu.length).toBe(1);
}); });
}); });
return it('should remove emoji menu when body is clicked', function(done) { it('should remove emoji menu when body is clicked', function(done) {
$('.js-add-award').eq(0).click(); $('.js-add-award').eq(0).click();
return lazyAssert(done, function() { return lazyAssert(done, function() {
var $emojiMenu; var $emojiMenu;
...@@ -90,6 +90,17 @@ import '~/lib/utils/common_utils'; ...@@ -90,6 +90,17 @@ import '~/lib/utils/common_utils';
return expect($('.js-awards-block.current').length).toBe(0); return expect($('.js-awards-block.current').length).toBe(0);
}); });
}); });
it('should not remove emoji menu when search is clicked', function(done) {
$('.js-add-award').eq(0).click();
return lazyAssert(done, function() {
var $emojiMenu;
$emojiMenu = $('.emoji-menu');
$('.emoji-search').click();
expect($emojiMenu.length).toBe(1);
expect($emojiMenu.hasClass('is-visible')).toBe(true);
return expect($('.js-awards-block.current').length).toBe(1);
});
});
}); });
describe('::addAwardToEmojiBar', function() { describe('::addAwardToEmojiBar', function() {
it('should add emoji to votes block', function() { it('should add emoji to votes block', function() {
......
...@@ -295,6 +295,15 @@ describe('mrWidgetOptions', () => { ...@@ -295,6 +295,15 @@ describe('mrWidgetOptions', () => {
expect(notify.notifyMe).not.toHaveBeenCalled(); expect(notify.notifyMe).not.toHaveBeenCalled();
}); });
it('should not notify if no pipeline provided', () => {
vm.handleNotification({
...data,
pipeline: undefined,
});
expect(notify.notifyMe).not.toHaveBeenCalled();
});
}); });
describe('resumePolling', () => { describe('resumePolling', () => {
......
...@@ -5,6 +5,14 @@ describe Gitlab::LDAP::Config do ...@@ -5,6 +5,14 @@ describe Gitlab::LDAP::Config do
let(:config) { described_class.new('ldapmain') } let(:config) { described_class.new('ldapmain') }
describe '.servers' do
it 'returns empty array if no server information is available' do
allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false)
expect(described_class.servers).to eq []
end
end
describe '#initialize' do describe '#initialize' do
it 'requires a provider' do it 'requires a provider' do
expect { described_class.new }.to raise_error ArgumentError expect { described_class.new }.to raise_error ArgumentError
......
...@@ -724,6 +724,10 @@ describe Gitlab::OAuth::User do ...@@ -724,6 +724,10 @@ describe Gitlab::OAuth::User do
it "does not update the user location" do it "does not update the user location" do
expect(gl_user.location).not_to eq(info_hash[:address][:country]) expect(gl_user.location).not_to eq(info_hash[:address][:country])
end end
it 'does not create associated user synced attributes metadata' do
expect(gl_user.user_synced_attributes_metadata).to be_nil
end
end end
end end
......
...@@ -18,6 +18,7 @@ describe Gitlab::Regex do ...@@ -18,6 +18,7 @@ describe Gitlab::Regex do
subject { described_class.environment_name_regex } subject { described_class.environment_name_regex }
it { is_expected.to match('foo') } it { is_expected.to match('foo') }
it { is_expected.to match('a') }
it { is_expected.to match('foo-1') } it { is_expected.to match('foo-1') }
it { is_expected.to match('FOO') } it { is_expected.to match('FOO') }
it { is_expected.to match('foo/1') } it { is_expected.to match('foo/1') }
...@@ -25,6 +26,10 @@ describe Gitlab::Regex do ...@@ -25,6 +26,10 @@ describe Gitlab::Regex do
it { is_expected.not_to match('9&foo') } it { is_expected.not_to match('9&foo') }
it { is_expected.not_to match('foo-^') } it { is_expected.not_to match('foo-^') }
it { is_expected.not_to match('!!()()') } it { is_expected.not_to match('!!()()') }
it { is_expected.not_to match('/foo') }
it { is_expected.not_to match('foo/') }
it { is_expected.not_to match('/foo/') }
it { is_expected.not_to match('/') }
end end
describe '.environment_slug_regex' do describe '.environment_slug_regex' do
......
...@@ -70,5 +70,38 @@ describe Identity do ...@@ -70,5 +70,38 @@ describe Identity do
end end
end end
end end
context 'after_destroy' do
let!(:user) { create(:user) }
let(:ldap_identity) { create(:identity, provider: 'ldapmain', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', user: user) }
let(:ldap_user_synced_attributes) { { provider: 'ldapmain', name_synced: true, email_synced: true } }
let(:other_provider_user_synced_attributes) { { provider: 'other', name_synced: true, email_synced: true } }
describe 'if user synced attributes metadada provider' do
context 'matches the identity provider ' do
it 'removes the user synced attributes' do
user.create_user_synced_attributes_metadata(ldap_user_synced_attributes)
expect(user.user_synced_attributes_metadata.provider).to eq 'ldapmain'
ldap_identity.destroy
expect(user.reload.user_synced_attributes_metadata).to be_nil
end
end
context 'does not matche the identity provider' do
it 'does not remove the user synced attributes' do
user.create_user_synced_attributes_metadata(other_provider_user_synced_attributes)
expect(user.user_synced_attributes_metadata.provider).to eq 'other'
ldap_identity.destroy
expect(user.reload.user_synced_attributes_metadata.provider).to eq 'other'
end
end
end
end
end end
end end
...@@ -905,6 +905,18 @@ describe Repository do ...@@ -905,6 +905,18 @@ describe Repository do
expect(repository.license_key).to be_nil expect(repository.license_key).to be_nil
end end
it 'returns nil when the commit SHA does not exist' do
allow(repository.head_commit).to receive(:sha).and_return('1' * 40)
expect(repository.license_key).to be_nil
end
it 'returns nil when master does not exist' do
repository.rm_branch(user, 'master')
expect(repository.license_key).to be_nil
end
it 'returns the license key' do it 'returns the license key' do
repository.create_file(user, 'LICENSE', repository.create_file(user, 'LICENSE',
Licensee::License.new('mit').content, Licensee::License.new('mit').content,
......
...@@ -921,6 +921,14 @@ describe User do ...@@ -921,6 +921,14 @@ describe User do
end end
end end
describe '.find_for_database_authentication' do
it 'strips whitespace from login' do
user = create(:user)
expect(described_class.find_for_database_authentication({ login: " #{user.username} " })).to eq user
end
end
describe '.find_by_any_email' do describe '.find_by_any_email' do
it 'finds by primary email' do it 'finds by primary email' do
user = create(:user, email: 'foo@example.com') user = create(:user, email: 'foo@example.com')
......
require 'spec_helper' require 'spec_helper'
describe MergeRequests::BuildService do describe MergeRequests::BuildService do
using RSpec::Parameterized::TableSyntax
include RepoHelpers include RepoHelpers
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
...@@ -111,6 +112,7 @@ describe MergeRequests::BuildService do ...@@ -111,6 +112,7 @@ describe MergeRequests::BuildService do
context 'one commit in the diff' do context 'one commit in the diff' do
let(:commits) { Commit.decorate([commit_1], project) } let(:commits) { Commit.decorate([commit_1], project) }
let(:commit_description) { commit_1.safe_message.split(/\n+/, 2).last }
before do before do
stub_compare stub_compare
...@@ -125,7 +127,7 @@ describe MergeRequests::BuildService do ...@@ -125,7 +127,7 @@ describe MergeRequests::BuildService do
end end
it 'uses the description of the commit as the description of the merge request' do it 'uses the description of the commit as the description of the merge request' do
expect(merge_request.description).to eq(commit_1.safe_message.split(/\n+/, 2).last) expect(merge_request.description).to eq(commit_description)
end end
context 'merge request already has a description set' do context 'merge request already has a description set' do
...@@ -148,68 +150,32 @@ describe MergeRequests::BuildService do ...@@ -148,68 +150,32 @@ describe MergeRequests::BuildService do
end end
end end
context 'branch starts with issue IID followed by a hyphen' do context 'when the source branch matches an issue' do
let(:source_branch) { "#{issue.iid}-fix-issue" } where(:issue_tracker, :source_branch, :closing_message) do
:jira | 'FOO-123-fix-issue' | 'Closes FOO-123'
it 'appends "Closes #$issue-iid" to the description' do :jira | 'fix-issue' | nil
expect(merge_request.description).to eq("#{commit_1.safe_message.split(/\n+/, 2).last}\n\nCloses ##{issue.iid}") :custom_issue_tracker | '123-fix-issue' | 'Closes #123'
:custom_issue_tracker | 'fix-issue' | nil
:internal | '123-fix-issue' | 'Closes #123'
:internal | 'fix-issue' | nil
end end
context 'merge request already has a description set' do with_them do
let(:description) { 'Merge request description' } before do
if issue_tracker == :internal
it 'appends "Closes #$issue-iid" to the description' do issue.update!(iid: 123)
expect(merge_request.description).to eq("#{description}\n\nCloses ##{issue.iid}") else
create(:"#{issue_tracker}_service", project: project)
end
end end
end
context 'commit has no description' do it 'appends the closing description' do
let(:commits) { Commit.decorate([commit_2], project) } expected_description = [commit_description, closing_message].compact.join("\n\n")
it 'sets the description to "Closes #$issue-iid"' do expect(merge_request.description).to eq(expected_description)
expect(merge_request.description).to eq("Closes ##{issue.iid}")
end end
end end
end end
context 'branch starts with numeric characters followed by a hyphen with no issue tracker' do
let(:source_branch) { '12345-fix-issue' }
before do
allow(project).to receive(:external_issue_tracker).and_return(false)
allow(project).to receive(:issues_enabled?).and_return(false)
end
it 'uses the title of the commit as the title of the merge request' do
expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first)
end
it 'uses the description of the commit as the description of the merge request' do
commit_description = commit_1.safe_message.split(/\n+/, 2).last
expect(merge_request.description).to eq("#{commit_description}")
end
end
context 'branch starts with JIRA-formatted external issue IID followed by a hyphen' do
let(:source_branch) { 'EXMPL-12345-fix-issue' }
before do
allow(project).to receive(:external_issue_tracker).and_return(true)
allow(project).to receive(:issues_enabled?).and_return(false)
allow(project).to receive(:external_issue_reference_pattern).and_return(IssueTrackerService.reference_pattern)
end
it 'uses the title of the commit as the title of the merge request' do
expect(merge_request.title).to eq(commit_1.safe_message.split("\n").first)
end
it 'uses the description of the commit as the description of the merge request and appends the closes text' do
commit_description = commit_1.safe_message.split(/\n+/, 2).last
expect(merge_request.description).to eq("#{commit_description}\n\nCloses EXMPL-12345")
end
end
end end
context 'more than one commit in the diff' do context 'more than one commit in the diff' do
...@@ -239,90 +205,62 @@ describe MergeRequests::BuildService do ...@@ -239,90 +205,62 @@ describe MergeRequests::BuildService do
end end
end end
context 'branch starts with GitLab issue IID followed by a hyphen' do context 'when the source branch matches an issue' do
let(:source_branch) { "#{issue.iid}-fix-issue" } where(:issue_tracker, :source_branch, :title, :closing_message) do
:jira | 'FOO-123-fix-issue' | 'Resolve FOO-123 "Fix issue"' | 'Closes FOO-123'
it 'sets the title to: Resolves "$issue-title"' do :jira | 'fix-issue' | 'Fix issue' | nil
expect(merge_request.title).to eq("Resolve \"#{issue.title}\"") :custom_issue_tracker | '123-fix-issue' | 'Resolve #123 "Fix issue"' | 'Closes #123'
:custom_issue_tracker | 'fix-issue' | 'Fix issue' | nil
:internal | '123-fix-issue' | 'Resolve "A bug"' | 'Closes #123'
:internal | 'fix-issue' | 'Fix issue' | nil
:internal | '124-fix-issue' | '124 fix issue' | nil
end end
context 'when issue is not accessible to user' do with_them do
before do before do
project.team.truncate if issue_tracker == :internal
end issue.update!(iid: 123)
else
it 'uses branch title as the merge request title' do create(:"#{issue_tracker}_service", project: project)
expect(merge_request.title).to eq("#{issue.iid} fix issue") end
end end
end
context 'issue does not exist' do
let(:source_branch) { "#{issue.iid.succ}-fix-issue" }
it 'uses the title of the branch as the merge request title' do it 'sets the correct title' do
expect(merge_request.title).to eq("#{issue.iid.succ} fix issue") expect(merge_request.title).to eq(title)
end end
end
context 'issue is confidential' do
let(:issue_confidential) { true }
it 'uses the title of the branch as the merge request title' do it 'sets the closing description' do
expect(merge_request.title).to eq("#{issue.iid} fix issue") expect(merge_request.description).to eq(closing_message)
end end
end end
end end
context 'branch starts with numeric characters followed by a hyphen with no issue tracker' do context 'when the issue is not accessible to user' do
let(:source_branch) { '12345-fix-issue' } let(:source_branch) { "#{issue.iid}-fix-issue" }
before do before do
allow(project).to receive(:external_issue_tracker).and_return(false) project.team.truncate
allow(project).to receive(:issues_enabled?).and_return(false)
end end
it 'sets the title to the humanized branch title' do it 'uses branch title as the merge request title' do
expect(merge_request.title).to eq('12345 fix issue') expect(merge_request.title).to eq("#{issue.iid} fix issue")
end end
end
describe 'with JIRA enabled' do it 'does not set a description' do
before do expect(merge_request.description).to be_nil
allow(project).to receive(:external_issue_tracker).and_return(true)
allow(project).to receive(:issues_enabled?).and_return(false)
allow(project).to receive(:external_issue_reference_pattern).and_return(IssueTrackerService.reference_pattern)
end end
end
context 'branch does not start with JIRA-formatted external issue IID' do context 'when the issue is confidential' do
let(:source_branch) { 'test-branch' } let(:source_branch) { "#{issue.iid}-fix-issue" }
let(:issue_confidential) { true }
it 'sets the title to the humanized branch title' do it 'uses the title of the branch as the merge request title' do
expect(merge_request.title).to eq('Test branch') expect(merge_request.title).to eq("#{issue.iid} fix issue")
end
end end
context 'branch starts with JIRA-formatted external issue IID' do it 'does not set a description' do
let(:source_branch) { 'EXMPL-12345' } expect(merge_request.description).to be_nil
it 'sets the title to the humanized branch title' do
expect(merge_request.title).to eq('Resolve EXMPL-12345')
end
it 'appends the closes text' do
expect(merge_request.description).to eq('Closes EXMPL-12345')
end
context 'followed by hyphenated text' do
let(:source_branch) { 'EXMPL-12345-fix-issue' }
it 'sets the title to the humanized branch title' do
expect(merge_request.title).to eq('Resolve EXMPL-12345 "Fix issue"')
end
it 'appends the closes text' do
expect(merge_request.description).to eq('Closes EXMPL-12345')
end
end
end end
end end
end end
......
...@@ -6,6 +6,11 @@ describe CheckGcpProjectBillingWorker do ...@@ -6,6 +6,11 @@ describe CheckGcpProjectBillingWorker do
subject { described_class.new.perform('token_key') } subject { described_class.new.perform('token_key') }
before do
allow(described_class).to receive(:get_billing_state)
allow_any_instance_of(described_class).to receive(:update_billing_change_counter)
end
context 'when there is a token in redis' do context 'when there is a token in redis' do
before do before do
allow(described_class).to receive(:get_session_token).and_return(token) allow(described_class).to receive(:get_session_token).and_return(token)
...@@ -23,11 +28,8 @@ describe CheckGcpProjectBillingWorker do ...@@ -23,11 +28,8 @@ describe CheckGcpProjectBillingWorker do
end end
it 'stores billing status in redis' do it 'stores billing status in redis' do
redis_double = double
expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([double]) expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([double])
expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) expect(described_class).to receive(:set_billing_state).with(token, true)
expect(redis_double).to receive(:set).with(described_class.redis_shared_state_key_for(token), anything, anything)
subject subject
end end
...@@ -48,7 +50,7 @@ describe CheckGcpProjectBillingWorker do ...@@ -48,7 +50,7 @@ describe CheckGcpProjectBillingWorker do
context 'when there is no token in redis' do context 'when there is no token in redis' do
before do before do
allow_any_instance_of(described_class).to receive(:get_session_token).and_return(nil) allow(described_class).to receive(:get_session_token).and_return(nil)
end end
it 'does not call the service' do it 'does not call the service' do
...@@ -58,4 +60,57 @@ describe CheckGcpProjectBillingWorker do ...@@ -58,4 +60,57 @@ describe CheckGcpProjectBillingWorker do
end end
end end
end end
describe 'billing change counter' do
subject { described_class.new.perform('token_key') }
before do
allow(described_class).to receive(:get_session_token).and_return('bogustoken')
allow_any_instance_of(described_class).to receive(:try_obtain_lease_for).and_return('randomuuid')
allow(described_class).to receive(:set_billing_state)
end
context 'when previous state was false' do
before do
expect(described_class).to receive(:get_billing_state).and_return(false)
end
context 'when the current state is false' do
before do
expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([])
end
it 'increments the billing change counter' do
expect_any_instance_of(described_class).to receive_message_chain(:billing_changed_counter, :increment)
subject
end
end
context 'when the current state is true' do
before do
expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([double])
end
it 'increments the billing change counter' do
expect_any_instance_of(described_class).to receive_message_chain(:billing_changed_counter, :increment)
subject
end
end
end
context 'when previous state was true' do
before do
expect(described_class).to receive(:get_billing_state).and_return(true)
expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return([double])
end
it 'increment the billing change counter' do
expect_any_instance_of(described_class).to receive_message_chain(:billing_changed_counter, :increment)
subject
end
end
end
end end
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