diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-06 21:07:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-06 21:07:44 +0300 |
commit | e1867c38fc5a4b931b4b2256d4909182e94f1051 (patch) | |
tree | 3047b637f7f9a31e74c62d3fe054b24c95e3534e /spec | |
parent | 63894d59abd34f76f399d755012cdcd32c5b1103 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
41 files changed, 810 insertions, 80 deletions
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index ae9932174e8..ebc22043891 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -77,6 +77,24 @@ describe Projects::RawController do execute_raw_requests(requests: 6, project: project, file_path: file_path) end + context 'when receiving an external storage request' do + let(:token) { 'letmein' } + + before do + stub_application_setting( + static_objects_external_storage_url: 'https://cdn.gitlab.com', + static_objects_external_storage_auth_token: token + ) + end + + it 'does not prevent from accessing the raw file' do + request.headers['X-Gitlab-External-Storage-Token'] = token + execute_raw_requests(requests: 6, project: project, file_path: file_path) + + expect(response).to have_gitlab_http_status(200) + end + end + context 'when the request uses a different version of a commit' do it 'prevents from accessing the raw file' do # 3 times with the normal sha @@ -131,15 +149,74 @@ describe Projects::RawController do end end end + + context 'as a sessionless user' do + let_it_be(:project) { create(:project, :private, :repository) } + let_it_be(:user) { create(:user, static_object_token: 'very-secure-token') } + let_it_be(:file_path) { 'master/README.md' } + + before do + project.add_developer(user) + end + + context 'when no token is provided' do + it 'redirects to sign in page' do + execute_raw_requests(requests: 1, project: project, file_path: file_path) + + expect(response).to have_gitlab_http_status(302) + expect(response.location).to end_with('/users/sign_in') + end + end + + context 'when a token param is present' do + context 'when token is correct' do + it 'calls the action normally' do + execute_raw_requests(requests: 1, project: project, file_path: file_path, token: user.static_object_token) + + expect(response).to have_gitlab_http_status(200) + end + end + + context 'when token is incorrect' do + it 'redirects to sign in page' do + execute_raw_requests(requests: 1, project: project, file_path: file_path, token: 'foobar') + + expect(response).to have_gitlab_http_status(302) + expect(response.location).to end_with('/users/sign_in') + end + end + end + + context 'when a token header is present' do + context 'when token is correct' do + it 'calls the action normally' do + request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token + execute_raw_requests(requests: 1, project: project, file_path: file_path) + + expect(response).to have_gitlab_http_status(200) + end + end + + context 'when token is incorrect' do + it 'redirects to sign in page' do + request.headers['X-Gitlab-Static-Object-Token'] = 'foobar' + execute_raw_requests(requests: 1, project: project, file_path: file_path) + + expect(response).to have_gitlab_http_status(302) + expect(response.location).to end_with('/users/sign_in') + end + end + end + end end - def execute_raw_requests(requests:, project:, file_path:) + def execute_raw_requests(requests:, project:, file_path:, **params) requests.times do get :show, params: { namespace_id: project.namespace, project_id: project, id: file_path - } + }.merge(params) end end end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index af6bb8c271f..5d86e4125df 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -611,4 +611,50 @@ describe 'File blob', :js do expect(page).to have_selector '.gpg-status-box.invalid' end end + + context 'when static objects external storage is enabled' do + before do + stub_application_setting(static_objects_external_storage_url: 'https://cdn.gitlab.com') + end + + context 'private project' do + let_it_be(:project) { create(:project, :repository, :private) } + let_it_be(:user) { create(:user) } + + before do + project.add_developer(user) + + sign_in(user) + visit_blob('README.md') + end + + it 'shows open raw and download buttons with external storage URL prepended and user token appended to their href' do + path = project_raw_path(project, 'master/README.md') + raw_uri = "https://cdn.gitlab.com#{path}?token=#{user.static_object_token}" + download_uri = "https://cdn.gitlab.com#{path}?inline=false&token=#{user.static_object_token}" + + aggregate_failures do + expect(page).to have_link 'Open raw', href: raw_uri + expect(page).to have_link 'Download', href: download_uri + end + end + end + + context 'public project' do + before do + visit_blob('README.md') + end + + it 'shows open raw and download buttons with external storage URL prepended to their href' do + path = project_raw_path(project, 'master/README.md') + raw_uri = "https://cdn.gitlab.com#{path}" + download_uri = "https://cdn.gitlab.com#{path}?inline=false" + + aggregate_failures do + expect(page).to have_link 'Open raw', href: raw_uri + expect(page).to have_link 'Download', href: download_uri + end + end + end + end end diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index 05d13a76e0e..116088f5141 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -170,41 +170,14 @@ describe PipelinesFinder do end end - context 'when order_by and sort are specified' do - context 'when order_by user_id' do - let(:params) { { order_by: 'user_id', sort: 'asc' } } - let(:users) { Array.new(2) { create(:user, developer_projects: [project]) } } - let!(:pipelines) { users.map { |user| create(:ci_pipeline, project: project, user: user) } } - - it 'sorts as user_id: :asc' do - is_expected.to match_array(pipelines) - end - - context 'when sort is invalid' do - let(:params) { { order_by: 'user_id', sort: 'invalid_sort' } } - - it 'sorts as user_id: :desc' do - is_expected.to eq(pipelines.sort_by { |p| -p.user.id }) - end - end - end - - context 'when order_by is invalid' do - let(:params) { { order_by: 'invalid_column', sort: 'asc' } } - let!(:pipelines) { create_list(:ci_pipeline, 2, project: project) } - - it 'sorts as id: :asc' do - is_expected.to eq(pipelines.sort_by { |p| p.id }) - end - end - - context 'when both are nil' do - let(:params) { { order_by: nil, sort: nil } } - let!(:pipelines) { create_list(:ci_pipeline, 2, project: project) } - - it 'sorts as id: :desc' do - is_expected.to eq(pipelines.sort_by { |p| -p.id }) - end + context 'when updated_at filters are specified' do + let(:params) { { updated_before: 1.day.ago, updated_after: 3.days.ago } } + let!(:pipeline1) { create(:ci_pipeline, project: project, updated_at: 2.days.ago) } + let!(:pipeline2) { create(:ci_pipeline, project: project, updated_at: 4.days.ago) } + let!(:pipeline3) { create(:ci_pipeline, project: project, updated_at: 1.hour.ago) } + + it 'returns deployments with matched updated_at' do + is_expected.to match_array([pipeline1]) end end @@ -249,5 +222,36 @@ describe PipelinesFinder do end end end + + describe 'ordering' do + using RSpec::Parameterized::TableSyntax + + let(:params) { { order_by: order_by, sort: sort } } + + let!(:pipeline_1) { create(:ci_pipeline, :scheduled, project: project, iid: 11, ref: 'master', created_at: Time.now, updated_at: Time.now, user: create(:user)) } + let!(:pipeline_2) { create(:ci_pipeline, :created, project: project, iid: 12, ref: 'feature', created_at: 1.day.ago, updated_at: 2.hours.ago, user: create(:user)) } + let!(:pipeline_3) { create(:ci_pipeline, :success, project: project, iid: 8, ref: 'patch', created_at: 2.days.ago, updated_at: 1.hour.ago, user: create(:user)) } + + where(:order_by, :sort, :ordered_pipelines) do + 'id' | 'asc' | [:pipeline_1, :pipeline_2, :pipeline_3] + 'id' | 'desc' | [:pipeline_3, :pipeline_2, :pipeline_1] + 'ref' | 'asc' | [:pipeline_2, :pipeline_1, :pipeline_3] + 'ref' | 'desc' | [:pipeline_3, :pipeline_1, :pipeline_2] + 'status' | 'asc' | [:pipeline_2, :pipeline_1, :pipeline_3] + 'status' | 'desc' | [:pipeline_3, :pipeline_1, :pipeline_2] + 'updated_at' | 'asc' | [:pipeline_2, :pipeline_3, :pipeline_1] + 'updated_at' | 'desc' | [:pipeline_1, :pipeline_3, :pipeline_2] + 'user_id' | 'asc' | [:pipeline_1, :pipeline_2, :pipeline_3] + 'user_id' | 'desc' | [:pipeline_3, :pipeline_2, :pipeline_1] + 'invalid' | 'asc' | [:pipeline_1, :pipeline_2, :pipeline_3] + 'id' | 'err' | [:pipeline_3, :pipeline_2, :pipeline_1] + end + + with_them do + it 'returns the pipelines ordered' do + expect(subject).to eq(ordered_pipelines.map { |name| public_send(name) }) + end + end + end end end diff --git a/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js b/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js index bff8ad0877a..21edcb7235a 100644 --- a/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js +++ b/spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js @@ -49,7 +49,9 @@ describe('error tracking settings form', () => { it('is rendered with labels and placeholders', () => { const pageText = wrapper.text(); - expect(pageText).toContain('Find your hostname in your Sentry account settings page'); + expect(pageText).toContain( + "If you self-host Sentry, enter the full URL of your Sentry instance. If you're using Sentry's hosted solution, enter https://sentry.io", + ); expect(pageText).toContain( "After adding your Auth Token, use the 'Connect' button to load projects", ); diff --git a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js index a3db3ee1b18..d02d8fa0253 100644 --- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js @@ -1,9 +1,9 @@ import Vue from 'vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import { projectData, branches } from 'spec/ide/mock_data'; import { createStore } from '~/ide/stores'; import commitActions from '~/ide/components/commit_sidebar/actions.vue'; import consts from '~/ide/stores/modules/commit/constants'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { projectData, branches } from 'spec/ide/mock_data'; const ACTION_UPDATE_COMMIT_ACTION = 'commit/updateCommitAction'; diff --git a/spec/javascripts/ide/components/commit_sidebar/form_spec.js b/spec/javascripts/ide/components/commit_sidebar/form_spec.js index b7a7afe4db4..fdbabf84e25 100644 --- a/spec/javascripts/ide/components/commit_sidebar/form_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/form_spec.js @@ -1,10 +1,10 @@ import Vue from 'vue'; -import store from '~/ide/stores'; -import CommitForm from '~/ide/components/commit_sidebar/form.vue'; -import { activityBarViews } from '~/ide/constants'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; import { projectData } from 'spec/ide/mock_data'; +import store from '~/ide/stores'; +import CommitForm from '~/ide/components/commit_sidebar/form.vue'; +import { activityBarViews } from '~/ide/constants'; import { resetStore } from '../../helpers'; describe('IDE commit form', () => { diff --git a/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js index 3c7d6192e2c..6eb912127d5 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import store from '~/ide/stores'; import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { file } from '../../helpers'; import { removeWhitespace } from '../../../helpers/text_helper'; diff --git a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js index c1dcd4928a0..caf06b5e1d8 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_item_spec.js @@ -1,9 +1,9 @@ import Vue from 'vue'; +import { trimText } from 'spec/helpers/text_helper'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import store from '~/ide/stores'; import listItem from '~/ide/components/commit_sidebar/list_item.vue'; import router from '~/ide/ide_router'; -import { trimText } from 'spec/helpers/text_helper'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { file, resetStore } from '../../helpers'; describe('Multi-file editor commit sidebar list item', () => { diff --git a/spec/javascripts/ide/components/commit_sidebar/list_spec.js b/spec/javascripts/ide/components/commit_sidebar/list_spec.js index b786be55019..81120f6d277 100644 --- a/spec/javascripts/ide/components/commit_sidebar/list_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/list_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import store from '~/ide/stores'; import commitSidebarList from '~/ide/components/commit_sidebar/list.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { file, resetStore } from '../../helpers'; describe('Multi-file editor commit sidebar list', () => { diff --git a/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js b/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js index af67991eadd..53508f52b2f 100644 --- a/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/message_field_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import CommitMessageField from '~/ide/components/commit_sidebar/message_field.vue'; import createComponent from 'spec/helpers/vue_mount_component_helper'; +import CommitMessageField from '~/ide/components/commit_sidebar/message_field.vue'; describe('IDE commit message field', () => { const Component = Vue.extend(CommitMessageField); diff --git a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js index 5f2db695241..02caf689c50 100644 --- a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js @@ -1,9 +1,9 @@ import Vue from 'vue'; -import store from '~/ide/stores'; -import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { projectData, branches } from 'spec/ide/mock_data'; import { resetStore } from 'spec/ide/helpers'; +import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue'; +import store from '~/ide/stores'; import consts from '../../../../../app/assets/javascripts/ide/stores/modules/commit/constants'; describe('create new MR checkbox', () => { diff --git a/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js index db1988be3e1..b30f0e6822b 100644 --- a/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js +++ b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js @@ -1,8 +1,8 @@ import Vue from 'vue'; -import store from '~/ide/stores'; -import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from 'spec/ide/helpers'; +import store from '~/ide/stores'; +import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue'; describe('IDE commit sidebar radio group', () => { let vm; diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js index 86146fcef69..4c2f29f55dd 100644 --- a/spec/javascripts/ide/components/file_row_extra_spec.js +++ b/spec/javascripts/ide/components/file_row_extra_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import { createStore } from '~/ide/stores'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import { createStore } from '~/ide/stores'; import FileRowExtra from '~/ide/components/file_row_extra.vue'; import { file, resetStore } from '../helpers'; diff --git a/spec/javascripts/ide/components/file_templates/bar_spec.js b/spec/javascripts/ide/components/file_templates/bar_spec.js index a688f7f69a6..5399ada94ae 100644 --- a/spec/javascripts/ide/components/file_templates/bar_spec.js +++ b/spec/javascripts/ide/components/file_templates/bar_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; +import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { createStore } from '~/ide/stores'; import Bar from '~/ide/components/file_templates/bar.vue'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore, file } from '../../helpers'; describe('IDE file templates bar component', () => { diff --git a/spec/javascripts/ide/components/ide_side_bar_spec.js b/spec/javascripts/ide/components/ide_side_bar_spec.js index 20ee20bc1d7..a2d15462ac5 100644 --- a/spec/javascripts/ide/components/ide_side_bar_spec.js +++ b/spec/javascripts/ide/components/ide_side_bar_spec.js @@ -1,8 +1,8 @@ import Vue from 'vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import store from '~/ide/stores'; import ideSidebar from '~/ide/components/ide_side_bar.vue'; import { activityBarViews } from '~/ide/constants'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../helpers'; import { projectData } from '../mock_data'; diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js index de4becec1cd..048db4a7533 100644 --- a/spec/javascripts/ide/components/ide_spec.js +++ b/spec/javascripts/ide/components/ide_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import store from '~/ide/stores'; import ide from '~/ide/components/ide.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { file, resetStore } from '../helpers'; import { projectData } from '../mock_data'; diff --git a/spec/javascripts/ide/components/ide_status_bar_spec.js b/spec/javascripts/ide/components/ide_status_bar_spec.js index bb8fb74c068..69f163574fb 100644 --- a/spec/javascripts/ide/components/ide_status_bar_spec.js +++ b/spec/javascripts/ide/components/ide_status_bar_spec.js @@ -1,8 +1,8 @@ import Vue from 'vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import store from '~/ide/stores'; import ideStatusBar from '~/ide/components/ide_status_bar.vue'; import { rightSidebarViews } from '~/ide/constants'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../helpers'; import { projectData } from '../mock_data'; diff --git a/spec/javascripts/ide/components/nav_dropdown_button_spec.js b/spec/javascripts/ide/components/nav_dropdown_button_spec.js index 19b0071567a..0d63869fba2 100644 --- a/spec/javascripts/ide/components/nav_dropdown_button_spec.js +++ b/spec/javascripts/ide/components/nav_dropdown_button_spec.js @@ -1,8 +1,8 @@ import Vue from 'vue'; -import NavDropdownButton from '~/ide/components/nav_dropdown_button.vue'; -import store from '~/ide/stores'; import { trimText } from 'spec/helpers/text_helper'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import NavDropdownButton from '~/ide/components/nav_dropdown_button.vue'; +import store from '~/ide/stores'; import { resetStore } from '../helpers'; describe('NavDropdown', () => { diff --git a/spec/javascripts/ide/components/nav_dropdown_spec.js b/spec/javascripts/ide/components/nav_dropdown_spec.js index af6665bcd62..fe1d0ca371d 100644 --- a/spec/javascripts/ide/components/nav_dropdown_spec.js +++ b/spec/javascripts/ide/components/nav_dropdown_spec.js @@ -1,8 +1,8 @@ import $ from 'jquery'; import Vue from 'vue'; +import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import store from '~/ide/stores'; import NavDropdown from '~/ide/components/nav_dropdown.vue'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; describe('IDE NavDropdown', () => { const Component = Vue.extend(NavDropdown); diff --git a/spec/javascripts/ide/components/new_dropdown/index_spec.js b/spec/javascripts/ide/components/new_dropdown/index_spec.js index aaebe88f314..03afe997fed 100644 --- a/spec/javascripts/ide/components/new_dropdown/index_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/index_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import store from '~/ide/stores'; import newDropdown from '~/ide/components/new_dropdown/index.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../../helpers'; describe('new dropdown component', () => { diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/javascripts/ide/components/new_dropdown/modal_spec.js index 0556feae46a..a1c00e99927 100644 --- a/spec/javascripts/ide/components/new_dropdown/modal_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/modal_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { createStore } from '~/ide/stores'; import modal from '~/ide/components/new_dropdown/modal.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; describe('new file modal component', () => { const Component = Vue.extend(modal); diff --git a/spec/javascripts/ide/components/new_dropdown/upload_spec.js b/spec/javascripts/ide/components/new_dropdown/upload_spec.js index d19af6af2d7..4ebd0977832 100644 --- a/spec/javascripts/ide/components/new_dropdown/upload_spec.js +++ b/spec/javascripts/ide/components/new_dropdown/upload_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import upload from '~/ide/components/new_dropdown/upload.vue'; import createComponent from 'spec/helpers/vue_mount_component_helper'; +import upload from '~/ide/components/new_dropdown/upload.vue'; describe('new dropdown upload', () => { let vm; diff --git a/spec/javascripts/ide/components/preview/navigator_spec.js b/spec/javascripts/ide/components/preview/navigator_spec.js index 576d2fae003..a5341997a3a 100644 --- a/spec/javascripts/ide/components/preview/navigator_spec.js +++ b/spec/javascripts/ide/components/preview/navigator_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import ClientsideNavigator from '~/ide/components/preview/navigator.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import ClientsideNavigator from '~/ide/components/preview/navigator.vue'; describe('IDE clientside preview navigator', () => { let vm; diff --git a/spec/javascripts/ide/components/repo_commit_section_spec.js b/spec/javascripts/ide/components/repo_commit_section_spec.js index 6c726c1e154..917eb1438bd 100644 --- a/spec/javascripts/ide/components/repo_commit_section_spec.js +++ b/spec/javascripts/ide/components/repo_commit_section_spec.js @@ -1,8 +1,8 @@ import Vue from 'vue'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import store from '~/ide/stores'; import router from '~/ide/ide_router'; import repoCommitSection from '~/ide/components/repo_commit_section.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { file, resetStore } from '../helpers'; describe('RepoCommitSection', () => { diff --git a/spec/javascripts/ide/components/shared/tokened_input_spec.js b/spec/javascripts/ide/components/shared/tokened_input_spec.js index b09bf760543..885fd976655 100644 --- a/spec/javascripts/ide/components/shared/tokened_input_spec.js +++ b/spec/javascripts/ide/components/shared/tokened_input_spec.js @@ -1,6 +1,6 @@ import Vue from 'vue'; -import TokenedInput from '~/ide/components/shared/tokened_input.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import TokenedInput from '~/ide/components/shared/tokened_input.vue'; const TEST_PLACEHOLDER = 'Searching in test'; const TEST_TOKENS = [ diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index 7e77b859fdd..246b3995395 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -1,3 +1,4 @@ +import MockAdapter from 'axios-mock-adapter'; import actions, { stageAllChanges, unstageAllChanges, @@ -18,7 +19,6 @@ import * as types from '~/ide/stores/mutation_types'; import router from '~/ide/ide_router'; import { resetStore, file } from '../helpers'; import testAction from '../../helpers/vuex_action_helper'; -import MockAdapter from 'axios-mock-adapter'; import eventHub from '~/ide/eventhub'; const store = createStore(); diff --git a/spec/javascripts/ide/stores/modules/branches/actions_spec.js b/spec/javascripts/ide/stores/modules/branches/actions_spec.js index 9c61ba3d1a6..4896eac46f4 100644 --- a/spec/javascripts/ide/stores/modules/branches/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/branches/actions_spec.js @@ -1,8 +1,8 @@ import MockAdapter from 'axios-mock-adapter'; +import testAction from 'spec/helpers/vuex_action_helper'; import axios from '~/lib/utils/axios_utils'; import state from '~/ide/stores/modules/branches/state'; import * as types from '~/ide/stores/modules/branches/mutation_types'; -import testAction from 'spec/helpers/vuex_action_helper'; import { requestBranches, receiveBranchesError, diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js index d464f30b947..cbc2401262f 100644 --- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js @@ -1,3 +1,4 @@ +import { resetStore, file } from 'spec/ide/helpers'; import rootActions from '~/ide/stores/actions'; import { createStore } from '~/ide/stores'; import service from '~/ide/services'; @@ -7,7 +8,6 @@ import consts from '~/ide/stores/modules/commit/constants'; import * as mutationTypes from '~/ide/stores/modules/commit/mutation_types'; import * as actions from '~/ide/stores/modules/commit/actions'; import { commitActionTypes } from '~/ide/constants'; -import { resetStore, file } from 'spec/ide/helpers'; import testAction from '../../../../helpers/vuex_action_helper'; const TEST_COMMIT_SHA = '123456789'; diff --git a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js index 548962c7a92..f049e6e01e4 100644 --- a/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/file_templates/actions_spec.js @@ -1,9 +1,9 @@ import MockAdapter from 'axios-mock-adapter'; +import testAction from 'spec/helpers/vuex_action_helper'; import axios from '~/lib/utils/axios_utils'; import createState from '~/ide/stores/modules/file_templates/state'; import * as actions from '~/ide/stores/modules/file_templates/actions'; import * as types from '~/ide/stores/modules/file_templates/mutation_types'; -import testAction from 'spec/helpers/vuex_action_helper'; describe('IDE file templates actions', () => { let state; diff --git a/spec/javascripts/ide/stores/modules/pane/actions_spec.js b/spec/javascripts/ide/stores/modules/pane/actions_spec.js index 799bc89a0c3..9345a58746a 100644 --- a/spec/javascripts/ide/stores/modules/pane/actions_spec.js +++ b/spec/javascripts/ide/stores/modules/pane/actions_spec.js @@ -1,6 +1,6 @@ +import testAction from 'spec/helpers/vuex_action_helper'; import * as actions from '~/ide/stores/modules/pane/actions'; import * as types from '~/ide/stores/modules/pane/mutation_types'; -import testAction from 'spec/helpers/vuex_action_helper'; describe('IDE pane module actions', () => { const TEST_VIEW = { name: 'test' }; diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index 040ff1a8ebe..2d5bec2e752 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -5,10 +5,16 @@ require 'spec_helper' describe API::Helpers::Pagination do subject { Class.new.include(described_class).new } + let(:expected_result) { double("result", to_a: double) } + let(:relation) { double("relation") } + let(:params) { {} } + + before do + allow(subject).to receive(:params).and_return(params) + end + describe '#paginate' do - let(:relation) { double("relation") } let(:offset_pagination) { double("offset pagination") } - let(:expected_result) { double("result") } it 'delegates to OffsetPagination' do expect(::Gitlab::Pagination::OffsetPagination).to receive(:new).with(subject).and_return(offset_pagination) @@ -19,4 +25,50 @@ describe API::Helpers::Pagination do expect(result).to eq(expected_result) end end + + describe '#paginate_and_retrieve!' do + context 'for offset pagination' do + before do + allow(Gitlab::Pagination::Keyset).to receive(:available?).and_return(false) + end + + it 'delegates to paginate' do + expect(subject).to receive(:paginate).with(relation).and_return(expected_result) + + result = subject.paginate_and_retrieve!(relation) + + expect(result).to eq(expected_result.to_a) + end + end + + context 'for keyset pagination' do + let(:params) { { pagination: 'keyset' } } + let(:request_context) { double('request context') } + + before do + allow(Gitlab::Pagination::Keyset::RequestContext).to receive(:new).with(subject).and_return(request_context) + end + + context 'when keyset pagination is available' do + it 'delegates to KeysetPagination' do + expect(Gitlab::Pagination::Keyset).to receive(:available?).and_return(true) + expect(Gitlab::Pagination::Keyset).to receive(:paginate).with(request_context, relation).and_return(expected_result) + + result = subject.paginate_and_retrieve!(relation) + + expect(result).to eq(expected_result.to_a) + end + end + + context 'when keyset pagination is not available' do + it 'renders a 501 error if keyset pagination isnt available yet' do + expect(Gitlab::Pagination::Keyset).to receive(:available?).with(request_context, relation).and_return(false) + expect(Gitlab::Pagination::Keyset).not_to receive(:paginate) + expect(subject).to receive(:error!).with(/not yet available/, 405) + + subject.paginate_and_retrieve!(relation) + end + end + end + end end diff --git a/spec/lib/api/projects_batch_counting_spec.rb b/spec/lib/api/projects_batch_counting_spec.rb new file mode 100644 index 00000000000..6094952bb52 --- /dev/null +++ b/spec/lib/api/projects_batch_counting_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::ProjectsBatchCounting do + subject do + Class.new do + include ::API::ProjectsBatchCounting + end + end + + describe '.preload_and_batch_count!' do + let(:projects) { double } + let(:preloaded_projects) { double } + + it 'preloads the relation' do + allow(subject).to receive(:execute_batch_counting).with(preloaded_projects) + + expect(subject).to receive(:preload_relation).with(projects).and_return(preloaded_projects) + + expect(subject.preload_and_batch_count!(projects)).to eq(preloaded_projects) + end + + it 'executes batch counting' do + allow(subject).to receive(:preload_relation).with(projects).and_return(preloaded_projects) + + expect(subject).to receive(:execute_batch_counting).with(preloaded_projects) + + subject.preload_and_batch_count!(projects) + end + end + + describe '.execute_batch_counting' do + let(:projects) { create_list(:project, 2) } + let(:count_service) { double } + + it 'counts forks' do + allow(::Projects::BatchForksCountService).to receive(:new).with(projects).and_return(count_service) + + expect(count_service).to receive(:refresh_cache) + + subject.execute_batch_counting(projects) + end + + it 'counts open issues' do + allow(::Projects::BatchOpenIssuesCountService).to receive(:new).with(projects).and_return(count_service) + + expect(count_service).to receive(:refresh_cache) + + subject.execute_batch_counting(projects) + end + + context 'custom fork counting' do + subject do + Class.new do + include ::API::ProjectsBatchCounting + def self.forks_counting_projects(projects) + [projects.first] + end + end + end + + it 'counts forks for other projects' do + allow(::Projects::BatchForksCountService).to receive(:new).with([projects.first]).and_return(count_service) + + expect(count_service).to receive(:refresh_cache) + + subject.execute_batch_counting(projects) + end + end + end +end diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb index dd8070c1240..125039edcf8 100644 --- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb +++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb @@ -116,9 +116,9 @@ describe Gitlab::Auth::UserAuthFinders do end describe '#find_user_from_static_object_token' do - context 'when request format is archive' do + shared_examples 'static object request' do before do - env['SCRIPT_NAME'] = 'project/-/archive/master.zip' + env['SCRIPT_NAME'] = path end context 'when token header param is present' do @@ -126,7 +126,7 @@ describe Gitlab::Auth::UserAuthFinders do it 'returns the user' do request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token - expect(find_user_from_static_object_token(:archive)).to eq(user) + expect(find_user_from_static_object_token(format)).to eq(user) end end @@ -134,7 +134,7 @@ describe Gitlab::Auth::UserAuthFinders do it 'returns the user' do request.headers['X-Gitlab-Static-Object-Token'] = 'foobar' - expect { find_user_from_static_object_token(:archive) }.to raise_error(Gitlab::Auth::UnauthorizedError) + expect { find_user_from_static_object_token(format) }.to raise_error(Gitlab::Auth::UnauthorizedError) end end end @@ -144,7 +144,7 @@ describe Gitlab::Auth::UserAuthFinders do it 'returns the user' do set_param(:token, user.static_object_token) - expect(find_user_from_static_object_token(:archive)).to eq(user) + expect(find_user_from_static_object_token(format)).to eq(user) end end @@ -152,13 +152,27 @@ describe Gitlab::Auth::UserAuthFinders do it 'returns the user' do set_param(:token, 'foobar') - expect { find_user_from_static_object_token(:archive) }.to raise_error(Gitlab::Auth::UnauthorizedError) + expect { find_user_from_static_object_token(format) }.to raise_error(Gitlab::Auth::UnauthorizedError) end end end end - context 'when request format is not archive' do + context 'when request format is archive' do + it_behaves_like 'static object request' do + let_it_be(:path) { 'project/-/archive/master.zip' } + let_it_be(:format) { :archive } + end + end + + context 'when request format is blob' do + it_behaves_like 'static object request' do + let_it_be(:path) { 'project/raw/master/README.md' } + let_it_be(:format) { :blob } + end + end + + context 'when request format is not archive nor blob' do before do env['script_name'] = 'url' end diff --git a/spec/lib/gitlab/pagination/keyset/page_spec.rb b/spec/lib/gitlab/pagination/keyset/page_spec.rb new file mode 100644 index 00000000000..5c03224c05a --- /dev/null +++ b/spec/lib/gitlab/pagination/keyset/page_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Pagination::Keyset::Page do + describe '#per_page' do + it 'limits to a maximum of 100 records per page' do + per_page = described_class.new(per_page: 101).per_page + + expect(per_page).to eq(described_class::MAXIMUM_PAGE_SIZE) + end + + it 'uses default value when given 0' do + per_page = described_class.new(per_page: 0).per_page + + expect(per_page).to eq(described_class::DEFAULT_PAGE_SIZE) + end + + it 'uses default value when given negative values' do + per_page = described_class.new(per_page: -1).per_page + + expect(per_page).to eq(described_class::DEFAULT_PAGE_SIZE) + end + + it 'uses the given value if it is within range' do + per_page = described_class.new(per_page: 10).per_page + + expect(per_page).to eq(10) + end + end + + describe '#next' do + let(:page) { described_class.new(order_by: order_by, lower_bounds: lower_bounds, per_page: per_page, end_reached: end_reached) } + subject { page.next(new_lower_bounds, new_end_reached) } + + let(:order_by) { { id: :desc } } + let(:lower_bounds) { { id: 42 } } + let(:per_page) { 10 } + let(:end_reached) { false } + + let(:new_lower_bounds) { { id: 21 } } + let(:new_end_reached) { true } + + it 'copies over order_by' do + expect(subject.order_by).to eq(page.order_by) + end + + it 'copies over per_page' do + expect(subject.per_page).to eq(page.per_page) + end + + it 'dups the instance' do + expect(subject).not_to eq(page) + end + + it 'sets lower_bounds only on new instance' do + expect(subject.lower_bounds).to eq(new_lower_bounds) + expect(page.lower_bounds).to eq(lower_bounds) + end + + it 'sets end_reached only on new instance' do + expect(subject.end_reached?).to eq(new_end_reached) + expect(page.end_reached?).to eq(end_reached) + end + end +end diff --git a/spec/lib/gitlab/pagination/keyset/pager_spec.rb b/spec/lib/gitlab/pagination/keyset/pager_spec.rb new file mode 100644 index 00000000000..6d23fe2adcc --- /dev/null +++ b/spec/lib/gitlab/pagination/keyset/pager_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Pagination::Keyset::Pager do + let(:relation) { Project.all.order(id: :asc) } + let(:request) { double('request', page: page, apply_headers: nil) } + let(:page) { Gitlab::Pagination::Keyset::Page.new(order_by: { id: :asc }, per_page: 3) } + let(:next_page) { double('next page') } + + before_all do + create_list(:project, 7) + end + + describe '#paginate' do + subject { described_class.new(request).paginate(relation) } + + it 'loads the result relation only once' do + expect do + subject + end.not_to exceed_query_limit(1) + end + + it 'passes information about next page to request' do + lower_bounds = relation.limit(page.per_page).last.slice(:id) + expect(page).to receive(:next).with(lower_bounds, false).and_return(next_page) + expect(request).to receive(:apply_headers).with(next_page) + + subject + end + + context 'when retrieving the last page' do + let(:relation) { Project.where('id > ?', Project.maximum(:id) - page.per_page).order(id: :asc) } + + it 'indicates this is the last page' do + expect(request).to receive(:apply_headers) do |next_page| + expect(next_page.end_reached?).to be_truthy + end + + subject + end + end + + context 'when retrieving an empty page' do + let(:relation) { Project.where('id > ?', Project.maximum(:id) + 1).order(id: :asc) } + + it 'indicates this is the last page' do + expect(request).to receive(:apply_headers) do |next_page| + expect(next_page.end_reached?).to be_truthy + end + + subject + end + end + + it 'returns an array with the loaded records' do + expect(subject).to eq(relation.limit(page.per_page).to_a) + end + + context 'validating the order clause' do + let(:page) { Gitlab::Pagination::Keyset::Page.new(order_by: { created_at: :asc }, per_page: 3) } + + it 'raises an error if has a different order clause than the page' do + expect { subject }.to raise_error(ArgumentError, /order_by does not match/) + end + end + end +end diff --git a/spec/lib/gitlab/pagination/keyset/request_context_spec.rb b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb new file mode 100644 index 00000000000..344ef90efa3 --- /dev/null +++ b/spec/lib/gitlab/pagination/keyset/request_context_spec.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Pagination::Keyset::RequestContext do + let(:request) { double('request', params: params) } + + describe '#page' do + subject { described_class.new(request).page } + + context 'with only order_by given' do + let(:params) { { order_by: :id } } + + it 'extracts order_by/sorting information' do + page = subject + + expect(page.order_by).to eq(id: :desc) + end + end + + context 'with order_by and sort given' do + let(:params) { { order_by: :created_at, sort: :desc } } + + it 'extracts order_by/sorting information and adds tie breaker' do + page = subject + + expect(page.order_by).to eq(created_at: :desc, id: :desc) + end + end + + context 'with no order_by information given' do + let(:params) { {} } + + it 'defaults to tie breaker' do + page = subject + + expect(page.order_by).to eq({ id: :desc }) + end + end + + context 'with per_page params given' do + let(:params) { { per_page: 10 } } + + it 'extracts per_page information' do + page = subject + + expect(page.per_page).to eq(params[:per_page]) + end + end + end + + describe '#apply_headers' do + let(:request) { double('request', url: "http://#{Gitlab.config.gitlab.host}/api/v4/projects?foo=bar") } + let(:params) { { foo: 'bar' } } + let(:request_context) { double('request context', params: params, request: request) } + let(:next_page) { double('next page', order_by: { id: :asc }, lower_bounds: { id: 42 }, end_reached?: false) } + + subject { described_class.new(request_context).apply_headers(next_page) } + + it 'sets Links header with same host/path as the original request' do + orig_uri = URI.parse(request_context.request.url) + + expect(request_context).to receive(:header) do |name, header| + expect(name).to eq('Links') + + first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures + + uri = URI.parse(first_link) + + expect(uri.host).to eq(orig_uri.host) + expect(uri.path).to eq(orig_uri.path) + end + + subject + end + + it 'sets Links header with a link to the next page' do + orig_uri = URI.parse(request_context.request.url) + + expect(request_context).to receive(:header) do |name, header| + expect(name).to eq('Links') + + first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures + + query = CGI.parse(URI.parse(first_link).query) + + expect(query.except('id_after')).to eq(CGI.parse(orig_uri.query).except('id_after')) + expect(query['id_after']).to eq(['42']) + end + + subject + end + + context 'with descending order' do + let(:next_page) { double('next page', order_by: { id: :desc }, lower_bounds: { id: 42 }, end_reached?: false) } + + it 'sets Links header with a link to the next page' do + orig_uri = URI.parse(request_context.request.url) + + expect(request_context).to receive(:header) do |name, header| + expect(name).to eq('Links') + + first_link, _ = /<([^>]+)>; rel="next"/.match(header).captures + + query = CGI.parse(URI.parse(first_link).query) + + expect(query.except('id_before')).to eq(CGI.parse(orig_uri.query).except('id_before')) + expect(query['id_before']).to eq(['42']) + end + + subject + end + end + end +end diff --git a/spec/lib/gitlab/pagination/keyset_spec.rb b/spec/lib/gitlab/pagination/keyset_spec.rb new file mode 100644 index 00000000000..755c422c46a --- /dev/null +++ b/spec/lib/gitlab/pagination/keyset_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Pagination::Keyset do + describe '.paginate' do + subject { described_class.paginate(request_context, relation) } + + let(:request_context) { double } + let(:relation) { double } + let(:pager) { double } + let(:result) { double } + + it 'uses Pager to paginate the relation' do + expect(Gitlab::Pagination::Keyset::Pager).to receive(:new).with(request_context).and_return(pager) + expect(pager).to receive(:paginate).with(relation).and_return(result) + + expect(subject).to eq(result) + end + end + + describe '.available?' do + subject { described_class } + + let(:request_context) { double("request context", page: page)} + let(:page) { double("page", order_by: order_by) } + + shared_examples_for 'keyset pagination is available' do + it 'returns true for Project' do + expect(subject.available?(request_context, Project.all)).to be_truthy + end + + it 'return false for other types of relations' do + expect(subject.available?(request_context, User.all)).to be_falsey + end + end + + context 'with order-by id asc' do + let(:order_by) { { id: :asc } } + + it_behaves_like 'keyset pagination is available' + end + + context 'with order-by id desc' do + let(:order_by) { { id: :desc } } + + it_behaves_like 'keyset pagination is available' + end + + context 'with other order-by columns' do + let(:order_by) { { created_at: :desc, id: :desc } } + it 'returns false for Project' do + expect(subject.available?(request_context, Project.all)).to be_falsey + end + + it 'return false for other types of relations' do + expect(subject.available?(request_context, User.all)).to be_falsey + end + end + end +end diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb index 986a44cc640..6e78b747a8c 100644 --- a/spec/requests/api/deployments_spec.rb +++ b/spec/requests/api/deployments_spec.rb @@ -30,6 +30,16 @@ describe API::Deployments do expect(json_response.last['iid']).to eq(deployment_3.iid) end + context 'with updated_at filters specified' do + it 'returns projects deployments with last update in specified datetime range' do + get api("/projects/#{project.id}/deployments", user), params: { updated_before: 30.minutes.ago, updated_after: 90.minutes.ago } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.first['id']).to eq(deployment_3.id) + end + end + describe 'ordering' do let(:order_by) { 'iid' } let(:sort) { 'desc' } diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 2b34b64812d..a9d570b5696 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -237,6 +237,20 @@ describe API::Pipelines do end end + context 'when updated_at filters are specified' do + let!(:pipeline1) { create(:ci_pipeline, project: project, updated_at: 2.days.ago) } + let!(:pipeline2) { create(:ci_pipeline, project: project, updated_at: 4.days.ago) } + let!(:pipeline3) { create(:ci_pipeline, project: project, updated_at: 1.hour.ago) } + + it 'returns pipelines with last update date in specified datetime range' do + get api("/projects/#{project.id}/pipelines", user), params: { updated_before: 1.day.ago, updated_after: 3.days.ago } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response.first['id']).to eq(pipeline1.id) + end + end + context 'when order_by and sort are specified' do context 'when order_by user_id' do before do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cda2dd7d2f4..5161de6de5e 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -155,6 +155,35 @@ describe API::Projects do project4 end + # This is a regression spec for https://gitlab.com/gitlab-org/gitlab/issues/37919 + context 'batch counting forks and open issues and refreshing count caches' do + # We expect to count these projects (only the ones on the first page, not all matching ones) + let(:projects) { Project.public_to_user(nil).order(id: :desc).first(per_page) } + let(:per_page) { 2 } + let(:count_service) { double } + + before do + # Create more projects, so we have more than one page + create_list(:project, 5, :public) + end + + it 'batch counts project forks' do + expect(::Projects::BatchForksCountService).to receive(:new).with(projects).and_return(count_service) + expect(count_service).to receive(:refresh_cache) + + get api("/projects?per_page=#{per_page}") + expect(response.status).to eq 200 + end + + it 'batch counts open issues' do + expect(::Projects::BatchOpenIssuesCountService).to receive(:new).with(projects).and_return(count_service) + expect(count_service).to receive(:refresh_cache) + + get api("/projects?per_page=#{per_page}") + expect(response.status).to eq 200 + end + end + context 'when unauthenticated' do it_behaves_like 'projects response' do let(:filter) { { search: project.name } } @@ -570,6 +599,87 @@ describe API::Projects do let(:projects) { Project.all } end end + + context 'with keyset pagination' do + let(:current_user) { user } + let(:projects) { [public_project, project, project2, project3] } + + context 'headers and records' do + let(:params) { { pagination: 'keyset', order_by: :id, sort: :asc, per_page: 1 } } + + it 'includes a pagination header with link to the next page' do + get api('/projects', current_user), params: params + + expect(response.header).to include('Links') + expect(response.header['Links']).to include('pagination=keyset') + expect(response.header['Links']).to include("id_after=#{public_project.id}") + end + + it 'contains only the first project with per_page = 1' do + get api('/projects', current_user), params: params + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |p| p['id'] }).to contain_exactly(public_project.id) + end + + it 'does not include a link if the end has reached and there is no more data' do + get api('/projects', current_user), params: params.merge(id_after: project2.id) + + expect(response.header).not_to include('Links') + end + + it 'responds with 501 if order_by is different from id' do + get api('/projects', current_user), params: params.merge(order_by: :created_at) + + expect(response).to have_gitlab_http_status(405) + end + end + + context 'with descending sorting' do + let(:params) { { pagination: 'keyset', order_by: :id, sort: :desc, per_page: 1 } } + + it 'includes a pagination header with link to the next page' do + get api('/projects', current_user), params: params + + expect(response.header).to include('Links') + expect(response.header['Links']).to include('pagination=keyset') + expect(response.header['Links']).to include("id_before=#{project3.id}") + end + + it 'contains only the last project with per_page = 1' do + get api('/projects', current_user), params: params + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to be_an Array + expect(json_response.map { |p| p['id'] }).to contain_exactly(project3.id) + end + end + + context 'retrieving the full relation' do + let(:params) { { pagination: 'keyset', order_by: :id, sort: :desc, per_page: 2 } } + + it 'returns all projects' do + url = '/projects' + requests = 0 + ids = [] + + while url && requests <= 5 # circuit breaker + requests += 1 + get api(url, current_user), params: params + + links = response.header['Links'] + url = links&.match(/<[^>]+(\/projects\?[^>]+)>; rel="next"/) do |match| + match[1] + end + + ids += JSON.parse(response.body).map { |p| p['id'] } + end + + expect(ids).to contain_exactly(*projects.map(&:id)) + end + end + end end describe 'POST /projects' do diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index 3cab3158cce..4b949761b8f 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -91,6 +91,25 @@ describe Ci::RetryPipelineService, '#execute' do end end + context 'when there is a failed test in a DAG' do + before do + create_build('build', :success, 0) + create_build('build2', :success, 0) + test_build = create_build('test', :failed, 1) + create(:ci_build_need, build: test_build, name: 'build') + create(:ci_build_need, build: test_build, name: 'build2') + end + + it 'retries the test' do + service.execute(pipeline) + + expect(build('build')).to be_success + expect(build('build2')).to be_success + expect(build('test')).to be_pending + expect(build('test').needs.map(&:name)).to match_array(%w(build build2)) + end + end + context 'when the last stage was skipepd' do before do create_build('build 1', :success, 0) |