# frozen_string_literal: true require 'spec_helper' RSpec.describe Issue do include ExternalAuthorizationServiceHelpers using RSpec::Parameterized::TableSyntax describe 'associations' do subject { build(:issue) } it { is_expected.to have_many(:resource_weight_events) } it { is_expected.to have_many(:resource_iteration_events) } it { is_expected.to have_one(:issuable_sla) } it { is_expected.to have_many(:metric_images) } it { is_expected.to have_one(:requirement) } it { is_expected.to have_many(:test_reports) } context 'for an issue with associated test report' do let_it_be(:requirement_issue) do ri = create(:requirement_issue) create(:test_report, requirement_issue: ri, requirement: nil) ri end context 'for an issue of type Requirement' do specify { expect(requirement_issue.test_reports.count).to eq(1) } end context 'for an issue of a different type' do before do requirement_issue.update_attribute(:issue_type, :incident) end specify { expect(requirement_issue.test_reports.count).to eq(0) } end end end describe 'modules' do subject { build(:issue) } it { is_expected.to include_module(EE::WeightEventable) } end context 'scopes' do describe '.counts_by_health_status' do it 'returns counts grouped by health_status' do create(:issue, health_status: :on_track) create(:issue, health_status: :on_track) create(:issue, health_status: :at_risk) expect(Issue.counts_by_health_status).to eq({ 'on_track' => 2, 'at_risk' => 1 } ) end end describe '.on_status_page' do let_it_be(:status_page_setting) { create(:status_page_setting, :enabled) } let_it_be(:project) { status_page_setting.project } let_it_be(:published_issue) { create(:issue, :published, project: project) } let_it_be(:confidential_issue) { create(:issue, :published, :confidential, project: project) } let_it_be(:nonpublished_issue) { create(:issue, project: project) } it { expect(Issue.on_status_page.count).to eq(1) } it { expect(Issue.on_status_page.first).to eq(published_issue) } context 'with status page disabled' do before do status_page_setting.update!(enabled: false) end it { expect(Issue.on_status_page.count).to eq(0) } end end describe '.with_feature' do let_it_be(:project) { create(:project) } let_it_be(:issue) { create(:issue, project: project) } let_it_be(:incident) { create(:incident, project: project) } let_it_be(:test_case) { create(:quality_test_case, project: project) } it 'gives issues that support the given feature', :aggregate_failures do expect(described_class.with_feature('epics')) .to contain_exactly(issue) expect(described_class.with_feature('sla')) .to contain_exactly(incident) end it 'returns an empty collection when given an unknown feature' do expect(described_class.with_feature('something-unknown')) .to be_empty end end context 'epics' do let_it_be(:epic1) { create(:epic) } let_it_be(:epic2) { create(:epic) } let_it_be(:epic_issue1) { create(:epic_issue, epic: epic1, relative_position: 2) } let_it_be(:epic_issue2) { create(:epic_issue, epic: epic2, relative_position: 1) } let_it_be(:issue_no_epic) { create(:issue) } before do stub_licensed_features(epics: true) end describe '.no_epic' do it 'returns only issues without an epic assigned' do expect(described_class.count).to eq 3 expect(described_class.no_epic).to eq [issue_no_epic] end end describe '.any_epic' do it 'returns only issues with an epic assigned' do expect(described_class.count).to eq 3 expect(described_class.any_epic).to contain_exactly(epic_issue1.issue, epic_issue2.issue) end end describe '.in_epics' do it 'returns only issues in selected epics' do expect(described_class.count).to eq 3 expect(described_class.in_epics([epic1])).to eq [epic_issue1.issue] end end describe '.not_in_epics' do it 'returns only issues not in selected epics' do expect(described_class.count).to eq 3 expect(described_class.not_in_epics([epic1])).to match_array([epic_issue2.issue, issue_no_epic]) end end describe '.distinct_epic_ids' do it 'returns distinct epic ids' do expect(described_class.distinct_epic_ids.map(&:epic_id)).to match_array([epic1.id, epic2.id]) end context 'when issues are grouped by labels' do let_it_be(:label_link1) { create(:label_link, target: epic_issue1.issue) } let_it_be(:label_link2) { create(:label_link, target: epic_issue1.issue) } it 'respects query grouping and returns distinct epic ids' do ids = described_class.with_label( [label_link1.label.title, label_link2.label.title] ).distinct_epic_ids.map(&:epic_id) expect(ids).to eq([epic1.id]) end end end describe '.sorted_by_epic_position' do it 'sorts by epic relative position' do expect(described_class.sorted_by_epic_position.ids).to eq([epic_issue2.issue_id, epic_issue1.issue_id]) end end end context 'iterations' do let_it_be(:iteration1) { create(:iteration) } let_it_be(:iteration2) { create(:iteration) } let_it_be(:iteration1_issue) { create(:issue, iteration: iteration1) } let_it_be(:iteration2_issue) { create(:issue, iteration: iteration2) } let_it_be(:issue_no_iteration) { create(:issue) } before do stub_licensed_features(iterations: true) end describe '.no_iteration' do it 'returns only issues without an iteration assigned' do expect(described_class.count).to eq 3 expect(described_class.no_iteration).to eq [issue_no_iteration] end end describe '.any_iteration' do it 'returns only issues with an iteration assigned' do expect(described_class.count).to eq 3 expect(described_class.any_iteration).to contain_exactly(iteration1_issue, iteration2_issue) end end describe '.in_iterations' do it 'returns only issues in selected iterations' do expect(described_class.count).to eq 3 expect(described_class.in_iterations([iteration1])).to eq [iteration1_issue] end end describe '.not_in_iterations' do it 'returns issues not in selected iterations' do expect(described_class.count).to eq 3 expect(described_class.not_in_iterations([iteration1])).to contain_exactly(iteration2_issue, issue_no_iteration) end end describe '.with_iteration_title' do it 'returns only issues with iterations that match the title' do expect(described_class.with_iteration_title(iteration1.title)).to eq [iteration1_issue] end end describe '.without_iteration_title' do it 'returns only issues without iterations or have iterations that do not match the title' do expect(described_class.without_iteration_title(iteration1.title)).to contain_exactly(issue_no_iteration, iteration2_issue) end end end context 'status page published' do let_it_be(:not_published) { create(:issue) } let_it_be(:published) { create(:issue, :published) } describe '.order_status_page_published_first' do subject { described_class.order_status_page_published_first } it { is_expected.to eq([published, not_published]) } end describe '.order_status_page_published_last' do subject { described_class.order_status_page_published_last } it { is_expected.to eq([not_published, published]) } end end context 'sla due at' do let_it_be(:project) { create(:project) } let_it_be(:sla_due_first) { create(:issue, project: project) } let_it_be(:sla_due_last) { create(:issue, project: project) } let_it_be(:no_sla) { create(:issue, project: project) } before_all do create(:issuable_sla, :exceeded, issue: sla_due_first) create(:issuable_sla, issue: sla_due_last) end describe '.order_sla_due_at_asc' do subject { described_class.order_sla_due_at_asc } it { is_expected.to eq([sla_due_first, sla_due_last, no_sla]) } end describe '.order_sla_due_at_desc' do subject { described_class.order_sla_due_at_desc } it { is_expected.to eq([sla_due_last, sla_due_first, no_sla]) } end end end describe 'validations' do describe 'weight' do subject { build(:issue) } it 'is not valid when negative number' do subject.weight = -1 expect(subject).not_to be_valid expect(subject.errors[:weight]).not_to be_empty end it 'is valid when non-negative' do subject.weight = 0 expect(subject).to be_valid subject.weight = 1 expect(subject).to be_valid end end describe 'confidential' do let_it_be(:epic) { create(:epic, :confidential) } context 'when assigning an epic to a new issue' do let(:issue) { build(:issue, confidential: confidential) } context 'when an issue is not confidential' do let(:confidential) { false } it 'is not valid' do issue.epic = epic expect(issue).not_to be_valid expect(issue.errors.messages[:issue]).to include(/this issue cannot be assigned to a confidential epic since it is public/) end end context 'when an issue is confidential' do let(:confidential) { true } it 'is valid' do issue.epic = epic expect(issue).to be_valid end end end context 'when updating an existing issue' do let(:confidential) { true } let(:issue) { create(:issue, confidential: confidential) } context 'when an issue is assigned to the confidential epic' do before do issue.update!(epic: epic) end context 'when changing issue to public' do it 'is not valid' do issue.confidential = false expect(issue).not_to be_valid expect(issue.errors.messages[:issue]).to include(/this issue cannot be made public since it belongs to a confidential epic/) end end end context 'when assigining a confidential issue' do it 'is valid' do issue.epic = epic expect(issue).to be_valid end end context 'when assigining a public issue' do let(:confidential) { false } it 'is not valid' do issue.epic = epic expect(issue).not_to be_valid expect(issue.errors.messages[:issue]).to include(/this issue cannot be assigned to a confidential epic since it is public/) end end end end end describe 'relations' do it { is_expected.to have_many(:vulnerability_links).class_name('Vulnerabilities::IssueLink').inverse_of(:issue) } it { is_expected.to have_many(:related_vulnerabilities).through(:vulnerability_links).source(:vulnerability) } it { is_expected.to belong_to(:promoted_to_epic).class_name('Epic') } it { is_expected.to have_many(:resource_weight_events) } it { is_expected.to have_one(:status_page_published_incident) } end it_behaves_like 'an editable mentionable with EE-specific mentions' do subject { create(:issue, project: create(:project, :repository)) } let(:backref_text) { "issue #{subject.to_reference}" } let(:set_mentionable_text) { ->(txt) { subject.description = txt } } end describe '#allows_multiple_assignees?' do it 'does not allow multiple assignees without license' do stub_licensed_features(multiple_issue_assignees: false) issue = build(:issue) expect(issue.allows_multiple_assignees?).to be_falsey end it 'does not allow multiple assignees without license' do stub_licensed_features(multiple_issue_assignees: true) issue = build(:issue) expect(issue.allows_multiple_assignees?).to be_truthy end end describe '.simple_sorts' do it 'includes weight with other base keys' do expect(Issue.simple_sorts.keys).to match_array( %w(created_asc created_at_asc created_date created_desc created_at_desc closest_future_date closest_future_date_asc due_date due_date_asc due_date_desc id_asc id_desc relative_position relative_position_asc updated_desc updated_asc updated_at_asc updated_at_desc weight weight_asc weight_desc)) end end describe '#sort' do let(:project) { create(:project) } context "by weight" do let!(:issue) { create(:issue, project: project) } let!(:issue2) { create(:issue, weight: 1, project: project) } let!(:issue3) { create(:issue, weight: 2, project: project) } let!(:issue4) { create(:issue, weight: 3, project: project) } it "sorts desc" do issues = project.issues.sort_by_attribute('weight_desc') expect(issues).to eq([issue4, issue3, issue2, issue]) end it "sorts asc" do issues = project.issues.sort_by_attribute('weight_asc') expect(issues).to eq([issue2, issue3, issue4, issue]) end end context 'when weight is the same' do subject { project.issues.sort_by_attribute(sorting_param) } let!(:issue) { create(:issue, project: project) } let!(:issue2) { create(:issue, weight: 1, project: project) } let!(:issue3) { create(:issue, weight: 1, project: project) } let!(:issue4) { create(:issue, weight: 1, project: project) } context 'sorting by asc' do let(:sorting_param) { 'weight_asc' } it 'arranges issues with the same weight by their ids' do is_expected.to eq([issue4, issue3, issue2, issue]) end end context 'sorting by desc' do let(:sorting_param) { 'weight_desc' } it 'arranges issues with the same weight by their ids' do is_expected.to eq([issue4, issue3, issue2, issue]) end end end context 'by blocking issues' do it 'orders by descending blocking issues count' do issue_1 = create(:issue, blocking_issues_count: 3) issue_2 = create(:issue, blocking_issues_count: 2) results = described_class.sort_by_attribute('blocking_issues_desc') expect(results.first).to eq(issue_1) expect(results.second).to eq(issue_2) end end end describe '#weight' do where(:license_value, :database_value, :expected) do true | 5 | 5 true | nil | nil false | 5 | nil false | nil | nil end with_them do let(:issue) { build(:issue, weight: database_value) } subject { issue.weight } before do stub_licensed_features(issue_weights: license_value) end it { is_expected.to eq(expected) } end end describe '#promoted?' do let(:issue) { create(:issue) } subject { issue.promoted? } context 'issue not promoted' do it { is_expected.to be_falsey } end context 'issue promoted' do let(:promoted_to_epic) { create(:epic) } let(:issue) { create(:issue, promoted_to_epic: promoted_to_epic) } it { is_expected.to be_truthy } end end context 'ES related specs', :elastic do before do stub_ee_application_setting(elasticsearch_indexing: true) end context 'when updating an Issue' do let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project, confidential: true) } let!(:note) { create(:note, noteable: issue, project: project) } let!(:system_note) { create(:note, :system, noteable: issue, project: project) } context 'when changing the confidential value' do it 'updates issue notes excluding system notes' do expect_any_instance_of(Note).to receive(:maintain_elasticsearch_update) do |instance| expect(instance.id).to eq(note.id) end issue.update!(confidential: false) end end context 'when changing the author' do it 'updates issue notes excluding system notes' do expect_any_instance_of(Note).to receive(:maintain_elasticsearch_update) do |instance| expect(instance.id).to eq(note.id) end issue.update!(author: create(:user)) end end context 'when changing the title' do it 'does not update issue notes' do expect_any_instance_of(Note).not_to receive(:maintain_elasticsearch_update) issue.update!(title: 'the new title') end end end end describe 'relative positioning with group boards' do let_it_be(:group) { create(:group) } let_it_be(:subgroup) { create(:group, parent: group) } let_it_be(:board) { create(:board, group: group) } let_it_be(:project) { create(:project, group: subgroup) } let_it_be(:project1) { create(:project, group: group) } let_it_be_with_reload(:issue) { create(:issue, project: project) } let_it_be_with_reload(:issue1) { create(:issue, project: project1, relative_position: issue.relative_position + RelativePositioning::IDEAL_DISTANCE) } let(:new_issue) { build(:issue, project: project1, relative_position: nil) } describe '.relative_positioning_query_base' do it 'includes cross project issues in the same group' do siblings = Issue.relative_positioning_query_base(issue) expect(siblings).to include(issue1) end end describe '#move_before' do it 'moves issue before' do [issue1, issue].each(&:move_to_end) issue.move_before(issue1) expect(issue.relative_position).to be < issue1.relative_position end end describe '#move_after' do it 'moves issue after' do [issue, issue1].each(&:move_to_end) issue.move_after(issue1) expect(issue.relative_position).to be > issue1.relative_position end end describe '#move_to_end' do it 'moves issue to the end' do new_issue.move_to_end expect(new_issue.relative_position).to be > issue1.relative_position end end describe '#move_between' do it 'positions issue between two other' do new_issue.move_between(issue, issue1) expect(new_issue.relative_position).to be > issue.relative_position expect(new_issue.relative_position).to be < issue1.relative_position end it 'positions issue between on top' do new_issue.move_between(nil, issue) expect(new_issue.relative_position).to be < issue.relative_position end it 'positions issue between to end' do new_issue.move_between(issue1, nil) expect(new_issue.relative_position).to be > issue1.relative_position end it 'positions issues even when after and before positions are the same' do issue1.update relative_position: issue.relative_position new_issue.move_between(issue, issue1) [issue, issue1].each(&:reset) expect(new_issue.relative_position) .to be_between(issue.relative_position, issue1.relative_position).exclusive end it 'positions issues between other two if distance is 1' do issue1.update relative_position: issue.relative_position + 1 new_issue.move_between(issue, issue1) [issue, issue1].each(&:reset) expect(new_issue.relative_position) .to be_between(issue.relative_position, issue1.relative_position).exclusive end it 'positions issue in the middle of other two if distance is big enough' do issue.update relative_position: 6000 issue1.update relative_position: 10000 new_issue.move_between(issue, issue1) expect(new_issue.relative_position) .to be_between(issue.relative_position, issue1.relative_position).exclusive end it 'positions issue closer to the middle if we are at the very top' do new_issue.move_between(nil, issue) expect(new_issue.relative_position).to eq(issue.relative_position - RelativePositioning::IDEAL_DISTANCE) end it 'positions issue closer to the middle if we are at the very bottom' do new_issue.move_between(issue1, nil) expect(new_issue.relative_position).to eq(issue1.relative_position + RelativePositioning::IDEAL_DISTANCE) end it 'positions issue in the middle of other two if distance is not big enough' do issue.update relative_position: 100 issue1.update relative_position: 400 new_issue.move_between(issue, issue1) expect(new_issue.relative_position).to eq(250) end it 'positions issue in the middle of other two is there is no place' do issue.update relative_position: 100 issue1.update relative_position: 101 new_issue.move_between(issue, issue1) [issue, issue1].each(&:reset) expect(new_issue.relative_position) .to be_between(issue.relative_position, issue1.relative_position).exclusive end it 'uses rebalancing if there is no place' do issue.update relative_position: 100 issue1.update relative_position: 101 issue2 = create(:issue, relative_position: 102, project: project) new_issue.update relative_position: 103 new_issue.move_between(issue1, issue2) new_issue.save! [issue, issue1, issue2].each(&:reset) expect(new_issue.relative_position) .to be_between(issue1.relative_position, issue2.relative_position).exclusive expect([issue, issue1, issue2, new_issue].map(&:relative_position).uniq).to have_attributes(size: 4) end it 'positions issue right if we pass non-sequential parameters' do issue.update relative_position: 99 issue1.update relative_position: 101 issue2 = create(:issue, relative_position: 102, project: project) new_issue.update relative_position: 103 new_issue.move_between(issue, issue2) new_issue.save! expect(new_issue.relative_position).to be(100) end end end context 'when an external authentication service' do before do enable_external_authorization_service_check end describe '#visible_to_user?' do it 'does not check the external webservice for auditors' do issue = build(:issue) user = build(:auditor) expect(::Gitlab::ExternalAuthorization).not_to receive(:access_allowed?) issue.visible_to_user?(user) end end end describe "#issue_link_type" do let(:issue) { build(:issue) } it 'returns nil for a regular issue' do expect(issue.issue_link_type).to be_nil end where(:id, :issue_link_source_id, :issue_link_type_value, :expected) do 1 | 1 | 0 | 'relates_to' 1 | 1 | 1 | 'blocks' 1 | 2 | 2 | 'relates_to' 1 | 2 | 1 | 'is_blocked_by' end with_them do let(:issue) { build(:issue) } subject { issue.issue_link_type } before do allow(issue).to receive(:id).and_return(id) allow(issue).to receive(:issue_link_source_id).and_return(issue_link_source_id) allow(issue).to receive(:issue_link_type_value).and_return(issue_link_type_value) end it { is_expected.to eq(expected) } end end describe "#blocked_by_issues" do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:issue) { create(:issue, project: project) } let_it_be(:blocking_issue) { create(:issue, project: project) } let_it_be(:other_project_blocking_issue) { create(:issue) } let_it_be(:blocked_by_issue) { create(:issue, project: project) } let_it_be(:confidential_blocked_by_issue) { create(:issue, :confidential, project: project) } let_it_be(:related_issue) { create(:issue, project: project) } let_it_be(:closed_blocking_issue) { create(:issue, project: project, state: :closed) } before_all do create(:issue_link, source: blocking_issue, target: issue, link_type: IssueLink::TYPE_BLOCKS) create(:issue_link, source: other_project_blocking_issue, target: issue, link_type: IssueLink::TYPE_BLOCKS) create(:issue_link, source: blocked_by_issue, target: issue, link_type: IssueLink::TYPE_BLOCKS) create(:issue_link, source: confidential_blocked_by_issue, target: issue, link_type: IssueLink::TYPE_BLOCKS) create(:issue_link, source: issue, target: related_issue, link_type: IssueLink::TYPE_RELATES_TO) create(:issue_link, source: closed_blocking_issue, target: issue, link_type: IssueLink::TYPE_BLOCKS) end context 'when user can read issues' do it 'returns blocked issues' do project.add_developer(user) other_project_blocking_issue.project.add_developer(user) expect(issue.blocked_by_issues_for(user)).to match_array([blocking_issue, blocked_by_issue, other_project_blocking_issue, confidential_blocked_by_issue]) end end context 'when user cannot read issues' do it 'returns empty array' do expect(issue.blocked_by_issues_for(user)).to be_empty end end context 'when user can read some issues' do it 'returns issues that user can read' do guest = create(:user) project.add_guest(guest) expect(issue.blocked_by_issues_for(guest)).to match_array([blocking_issue, blocked_by_issue]) end end end it_behaves_like 'having health status' describe '#can_assign_epic?' do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, group: group) } let(:issue) { create(:issue, project: project) } subject { issue.can_assign_epic?(user) } context 'when epics feature is available' do before do stub_licensed_features(epics: true) end context 'when a user is not a project member' do it 'returns false' do expect(subject).to be_falsey end end context 'when a user is a project member' do it 'returns false' do project.add_developer(user) expect(subject).to be_falsey end end context 'when a user is a group member' do it 'returns true' do group.add_developer(user) expect(subject).to be_truthy end end end context 'when epics feature is not available' do it 'returns false' do group.add_developer(user) expect(subject).to be_falsey end end describe '#update_blocking_issues_count' do it 'updates blocking issues count' do issue = create(:issue, project: project) blocked_issue_1 = create(:issue, project: project) blocked_issue_2 = create(:issue, project: project) blocked_issue_3 = create(:issue, project: project) create(:issue_link, source: issue, target: blocked_issue_1, link_type: IssueLink::TYPE_BLOCKS) create(:issue_link, source: issue, target: blocked_issue_2, link_type: IssueLink::TYPE_BLOCKS) create(:issue_link, source: issue, target: blocked_issue_3, link_type: IssueLink::TYPE_BLOCKS) # Set to 0 for proper testing, this is being set by IssueLink callbacks. issue.update(blocking_issues_count: 0) expect { issue.update_blocking_issues_count! } .to change { issue.blocking_issues_count }.from(0).to(3) end end end context 'when changing state of blocking issues' do let_it_be(:project) { create(:project) } let_it_be(:blocking_issue1) { create(:issue, project: project) } let_it_be(:blocking_issue2) { create(:issue, project: project) } let_it_be(:blocked_issue) { create(:issue, project: project) } let_it_be(:blocked_by_blocked_issue) { create(:issue, project: project) } before_all do create(:issue_link, source: blocking_issue1, target: blocked_issue, link_type: IssueLink::TYPE_BLOCKS) create(:issue_link, source: blocking_issue2, target: blocked_issue, link_type: IssueLink::TYPE_BLOCKS) create(:issue_link, source: blocked_issue, target: blocked_by_blocked_issue, link_type: IssueLink::TYPE_BLOCKS) end before do blocked_issue.update(blocking_issues_count: 0) end context 'when blocked issue is closed' do it 'updates blocking and blocked issues cache' do blocked_issue.close expect(blocking_issue1.reload.blocking_issues_count).to eq(0) expect(blocking_issue2.reload.blocking_issues_count).to eq(0) expect(blocked_issue.reload.blocking_issues_count).to eq(1) end end context 'when blocked issue is reopened' do before do blocked_issue.close blocked_issue.update(blocking_issues_count: 0) blocking_issue1.update(blocking_issues_count: 0) blocking_issue2.update(blocking_issues_count: 0) end it 'updates blocking and blocked issues cache' do blocked_issue.reopen expect(blocking_issue1.reload.blocking_issues_count).to eq(1) expect(blocking_issue2.reload.blocking_issues_count).to eq(1) expect(blocked_issue.reload.blocking_issues_count).to eq(1) end end end describe '#can_be_promoted_to_epic?' do before do stub_licensed_features(epics: true) end let_it_be(:user) { create(:user) } let(:group) { nil } subject { issue.can_be_promoted_to_epic?(user, group) } context 'when project on the issue does not have a parent group' do let(:project) { create(:project) } let(:issue) { create(:issue, project: project) } before do project.add_developer(user) end it { is_expected.to be_falsey } end context 'when project on the issue is in a subgroup' do let(:parent_group) { create(:group) } let(:group) { create(:group, parent: parent_group) } let(:project) { create(:project, group: group) } let(:issue) { create(:issue, project: project) } before do group.add_developer(user) project.add_developer(user) end it { is_expected.to be_truthy } end context 'when project has a parent group' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } let_it_be(:issue) { create(:issue, project: project) } context 'when a user is not a project member' do it { is_expected.to be_falsey } end context 'when a user is a project member' do before do project.add_developer(user) end it { is_expected.to be_falsey } end context 'when a user is a group member' do before do group.add_developer(user) end it { is_expected.to be_truthy } context 'when issue is an incident' do before do issue.update!(issue_type: :incident) end it { is_expected.to be_falsey } end end end end describe '#supports_iterations?' do let(:group) { build_stubbed(:group) } let(:project_with_group) { build_stubbed(:project, group: group) } where(:issuable_type, :project, :supports_iterations) do [ [:issue, :project_with_group, true], [:incident, :project_with_group, false] ] end with_them do let(:issue) { build_stubbed(issuable_type, project: send(project)) } subject { issue.supports_iterations? } it { is_expected.to eq(supports_iterations) } end end describe '#issue_type_supports?' do let_it_be(:issue) { create(:issue) } let_it_be(:test_case) { create(:quality_test_case) } let_it_be(:incident) { create(:incident) } it do expect(issue.issue_type_supports?(:epics)).to be(true) expect(test_case.issue_type_supports?(:epics)).to be(false) expect(incident.issue_type_supports?(:epics)).to be(false) end end describe '#sla_available?' do let_it_be(:project) { create(:project) } let_it_be_with_refind(:issue) { create(:incident, project: project) } subject { issue.sla_available? } where(:incident_type, :license_available, :sla_available) do false | true | false true | false | false true | true | true end with_them do before do stub_licensed_features(incident_sla: license_available) issue_type = incident_type ? 'incident' : 'issue' issue.update(issue_type: issue_type) end it 'returns the expected value' do expect(subject).to eq(sla_available) end end end describe '#supports_time_tracking?' do let_it_be(:project) { create(:project) } let_it_be_with_refind(:issue) { create(:incident, project: project) } where(:issue_type, :supports_time_tracking) do :requirement | false :test_case | false end with_them do before do issue.update!(issue_type: issue_type) end it do expect(issue.supports_time_tracking?).to eq(supports_time_tracking) end end end describe '#related_feature_flags' do let_it_be(:user) { create(:user) } let_it_be(:authorized_project) { create(:project) } let_it_be(:authorized_project2) { create(:project) } let_it_be(:unauthorized_project) { create(:project) } let_it_be(:issue) { create(:issue, project: authorized_project) } let_it_be(:authorized_feature_flag) { create(:operations_feature_flag, project: authorized_project) } let_it_be(:authorized_feature_flag_b) { create(:operations_feature_flag, project: authorized_project2) } let_it_be(:unauthorized_feature_flag) { create(:operations_feature_flag, project: unauthorized_project) } let_it_be(:issue_link_a) { create(:feature_flag_issue, issue: issue, feature_flag: authorized_feature_flag) } let_it_be(:issue_link_b) { create(:feature_flag_issue, issue: issue, feature_flag: unauthorized_feature_flag) } let_it_be(:issue_link_c) { create(:feature_flag_issue, issue: issue, feature_flag: authorized_feature_flag_b) } before_all do authorized_project.add_developer(user) authorized_project2.add_developer(user) end it 'returns only authorized related feature flags for a given user' do expect(issue.related_feature_flags(user)).to contain_exactly(authorized_feature_flag, authorized_feature_flag_b) end describe 'when a user cannot read cross project' do it 'only returns feature_flags within the same project' do expect(Ability).to receive(:allowed?).with(user, :read_feature_flag, authorized_feature_flag).and_return(true) expect(Ability).to receive(:allowed?).with(user, :read_cross_project).and_return(false) expect(issue.related_feature_flags(user)) .to contain_exactly(authorized_feature_flag) end end end describe '.with_issue_type' do let_it_be(:project) { create(:project) } let_it_be(:issue) { create(:issue, project: project) } let_it_be(:test_case) { create(:quality_test_case, project: project) } it 'gives issues with test case type' do expect(described_class.with_issue_type('test_case')) .to contain_exactly(test_case) end it 'gives issues with the given issue types list' do expect(described_class.with_issue_type(%w(issue test_case))) .to contain_exactly(issue, test_case) end end end