diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-19 18:09:10 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-19 18:09:10 +0300 |
commit | 9c8e8b5ffc6e11d827fa42f2dce5f90c4dc19493 (patch) | |
tree | fef494515627439a22a06addc7ff5f57d418778a /spec | |
parent | 690c904b5e340f14c04f93ff688b647b46f7d1a2 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
30 files changed, 715 insertions, 424 deletions
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb index 0f6845dc5ee..dbb5c357acb 100644 --- a/spec/factories/project_hooks.rb +++ b/spec/factories/project_hooks.rb @@ -29,5 +29,9 @@ FactoryBot.define do trait :with_push_branch_filter do push_events_branch_filter { 'my-branch-*' } end + + trait :permanently_disabled do + recent_failures { WebHook::FAILURE_THRESHOLD + 1 } + end end end diff --git a/spec/factories/users/namespace_user_callouts.rb b/spec/factories/users/namespace_user_callouts.rb new file mode 100644 index 00000000000..fded63d0cce --- /dev/null +++ b/spec/factories/users/namespace_user_callouts.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :namespace_callout, class: 'Users::NamespaceCallout' do + feature_name { :invite_members_banner } + + user + namespace + end +end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index d312965f6cf..44fd21e510a 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -21,7 +21,7 @@ RSpec.describe "Admin Runners" do let_it_be(:namespace) { create(:namespace) } let_it_be(:project) { create(:project, namespace: namespace, creator: user) } - context "runners registration" do + describe "runners registration" do before do visit admin_runners_path end @@ -164,7 +164,9 @@ RSpec.describe "Admin Runners" do end describe 'filter by status' do - let!(:never_contacted) { create(:ci_runner, :instance, description: 'runner-never-contacted', contacted_at: nil) } + let!(:never_contacted) do + create(:ci_runner, :instance, description: 'runner-never-contacted', contacted_at: nil) + end before do create(:ci_runner, :instance, description: 'runner-1', contacted_at: Time.zone.now) @@ -326,13 +328,15 @@ RSpec.describe "Admin Runners" do visit admin_runners_path page.within('[data-testid="runner-type-tabs"]') do click_on 'Instance' - - expect(page).to have_link('Instance', class: 'active') end end it_behaves_like 'shows no runners found' + it 'shows active tab' do + expect(page).to have_link('Instance', class: 'active') + end + it 'shows no runner' do expect(page).not_to have_content 'runner-project' expect(page).not_to have_content 'runner-group' @@ -402,8 +406,8 @@ RSpec.describe "Admin Runners" do end it 'sorts by last contact date' do - create(:ci_runner, :instance, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37') - create(:ci_runner, :instance, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37') + create(:ci_runner, :instance, description: 'runner-1', contacted_at: '2018-07-12') + create(:ci_runner, :instance, description: 'runner-2', contacted_at: '2018-07-13') visit admin_runners_path @@ -448,13 +452,13 @@ RSpec.describe "Admin Runners" do it 'updates ACTIVE runner status to paused=false' do visit admin_runners_path('status[]': 'ACTIVE') - expect(page).to have_current_path(admin_runners_path('paused[]': 'false') ) + expect(page).to have_current_path(admin_runners_path('paused[]': 'false')) end it 'updates PAUSED runner status to paused=true' do visit admin_runners_path('status[]': 'PAUSED') - expect(page).to have_current_path(admin_runners_path('paused[]': 'true') ) + expect(page).to have_current_path(admin_runners_path('paused[]': 'true')) end end end @@ -477,7 +481,9 @@ RSpec.describe "Admin Runners" do describe 'runner show page breadcrumbs' do it 'contains the current runner id and token' do page.within '[data-testid="breadcrumb-links"]' do - expect(page.find('[data-testid="breadcrumb-current-link"]')).to have_link("##{runner.id} (#{runner.short_sha})") + expect(page.find('[data-testid="breadcrumb-current-link"]')).to have_link( + "##{runner.id} (#{runner.short_sha})" + ) end end end @@ -515,16 +521,16 @@ RSpec.describe "Admin Runners" do describe "Runner edit page" do let(:runner) { create(:ci_runner, :project) } + let!(:project1) { create(:project) } + let!(:project2) { create(:project) } before do - @project1 = create(:project) - @project2 = create(:project) visit edit_admin_runner_path(runner) wait_for_requests end - describe 'runner edit page breadcrumbs' do + describe 'breadcrumbs' do it 'contains the current runner id and token' do page.within '[data-testid="breadcrumb-links"]' do expect(page).to have_link("##{runner.id} (#{runner.short_sha})") @@ -539,7 +545,7 @@ RSpec.describe "Admin Runners" do end end - describe 'when a runner is updated', :js do + context 'when a runner is updated', :js do before do click_on _('Save changes') wait_for_requests @@ -556,21 +562,21 @@ RSpec.describe "Admin Runners" do describe 'projects' do it 'contains project names' do - expect(page).to have_content(@project1.full_name) - expect(page).to have_content(@project2.full_name) + expect(page).to have_content(project1.full_name) + expect(page).to have_content(project2.full_name) end end describe 'search' do before do search_form = find('#runner-projects-search') - search_form.fill_in 'search', with: @project1.name + search_form.fill_in 'search', with: project1.name search_form.click_button 'Search' end it 'contains name of correct project' do - expect(page).to have_content(@project1.full_name) - expect(page).not_to have_content(@project2.full_name) + expect(page).to have_content(project1.full_name) + expect(page).not_to have_content(project2.full_name) end end @@ -584,12 +590,12 @@ RSpec.describe "Admin Runners" do assigned_project = page.find('[data-testid="assigned-projects"]') expect(page).to have_content('Runner assigned to project.') - expect(assigned_project).to have_content(@project2.path) + expect(assigned_project).to have_content(project2.path) end end context 'with specific runner' do - let(:runner) { create(:ci_runner, :project, projects: [@project1]) } + let(:runner) { create(:ci_runner, :project, projects: [project1]) } before do visit edit_admin_runner_path(runner) @@ -599,7 +605,7 @@ RSpec.describe "Admin Runners" do end context 'with locked runner' do - let(:runner) { create(:ci_runner, :project, projects: [@project1], locked: true) } + let(:runner) { create(:ci_runner, :project, projects: [project1], locked: true) } before do visit edit_admin_runner_path(runner) @@ -610,7 +616,7 @@ RSpec.describe "Admin Runners" do end describe 'disable/destroy' do - let(:runner) { create(:ci_runner, :project, projects: [@project1]) } + let(:runner) { create(:ci_runner, :project, projects: [project1]) } before do visit edit_admin_runner_path(runner) @@ -624,7 +630,7 @@ RSpec.describe "Admin Runners" do new_runner_project = page.find('[data-testid="unassigned-projects"]') expect(page).to have_content('Runner unassigned from project.') - expect(new_runner_project).to have_content(@project1.path) + expect(new_runner_project).to have_content(project1.path) end end end diff --git a/spec/features/groups/group_runners_spec.rb b/spec/features/groups/group_runners_spec.rb index b6aaab207ce..a129db6cb6f 100644 --- a/spec/features/groups/group_runners_spec.rb +++ b/spec/features/groups/group_runners_spec.rb @@ -17,7 +17,7 @@ RSpec.describe "Group Runners" do describe "Group runners page", :js do let!(:group_registration_token) { group.runners_token } - context "runners registration" do + describe "runners registration" do before do visit group_runners_path(group) end @@ -128,7 +128,7 @@ RSpec.describe "Group Runners" do end end - context 'filtered search' do + describe 'filtered search' do before do visit group_runners_path(group) end diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 2dafd66b406..1d3effd4a2a 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -591,14 +591,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do context 'when a new failures exists' do let(:base_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_success) reports.get_suite('junit').add_test_case(create_test_case_java_success) end end let(:head_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_success) reports.get_suite('junit').add_test_case(create_test_case_java_failed) end @@ -639,14 +639,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do context 'when an existing failure exists' do let(:base_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_failed) reports.get_suite('junit').add_test_case(create_test_case_java_success) end end let(:head_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_failed) reports.get_suite('junit').add_test_case(create_test_case_java_success) end @@ -686,14 +686,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do context 'when a resolved failure exists' do let(:base_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_success) reports.get_suite('junit').add_test_case(create_test_case_java_failed) end end let(:head_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_success) reports.get_suite('junit').add_test_case(create_test_case_java_success) end @@ -732,14 +732,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do context 'when a new error exists' do let(:base_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_success) reports.get_suite('junit').add_test_case(create_test_case_java_success) end end let(:head_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_success) reports.get_suite('junit').add_test_case(create_test_case_java_error) end @@ -779,14 +779,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do context 'when an existing error exists' do let(:base_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_error) reports.get_suite('junit').add_test_case(create_test_case_java_success) end end let(:head_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_error) reports.get_suite('junit').add_test_case(create_test_case_java_success) end @@ -825,14 +825,14 @@ RSpec.describe 'Merge request > User sees merge widget', :js do context 'when a resolved error exists' do let(:base_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_success) reports.get_suite('junit').add_test_case(create_test_case_java_error) end end let(:head_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| reports.get_suite('rspec').add_test_case(create_test_case_rspec_success) reports.get_suite('junit').add_test_case(create_test_case_java_success) end @@ -871,7 +871,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do context 'properly truncates the report' do let(:base_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| 10.times do |index| reports.get_suite('rspec').add_test_case( create_test_case_rspec_failed(index)) @@ -882,7 +882,7 @@ RSpec.describe 'Merge request > User sees merge widget', :js do end let(:head_reports) do - Gitlab::Ci::Reports::TestReports.new.tap do |reports| + Gitlab::Ci::Reports::TestReport.new.tap do |reports| 10.times do |index| reports.get_suite('rspec').add_test_case( create_test_case_rspec_failed(index)) diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index e222f0d34a9..f5cafa2b2ec 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -464,6 +464,20 @@ RSpec.describe 'File blob', :js do end end + context 'binary file that appears to be text in the first 1024 bytes' do + before do + visit_blob('encoding/binary-1.bin', ref: 'binary-encoding') + end + + it 'displays the blob' do + expect(page).to have_link('Download (23.81 KiB)') + # does not show a viewer switcher + expect(page).not_to have_selector('.js-blob-viewer-switcher') + expect(page).not_to have_selector('.js-copy-blob-source-btn:not(.disabled)') + expect(page).not_to have_link('Open raw') + end + end + context 'empty file' do before do project.add_maintainer(project.creator) diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb index 56506ada3ce..dcd6f1239bb 100644 --- a/spec/features/projects/diffs/diff_show_spec.rb +++ b/spec/features/projects/diffs/diff_show_spec.rb @@ -169,8 +169,8 @@ RSpec.describe 'Diff file viewer', :js, :with_clean_rails_cache do wait_for_requests end - it 'shows there is no preview' do - expect(page).to have_content('No preview for this file type') + it 'shows that file was added' do + expect(page).to have_content('File added') end end end diff --git a/spec/frontend/issuable/linked_resources/components/__snapshots__/resource_links_block_spec.js.snap b/spec/frontend/issuable/linked_resources/components/__snapshots__/resource_links_block_spec.js.snap deleted file mode 100644 index 2ccfe4f91e7..00000000000 --- a/spec/frontend/issuable/linked_resources/components/__snapshots__/resource_links_block_spec.js.snap +++ /dev/null @@ -1,215 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ResourceLinksBlock with defaults renders correct component 1`] = ` -<div - class="gl-mt-5" - id="resource-links" -> - <div - class="card card-slim gl-overflow-hidden" - > - <div - class="card-header gl-display-flex gl-justify-content-space-between panel-empty-heading border-bottom-0" - > - <h3 - class="card-title h5 position-relative gl-my-0 gl-display-flex gl-align-items-center gl-h-7" - > - <a - aria-hidden="true" - class="gl-link anchor position-absolute gl-text-decoration-none" - href="#resource-links" - id="user-content-resource-links" - /> - Linked resources - <a - aria-label="Read more about linked resources" - class="gl-link gl-display-flex gl-align-items-center gl-ml-2 gl-text-gray-500" - data-testid="help-link" - href="/help/user/project/issues/linked_resources" - rel="noopener" - target="_blank" - > - <svg - aria-hidden="true" - class="gl-icon s12" - data-testid="question-icon" - role="img" - > - <use - href="#question" - /> - </svg> - </a> - - <div - class="gl-display-inline-flex" - > - <div - class="gl-display-inline-flex gl-mx-5" - > - <span - class="gl-display-inline-flex gl-align-items-center" - > - <svg - aria-hidden="true" - class="gl-mr-2 gl-text-gray-500 gl-icon s16" - data-testid="link-icon" - role="img" - > - <use - href="#link" - /> - </svg> - - 0 - - </span> - </div> - - <button - aria-label="Add a resource link" - class="btn btn-default btn-md gl-button btn-icon" - type="button" - > - <!----> - - <svg - aria-hidden="true" - class="gl-button-icon gl-icon s16" - data-testid="plus-icon" - role="img" - > - <use - href="#plus" - /> - </svg> - - <!----> - </button> - </div> - </h3> - </div> - - <div - class="linked-issues-card-body bg-gray-light" - > - <div - class="card-body bordered-box gl-bg-white" - style="display: none;" - > - <form> - <fieldset - aria-describedby="" - class="form-group gl-form-group" - id="__BVID__14" - > - <legend - class="bv-no-focus-ring col-form-label pt-0 col-form-label" - id="__BVID__14__BV_label_" - tabindex="-1" - > - - Text (Optional) - - <!----> - - <!----> - </legend> - <div - aria-labelledby="__BVID__14__BV_label_" - class="bv-no-focus-ring" - role="group" - tabindex="-1" - > - <input - class="gl-form-input form-control" - data-testid="link-text-input" - id="__BVID__16" - type="text" - /> - <!----> - <!----> - <!----> - </div> - </fieldset> - - <fieldset - aria-describedby="" - class="form-group gl-form-group" - id="__BVID__18" - > - <legend - class="bv-no-focus-ring col-form-label pt-0 col-form-label" - id="__BVID__18__BV_label_" - tabindex="-1" - > - - Link - - <!----> - - <!----> - </legend> - <div - aria-labelledby="__BVID__18__BV_label_" - class="bv-no-focus-ring" - role="group" - tabindex="-1" - > - <input - class="gl-form-input form-control" - data-testid="link-value-input" - id="__BVID__20" - type="text" - /> - <!----> - <!----> - <!----> - </div> - </fieldset> - - <div - class="gl-mt-5 gl-clearfix" - > - <button - class="btn gl-float-left btn-confirm btn-md disabled gl-button" - data-testid="add-button" - disabled="disabled" - type="submit" - > - <!----> - - <!----> - - <span - class="gl-button-text" - > - - Add - - </span> - </button> - - <button - class="btn gl-float-right btn-default btn-md gl-button" - type="button" - > - <!----> - - <!----> - - <span - class="gl-button-text" - > - - Cancel - - </span> - </button> - </div> - </form> - </div> - </div> - </div> -</div> -`; diff --git a/spec/frontend/issuable/linked_resources/components/add_issuable_resource_link_form_spec.js b/spec/frontend/issuable/linked_resources/components/add_issuable_resource_link_form_spec.js deleted file mode 100644 index b3707569848..00000000000 --- a/spec/frontend/issuable/linked_resources/components/add_issuable_resource_link_form_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import { nextTick } from 'vue'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -import AddIssuableResourceLinkForm from '~/linked_resources/components/add_issuable_resource_link_form.vue'; - -describe('AddIssuableResourceLinkForm', () => { - let wrapper; - - const mountComponent = () => { - wrapper = mountExtended(AddIssuableResourceLinkForm); - }; - - afterEach(() => { - if (wrapper) { - wrapper.destroy(); - } - }); - - const findAddButton = () => wrapper.findByTestId('add-button'); - const findCancelButton = () => wrapper.findByText('Cancel'); - const findLinkTextInput = () => wrapper.findByTestId('link-text-input'); - const findLinkValueInput = () => wrapper.findByTestId('link-value-input'); - - const cancelForm = async () => { - await findCancelButton().trigger('click'); - }; - - describe('cancel form button', () => { - const closeFormEvent = { 'add-issuable-resource-link-form-cancel': [[]] }; - - beforeEach(() => { - mountComponent(); - }); - - it('should close the form on cancel', async () => { - await cancelForm(); - - expect(wrapper.emitted()).toEqual(closeFormEvent); - }); - - it('keeps the button disabled without input', () => { - expect(findAddButton().props('disabled')).toBe(true); - }); - - it('keeps the button disabled with only text input', async () => { - findLinkTextInput().setValue('link text'); - - await nextTick(); - - expect(findAddButton().props('disabled')).toBe(true); - }); - - it('enables add button when link input is provided', async () => { - findLinkTextInput().setValue('link text'); - findLinkValueInput().setValue('https://foo.example.com'); - - await nextTick(); - - expect(findAddButton().props('disabled')).toBe(false); - }); - }); -}); diff --git a/spec/frontend/issuable/linked_resources/components/resource_links_block_spec.js b/spec/frontend/issuable/linked_resources/components/resource_links_block_spec.js deleted file mode 100644 index bca63a34b2e..00000000000 --- a/spec/frontend/issuable/linked_resources/components/resource_links_block_spec.js +++ /dev/null @@ -1,90 +0,0 @@ -import { GlButton } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -import ResourceLinksBlock from '~/linked_resources/components/resource_links_block.vue'; -import AddIssuableResourceLinkForm from '~/linked_resources/components/add_issuable_resource_link_form.vue'; - -describe('ResourceLinksBlock', () => { - let wrapper; - - const findResourceLinkAddButton = () => wrapper.find(GlButton); - const resourceLinkForm = () => wrapper.findComponent(AddIssuableResourceLinkForm); - const helpPath = '/help/user/project/issues/linked_resources'; - - const mountComponent = () => { - wrapper = mountExtended(ResourceLinksBlock, { - propsData: { - helpPath, - canAddResourceLinks: true, - }, - data() { - return { - isFormVisible: false, - }; - }, - }); - - afterEach(() => { - if (wrapper) { - wrapper.destroy(); - } - }); - }; - - describe('with defaults', () => { - beforeEach(() => { - mountComponent(); - }); - - it('renders correct component', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - it('should show the form when add button is clicked', async () => { - await findResourceLinkAddButton().trigger('click'); - - expect(resourceLinkForm().isVisible()).toBe(true); - }); - - it('should hide the form when the hide event is emitted', async () => { - // open the form - await findResourceLinkAddButton().trigger('click'); - - await resourceLinkForm().vm.$emit('add-issuable-resource-link-form-cancel'); - - expect(resourceLinkForm().isVisible()).toBe(false); - }); - }); - - describe('with canAddResourceLinks=false', () => { - it('does not show the add button', () => { - wrapper = shallowMount(ResourceLinksBlock, { - propsData: { - canAddResourceLinks: false, - }, - }); - - expect(findResourceLinkAddButton().exists()).toBe(false); - expect(resourceLinkForm().isVisible()).toBe(false); - }); - }); - - describe('with isFormVisible=true', () => { - it('renders the form with correct props', () => { - wrapper = shallowMount(ResourceLinksBlock, { - propsData: { - canAddResourceLinks: true, - }, - data() { - return { - isFormVisible: true, - isSubmitting: false, - }; - }, - }); - - expect(resourceLinkForm().exists()).toBe(true); - expect(resourceLinkForm().props('isSubmitting')).toBe(false); - }); - }); -}); diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js index 0581a40b6a2..a5b2b1d7cf8 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js @@ -109,5 +109,17 @@ describe('cleanup_status', () => { expect(findPopover().findComponent(GlLink).exists()).toBe(true); expect(findPopover().findComponent(GlLink).attributes('href')).toBe(cleanupPolicyHelpPage); }); + + it('id matches popover target attribute', () => { + mountComponent({ + status: UNFINISHED_STATUS, + next_run_at: '2063-04-08T01:44:03Z', + }); + + const id = findExtraInfoIcon().attributes('id'); + + expect(id).toMatch(/status-info-[0-9]+/); + expect(findPopover().props('target')).toEqual(id); + }); }); }); diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_menu_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_menu_spec.js new file mode 100644 index 00000000000..f8471b7f167 --- /dev/null +++ b/spec/frontend/work_items/components/work_item_links/work_item_links_menu_spec.js @@ -0,0 +1,141 @@ +import Vue from 'vue'; +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { cloneDeep } from 'lodash'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import WorkItemLinksMenu from '~/work_items/components/work_item_links/work_item_links_menu.vue'; +import changeWorkItemParentMutation from '~/work_items/graphql/change_work_item_parent_link.mutation.graphql'; +import getWorkItemLinksQuery from '~/work_items/graphql/work_item_links.query.graphql'; +import { WIDGET_TYPE_HIERARCHY } from '~/work_items/constants'; +import { workItemHierarchyResponse, changeWorkItemParentMutationResponse } from '../../mock_data'; + +Vue.use(VueApollo); + +const PARENT_ID = 'gid://gitlab/WorkItem/1'; +const WORK_ITEM_ID = 'gid://gitlab/WorkItem/3'; + +describe('WorkItemLinksMenu', () => { + let wrapper; + let mockApollo; + + const $toast = { + show: jest.fn(), + }; + + const createComponent = async ({ + data = {}, + mutationHandler = jest.fn().mockResolvedValue(changeWorkItemParentMutationResponse), + } = {}) => { + mockApollo = createMockApollo([ + [getWorkItemLinksQuery, jest.fn().mockResolvedValue(workItemHierarchyResponse)], + [changeWorkItemParentMutation, mutationHandler], + ]); + + mockApollo.clients.defaultClient.cache.writeQuery({ + query: getWorkItemLinksQuery, + variables: { + id: PARENT_ID, + }, + data: workItemHierarchyResponse.data, + }); + + wrapper = shallowMountExtended(WorkItemLinksMenu, { + data() { + return { + ...data, + }; + }, + propsData: { + workItemId: WORK_ITEM_ID, + parentWorkItemId: PARENT_ID, + }, + apolloProvider: mockApollo, + mocks: { + $toast, + }, + }); + + await waitForPromises(); + }; + + const findDropdown = () => wrapper.find(GlDropdown); + const findRemoveDropdownItem = () => wrapper.find(GlDropdownItem); + + beforeEach(async () => { + await createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + mockApollo = null; + }); + + it('renders dropdown and dropdown items', () => { + expect(findDropdown().exists()).toBe(true); + expect(findRemoveDropdownItem().exists()).toBe(true); + }); + + it('calls correct mutation with correct variables', async () => { + const mutationHandler = jest.fn().mockResolvedValue(changeWorkItemParentMutationResponse); + + createComponent({ mutationHandler }); + + findRemoveDropdownItem().vm.$emit('click'); + + await waitForPromises(); + + expect(mutationHandler).toHaveBeenCalledWith({ + id: WORK_ITEM_ID, + parentId: null, + }); + }); + + it('shows toast when mutation succeeds', async () => { + const mutationHandler = jest.fn().mockResolvedValue(changeWorkItemParentMutationResponse); + + createComponent({ mutationHandler }); + + findRemoveDropdownItem().vm.$emit('click'); + + await waitForPromises(); + + expect($toast.show).toHaveBeenCalledWith('Child removed', { + action: { onClick: expect.anything(), text: 'Undo' }, + }); + }); + + it('updates the cache when mutation succeeds', async () => { + const mutationHandler = jest.fn().mockResolvedValue(changeWorkItemParentMutationResponse); + + createComponent({ mutationHandler }); + + mockApollo.clients.defaultClient.cache.readQuery = jest.fn( + () => workItemHierarchyResponse.data, + ); + + mockApollo.clients.defaultClient.cache.writeQuery = jest.fn(); + + findRemoveDropdownItem().vm.$emit('click'); + + await waitForPromises(); + + // Remove the work item from parent's children + const resp = cloneDeep(workItemHierarchyResponse); + const index = resp.data.workItem.widgets + .find((widget) => widget.type === WIDGET_TYPE_HIERARCHY) + .children.nodes.findIndex((child) => child.id === WORK_ITEM_ID); + resp.data.workItem.widgets + .find((widget) => widget.type === WIDGET_TYPE_HIERARCHY) + .children.nodes.splice(index, 1); + + expect(mockApollo.clients.defaultClient.cache.writeQuery).toHaveBeenCalledWith( + expect.objectContaining({ + query: expect.anything(), + variables: { id: PARENT_ID }, + data: resp.data, + }), + ); + }); +}); diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index f33d884144b..0359caf7116 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -392,6 +392,25 @@ export const workItemHierarchyResponse = { }, }; +export const changeWorkItemParentMutationResponse = { + data: { + workItemUpdate: { + workItem: { + id: 'gid://gitlab/WorkItem/2', + workItemType: { + id: 'gid://gitlab/WorkItems::Type/5', + __typename: 'WorkItemType', + }, + title: 'Foo', + state: 'OPEN', + __typename: 'WorkItem', + }, + errors: [], + __typename: 'WorkItemUpdatePayload', + }, + }, +}; + export const availableWorkItemsResponse = { data: { workspace: { diff --git a/spec/helpers/users/callouts_helper_spec.rb b/spec/helpers/users/callouts_helper_spec.rb index 71a8d340b30..2c148aabead 100644 --- a/spec/helpers/users/callouts_helper_spec.rb +++ b/spec/helpers/users/callouts_helper_spec.rb @@ -222,4 +222,60 @@ RSpec.describe Users::CalloutsHelper do it { is_expected.to be true } end end + + describe '#web_hook_disabled_dismissed?' do + context 'without a project' do + it 'is false' do + expect(helper).not_to be_web_hook_disabled_dismissed(nil) + end + end + + context 'with a project' do + let_it_be(:project) { create(:project) } + + context 'the web-hook failure callout has never been dismissed' do + it 'is false' do + expect(helper).not_to be_web_hook_disabled_dismissed(project) + end + end + + context 'the web-hook failure callout has been dismissed', :freeze_time do + before do + create(:namespace_callout, + feature_name: described_class::WEB_HOOK_DISABLED, + user: user, + namespace: project.namespace, + dismissed_at: 1.week.ago) + end + + it 'is true' do + expect(helper).to be_web_hook_disabled_dismissed(project) + end + + context 'when there was an older failure', :clean_gitlab_redis_shared_state do + let(:key) { "web_hooks:last_failure:project-#{project.id}" } + + before do + Gitlab::Redis::SharedState.with { |r| r.set(key, 1.month.ago.iso8601) } + end + + it 'is true' do + expect(helper).to be_web_hook_disabled_dismissed(project) + end + end + + context 'when there has been a more recent failure', :clean_gitlab_redis_shared_state do + let(:key) { "web_hooks:last_failure:project-#{project.id}" } + + before do + Gitlab::Redis::SharedState.with { |r| r.set(key, 1.day.ago.iso8601) } + end + + it 'is false' do + expect(helper).not_to be_web_hook_disabled_dismissed(project) + end + end + end + end + end end diff --git a/spec/helpers/web_hooks/web_hooks_helper_spec.rb b/spec/helpers/web_hooks/web_hooks_helper_spec.rb new file mode 100644 index 00000000000..473f33a982f --- /dev/null +++ b/spec/helpers/web_hooks/web_hooks_helper_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WebHooks::WebHooksHelper do + let_it_be_with_reload(:project) { create(:project) } + + let(:current_user) { nil } + let(:callout_dismissed) { false } + let(:web_hooks_disable_failed) { false } + let(:webhooks_failed_callout) { false } + + before do + allow(helper).to receive(:current_user).and_return(current_user) + allow(helper).to receive(:web_hook_disabled_dismissed?).with(project).and_return(callout_dismissed) + + stub_feature_flags( + webhooks_failed_callout: webhooks_failed_callout, + web_hooks_disable_failed: web_hooks_disable_failed + ) + end + + shared_context 'user is logged in' do + let(:current_user) { create(:user) } + end + + shared_context 'webhooks_failed_callout is enabled' do + let(:webhooks_failed_callout) { true } + end + + shared_context 'webhooks_failed_callout is enabled for this project' do + let(:webhooks_failed_callout) { project } + end + + shared_context 'web_hooks_disable_failed is enabled' do + let(:web_hooks_disable_failed) { true } + end + + shared_context 'web_hooks_disable_failed is enabled for this project' do + let(:web_hooks_disable_failed) { project } + end + + shared_context 'the user has permission' do + before do + project.add_maintainer(current_user) + end + end + + shared_context 'the user dismissed the callout' do + let(:callout_dismissed) { true } + end + + shared_context 'a hook has failed' do + before do + create(:project_hook, :permanently_disabled, project: project) + end + end + + describe '#show_project_hook_failed_callout?' do + context 'all conditions are met' do + include_context 'user is logged in' + include_context 'webhooks_failed_callout is enabled' + include_context 'web_hooks_disable_failed is enabled' + include_context 'the user has permission' + include_context 'a hook has failed' + + it 'is true' do + expect(helper).to be_show_project_hook_failed_callout(project: project) + end + + it 'caches the DB calls until the TTL', :use_clean_rails_memory_store_caching, :request_store do + helper.show_project_hook_failed_callout?(project: project) + + travel_to((described_class::EXPIRY_TTL - 1.second).from_now) do + expect do + helper.show_project_hook_failed_callout?(project: project) + end.not_to exceed_query_limit(0) + end + + travel_to((described_class::EXPIRY_TTL + 1.second).from_now) do + expect do + helper.show_project_hook_failed_callout?(project: project) + end.to exceed_query_limit(0) + end + end + end + + context 'all conditions are met, project scoped flags' do + include_context 'user is logged in' + include_context 'webhooks_failed_callout is enabled for this project' + include_context 'web_hooks_disable_failed is enabled for this project' + include_context 'the user has permission' + include_context 'a hook has failed' + + it 'is true' do + expect(helper).to be_show_project_hook_failed_callout(project: project) + end + end + + context 'one condition is not met' do + contexts = [ + 'user is logged in', + 'webhooks_failed_callout is enabled', + 'web_hooks_disable_failed is enabled', + 'the user has permission', + 'a hook has failed' + ] + + contexts.each do |name| + context "namely #{name}" do + contexts.each { |ctx| include_context(ctx) unless ctx == name } + + it 'is false' do + expect(helper).not_to be_show_project_hook_failed_callout(project: project) + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/reports/test_reports_spec.rb b/spec/lib/gitlab/ci/reports/test_report_spec.rb index 24c00de3731..539510bca9e 100644 --- a/spec/lib/gitlab/ci/reports/test_reports_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_report_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Reports::TestReports do +RSpec.describe Gitlab::Ci::Reports::TestReport do include TestReportsHelper let(:test_reports) { described_class.new } diff --git a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb index 3483dddca3a..ac64e4699fe 100644 --- a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb +++ b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb @@ -6,8 +6,8 @@ RSpec.describe Gitlab::Ci::Reports::TestReportsComparer do include TestReportsHelper let(:comparer) { described_class.new(base_reports, head_reports) } - let(:base_reports) { Gitlab::Ci::Reports::TestReports.new } - let(:head_reports) { Gitlab::Ci::Reports::TestReports.new } + let(:base_reports) { Gitlab::Ci::Reports::TestReport.new } + let(:head_reports) { Gitlab::Ci::Reports::TestReport.new } describe '#suite_comparers' do subject { comparer.suite_comparers } diff --git a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb index 2f3d44f6f8f..f1f72d71e1a 100644 --- a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb +++ b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb @@ -68,10 +68,10 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez end context 'with multiple jobs to run' do - it 'runs all jobs created within the last 48 hours' do + it 'runs all jobs created within the last 3 hours' do old_migration = define_background_migration(migration_name) - travel 3.days + travel 4.hours new_migration = define_background_migration('NewMigration') { travel 1.second } migration.queue_batched_background_migration('NewMigration', table_name, :id, diff --git a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb index 678d4a90e8d..2811bc859da 100644 --- a/spec/lib/gitlab/dependency_linker/base_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/base_linker_spec.rb @@ -33,7 +33,7 @@ RSpec.describe Gitlab::DependencyLinker::BaseLinker do it 'only converts valid links' do expect(subject).to eq( <<~CONTENT - <span><span>#{link('http://')}</span><span>#{link('\n', url: '%5Cn')}</span><span>#{link('javascript:alert(1)', url: nil)}</span></span> + <span><span>#{link('http://', url: nil)}</span><span>#{link('\n', url: nil)}</span><span>#{link('javascript:alert(1)', url: nil)}</span></span> <span><span>#{link('https://gitlab.com/gitlab-org/gitlab')}</span></span> CONTENT ) diff --git a/spec/migrations/20220715163254_update_notes_in_past_spec.rb b/spec/migrations/20220715163254_update_notes_in_past_spec.rb new file mode 100644 index 00000000000..58e6cabc129 --- /dev/null +++ b/spec/migrations/20220715163254_update_notes_in_past_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require_migration! + +RSpec.describe UpdateNotesInPast, :migration do + let(:notes) { table(:notes) } + + it 'updates created_at when it is too much in the past' do + notes.create!(id: 10, note: 'note', created_at: '2009-06-01') + notes.create!(id: 11, note: 'note', created_at: '1970-01-01') + notes.create!(id: 12, note: 'note', created_at: '1600-06-01') + + migrate! + + expect(notes.all).to contain_exactly( + an_object_having_attributes(id: 10, created_at: DateTime.parse('2009-06-01')), + an_object_having_attributes(id: 11, created_at: DateTime.parse('1970-01-01')), + an_object_having_attributes(id: 12, created_at: DateTime.parse('1970-01-01')) + ) + end +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 6d04fb86d22..e0166ba64a4 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -4281,7 +4281,7 @@ RSpec.describe Ci::Build do describe '#collect_test_reports!' do subject { build.collect_test_reports!(test_reports) } - let(:test_reports) { Gitlab::Ci::Reports::TestReports.new } + let(:test_reports) { Gitlab::Ci::Reports::TestReport.new } it { expect(test_reports.get_suite(build.name).total_count).to eq(0) } @@ -4332,7 +4332,7 @@ RSpec.describe Ci::Build do context 'when build is part of parallel build' do let(:build_1) { create(:ci_build, name: 'build 1/2') } - let(:test_report) { Gitlab::Ci::Reports::TestReports.new } + let(:test_report) { Gitlab::Ci::Reports::TestReport.new } before do build_1.collect_test_reports!(test_report) @@ -4356,7 +4356,7 @@ RSpec.describe Ci::Build do end context 'when build is part of matrix build' do - let(:test_report) { Gitlab::Ci::Reports::TestReports.new } + let(:test_report) { Gitlab::Ci::Reports::TestReport.new } let(:matrix_build_1) { create(:ci_build, :matrix) } before do diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index 4253686b843..923a6f92424 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -15,6 +15,19 @@ RSpec.describe ProjectHook do subject { build(:project_hook, project: create(:project)) } end + describe '.for_projects' do + it 'finds related project hooks' do + hook_a = create(:project_hook) + hook_b = create(:project_hook) + hook_c = create(:project_hook) + + expect(described_class.for_projects([hook_a.project, hook_b.project])) + .to contain_exactly(hook_a, hook_b) + expect(described_class.for_projects(hook_c.project)) + .to contain_exactly(hook_c) + end + end + describe '.push_hooks' do it 'returns hooks for push events only' do hook = create(:project_hook, push_events: true) @@ -50,4 +63,62 @@ RSpec.describe ProjectHook do ) end end + + describe '#update_last_failure', :clean_gitlab_redis_shared_state do + let_it_be(:hook) { create(:project_hook) } + + it 'is a method of this class' do + expect { hook.update_last_failure }.not_to raise_error + end + + context 'when the hook is executable' do + it 'does not update the state' do + expect(Gitlab::Redis::SharedState).not_to receive(:with) + + hook.update_last_failure + end + end + + context 'when the hook is failed' do + before do + allow(hook).to receive(:executable?).and_return(false) + end + + def last_failure + Gitlab::Redis::SharedState.with do |redis| + redis.get("web_hooks:last_failure:project-#{hook.project.id}") + end + end + + context 'there is no prior value', :freeze_time do + it 'updates the state' do + expect { hook.update_last_failure }.to change { last_failure }.to(Time.current) + end + end + + context 'there is a prior value, from before now' do + it 'updates the state' do + the_future = 1.minute.from_now + + hook.update_last_failure + + travel_to(the_future) do + expect { hook.update_last_failure }.to change { last_failure }.to(the_future.iso8601) + end + end + end + + context 'there is a prior value, from after now' do + it 'does not update the state' do + the_past = 1.minute.ago + + hook.update_last_failure + + travel_to(the_past) do + expect { hook.update_last_failure }.not_to change { last_failure } + end + end + end + end + end end diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index fb3968777bf..9faa5e1567c 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -187,8 +187,8 @@ RSpec.describe WebHook do end end - describe '.executable' do - let(:not_executable) do + describe '.executable/.disabled' do + let!(:not_executable) do [ [0, Time.current], [0, 1.minute.from_now], @@ -202,7 +202,7 @@ RSpec.describe WebHook do end end - let(:executables) do + let!(:executables) do [ [0, nil], [0, 1.day.ago], @@ -217,6 +217,7 @@ RSpec.describe WebHook do it 'finds the correct set of project hooks' do expect(described_class.where(project_id: project.id).executable).to match_array executables + expect(described_class.where(project_id: project.id).disabled).to match_array not_executable end context 'when the feature flag is not enabled' do @@ -224,7 +225,7 @@ RSpec.describe WebHook do stub_feature_flags(web_hooks_disable_failed: false) end - it 'is the same as all' do + specify 'enabled is the same as all' do expect(described_class.where(project_id: project.id).executable).to match_array(executables + not_executable) end end @@ -635,4 +636,10 @@ RSpec.describe WebHook do end end end + + describe '#update_last_failure' do + it 'is a method of this class' do + expect { described_class.new.update_last_failure }.not_to raise_error + end + end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 4b262c1f3a9..fc6f7832c2c 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -106,6 +106,22 @@ RSpec.describe Note do end end + describe 'created_at in the past' do + let_it_be(:noteable) { create(:issue) } + + context 'when creating a note not too much in the past' do + subject { build(:note, project: noteable.project, noteable: noteable, created_at: '1990-05-06') } + + it { is_expected.to be_valid } + end + + context 'when creating a note too much in the past' do + subject { build(:note, project: noteable.project, noteable: noteable, created_at: '1600-05-06') } + + it { is_expected.not_to be_valid } + end + end + describe 'confidentiality' do context 'for existing public note' do let_it_be(:existing_note) { create(:note) } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d3b229c2094..6d2ba66d5f4 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -136,6 +136,7 @@ RSpec.describe User do it { is_expected.to have_many(:timelogs) } it { is_expected.to have_many(:callouts).class_name('Users::Callout') } it { is_expected.to have_many(:group_callouts).class_name('Users::GroupCallout') } + it { is_expected.to have_many(:namespace_callouts).class_name('Users::NamespaceCallout') } describe '#user_detail' do it 'does not persist `user_detail` by default' do @@ -6415,6 +6416,96 @@ RSpec.describe User do end end + describe 'Users::NamespaceCallout' do + describe '#dismissed_callout_for_namespace?' do + let_it_be(:user, refind: true) { create(:user) } + let_it_be(:namespace) { create(:namespace) } + let_it_be(:feature_name) { Users::NamespaceCallout.feature_names.each_key.first } + + let(:query) do + { feature_name: feature_name, namespace: namespace } + end + + def have_dismissed_callout + be_dismissed_callout_for_namespace(**query) + end + + context 'when no callout dismissal record exists' do + it 'returns false when no ignore_dismissal_earlier_than provided' do + expect(user).not_to have_dismissed_callout + end + end + + context 'when dismissed callout exists' do + before_all do + create(:namespace_callout, + user: user, + namespace_id: namespace.id, + feature_name: feature_name, + dismissed_at: 4.months.ago) + end + + it 'returns true when no ignore_dismissal_earlier_than provided' do + expect(user).to have_dismissed_callout + end + + it 'returns true when ignore_dismissal_earlier_than is earlier than dismissed_at' do + query[:ignore_dismissal_earlier_than] = 6.months.ago + + expect(user).to have_dismissed_callout + end + + it 'returns false when ignore_dismissal_earlier_than is later than dismissed_at' do + query[:ignore_dismissal_earlier_than] = 2.months.ago + + expect(user).not_to have_dismissed_callout + end + end + end + + describe '#find_or_initialize_namespace_callout' do + let_it_be(:user, refind: true) { create(:user) } + let_it_be(:namespace) { create(:namespace) } + let_it_be(:feature_name) { Users::NamespaceCallout.feature_names.each_key.first } + + subject(:callout_with_source) do + user.find_or_initialize_namespace_callout(feature_name, namespace.id) + end + + context 'when callout exists' do + let!(:callout) do + create(:namespace_callout, user: user, feature_name: feature_name, namespace_id: namespace.id) + end + + it 'returns existing callout' do + expect(callout_with_source).to eq(callout) + end + end + + context 'when callout does not exist' do + context 'when feature name is valid' do + it 'initializes a new callout' do + expect(callout_with_source) + .to be_a_new(Users::NamespaceCallout) + .and be_valid + end + end + + context 'when feature name is not valid' do + let(:feature_name) { 'notvalid' } + + it 'initializes a new callout' do + expect(callout_with_source).to be_a_new(Users::NamespaceCallout) + end + + it 'is not valid' do + expect(callout_with_source).not_to be_valid + end + end + end + end + end + describe '#dismissed_callout_for_group?' do let_it_be(:user, refind: true) { create(:user) } let_it_be(:group) { create(:group) } diff --git a/spec/models/users/namespace_callout_spec.rb b/spec/models/users/namespace_callout_spec.rb new file mode 100644 index 00000000000..f8207f2abc8 --- /dev/null +++ b/spec/models/users/namespace_callout_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Users::NamespaceCallout do + let_it_be(:user) { create_default(:user) } + let_it_be(:namespace) { create_default(:namespace) } + let_it_be(:callout) { create(:namespace_callout) } + + it_behaves_like 'having unique enum values' + + describe 'relationships' do + it { is_expected.to belong_to(:namespace) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:namespace) } + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:feature_name) } + + specify do + is_expected.to validate_uniqueness_of(:feature_name) + .scoped_to(:user_id, :namespace_id) + .ignoring_case_sensitivity + end + + it { is_expected.to allow_value(:web_hook_disabled).for(:feature_name) } + + it 'rejects invalid feature names' do + expect { callout.feature_name = :non_existent_feature }.to raise_error(ArgumentError) + end + end + + describe '#source_feature_name' do + it 'provides string based off source and feature' do + expect(callout.source_feature_name).to eq "#{callout.feature_name}_#{callout.namespace_id}" + end + end +end diff --git a/spec/serializers/test_reports_comparer_entity_spec.rb b/spec/serializers/test_reports_comparer_entity_spec.rb index 3f88438ccde..78aa64edae0 100644 --- a/spec/serializers/test_reports_comparer_entity_spec.rb +++ b/spec/serializers/test_reports_comparer_entity_spec.rb @@ -7,8 +7,8 @@ RSpec.describe TestReportsComparerEntity do let(:entity) { described_class.new(comparer) } let(:comparer) { Gitlab::Ci::Reports::TestReportsComparer.new(base_reports, head_reports) } - let(:base_reports) { Gitlab::Ci::Reports::TestReports.new } - let(:head_reports) { Gitlab::Ci::Reports::TestReports.new } + let(:base_reports) { Gitlab::Ci::Reports::TestReport.new } + let(:head_reports) { Gitlab::Ci::Reports::TestReport.new } describe '#as_json' do subject { entity.as_json } diff --git a/spec/serializers/test_reports_comparer_serializer_spec.rb b/spec/serializers/test_reports_comparer_serializer_spec.rb index f9c37f49039..d19d9681e07 100644 --- a/spec/serializers/test_reports_comparer_serializer_spec.rb +++ b/spec/serializers/test_reports_comparer_serializer_spec.rb @@ -8,8 +8,8 @@ RSpec.describe TestReportsComparerSerializer do let(:project) { double(:project) } let(:serializer) { described_class.new(project: project).represent(comparer) } let(:comparer) { Gitlab::Ci::Reports::TestReportsComparer.new(base_reports, head_reports) } - let(:base_reports) { Gitlab::Ci::Reports::TestReports.new } - let(:head_reports) { Gitlab::Ci::Reports::TestReports.new } + let(:base_reports) { Gitlab::Ci::Reports::TestReport.new } + let(:head_reports) { Gitlab::Ci::Reports::TestReport.new } describe '#to_json' do subject { serializer.to_json } diff --git a/spec/services/web_hooks/log_execution_service_spec.rb b/spec/services/web_hooks/log_execution_service_spec.rb index 0ba0372b99d..873f6adc8dc 100644 --- a/spec/services/web_hooks/log_execution_service_spec.rb +++ b/spec/services/web_hooks/log_execution_service_spec.rb @@ -35,6 +35,12 @@ RSpec.describe WebHooks::LogExecutionService do expect(WebHookLog.recent.first).to have_attributes(data) end + it 'updates the last failure' do + expect(project_hook).to receive(:update_last_failure) + + service.execute + end + context 'obtaining an exclusive lease' do let(:lease_key) { "web_hooks:update_hook_failure_state:#{project_hook.id}" } diff --git a/spec/views/errors/omniauth_error.html.haml_spec.rb b/spec/views/errors/omniauth_error.html.haml_spec.rb new file mode 100644 index 00000000000..e99cb536bd8 --- /dev/null +++ b/spec/views/errors/omniauth_error.html.haml_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'errors/omniauth_error' do + let(:provider) { FFaker::Product.brand } + let(:error) { FFaker::Lorem.sentence } + + before do + assign(:provider, provider) + assign(:error, error) + end + + it 'renders template' do + render + + expect(rendered).to have_content(provider) + expect(rendered).to have_content(_('Sign-in failed because %{error}.') % { error: error }) + expect(rendered).to have_link('Sign in') + expect(rendered).to have_content(_('If none of the options work, try contacting a GitLab administrator.')) + end +end |