Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-07-19 18:09:10 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-07-19 18:09:10 +0300
commit9c8e8b5ffc6e11d827fa42f2dce5f90c4dc19493 (patch)
treefef494515627439a22a06addc7ff5f57d418778a /spec
parent690c904b5e340f14c04f93ff688b647b46f7d1a2 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/project_hooks.rb4
-rw-r--r--spec/factories/users/namespace_user_callouts.rb10
-rw-r--r--spec/features/admin/admin_runners_spec.rb52
-rw-r--r--spec/features/groups/group_runners_spec.rb4
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb28
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb14
-rw-r--r--spec/features/projects/diffs/diff_show_spec.rb4
-rw-r--r--spec/frontend/issuable/linked_resources/components/__snapshots__/resource_links_block_spec.js.snap215
-rw-r--r--spec/frontend/issuable/linked_resources/components/add_issuable_resource_link_form_spec.js61
-rw-r--r--spec/frontend/issuable/linked_resources/components/resource_links_block_spec.js90
-rw-r--r--spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status_spec.js12
-rw-r--r--spec/frontend/work_items/components/work_item_links/work_item_links_menu_spec.js141
-rw-r--r--spec/frontend/work_items/mock_data.js19
-rw-r--r--spec/helpers/users/callouts_helper_spec.rb56
-rw-r--r--spec/helpers/web_hooks/web_hooks_helper_spec.rb120
-rw-r--r--spec/lib/gitlab/ci/reports/test_report_spec.rb (renamed from spec/lib/gitlab/ci/reports/test_reports_spec.rb)2
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb4
-rw-r--r--spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb4
-rw-r--r--spec/lib/gitlab/dependency_linker/base_linker_spec.rb2
-rw-r--r--spec/migrations/20220715163254_update_notes_in_past_spec.rb23
-rw-r--r--spec/models/ci/build_spec.rb6
-rw-r--r--spec/models/hooks/project_hook_spec.rb71
-rw-r--r--spec/models/hooks/web_hook_spec.rb15
-rw-r--r--spec/models/note_spec.rb16
-rw-r--r--spec/models/user_spec.rb91
-rw-r--r--spec/models/users/namespace_callout_spec.rb39
-rw-r--r--spec/serializers/test_reports_comparer_entity_spec.rb4
-rw-r--r--spec/serializers/test_reports_comparer_serializer_spec.rb4
-rw-r--r--spec/services/web_hooks/log_execution_service_spec.rb6
-rw-r--r--spec/views/errors/omniauth_error.html.haml_spec.rb22
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