Merge branch 'confidential-issues' into 'master'
Add confidential issues Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/3678 More information: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/227 See merge request !3282
This commit is contained in:
commit
9813eac56b
|
@ -1,6 +1,7 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.6.0 (unreleased)
|
||||
- Add confidential issues
|
||||
- Bump gitlab_git to 9.0.3 (Stan Hu)
|
||||
- Support Golang subpackage fetching (Stan Hu)
|
||||
- Bump Capybara gem to 2.6.2 (Stan Hu)
|
||||
|
|
|
@ -5,7 +5,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
before_action :issue, only: [:edit, :update, :show]
|
||||
|
||||
# Allow read any issue
|
||||
before_action :authorize_read_issue!
|
||||
before_action :authorize_read_issue!, only: [:show]
|
||||
|
||||
# Allow write(create) issue
|
||||
before_action :authorize_create_issue!, only: [:new, :create]
|
||||
|
@ -128,6 +128,10 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
end
|
||||
alias_method :subscribable_resource, :issue
|
||||
|
||||
def authorize_read_issue!
|
||||
return render_404 unless can?(current_user, :read_issue, @issue)
|
||||
end
|
||||
|
||||
def authorize_update_issue!
|
||||
return render_404 unless can?(current_user, :update_issue, @issue)
|
||||
end
|
||||
|
@ -158,7 +162,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
def issue_params
|
||||
params.require(:issue).permit(
|
||||
:title, :assignee_id, :position, :description,
|
||||
:title, :assignee_id, :position, :description, :confidential,
|
||||
:milestone_id, :state_event, :task_num, label_ids: []
|
||||
)
|
||||
end
|
||||
|
|
|
@ -134,7 +134,7 @@ class ProjectsController < ApplicationController
|
|||
def autocomplete_sources
|
||||
note_type = params['type']
|
||||
note_id = params['type_id']
|
||||
autocomplete = ::Projects::AutocompleteService.new(@project)
|
||||
autocomplete = ::Projects::AutocompleteService.new(@project, current_user)
|
||||
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
|
||||
|
||||
@suggestions = {
|
||||
|
|
|
@ -19,4 +19,10 @@ class IssuesFinder < IssuableFinder
|
|||
def klass
|
||||
Issue
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_collection
|
||||
Issue.visible_to_user(current_user)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -301,7 +301,7 @@ module ApplicationHelper
|
|||
if project.nil?
|
||||
nil
|
||||
elsif current_controller?(:issues)
|
||||
project.issues.send(entity).count
|
||||
project.issues.visible_to_user(current_user).send(entity).count
|
||||
elsif current_controller?(:merge_requests)
|
||||
project.merge_requests.send(entity).count
|
||||
end
|
||||
|
|
|
@ -194,7 +194,7 @@ module EventsHelper
|
|||
end
|
||||
|
||||
def event_to_atom(xml, event)
|
||||
if event.proper?
|
||||
if event.proper?(current_user)
|
||||
xml.entry do
|
||||
event_link = event_feed_url(event)
|
||||
event_title = event_feed_title(event)
|
||||
|
|
|
@ -98,6 +98,10 @@ module IssuesHelper
|
|||
end.sort.to_sentence(last_word_connector: ', or ')
|
||||
end
|
||||
|
||||
def confidential_icon(issue)
|
||||
icon('eye-slash') if issue.confidential?
|
||||
end
|
||||
|
||||
def emoji_icon(name, unicode = nil, aliases = [])
|
||||
unicode ||= Emoji.emoji_filename(name) rescue ""
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ module MilestonesHelper
|
|||
def milestone_progress_bar(milestone)
|
||||
options = {
|
||||
class: 'progress-bar progress-bar-success',
|
||||
style: "width: #{milestone.percent_complete}%;"
|
||||
style: "width: #{milestone.percent_complete(current_user)}%;"
|
||||
}
|
||||
|
||||
content_tag :div, class: 'progress' do
|
||||
|
|
|
@ -49,7 +49,6 @@ class Ability
|
|||
rules = [
|
||||
:read_project,
|
||||
:read_wiki,
|
||||
:read_issue,
|
||||
:read_label,
|
||||
:read_milestone,
|
||||
:read_project_snippet,
|
||||
|
@ -63,6 +62,9 @@ class Ability
|
|||
# Allow to read builds by anonymous user if guests are allowed
|
||||
rules << :read_build if project.public_builds?
|
||||
|
||||
# Allow to read issues by anonymous user if issue is not confidential
|
||||
rules << :read_issue unless subject.is_a?(Issue) && subject.confidential?
|
||||
|
||||
rules - project_disabled_features_rules(project)
|
||||
else
|
||||
[]
|
||||
|
@ -321,6 +323,7 @@ class Ability
|
|||
end
|
||||
|
||||
rules += project_abilities(user, subject.project)
|
||||
rules = filter_confidential_issues_abilities(user, subject, rules) if subject.is_a?(Issue)
|
||||
rules
|
||||
end
|
||||
end
|
||||
|
@ -439,5 +442,17 @@ class Ability
|
|||
:"admin_#{name}"
|
||||
]
|
||||
end
|
||||
|
||||
def filter_confidential_issues_abilities(user, issue, rules)
|
||||
return rules if user.admin? || !issue.confidential?
|
||||
|
||||
unless issue.author == user || issue.assignee == user || issue.project.team.member?(user.id)
|
||||
rules.delete(:admin_issue)
|
||||
rules.delete(:read_issue)
|
||||
rules.delete(:update_issue)
|
||||
end
|
||||
|
||||
rules
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
module Milestoneish
|
||||
def closed_items_count
|
||||
issues.closed.size + merge_requests.closed_and_merged.size
|
||||
def closed_items_count(user = nil)
|
||||
issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size
|
||||
end
|
||||
|
||||
def total_items_count
|
||||
issues.size + merge_requests.size
|
||||
def total_items_count(user = nil)
|
||||
issues_visible_to_user(user).size + merge_requests.size
|
||||
end
|
||||
|
||||
def complete?
|
||||
total_items_count == closed_items_count
|
||||
def complete?(user = nil)
|
||||
total_items_count(user) == closed_items_count(user)
|
||||
end
|
||||
|
||||
def percent_complete
|
||||
((closed_items_count * 100) / total_items_count).abs
|
||||
def percent_complete(user = nil)
|
||||
((closed_items_count(user) * 100) / total_items_count(user)).abs
|
||||
rescue ZeroDivisionError
|
||||
0
|
||||
end
|
||||
|
@ -22,4 +22,8 @@ module Milestoneish
|
|||
|
||||
(due_date - Date.today).to_i
|
||||
end
|
||||
|
||||
def issues_visible_to_user(user = nil)
|
||||
issues.visible_to_user(user)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -73,15 +73,17 @@ class Event < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def proper?
|
||||
def proper?(user = nil)
|
||||
if push?
|
||||
true
|
||||
elsif membership_changed?
|
||||
true
|
||||
elsif created_project?
|
||||
true
|
||||
elsif issue?
|
||||
Ability.abilities.allowed?(user, :read_issue, issue)
|
||||
else
|
||||
((issue? || merge_request? || note?) && target) || milestone?
|
||||
((merge_request? || note?) && target) || milestone?
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -58,6 +58,13 @@ class Issue < ActiveRecord::Base
|
|||
attributes
|
||||
end
|
||||
|
||||
def self.visible_to_user(user)
|
||||
return where(confidential: false) if user.blank?
|
||||
return all if user.admin?
|
||||
|
||||
where('issues.confidential = false OR (issues.confidential = true AND (issues.author_id = :user_id OR issues.assignee_id = :user_id OR issues.project_id IN(:project_ids)))', user_id: user.id, project_ids: user.authorized_projects.select(:id))
|
||||
end
|
||||
|
||||
def self.reference_prefix
|
||||
'#'
|
||||
end
|
||||
|
|
|
@ -121,8 +121,8 @@ class Milestone < ActiveRecord::Base
|
|||
active? && issues.opened.count.zero?
|
||||
end
|
||||
|
||||
def is_empty?
|
||||
total_items_count.zero?
|
||||
def is_empty?(user = nil)
|
||||
total_items_count(user).zero?
|
||||
end
|
||||
|
||||
def author_id
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
module Projects
|
||||
class AutocompleteService < BaseService
|
||||
def initialize(project)
|
||||
@project = project
|
||||
end
|
||||
|
||||
def issues
|
||||
@project.issues.opened.select([:iid, :title])
|
||||
@project.issues.visible_to_user(current_user).opened.select([:iid, :title])
|
||||
end
|
||||
|
||||
def merge_requests
|
||||
|
|
|
@ -11,7 +11,7 @@ module Search
|
|||
projects = ProjectsFinder.new.execute(current_user)
|
||||
projects = projects.in_namespace(group.id) if group
|
||||
|
||||
Gitlab::SearchResults.new(projects, params[:search])
|
||||
Gitlab::SearchResults.new(current_user, projects, params[:search])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,8 @@ module Search
|
|||
end
|
||||
|
||||
def execute
|
||||
Gitlab::ProjectSearchResults.new(project,
|
||||
Gitlab::ProjectSearchResults.new(current_user,
|
||||
project,
|
||||
params[:search],
|
||||
params[:repository_ref])
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- if event.proper?
|
||||
- if event.proper?(current_user)
|
||||
.event-item{class: "#{event.body? ? "event-block" : "event-inline" }"}
|
||||
.event-item-timestamp
|
||||
#{time_ago_with_tooltip(event.created_at)}
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
%span
|
||||
Issues
|
||||
- if @project.default_issues_tracker?
|
||||
%span.count.issue_counter= number_with_delimiter(@project.issues.opened.count)
|
||||
%span.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count)
|
||||
|
||||
- if project_nav_tab? :merge_requests
|
||||
= nav_link(controller: :merge_requests) do
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
.issue-title
|
||||
%span.issue-title-text
|
||||
= confidential_icon(issue)
|
||||
= link_to_gfm issue.title, issue_path(issue), class: "title"
|
||||
%ul.controls.light
|
||||
- if issue.closed?
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
= icon('angle-double-left')
|
||||
|
||||
.issue-meta
|
||||
= confidential_icon(@issue)
|
||||
%strong.identifier
|
||||
Issue ##{@issue.iid}
|
||||
%span.creator
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
= preserve do
|
||||
= markdown @milestone.description
|
||||
|
||||
- if @milestone.complete? && @milestone.active?
|
||||
- if @milestone.complete?(current_user) && @milestone.active?
|
||||
.alert.alert-success.prepend-top-default
|
||||
%span All issues for this milestone are closed. You may close milestone now.
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.search-result-row
|
||||
%h4
|
||||
= confidential_icon(issue)
|
||||
= link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
|
||||
%span.term.str-truncated= issue.title
|
||||
.pull-right ##{issue.iid}
|
||||
|
|
|
@ -29,6 +29,15 @@
|
|||
= render 'projects/notes/hints'
|
||||
.clearfix
|
||||
.error-alert
|
||||
|
||||
- if issuable.is_a?(Issue) && !issuable.project.private?
|
||||
.form-group
|
||||
.col-sm-offset-2.col-sm-10
|
||||
.checkbox
|
||||
= f.label :confidential do
|
||||
= f.check_box :confidential
|
||||
This issue is confidential and should only be visible to team members
|
||||
|
||||
- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
|
||||
%hr
|
||||
.form-group
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
%strong #{project.name} ·
|
||||
- elsif show_full_project_name
|
||||
%strong #{project.name_with_namespace} ·
|
||||
- if issuable.is_a?(Issue)
|
||||
= confidential_icon(issuable)
|
||||
= link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
|
||||
%div{class: 'issuable-detail'}
|
||||
= link_to [project.namespace.becomes(Namespace), project, issuable] do
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
.col-sm-6
|
||||
%strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path
|
||||
.col-sm-6
|
||||
.pull-right.light #{milestone.percent_complete}% complete
|
||||
.pull-right.light #{milestone.percent_complete(current_user)}% complete
|
||||
.row
|
||||
.col-sm-6
|
||||
= link_to pluralize(milestone.issues.size, 'Issue'), issues_path
|
||||
= link_to pluralize(milestone.issues_visible_to_user(current_user).size, 'Issue'), issues_path
|
||||
·
|
||||
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
|
||||
.col-sm-6= milestone_progress_bar(milestone)
|
||||
|
|
|
@ -3,15 +3,15 @@
|
|||
.context.prepend-top-default
|
||||
.milestone-summary
|
||||
%h4 Progress
|
||||
%strong= milestone.issues.size
|
||||
%strong= milestone.issues_visible_to_user(current_user).size
|
||||
issues:
|
||||
%span.milestone-stat
|
||||
%strong= milestone.issues.opened.size
|
||||
%strong= milestone.issues_visible_to_user(current_user).opened.size
|
||||
open and
|
||||
%strong= milestone.issues.closed.size
|
||||
%strong= milestone.issues_visible_to_user(current_user).closed.size
|
||||
closed
|
||||
%span.milestone-stat
|
||||
%strong== #{milestone.percent_complete}%
|
||||
%strong== #{milestone.percent_complete(current_user)}%
|
||||
complete
|
||||
|
||||
%span.milestone-stat
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
%li.active
|
||||
= link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do
|
||||
Issues
|
||||
%span.badge= milestone.issues.size
|
||||
%span.badge= milestone.issues_visible_to_user(current_user).size
|
||||
%li
|
||||
= link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-show' => '.tab-merge-requests-buttons' do
|
||||
Merge Requests
|
||||
|
@ -21,7 +21,7 @@
|
|||
|
||||
.tab-content.milestone-content
|
||||
.tab-pane.active#tab-issues
|
||||
= render 'shared/milestones/issues_tab', issues: milestone.issues, show_project_name: show_project_name, show_full_project_name: show_full_project_name
|
||||
= render 'shared/milestones/issues_tab', issues: milestone.issues_visible_to_user(current_user), show_project_name: show_project_name, show_full_project_name: show_full_project_name
|
||||
.tab-pane#tab-merge-requests
|
||||
= render 'shared/milestones/merge_requests_tab', merge_requests: milestone.merge_requests, show_project_name: show_project_name, show_full_project_name: show_full_project_name
|
||||
.tab-pane#tab-participants
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
%h2.title
|
||||
= markdown escape_once(milestone.title), pipeline: :single_line
|
||||
|
||||
- if milestone.complete? && milestone.active?
|
||||
- if milestone.complete?(current_user) && milestone.active?
|
||||
.alert.alert-success.prepend-top-default
|
||||
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
|
||||
%span All issues for this milestone are closed. #{close_msg}
|
||||
|
@ -47,7 +47,7 @@
|
|||
- project_name = group ? ms.project.name : ms.project.name_with_namespace
|
||||
= link_to project_name, namespace_project_milestone_path(ms.project.namespace, ms.project, ms)
|
||||
%td
|
||||
= ms.issues.opened.count
|
||||
= ms.issues_visible_to_user(current_user).opened.count
|
||||
%td
|
||||
- if ms.closed?
|
||||
Closed
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class AddConfidentialToIssues < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :issues, :confidential, :boolean, default: false
|
||||
add_index :issues, :confidential
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20160316123110) do
|
||||
ActiveRecord::Schema.define(version: 20160316204731) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -416,10 +416,12 @@ ActiveRecord::Schema.define(version: 20160316123110) do
|
|||
t.string "state"
|
||||
t.integer "iid"
|
||||
t.integer "updated_by_id"
|
||||
t.boolean "confidential", default: false
|
||||
end
|
||||
|
||||
add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree
|
||||
add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree
|
||||
add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree
|
||||
add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree
|
||||
add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree
|
||||
add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
|
||||
|
|
|
@ -35,7 +35,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
|
|||
end
|
||||
|
||||
step 'I should see projects activity feed' do
|
||||
expect(page).to have_content 'closed issue'
|
||||
expect(page).to have_content 'joined project'
|
||||
end
|
||||
|
||||
step 'I should see issues from group "Owned" assigned to me' do
|
||||
|
|
|
@ -82,7 +82,7 @@ module API
|
|||
# GET /projects/:id/issues?milestone=1.0.0&state=closed
|
||||
# GET /issues?iid=42
|
||||
get ":id/issues" do
|
||||
issues = user_project.issues
|
||||
issues = user_project.issues.visible_to_user(current_user)
|
||||
issues = filter_issues_state(issues, params[:state]) unless params[:state].nil?
|
||||
issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil?
|
||||
issues = filter_by_iid(issues, params[:iid]) unless params[:iid].nil?
|
||||
|
@ -104,6 +104,7 @@ module API
|
|||
# GET /projects/:id/issues/:issue_id
|
||||
get ":id/issues/:issue_id" do
|
||||
@issue = user_project.issues.find(params[:issue_id])
|
||||
not_found! unless can?(current_user, :read_issue, @issue)
|
||||
present @issue, with: Entities::Issue
|
||||
end
|
||||
|
||||
|
|
|
@ -9,6 +9,11 @@ module Banzai
|
|||
Issue
|
||||
end
|
||||
|
||||
def self.user_can_see_reference?(user, node, context)
|
||||
issue = Issue.find(node.attr('data-issue')) rescue nil
|
||||
Ability.abilities.allowed?(user, :read_issue, issue)
|
||||
end
|
||||
|
||||
def find_object(project, id)
|
||||
project.get_issue(id)
|
||||
end
|
||||
|
|
|
@ -2,7 +2,8 @@ module Gitlab
|
|||
class ProjectSearchResults < SearchResults
|
||||
attr_reader :project, :repository_ref
|
||||
|
||||
def initialize(project, query, repository_ref = nil)
|
||||
def initialize(current_user, project, query, repository_ref = nil)
|
||||
@current_user = current_user
|
||||
@project = project
|
||||
@repository_ref = if repository_ref.present?
|
||||
repository_ref
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
module Gitlab
|
||||
class SearchResults
|
||||
attr_reader :query
|
||||
attr_reader :current_user, :query
|
||||
|
||||
# Limit search results by passed projects
|
||||
# It allows us to search only for projects user has access to
|
||||
attr_reader :limit_projects
|
||||
|
||||
def initialize(limit_projects, query)
|
||||
def initialize(current_user, limit_projects, query)
|
||||
@current_user = current_user
|
||||
@limit_projects = limit_projects || Project.all
|
||||
@query = Shellwords.shellescape(query) if query.present?
|
||||
end
|
||||
|
@ -58,7 +59,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def issues
|
||||
issues = Issue.where(project_id: project_ids_relation)
|
||||
issues = Issue.visible_to_user(current_user).where(project_id: project_ids_relation)
|
||||
|
||||
if query =~ /#(\d+)\z/
|
||||
issues = issues.where(iid: $1)
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
require('spec_helper')
|
||||
|
||||
describe Projects::IssuesController do
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project.team << [user, :developer]
|
||||
end
|
||||
|
||||
describe "GET #index" do
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
project.team << [user, :developer]
|
||||
end
|
||||
|
||||
it "returns index" do
|
||||
get :index, namespace_id: project.namespace.path, project_id: project.path
|
||||
|
||||
|
@ -38,6 +38,152 @@ describe Projects::IssuesController do
|
|||
get :index, namespace_id: project.namespace.path, project_id: project.path
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Confidential Issues' do
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:assignee) { create(:assignee) }
|
||||
let(:author) { create(:user) }
|
||||
let(:non_member) { create(:user) }
|
||||
let(:member) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let!(:issue) { create(:issue, project: project) }
|
||||
let!(:unescaped_parameter_value) { create(:issue, :confidential, project: project, author: author) }
|
||||
let!(:request_forgery_timing_attack) { create(:issue, :confidential, project: project, assignee: assignee) }
|
||||
|
||||
describe 'GET #index' do
|
||||
it 'should not list confidential issues for guests' do
|
||||
sign_out(:user)
|
||||
get_issues
|
||||
|
||||
expect(assigns(:issues)).to eq [issue]
|
||||
end
|
||||
|
||||
it 'should not list confidential issues for non project members' do
|
||||
sign_in(non_member)
|
||||
get_issues
|
||||
|
||||
expect(assigns(:issues)).to eq [issue]
|
||||
end
|
||||
|
||||
it 'should list confidential issues for author' do
|
||||
sign_in(author)
|
||||
get_issues
|
||||
|
||||
expect(assigns(:issues)).to include unescaped_parameter_value
|
||||
expect(assigns(:issues)).not_to include request_forgery_timing_attack
|
||||
end
|
||||
|
||||
it 'should list confidential issues for assignee' do
|
||||
sign_in(assignee)
|
||||
get_issues
|
||||
|
||||
expect(assigns(:issues)).not_to include unescaped_parameter_value
|
||||
expect(assigns(:issues)).to include request_forgery_timing_attack
|
||||
end
|
||||
|
||||
it 'should list confidential issues for project members' do
|
||||
sign_in(member)
|
||||
project.team << [member, :developer]
|
||||
|
||||
get_issues
|
||||
|
||||
expect(assigns(:issues)).to include unescaped_parameter_value
|
||||
expect(assigns(:issues)).to include request_forgery_timing_attack
|
||||
end
|
||||
|
||||
it 'should list confidential issues for admin' do
|
||||
sign_in(admin)
|
||||
get_issues
|
||||
|
||||
expect(assigns(:issues)).to include unescaped_parameter_value
|
||||
expect(assigns(:issues)).to include request_forgery_timing_attack
|
||||
end
|
||||
|
||||
def get_issues
|
||||
get :index,
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project.to_param
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'restricted action' do |http_status|
|
||||
it 'returns 404 for guests' do
|
||||
sign_out :user
|
||||
go(id: unescaped_parameter_value.to_param)
|
||||
|
||||
expect(response).to have_http_status :not_found
|
||||
end
|
||||
|
||||
it 'returns 404 for non project members' do
|
||||
sign_in(non_member)
|
||||
go(id: unescaped_parameter_value.to_param)
|
||||
|
||||
expect(response).to have_http_status :not_found
|
||||
end
|
||||
|
||||
it "returns #{http_status[:success]} for author" do
|
||||
sign_in(author)
|
||||
go(id: unescaped_parameter_value.to_param)
|
||||
|
||||
expect(response).to have_http_status http_status[:success]
|
||||
end
|
||||
|
||||
it "returns #{http_status[:success]} for assignee" do
|
||||
sign_in(assignee)
|
||||
go(id: request_forgery_timing_attack.to_param)
|
||||
|
||||
expect(response).to have_http_status http_status[:success]
|
||||
end
|
||||
|
||||
it "returns #{http_status[:success]} for project members" do
|
||||
sign_in(member)
|
||||
project.team << [member, :developer]
|
||||
go(id: unescaped_parameter_value.to_param)
|
||||
|
||||
expect(response).to have_http_status http_status[:success]
|
||||
end
|
||||
|
||||
it "returns #{http_status[:success]} for admin" do
|
||||
sign_in(admin)
|
||||
go(id: unescaped_parameter_value.to_param)
|
||||
|
||||
expect(response).to have_http_status http_status[:success]
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
it_behaves_like 'restricted action', success: 200
|
||||
|
||||
def go(id:)
|
||||
get :show,
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project.to_param,
|
||||
id: id
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #edit' do
|
||||
it_behaves_like 'restricted action', success: 200
|
||||
|
||||
def go(id:)
|
||||
get :edit,
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project.to_param,
|
||||
id: id
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
it_behaves_like 'restricted action', success: 302
|
||||
|
||||
def go(id:)
|
||||
put :update,
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project.to_param,
|
||||
id: id,
|
||||
issue: { title: 'New title' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,10 @@ FactoryGirl.define do
|
|||
author
|
||||
project
|
||||
|
||||
trait :confidential do
|
||||
confidential true
|
||||
end
|
||||
|
||||
trait :closed do
|
||||
state :closed
|
||||
end
|
||||
|
|
|
@ -24,7 +24,7 @@ feature 'Start new branch from an issue', feature: true do
|
|||
end
|
||||
let(:referenced_mr) do
|
||||
create(:merge_request, :simple, source_project: project, target_project: project,
|
||||
description: "Fixes ##{issue.iid}")
|
||||
description: "Fixes ##{issue.iid}", author: user)
|
||||
end
|
||||
|
||||
before do
|
||||
|
|
|
@ -44,8 +44,78 @@ describe Banzai::Filter::RedactorFilter, lib: true do
|
|||
end
|
||||
end
|
||||
|
||||
context "for user references" do
|
||||
context 'with data-issue' do
|
||||
context 'for confidential issues' do
|
||||
it 'removes references for non project members' do
|
||||
non_member = create(:user)
|
||||
project = create(:empty_project, :public)
|
||||
issue = create(:issue, :confidential, project: project)
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
|
||||
doc = filter(link, current_user: non_member)
|
||||
|
||||
expect(doc.css('a').length).to eq 0
|
||||
end
|
||||
|
||||
it 'allows references for author' do
|
||||
author = create(:user)
|
||||
project = create(:empty_project, :public)
|
||||
issue = create(:issue, :confidential, project: project, author: author)
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
|
||||
doc = filter(link, current_user: author)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'allows references for assignee' do
|
||||
assignee = create(:user)
|
||||
project = create(:empty_project, :public)
|
||||
issue = create(:issue, :confidential, project: project, assignee: assignee)
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
|
||||
doc = filter(link, current_user: assignee)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'allows references for project members' do
|
||||
member = create(:user)
|
||||
project = create(:empty_project, :public)
|
||||
project.team << [member, :developer]
|
||||
issue = create(:issue, :confidential, project: project)
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
|
||||
doc = filter(link, current_user: member)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
|
||||
it 'allows references for admin' do
|
||||
admin = create(:admin)
|
||||
project = create(:empty_project, :public)
|
||||
issue = create(:issue, :confidential, project: project)
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
|
||||
doc = filter(link, current_user: admin)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows references for non confidential issues' do
|
||||
user = create(:user)
|
||||
project = create(:empty_project, :public)
|
||||
issue = create(:issue, project: project)
|
||||
|
||||
link = reference_link(project: project.id, issue: issue.id, reference_filter: 'IssueReferenceFilter')
|
||||
doc = filter(link, current_user: user)
|
||||
|
||||
expect(doc.css('a').length).to eq 1
|
||||
end
|
||||
end
|
||||
|
||||
context "for user references" do
|
||||
context 'with data-group' do
|
||||
it 'removes unpermitted Group references' do
|
||||
user = create(:user)
|
||||
|
|
|
@ -11,6 +11,7 @@ describe Gitlab::ClosingIssueExtractor, lib: true do
|
|||
subject { described_class.new(project, project.creator) }
|
||||
|
||||
before do
|
||||
project.team << [project.creator, :developer]
|
||||
project2.team << [project.creator, :master]
|
||||
end
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::ProjectSearchResults, lib: true do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project) }
|
||||
let(:query) { 'hello world' }
|
||||
|
||||
describe 'initialize with empty ref' do
|
||||
let(:results) { Gitlab::ProjectSearchResults.new(project, query, '') }
|
||||
let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, '') }
|
||||
|
||||
it { expect(results.project).to eq(project) }
|
||||
it { expect(results.repository_ref).to be_nil }
|
||||
|
@ -14,10 +15,74 @@ describe Gitlab::ProjectSearchResults, lib: true do
|
|||
|
||||
describe 'initialize with ref' do
|
||||
let(:ref) { 'refs/heads/test' }
|
||||
let(:results) { Gitlab::ProjectSearchResults.new(project, query, ref) }
|
||||
let(:results) { Gitlab::ProjectSearchResults.new(user, project, query, ref) }
|
||||
|
||||
it { expect(results.project).to eq(project) }
|
||||
it { expect(results.repository_ref).to eq(ref) }
|
||||
it { expect(results.query).to eq('hello world') }
|
||||
end
|
||||
|
||||
describe 'confidential issues' do
|
||||
let(:query) { 'issue' }
|
||||
let(:author) { create(:user) }
|
||||
let(:assignee) { create(:user) }
|
||||
let(:non_member) { create(:user) }
|
||||
let(:member) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
|
||||
let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
|
||||
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
|
||||
|
||||
it 'should not list project confidential issues for non project members' do
|
||||
results = described_class.new(non_member, project, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).not_to include security_issue_1
|
||||
expect(issues).not_to include security_issue_2
|
||||
expect(results.issues_count).to eq 1
|
||||
end
|
||||
|
||||
it 'should list project confidential issues for author' do
|
||||
results = described_class.new(author, project, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).to include security_issue_1
|
||||
expect(issues).not_to include security_issue_2
|
||||
expect(results.issues_count).to eq 2
|
||||
end
|
||||
|
||||
it 'should list project confidential issues for assignee' do
|
||||
results = described_class.new(assignee, project.id, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).not_to include security_issue_1
|
||||
expect(issues).to include security_issue_2
|
||||
expect(results.issues_count).to eq 2
|
||||
end
|
||||
|
||||
it 'should list project confidential issues for project members' do
|
||||
project.team << [member, :developer]
|
||||
|
||||
results = described_class.new(member, project, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).to include security_issue_1
|
||||
expect(issues).to include security_issue_2
|
||||
expect(results.issues_count).to eq 3
|
||||
end
|
||||
|
||||
it 'should list all project issues for admin' do
|
||||
results = described_class.new(admin, project, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).to include security_issue_1
|
||||
expect(issues).to include security_issue_2
|
||||
expect(results.issues_count).to eq 3
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ require 'spec_helper'
|
|||
|
||||
describe Gitlab::ReferenceExtractor, lib: true do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
subject { Gitlab::ReferenceExtractor.new(project, project.creator) }
|
||||
|
||||
it 'accesses valid user objects' do
|
||||
|
@ -41,6 +42,7 @@ describe Gitlab::ReferenceExtractor, lib: true do
|
|||
end
|
||||
|
||||
it 'accesses valid issue objects' do
|
||||
project.team << [project.creator, :developer]
|
||||
@i0 = create(:issue, project: project)
|
||||
@i1 = create(:issue, project: project)
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::SearchResults do
|
||||
let(:user) { create(:user) }
|
||||
let!(:project) { create(:project, name: 'foo') }
|
||||
let!(:issue) { create(:issue, project: project, title: 'foo') }
|
||||
|
||||
|
@ -9,7 +10,7 @@ describe Gitlab::SearchResults do
|
|||
end
|
||||
|
||||
let!(:milestone) { create(:milestone, project: project, title: 'foo') }
|
||||
let(:results) { described_class.new(Project.all, 'foo') }
|
||||
let(:results) { described_class.new(user, Project.all, 'foo') }
|
||||
|
||||
describe '#total_count' do
|
||||
it 'returns the total amount of search hits' do
|
||||
|
@ -52,4 +53,92 @@ describe Gitlab::SearchResults do
|
|||
expect(results.empty?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'confidential issues' do
|
||||
let(:project_1) { create(:empty_project) }
|
||||
let(:project_2) { create(:empty_project) }
|
||||
let(:project_3) { create(:empty_project) }
|
||||
let(:project_4) { create(:empty_project) }
|
||||
let(:query) { 'issue' }
|
||||
let(:limit_projects) { Project.where(id: [project_1.id, project_2.id, project_3.id]) }
|
||||
let(:author) { create(:user) }
|
||||
let(:assignee) { create(:user) }
|
||||
let(:non_member) { create(:user) }
|
||||
let(:member) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let!(:issue) { create(:issue, project: project_1, title: 'Issue 1') }
|
||||
let!(:security_issue_1) { create(:issue, :confidential, project: project_1, title: 'Security issue 1', author: author) }
|
||||
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project_1, assignee: assignee) }
|
||||
let!(:security_issue_3) { create(:issue, :confidential, project: project_2, title: 'Security issue 3', author: author) }
|
||||
let!(:security_issue_4) { create(:issue, :confidential, project: project_3, title: 'Security issue 4', assignee: assignee) }
|
||||
let!(:security_issue_5) { create(:issue, :confidential, project: project_4, title: 'Security issue 5') }
|
||||
|
||||
it 'should not list confidential issues for non project members' do
|
||||
results = described_class.new(non_member, limit_projects, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).not_to include security_issue_1
|
||||
expect(issues).not_to include security_issue_2
|
||||
expect(issues).not_to include security_issue_3
|
||||
expect(issues).not_to include security_issue_4
|
||||
expect(issues).not_to include security_issue_5
|
||||
expect(results.issues_count).to eq 1
|
||||
end
|
||||
|
||||
it 'should list confidential issues for author' do
|
||||
results = described_class.new(author, limit_projects, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).to include security_issue_1
|
||||
expect(issues).not_to include security_issue_2
|
||||
expect(issues).to include security_issue_3
|
||||
expect(issues).not_to include security_issue_4
|
||||
expect(issues).not_to include security_issue_5
|
||||
expect(results.issues_count).to eq 3
|
||||
end
|
||||
|
||||
it 'should list confidential issues for assignee' do
|
||||
results = described_class.new(assignee, limit_projects, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).not_to include security_issue_1
|
||||
expect(issues).to include security_issue_2
|
||||
expect(issues).not_to include security_issue_3
|
||||
expect(issues).to include security_issue_4
|
||||
expect(issues).not_to include security_issue_5
|
||||
expect(results.issues_count).to eq 3
|
||||
end
|
||||
|
||||
it 'should list confidential issues for project members' do
|
||||
project_1.team << [member, :developer]
|
||||
project_2.team << [member, :developer]
|
||||
|
||||
results = described_class.new(member, limit_projects, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).to include security_issue_1
|
||||
expect(issues).to include security_issue_2
|
||||
expect(issues).to include security_issue_3
|
||||
expect(issues).not_to include security_issue_4
|
||||
expect(issues).not_to include security_issue_5
|
||||
expect(results.issues_count).to eq 4
|
||||
end
|
||||
|
||||
it 'should list all issues for admin' do
|
||||
results = described_class.new(admin, limit_projects, query)
|
||||
issues = results.objects('issues')
|
||||
|
||||
expect(issues).to include issue
|
||||
expect(issues).to include security_issue_1
|
||||
expect(issues).to include security_issue_2
|
||||
expect(issues).to include security_issue_3
|
||||
expect(issues).to include security_issue_4
|
||||
expect(issues).not_to include security_issue_5
|
||||
expect(results.issues_count).to eq 5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -86,10 +86,21 @@ eos
|
|||
let(:issue) { create :issue, project: project }
|
||||
let(:other_project) { create :project, :public }
|
||||
let(:other_issue) { create :issue, project: other_project }
|
||||
let(:commiter) { create :user }
|
||||
|
||||
before do
|
||||
project.team << [commiter, :developer]
|
||||
other_project.team << [commiter, :developer]
|
||||
end
|
||||
|
||||
it 'detects issues that this commit is marked as closing' do
|
||||
ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}"
|
||||
allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid} and #{ext_ref}")
|
||||
|
||||
allow(commit).to receive_messages(
|
||||
safe_message: "Fixes ##{issue.iid} and #{ext_ref}",
|
||||
committer_email: commiter.email
|
||||
)
|
||||
|
||||
expect(commit.closes_issues).to include(issue)
|
||||
expect(commit.closes_issues).to include(other_issue)
|
||||
end
|
||||
|
|
|
@ -48,7 +48,8 @@ describe Issue, "Mentionable" do
|
|||
|
||||
describe '#create_new_cross_references!' do
|
||||
let(:project) { create(:project) }
|
||||
let(:issues) { create_list(:issue, 2, project: project) }
|
||||
let(:author) { create(:author) }
|
||||
let(:issues) { create_list(:issue, 2, project: project, author: author) }
|
||||
|
||||
context 'before changes are persisted' do
|
||||
it 'ignores pre-existing references' do
|
||||
|
@ -91,7 +92,7 @@ describe Issue, "Mentionable" do
|
|||
end
|
||||
|
||||
def create_issue(description:)
|
||||
create(:issue, project: project, description: description)
|
||||
create(:issue, project: project, description: description, author: author)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Milestone, 'Milestoneish' do
|
||||
let(:author) { create(:user) }
|
||||
let(:assignee) { create(:user) }
|
||||
let(:non_member) { create(:user) }
|
||||
let(:member) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let(:project) { create(:project, :public) }
|
||||
let(:milestone) { create(:milestone, project: project) }
|
||||
let!(:issue) { create(:issue, project: project, milestone: milestone) }
|
||||
let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) }
|
||||
let!(:security_issue_2) { create(:issue, :confidential, project: project, assignee: assignee, milestone: milestone) }
|
||||
let!(:closed_issue_1) { create(:issue, :closed, project: project, milestone: milestone) }
|
||||
let!(:closed_issue_2) { create(:issue, :closed, project: project, milestone: milestone) }
|
||||
let!(:closed_security_issue_1) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
|
||||
let!(:closed_security_issue_2) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
|
||||
let!(:closed_security_issue_3) { create(:issue, :confidential, :closed, project: project, author: author, milestone: milestone) }
|
||||
let!(:closed_security_issue_4) { create(:issue, :confidential, :closed, project: project, assignee: assignee, milestone: milestone) }
|
||||
let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, milestone: milestone) }
|
||||
|
||||
before do
|
||||
project.team << [member, :developer]
|
||||
end
|
||||
|
||||
describe '#closed_items_count' do
|
||||
it 'should not count confidential issues for non project members' do
|
||||
expect(milestone.closed_items_count(non_member)).to eq 2
|
||||
end
|
||||
|
||||
it 'should count confidential issues for author' do
|
||||
expect(milestone.closed_items_count(author)).to eq 4
|
||||
end
|
||||
|
||||
it 'should count confidential issues for assignee' do
|
||||
expect(milestone.closed_items_count(assignee)).to eq 4
|
||||
end
|
||||
|
||||
it 'should count confidential issues for project members' do
|
||||
expect(milestone.closed_items_count(member)).to eq 6
|
||||
end
|
||||
|
||||
it 'should count all issues for admin' do
|
||||
expect(milestone.closed_items_count(admin)).to eq 6
|
||||
end
|
||||
end
|
||||
|
||||
describe '#total_items_count' do
|
||||
it 'should not count confidential issues for non project members' do
|
||||
expect(milestone.total_items_count(non_member)).to eq 4
|
||||
end
|
||||
|
||||
it 'should count confidential issues for author' do
|
||||
expect(milestone.total_items_count(author)).to eq 7
|
||||
end
|
||||
|
||||
it 'should count confidential issues for assignee' do
|
||||
expect(milestone.total_items_count(assignee)).to eq 7
|
||||
end
|
||||
|
||||
it 'should count confidential issues for project members' do
|
||||
expect(milestone.total_items_count(member)).to eq 10
|
||||
end
|
||||
|
||||
it 'should count all issues for admin' do
|
||||
expect(milestone.total_items_count(admin)).to eq 10
|
||||
end
|
||||
end
|
||||
|
||||
describe '#complete?' do
|
||||
it 'returns false when has items opened' do
|
||||
expect(milestone.complete?(non_member)).to eq false
|
||||
end
|
||||
|
||||
it 'returns true when all items are closed' do
|
||||
issue.close
|
||||
merge_request.close
|
||||
|
||||
expect(milestone.complete?(non_member)).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#percent_complete' do
|
||||
it 'should not count confidential issues for non project members' do
|
||||
expect(milestone.percent_complete(non_member)).to eq 50
|
||||
end
|
||||
|
||||
it 'should count confidential issues for author' do
|
||||
expect(milestone.percent_complete(author)).to eq 57
|
||||
end
|
||||
|
||||
it 'should count confidential issues for assignee' do
|
||||
expect(milestone.percent_complete(assignee)).to eq 57
|
||||
end
|
||||
|
||||
it 'should count confidential issues for project members' do
|
||||
expect(milestone.percent_complete(member)).to eq 60
|
||||
end
|
||||
|
||||
it 'should count confidential issues for admin' do
|
||||
expect(milestone.percent_complete(admin)).to eq 60
|
||||
end
|
||||
end
|
||||
end
|
|
@ -65,6 +65,42 @@ describe Event, models: true do
|
|||
it { expect(@event.author).to eq(@user) }
|
||||
end
|
||||
|
||||
describe '#proper?' do
|
||||
context 'issue event' do
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:non_member) { create(:user) }
|
||||
let(:member) { create(:user) }
|
||||
let(:author) { create(:author) }
|
||||
let(:assignee) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let(:event) { Event.new(project: project, action: Event::CREATED, target: issue, author_id: author.id) }
|
||||
|
||||
before do
|
||||
project.team << [member, :developer]
|
||||
end
|
||||
|
||||
context 'for non confidential issues' do
|
||||
let(:issue) { create(:issue, project: project, author: author, assignee: assignee) }
|
||||
|
||||
it { expect(event.proper?(non_member)).to eq true }
|
||||
it { expect(event.proper?(author)).to eq true }
|
||||
it { expect(event.proper?(assignee)).to eq true }
|
||||
it { expect(event.proper?(member)).to eq true }
|
||||
it { expect(event.proper?(admin)).to eq true }
|
||||
end
|
||||
|
||||
context 'for confidential issues' do
|
||||
let(:issue) { create(:issue, :confidential, project: project, author: author, assignee: assignee) }
|
||||
|
||||
it { expect(event.proper?(non_member)).to eq false }
|
||||
it { expect(event.proper?(author)).to eq true }
|
||||
it { expect(event.proper?(assignee)).to eq true }
|
||||
it { expect(event.proper?(member)).to eq true }
|
||||
it { expect(event.proper?(admin)).to eq true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.limit_recent' do
|
||||
let!(:event1) { create(:closed_issue_event) }
|
||||
let!(:event2) { create(:closed_issue_event) }
|
||||
|
|
|
@ -150,6 +150,7 @@ describe MergeRequest, models: true do
|
|||
let(:commit2) { double('commit2', safe_message: "Fixes #{issue1.to_reference}") }
|
||||
|
||||
before do
|
||||
subject.project.team << [subject.author, :developer]
|
||||
allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
|
||||
end
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ describe Milestone, models: true do
|
|||
|
||||
let(:milestone) { create(:milestone) }
|
||||
let(:issue) { create(:issue) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
describe "unique milestone title per project" do
|
||||
it "shouldn't accept the same title in a project twice" do
|
||||
|
@ -50,18 +51,17 @@ describe Milestone, models: true do
|
|||
describe "#percent_complete" do
|
||||
it "should not count open issues" do
|
||||
milestone.issues << issue
|
||||
expect(milestone.percent_complete).to eq(0)
|
||||
expect(milestone.percent_complete(user)).to eq(0)
|
||||
end
|
||||
|
||||
it "should count closed issues" do
|
||||
issue.close
|
||||
milestone.issues << issue
|
||||
expect(milestone.percent_complete).to eq(100)
|
||||
expect(milestone.percent_complete(user)).to eq(100)
|
||||
end
|
||||
|
||||
it "should recover from dividing by zero" do
|
||||
expect(milestone.issues).to receive(:size).and_return(0)
|
||||
expect(milestone.percent_complete).to eq(0)
|
||||
expect(milestone.percent_complete(user)).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -103,7 +103,7 @@ describe Milestone, models: true do
|
|||
)
|
||||
end
|
||||
|
||||
it { expect(milestone.percent_complete).to eq(75) }
|
||||
it { expect(milestone.percent_complete(user)).to eq(75) }
|
||||
end
|
||||
|
||||
describe :items_count do
|
||||
|
@ -113,23 +113,23 @@ describe Milestone, models: true do
|
|||
milestone.merge_requests << create(:merge_request)
|
||||
end
|
||||
|
||||
it { expect(milestone.closed_items_count).to eq(1) }
|
||||
it { expect(milestone.total_items_count).to eq(3) }
|
||||
it { expect(milestone.is_empty?).to be_falsey }
|
||||
it { expect(milestone.closed_items_count(user)).to eq(1) }
|
||||
it { expect(milestone.total_items_count(user)).to eq(3) }
|
||||
it { expect(milestone.is_empty?(user)).to be_falsey }
|
||||
end
|
||||
|
||||
describe :can_be_closed? do
|
||||
it { expect(milestone.can_be_closed?).to be_truthy }
|
||||
end
|
||||
|
||||
describe :is_empty? do
|
||||
describe :total_items_count do
|
||||
before do
|
||||
create :closed_issue, milestone: milestone
|
||||
create :merge_request, milestone: milestone
|
||||
end
|
||||
|
||||
it 'Should return total count of issues and merge requests assigned to milestone' do
|
||||
expect(milestone.total_items_count).to eq 2
|
||||
expect(milestone.total_items_count(user)).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,11 @@ require 'spec_helper'
|
|||
describe API::API, api: true do
|
||||
include ApiHelpers
|
||||
let(:user) { create(:user) }
|
||||
let!(:project) { create(:project, namespace: user.namespace ) }
|
||||
let(:non_member) { create(:user) }
|
||||
let(:author) { create(:author) }
|
||||
let(:assignee) { create(:assignee) }
|
||||
let(:admin) { create(:admin) }
|
||||
let!(:project) { create(:project, :public, namespace: user.namespace ) }
|
||||
let!(:closed_issue) do
|
||||
create :closed_issue,
|
||||
author: user,
|
||||
|
@ -12,6 +16,13 @@ describe API::API, api: true do
|
|||
state: :closed,
|
||||
milestone: milestone
|
||||
end
|
||||
let!(:confidential_issue) do
|
||||
create :issue,
|
||||
:confidential,
|
||||
project: project,
|
||||
author: author,
|
||||
assignee: assignee
|
||||
end
|
||||
let!(:issue) do
|
||||
create :issue,
|
||||
author: user,
|
||||
|
@ -123,10 +134,43 @@ describe API::API, api: true do
|
|||
let(:base_url) { "/projects/#{project.id}" }
|
||||
let(:title) { milestone.title }
|
||||
|
||||
it "should return project issues" do
|
||||
it 'should return project issues without confidential issues for non project members' do
|
||||
get api("#{base_url}/issues", non_member)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(2)
|
||||
expect(json_response.first['title']).to eq(issue.title)
|
||||
end
|
||||
|
||||
it 'should return project confidential issues for author' do
|
||||
get api("#{base_url}/issues", author)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(3)
|
||||
expect(json_response.first['title']).to eq(issue.title)
|
||||
end
|
||||
|
||||
it 'should return project confidential issues for assignee' do
|
||||
get api("#{base_url}/issues", assignee)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(3)
|
||||
expect(json_response.first['title']).to eq(issue.title)
|
||||
end
|
||||
|
||||
it 'should return project issues with confidential issues for project members' do
|
||||
get api("#{base_url}/issues", user)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(3)
|
||||
expect(json_response.first['title']).to eq(issue.title)
|
||||
end
|
||||
|
||||
it 'should return project confidential issues for admin' do
|
||||
get api("#{base_url}/issues", admin)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response).to be_an Array
|
||||
expect(json_response.length).to eq(3)
|
||||
expect(json_response.first['title']).to eq(issue.title)
|
||||
end
|
||||
|
||||
|
@ -206,6 +250,41 @@ describe API::API, api: true do
|
|||
get api("/projects/#{project.id}/issues/54321", user)
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
context 'confidential issues' do
|
||||
it "should return 404 for non project members" do
|
||||
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member)
|
||||
expect(response.status).to eq(404)
|
||||
end
|
||||
|
||||
it "should return confidential issue for project members" do
|
||||
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", user)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response['title']).to eq(confidential_issue.title)
|
||||
expect(json_response['iid']).to eq(confidential_issue.iid)
|
||||
end
|
||||
|
||||
it "should return confidential issue for author" do
|
||||
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", author)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response['title']).to eq(confidential_issue.title)
|
||||
expect(json_response['iid']).to eq(confidential_issue.iid)
|
||||
end
|
||||
|
||||
it "should return confidential issue for assignee" do
|
||||
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", assignee)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response['title']).to eq(confidential_issue.title)
|
||||
expect(json_response['iid']).to eq(confidential_issue.iid)
|
||||
end
|
||||
|
||||
it "should return confidential issue for admin" do
|
||||
get api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin)
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response['title']).to eq(confidential_issue.title)
|
||||
expect(json_response['iid']).to eq(confidential_issue.iid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /projects/:id/issues" do
|
||||
|
@ -294,6 +373,35 @@ describe API::API, api: true do
|
|||
expect(response.status).to eq(400)
|
||||
expect(json_response['message']['labels']['?']['title']).to eq(['is invalid'])
|
||||
end
|
||||
|
||||
context 'confidential issues' do
|
||||
it "should return 403 for non project members" do
|
||||
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", non_member),
|
||||
title: 'updated title'
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
|
||||
it "should update a confidential issue for project members" do
|
||||
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", user),
|
||||
title: 'updated title'
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response['title']).to eq('updated title')
|
||||
end
|
||||
|
||||
it "should update a confidential issue for author" do
|
||||
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", author),
|
||||
title: 'updated title'
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response['title']).to eq('updated title')
|
||||
end
|
||||
|
||||
it "should update a confidential issue for admin" do
|
||||
put api("/projects/#{project.id}/issues/#{confidential_issue.id}", admin),
|
||||
title: 'updated title'
|
||||
expect(response.status).to eq(200)
|
||||
expect(json_response['title']).to eq('updated title')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /projects/:id/issues/:issue_id to update labels' do
|
||||
|
|
|
@ -215,12 +215,16 @@ describe GitPushService, services: true do
|
|||
let(:commit) { project.commit }
|
||||
|
||||
before do
|
||||
project.team << [commit_author, :developer]
|
||||
project.team << [user, :developer]
|
||||
|
||||
allow(commit).to receive_messages(
|
||||
safe_message: "this commit \n mentions #{issue.to_reference}",
|
||||
references: [issue],
|
||||
author_name: commit_author.name,
|
||||
author_email: commit_author.email
|
||||
)
|
||||
|
||||
allow(project.repository).to receive(:commits_between).and_return([commit])
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Projects::AutocompleteService, services: true do
|
||||
describe '#issues' do
|
||||
describe 'confidential issues' do
|
||||
let(:author) { create(:user) }
|
||||
let(:assignee) { create(:user) }
|
||||
let(:non_member) { create(:user) }
|
||||
let(:member) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
|
||||
let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
|
||||
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignee: assignee) }
|
||||
|
||||
it 'should not list project confidential issues for guests' do
|
||||
autocomplete = described_class.new(project, nil)
|
||||
issues = autocomplete.issues.map(&:iid)
|
||||
|
||||
expect(issues).to include issue.iid
|
||||
expect(issues).not_to include security_issue_1.iid
|
||||
expect(issues).not_to include security_issue_2.iid
|
||||
expect(issues.count).to eq 1
|
||||
end
|
||||
|
||||
it 'should not list project confidential issues for non project members' do
|
||||
autocomplete = described_class.new(project, non_member)
|
||||
issues = autocomplete.issues.map(&:iid)
|
||||
|
||||
expect(issues).to include issue.iid
|
||||
expect(issues).not_to include security_issue_1.iid
|
||||
expect(issues).not_to include security_issue_2.iid
|
||||
expect(issues.count).to eq 1
|
||||
end
|
||||
|
||||
it 'should list project confidential issues for author' do
|
||||
autocomplete = described_class.new(project, author)
|
||||
issues = autocomplete.issues.map(&:iid)
|
||||
|
||||
expect(issues).to include issue.iid
|
||||
expect(issues).to include security_issue_1.iid
|
||||
expect(issues).not_to include security_issue_2.iid
|
||||
expect(issues.count).to eq 2
|
||||
end
|
||||
|
||||
it 'should list project confidential issues for assignee' do
|
||||
autocomplete = described_class.new(project, assignee)
|
||||
issues = autocomplete.issues.map(&:iid)
|
||||
|
||||
expect(issues).to include issue.iid
|
||||
expect(issues).not_to include security_issue_1.iid
|
||||
expect(issues).to include security_issue_2.iid
|
||||
expect(issues.count).to eq 2
|
||||
end
|
||||
|
||||
it 'should list project confidential issues for project members' do
|
||||
project.team << [member, :developer]
|
||||
|
||||
autocomplete = described_class.new(project, member)
|
||||
issues = autocomplete.issues.map(&:iid)
|
||||
|
||||
expect(issues).to include issue.iid
|
||||
expect(issues).to include security_issue_1.iid
|
||||
expect(issues).to include security_issue_2.iid
|
||||
expect(issues.count).to eq 3
|
||||
end
|
||||
|
||||
it 'should list all project issues for admin' do
|
||||
autocomplete = described_class.new(project, admin)
|
||||
issues = autocomplete.issues.map(&:iid)
|
||||
|
||||
expect(issues).to include issue.iid
|
||||
expect(issues).to include security_issue_1.iid
|
||||
expect(issues).to include security_issue_2.iid
|
||||
expect(issues.count).to eq 3
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -52,6 +52,8 @@ shared_context 'mentionable context' do
|
|||
end
|
||||
|
||||
set_mentionable_text.call(ref_string)
|
||||
|
||||
project.team << [author, :developer]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue