summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-06 18:07:44 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-06 18:07:44 +0000
commite1867c38fc5a4b931b4b2256d4909182e94f1051 (patch)
tree3047b637f7f9a31e74c62d3fe054b24c95e3534e /spec
parent63894d59abd34f76f399d755012cdcd32c5b1103 (diff)
downloadgitlab-ce-e1867c38fc5a4b931b4b2256d4909182e94f1051.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb81
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb46
-rw-r--r--spec/finders/pipelines_finder_spec.rb74
-rw-r--r--spec/frontend/error_tracking_settings/components/error_tracking_form_spec.js4
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/actions_spec.js4
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/form_spec.js6
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/list_collapsed_spec.js2
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/list_item_spec.js4
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/list_spec.js2
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/message_field_spec.js2
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js4
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js4
-rw-r--r--spec/javascripts/ide/components/file_row_extra_spec.js2
-rw-r--r--spec/javascripts/ide/components/file_templates/bar_spec.js2
-rw-r--r--spec/javascripts/ide/components/ide_side_bar_spec.js2
-rw-r--r--spec/javascripts/ide/components/ide_spec.js2
-rw-r--r--spec/javascripts/ide/components/ide_status_bar_spec.js2
-rw-r--r--spec/javascripts/ide/components/nav_dropdown_button_spec.js4
-rw-r--r--spec/javascripts/ide/components/nav_dropdown_spec.js2
-rw-r--r--spec/javascripts/ide/components/new_dropdown/index_spec.js2
-rw-r--r--spec/javascripts/ide/components/new_dropdown/modal_spec.js2
-rw-r--r--spec/javascripts/ide/components/new_dropdown/upload_spec.js2
-rw-r--r--spec/javascripts/ide/components/preview/navigator_spec.js2
-rw-r--r--spec/javascripts/ide/components/repo_commit_section_spec.js2
-rw-r--r--spec/javascripts/ide/components/shared/tokened_input_spec.js2
-rw-r--r--spec/javascripts/ide/stores/actions_spec.js2
-rw-r--r--spec/javascripts/ide/stores/modules/branches/actions_spec.js2
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js2
-rw-r--r--spec/javascripts/ide/stores/modules/file_templates/actions_spec.js2
-rw-r--r--spec/javascripts/ide/stores/modules/pane/actions_spec.js2
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb56
-rw-r--r--spec/lib/api/projects_batch_counting_spec.rb72
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb28
-rw-r--r--spec/lib/gitlab/pagination/keyset/page_spec.rb66
-rw-r--r--spec/lib/gitlab/pagination/keyset/pager_spec.rb68
-rw-r--r--spec/lib/gitlab/pagination/keyset/request_context_spec.rb115
-rw-r--r--spec/lib/gitlab/pagination/keyset_spec.rb61
-rw-r--r--spec/requests/api/deployments_spec.rb10
-rw-r--r--spec/requests/api/pipelines_spec.rb14
-rw-r--r--spec/requests/api/projects_spec.rb110
-rw-r--r--spec/services/ci/retry_pipeline_service_spec.rb19
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)