Commit 439a6178 authored by Ciro Santillli's avatar Ciro Santillli

User can leave group from group page.

parent c86553cd
......@@ -135,12 +135,12 @@ class ApplicationController < ActionController::Base
end
end
def render_404
render file: Rails.root.join("public", "404"), layout: false, status: "404"
def render_403
head :forbidden
end
def render_403
render file: Rails.root.join("public", "403"), layout: false, status: "403"
def render_404
render file: Rails.root.join("public", "404"), layout: false, status: "404"
end
def require_non_empty_project
......
......@@ -7,12 +7,11 @@ class Profiles::GroupsController < ApplicationController
def leave
@users_group = group.users_groups.where(user_id: current_user.id).first
if group.last_owner?(current_user)
redirect_to(profile_groups_path, alert: "You can't leave group. You must add at least one more owner to it.")
else
if can?(current_user, :destroy, @users_group)
@users_group.destroy
redirect_to(profile_groups_path, info: "You left #{group.name} group.")
else
return render_403
end
end
......
......@@ -19,11 +19,14 @@ class UsersGroupsController < ApplicationController
def destroy
@users_group = @group.users_groups.find(params[:id])
@users_group.destroy
respond_to do |format|
format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true }
if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy
respond_to do |format|
format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true }
end
else
return render_403
end
end
......
module GroupsHelper
def remove_user_from_group_message(group, user)
"You are going to remove #{user.name} from #{group.name} Group. Are you sure?"
"Are you sure you want to remove \"#{user.name}\" from \"#{group.name}\"?"
end
def leave_group_message(group)
"Are you sure you want to leave \"#{group}\" group?"
end
def group_head_title
......
......@@ -14,6 +14,7 @@ class Ability
when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject)
when "UsersGroup" then users_group_abilities(user, subject)
else []
end.concat(global_abilities(user))
end
......@@ -219,5 +220,19 @@ class Ability
end
end
end
def users_group_abilities(user, subject)
rules = []
target_user = subject.user
group = subject.group
can_manage = group_abilities(user, group).include?(:manage_group)
if can_manage && (user != target_user)
rules << :modify
end
if !group.last_owner?(user) && (can_manage || (user == target_user))
rules << :destroy
end
rules
end
end
end
......@@ -6,7 +6,6 @@
%strong= link_to "here", help_permissions_path, class: "vlink"
%hr
- can_manage_group = current_user.can? :manage_group, @group
.ui-box
.title
%strong #{@group.name}
......@@ -15,6 +14,6 @@
(#{@members.count})
%ul.well-list
- @members.each do |member|
= render 'users_groups/users_group', member: member, show_controls: can_manage_group
- if can_manage_group
= render 'users_groups/users_group', member: member, show_controls: true
- if current_user.can? :manage_group, @group
= render "new_group_member"
......@@ -217,3 +217,4 @@
%td
%td
%td.permission-x &#10003;
%p.light Any user can remove himself from a group, unless he is the last Owner of the group.
......@@ -22,9 +22,10 @@
%i.icon-cogs
Settings
= link_to leave_profile_group_path(group), data: { confirm: "Are you sure you want to leave #{group.name} group?"}, method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do
%i.icon-signout
Leave
- if can?(current_user, :destroy, user_group)
= link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn grouped", title: 'Remove user from group' do
%i.icon-signout
Leave
= link_to group, class: 'group-name' do
%strong= group.name
......
......@@ -9,12 +9,17 @@
%span.pull-right
%strong= member.human_access
- if show_controls && can?(current_user, :manage_group, @group) && current_user != user
= link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
%i.icon-edit
= link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
%i.icon-minus.icon-white
- if show_controls
- if can?(current_user, :modify, member)
= link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do
%i.icon-edit
- if can?(current_user, :destroy, member)
- if current_user == member.user
= link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
%i.icon-minus.icon-white
- else
= link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
%i.icon-minus.icon-white
.edit-member.hide.js-toggle-content
= form_for [@group, member], remote: true do |f|
......
......@@ -2,7 +2,7 @@ Feature: Admin Groups
Background:
Given I sign in as an admin
And I have group with projects
And Create user "John Doe"
And User "John Doe" exists
And I visit admin groups page
Scenario: See group list
......
Feature: Groups
Background:
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest"
@javascript
Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page
Then I should see group "Owned" projects list
And I should see projects activity feed
Scenario: Create a group from dasboard
When I visit group "Owned" page
And I visit dashboard page
And I click new group link
And submit form with new group "Samurai" info
Then I should be redirected to group "Samurai" page
And I should see newly created group "Samurai"
Scenario: I should see group "Owned" issues list
Given project from group "Owned" has issues assigned to me
When I visit group "Owned" issues page
Then I should see issues from group "Owned" assigned to me
Scenario: I should see group "Owned" merge requests list
Given project from group "Owned" has merge requests assigned to me
When I visit group "Owned" merge requests page
Then I should see merge requests from group "Owned" assigned to me
@javascript
Scenario: I should add user to projects in group "Owned"
Given User "Mary Jane" exists
When I visit group "Owned" members page
And I select user "Mary Jane" from list with role "Reporter"
Then I should see user "Mary Jane" in team list
Scenario: I should see edit group "Owned" page
When I visit group "Owned" settings page
And I change group "Owned" name to "new-name"
Then I should see new group "Owned" name
Scenario: I edit group "Owned" avatar
When I visit group "Owned" settings page
And I change group "Owned" avatar
And I visit group "Owned" settings page
Then I should see new group "Owned" avatar
And I should see the "Remove avatar" button
Scenario: I remove group "Owned" avatar
When I visit group "Owned" settings page
And I have group "Owned" avatar
And I visit group "Owned" settings page
And I remove group "Owned" avatar
Then I should not see group "Owned" avatar
And I should not see the "Remove avatar" button
# Leave
@javascript
Scenario: Owner should be able to remove himself from group if he is not the last owner
Given "Mary Jane" is owner of group "Owned"
When I visit group "Owned" members page
Then I should see user "John Doe" in team list
Then I should see user "Mary Jane" in team list
When I click on the "Remove User From Group" button for "John Doe"
And I visit group "Owned" members page
Then I should not see user "John Doe" in team list
Then I should see user "Mary Jane" in team list
@javascript
Scenario: Owner should not be able to remove himself from group if he is the last owner
Given "Mary Jane" is guest of group "Owned"
When I visit group "Owned" members page
Then I should see user "John Doe" in team list
Then I should see user "Mary Jane" in team list
Then I should not see the "Remove User From Group" button for "Mary Jane"
@javascript
Scenario: Guest should be able to remove himself from group
Given "Mary Jane" is guest of group "Guest"
When I visit group "Guest" members page
Then I should see user "John Doe" in team list
Then I should see user "Mary Jane" in team list
When I click on the "Remove User From Group" button for "John Doe"
When I visit group "Guest" members page
Then I should not see user "John Doe" in team list
Then I should see user "Mary Jane" in team list
@javascript
Scenario: Guest should be able to remove himself from group even if he is the only user in the group
When I visit group "Guest" members page
Then I should see user "John Doe" in team list
When I click on the "Remove User From Group" button for "John Doe"
When I visit group "Guest" members page
Then I should not see user "John Doe" in team list
# Remove others
@javascript
Scenario: Owner should be able to remove other users from group
Given "Mary Jane" is owner of group "Owned"
When I visit group "Owned" members page
Then I should see user "John Doe" in team list
Then I should see user "Mary Jane" in team list
When I click on the "Remove User From Group" button for "Mary Jane"
When I visit group "Owned" members page
Then I should see user "John Doe" in team list
Then I should not see user "Mary Jane" in team list
Scenario: Guest should not be able to remove other users from group
Given "Mary Jane" is guest of group "Guest"
When I visit group "Guest" members page
Then I should see user "John Doe" in team list
Then I should see user "Mary Jane" in team list
Then I should not see the "Remove User From Group" button for "Mary Jane"
Feature: Groups
Background:
Given I sign in as a user
Scenario: Create a group from dasboard
Given I have group with projects
And I visit dashboard page
When I click new group link
And submit form with new group info
Then I should be redirected to group page
And I should see newly created group
Feature: Groups
Background:
Given I sign in as a user
And I have group with projects
@javascript
Scenario: I should see group dashboard list
When I visit group page
Then I should see projects list
And I should see projects activity feed
Scenario: I should see group issues list
Given project from group has issues assigned to me
When I visit group issues page
Then I should see issues from this group assigned to me
Scenario: I should see group merge requests list
Given project from group has merge requests assigned to me
When I visit group merge requests page
Then I should see merge requests from this group assigned to me
@javascript
Scenario: I should add user to projects in Group
Given Create user "John Doe"
When I visit group members page
And I select user "John Doe" from list with role "Reporter"
Then I should see user "John Doe" in team list
Scenario: I should see edit group page
When I visit group settings page
And I change group name
Then I should see new group name
Scenario: I edit my group avatar
When I visit group settings page
And I change my group avatar
And I visit group settings page
Then I should see new group avatar
And I should see the "Remove avatar" button
Scenario: I remove my group avatar
When I visit group settings page
And I have an group avatar
And I visit group settings page
And I remove my group avatar
Then I should not see my group avatar
And I should not see the "Remove avatar" button
Feature: Profile Group
Background:
Given I sign in as "John Doe"
And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest"
# Leave groups
@javascript
Scenario: Owner should be able to leave from group if he is not the last owner
Given "Mary Jane" is owner of group "Owned"
When I visit profile groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
When I click on the "Leave" button for group "Owned"
And I visit profile groups page
Then I should not see group "Owned" in group list
Then I should see group "Guest" in group list
@javascript
Scenario: Owner should not be able to leave from group if he is the last owner
Given "Mary Jane" is guest of group "Owned"
When I visit profile groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
Then I should not see the "Leave" button for group "Owned"
@javascript
Scenario: Guest should be able to leave from group
Given "Mary Jane" is guest of group "Guest"
When I visit profile groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
When I click on the "Leave" button for group "Guest"
When I visit profile groups page
Then I should see group "Owned" in group list
Then I should not see group "Guest" in group list
@javascript
Scenario: Guest should be able to leave from group even if he is the only user in the group
When I visit profile groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
When I click on the "Leave" button for group "Guest"
When I visit profile groups page
Then I should see group "Owned" in group list
Then I should not see group "Guest" in group list
class Groups < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedGroup
include SharedUser
include Select2Helper
Then 'I should see projects list' do
current_user.authorized_projects.each do |project|
Then 'I should see group "Owned" projects list' do
Group.find_by(name: "Owned").projects.each do |project|
page.should have_link project.name
end
end
And 'I have group with projects' do
@group = create(:group)
@group.add_owner(current_user)
@project = create(:project, namespace: @group)
@event = create(:closed_issue_event, project: @project)
@project.team << [current_user, :master]
end
And 'I should see projects activity feed' do
page.should have_content 'closed issue'
end
Then 'I should see issues from this group assigned to me' do
Then 'I should see issues from group "Owned" assigned to me' do
assigned_to_me(:issues).each do |issue|
page.should have_content issue.title
end
end
Then 'I should see merge requests from this group assigned to me' do
Then 'I should see merge requests from group "Owned" assigned to me' do
assigned_to_me(:merge_requests).each do |issue|
page.should have_content issue.title[0..80]
end
end
And 'I select user "John Doe" from list with role "Reporter"' do
user = User.find_by(name: "John Doe")
And 'I select user "Mary Jane" from list with role "Reporter"' do
user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane")
within ".users-group-form" do
select2(user.id, from: "#user_ids", multiple: true)
select "Reporter", from: "group_access"
......@@ -49,14 +41,29 @@ class Groups < Spinach::FeatureSteps
projects_with_access.should have_content("John Doe")
end
Given 'project from group has issues assigned to me' do
Then 'I should not see user "John Doe" in team list' do
projects_with_access = find(".ui-box .well-list")
projects_with_access.should_not have_content("John Doe")
end
Then 'I should see user "Mary Jane" in team list' do
projects_with_access = find(".ui-box .well-list")
projects_with_access.should have_content("Mary Jane")
end
Then 'I should not see user "Mary Jane" in team list' do
projects_with_access = find(".ui-box .well-list")
projects_with_access.should_not have_content("Mary Jane")
end
Given 'project from group "Owned" has issues assigned to me' do
create :issue,
project: project,
assignee: current_user,
author: current_user
end
Given 'project from group has merge requests assigned to me' do
Given 'project from group "Owned" has merge requests assigned to me' do
create :merge_request,
source_project: project,
target_project: project,
......@@ -68,78 +75,94 @@ class Groups < Spinach::FeatureSteps
click_link "New group"
end
And 'submit form with new group info' do
And 'submit form with new group "Samurai" info' do
fill_in 'group_name', with: 'Samurai'
fill_in 'group_description', with: 'Tokugawa Shogunate'
click_button "Create group"
end
Then 'I should see newly created group' do
Then 'I should be redirected to group "Samurai" page' do
current_path.should == group_path(Group.last)
end
Then 'I should see newly created group "Samurai"' do
page.should have_content "Samurai"
page.should have_content "Tokugawa Shogunate"
page.should have_content "You will only see events from projects in this group"
end
Then 'I should be redirected to group page' do
current_path.should == group_path(Group.last)
end
And 'I change group name' do
And 'I change group "Owned" name to "new-name"' do
fill_in 'group_name', with: 'new-name'
click_button "Save group"
end
Then 'I should see new group name' do
Then 'I should see new group "Owned" name' do
within ".navbar-gitlab" do
page.should have_content "group: new-name"
end
end
step 'I change my group avatar' do
step 'I change group "Owned" avatar' do
attach_file(:group_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
click_button "Save group"
@group.reload
Group.find_by(name: "Owned").reload
end
step 'I should see new group avatar' do
@group.avatar.should be_instance_of AttachmentUploader
@group.avatar.url.should == "/uploads/group/avatar/#{ @group.id }/gitlab_logo.png"
step 'I should see new group "Owned" avatar' do
Group.find_by(name: "Owned").avatar.should be_instance_of AttachmentUploader
Group.find_by(name: "Owned").avatar.url.should == "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/gitlab_logo.png"
end
step 'I should see the "Remove avatar" button' do
page.should have_link("Remove avatar")
end
step 'I have an group avatar' do
step 'I have group "Owned" avatar' do
attach_file(:group_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
click_button "Save group"
@group.reload
Group.find_by(name: "Owned").reload
end
step 'I remove my group avatar' do
step 'I remove group "Owned" avatar' do
click_link "Remove avatar"
@group.reload
Group.find_by(name: "Owned").reload
end
step 'I should not see my group avatar' do
@group.avatar?.should be_false
step 'I should not see group "Owned" avatar' do
Group.find_by(name: "Owned").avatar?.should be_false
end
step 'I should not see the "Remove avatar" button' do
page.should_not have_link("Remove avatar")
end
protected
step 'I click on the "Remove User From Group" button for "John Doe"' do
find(:css, 'li', text: "John Doe").find(:css, 'a.btn-remove').click
# poltergeist always confirms popups.
end
def current_group
@group ||= Group.first
step 'I click on the "Remove User From Group" button for "Mary Jane"' do
find(:css, 'li', text: "Mary Jane").find(:css, 'a.btn-remove').click
# poltergeist always confirms popups.
end
def project
current_group.projects.first
step 'I should not see the "Remove User From Group" button for "John Doe"' do
find(:css, 'li', text: "John Doe").should_not have_selector(:css, 'a.btn-remove')
# poltergeist always confirms popups.
end
step 'I should not see the "Remove User From Group" button for "Mary Jane"' do
find(:css, 'li', text: "Mary Jane").should_not have_selector(:css, 'a.btn-remove')
# poltergeist always confirms popups.
end
protected
def assigned_to_me key
project.send(key).where(assignee_id: current_user.id)
end
def project
Group.find_by(name: "Owned").projects.first
end
end
class ProfileGroup < Spinach::FeatureSteps
include SharedAuthentication
include SharedGroup
include SharedPaths
include SharedUser
# Leave
step 'I click on the "Leave" button for group "Owned"' do
find(:css, 'li', text: "Owner").find(:css, 'i.icon-signout').click
# poltergeist always confirms popups.
end
step 'I click on the "Leave" button for group "Guest"' do
find(:css, 'li', text: "Guest").find(:css, 'i.icon-signout').click
# poltergeist always confirms popups.
end
step 'I should not see the "Leave" button for group "Owned"' do
find(:css, 'li', text: "Owner").should_not have_selector(:css, 'i.icon-signout')
# poltergeist always confirms popups.
end
step 'I should not see the "Leave" button for groupr "Guest"' do
find(:css, 'li', text: "Guest").should_not have_selector(:css, 'i.icon-signout')
# poltergeist always confirms popups.
end
step 'I should see group "Owned" in group list' do
page.should have_content("Owned")
end
step 'I should not see group "Owned" in group list' do
page.should_not have_content("Owned")
end
step 'I should see group "Guest" in group list' do
page.should have_content("Guest")
end
step 'I should not see group "Guest" in group list' do
page.should_not have_content("Guest")
end
end
class ProjectNetworkGraph < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedProject
Then 'page should have network graph' do
......
......@@ -12,6 +12,14 @@ module SharedAuthentication
login_as :admin
end
step 'I sign in as "John Doe"' do
login_with(user_exists("John Doe"))
end
step 'I sign in as "Mary Jane"' do
login_with(user_exists("Mary Jane"))
end
step 'I should be redirected to sign in page' do
current_path.should == new_user_session_path
end
......
module SharedGroup
include Spinach::DSL
step '"John Doe" is owner of group "Owned"' do
is_member_of("John Doe", "Owned", Gitlab::Access::OWNER)
end
step '"John Doe" is guest of group "Guest"' do
is_member_of("John Doe", "Guest", Gitlab::Access::GUEST)
end
step '"Mary Jane" is owner of group "Owned"' do
is_member_of("Mary Jane", "Owned", Gitlab::Access::OWNER)
end
step '"Mary Jane" is guest of group "Owned"' do
is_member_of("Mary Jane", "Owned", Gitlab::Access::GUEST)
end
step '"Mary Jane" is guest of group "Guest"' do
is_member_of("Mary Jane", "Guest", Gitlab::Access::GUEST)
end
protected
def is_member_of(username, groupname, role)
@project_count ||= 0
user = User.find_by(name: username) || create(:user, name: username)
group = Group.find_by(name: groupname) || create(:group, name: groupname)
group.add_user(user, role)
project ||= create(:project, namespace: group, path: "project#{@project_count}")
event ||= create(:closed_issue_event, project: project)
project.team << [user, :master]
@project_count += 1
end
end
......@@ -17,24 +17,44 @@ module SharedPaths
# Group
# ----------------------------------------
step 'I visit group page' do
visit group_path(current_group)
step 'I visit group "Owned" page' do
visit group_path(Group.find_by(name:"Owned"))
end
step 'I visit group issues page' do
visit issues_group_path(current_group)
step 'I visit group "Owned" issues page' do
visit issues_group_path(Group.find_by(name:"Owned"))
end
step 'I visit group merge requests page' do
visit merge_requests_group_path(current_group)
step 'I visit group "Owned" merge requests page' do
visit merge_requests_group_path(Group.find_by(name:"Owned"))
end
step 'I visit group members page' do
visit members_group_path(current_group)
step 'I visit group "Owned" members page' do
visit members_group_path(Group.find_by(name:"Owned"))
end
step 'I visit group settings page' do
visit edit_group_path(current_group)
step 'I visit group "Owned" settings page' do
visit edit_group_path(Group.find_by(name:"Owned"))
end
step 'I visit group "Guest" page' do
visit group_path(Group.find_by(name:"Guest"))
end
step 'I visit group "Guest" issues page' do
visit issues_group_path(Group.find_by(name:"Guest"))
end
step 'I visit group "Guest" merge requests page' do
visit merge_requests_group_path(Group.find_by(name:"Guest"))
end
step 'I visit group "Guest" members page' do
visit members_group_path(Group.find_by(name:"Guest"))
end
step 'I visit group "Guest" settings page' do
visit edit_group_path(Group.find_by(name:"Guest"))
end
# ----------------------------------------
......@@ -93,6 +113,14 @@ module SharedPaths
visit history_profile_path
end
step 'I visit profile groups page' do
visit profile_groups_path
end
step 'I should be redirected to the profile groups page' do
current_path.should == profile_groups_path
end
# ----------------------------------------
# Admin
# ----------------------------------------
......@@ -326,4 +354,12 @@ module SharedPaths
def project
project = Project.find_by!(name: "Shop")
end
# ----------------------------------------
# Errors
# ----------------------------------------
Then 'page status code should be 404' do
page.status_code.should == 404
end
end
......@@ -58,10 +58,6 @@ module SharedProject
page.should have_content("Features:")
end
Then 'page status code should be 404' do
page.status_code.should == 404
end
def current_project
@project ||= Project.first
end
......@@ -107,24 +103,21 @@ module SharedProject
end
step '"John Doe" is authorized to private project "Enterprise"' do
user = User.find_by(name: "John Doe")
user ||= create(:user, name: "John Doe", username: "john_doe")
user = user_exists("John Doe", username: "john_doe")
project = Project.find_by(name: "Enterprise")
project ||= create(:project, name: "Enterprise", namespace: user.namespace)
project.team << [user, :master]
end
step '"John Doe" is authorized to internal project "Internal"' do
user = User.find_by(name: "John Doe")
user ||= create(:user, name: "John Doe", username: "john_doe")
user = user_exists("John Doe", username: "john_doe")
project = Project.find_by(name: "Internal")
project ||= create :project, name: 'Internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL
project.team << [user, :master]
end
step '"John Doe" is authorized to public project "Community"' do
user = User.find_by(name: "John Doe")
user ||= create(:user, name: "John Doe", username: "john_doe")
user = user_exists("John Doe", username: "john_doe")
project = Project.find_by(name: "Community")
project ||= create :project, name: 'Community', visibility_level: Gitlab::VisibilityLevel::PUBLIC
project.team << [user, :master]
......
module SharedUser
include Spinach::DSL
step 'Create user "John Doe"' do
create(:user, name: "John Doe", username: "john_doe")
step 'User "John Doe" exists' do
user_exists("John Doe", {username: "john_doe"})
end
step 'I sign in as "John Doe"' do
login_with(User.find_by(name: "John Doe"))
step 'User "Mary Jane" exists' do
user_exists("Mary Jane", {username: "mary_jane"})
end
protected
def user_exists(name, options = {})
User.find_by(name: name) || create(:user, {name: name, admin: false}.merge(options))
end
end
Feature: User
Background:
Given Create user "John Doe"
Given User "John Doe" exists
And "John Doe" is authorized to private project "Enterprise"
# Signed out
......
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