diff options
Diffstat (limited to 'spec')
31 files changed, 569 insertions, 208 deletions
diff --git a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb index d20471ef603..3c9452cc42a 100644 --- a/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb +++ b/spec/controllers/concerns/controller_with_cross_project_access_check_spec.rb @@ -27,11 +27,11 @@ describe ControllerWithCrossProjectAccessCheck do if: -> { if_condition } def index - render nothing: true + head :ok end def show - render nothing: true + head :ok end def unless_condition @@ -88,15 +88,15 @@ describe ControllerWithCrossProjectAccessCheck do if: -> { if_condition } def index - render nothing: true + head :ok end def show - render nothing: true + head :ok end def edit - render nothing: true + head :ok end def unless_condition diff --git a/spec/controllers/concerns/lfs_request_spec.rb b/spec/controllers/concerns/lfs_request_spec.rb index 33b23db302a..76c878ec5d7 100644 --- a/spec/controllers/concerns/lfs_request_spec.rb +++ b/spec/controllers/concerns/lfs_request_spec.rb @@ -10,7 +10,7 @@ describe LfsRequest do def show storage_project - render nothing: true + head :ok end def project diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb index ea26bc83353..685db8488f0 100644 --- a/spec/controllers/profiles/keys_controller_spec.rb +++ b/spec/controllers/profiles/keys_controller_spec.rb @@ -62,8 +62,15 @@ describe Profiles::KeysController do it "responds with text/plain content type" do get :get_keys, username: user.username + expect(response.content_type).to eq("text/plain") end + + it "responds with attachment content disposition" do + get :get_keys, username: user.username + + expect(response.headers['Content-Disposition']).to eq('attachment') + end end end end diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index ff65c76cf26..7fc3d16e864 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -49,6 +49,11 @@ FactoryBot.define do cluster factory: %i(cluster with_installed_helm provided_by_gcp) end + factory :clusters_applications_cert_managers, class: Clusters::Applications::CertManager do + email 'admin@example.com' + cluster factory: %i(cluster with_installed_helm provided_by_gcp) + end + factory :clusters_applications_prometheus, class: Clusters::Applications::Prometheus do cluster factory: %i(cluster with_installed_helm provided_by_gcp) end diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 282bf542e77..9ffa75aee47 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -6,6 +6,7 @@ describe 'Dashboard Merge Requests' do include ProjectForksHelper let(:current_user) { create :user } + let(:user) { current_user } let(:project) { create(:project) } let(:public_project) { create(:project, :public, :repository) } diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb index 0d04ed612c2..c29dfb01381 100644 --- a/spec/features/help_pages_spec.rb +++ b/spec/features/help_pages_spec.rb @@ -4,7 +4,7 @@ describe 'Help Pages' do describe 'Get the main help page' do shared_examples_for 'help page' do |prefix: ''| it 'prefixes links correctly' do - expect(page).to have_selector(%(div.documentation-index > ul a[href="#{prefix}/help/api/README.md"])) + expect(page).to have_selector(%(div.documentation-index > table tbody tr td a[href="#{prefix}/help/api/README.md"])) end end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 4d9b8a10e04..5c1ffb76351 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -8,6 +8,17 @@ describe 'Issues' do let(:user) { create(:user) } let(:project) { create(:project, :public) } + shared_examples_for 'empty state with filters' do + it 'user sees empty state with filters' do + create(:issue, author: user, project: project) + + visit project_issues_path(project, milestone_title: "1.0") + + expect(page).to have_content('Sorry, your filter produced no results') + expect(page).to have_content('To widen your search, change or remove filters above') + end + end + describe 'while user is signed out' do describe 'empty state' do it 'user sees empty state' do @@ -17,6 +28,8 @@ describe 'Issues' do expect(page).to have_content('The Issue Tracker is the place to add things that need to be improved or solved in a project.') expect(page).to have_content('You can register or sign in to create issues for this project.') end + + it_behaves_like 'empty state with filters' end end @@ -37,6 +50,8 @@ describe 'Issues' do expect(page).to have_content('Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable.') expect(page).to have_content('New issue') end + + it_behaves_like 'empty state with filters' end describe 'Edit issue' do diff --git a/spec/features/merge_request/user_sees_empty_state_spec.rb b/spec/features/merge_request/user_sees_empty_state_spec.rb index 482f31b02d4..012bfd6e458 100644 --- a/spec/features/merge_request/user_sees_empty_state_spec.rb +++ b/spec/features/merge_request/user_sees_empty_state_spec.rb @@ -19,12 +19,20 @@ describe 'Merge request > User sees empty state' do context 'if there are merge requests' do before do create(:merge_request, source_project: project) - - visit project_merge_requests_path(project) end it 'does not show an empty state' do + visit project_merge_requests_path(project) + expect(page).not_to have_selector('.empty-state') end + + it 'shows empty state when filter results empty' do + visit project_merge_requests_path(project, milestone_title: "1.0") + + expect(page).to have_selector('.empty-state') + expect(page).to have_content('Sorry, your filter produced no results') + expect(page).to have_content('To widen your search, change or remove filters above') + end end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index c0488c83bd8..515f6f70b99 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -256,19 +256,51 @@ describe IssuesFinder do create(:label_link, label: label2, target: issue2) end - it 'returns the unique issues with any of those labels' do + it 'returns the unique issues with all those labels' do + expect(issues).to contain_exactly(issue2) + end + end + + context 'filtering by a label that includes any or none in the title' do + let(:params) { { label_name: [label.title, label2.title].join(',') } } + let(:label) { create(:label, title: 'any foo', project: project2) } + let(:label2) { create(:label, title: 'bar none', project: project2) } + + it 'returns the unique issues with all those labels' do + create(:label_link, label: label2, target: issue2) + expect(issues).to contain_exactly(issue2) end end context 'filtering by no label' do - let(:params) { { label_name: Label::None.title } } + let(:params) { { label_name: described_class::FILTER_NONE } } it 'returns issues with no labels' do expect(issues).to contain_exactly(issue1, issue3, issue4) end end + context 'filtering by legacy No+Label' do + let(:params) { { label_name: Label::NONE } } + + it 'returns issues with no labels' do + expect(issues).to contain_exactly(issue1, issue3, issue4) + end + end + + context 'filtering by any label' do + let(:params) { { label_name: described_class::FILTER_ANY } } + + it 'returns issues that have one or more label' do + 2.times do + create(:label_link, label: create(:label, project: project2), target: issue3) + end + + expect(issues).to contain_exactly(issue2, issue3) + end + end + context 'filtering by issue term' do let(:params) { { search: 'git' } } diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js index 0e2cc13fa52..928bf70f3a2 100644 --- a/spec/javascripts/clusters/components/applications_spec.js +++ b/spec/javascripts/clusters/components/applications_spec.js @@ -20,6 +20,7 @@ describe('Applications', () => { applications: { helm: { title: 'Helm Tiller' }, ingress: { title: 'Ingress' }, + cert_manager: { title: 'Cert-Manager' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, jupyter: { title: 'JupyterHub' }, @@ -36,6 +37,10 @@ describe('Applications', () => { expect(vm.$el.querySelector('.js-cluster-application-row-ingress')).toBeDefined(); }); + it('renders a row for Cert-Manager', () => { + expect(vm.$el.querySelector('.js-cluster-application-row-cert_manager')).toBeDefined(); + }); + it('renders a row for Prometheus', () => { expect(vm.$el.querySelector('.js-cluster-application-row-prometheus')).toBeDefined(); }); @@ -65,6 +70,7 @@ describe('Applications', () => { externalIp: '0.0.0.0', }, helm: { title: 'Helm Tiller' }, + cert_manager: { title: 'Cert-Manager' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, jupyter: { title: 'JupyterHub', hostname: '' }, @@ -89,6 +95,7 @@ describe('Applications', () => { status: 'installed', }, helm: { title: 'Helm Tiller' }, + cert_manager: { title: 'Cert-Manager' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, jupyter: { title: 'JupyterHub', hostname: '' }, @@ -109,6 +116,7 @@ describe('Applications', () => { applications: { helm: { title: 'Helm Tiller' }, ingress: { title: 'Ingress' }, + cert_manager: { title: 'Cert-Manager' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, jupyter: { title: 'JupyterHub', hostname: '' }, @@ -128,6 +136,7 @@ describe('Applications', () => { applications: { helm: { title: 'Helm Tiller', status: 'installed' }, ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + cert_manager: { title: 'Cert-Manager' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' }, @@ -145,6 +154,7 @@ describe('Applications', () => { applications: { helm: { title: 'Helm Tiller', status: 'installed' }, ingress: { title: 'Ingress', status: 'installed' }, + cert_manager: { title: 'Cert-Manager' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, jupyter: { title: 'JupyterHub', hostname: '', status: 'installable' }, @@ -162,6 +172,7 @@ describe('Applications', () => { applications: { helm: { title: 'Helm Tiller', status: 'installed' }, ingress: { title: 'Ingress', status: 'installed', externalIp: '1.1.1.1' }, + cert_manager: { title: 'Cert-Manager' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, jupyter: { title: 'JupyterHub', status: 'installed', hostname: '' }, @@ -179,6 +190,7 @@ describe('Applications', () => { applications: { helm: { title: 'Helm Tiller' }, ingress: { title: 'Ingress' }, + cert_manager: { title: 'Cert-Manager' }, runner: { title: 'GitLab Runner' }, prometheus: { title: 'Prometheus' }, jupyter: { title: 'JupyterHub', status: 'not_installable' }, diff --git a/spec/javascripts/clusters/services/mock_data.js b/spec/javascripts/clusters/services/mock_data.js index 73abf6504c0..540d7f30858 100644 --- a/spec/javascripts/clusters/services/mock_data.js +++ b/spec/javascripts/clusters/services/mock_data.js @@ -38,6 +38,11 @@ const CLUSTERS_MOCK_DATA = { status: APPLICATION_STATUS.INSTALLING, status_reason: 'Cannot connect', }, + { + name: 'cert_manager', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + }, ], }, }, @@ -77,6 +82,11 @@ const CLUSTERS_MOCK_DATA = { status: APPLICATION_STATUS.INSTALLABLE, status_reason: 'Cannot connect', }, + { + name: 'cert_manager', + status: APPLICATION_STATUS.ERROR, + status_reason: 'Cannot connect', + }, ], }, }, @@ -84,6 +94,7 @@ const CLUSTERS_MOCK_DATA = { POST: { '/gitlab-org/gitlab-shell/clusters/1/applications/helm': {}, '/gitlab-org/gitlab-shell/clusters/1/applications/ingress': {}, + '/gitlab-org/gitlab-shell/clusters/1/applications/cert_manager': {}, '/gitlab-org/gitlab-shell/clusters/1/applications/runner': {}, '/gitlab-org/gitlab-shell/clusters/1/applications/prometheus': {}, '/gitlab-org/gitlab-shell/clusters/1/applications/jupyter': {}, diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js index 34ed36afa5b..6a08d08f33e 100644 --- a/spec/javascripts/clusters/stores/clusters_store_spec.js +++ b/spec/javascripts/clusters/stores/clusters_store_spec.js @@ -108,6 +108,13 @@ describe('Clusters Store', () => { requestReason: null, hostname: null, }, + cert_manager: { + title: 'Cert-Manager', + status: mockResponseData.applications[6].status, + statusReason: mockResponseData.applications[6].status_reason, + requestStatus: null, + requestReason: null, + }, }, }); }); diff --git a/spec/javascripts/helpers/scroll_into_view_promise.js b/spec/javascripts/helpers/scroll_into_view_promise.js new file mode 100644 index 00000000000..0edea2103da --- /dev/null +++ b/spec/javascripts/helpers/scroll_into_view_promise.js @@ -0,0 +1,28 @@ +export default function scrollIntoViewPromise(intersectionTarget, timeout = 100, maxTries = 5) { + return new Promise((resolve, reject) => { + let intersectionObserver; + let retry = 0; + + const intervalId = setInterval(() => { + if (retry >= maxTries) { + intersectionObserver.disconnect(); + clearInterval(intervalId); + reject(new Error(`Could not scroll target into viewPort within ${timeout * maxTries} ms`)); + } + retry += 1; + intersectionTarget.scrollIntoView(); + }, timeout); + + intersectionObserver = new IntersectionObserver(entries => { + if (entries[0].isIntersecting) { + intersectionObserver.disconnect(); + clearInterval(intervalId); + resolve(); + } + }); + + intersectionObserver.observe(intersectionTarget); + + intersectionTarget.scrollIntoView(); + }); +} diff --git a/spec/javascripts/helpers/wait_for_attribute_change.js b/spec/javascripts/helpers/wait_for_attribute_change.js new file mode 100644 index 00000000000..8f22d569222 --- /dev/null +++ b/spec/javascripts/helpers/wait_for_attribute_change.js @@ -0,0 +1,16 @@ +export default (domElement, attributes, timeout = 1500) => + new Promise((resolve, reject) => { + let observer; + const timeoutId = setTimeout(() => { + observer.disconnect(); + reject(new Error(`Could not see an attribute update within ${timeout} ms`)); + }, timeout); + + observer = new MutationObserver(() => { + clearTimeout(timeoutId); + observer.disconnect(); + resolve(); + }); + + observer.observe(domElement, { attributes: true, attributeFilter: attributes }); + }); diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js index eac4756e8a9..cbdc1644430 100644 --- a/spec/javascripts/lazy_loader_spec.js +++ b/spec/javascripts/lazy_loader_spec.js @@ -1,16 +1,19 @@ import LazyLoader from '~/lazy_loader'; import { TEST_HOST } from './test_constants'; - -let lazyLoader = null; +import scrollIntoViewPromise from './helpers/scroll_into_view_promise'; +import waitForPromises from './helpers/wait_for_promises'; +import waitForAttributeChange from './helpers/wait_for_attribute_change'; const execImmediately = callback => { callback(); }; describe('LazyLoader', function() { + let lazyLoader = null; + preloadFixtures('issues/issue_with_comment.html.raw'); - describe('with IntersectionObserver disabled', () => { + describe('without IntersectionObserver', () => { beforeEach(function() { loadFixtures('issues/issue_with_comment.html.raw'); @@ -36,14 +39,15 @@ describe('LazyLoader', function() { it('should copy value from data-src to src for img 1', function(done) { const img = document.querySelectorAll('img[data-src]')[0]; const originalDataSrc = img.getAttribute('data-src'); - img.scrollIntoView(); - - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(img.getAttribute('src')).toBe(originalDataSrc); - expect(img).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + + Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(img.getAttribute('src')).toBe(originalDataSrc); + expect(img).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should lazy load dynamically added data-src images', function(done) { @@ -52,14 +56,18 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(newImg.getAttribute('src')).toBe(testPath); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + + Promise.all([ + scrollIntoViewPromise(newImg), + waitForAttributeChange(newImg, ['data-src', 'src']), + ]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(newImg.getAttribute('src')).toBe(testPath); + expect(newImg).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should not alter normal images', function(done) { @@ -67,13 +75,15 @@ describe('LazyLoader', function() { const testPath = `${TEST_HOST}/img/testimg.png`; newImg.setAttribute('src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }, 50); + scrollIntoViewPromise(newImg) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(newImg).not.toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should not load dynamically added pictures if content observer is turned off', done => { @@ -84,13 +94,15 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }, 50); + scrollIntoViewPromise(newImg) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(newImg).not.toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should load dynamically added pictures if content observer is turned off and on again', done => { @@ -102,17 +114,22 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + Promise.all([ + scrollIntoViewPromise(newImg), + waitForAttributeChange(newImg, ['data-src', 'src']), + ]) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(newImg).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); }); - describe('with IntersectionObserver enabled', () => { + describe('with IntersectionObserver', () => { beforeEach(function() { loadFixtures('issues/issue_with_comment.html.raw'); @@ -136,14 +153,15 @@ describe('LazyLoader', function() { it('should copy value from data-src to src for img 1', function(done) { const img = document.querySelectorAll('img[data-src]')[0]; const originalDataSrc = img.getAttribute('data-src'); - img.scrollIntoView(); - - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(img.getAttribute('src')).toBe(originalDataSrc); - expect(img).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + + Promise.all([scrollIntoViewPromise(img), waitForAttributeChange(img, ['data-src', 'src'])]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(img.getAttribute('src')).toBe(originalDataSrc); + expect(img).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should lazy load dynamically added data-src images', function(done) { @@ -152,14 +170,18 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(newImg.getAttribute('src')).toBe(testPath); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + + Promise.all([ + scrollIntoViewPromise(newImg), + waitForAttributeChange(newImg, ['data-src', 'src']), + ]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(newImg.getAttribute('src')).toBe(testPath); + expect(newImg).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should not alter normal images', function(done) { @@ -167,13 +189,15 @@ describe('LazyLoader', function() { const testPath = `${TEST_HOST}/img/testimg.png`; newImg.setAttribute('src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }, 50); + scrollIntoViewPromise(newImg) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(newImg).not.toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should not load dynamically added pictures if content observer is turned off', done => { @@ -184,13 +208,15 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).not.toHaveBeenCalled(); - expect(newImg).not.toHaveClass('js-lazy-loaded'); - done(); - }, 50); + scrollIntoViewPromise(newImg) + .then(waitForPromises) + .then(() => { + expect(LazyLoader.loadImage).not.toHaveBeenCalled(); + expect(newImg).not.toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); it('should load dynamically added pictures if content observer is turned off and on again', done => { @@ -202,13 +228,17 @@ describe('LazyLoader', function() { newImg.className = 'lazy'; newImg.setAttribute('data-src', testPath); document.body.appendChild(newImg); - newImg.scrollIntoView(); - setTimeout(() => { - expect(LazyLoader.loadImage).toHaveBeenCalled(); - expect(newImg).toHaveClass('js-lazy-loaded'); - done(); - }, 50); + Promise.all([ + scrollIntoViewPromise(newImg), + waitForAttributeChange(newImg, ['data-src', 'src']), + ]) + .then(() => { + expect(LazyLoader.loadImage).toHaveBeenCalled(); + expect(newImg).toHaveClass('js-lazy-loaded'); + done(); + }) + .catch(done.fail); }); }); }); diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js index 81cb3e1f74d..4b4403689d9 100644 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ b/spec/javascripts/notes/components/noteable_discussion_spec.js @@ -6,6 +6,7 @@ import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data'; import mockDiffFile from '../../diffs/mock_data/diff_file'; const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json'; +const diffDiscussionFixture = 'merge_requests/diff_discussion.json'; describe('noteable_discussion component', () => { const Component = Vue.extend(noteableDiscussion); @@ -115,6 +116,49 @@ describe('noteable_discussion component', () => { .catch(done.fail); }); }); + + describe('isRepliesCollapsed', () => { + it('should return false for diff discussions', done => { + const diffDiscussion = getJSONFixture(diffDiscussionFixture)[0]; + vm.$store.dispatch('setInitialNotes', [diffDiscussion]); + + Vue.nextTick() + .then(() => { + expect(vm.isRepliesCollapsed).toEqual(false); + expect(vm.$el.querySelector('.js-toggle-replies')).not.toBeNull(); + expect(vm.$el.querySelector('.discussion-reply-holder')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); + }); + + it('should return false if discussion does not have a reply', () => { + const discussion = { ...discussionMock, resolved: true }; + discussion.notes = discussion.notes.slice(0, 1); + const noRepliesVm = new Component({ + store, + propsData: { discussion }, + }).$mount(); + + expect(noRepliesVm.isRepliesCollapsed).toEqual(false); + expect(noRepliesVm.$el.querySelector('.js-toggle-replies')).toBeNull(); + expect(vm.$el.querySelector('.discussion-reply-holder')).not.toBeNull(); + noRepliesVm.$destroy(); + }); + + it('should return true for resolved non-diff discussion which has replies', () => { + const discussion = { ...discussionMock, resolved: true }; + const resolvedDiscussionVm = new Component({ + store, + propsData: { discussion }, + }).$mount(); + + expect(resolvedDiscussionVm.isRepliesCollapsed).toEqual(true); + expect(resolvedDiscussionVm.$el.querySelector('.js-toggle-replies')).not.toBeNull(); + expect(vm.$el.querySelector('.discussion-reply-holder')).not.toBeNull(); + resolvedDiscussionVm.$destroy(); + }); + }); }); describe('methods', () => { diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb index e5999a1c509..be9b2588c90 100644 --- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb +++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do let!(:project) { create(:project, :repository) } let(:pipeline_status) { described_class.new(project) } - let(:cache_key) { described_class.cache_key_for_project(project) } + let(:cache_key) { pipeline_status.cache_key } describe '.load_for_project' do it "loads the status" do @@ -14,94 +14,24 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do end describe 'loading in batches' do - let(:status) { 'success' } - let(:sha) { '424d1b73bc0d3cb726eb7dc4ce17a4d48552f8c6' } - let(:ref) { 'master' } - let(:pipeline_info) { { sha: sha, status: status, ref: ref } } - let!(:project_without_status) { create(:project, :repository) } - describe '.load_in_batch_for_projects' do - it 'preloads pipeline_status on projects' do + it 'loads pipeline_status on projects' do described_class.load_in_batch_for_projects([project]) # Don't call the accessor that would lazy load the variable - expect(project.instance_variable_get('@pipeline_status')).to be_a(described_class) - end - - describe 'without a status in redis_cache' do - it 'loads the status from a commit when it was not in redis_cache' do - empty_status = { sha: nil, status: nil, ref: nil } - fake_pipeline = described_class.new( - project_without_status, - pipeline_info: empty_status, - loaded_from_cache: false - ) - - expect(described_class).to receive(:new) - .with(project_without_status, - pipeline_info: empty_status, - loaded_from_cache: false) - .and_return(fake_pipeline) - expect(fake_pipeline).to receive(:load_from_project) - expect(fake_pipeline).to receive(:store_in_cache) - - described_class.load_in_batch_for_projects([project_without_status]) - end - - it 'only connects to redis twice' do - expect(Gitlab::Redis::Cache).to receive(:with).exactly(2).and_call_original - - described_class.load_in_batch_for_projects([project_without_status]) - - expect(project_without_status.pipeline_status).not_to be_nil - end - end - - describe 'when a status was cached in redis_cache' do - before do - Gitlab::Redis::Cache.with do |redis| - redis.mapped_hmset(cache_key, - { sha: sha, status: status, ref: ref }) - end - end - - it 'loads the correct status' do - described_class.load_in_batch_for_projects([project]) - - pipeline_status = project.instance_variable_get('@pipeline_status') - - expect(pipeline_status.sha).to eq(sha) - expect(pipeline_status.status).to eq(status) - expect(pipeline_status.ref).to eq(ref) - end - - it 'only connects to redis_cache once' do - expect(Gitlab::Redis::Cache).to receive(:with).exactly(1).and_call_original + project_pipeline_status = project.instance_variable_get('@pipeline_status') - described_class.load_in_batch_for_projects([project]) - - expect(project.pipeline_status).not_to be_nil - end - - it "doesn't load the status separatly" do - expect_any_instance_of(described_class).not_to receive(:load_from_project) - expect_any_instance_of(described_class).not_to receive(:load_from_cache) - - described_class.load_in_batch_for_projects([project]) - end + expect(project_pipeline_status).to be_a(described_class) + expect(project_pipeline_status).to be_loaded end - end - describe '.cached_results_for_projects' do - it 'loads a status from caching for all projects' do - Gitlab::Redis::Cache.with do |redis| - redis.mapped_hmset(cache_key, { sha: sha, status: status, ref: ref }) + it 'loads 10 projects without hitting Gitaly call limit', :request_store do + projects = Gitlab::GitalyClient.allow_n_plus_1_calls do + (1..10).map { create(:project, :repository) } end + Gitlab::GitalyClient.reset_counts - result = [{ loaded_from_cache: false, pipeline_info: { sha: nil, status: nil, ref: nil } }, - { loaded_from_cache: true, pipeline_info: pipeline_info }] - - expect(described_class.cached_results_for_projects([project_without_status, project])).to eq(result) + expect { described_class.load_in_batch_for_projects(projects) }.not_to raise_error end end end @@ -198,7 +128,9 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do status_for_empty_commit.load_status - expect(status_for_empty_commit).to be_loaded + expect(status_for_empty_commit.sha).to be_nil + expect(status_for_empty_commit.status).to be_nil + expect(status_for_empty_commit.ref).to be_nil end end diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index e1e0582cd11..8bf44acb228 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -36,7 +36,7 @@ describe Gitlab::Ci::Variables::Collection::Item do shared_examples 'raises error for invalid type' do it do expect { described_class.new(key: variable_key, value: variable_value) } - .to raise_error ArgumentError, /`#{variable_key}` must be of type String, while it was:/ + .to raise_error ArgumentError, /`#{variable_key}` must be of type String or nil value, while it was:/ end end @@ -46,7 +46,7 @@ describe Gitlab::Ci::Variables::Collection::Item do let(:variable_value) { nil } let(:expected_value) { nil } - it_behaves_like 'raises error for invalid type' + it_behaves_like 'creates variable' end context "when it's an empty string" do diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index 39852b7fe29..82ed4d47857 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -43,6 +43,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 + --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml EOS @@ -101,6 +102,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 + --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml EOS @@ -126,7 +128,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do <<~EOS.strip /bin/date /bin/true - helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml + helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml EOS end end @@ -148,7 +150,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do let(:helm_install_command) do <<~EOS.strip - helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml + helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml /bin/date /bin/false EOS @@ -175,6 +177,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do helm install chart-name --name app-name --version 1.2.3 + --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml EOS @@ -204,6 +207,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem + --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml EOS diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb new file mode 100644 index 00000000000..78974cadb69 --- /dev/null +++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Prometheus::QueryVariables do + describe '.call' do + set(:environment) { create(:environment) } + let(:slug) { environment.slug } + + subject { described_class.call(environment) } + + it { is_expected.to include(ci_environment_slug: slug) } + + it do + is_expected.to include(environment_filter: + %{container_name!="POD",environment="#{slug}"}) + end + + context 'without deployment platform' do + it { is_expected.to include(kube_namespace: '') } + end + + context 'with deplyoment platform' do + let(:kube_namespace) { environment.deployment_platform.actual_namespace } + + before do + create(:cluster, :provided_by_user, projects: [environment.project]) + end + + it { is_expected.to include(kube_namespace: kube_namespace) } + end + end +end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index b212d2b05f2..5390f237073 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -19,6 +19,7 @@ describe Gitlab::UsageData do create(:cluster, :provided_by_user, :disabled) create(:clusters_applications_helm, :installed, cluster: gcp_cluster) create(:clusters_applications_ingress, :installed, cluster: gcp_cluster) + create(:clusters_applications_cert_managers, :installed, cluster: gcp_cluster) create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster) create(:clusters_applications_runner, :installed, cluster: gcp_cluster) create(:clusters_applications_knative, :installed, cluster: gcp_cluster) @@ -81,6 +82,7 @@ describe Gitlab::UsageData do clusters_platforms_user clusters_applications_helm clusters_applications_ingress + clusters_applications_cert_managers clusters_applications_prometheus clusters_applications_runner clusters_applications_knative @@ -131,6 +133,7 @@ describe Gitlab::UsageData do expect(count_data[:clusters_platforms_user]).to eq(1) expect(count_data[:clusters_applications_helm]).to eq(1) expect(count_data[:clusters_applications_ingress]).to eq(1) + expect(count_data[:clusters_applications_cert_managers]).to eq(1) expect(count_data[:clusters_applications_prometheus]).to eq(1) expect(count_data[:clusters_applications_runner]).to eq(1) expect(count_data[:clusters_applications_knative]).to eq(1) diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb new file mode 100644 index 00000000000..170c6001eaf --- /dev/null +++ b/spec/models/clusters/applications/cert_manager_spec.rb @@ -0,0 +1,79 @@ +require 'rails_helper' + +describe Clusters::Applications::CertManager do + let(:cert_manager) { create(:clusters_applications_cert_managers) } + + include_examples 'cluster application core specs', :clusters_applications_cert_managers + + describe '#make_installing!' do + before do + application.make_installing! + end + + context 'application install previously errored with older version' do + let(:application) { create(:clusters_applications_cert_managers, :scheduled, version: 'v0.4.0') } + + it 'updates the application version' do + expect(application.reload.version).to eq('v0.5.0') + end + end + end + + describe '#install_command' do + let(:cluster_issuer_file) { { "cluster_issuer.yaml": "---\napiVersion: certmanager.k8s.io/v1alpha1\nkind: ClusterIssuer\nmetadata:\n name: letsencrypt-prod\nspec:\n acme:\n server: https://acme-v02.api.letsencrypt.org/directory\n email: admin@example.com\n privateKeySecretRef:\n name: letsencrypt-prod\n http01: {}\n" } } + subject { cert_manager.install_command } + + it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) } + + it 'should be initialized with cert_manager arguments' do + expect(subject.name).to eq('certmanager') + expect(subject.chart).to eq('stable/cert-manager') + expect(subject.version).to eq('v0.5.0') + expect(subject).not_to be_rbac + expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file)) + expect(subject.postinstall).to eq(['/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml']) + end + + context 'for a specific user' do + before do + cert_manager.email = 'abc@xyz.com' + cluster_issuer_file[:'cluster_issuer.yaml'].gsub! 'admin@example.com', 'abc@xyz.com' + end + + it 'should use his/her email to register issuer with certificate provider' do + expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file)) + end + end + + context 'on a rbac enabled cluster' do + before do + cert_manager.cluster.platform_kubernetes.rbac! + end + + it { is_expected.to be_rbac } + end + + context 'application failed to install previously' do + let(:cert_manager) { create(:clusters_applications_cert_managers, :errored, version: '0.0.1') } + + it 'should be initialized with the locked version' do + expect(subject.version).to eq('v0.5.0') + end + end + end + + describe '#files' do + let(:application) { cert_manager } + let(:values) { subject[:'values.yaml'] } + + subject { application.files } + + it 'should include cert_manager specific keys in the values.yaml file' do + expect(values).to include('ingressShim') + end + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:email) } + end +end diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 052cfdbc4b1..a8b28a335d6 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') } it 'updates the application version' do - expect(application.reload.version).to eq('0.1.35') + expect(application.reload.version).to eq('0.1.38') end end end @@ -46,7 +46,7 @@ describe Clusters::Applications::Runner do it 'should be initialized with 4 arguments' do expect(subject.name).to eq('runner') expect(subject.chart).to eq('runner/gitlab-runner') - expect(subject.version).to eq('0.1.35') + expect(subject.version).to eq('0.1.38') expect(subject).not_to be_rbac expect(subject.repository).to eq('https://charts.gitlab.io') expect(subject.files).to eq(gitlab_runner.files) @@ -64,7 +64,7 @@ describe Clusters::Applications::Runner do let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') } it 'should be initialized with the locked version' do - expect(subject.version).to eq('0.1.35') + expect(subject.version).to eq('0.1.38') end end end diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index 98d7e799d67..eb68ebccdcb 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -311,13 +311,14 @@ describe Clusters::Cluster do context 'when applications are created' do let!(:helm) { create(:clusters_applications_helm, cluster: cluster) } let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) } + let!(:cert_manager) { create(:clusters_applications_cert_managers, cluster: cluster) } let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) } let!(:runner) { create(:clusters_applications_runner, cluster: cluster) } let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) } let!(:knative) { create(:clusters_applications_knative, cluster: cluster) } it 'returns a list of created applications' do - is_expected.to contain_exactly(helm, ingress, prometheus, runner, jupyter, knative) + is_expected.to contain_exactly(helm, ingress, cert_manager, prometheus, runner, jupyter, knative) end end end diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb index 66c1f47d12b..ac8da30b6c9 100644 --- a/spec/models/concerns/relative_positioning_spec.rb +++ b/spec/models/concerns/relative_positioning_spec.rb @@ -14,6 +14,14 @@ describe RelativePositioning do expect(issue.prev_relative_position).to eq nil expect(issue1.next_relative_position).to eq nil end + + it 'does not perform any moves if all issues have their relative_position set' do + issue.update!(relative_position: 1) + + expect(issue).not_to receive(:save) + + Issue.move_to_end([issue]) + end end describe '#max_relative_position' do diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index a2b41d56b8b..334dbb1c34c 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -178,6 +178,14 @@ describe API::Files do expect(response).to have_gitlab_http_status(200) end + it 'forces attachment content disposition' do + url = route(file_path) + "/raw" + + get api(url, current_user), params + + expect(headers['Content-Disposition']).to match(/^attachment/) + end + context 'when mandatory params are not given' do it_behaves_like '400 response' do let(:request) { get api(route("any%2Ffile"), current_user) } diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 3d532dd83c7..1827da61e2d 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -300,17 +300,31 @@ describe API::Issues do expect(json_response.first['state']).to eq('opened') end - it 'returns unlabeled issues for "No Label" label' do - get api("/issues", user), labels: 'No Label' + it 'returns an empty array if no issue matches labels and state filters' do + get api("/issues", user), labels: label.title, state: :closed + + expect_paginated_array_response(size: 0) + end + + it 'returns an array of issues with any label' do + get api("/issues", user), labels: IssuesFinder::FILTER_ANY expect_paginated_array_response(size: 1) - expect(json_response.first['labels']).to be_empty + expect(json_response.first['id']).to eq(issue.id) end - it 'returns an empty array if no issue matches labels and state filters' do - get api("/issues?labels=#{label.title}&state=closed", user) + it 'returns an array of issues with no label' do + get api("/issues", user), labels: IssuesFinder::FILTER_NONE - expect_paginated_array_response(size: 0) + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(closed_issue.id) + end + + it 'returns an array of issues with no label when using the legacy No+Label filter' do + get api("/issues", user), labels: "No Label" + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(closed_issue.id) end it 'returns an empty array if no issue matches milestone' do @@ -492,58 +506,58 @@ describe API::Issues do end it 'returns group issues without confidential issues for non project members' do - get api("#{base_url}?state=opened", non_member) + get api(base_url, non_member), state: :opened expect_paginated_array_response(size: 1) expect(json_response.first['title']).to eq(group_issue.title) end it 'returns group confidential issues for author' do - get api("#{base_url}?state=opened", author) + get api(base_url, author), state: :opened expect_paginated_array_response(size: 2) end it 'returns group confidential issues for assignee' do - get api("#{base_url}?state=opened", assignee) + get api(base_url, assignee), state: :opened expect_paginated_array_response(size: 2) end it 'returns group issues with confidential issues for project members' do - get api("#{base_url}?state=opened", user) + get api(base_url, user), state: :opened expect_paginated_array_response(size: 2) end it 'returns group confidential issues for admin' do - get api("#{base_url}?state=opened", admin) + get api(base_url, admin), state: :opened expect_paginated_array_response(size: 2) end it 'returns an array of labeled group issues' do - get api("#{base_url}?labels=#{group_label.title}", user) + get api(base_url, user), labels: group_label.title expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([group_label.title]) end it 'returns an array of labeled group issues where all labels match' do - get api("#{base_url}?labels=#{group_label.title},foo,bar", user) + get api(base_url, user), labels: "#{group_label.title},foo,bar" expect_paginated_array_response(size: 0) end it 'returns issues matching given search string for title' do - get api("#{base_url}?search=#{group_issue.title}", user) + get api(base_url, user), search: group_issue.title expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) end it 'returns issues matching given search string for description' do - get api("#{base_url}?search=#{group_issue.description}", user) + get api(base_url, user), search: group_issue.description expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) @@ -556,7 +570,7 @@ describe API::Issues do create(:label_link, label: label_b, target: group_issue) create(:label_link, label: label_c, target: group_issue) - get api("#{base_url}", user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}" + get api(base_url, user), labels: "#{group_label.title},#{label_b.title},#{label_c.title}" expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title]) @@ -576,40 +590,55 @@ describe API::Issues do end it 'returns an empty array if no group issue matches labels' do - get api("#{base_url}?labels=foo,bar", user) + get api(base_url, user), labels: 'foo,bar' expect_paginated_array_response(size: 0) end + it 'returns an array of group issues with any label' do + get api(base_url, user), labels: IssuesFinder::FILTER_ANY + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(group_issue.id) + end + + it 'returns an array of group issues with no label' do + get api(base_url, user), labels: IssuesFinder::FILTER_NONE + + response_ids = json_response.map { |issue| issue['id'] } + + expect_paginated_array_response(size: 2) + expect(response_ids).to contain_exactly(group_closed_issue.id, group_confidential_issue.id) + end + it 'returns an empty array if no issue matches milestone' do - get api("#{base_url}?milestone=#{group_empty_milestone.title}", user) + get api(base_url, user), milestone: group_empty_milestone.title expect_paginated_array_response(size: 0) end it 'returns an empty array if milestone does not exist' do - get api("#{base_url}?milestone=foo", user) + get api(base_url, user), milestone: 'foo' expect_paginated_array_response(size: 0) end it 'returns an array of issues in given milestone' do - get api("#{base_url}?state=opened&milestone=#{group_milestone.title}", user) + get api(base_url, user), state: :opened, milestone: group_milestone.title expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_issue.id) end it 'returns an array of issues matching state in milestone' do - get api("#{base_url}?milestone=#{group_milestone.title}"\ - '&state=closed', user) + get api(base_url, user), milestone: group_milestone.title, state: :closed expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(group_closed_issue.id) end it 'returns an array of issues with no milestone' do - get api("#{base_url}?milestone=#{no_milestone_title}", user) + get api(base_url, user), milestone: no_milestone_title expect(response).to have_gitlab_http_status(200) @@ -645,7 +674,7 @@ describe API::Issues do end it 'sorts by updated_at ascending when requested' do - get api("#{base_url}?order_by=updated_at&sort=asc", user) + get api(base_url, user), order_by: :updated_at, sort: :asc response_dates = json_response.map { |issue| issue['updated_at'] } @@ -748,7 +777,7 @@ describe API::Issues do end it 'returns an array of labeled project issues' do - get api("#{base_url}/issues?labels=#{label.title}", user) + get api("#{base_url}/issues", user), labels: label.title expect_paginated_array_response(size: 1) expect(json_response.first['labels']).to eq([label.title]) @@ -800,26 +829,42 @@ describe API::Issues do expect_paginated_array_response(size: 0) end + it 'returns an array of project issues with any label' do + get api("#{base_url}/issues", user), labels: IssuesFinder::FILTER_ANY + + expect_paginated_array_response(size: 1) + expect(json_response.first['id']).to eq(issue.id) + end + + it 'returns an array of project issues with no label' do + get api("#{base_url}/issues", user), labels: IssuesFinder::FILTER_NONE + + response_ids = json_response.map { |issue| issue['id'] } + + expect_paginated_array_response(size: 2) + expect(response_ids).to contain_exactly(closed_issue.id, confidential_issue.id) + end + it 'returns an empty array if no project issue matches labels' do - get api("#{base_url}/issues?labels=foo,bar", user) + get api("#{base_url}/issues", user), labels: 'foo,bar' expect_paginated_array_response(size: 0) end it 'returns an empty array if no issue matches milestone' do - get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user) + get api("#{base_url}/issues", user), milestone: empty_milestone.title expect_paginated_array_response(size: 0) end it 'returns an empty array if milestone does not exist' do - get api("#{base_url}/issues?milestone=foo", user) + get api("#{base_url}/issues", user), milestone: :foo expect_paginated_array_response(size: 0) end it 'returns an array of issues in given milestone' do - get api("#{base_url}/issues?milestone=#{milestone.title}", user) + get api("#{base_url}/issues", user), milestone: milestone.title expect_paginated_array_response(size: 2) expect(json_response.first['id']).to eq(issue.id) @@ -827,21 +872,21 @@ describe API::Issues do end it 'returns an array of issues matching state in milestone' do - get api("#{base_url}/issues?milestone=#{milestone.title}&state=closed", user) + get api("#{base_url}/issues", user), milestone: milestone.title, state: :closed expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(closed_issue.id) end it 'returns an array of issues with no milestone' do - get api("#{base_url}/issues?milestone=#{no_milestone_title}", user) + get api("#{base_url}/issues", user), milestone: no_milestone_title expect_paginated_array_response(size: 1) expect(json_response.first['id']).to eq(confidential_issue.id) end it 'returns an array of issues with any milestone' do - get api("#{base_url}/issues?milestone=#{any_milestone_title}", user) + get api("#{base_url}/issues", user), milestone: any_milestone_title response_ids = json_response.map { |issue| issue['id'] } @@ -859,7 +904,7 @@ describe API::Issues do end it 'sorts ascending when requested' do - get api("#{base_url}/issues?sort=asc", user) + get api("#{base_url}/issues", user), sort: :asc response_dates = json_response.map { |issue| issue['created_at'] } @@ -868,7 +913,7 @@ describe API::Issues do end it 'sorts by updated_at descending when requested' do - get api("#{base_url}/issues?order_by=updated_at", user) + get api("#{base_url}/issues", user), order_by: :updated_at response_dates = json_response.map { |issue| issue['updated_at'] } @@ -877,7 +922,7 @@ describe API::Issues do end it 'sorts by updated_at ascending when requested' do - get api("#{base_url}/issues?order_by=updated_at&sort=asc", user) + get api("#{base_url}/issues", user), order_by: :updated_at, sort: :asc response_dates = json_response.map { |issue| issue['updated_at'] } diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index fa38751fe58..de141377793 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -168,6 +168,12 @@ describe API::Repositories do expect(response).to have_gitlab_http_status(200) end + it 'forces attachment content disposition' do + get api(route, current_user) + + expect(headers['Content-Disposition']).to match(/^attachment/) + end + context 'when sha does not exist' do it_behaves_like '404 response' do let(:request) { get api(route.sub(sample_blob.oid, '123456'), current_user) } diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index 6da769cb3ed..c546ba3e127 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -94,6 +94,12 @@ describe API::Snippets do expect(response.body).to eq(snippet.content) end + it 'forces attachment content disposition' do + get api("/snippets/#{snippet.id}/raw", user) + + expect(headers['Content-Disposition']).to match(/^attachment/) + end + it 'returns 404 for invalid snippet id' do get api("/snippets/1234/raw", user) diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb index 668a390b5d2..92d4dd598d5 100644 --- a/spec/support/shared_examples/requests/api/merge_requests_list.rb +++ b/spec/support/shared_examples/requests/api/merge_requests_list.rb @@ -186,6 +186,23 @@ shared_examples 'merge requests list' do expect(json_response.length).to eq(0) end + it 'returns an array of merge requests with any label when filtering by any label' do + get api(endpoint_path, user), labels: IssuesFinder::FILTER_ANY + + expect_paginated_array_response + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(merge_request.id) + end + + it 'returns an array of merge requests without a label when filtering by no label' do + get api(endpoint_path, user), labels: IssuesFinder::FILTER_NONE + + response_ids = json_response.map { |merge_request| merge_request['id'] } + + expect_paginated_array_response + expect(response_ids).to contain_exactly(merge_request_closed.id, merge_request_merged.id, merge_request_locked.id) + end + it 'returns an array of labeled merge requests that are merged for a milestone' do bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project) diff --git a/spec/tasks/cache/clear/redis_spec.rb b/spec/tasks/cache/clear/redis_spec.rb index cca2b864e9b..97c8c943f3a 100644 --- a/spec/tasks/cache/clear/redis_spec.rb +++ b/spec/tasks/cache/clear/redis_spec.rb @@ -6,7 +6,10 @@ describe 'clearing redis cache' do end describe 'clearing pipeline status cache' do - let(:pipeline_status) { create(:ci_pipeline).project.pipeline_status } + let(:pipeline_status) do + project = create(:project, :repository) + create(:ci_pipeline, project: project).project.pipeline_status + end before do allow(pipeline_status).to receive(:loaded).and_return(nil) |