diff options
25 files changed, 411 insertions, 34 deletions
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 5d9ac4d350a..89fe13b7a45 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -53,6 +53,26 @@ } else { return value; } + }, + matcher: function (flag, subtext) { + // The below is taken from At.js source + // Tweaked to commands to start without a space only if char before is a non-word character + // https://github.com/ichord/At.js + var _a, _y, regexp, match; + flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + + _a = decodeURI("%C3%80"); + _y = decodeURI("%C3%BF"); + + regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi'); + + match = regexp.exec(subtext); + + if (match) { + return match[2] || match[1]; + } else { + return null; + } } }, setup: _.debounce(function(input) { @@ -91,10 +111,12 @@ })(this), insertTpl: ':${name}:', data: ['loading'], + startWithSpace: false, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, - beforeInsert: this.DefaultOptions.beforeInsert + beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher } }); // Team Members @@ -112,11 +134,13 @@ insertTpl: '${atwho-at}${username}', searchKey: 'search', data: ['loading'], + startWithSpace: false, alwaysHighlightFirst: true, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher, beforeSave: function(members) { return $.map(members, function(m) { let title = ''; @@ -157,10 +181,12 @@ })(this), data: ['loading'], insertTpl: '${atwho-at}${id}', + startWithSpace: false, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher, beforeSave: function(issues) { return $.map(issues, function(i) { if (i.title == null) { @@ -190,7 +216,9 @@ })(this), insertTpl: '${atwho-at}"${title}"', data: ['loading'], + startWithSpace: false, callbacks: { + matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, beforeSave: function(milestones) { return $.map(milestones, function(m) { @@ -220,11 +248,13 @@ }; })(this), data: ['loading'], + startWithSpace: false, insertTpl: '${atwho-at}${id}', callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher, beforeSave: function(merges) { return $.map(merges, function(m) { if (m.title == null) { @@ -245,7 +275,9 @@ searchKey: 'search', displayTpl: this.Labels.template, insertTpl: '${atwho-at}${title}', + startWithSpace: false, callbacks: { + matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, beforeSave: function(merges) { var sanitizeLabelTitle; diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 3fee6c18770..4294a10e9e3 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -161,23 +161,27 @@ module Ci end def retryable? - builds.latest.any? do |build| - (build.failed? || build.canceled?) && build.retryable? - end + builds.latest.failed_or_canceled.any?(&:retryable?) end def cancelable? - builds.running_or_pending.any? + statuses.cancelable.any? end def cancel_running - builds.running_or_pending.each(&:cancel) + Gitlab::OptimisticLocking.retry_lock( + statuses.cancelable) do |cancelable| + cancelable.each(&:cancel) + end end def retry_failed(user) - builds.latest.failed.select(&:retryable?).each do |build| - Ci::Build.retry(build, user) - end + Gitlab::OptimisticLocking.retry_lock( + builds.latest.failed_or_canceled) do |failed_or_canceled| + failed_or_canceled.select(&:retryable?).each do |build| + Ci::Build.retry(build, user) + end + end end def mark_as_processable_after_stage(stage_idx) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index ef3e73a4072..2f5aa91a964 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -73,6 +73,11 @@ module HasStatus scope :skipped, -> { where(status: 'skipped') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } + scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } + + scope :cancelable, -> do + where(status: [:running, :pending, :created]) + end end def started? diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index f166fac105d..02427650219 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -9,12 +9,12 @@ %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } = sidebar_gutter_toggle_icon - if current_user - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add Todo" : "Mark Done") }, data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } + %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add todo" : "Mark done") }, data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } %span.js-issuable-todo-text - if todo - Mark Done + Mark done - else - Add Todo + Add todo = icon('spin spinner', class: 'hidden js-issuable-todo-loading') = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| diff --git a/changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml b/changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml new file mode 100644 index 00000000000..7f1d40e7c21 --- /dev/null +++ b/changelogs/unreleased/22373-reduce-queries-in-api-helpers-find_project.yml @@ -0,0 +1,4 @@ +--- +title: 'Make API::Helpers find a project with only one query' +merge_request: 7714 +author: diff --git a/changelogs/unreleased/fix-cancelling-pipelines.yml b/changelogs/unreleased/fix-cancelling-pipelines.yml new file mode 100644 index 00000000000..c21e663093a --- /dev/null +++ b/changelogs/unreleased/fix-cancelling-pipelines.yml @@ -0,0 +1,4 @@ +--- +title: Fix cancelling created or external pipelines +merge_request: 7508 +author: diff --git a/changelogs/unreleased/issue_24748.yml b/changelogs/unreleased/issue_24748.yml new file mode 100644 index 00000000000..4c1df542c53 --- /dev/null +++ b/changelogs/unreleased/issue_24748.yml @@ -0,0 +1,4 @@ +--- +title: Fix title case to sentence case +merge_request: +author: Luis Alonso Chavez Armendariz diff --git a/doc/workflow/img/todos_add_todo_sidebar.png b/doc/workflow/img/todos_add_todo_sidebar.png Binary files differindex 59175ae44c5..3fa37067d1e 100644 --- a/doc/workflow/img/todos_add_todo_sidebar.png +++ b/doc/workflow/img/todos_add_todo_sidebar.png diff --git a/doc/workflow/img/todos_mark_done_sidebar.png b/doc/workflow/img/todos_mark_done_sidebar.png Binary files differindex aa35bb672ea..a8e756a71db 100644 --- a/doc/workflow/img/todos_mark_done_sidebar.png +++ b/doc/workflow/img/todos_mark_done_sidebar.png diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index 54e7ae19ea5..1a8fc39bb33 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -35,7 +35,7 @@ A Todo appears in your Todos dashboard when: ### Manually creating a Todo You can also add an issue or merge request to your Todos dashboard by clicking -the "Add Todo" button in the issue or merge request sidebar. +the "Add todo" button in the issue or merge request sidebar. ![Adding a Todo from the issuable sidebar](img/todos_add_todo_sidebar.png) @@ -69,7 +69,7 @@ corresponding **Done** button, and it will disappear from your Todo list. ![A Todo in the Todos dashboard](img/todo_list_item.png) A Todo can also be marked as done from the issue or merge request sidebar using -the "Mark Done" button. +the "Mark done" button. ![Mark Done from the issuable sidebar](img/todos_mark_done_sidebar.png) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index fc39fdf4b67..5315c22e1e4 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -82,7 +82,7 @@ module API :lfs_enabled, :request_access_enabled end put ':id' do - group = find_group(params[:id]) + group = find_group!(params[:id]) authorize! :admin_group, group if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute @@ -96,13 +96,13 @@ module API success Entities::GroupDetail end get ":id" do - group = find_group(params[:id]) + group = find_group!(params[:id]) present group, with: Entities::GroupDetail end desc 'Remove a group.' delete ":id" do - group = find_group(params[:id]) + group = find_group!(params[:id]) authorize! :admin_group, group DestroyGroupService.new(group, current_user).execute end @@ -111,7 +111,7 @@ module API success Entities::Project end get ":id/projects" do - group = find_group(params[:id]) + group = find_group!(params[:id]) projects = GroupProjectsFinder.new(group).execute(current_user) projects = paginate projects present projects, with: Entities::Project, user: current_user diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2c593dbb4ea..0d3ddb89dc3 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -68,7 +68,7 @@ module API end def user_project - @project ||= find_project(params[:id]) + @project ||= find_project!(params[:id]) end def available_labels @@ -76,7 +76,15 @@ module API end def find_project(id) - project = Project.find_with_namespace(id) || Project.find_by(id: id) + if id =~ /^\d+$/ + Project.find_by(id: id) + else + Project.find_with_namespace(id) + end + end + + def find_project!(id) + project = find_project(id) if can?(current_user, :read_project, project) project @@ -97,7 +105,15 @@ module API end def find_group(id) - group = Group.find_by(path: id) || Group.find_by(id: id) + if id =~ /^\d+$/ + Group.find_by(id: id) + else + Group.find_by(path: id) + end + end + + def find_group!(id) + group = find_group(id) if can?(current_user, :read_group, group) group diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 90114f6f667..d9cae1501f8 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -2,7 +2,7 @@ module API module Helpers module MembersHelpers def find_source(source_type, id) - public_send("find_#{source_type}", id) + public_send("find_#{source_type}!", id) end def authorize_admin_source!(source_type, source) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index eea5b91d4f9..2fea71870b8 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -68,7 +68,7 @@ module API # GET /groups/:id/issues?milestone=1.0.0 # GET /groups/:id/issues?milestone=1.0.0&state=closed get ":id/issues" do - group = find_group(params[:id]) + group = find_group!(params[:id]) params[:state] ||= 'opened' params[:group_id] = group.id diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ddfde178d30..2ea3c433ae2 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -379,7 +379,7 @@ module API # POST /projects/:id/fork/:forked_from_id post ":id/fork/:forked_from_id" do authenticated_as_admin! - forked_from_project = find_project(params[:forked_from_id]) + forked_from_project = find_project!(params[:forked_from_id]) unless forked_from_project.nil? if user_project.forked_from_project.nil? user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id) diff --git a/lib/api/services.rb b/lib/api/services.rb index 4d23499aa39..bc427705777 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -65,7 +65,7 @@ module API detail 'Added in GitLab 8.13' end post ':id/services/:service_slug/trigger' do - project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) + project = find_project(params[:id]) # This is not accurate, but done to prevent leakage of the project names not_found!('Service') unless project diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index 569598fbd2c..bb4de39def1 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -13,7 +13,7 @@ module API optional :variables, type: Hash, desc: 'The list of variables to be injected into build' end post ":id/(ref/:ref/)trigger/builds" do - project = Project.find_with_namespace(params[:id]) || Project.find_by(id: params[:id]) + project = find_project(params[:id]) trigger = Ci::Trigger.find_by_token(params[:token].to_s) not_found! unless project && trigger unauthorized! unless trigger.project == project diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index eb20bd7dd58..c443af09075 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -38,6 +38,10 @@ FactoryGirl.define do status 'canceled' end + trait :skipped do + status 'skipped' + end + trait :running do status 'running' end diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb index 995f2080f10..756b341ecba 100644 --- a/spec/factories/commit_statuses.rb +++ b/spec/factories/commit_statuses.rb @@ -19,6 +19,10 @@ FactoryGirl.define do status 'canceled' end + trait :skipped do + status 'skipped' + end + trait :running do status 'running' end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb new file mode 100644 index 00000000000..c421da97d76 --- /dev/null +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +feature 'GFM autocomplete', feature: true, js: true do + include WaitForAjax + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + + before do + project.team << [user, :master] + login_as(user) + visit namespace_project_issue_path(project.namespace, project, issue) + + wait_for_ajax + end + + it 'opens autocomplete menu when field starts with text' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + end + + it 'opens autocomplete menu when field is prefixed with non-text character' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('') + find('#note_note').native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + end + + it 'doesnt open autocomplete menu character is prefixed with text' do + page.within '.timeline-content-form' do + find('#note_note').native.send_keys('testing') + find('#note_note').native.send_keys('@') + end + + expect(page).not_to have_selector('.atwho-view') + end +end diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb index de8fdda388d..41ff31d2b99 100644 --- a/spec/features/issues/todo_spec.rb +++ b/spec/features/issues/todo_spec.rb @@ -13,8 +13,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do it 'creates todo when clicking button' do page.within '.issuable-sidebar' do - click_button 'Add Todo' - expect(page).to have_content 'Mark Done' + click_button 'Add todo' + expect(page).to have_content 'Mark done' end page.within '.header-content .todos-pending-count' do @@ -30,8 +30,8 @@ feature 'Manually create a todo item from issue', feature: true, js: true do it 'marks a todo as done' do page.within '.issuable-sidebar' do - click_button 'Add Todo' - click_button 'Mark Done' + click_button 'Add todo' + click_button 'Mark done' end expect(page).to have_selector('.todos-pending-count', visible: false) diff --git a/spec/features/projects/pipelines_spec.rb b/spec/features/projects/pipelines_spec.rb index 002c6f6b359..10e5466fc85 100644 --- a/spec/features/projects/pipelines_spec.rb +++ b/spec/features/projects/pipelines_spec.rb @@ -90,13 +90,20 @@ describe "Pipelines" do visit namespace_project_pipelines_path(project.namespace, project) end - it 'is not cancelable' do - expect(page).not_to have_link('Cancel') + it 'is cancelable' do + expect(page).to have_link('Cancel') end it 'has pipeline running' do expect(page).to have_selector('.ci-running') end + + context 'when canceling' do + before { click_link('Cancel') } + + it { expect(page).not_to have_link('Cancel') } + it { expect(page).to have_selector('.ci-canceled') } + end end context 'when failed' do diff --git a/spec/javascripts/fixtures/right_sidebar.html.haml b/spec/javascripts/fixtures/right_sidebar.html.haml index d48b77cf0ce..d259b58f235 100644 --- a/spec/javascripts/fixtures/right_sidebar.html.haml +++ b/spec/javascripts/fixtures/right_sidebar.html.haml @@ -5,9 +5,9 @@ %div.block.issuable-sidebar-header %a.gutter-toggle.pull-right.js-sidebar-toggle %i.fa.fa-angle-double-left - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: "1", issuable_type: "issue", url: "/todos" }} + %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: "1", issuable_type: "issue", url: "/todos" }} %span.js-issuable-todo-text - Add Todo + Add todo %i.fa.fa-spin.fa-spinner.js-issuable-todo-loading.hidden %form.issuable-context-form diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index ea022e03608..3b12f16b4db 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -402,6 +402,160 @@ describe Ci::Pipeline, models: true do end end + describe '#cancelable?' do + %i[created running pending].each do |status0| + context "when there is a build #{status0}" do + before do + create(:ci_build, status0, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there is an external job #{status0}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + %i[success failed canceled].each do |status1| + context "when there are generic_commit_status jobs for #{status0} and #{status1}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + create(:generic_commit_status, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there are generic_commit_status and ci_build jobs for #{status0} and #{status1}" do + before do + create(:generic_commit_status, status0, pipeline: pipeline) + create(:ci_build, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + + context "when there are ci_build jobs for #{status0} and #{status1}" do + before do + create(:ci_build, status0, pipeline: pipeline) + create(:ci_build, status1, pipeline: pipeline) + end + + it 'is cancelable' do + expect(pipeline.cancelable?).to be_truthy + end + end + end + end + + %i[success failed canceled].each do |status| + context "when there is a build #{status}" do + before do + create(:ci_build, status, pipeline: pipeline) + end + + it 'is not cancelable' do + expect(pipeline.cancelable?).to be_falsey + end + end + + context "when there is an external job #{status}" do + before do + create(:generic_commit_status, status, pipeline: pipeline) + end + + it 'is not cancelable' do + expect(pipeline.cancelable?).to be_falsey + end + end + end + end + + describe '#cancel_running' do + let(:latest_status) { pipeline.statuses.pluck(:status) } + + context 'when there is a running external job and created build' do + before do + create(:ci_build, :running, pipeline: pipeline) + create(:generic_commit_status, :running, pipeline: pipeline) + + pipeline.cancel_running + end + + it 'cancels both jobs' do + expect(latest_status).to contain_exactly('canceled', 'canceled') + end + end + + context 'when builds are in different stages' do + before do + create(:ci_build, :running, stage_idx: 0, pipeline: pipeline) + create(:ci_build, :running, stage_idx: 1, pipeline: pipeline) + + pipeline.cancel_running + end + + it 'cancels both jobs' do + expect(latest_status).to contain_exactly('canceled', 'canceled') + end + end + end + + describe '#retry_failed' do + let(:latest_status) { pipeline.statuses.latest.pluck(:status) } + + context 'when there is a failed build and failed external status' do + before do + create(:ci_build, :failed, name: 'build', pipeline: pipeline) + create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline) + + pipeline.retry_failed(create(:user)) + end + + it 'retries only build' do + expect(latest_status).to contain_exactly('pending', 'failed') + end + end + + context 'when builds are in different stages' do + before do + create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) + create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline) + + pipeline.retry_failed(create(:user)) + end + + it 'retries both builds' do + expect(latest_status).to contain_exactly('pending', 'pending') + end + end + + context 'when there are canceled and failed' do + before do + create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline) + create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline) + + pipeline.retry_failed(create(:user)) + end + + it 'retries both builds' do + expect(latest_status).to contain_exactly('pending', 'pending') + end + end + end + describe '#execute_hooks' do let!(:build_a) { create_build('a', 0) } let!(:build_b) { create_build('b', 1) } diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb index 87bffbdc54e..9defb17dc92 100644 --- a/spec/models/concerns/has_status_spec.rb +++ b/spec/models/concerns/has_status_spec.rb @@ -123,4 +123,100 @@ describe HasStatus do it_behaves_like 'build status summary' end end + + context 'for scope with one status' do + shared_examples 'having a job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + describe ".#{status}" do + it 'contains the job' do + expect(CommitStatus.public_send(status).all). + to contain_exactly(job) + end + end + + describe '.relevant' do + if status == :created + it 'contains nothing' do + expect(CommitStatus.relevant.all).to be_empty + end + else + it 'contains the job' do + expect(CommitStatus.relevant.all).to contain_exactly(job) + end + end + end + end + end + end + + %i[created running pending success + failed canceled skipped].each do |status| + it_behaves_like 'having a job', status + end + end + + context 'for scope with more statuses' do + shared_examples 'containing the job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + it 'contains the job' do + is_expected.to contain_exactly(job) + end + end + end + end + + shared_examples 'not containing the job' do |status| + %i[ci_build generic_commit_status].each do |type| + context "when it's #{status} #{type} job" do + let!(:job) { create(type, status) } + + it 'contains nothing' do + is_expected.to be_empty + end + end + end + end + + describe '.running_or_pending' do + subject { CommitStatus.running_or_pending } + + %i[running pending].each do |status| + it_behaves_like 'containing the job', status + end + + %i[created failed success].each do |status| + it_behaves_like 'not containing the job', status + end + end + + describe '.finished' do + subject { CommitStatus.finished } + + %i[success failed canceled].each do |status| + it_behaves_like 'containing the job', status + end + + %i[created running pending].each do |status| + it_behaves_like 'not containing the job', status + end + end + + describe '.cancelable' do + subject { CommitStatus.cancelable } + + %i[running pending created].each do |status| + it_behaves_like 'containing the job', status + end + + %i[failed success skipped canceled].each do |status| + it_behaves_like 'not containing the job', status + end + end + end end |