diff options
Diffstat (limited to 'spec')
37 files changed, 1020 insertions, 147 deletions
diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 2896636db5a..566658b508d 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe Projects::Boards::IssuesController do let(:project) { create(:project_with_board) } let(:user) { create(:user) } + let(:guest) { create(:user) } let(:planning) { create(:label, project: project, name: 'Planning') } let(:development) { create(:label, project: project, name: 'Development') } @@ -12,6 +13,7 @@ describe Projects::Boards::IssuesController do before do project.team << [user, :master] + project.team << [guest, :guest] end describe 'GET index' do @@ -61,6 +63,60 @@ describe Projects::Boards::IssuesController do end end + describe 'POST create' do + context 'with valid params' do + it 'returns a successful 200 response' do + create_issue user: user, list: list1, title: 'New issue' + + expect(response).to have_http_status(200) + end + + it 'returns the created issue' do + create_issue user: user, list: list1, title: 'New issue' + + expect(response).to match_response_schema('issue') + end + end + + context 'with invalid params' do + context 'when title is nil' do + it 'returns an unprocessable entity 422 response' do + create_issue user: user, list: list1, title: nil + + expect(response).to have_http_status(422) + end + end + + context 'when list does not belongs to project board' do + it 'returns a not found 404 response' do + list = create(:list) + + create_issue user: user, list: list, title: 'New issue' + + expect(response).to have_http_status(404) + end + end + end + + context 'with unauthorized user' do + it 'returns a forbidden 403 response' do + create_issue user: guest, list: list1, title: 'New issue' + + expect(response).to have_http_status(403) + end + end + + def create_issue(user:, list:, title:) + sign_in(user) + + post :create, namespace_id: project.namespace.to_param, + project_id: project.to_param, + list_id: list.to_param, + issue: { title: title }, + format: :json + end + end + describe 'PATCH update' do let(:issue) { create(:labeled_issue, project: project, labels: [planning]) } @@ -93,13 +149,7 @@ describe Projects::Boards::IssuesController do end context 'with unauthorized user' do - let(:guest) { create(:user) } - - before do - project.team << [guest, :guest] - end - - it 'returns a successful 403 response' do + it 'returns a forbidden 403 response' do move user: guest, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(403) diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 873d3fcb5af..331172445e4 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -9,6 +9,9 @@ FactoryGirl.define do namespace creator + # Behaves differently to nil due to cache_has_external_issue_tracker + has_external_issue_tracker false + trait :public do visibility_level Gitlab::VisibilityLevel::PUBLIC end @@ -92,6 +95,8 @@ FactoryGirl.define do end factory :redmine_project, parent: :project do + has_external_issue_tracker true + after :create do |project| project.create_redmine_service( active: true, @@ -105,6 +110,8 @@ FactoryGirl.define do end factory :jira_project, parent: :project do + has_external_issue_tracker true + after :create do |project| project.create_jira_service( active: true, diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 26ea06e002b..470e2bdbb9b 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -34,14 +34,14 @@ describe 'Issue Boards', feature: true, js: true do end it 'creates default lists' do - lists = ['Backlog', 'Development', 'Testing', 'Production', 'Ready', 'Done'] + lists = ['Backlog', 'To Do', 'Doing', 'Done'] page.within(find('.board-blank-state')) do click_button('Add default lists') end wait_for_vue_resource - expect(page).to have_selector('.board', count: 6) + expect(page).to have_selector('.board', count: 4) page.all('.board').each_with_index do |list, i| expect(list.find('.board-title')).to have_content(lists[i]) diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb new file mode 100644 index 00000000000..c046e6b8d79 --- /dev/null +++ b/spec/features/boards/new_issue_spec.rb @@ -0,0 +1,80 @@ +require 'rails_helper' + +describe 'Issue Boards new issue', feature: true, js: true do + include WaitForAjax + include WaitForVueResource + + let(:project) { create(:project_with_board, :public) } + let(:user) { create(:user) } + + context 'authorized user' do + before do + project.team << [user, :master] + + login_as(user) + + visit namespace_project_board_path(project.namespace, project) + wait_for_vue_resource + + expect(page).to have_selector('.board', count: 3) + end + + it 'displays new issue button' do + expect(page).to have_selector('.board-issue-count-holder .btn', count: 1) + end + + it 'does not display new issue button in done list' do + page.within('.board:nth-child(3)') do + expect(page).not_to have_selector('.board-issue-count-holder .btn') + end + end + + it 'shows form when clicking button' do + page.within(first('.board')) do + find('.board-issue-count-holder .btn').click + + expect(page).to have_selector('.board-new-issue-form') + end + end + + it 'hides form when clicking cancel' do + page.within(first('.board')) do + find('.board-issue-count-holder .btn').click + + expect(page).to have_selector('.board-new-issue-form') + + click_button 'Cancel' + + expect(page).to have_selector('.board-new-issue-form', visible: false) + end + end + + it 'creates new issue' do + page.within(first('.board')) do + find('.board-issue-count-holder .btn').click + end + + page.within(first('.board-new-issue-form')) do + find('.form-control').set('bug') + click_button 'Submit issue' + end + + wait_for_vue_resource + + page.within(first('.board .board-issue-count')) do + expect(page).to have_content('1') + end + end + end + + context 'unauthorized user' do + before do + visit namespace_project_board_path(project.namespace, project) + wait_for_vue_resource + end + + it 'does not display new issue button' do + expect(page).to have_selector('.board-issue-count-holder .btn', count: 0) + end + end +end diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index 4309a726917..68ea4eeae31 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -44,6 +44,10 @@ feature 'Environments', feature: true do scenario 'does show deployment SHA' do expect(page).to have_link(deployment.short_sha) end + + scenario 'does show deployment internal id' do + expect(page).to have_content(deployment.iid) + end context 'with build and manual actions' do given(:pipeline) { create(:ci_pipeline, project: project) } @@ -61,6 +65,20 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end + + scenario 'does show build name and id' do + expect(page).to have_link("#{build.name} (##{build.id})") + end + + context 'with external_url' do + given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } + given(:build) { create(:ci_build, pipeline: pipeline) } + given(:deployment) { create(:deployment, environment: environment, deployable: build) } + + scenario 'does show an external link button' do + expect(page).to have_link(nil, href: environment.external_url) + end + end end end end @@ -122,6 +140,16 @@ feature 'Environments', feature: true do expect(page).to have_content(manual.name) expect(manual.reload).to be_pending end + + context 'with external_url' do + given(:environment) { create(:environment, project: project, external_url: 'https://git.gitlab.com') } + given(:build) { create(:ci_build, pipeline: pipeline) } + given(:deployment) { create(:deployment, environment: environment, deployable: build) } + + scenario 'does show an external link button' do + expect(page).to have_link(nil, href: environment.external_url) + end + end end end end diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index b5a94fe0383..6498b7317b4 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -40,6 +40,17 @@ feature 'Users', feature: true do expect(number_of_errors_on_page(page)).to be(1), 'errors on page:\n #{errors_on_page page}' end + describe 'redirect alias routes' do + before { user } + + scenario '/u/user1 redirects to user page' do + visit '/u/user1' + + expect(current_path).to eq user_path(user) + expect(page).to have_text(user.name) + end + end + def errors_on_page(page) page.find('#error_explanation').find('ul').all('li').map{ |item| item.text }.join("\n") end diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb index 157cc4665a2..c6e3c5c2368 100644 --- a/spec/helpers/broadcast_messages_helper_spec.rb +++ b/spec/helpers/broadcast_messages_helper_spec.rb @@ -7,7 +7,7 @@ describe BroadcastMessagesHelper do end it 'includes the current message' do - current = double(message: 'Current Message') + current = BroadcastMessage.new(message: 'Current Message') allow(helper).to receive(:broadcast_message_style).and_return(nil) @@ -15,7 +15,7 @@ describe BroadcastMessagesHelper do end it 'includes custom style' do - current = double(message: 'Current Message') + current = BroadcastMessage.new(message: 'Current Message') allow(helper).to receive(:broadcast_message_style).and_return('foo') diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 67bac782591..abe08d95ece 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -63,28 +63,38 @@ describe IssuesHelper do end describe '#award_user_list' do - let!(:awards) { build_list(:award_emoji, 15) } + it "returns a comma-separated list of the first X users" do + user = build_stubbed(:user, name: 'Joe') + awards = Array.new(3, build_stubbed(:award_emoji, user: user)) - it "returns a comma seperated list of 1-9 users" do - expect(award_user_list(awards.first(9), nil)).to eq(awards.first(9).map { |a| a.user.name }.to_sentence) + expect(award_user_list(awards, nil, limit: 3)) + .to eq('Joe, Joe, and Joe') end it "displays the current user's name as 'You'" do - expect(award_user_list(awards.first(1), awards[0].user)).to eq('You') - end + user = build_stubbed(:user, name: 'Joe') + award = build_stubbed(:award_emoji, user: user) - it "truncates lists of larger than 9 users" do - expect(award_user_list(awards, nil)).to eq(awards.first(9).map { |a| a.user.name }.join(', ') + ", and 6 more.") + expect(award_user_list([award], user)).to eq('You') + expect(award_user_list([award], nil)).to eq 'Joe' end - it "displays the current user in front of 0-9 other users" do - expect(award_user_list(awards, awards[0].user)). - to eq("You, " + awards[1..9].map { |a| a.user.name }.join(', ') + ", and 5 more.") + it "truncates lists" do + user = build_stubbed(:user, name: 'Jane') + awards = Array.new(5, build_stubbed(:award_emoji, user: user)) + + expect(award_user_list(awards, nil, limit: 3)) + .to eq('Jane, Jane, Jane, and 2 more.') end - it "displays the current user in front regardless of position in the list" do - expect(award_user_list(awards, awards[12].user)). - to eq("You, " + awards[0..8].map { |a| a.user.name }.join(', ') + ", and 5 more.") + it "displays the current user in front of other users" do + current_user = build_stubbed(:user) + my_award = build_stubbed(:award_emoji, user: current_user) + award = build_stubbed(:award_emoji, user: build_stubbed(:user, name: 'Jane')) + awards = Array.new(5, award).push(my_award) + + expect(award_user_list(awards, current_user, limit: 2)). + to eq("You, Jane, and 4 more.") end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index bcd53440cb4..8113742923b 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -72,7 +72,7 @@ describe ProjectsHelper do it 'returns an HTML link to the user' do link = helper.link_to_member(project, user) - expect(link).to match(%r{/u/#{user.username}}) + expect(link).to match(%r{/#{user.username}}) end end end diff --git a/spec/javascripts/activities_spec.js.es6 b/spec/javascripts/activities_spec.js.es6 new file mode 100644 index 00000000000..743b15460c6 --- /dev/null +++ b/spec/javascripts/activities_spec.js.es6 @@ -0,0 +1,61 @@ +/*= require jquery.cookie.js */ +/*= require jquery.endless-scroll.js */ +/*= require pager */ +/*= require activities */ + +(() => { + window.gon || (window.gon = {}); + const fixtureTemplate = 'event_filter.html'; + const filters = [ + { + id: 'all', + }, { + id: 'push', + name: 'push events', + }, { + id: 'merged', + name: 'merge events', + }, { + id: 'comments', + },{ + id: 'team', + }]; + + function getEventName(index) { + let filter = filters[index]; + return filter.hasOwnProperty('name') ? filter.name : filter.id; + } + + function getSelector(index) { + let filter = filters[index]; + return `#${filter.id}_event_filter` + } + + describe('Activities', () => { + beforeEach(() => { + fixture.load(fixtureTemplate); + new Activities(); + }); + + for(let i = 0; i < filters.length; i++) { + ((i) => { + describe(`when selecting ${getEventName(i)}`, () => { + beforeEach(() => { + $(getSelector(i)).click(); + }); + + for(let x = 0; x < filters.length; x++) { + ((x) => { + let shouldHighlight = i === x; + let testName = shouldHighlight ? 'should highlight' : 'should not highlight'; + + it(`${testName} ${getEventName(x)}`, () => { + expect($(getSelector(x)).parent().hasClass('active')).toEqual(shouldHighlight); + }); + })(x); + } + }); + })(i); + } + }); +})(); diff --git a/spec/javascripts/fixtures/event_filter.html.haml b/spec/javascripts/fixtures/event_filter.html.haml new file mode 100644 index 00000000000..95e248cadf8 --- /dev/null +++ b/spec/javascripts/fixtures/event_filter.html.haml @@ -0,0 +1,21 @@ +%ul.nav-links.event-filter.scrolling-tabs + %li.active + %a.event-filter-link{ id: "all_event_filter", title: "Filter by all", href: "/dashboard/activity"} + %span + All + %li + %a.event-filter-link{ id: "push_event_filter", title: "Filter by push events", href: "/dashboard/activity"} + %span + Push events + %li + %a.event-filter-link{ id: "merged_event_filter", title: "Filter by merge events", href: "/dashboard/activity"} + %span + Merge events + %li + %a.event-filter-link{ id: "comments_event_filter", title: "Filter by comments", href: "/dashboard/activity"} + %span + Comments + %li + %a.event-filter-link{ id: "team_event_filter", title: "Filter by team", href: "/dashboard/activity"} + %span + Team
\ No newline at end of file diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 4470fbcb099..333128782a2 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -5,6 +5,8 @@ /*= require lib/utils/common_utils */ /*= require lib/utils/type_utility */ /*= require fuzzaldrin-plus */ +/*= require turbolinks */ +/*= require jquery.turbolinks */ (function() { var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget; @@ -138,7 +140,7 @@ list = widget.wrap.find('.dropdown-menu').find('ul'); return assertLinks(list, projectIssuesPath, projectMRsPath); }); - return it('should not show category related menu if there is text in the input', function() { + it('should not show category related menu if there is text in the input', function() { var link, list; addBodyAttributes('project'); mockProjectOptions(); @@ -148,6 +150,23 @@ link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']"; return expect(list.find(link).length).toBe(0); }); + return it('should not submit the search form when selecting an autocomplete row with the keyboard', function() { + var ENTER = 13; + var DOWN = 40; + addBodyAttributes(); + mockDashboardOptions(true); + var submitSpy = spyOnEvent('form', 'submit'); + widget.searchInput.focus(); + widget.wrap.trigger($.Event('keydown', { which: DOWN })); + var enterKeyEvent = $.Event('keydown', { which: ENTER }); + widget.searchInput.trigger(enterKeyEvent); + // This does not currently catch failing behavior. For security reasons, + // browsers will not trigger default behavior (form submit, in this + // example) on JavaScript-created keypresses. + expect(submitSpy).not.toHaveBeenTriggered(); + // Does a worse job at capturing the intent of the test, but works. + expect(enterKeyEvent.isDefaultPrevented()).toBe(true); + }); }); }).call(this); diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb new file mode 100644 index 00000000000..6dc4a970071 --- /dev/null +++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Banzai::Filter::HTMLEntityFilter, lib: true do + include FilterSpecHelper + + let(:unescaped) { 'foo <strike attr="foo">&&&</strike>' } + let(:escaped) { 'foo <strike attr="foo">&&&</strike>' } + + it 'converts common entities to their HTML-escaped equivalents' do + output = filter(unescaped) + + expect(output).to eq(escaped) + end +end diff --git a/spec/lib/banzai/note_renderer_spec.rb b/spec/lib/banzai/note_renderer_spec.rb index 98f76f36fd5..49556074278 100644 --- a/spec/lib/banzai/note_renderer_spec.rb +++ b/spec/lib/banzai/note_renderer_spec.rb @@ -12,8 +12,7 @@ describe Banzai::NoteRenderer do with(project, user, requested_path: 'foo', project_wiki: wiki, - ref: 'bar', - pipeline: :note). + ref: 'bar'). and_call_original expect_any_instance_of(Banzai::ObjectRenderer). diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index bcdb95250ca..90da78a67dd 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -4,10 +4,18 @@ describe Banzai::ObjectRenderer do let(:project) { create(:empty_project) } let(:user) { project.owner } + def fake_object(attrs = {}) + object = double(attrs.merge("new_record?" => true, "destroyed?" => true)) + allow(object).to receive(:markdown_cache_field_for).with(:note).and_return(:note_html) + allow(object).to receive(:banzai_render_context).with(:note).and_return(project: nil, author: nil) + allow(object).to receive(:update_column).with(:note_html, anything).and_return(true) + object + end + describe '#render' do it 'renders and redacts an Array of objects' do renderer = described_class.new(project, user) - object = double(:object, note: 'hello', note_html: nil) + object = fake_object(note: 'hello', note_html: nil) expect(renderer).to receive(:render_objects).with([object], :note). and_call_original @@ -16,7 +24,7 @@ describe Banzai::ObjectRenderer do with(an_instance_of(Array)). and_call_original - expect(object).to receive(:note_html=).with('<p>hello</p>') + expect(object).to receive(:redacted_note_html=).with('<p>hello</p>') expect(object).to receive(:user_visible_reference_count=).with(0) renderer.render([object], :note) @@ -25,7 +33,7 @@ describe Banzai::ObjectRenderer do describe '#render_objects' do it 'renders an Array of objects' do - object = double(:object, note: 'hello') + object = fake_object(note: 'hello', note_html: nil) renderer = described_class.new(project, user) @@ -57,49 +65,29 @@ describe Banzai::ObjectRenderer do end describe '#context_for' do - let(:object) { double(:object, note: 'hello') } + let(:object) { fake_object(note: 'hello') } let(:renderer) { described_class.new(project, user) } it 'returns a Hash' do expect(renderer.context_for(object, :note)).to be_an_instance_of(Hash) end - it 'includes the cache key' do + it 'includes the banzai render context for the object' do + expect(object).to receive(:banzai_render_context).with(:note).and_return(foo: :bar) context = renderer.context_for(object, :note) - - expect(context[:cache_key]).to eq([object, :note]) - end - - context 'when the object responds to "author"' do - it 'includes the author in the context' do - expect(object).to receive(:author).and_return('Alice') - - context = renderer.context_for(object, :note) - - expect(context[:author]).to eq('Alice') - end - end - - context 'when the object does not respond to "author"' do - it 'does not include the author in the context' do - context = renderer.context_for(object, :note) - - expect(context.key?(:author)).to eq(false) - end + expect(context).to have_key(:foo) + expect(context[:foo]).to eq(:bar) end end describe '#render_attributes' do it 'renders the attribute of a list of objects' do - objects = [double(:doc, note: 'hello'), double(:doc, note: 'bye')] - renderer = described_class.new(project, user, pipeline: :note) + objects = [fake_object(note: 'hello', note_html: nil), fake_object(note: 'bye', note_html: nil)] + renderer = described_class.new(project, user) - expect(Banzai).to receive(:cache_collection_render). - with([ - { text: 'hello', context: renderer.context_for(objects[0], :note) }, - { text: 'bye', context: renderer.context_for(objects[1], :note) } - ]). - and_call_original + objects.each do |object| + expect(Banzai).to receive(:render_field).with(object, :note).and_call_original + end docs = renderer.render_attributes(objects, :note) @@ -114,17 +102,13 @@ describe Banzai::ObjectRenderer do objects = [] renderer = described_class.new(project, user, pipeline: :note) - expect(Banzai).to receive(:cache_collection_render). - with([]). - and_call_original - expect(renderer.render_attributes(objects, :note)).to eq([]) end end describe '#base_context' do let(:context) do - described_class.new(project, user, pipeline: :note).base_context + described_class.new(project, user, foo: :bar).base_context end it 'returns a Hash' do @@ -132,7 +116,7 @@ describe Banzai::ObjectRenderer do end it 'includes the custom attributes' do - expect(context[:pipeline]).to eq(:note) + expect(context[:foo]).to eq(:bar) end it 'includes the current user' do diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb new file mode 100644 index 00000000000..aaa6b12e67e --- /dev/null +++ b/spec/lib/banzai/renderer_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' + +describe Banzai::Renderer do + def expect_render(project = :project) + expected_context = { project: project } + expect(renderer).to receive(:cacheless_render) { :html }.with(:markdown, expected_context) + end + + def expect_cache_update + expect(object).to receive(:update_column).with("field_html", :html) + end + + def fake_object(*features) + markdown = :markdown if features.include?(:markdown) + html = :html if features.include?(:html) + + object = double( + "object", + banzai_render_context: { project: :project }, + field: markdown, + field_html: html + ) + + allow(object).to receive(:markdown_cache_field_for).with(:field).and_return("field_html") + allow(object).to receive(:new_record?).and_return(features.include?(:new)) + allow(object).to receive(:destroyed?).and_return(features.include?(:destroyed)) + + object + end + + describe "#render_field" do + let(:renderer) { Banzai::Renderer } + let(:subject) { renderer.render_field(object, :field) } + + context "with an empty cache" do + let(:object) { fake_object(:markdown) } + it "caches and returns the result" do + expect_render + expect_cache_update + expect(subject).to eq(:html) + end + end + + context "with a filled cache" do + let(:object) { fake_object(:markdown, :html) } + + it "uses the cache" do + expect_render.never + expect_cache_update.never + should eq(:html) + end + end + + context "new object" do + let(:object) { fake_object(:new, :markdown) } + + it "doesn't cache the result" do + expect_render + expect_cache_update.never + expect(subject).to eq(:html) + end + end + + context "destroyed object" do + let(:object) { fake_object(:destroyed, :markdown) } + + it "doesn't cache the result" do + expect_render + expect_cache_update.never + expect(subject).to eq(:html) + end + end + end +end diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb new file mode 100644 index 00000000000..f0b75a664f2 --- /dev/null +++ b/spec/lib/constraints/group_url_constrainer_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +describe GroupUrlConstrainer, lib: true do + let!(:username) { create(:group, path: 'gitlab-org') } + + describe '#find_resource' do + it { expect(!!subject.find_resource('gitlab-org')).to be_truthy } + it { expect(!!subject.find_resource('gitlab-com')).to be_falsey } + end +end diff --git a/spec/lib/constraints/namespace_url_constrainer_spec.rb b/spec/lib/constraints/namespace_url_constrainer_spec.rb new file mode 100644 index 00000000000..a5feaacb8ee --- /dev/null +++ b/spec/lib/constraints/namespace_url_constrainer_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe NamespaceUrlConstrainer, lib: true do + let!(:group) { create(:group, path: 'gitlab') } + + describe '#matches?' do + context 'existing namespace' do + it { expect(subject.matches?(request '/gitlab')).to be_truthy } + it { expect(subject.matches?(request '/gitlab.atom')).to be_truthy } + it { expect(subject.matches?(request '/gitlab/')).to be_truthy } + it { expect(subject.matches?(request '//gitlab/')).to be_truthy } + end + + context 'non-existing namespace' do + it { expect(subject.matches?(request '/gitlab-ce')).to be_falsey } + it { expect(subject.matches?(request '/gitlab.ce')).to be_falsey } + it { expect(subject.matches?(request '/g/gitlab')).to be_falsey } + it { expect(subject.matches?(request '/.gitlab')).to be_falsey } + end + end + + def request(path) + OpenStruct.new(path: path) + end +end diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb new file mode 100644 index 00000000000..4b26692672f --- /dev/null +++ b/spec/lib/constraints/user_url_constrainer_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +describe UserUrlConstrainer, lib: true do + let!(:username) { create(:user, username: 'dz') } + + describe '#find_resource' do + it { expect(!!subject.find_resource('dz')).to be_truthy } + it { expect(!!subject.find_resource('john')).to be_falsey } + end +end diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb new file mode 100644 index 00000000000..a6d8e6927e0 --- /dev/null +++ b/spec/lib/event_filter_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe EventFilter, lib: true do + describe '#apply_filter' do + let(:source_user) { create(:user) } + let!(:public_project) { create(:project, :public) } + + let!(:push_event) { create(:event, action: Event::PUSHED, project: public_project, target: public_project, author: source_user) } + let!(:merged_event) { create(:event, action: Event::MERGED, project: public_project, target: public_project, author: source_user) } + let!(:comments_event) { create(:event, action: Event::COMMENTED, project: public_project, target: public_project, author: source_user) } + let!(:joined_event) { create(:event, action: Event::JOINED, project: public_project, target: public_project, author: source_user) } + let!(:left_event) { create(:event, action: Event::LEFT, project: public_project, target: public_project, author: source_user) } + + it 'applies push filter' do + events = EventFilter.new(EventFilter.push).apply_filter(Event.all) + expect(events).to contain_exactly(push_event) + end + + it 'applies merged filter' do + events = EventFilter.new(EventFilter.merged).apply_filter(Event.all) + expect(events).to contain_exactly(merged_event) + end + + it 'applies comments filter' do + events = EventFilter.new(EventFilter.comments).apply_filter(Event.all) + expect(events).to contain_exactly(comments_event) + end + + it 'applies team filter' do + events = EventFilter.new(EventFilter.team).apply_filter(Event.all) + expect(events).to contain_exactly(joined_event, left_event) + end + + it 'applies all filter' do + events = EventFilter.new(EventFilter.all).apply_filter(Event.all) + expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event) + end + + it 'applies no filter' do + events = EventFilter.new(nil).apply_filter(Event.all) + expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event) + end + + it 'applies unknown filter' do + events = EventFilter.new('').apply_filter(Event.all) + expect(events).to contain_exactly(push_event, merged_event, comments_event, joined_event, left_event) + end + end +end diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb index 07407f212aa..f826d0d1b04 100644 --- a/spec/lib/gitlab/backend/shell_spec.rb +++ b/spec/lib/gitlab/backend/shell_spec.rb @@ -22,15 +22,15 @@ describe Gitlab::Shell, lib: true do it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") } - describe 'generate_and_link_secret_token' do + describe 'memoized secret_token' do let(:secret_file) { 'tmp/tests/.secret_shell_test' } let(:link_file) { 'tmp/tests/shell-secret-test/.gitlab_shell_secret' } before do - allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test') allow(Gitlab.config.gitlab_shell).to receive(:secret_file).and_return(secret_file) + allow(Gitlab.config.gitlab_shell).to receive(:path).and_return('tmp/tests/shell-secret-test') FileUtils.mkdir('tmp/tests/shell-secret-test') - gitlab_shell.generate_and_link_secret_token + Gitlab::Shell.ensure_secret_token! end after do @@ -39,7 +39,10 @@ describe Gitlab::Shell, lib: true do end it 'creates and links the secret token file' do + secret_token = Gitlab::Shell.secret_token + expect(File.exist?(secret_file)).to be(true) + expect(File.read(secret_file).chomp).to eq(secret_token) expect(File.symlink?(link_file)).to be(true) expect(File.readlink(link_file)).to eq(secret_file) end diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb index 2e19d590d83..ea65a5dfed1 100644 --- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb +++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb @@ -26,10 +26,11 @@ describe 'Import/Export attribute configuration', lib: true do it 'has no new columns' do relation_names.each do |relation_name| relation_class = relation_class_for_name(relation_name) + relation_attributes = relation_class.new.attributes.keys expect(safe_model_attributes[relation_class.to_s]).not_to be_nil, "Expected exported class #{relation_class} to exist in safe_model_attributes" - current_attributes = parsed_attributes(relation_name, relation_class.attribute_names) + current_attributes = parsed_attributes(relation_name, relation_attributes) safe_attributes = safe_model_attributes[relation_class.to_s] new_attributes = current_attributes - safe_attributes diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index 305f8bc88cc..c4486a32082 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -9,6 +9,10 @@ RSpec.describe AbuseReport, type: :model do describe 'associations' do it { is_expected.to belong_to(:reporter).class_name('User') } it { is_expected.to belong_to(:user) } + + it "aliases reporter to author" do + expect(subject.author).to be(subject.reporter) + end end describe 'validations' do diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb new file mode 100644 index 00000000000..15cd3a7ed70 --- /dev/null +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -0,0 +1,181 @@ +require 'spec_helper' + +describe CacheMarkdownField do + CacheMarkdownField::CACHING_CLASSES << "ThingWithMarkdownFields" + + # The minimum necessary ActiveModel to test this concern + class ThingWithMarkdownFields + include ActiveModel::Model + include ActiveModel::Dirty + + include ActiveModel::Serialization + + class_attribute :attribute_names + self.attribute_names = [] + + def attributes + attribute_names.each_with_object({}) do |name, hsh| + hsh[name.to_s] = send(name) + end + end + + extend ActiveModel::Callbacks + define_model_callbacks :save + + include CacheMarkdownField + cache_markdown_field :foo + cache_markdown_field :baz, pipeline: :single_line + + def self.add_attr(attr_name) + self.attribute_names += [attr_name] + define_attribute_methods(attr_name) + attr_reader(attr_name) + define_method("#{attr_name}=") do |val| + send("#{attr_name}_will_change!") unless val == send(attr_name) + instance_variable_set("@#{attr_name}", val) + end + end + + [:foo, :foo_html, :bar, :baz, :baz_html].each do |attr_name| + add_attr(attr_name) + end + + def initialize(*) + super + + # Pretend new is load + clear_changes_information + end + + def save + run_callbacks :save do + changes_applied + end + end + end + + CacheMarkdownField::CACHING_CLASSES.delete("ThingWithMarkdownFields") + + def thing_subclass(new_attr) + Class.new(ThingWithMarkdownFields) { add_attr(new_attr) } + end + + let(:markdown) { "`Foo`" } + let(:html) { "<p><code>Foo</code></p>" } + + let(:updated_markdown) { "`Bar`" } + let(:updated_html) { "<p><code>Bar</code></p>" } + + subject { ThingWithMarkdownFields.new(foo: markdown, foo_html: html) } + + describe ".attributes" do + it "excludes cache attributes" do + expect(thing_subclass(:qux).new.attributes.keys.sort).to eq(%w[bar baz foo qux]) + end + end + + describe ".cache_markdown_field" do + it "refuses to allow untracked classes" do + expect { thing_subclass(:qux).__send__(:cache_markdown_field, :qux) }.to raise_error(RuntimeError) + end + end + + context "an unchanged markdown field" do + before do + subject.foo = subject.foo + subject.save + end + + it { expect(subject.foo).to eq(markdown) } + it { expect(subject.foo_html).to eq(html) } + it { expect(subject.foo_html_changed?).not_to be_truthy } + end + + context "a changed markdown field" do + before do + subject.foo = updated_markdown + subject.save + end + + it { expect(subject.foo_html).to eq(updated_html) } + end + + context "a non-markdown field changed" do + before do + subject.bar = "OK" + subject.save + end + + it { expect(subject.bar).to eq("OK") } + it { expect(subject.foo).to eq(markdown) } + it { expect(subject.foo_html).to eq(html) } + end + + describe '#banzai_render_context' do + it "sets project to nil if the object lacks a project" do + context = subject.banzai_render_context(:foo) + expect(context).to have_key(:project) + expect(context[:project]).to be_nil + end + + it "excludes author if the object lacks an author" do + context = subject.banzai_render_context(:foo) + expect(context).not_to have_key(:author) + end + + it "raises if the context for an unrecognised field is requested" do + expect{subject.banzai_render_context(:not_found)}.to raise_error(ArgumentError) + end + + it "includes the pipeline" do + context = subject.banzai_render_context(:baz) + expect(context[:pipeline]).to eq(:single_line) + end + + it "returns copies of the context template" do + template = subject.cached_markdown_fields[:baz] + copy = subject.banzai_render_context(:baz) + expect(copy).not_to be(template) + end + + context "with a project" do + subject { thing_subclass(:project).new(foo: markdown, foo_html: html, project: :project) } + + it "sets the project in the context" do + context = subject.banzai_render_context(:foo) + expect(context).to have_key(:project) + expect(context[:project]).to eq(:project) + end + + it "invalidates the cache when project changes" do + subject.project = :new_project + allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + + subject.save + + expect(subject.foo_html).to eq(updated_html) + expect(subject.baz_html).to eq(updated_html) + end + end + + context "with an author" do + subject { thing_subclass(:author).new(foo: markdown, foo_html: html, author: :author) } + + it "sets the author in the context" do + context = subject.banzai_render_context(:foo) + expect(context).to have_key(:author) + expect(context[:author]).to eq(:author) + end + + it "invalidates the cache when author changes" do + subject.author = :new_author + allow(Banzai::Renderer).to receive(:cacheless_render_field).and_return(updated_html) + + subject.save + + expect(subject.foo_html).to eq(updated_html) + expect(subject.baz_html).to eq(updated_html) + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e52d4aaf884..8aadfcb439b 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -308,7 +308,9 @@ describe Project, models: true do end describe 'last_activity methods' do - let(:project) { create(:project, last_activity_at: 2.hours.ago) } + let(:timestamp) { 2.hours.ago } + # last_activity_at gets set to created_at upon creation + let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) } describe 'last_activity' do it 'alias last_activity to last_event' do @@ -322,6 +324,7 @@ describe Project, models: true do it 'returns the creation date of the project\'s last event if present' do new_event = create(:event, project: project, created_at: Time.now) + project.reload expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i) end @@ -518,7 +521,7 @@ describe Project, models: true do end describe '#cache_has_external_issue_tracker' do - let(:project) { create(:project) } + let(:project) { create(:project, has_external_issue_tracker: nil) } it 'stores true if there is any external_issue_tracker' do services = double(:service, external_issue_trackers: [RedmineService.new]) diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index ed1bc9271ae..43937a54b2c 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -238,7 +238,7 @@ describe Service, models: true do it "updates the has_external_issue_tracker boolean" do expect do service.save! - end.to change { service.project.has_external_issue_tracker }.from(nil).to(true) + end.to change { service.project.has_external_issue_tracker }.from(false).to(true) end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index e6bc5296398..f62f6bacbaa 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -46,6 +46,13 @@ describe Snippet, models: true do end end + describe "#content_html_invalidated?" do + let(:snippet) { create(:snippet, content: "md", content_html: "html", file_name: "foo.md") } + it "invalidates the HTML cache of content when the filename changes" do + expect { snippet.file_name = "foo.rb" }.to change { snippet.content_html_invalidated? }.from(false).to(true) + end + end + describe '.search' do let(:snippet) { create(:snippet) } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index a7a06744428..43c8d884a47 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1,20 +1,68 @@ require 'spec_helper' describe ProjectPolicy, models: true do - let(:project) { create(:empty_project, :public) } let(:guest) { create(:user) } let(:reporter) { create(:user) } let(:dev) { create(:user) } let(:master) { create(:user) } let(:owner) { create(:user) } - let(:admin) { create(:admin) } + let(:project) { create(:empty_project, :public, namespace: owner.namespace) } - let(:users_ordered_by_permissions) do - [nil, guest, reporter, dev, master, owner, admin] + let(:guest_permissions) do + [ + :read_project, :read_board, :read_list, :read_wiki, :read_issue, :read_label, + :read_milestone, :read_project_snippet, :read_project_member, + :read_merge_request, :read_note, :create_project, :create_issue, :create_note, + :upload_file + ] end - let(:users_permissions) do - users_ordered_by_permissions.map { |u| Ability.allowed(u, project).size } + let(:reporter_permissions) do + [ + :download_code, :fork_project, :create_project_snippet, :update_issue, + :admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build, + :read_container_image, :read_pipeline, :read_environment, :read_deployment + ] + end + + let(:team_member_reporter_permissions) do + [ + :build_download_code, :build_read_container_image + ] + end + + let(:developer_permissions) do + [ + :admin_merge_request, :update_merge_request, :create_commit_status, + :update_commit_status, :create_build, :update_build, :create_pipeline, + :update_pipeline, :create_merge_request, :create_wiki, :push_code, + :resolve_note, :create_container_image, :update_container_image, + :create_environment, :create_deployment + ] + end + + let(:master_permissions) do + [ + :push_code_to_protected_branches, :update_project_snippet, :update_environment, + :update_deployment, :admin_milestone, :admin_project_snippet, + :admin_project_member, :admin_note, :admin_wiki, :admin_project, + :admin_commit_status, :admin_build, :admin_container_image, + :admin_pipeline, :admin_environment, :admin_deployment + ] + end + + let(:public_permissions) do + [ + :download_code, :fork_project, :read_commit_status, :read_pipeline, + :read_container_image, :build_download_code, :build_read_container_image + ] + end + + let(:owner_permissions) do + [ + :change_namespace, :change_visibility_level, :rename_project, :remove_project, + :archive_project, :remove_fork_project, :destroy_merge_request, :destroy_issue + ] end before do @@ -22,16 +70,6 @@ describe ProjectPolicy, models: true do project.team << [master, :master] project.team << [dev, :developer] project.team << [reporter, :reporter] - - group = create(:group) - project.project_group_links.create( - group: group, - group_access: Gitlab::Access::MASTER) - group.add_owner(owner) - end - - it 'returns increasing permissions for each level' do - expect(users_permissions).to eq(users_permissions.sort.uniq) end it 'does not include the read_issue permission when the issue author is not a member of the private project' do @@ -46,4 +84,81 @@ describe ProjectPolicy, models: true do expect(Ability.allowed?(user, :read_issue, project)).to be_falsy end + + context 'abilities for non-public projects' do + let(:project) { create(:empty_project, namespace: owner.namespace) } + + subject { described_class.abilities(current_user, project).to_set } + + context 'with no user' do + let(:current_user) { nil } + + it { is_expected.to be_empty } + end + + context 'guests' do + let(:current_user) { guest } + + it do + is_expected.to include(*guest_permissions) + is_expected.not_to include(*reporter_permissions) + is_expected.not_to include(*team_member_reporter_permissions) + is_expected.not_to include(*developer_permissions) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'reporter' do + let(:current_user) { reporter } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.to include(*team_member_reporter_permissions) + is_expected.not_to include(*developer_permissions) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'developer' do + let(:current_user) { dev } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.to include(*team_member_reporter_permissions) + is_expected.to include(*developer_permissions) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'master' do + let(:current_user) { master } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.to include(*team_member_reporter_permissions) + is_expected.to include(*developer_permissions) + is_expected.to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'owner' do + let(:current_user) { owner } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.not_to include(*team_member_reporter_permissions) + is_expected.to include(*developer_permissions) + is_expected.to include(*master_permissions) + is_expected.to include(*owner_permissions) + end + end + end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 4a0d727faea..5f19638b460 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -175,6 +175,36 @@ describe API::API, api: true do end end + describe 'GET /projects/visible' do + let(:public_project) { create(:project, :public) } + + before do + public_project + project + project2 + project3 + project4 + end + + it 'returns the projects viewable by the user' do + get api('/projects/visible', user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }). + to contain_exactly(public_project.id, project.id, project2.id, project3.id) + end + + it 'shows only public projects when the user only has access to those' do + get api('/projects/visible', user2) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |project| project['id'] }). + to contain_exactly(public_project.id) + end + end + describe 'GET /projects/starred' do let(:public_project) { create(:project, :public) } @@ -232,7 +262,7 @@ describe API::API, api: true do post api('/projects', user), project project.each_pair do |k, v| - next if %i{ issues_enabled merge_requests_enabled wiki_enabled }.include?(k) + next if %i[has_external_issue_tracker issues_enabled merge_requests_enabled wiki_enabled].include?(k) expect(json_response[k.to_s]).to eq(v) end @@ -360,7 +390,7 @@ describe API::API, api: true do post api("/projects/user/#{user.id}", admin), project project.each_pair do |k, v| - next if k == :path + next if %i[has_external_issue_tracker path].include?(k) expect(json_response[k.to_s]).to eq(v) end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 4bc3cddd9c2..0dd00af878d 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -9,7 +9,9 @@ require 'spec_helper' # user_calendar_activities GET /u/:username/calendar_activities(.:format) describe UsersController, "routing" do it "to #show" do - expect(get("/u/User")).to route_to('users#show', username: 'User') + allow(User).to receive(:find_by).and_return(true) + + expect(get("/User")).to route_to('users#show', username: 'User') end it "to #groups" do diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb new file mode 100644 index 00000000000..33e10e79f6d --- /dev/null +++ b/spec/services/boards/issues/create_service_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Boards::Issues::CreateService, services: true do + describe '#execute' do + let(:project) { create(:project_with_board) } + let(:board) { project.board } + let(:user) { create(:user) } + let(:label) { create(:label, project: project, name: 'in-progress') } + let!(:list) { create(:list, board: board, label: label, position: 0) } + + subject(:service) { described_class.new(project, user, title: 'New issue') } + + before do + project.team << [user, :developer] + end + + it 'delegates the create proceedings to Issues::CreateService' do + expect_any_instance_of(Issues::CreateService).to receive(:execute).once + + service.execute(list) + end + + it 'creates a new issue' do + expect { service.execute(list) }.to change(project.issues, :count).by(1) + end + + it 'adds the label of the list to the issue' do + issue = service.execute(list) + + expect(issue.labels).to eq [label] + end + end +end diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 9fd39122737..4171e4d816c 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -10,7 +10,7 @@ describe Boards::Lists::GenerateService, services: true do context 'when board lists is empty' do it 'creates the default lists' do - expect { service.execute }.to change(board.lists, :count).by(4) + expect { service.execute }.to change(board.lists, :count).by(2) end end @@ -24,16 +24,15 @@ describe Boards::Lists::GenerateService, services: true do context 'when project labels does not contains any list label' do it 'creates labels' do - expect { service.execute }.to change(project.labels, :count).by(4) + expect { service.execute }.to change(project.labels, :count).by(2) end end context 'when project labels contains some of list label' do it 'creates the missing labels' do - create(:label, project: project, name: 'Development') - create(:label, project: project, name: 'Ready') + create(:label, project: project, name: 'Doing') - expect { service.execute }.to change(project.labels, :count).by(2) + expect { service.execute }.to change(project.labels, :count).by(1) end end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 22991c5bc86..8e3e12114f2 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -448,6 +448,8 @@ describe GitPushService, services: true do let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } before do + # project.create_jira_service doesn't seem to invalidate the cache here + project.has_external_issue_tracker = true jira_service_settings WebMock.stub_request(:post, jira_api_transition_url) diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index e49a0d5e553..ee53e110aee 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -60,7 +60,10 @@ describe MergeRequests::MergeService, services: true do let(:jira_tracker) { project.create_jira_service } - before { jira_service_settings } + before do + project.update_attributes!(has_external_issue_tracker: true) + jira_service_settings + end it 'closes issues on JIRA issue tracker' do jira_issue = ExternalIssue.new('JIRA-123', project) diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index c22dd9ab77a..304d4e62396 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -531,12 +531,12 @@ describe SystemNoteService, services: true do include JiraServiceHelper describe 'JIRA integration' do - let(:project) { create(:project) } + let(:project) { create(:jira_project) } let(:author) { create(:user) } let(:issue) { create(:issue, project: project) } let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) } let(:jira_issue) { ExternalIssue.new("JIRA-1", project)} - let(:jira_tracker) { project.create_jira_service if project.jira_service.nil? } + let(:jira_tracker) { project.jira_service } let(:commit) { project.commit } context 'in JIRA issue tracker' do @@ -545,10 +545,6 @@ describe SystemNoteService, services: true do WebMock.stub_request(:post, jira_api_comment_url) end - after do - jira_tracker.destroy! - end - describe "new reference" do before do WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments) @@ -561,7 +557,7 @@ describe SystemNoteService, services: true do describe "existing reference" do before do - message = %Q{[#{author.name}|http://localhost/u/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\\n'#{commit.title}'} + message = %Q{[#{author.name}|http://localhost/#{author.username}] mentioned this issue in [a commit of #{project.path_with_namespace}|http://localhost/#{project.path_with_namespace}/commit/#{commit.id}]:\\n'#{commit.title}'} WebMock.stub_request(:get, jira_api_comment_url).to_return(body: %Q({"comments":[{"body":"#{message}"}]})) end @@ -578,10 +574,6 @@ describe SystemNoteService, services: true do WebMock.stub_request(:get, jira_api_comment_url).to_return(body: jira_issue_comments) end - after do - jira_tracker.destroy! - end - subject { described_class.cross_reference(jira_issue, issue, author) } it { is_expected.to eq(jira_status_message) } diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb index 7d6668920c0..73cbadc13d9 100644 --- a/spec/workers/expire_build_artifacts_worker_spec.rb +++ b/spec/workers/expire_build_artifacts_worker_spec.rb @@ -5,65 +5,42 @@ describe ExpireBuildArtifactsWorker do let(:worker) { described_class.new } + before { Sidekiq::Worker.clear_all } + describe '#perform' do before { build } - subject! { worker.perform } + subject! do + Sidekiq::Testing.fake! { worker.perform } + end context 'with expired artifacts' do let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) } - it 'does expire' do - expect(build.reload.artifacts_expired?).to be_truthy - end - - it 'does remove files' do - expect(build.reload.artifacts_file.exists?).to be_falsey - end - - it 'does nullify artifacts_file column' do - expect(build.reload.artifacts_file_identifier).to be_nil + it 'enqueues that build' do + expect(jobs_enqueued.size).to eq(1) + expect(jobs_enqueued[0]["args"]).to eq([build.id]) end end context 'with not yet expired artifacts' do let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) } - it 'does not expire' do - expect(build.reload.artifacts_expired?).to be_falsey - end - - it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy - end - - it 'does not nullify artifacts_file column' do - expect(build.reload.artifacts_file_identifier).not_to be_nil + it 'does not enqueue that build' do + expect(jobs_enqueued.size).to eq(0) end end context 'without expire date' do let(:build) { create(:ci_build, :artifacts) } - it 'does not expire' do - expect(build.reload.artifacts_expired?).to be_falsey - end - - it 'does not remove files' do - expect(build.reload.artifacts_file.exists?).to be_truthy - end - - it 'does not nullify artifacts_file column' do - expect(build.reload.artifacts_file_identifier).not_to be_nil + it 'does not enqueue that build' do + expect(jobs_enqueued.size).to eq(0) end end - context 'for expired artifacts' do - let(:build) { create(:ci_build, artifacts_expire_at: Time.now - 7.days) } - - it 'is still expired' do - expect(build.reload.artifacts_expired?).to be_truthy - end + def jobs_enqueued + Sidekiq::Queues.jobs_by_worker['ExpireBuildInstanceArtifactsWorker'] end end end diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb new file mode 100644 index 00000000000..2b140f2ba28 --- /dev/null +++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe ExpireBuildInstanceArtifactsWorker do + include RepoHelpers + + let(:worker) { described_class.new } + + describe '#perform' do + before { build } + + subject! { worker.perform(build.id) } + + context 'with expired artifacts' do + let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) } + + it 'does expire' do + expect(build.reload.artifacts_expired?).to be_truthy + end + + it 'does remove files' do + expect(build.reload.artifacts_file.exists?).to be_falsey + end + + it 'does nullify artifacts_file column' do + expect(build.reload.artifacts_file_identifier).to be_nil + end + end + + context 'with not yet expired artifacts' do + let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) } + + it 'does not expire' do + expect(build.reload.artifacts_expired?).to be_falsey + end + + it 'does not remove files' do + expect(build.reload.artifacts_file.exists?).to be_truthy + end + + it 'does not nullify artifacts_file column' do + expect(build.reload.artifacts_file_identifier).not_to be_nil + end + end + + context 'without expire date' do + let(:build) { create(:ci_build, :artifacts) } + + it 'does not expire' do + expect(build.reload.artifacts_expired?).to be_falsey + end + + it 'does not remove files' do + expect(build.reload.artifacts_file.exists?).to be_truthy + end + + it 'does not nullify artifacts_file column' do + expect(build.reload.artifacts_file_identifier).not_to be_nil + end + end + + context 'for expired artifacts' do + let(:build) { create(:ci_build, artifacts_expire_at: Time.now - 7.days) } + + it 'is still expired' do + expect(build.reload.artifacts_expired?).to be_truthy + end + end + end +end |