diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-11 21:08:58 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-11 21:08:58 +0300 |
commit | 1ca9950d5f890cd8f185e1eda158b969a7244fe2 (patch) | |
tree | 6f62715938a4b2b001705c51c697609a8e0850ae /spec | |
parent | bcc77054ee9aefd1e332e04a4189390fd5a3112e (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
29 files changed, 708 insertions, 632 deletions
diff --git a/spec/controllers/admin/services_controller_spec.rb b/spec/controllers/admin/services_controller_spec.rb index 6f59a5ac016..44233776865 100644 --- a/spec/controllers/admin/services_controller_spec.rb +++ b/spec/controllers/admin/services_controller_spec.rb @@ -15,11 +15,11 @@ describe Admin::ServicesController do Service.available_services_names.each do |service_name| context "#{service_name}" do let!(:service) do - service_instance = "#{service_name}_service".camelize.constantize - service_instance.where(instance: true).first_or_create + service_template = "#{service_name}_service".camelize.constantize + service_template.where(template: true).first_or_create end - it 'successfully displays the service' do + it 'successfully displays the template' do get :edit, params: { id: service.id } expect(response).to have_gitlab_http_status(:ok) @@ -34,7 +34,7 @@ describe Admin::ServicesController do RedmineService.create( project: project, active: false, - instance: true, + template: true, properties: { project_url: 'http://abc', issues_url: 'http://abc', @@ -44,7 +44,7 @@ describe Admin::ServicesController do end it 'calls the propagation worker when service is active' do - expect(PropagateInstanceLevelServiceWorker).to receive(:perform_async).with(service.id) + expect(PropagateServiceTemplateWorker).to receive(:perform_async).with(service.id) put :update, params: { id: service.id, service: { active: true } } @@ -52,7 +52,7 @@ describe Admin::ServicesController do end it 'does not call the propagation worker when service is not active' do - expect(PropagateInstanceLevelServiceWorker).not_to receive(:perform_async) + expect(PropagateServiceTemplateWorker).not_to receive(:perform_async) put :update, params: { id: service.id, service: { properties: {} } } diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index f0153ac37bf..db7533eb609 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -14,9 +14,11 @@ describe Projects::Serverless::FunctionsController do let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) } let(:knative_services_finder) { environment.knative_services_finder } let(:function_description) { 'A serverless function' } + let(:function_name) { 'some-function-name' } let(:knative_stub_options) do - { namespace: namespace.namespace, name: cluster.project.name, description: function_description } + { namespace: namespace.namespace, name: function_name, description: function_description } end + let(:knative) { create(:clusters_applications_knative, :installed, cluster: cluster) } let(:namespace) do create(:cluster_kubernetes_namespace, @@ -87,25 +89,65 @@ describe Projects::Serverless::FunctionsController do end context 'when functions were found' do - let(:functions) { ["asdf"] } + let(:functions) { [{}, {}] } before do - stub_kubeclient_knative_services(namespace: namespace.namespace) - get :index, params: params({ format: :json }) + stub_kubeclient_knative_services(namespace: namespace.namespace, cluster_id: cluster.id, name: function_name) end it 'returns functions' do + get :index, params: params({ format: :json }) expect(json_response["functions"]).not_to be_empty end - it { expect(response).to have_gitlab_http_status(:ok) } + it 'filters out the functions whose cluster the user does not have permission to read' do + allow(controller).to receive(:can?).and_return(true) + expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false) + + get :index, params: params({ format: :json }) + + expect(json_response["functions"]).to be_empty + end + + it 'returns a successful response status' do + get :index, params: params({ format: :json }) + expect(response).to have_gitlab_http_status(:ok) + end + + context 'when there is serverless domain for a cluster' do + let!(:serverless_domain_cluster) do + create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id) + end + + it 'returns JSON with function details with serverless domain URL' do + get :index, params: params({ format: :json }) + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response["functions"]).not_to be_empty + + expect(json_response["functions"]).to all( + include( + 'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}" + ) + ) + end + end + + context 'when there is no serverless domain for a cluster' do + it 'keeps function URL as it was' do + expect(Gitlab::Serverless::Domain).not_to receive(:new) + + get :index, params: params({ format: :json }) + expect(response).to have_gitlab_http_status(:ok) + end + end end end end describe 'GET #show' do - context 'invalid data' do - it 'has a bad function name' do + context 'with function that does not exist' do + it 'returns 404' do get :show, params: params({ format: :json, environment_id: "*", id: "foo" }) expect(response).to have_gitlab_http_status(:not_found) end @@ -113,15 +155,50 @@ describe Projects::Serverless::FunctionsController do context 'with valid data', :use_clean_rails_memory_store_caching do shared_examples 'GET #show with valid data' do - it 'has a valid function name' do - get :show, params: params({ format: :json, environment_id: "*", id: cluster.project.name }) + context 'when there is serverless domain for a cluster' do + let!(:serverless_domain_cluster) do + create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id) + end + + it 'returns JSON with function details with serverless domain URL' do + get :show, params: params({ format: :json, environment_id: "*", id: function_name }) + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response).to include( + 'url' => "https://#{function_name}-#{serverless_domain_cluster.uuid[0..1]}a1#{serverless_domain_cluster.uuid[2..-3]}f2#{serverless_domain_cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}" + ) + end + + it 'returns 404 when user does not have permission to read the cluster' do + allow(controller).to receive(:can?).and_return(true) + expect(controller).to receive(:can?).with(user, :read_cluster, cluster).and_return(false) + + get :show, params: params({ format: :json, environment_id: "*", id: function_name }) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + context 'when there is no serverless domain for a cluster' do + it 'keeps function URL as it was' do + get :show, params: params({ format: :json, environment_id: "*", id: function_name }) + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response).to include( + 'url' => "http://#{function_name}.#{namespace.namespace}.example.com" + ) + end + end + + it 'return json with function details' do + get :show, params: params({ format: :json, environment_id: "*", id: function_name }) expect(response).to have_gitlab_http_status(:ok) expect(json_response).to include( - 'name' => project.name, - 'url' => "http://#{project.name}.#{namespace.namespace}.example.com", + 'name' => function_name, + 'url' => "http://#{function_name}.#{namespace.namespace}.example.com", 'description' => function_description, - 'podcount' => 1 + 'podcount' => 0 ) end end @@ -180,8 +257,8 @@ describe Projects::Serverless::FunctionsController do 'knative_installed' => 'checking', 'functions' => [ a_hash_including( - 'name' => project.name, - 'url' => "http://#{project.name}.#{namespace.namespace}.example.com", + 'name' => function_name, + 'url' => "http://#{function_name}.#{namespace.namespace}.example.com", 'description' => function_description ) ] diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index b6c64a964a6..fb7cca3997b 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -154,12 +154,12 @@ describe Projects::ServicesController do end end - context 'when activating Jira service from instance level service' do + context 'when activating Jira service from a template' do let(:service) do - create(:jira_service, project: project, instance: true) + create(:jira_service, project: project, template: true) end - it 'activate Jira service from instance level service' do + it 'activate Jira service from template' do expect(flash[:notice]).to eq 'Jira activated.' end end diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index 7606e806e04..f914128ed3b 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -380,5 +380,9 @@ x6zG6WoibsbsJMj70nwseUnPTBQNDP+j61RJjC/r scope { :instance } usage { :serverless } end + + trait :with_project do + association :project + end end end diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb index 67eda297b91..4e9f3d371ce 100644 --- a/spec/finders/projects/serverless/functions_finder_spec.rb +++ b/spec/finders/projects/serverless/functions_finder_spec.rb @@ -153,8 +153,8 @@ describe Projects::Serverless::FunctionsFinder do *knative_services_finder.cache_args) result = finder.service(cluster.environment_scope, cluster.project.name) - expect(result).not_to be_empty - expect(result["metadata"]["name"]).to be_eql(cluster.project.name) + expect(result).to be_present + expect(result.name).to be_eql(cluster.project.name) end it 'has metrics', :use_clean_rails_memory_store_caching do diff --git a/spec/fixtures/trace/sample_trace b/spec/fixtures/trace/sample_trace index 1aba1e76d0b..d774d154496 100644 --- a/spec/fixtures/trace/sample_trace +++ b/spec/fixtures/trace/sample_trace @@ -2736,7 +2736,7 @@ Service when repository is empty test runs execute Template - .build_from_instance + .build_from_template when template is invalid sets service template to inactive when template is invalid for pushover service diff --git a/spec/frontend/environments/environments_app_spec.js b/spec/frontend/environments/environments_app_spec.js new file mode 100644 index 00000000000..f3d2bd2462e --- /dev/null +++ b/spec/frontend/environments/environments_app_spec.js @@ -0,0 +1,168 @@ +import { mount, shallowMount } from '@vue/test-utils'; +import axios from '~/lib/utils/axios_utils'; +import MockAdapter from 'axios-mock-adapter'; +import Container from '~/environments/components/container.vue'; +import EmptyState from '~/environments/components/empty_state.vue'; +import EnvironmentsApp from '~/environments/components/environments_app.vue'; +import { environment, folder } from './mock_data'; + +describe('Environment', () => { + let mock; + let wrapper; + + const mockData = { + endpoint: 'environments.json', + canCreateEnvironment: true, + canReadEnvironment: true, + newEnvironmentPath: 'environments/new', + helpPagePath: 'help', + canaryDeploymentFeatureId: 'canary_deployment', + showCanaryDeploymentCallout: true, + userCalloutsPath: '/callouts', + lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', + helpCanaryDeploymentsPath: 'help/canary-deployments', + }; + + const mockRequest = (response, body) => { + mock.onGet(mockData.endpoint).reply(response, body, { + 'X-nExt-pAge': '2', + 'x-page': '1', + 'X-Per-Page': '1', + 'X-Prev-Page': '', + 'X-TOTAL': '37', + 'X-Total-Pages': '2', + }); + }; + + const createWrapper = (shallow = false) => { + const fn = shallow ? shallowMount : mount; + wrapper = fn(EnvironmentsApp, { propsData: mockData }); + return axios.waitForAll(); + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + wrapper.destroy(); + mock.restore(); + }); + + describe('successful request', () => { + describe('without environments', () => { + beforeEach(() => { + mockRequest(200, { environments: [] }); + return createWrapper(true); + }); + + it('should render the empty state', () => { + expect(wrapper.find(EmptyState).exists()).toBe(true); + }); + + describe('when it is possible to enable a review app', () => { + beforeEach(() => { + mockRequest(200, { environments: [], review_app: { can_setup_review_app: true } }); + return createWrapper(); + }); + + it('should render the enable review app button', () => { + expect(wrapper.find('.js-enable-review-app-button').text()).toContain( + 'Enable review app', + ); + }); + }); + }); + + describe('with paginated environments', () => { + const environmentList = [environment]; + + beforeEach(() => { + mockRequest(200, { + environments: environmentList, + stopped_count: 1, + available_count: 0, + }); + return createWrapper(); + }); + + it('should render a conatiner table with environments', () => { + const containerTable = wrapper.find(Container); + + expect(containerTable.exists()).toBe(true); + expect(containerTable.props('environments').length).toEqual(environmentList.length); + expect(containerTable.find('.environment-name').text()).toEqual(environmentList[0].name); + }); + + describe('pagination', () => { + it('should render pagination', () => { + expect(wrapper.findAll('.gl-pagination li').length).toEqual(9); + }); + + it('should make an API request when page is clicked', () => { + jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {}); + + wrapper.find('.gl-pagination li:nth-child(3) .page-link').trigger('click'); + expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' }); + }); + + it('should make an API request when using tabs', () => { + jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {}); + wrapper.find('.js-environments-tab-stopped').trigger('click'); + expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); + }); + }); + }); + }); + + describe('unsuccessful request', () => { + beforeEach(() => { + mockRequest(500, {}); + return createWrapper(true); + }); + + it('should render empty state', () => { + expect(wrapper.find(EmptyState).exists()).toBe(true); + }); + }); + + describe('expandable folders', () => { + beforeEach(() => { + mockRequest(200, { + environments: [folder], + stopped_count: 1, + available_count: 0, + }); + + mock.onGet(environment.folder_path).reply(200, { environments: [environment] }); + + return createWrapper().then(() => { + // open folder + wrapper.find('.folder-name').trigger('click'); + return axios.waitForAll(); + }); + }); + + it('should open a closed folder', () => { + expect(wrapper.find('.folder-icon.ic-chevron-right').exists()).toBe(false); + }); + + it('should close an opened folder', () => { + expect(wrapper.find('.folder-icon.ic-chevron-down').exists()).toBe(true); + + // close folder + wrapper.find('.folder-name').trigger('click'); + wrapper.vm.$nextTick(() => { + expect(wrapper.find('.folder-icon.ic-chevron-down').exists()).toBe(false); + }); + }); + + it('should show children environments', () => { + expect(wrapper.findAll('.js-child-row').length).toEqual(1); + }); + + it('should show a button to show all environments', () => { + expect(wrapper.find('.text-center > a.btn').text()).toContain('Show all'); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 7a039d46d39..15c82242262 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -91,10 +91,10 @@ describe('Dashboard', () => { }); describe('no data found', () => { - beforeEach(done => { + beforeEach(() => { createShallowWrapper(); - wrapper.vm.$nextTick(done); + return wrapper.vm.$nextTick(); }); it('shows the environment selector dropdown', () => { @@ -118,20 +118,15 @@ describe('Dashboard', () => { }); }); - it('shows up a loading state', done => { + it('shows up a loading state', () => { createShallowWrapper({ hasMetrics: true }, { methods: {} }); - wrapper.vm - .$nextTick() - .then(() => { - expect(wrapper.vm.emptyState).toEqual('loading'); - - done(); - }) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.emptyState).toEqual('loading'); + }); }); - it('hides the group panels when showPanels is false', done => { + it('hides the group panels when showPanels is false', () => { createMountedWrapper( { hasMetrics: true, showPanels: false }, { stubs: ['graph-group', 'panel-type'] }, @@ -139,15 +134,10 @@ describe('Dashboard', () => { setupComponentStore(wrapper); - wrapper.vm - .$nextTick() - .then(() => { - expect(wrapper.vm.showEmptyState).toEqual(false); - expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0); - - done(); - }) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.showEmptyState).toEqual(false); + expect(wrapper.findAll('.prometheus-panel')).toHaveLength(0); + }); }); it('fetches the metrics data with proper time window', () => { @@ -171,43 +161,32 @@ describe('Dashboard', () => { createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); setupComponentStore(wrapper); + + return wrapper.vm.$nextTick(); }); - it('renders the environments dropdown with a number of environments', done => { - wrapper.vm - .$nextTick() - .then(() => { - expect(findAllEnvironmentsDropdownItems().length).toEqual(environmentData.length); - - findAllEnvironmentsDropdownItems().wrappers.forEach((itemWrapper, index) => { - const anchorEl = itemWrapper.find('a'); - if (anchorEl.exists() && environmentData[index].metrics_path) { - const href = anchorEl.attributes('href'); - expect(href).toBe(environmentData[index].metrics_path); - } - }); + it('renders the environments dropdown with a number of environments', () => { + expect(findAllEnvironmentsDropdownItems().length).toEqual(environmentData.length); - done(); - }) - .catch(done.fail); + findAllEnvironmentsDropdownItems().wrappers.forEach((itemWrapper, index) => { + const anchorEl = itemWrapper.find('a'); + if (anchorEl.exists() && environmentData[index].metrics_path) { + const href = anchorEl.attributes('href'); + expect(href).toBe(environmentData[index].metrics_path); + } + }); }); - it('renders the environments dropdown with a single active element', done => { - wrapper.vm - .$nextTick() - .then(() => { - const activeItem = findAllEnvironmentsDropdownItems().wrappers.filter(itemWrapper => - itemWrapper.find('.active').exists(), - ); + it('renders the environments dropdown with a single active element', () => { + const activeItem = findAllEnvironmentsDropdownItems().wrappers.filter(itemWrapper => + itemWrapper.find('.active').exists(), + ); - expect(activeItem.length).toBe(1); - done(); - }) - .catch(done.fail); + expect(activeItem.length).toBe(1); }); }); - it('hides the environments dropdown list when there is no environments', done => { + it('hides the environments dropdown list when there is no environments', () => { createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); wrapper.vm.$store.commit( @@ -219,35 +198,27 @@ describe('Dashboard', () => { mockedQueryResultPayload, ); - wrapper.vm - .$nextTick() - .then(() => { - expect(findAllEnvironmentsDropdownItems()).toHaveLength(0); - done(); - }) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(findAllEnvironmentsDropdownItems()).toHaveLength(0); + }); }); - it('renders the datetimepicker dropdown', done => { + it('renders the datetimepicker dropdown', () => { createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); setupComponentStore(wrapper); - wrapper.vm - .$nextTick() - .then(() => { - expect(wrapper.find(DateTimePicker).exists()).toBe(true); - done(); - }) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.find(DateTimePicker).exists()).toBe(true); + }); }); describe('when one of the metrics is missing', () => { - beforeEach(done => { + beforeEach(() => { createShallowWrapper({ hasMetrics: true }); setupComponentStore(wrapper); - wrapper.vm.$nextTick(done); + return wrapper.vm.$nextTick(); }); it('shows a group empty area', () => { @@ -300,7 +271,7 @@ describe('Dashboard', () => { const resultEnvs = environmentData.filter(({ name }) => name.indexOf(searchTerm) !== -1); setSearchTerm(searchTerm); - return wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick().then(() => { expect(findAllEnvironmentsDropdownItems().length).toEqual(resultEnvs.length); }); }); @@ -349,12 +320,12 @@ describe('Dashboard', () => { const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel'); const findRearrangeButton = () => wrapper.find('.js-rearrange-button'); - beforeEach(done => { + beforeEach(() => { createShallowWrapper({ hasMetrics: true }); setupComponentStore(wrapper); - wrapper.vm.$nextTick(done); + return wrapper.vm.$nextTick(); }); it('wraps vuedraggable', () => { @@ -368,9 +339,9 @@ describe('Dashboard', () => { }); describe('when rearrange is enabled', () => { - beforeEach(done => { + beforeEach(() => { wrapper.setProps({ rearrangePanelsAvailable: true }); - wrapper.vm.$nextTick(done); + return wrapper.vm.$nextTick(); }); it('displays rearrange button', () => { @@ -383,9 +354,9 @@ describe('Dashboard', () => { .at(0) .find('.js-draggable-remove'); - beforeEach(done => { + beforeEach(() => { findRearrangeButton().vm.$emit('click'); - wrapper.vm.$nextTick(done); + return wrapper.vm.$nextTick(); }); it('it enables draggables', () => { @@ -393,7 +364,7 @@ describe('Dashboard', () => { expect(findEnabledDraggables()).toEqual(findDraggables()); }); - it('metrics can be swapped', done => { + it('metrics can be swapped', () => { const firstDraggable = findDraggables().at(0); const mockMetrics = [...metricsDashboardPayload.panel_groups[1].panels]; @@ -404,33 +375,30 @@ describe('Dashboard', () => { [mockMetrics[0], mockMetrics[1]] = [mockMetrics[1], mockMetrics[0]]; firstDraggable.vm.$emit('input', mockMetrics); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { const { panels } = wrapper.vm.dashboard.panel_groups[1]; expect(panels[1].title).toEqual(firstTitle); expect(panels[0].title).toEqual(secondTitle); - done(); }); }); - it('shows a remove button, which removes a panel', done => { + it('shows a remove button, which removes a panel', () => { expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false); expect(findDraggablePanels().length).toEqual(expectedPanelCount); findFirstDraggableRemoveButton().trigger('click'); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1); - done(); }); }); - it('it disables draggables when clicked again', done => { + it('it disables draggables when clicked again', () => { findRearrangeButton().vm.$emit('click'); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findRearrangeButton().attributes('pressed')).toBeFalsy(); expect(findEnabledDraggables().length).toBe(0); - done(); }); }); }); @@ -438,13 +406,13 @@ describe('Dashboard', () => { }); describe('cluster health', () => { - beforeEach(done => { + beforeEach(() => { mock.onGet(propsData.metricsEndpoint).reply(statusCodes.OK, JSON.stringify({})); createShallowWrapper({ hasMetrics: true, showHeader: false }); // all_dashboards is not defined in health dashboards wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined); - wrapper.vm.$nextTick(done); + return wrapper.vm.$nextTick(); }); it('hides dashboard header by default', () => { @@ -460,33 +428,29 @@ describe('Dashboard', () => { describe('dashboard edit link', () => { const findEditLink = () => wrapper.find('.js-edit-link'); - beforeEach(done => { + beforeEach(() => { createShallowWrapper({ hasMetrics: true }); wrapper.vm.$store.commit( `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, dashboardGitResponse, ); - wrapper.vm.$nextTick(done); + return wrapper.vm.$nextTick(); }); it('is not present for the default dashboard', () => { expect(findEditLink().exists()).toBe(false); }); - it('is present for a custom dashboard, and links to its edit_path', done => { + it('is present for a custom dashboard, and links to its edit_path', () => { const dashboard = dashboardGitResponse[1]; // non-default dashboard const currentDashboard = dashboard.path; wrapper.setProps({ currentDashboard }); - wrapper.vm - .$nextTick() - .then(() => { - expect(findEditLink().exists()).toBe(true); - expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path); - done(); - }) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + expect(findEditLink().exists()).toBe(true); + expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path); + }); }); }); @@ -498,18 +462,14 @@ describe('Dashboard', () => { `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, dashboardGitResponse, ); + + return wrapper.vm.$nextTick(); }); - it('shows the dashboard dropdown', done => { - wrapper.vm - .$nextTick() - .then(() => { - const dashboardDropdown = wrapper.find(DashboardsDropdown); + it('shows the dashboard dropdown', () => { + const dashboardDropdown = wrapper.find(DashboardsDropdown); - expect(dashboardDropdown.exists()).toBe(true); - done(); - }) - .catch(done.fail); + expect(dashboardDropdown.exists()).toBe(true); }); }); @@ -524,20 +484,16 @@ describe('Dashboard', () => { }, { stubs: ['graph-group', 'panel-type'] }, ); + + return wrapper.vm.$nextTick(); }); - it('shows the link', done => { - wrapper.vm - .$nextTick() - .then(() => { - const externalDashboardButton = wrapper.find('.js-external-dashboard-link'); + it('shows the link', () => { + const externalDashboardButton = wrapper.find('.js-external-dashboard-link'); - expect(externalDashboardButton.exists()).toBe(true); - expect(externalDashboardButton.is(GlButton)).toBe(true); - expect(externalDashboardButton.text()).toContain('View full dashboard'); - done(); - }) - .catch(done.fail); + expect(externalDashboardButton.exists()).toBe(true); + expect(externalDashboardButton.is(GlButton)).toBe(true); + expect(externalDashboardButton.text()).toContain('View full dashboard'); }); }); @@ -550,12 +506,12 @@ describe('Dashboard', () => { .at(i) .props('clipboardText'); - beforeEach(done => { + beforeEach(() => { createShallowWrapper({ hasMetrics: true, currentDashboard }); setupComponentStore(wrapper); - wrapper.vm.$nextTick(done); + return wrapper.vm.$nextTick(); }); it('contains a link to the dashboard', () => { @@ -565,23 +521,21 @@ describe('Dashboard', () => { expect(getClipboardTextAt(0)).toContain(`y_label=`); }); - it('strips the undefined parameter', done => { + it('strips the undefined parameter', () => { wrapper.setProps({ currentDashboard: undefined }); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(getClipboardTextAt(0)).not.toContain(`dashboard=`); expect(getClipboardTextAt(0)).toContain(`y_label=`); - done(); }); }); - it('null parameter is stripped', done => { + it('null parameter is stripped', () => { wrapper.setProps({ currentDashboard: null }); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(getClipboardTextAt(0)).not.toContain(`dashboard=`); expect(getClipboardTextAt(0)).toContain(`y_label=`); - done(); }); }); }); diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js index 51c7b22f242..0bcfabe6415 100644 --- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js +++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js @@ -131,20 +131,17 @@ describe('DashboardsDropdown', () => { expect(findModal().contains(DuplicateDashboardForm)).toBe(true); }); - it('saves a new dashboard', done => { + it('saves a new dashboard', () => { findModal().vm.$emit('ok', okEvent); - waitForPromises() - .then(() => { - expect(okEvent.preventDefault).toHaveBeenCalled(); - - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); - expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled(); - expect(wrapper.emitted().selectDashboard).toBeTruthy(); - expect(findAlert().exists()).toBe(false); - done(); - }) - .catch(done.fail); + return waitForPromises().then(() => { + expect(okEvent.preventDefault).toHaveBeenCalled(); + + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled(); + expect(wrapper.emitted().selectDashboard).toBeTruthy(); + expect(findAlert().exists()).toBe(false); + }); }); describe('when a new dashboard is saved succesfully', () => { @@ -167,52 +164,42 @@ describe('DashboardsDropdown', () => { findModal().vm.$emit('ok', okEvent); }; - it('to the default branch, redirects to the new dashboard', done => { + it('to the default branch, redirects to the new dashboard', () => { submitForm({ branch: defaultBranch, }); - waitForPromises() - .then(() => { - expect(wrapper.emitted().selectDashboard[0][0]).toEqual(newDashboard); - done(); - }) - .catch(done.fail); + return waitForPromises().then(() => { + expect(wrapper.emitted().selectDashboard[0][0]).toEqual(newDashboard); + }); }); - it('to a new branch refreshes in the current dashboard', done => { + it('to a new branch refreshes in the current dashboard', () => { submitForm({ branch: 'another-branch', }); - waitForPromises() - .then(() => { - expect(wrapper.emitted().selectDashboard[0][0]).toEqual(dashboardGitResponse[0]); - done(); - }) - .catch(done.fail); + return waitForPromises().then(() => { + expect(wrapper.emitted().selectDashboard[0][0]).toEqual(dashboardGitResponse[0]); + }); }); }); - it('handles error when a new dashboard is not saved', done => { + it('handles error when a new dashboard is not saved', () => { const errMsg = 'An error occurred'; duplicateDashboardAction.mockRejectedValueOnce(errMsg); findModal().vm.$emit('ok', okEvent); - waitForPromises() - .then(() => { - expect(okEvent.preventDefault).toHaveBeenCalled(); - - expect(findAlert().exists()).toBe(true); - expect(findAlert().text()).toBe(errMsg); + return waitForPromises().then(() => { + expect(okEvent.preventDefault).toHaveBeenCalled(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); - expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled(); + expect(findAlert().exists()).toBe(true); + expect(findAlert().text()).toBe(errMsg); - done(); - }) - .catch(done.fail); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled(); + }); }); it('id is correct, as the value of modal directive binding matches modal id', () => { diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js index 75a488b5c7b..10fd58f749d 100644 --- a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js +++ b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js @@ -44,30 +44,27 @@ describe('DuplicateDashboardForm', () => { describe('validates the file name', () => { const findInvalidFeedback = () => findByRef('fileNameFormGroup').find('.invalid-feedback'); - it('when is empty', done => { + it('when is empty', () => { setValue('fileName', ''); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findByRef('fileNameFormGroup').is('.is-valid')).toBe(true); expect(findInvalidFeedback().exists()).toBe(false); - done(); }); }); - it('when is valid', done => { + it('when is valid', () => { setValue('fileName', 'my_dashboard.yml'); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findByRef('fileNameFormGroup').is('.is-valid')).toBe(true); expect(findInvalidFeedback().exists()).toBe(false); - done(); }); }); - it('when is not valid', done => { + it('when is not valid', () => { setValue('fileName', 'my_dashboard.exe'); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findByRef('fileNameFormGroup').is('.is-invalid')).toBe(true); expect(findInvalidFeedback().text()).toBeTruthy(); - done(); }); }); }); @@ -124,30 +121,26 @@ describe('DuplicateDashboardForm', () => { }); }); - it('when a `default` branch option is set, branch input is invisible and ignored', done => { + it('when a `default` branch option is set, branch input is invisible and ignored', () => { setChecked(wrapper.vm.$options.radioVals.DEFAULT); setValue('branchName', 'a-new-branch'); expect(lastChange()).resolves.toMatchObject({ branch: defaultBranch, }); - wrapper.vm.$nextTick(() => { + + return wrapper.vm.$nextTick(() => { expect(findByRef('branchName').isVisible()).toBe(false); - done(); }); }); - it('when `new` branch option is chosen, focuses on the branch name input', done => { + it('when `new` branch option is chosen, focuses on the branch name input', () => { setChecked(wrapper.vm.$options.radioVals.NEW); - wrapper.vm - .$nextTick() - .then(() => { - wrapper.find('form').trigger('change'); - expect(findByRef('branchName').is(':focus')).toBe(true); - }) - .then(done) - .catch(done.fail); + return wrapper.vm.$nextTick().then(() => { + wrapper.find('form').trigger('change'); + expect(findByRef('branchName').is(':focus')).toBe(true); + }); }); }); }); diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js index 983785d0ecc..28a6af64394 100644 --- a/spec/frontend/monitoring/components/graph_group_spec.js +++ b/spec/frontend/monitoring/components/graph_group_spec.js @@ -32,25 +32,23 @@ describe('Graph group component', () => { expect(findCaretIcon().props('name')).toBe('angle-down'); }); - it('should show the angle-right caret icon when the user collapses the group', done => { + it('should show the angle-right caret icon when the user collapses the group', () => { wrapper.vm.collapse(); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findContent().isVisible()).toBe(false); expect(findCaretIcon().props('name')).toBe('angle-right'); - done(); }); }); - it('should show the open the group when collapseGroup is set to true', done => { + it('should show the open the group when collapseGroup is set to true', () => { wrapper.setProps({ collapseGroup: true, }); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findContent().isVisible()).toBe(true); expect(findCaretIcon().props('name')).toBe('angle-down'); - done(); }); }); @@ -102,13 +100,12 @@ describe('Graph group component', () => { expect(findCaretIcon().exists()).toBe(false); }); - it('should show the panel content when clicked', done => { + it('should show the panel content when clicked', () => { wrapper.vm.collapse(); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findContent().isVisible()).toBe(true); expect(findCaretIcon().exists()).toBe(false); - done(); }); }); }); diff --git a/spec/frontend/monitoring/components/panel_type_spec.js b/spec/frontend/monitoring/components/panel_type_spec.js index 6d8bd1d3a30..0d79babf386 100644 --- a/spec/frontend/monitoring/components/panel_type_spec.js +++ b/spec/frontend/monitoring/components/panel_type_spec.js @@ -28,6 +28,8 @@ describe('Panel Type component', () => { const exampleText = 'example_text'; + const findCopyLink = () => wrapper.find({ ref: 'copyChartLink' }); + const createWrapper = props => { wrapper = shallowMount(PanelType, { propsData: { @@ -96,8 +98,7 @@ describe('Panel Type component', () => { }); it('sets no clipboard copy link on dropdown by default', () => { - const link = () => wrapper.find({ ref: 'copyChartLink' }); - expect(link().exists()).toBe(false); + expect(findCopyLink().exists()).toBe(false); }); describe('Time Series Chart panel type', () => { @@ -204,7 +205,6 @@ describe('Panel Type component', () => { }); describe('when cliboard data is available', () => { - const link = () => wrapper.find({ ref: 'copyChartLink' }); const clipboardText = 'A value to copy.'; beforeEach(() => { @@ -219,16 +219,16 @@ describe('Panel Type component', () => { }); it('sets clipboard text on the dropdown', () => { - expect(link().exists()).toBe(true); - expect(link().element.dataset.clipboardText).toBe(clipboardText); + expect(findCopyLink().exists()).toBe(true); + expect(findCopyLink().element.dataset.clipboardText).toBe(clipboardText); }); it('adds a copy button to the dropdown', () => { - expect(link().text()).toContain('Generate link to chart'); + expect(findCopyLink().text()).toContain('Generate link to chart'); }); it('opens a toast on click', () => { - link().vm.$emit('click'); + findCopyLink().vm.$emit('click'); expect(wrapper.vm.$toast.show).toHaveBeenCalled(); }); diff --git a/spec/graphql/types/snippets/blob_type_spec.rb b/spec/graphql/types/snippets/blob_type_spec.rb index e7d4e5dfa2d..a263fada644 100644 --- a/spec/graphql/types/snippets/blob_type_spec.rb +++ b/spec/graphql/types/snippets/blob_type_spec.rb @@ -4,10 +4,9 @@ require 'spec_helper' describe GitlabSchema.types['SnippetBlob'] do it 'has the correct fields' do - expected_fields = [:highlighted_data, :raw_path, - :size, :binary, :name, :path, - :simple_viewer, :rich_viewer, - :mode] + expected_fields = [:highlighted_data, :plain_highlighted_data, + :raw_path, :size, :binary, :name, :path, + :simple_viewer, :rich_viewer, :mode] is_expected.to have_graphql_fields(*expected_fields) end diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js deleted file mode 100644 index 6c05b609923..00000000000 --- a/spec/javascripts/environments/environments_app_spec.js +++ /dev/null @@ -1,279 +0,0 @@ -import Vue from 'vue'; -import MockAdapter from 'axios-mock-adapter'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import axios from '~/lib/utils/axios_utils'; -import environmentsComponent from '~/environments/components/environments_app.vue'; -import { environment, folder } from './mock_data'; - -describe('Environment', () => { - const mockData = { - endpoint: 'environments.json', - canCreateEnvironment: true, - canReadEnvironment: true, - newEnvironmentPath: 'environments/new', - helpPagePath: 'help', - canaryDeploymentFeatureId: 'canary_deployment', - showCanaryDeploymentCallout: true, - userCalloutsPath: '/callouts', - lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', - helpCanaryDeploymentsPath: 'help/canary-deployments', - }; - - let EnvironmentsComponent; - let component; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - - EnvironmentsComponent = Vue.extend(environmentsComponent); - }); - - afterEach(() => { - component.$destroy(); - mock.restore(); - }); - - describe('successful request', () => { - describe('without environments', () => { - beforeEach(done => { - mock.onGet(mockData.endpoint).reply(200, { environments: [] }); - - component = mountComponent(EnvironmentsComponent, mockData); - - setTimeout(() => { - done(); - }, 0); - }); - - it('should render the empty state', () => { - expect(component.$el.querySelector('.js-new-environment-button').textContent).toContain( - 'New environment', - ); - - expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain( - "You don't have any environments right now", - ); - }); - - describe('when it is possible to enable a review app', () => { - beforeEach(done => { - mock - .onGet(mockData.endpoint) - .reply(200, { environments: [], review_app: { can_setup_review_app: true } }); - - component = mountComponent(EnvironmentsComponent, mockData); - - setTimeout(() => { - done(); - }, 0); - }); - - it('should render the enable review app button', () => { - expect(component.$el.querySelector('.js-enable-review-app-button').textContent).toContain( - 'Enable review app', - ); - }); - }); - }); - - describe('with paginated environments', () => { - beforeEach(done => { - mock.onGet(mockData.endpoint).reply( - 200, - { - environments: [environment], - stopped_count: 1, - available_count: 0, - }, - { - 'X-nExt-pAge': '2', - 'x-page': '1', - 'X-Per-Page': '1', - 'X-Prev-Page': '', - 'X-TOTAL': '37', - 'X-Total-Pages': '2', - }, - ); - - component = mountComponent(EnvironmentsComponent, mockData); - - setTimeout(() => { - done(); - }, 0); - }); - - it('should render a table with environments', () => { - expect(component.$el.querySelectorAll('table')).not.toBeNull(); - expect(component.$el.querySelector('.environment-name').textContent.trim()).toEqual( - environment.name, - ); - }); - - describe('pagination', () => { - it('should render pagination', () => { - expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(9); - }); - - it('should make an API request when page is clicked', done => { - spyOn(component, 'updateContent'); - setTimeout(() => { - component.$el.querySelector('.gl-pagination li:nth-child(3) .page-link').click(); - - expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' }); - done(); - }, 0); - }); - - it('should make an API request when using tabs', done => { - setTimeout(() => { - spyOn(component, 'updateContent'); - component.$el.querySelector('.js-environments-tab-stopped').click(); - - expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); - done(); - }, 0); - }); - }); - }); - }); - - describe('unsuccessfull request', () => { - beforeEach(done => { - mock.onGet(mockData.endpoint).reply(500, {}); - - component = mountComponent(EnvironmentsComponent, mockData); - - setTimeout(() => { - done(); - }, 0); - }); - - it('should render empty state', () => { - expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain( - "You don't have any environments right now", - ); - }); - }); - - describe('expandable folders', () => { - beforeEach(() => { - mock.onGet(mockData.endpoint).reply( - 200, - { - environments: [folder], - stopped_count: 0, - available_count: 1, - }, - { - 'X-nExt-pAge': '2', - 'x-page': '1', - 'X-Per-Page': '1', - 'X-Prev-Page': '', - 'X-TOTAL': '37', - 'X-Total-Pages': '2', - }, - ); - - mock.onGet(environment.folder_path).reply(200, { environments: [environment] }); - - component = mountComponent(EnvironmentsComponent, mockData); - }); - - it('should open a closed folder', done => { - setTimeout(() => { - component.$el.querySelector('.folder-name').click(); - - Vue.nextTick(() => { - expect(component.$el.querySelector('.folder-icon.ic-chevron-right')).toBe(null); - done(); - }); - }, 0); - }); - - it('should close an opened folder', done => { - setTimeout(() => { - // open folder - component.$el.querySelector('.folder-name').click(); - - Vue.nextTick(() => { - // close folder - component.$el.querySelector('.folder-name').click(); - - Vue.nextTick(() => { - expect(component.$el.querySelector('.folder-icon.ic-chevron-down')).toBe(null); - done(); - }); - }); - }, 0); - }); - - it('should show children environments and a button to show all environments', done => { - setTimeout(() => { - // open folder - component.$el.querySelector('.folder-name').click(); - - Vue.nextTick(() => { - // wait for next async request - setTimeout(() => { - expect(component.$el.querySelectorAll('.js-child-row').length).toEqual(1); - expect(component.$el.querySelector('.text-center > a.btn').textContent).toContain( - 'Show all', - ); - done(); - }); - }); - }, 0); - }); - }); - - describe('methods', () => { - beforeEach(() => { - mock.onGet(mockData.endpoint).reply( - 200, - { - environments: [], - stopped_count: 0, - available_count: 1, - }, - {}, - ); - - component = mountComponent(EnvironmentsComponent, mockData); - spyOn(window.history, 'pushState').and.stub(); - }); - - describe('updateContent', () => { - it('should set given parameters', done => { - component - .updateContent({ scope: 'stopped', page: '3' }) - .then(() => { - expect(component.page).toEqual('3'); - expect(component.scope).toEqual('stopped'); - expect(component.requestData.scope).toEqual('stopped'); - expect(component.requestData.page).toEqual('3'); - done(); - }) - .catch(done.fail); - }); - }); - - describe('onChangeTab', () => { - it('should set page to 1', () => { - spyOn(component, 'updateContent'); - component.onChangeTab('stopped'); - - expect(component.updateContent).toHaveBeenCalledWith({ scope: 'stopped', page: '1' }); - }); - }); - - describe('onChangePage', () => { - it('should update page and keep scope', () => { - spyOn(component, 'updateContent'); - component.onChangePage(4); - - expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' }); - }); - }); - }); -}); diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index f4d3c9e613e..c899217d164 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -652,10 +652,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do setup_import_export_config('light') end - it 'does not import any instance-level services' do + it 'does not import any templated services' do expect(restored_project_json).to eq(true) - expect(project.services.where(instance: true).count).to eq(0) + expect(project.services.where(template: true).count).to eq(0) end it 'imports labels' do diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 7695617cb57..55e7d6bd1e3 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -453,7 +453,7 @@ Service: - updated_at - active - properties -- instance +- template - push_events - issues_events - commit_events diff --git a/spec/lib/gitlab/serverless/service_spec.rb b/spec/lib/gitlab/serverless/service_spec.rb new file mode 100644 index 00000000000..f618dd02cdb --- /dev/null +++ b/spec/lib/gitlab/serverless/service_spec.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Serverless::Service do + let(:cluster) { create(:cluster) } + let(:environment) { create(:environment) } + let(:attributes) do + { + 'apiVersion' => 'serving.knative.dev/v1alpha1', + 'kind' => 'Service', + 'metadata' => { + 'creationTimestamp' => '2019-10-22T21:19:13Z', + 'name' => 'kubetest', + 'namespace' => 'project1-1-environment1' + }, + 'spec' => { + 'runLatest' => { + 'configuration' => { + 'build' => { + 'template' => { + 'name' => 'some-image' + } + } + } + } + }, + 'environment_scope' => '*', + 'cluster' => cluster, + 'environment' => environment, + 'podcount' => 0 + } + end + + it 'exposes methods extracting data from the attributes hash' do + service = Gitlab::Serverless::Service.new(attributes) + + expect(service.name).to eq('kubetest') + expect(service.namespace).to eq('project1-1-environment1') + expect(service.environment_scope).to eq('*') + expect(service.podcount).to eq(0) + expect(service.created_at).to eq(DateTime.parse('2019-10-22T21:19:13Z')) + expect(service.image).to eq('some-image') + expect(service.cluster).to eq(cluster) + expect(service.environment).to eq(environment) + end + + it 'returns nil for missing attributes' do + service = Gitlab::Serverless::Service.new({}) + + [:name, :namespace, :environment_scope, :cluster, :podcount, :created_at, :image, :description, :url, :environment].each do |method| + expect(service.send(method)).to be_nil + end + end + + describe '#description' do + it 'extracts the description in knative 7 format if available' do + attributes = { + 'spec' => { + 'template' => { + 'metadata' => { + 'annotations' => { + 'Description' => 'some description' + } + } + } + } + } + service = Gitlab::Serverless::Service.new(attributes) + + expect(service.description).to eq('some description') + end + + it 'extracts the description in knative 5/6 format if 7 is not available' do + attributes = { + 'spec' => { + 'runLatest' => { + 'configuration' => { + 'revisionTemplate' => { + 'metadata' => { + 'annotations' => { + 'Description' => 'some description' + } + } + } + } + } + } + } + service = Gitlab::Serverless::Service.new(attributes) + + expect(service.description).to eq('some description') + end + end + + describe '#url' do + it 'returns proxy URL if cluster has serverless domain' do + # cluster = create(:cluster) + knative = create(:clusters_applications_knative, :installed, cluster: cluster) + create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id) + service = Gitlab::Serverless::Service.new(attributes.merge('cluster' => cluster)) + + expect(Gitlab::Serverless::FunctionURI).to receive(:new).with( + function: service.name, + cluster: service.cluster.serverless_domain, + environment: service.environment + ).and_return('https://proxy.example.com') + + expect(service.url).to eq('https://proxy.example.com') + end + + it 'returns the URL from the knative 6/7 format' do + attributes = { + 'status' => { + 'url' => 'https://example.com' + } + } + service = Gitlab::Serverless::Service.new(attributes) + + expect(service.url).to eq('https://example.com') + end + + it 'returns the URL from the knative 5 format' do + attributes = { + 'status' => { + 'domain' => 'example.com' + } + } + service = Gitlab::Serverless::Service.new(attributes) + + expect(service.url).to eq('http://example.com') + end + end +end diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index 8e9a816ba6a..9a49d334f52 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::UsageData do create(:service, project: projects[1], type: 'SlackService', active: true) create(:service, project: projects[2], type: 'SlackService', active: true) create(:service, project: projects[2], type: 'MattermostService', active: false) - create(:service, project: projects[2], type: 'MattermostService', active: true, instance: true) + create(:service, project: projects[2], type: 'MattermostService', active: true, template: true) create(:service, project: projects[2], type: 'CustomIssueTrackerService', active: true) create(:project_error_tracking_setting, project: projects[0]) create(:project_error_tracking_setting, project: projects[1], enabled: false) diff --git a/spec/migrations/migrate_propagate_service_template_sidekiq_queue_spec.rb b/spec/migrations/migrate_propagate_service_template_sidekiq_queue_spec.rb deleted file mode 100644 index 2fffe638117..00000000000 --- a/spec/migrations/migrate_propagate_service_template_sidekiq_queue_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20200206111847_migrate_propagate_service_template_sidekiq_queue.rb') - -describe MigratePropagateServiceTemplateSidekiqQueue, :sidekiq, :redis do - include Gitlab::Database::MigrationHelpers - include StubWorker - - context 'when there are jobs in the queue' do - it 'correctly migrates queue when migrating up' do - Sidekiq::Testing.disable! do - stub_worker(queue: 'propagate_service_template').perform_async('Something', [1]) - stub_worker(queue: 'propagate_instance_level_service').perform_async('Something', [1]) - - described_class.new.up - - expect(sidekiq_queue_length('propagate_service_template')).to eq 0 - expect(sidekiq_queue_length('propagate_instance_level_service')).to eq 2 - end - end - end - - context 'when there are no jobs in the queues' do - it 'does not raise error when migrating up' do - expect { described_class.new.up }.not_to raise_error - end - end -end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index df2ed4911ec..f58bcbebd67 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -97,23 +97,23 @@ describe Service do end end - describe "Instance" do + describe "Template" do let(:project) { create(:project) } - describe '.build_from_instance' do - context 'when instance level integration is invalid' do - it 'sets instance level integration to inactive when instance is invalid' do - instance = build(:prometheus_service, instance: true, active: true, properties: {}) - instance.save(validate: false) + describe '.build_from_template' do + context 'when template is invalid' do + it 'sets service template to inactive when template is invalid' do + template = build(:prometheus_service, template: true, active: true, properties: {}) + template.save(validate: false) - service = described_class.build_from_instance(project.id, instance) + service = described_class.build_from_template(project.id, template) expect(service).to be_valid expect(service.active).to be false end end - describe 'build issue tracker from a instance level integration' do + describe 'build issue tracker from a template' do let(:title) { 'custom title' } let(:description) { 'custom description' } let(:url) { 'http://jira.example.com' } @@ -127,9 +127,9 @@ describe Service do } end - shared_examples 'integration creation from instance level' do + shared_examples 'service creation from a template' do it 'creates a correct service' do - service = described_class.build_from_instance(project.id, instance_level_integration) + service = described_class.build_from_template(project.id, template) expect(service).to be_active expect(service.title).to eq(title) @@ -144,38 +144,38 @@ describe Service do # this will be removed as part of https://gitlab.com/gitlab-org/gitlab/issues/29404 context 'when data are stored in properties' do let(:properties) { data_params.merge(title: title, description: description) } - let!(:instance_level_integration) do - create(:jira_service, :without_properties_callback, instance: true, properties: properties.merge(additional: 'something')) + let!(:template) do + create(:jira_service, :without_properties_callback, template: true, properties: properties.merge(additional: 'something')) end - it_behaves_like 'integration creation from instance level' + it_behaves_like 'service creation from a template' end context 'when data are stored in separated fields' do - let(:instance_level_integration) do - create(:jira_service, data_params.merge(properties: {}, title: title, description: description, instance: true)) + let(:template) do + create(:jira_service, data_params.merge(properties: {}, title: title, description: description, template: true)) end - it_behaves_like 'integration creation from instance level' + it_behaves_like 'service creation from a template' end context 'when data are stored in both properties and separated fields' do let(:properties) { data_params.merge(title: title, description: description) } - let(:instance_level_integration) do - create(:jira_service, :without_properties_callback, active: true, instance: true, properties: properties).tap do |service| + let(:template) do + create(:jira_service, :without_properties_callback, active: true, template: true, properties: properties).tap do |service| create(:jira_tracker_data, data_params.merge(service: service)) end end - it_behaves_like 'integration creation from instance level' + it_behaves_like 'service creation from a template' end end end describe "for pushover service" do - let!(:instance_level_integration) do + let!(:service_template) do PushoverService.create( - instance: true, + template: true, properties: { device: 'MyDevice', sound: 'mic', @@ -188,7 +188,7 @@ describe Service do it "has all fields prefilled" do service = project.find_or_initialize_service('pushover') - expect(service.instance).to eq(false) + expect(service.template).to eq(false) expect(service.device).to eq('MyDevice') expect(service.sound).to eq('mic') expect(service.priority).to eq(4) @@ -391,6 +391,14 @@ describe Service do end end + describe '.find_by_template' do + let!(:service) { create(:service, template: true) } + + it 'returns service template' do + expect(described_class.find_by_template).to eq(service) + end + end + describe '#api_field_names' do let(:fake_service) do Class.new(Service) do diff --git a/spec/presenters/snippet_blob_presenter_spec.rb b/spec/presenters/snippet_blob_presenter_spec.rb index 2a113e353c8..92893ec597a 100644 --- a/spec/presenters/snippet_blob_presenter_spec.rb +++ b/spec/presenters/snippet_blob_presenter_spec.rb @@ -18,7 +18,7 @@ describe SnippetBlobPresenter do snippet.file_name = 'test.md' snippet.content = '*foo*' - expect(subject).to eq '<p data-sourcepos="1:1-1:5" dir="auto"><em>foo</em></p>' + expect(subject).to eq '<span id="LC1" class="line" lang="markdown"><span class="ge">*foo*</span></span>' end it 'returns syntax highlighted content' do @@ -33,7 +33,41 @@ describe SnippetBlobPresenter do snippet.file_name = 'test' snippet.content = 'foo' - expect(described_class.new(snippet.blob).highlighted_data).to eq '<span id="LC1" class="line" lang="plaintext">foo</span>' + expect(subject).to eq '<span id="LC1" class="line" lang="plaintext">foo</span>' + end + end + + describe '#plain_highlighted_data' do + let(:snippet) { build(:personal_snippet) } + + subject { described_class.new(snippet.blob).plain_highlighted_data } + + it 'returns nil when the snippet blob is binary' do + allow(snippet.blob).to receive(:binary?).and_return(true) + + expect(subject).to be_nil + end + + it 'returns plain content when snippet file is markup' do + snippet.file_name = 'test.md' + snippet.content = '*foo*' + + expect(subject).to eq '<span id="LC1" class="line" lang="">*foo*</span>' + end + + it 'returns plain syntax content' do + snippet.file_name = 'test.rb' + snippet.content = 'class Foo;end' + + expect(subject) + .to eq '<span id="LC1" class="line" lang="">class Foo;end</span>' + end + + it 'returns plain text highlighted content' do + snippet.file_name = 'test' + snippet.content = 'foo' + + expect(subject).to eq '<span id="LC1" class="line" lang="">foo</span>' end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 9d23556efda..a8e7919dc81 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -15,7 +15,7 @@ describe Projects::CreateService, '#execute' do } end - it 'creates labels on Project creation if there are instance level services' do + it 'creates labels on Project creation if there are templates' do Label.create(title: "bug", template: true) project = create_project(user, opts) @@ -96,7 +96,7 @@ describe Projects::CreateService, '#execute' do end it 'sets invalid service as inactive' do - create(:service, type: 'JiraService', project: nil, instance: true, active: true) + create(:service, type: 'JiraService', project: nil, template: true, active: true) project = create_project(user, opts) service = project.services.first @@ -342,22 +342,22 @@ describe Projects::CreateService, '#execute' do end end - context 'when there is an active instance level service' do + context 'when there is an active service template' do before do - create(:service, project: nil, instance: true, active: true) + create(:service, project: nil, template: true, active: true) end - it 'creates a service from instance level service' do + it 'creates a service from this template' do project = create_project(user, opts) expect(project.services.count).to eq 1 end end - context 'when a bad instance level service is created' do + context 'when a bad service template is created' do it 'sets service to be inactive' do opts[:import_url] = 'http://www.gitlab.com/gitlab-org/gitlab-foss' - create(:service, type: 'DroneCiService', project: nil, instance: true, active: true) + create(:service, type: 'DroneCiService', project: nil, template: true, active: true) project = create_project(user, opts) service = project.services.first diff --git a/spec/services/projects/propagate_instance_level_service_spec.rb b/spec/services/projects/propagate_service_template_spec.rb index a842842a010..2c3effec617 100644 --- a/spec/services/projects/propagate_instance_level_service_spec.rb +++ b/spec/services/projects/propagate_service_template_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' -describe Projects::PropagateInstanceLevelService do +describe Projects::PropagateServiceTemplate do describe '.propagate' do - let!(:instance_level_integration) do + let!(:service_template) do PushoverService.create( - instance: true, + template: true, active: true, properties: { device: 'MyDevice', @@ -22,14 +22,14 @@ describe Projects::PropagateInstanceLevelService do it 'creates services for projects' do expect(project.pushover_service).to be_nil - described_class.propagate(instance_level_integration) + described_class.propagate(service_template) expect(project.reload.pushover_service).to be_present end it 'creates services for a project that has another service' do BambooService.create( - instance: true, + template: true, active: true, project: project, properties: { @@ -42,14 +42,14 @@ describe Projects::PropagateInstanceLevelService do expect(project.pushover_service).to be_nil - described_class.propagate(instance_level_integration) + described_class.propagate(service_template) expect(project.reload.pushover_service).to be_present end it 'does not create the service if it exists already' do other_service = BambooService.create( - instance: true, + template: true, active: true, properties: { bamboo_url: 'http://gitlab.com', @@ -59,17 +59,17 @@ describe Projects::PropagateInstanceLevelService do } ) - Service.build_from_instance(project.id, instance_level_integration).save! - Service.build_from_instance(project.id, other_service).save! + Service.build_from_template(project.id, service_template).save! + Service.build_from_template(project.id, other_service).save! - expect { described_class.propagate(instance_level_integration) } + expect { described_class.propagate(service_template) } .not_to change { Service.count } end - it 'creates the service containing the instance attributes' do - described_class.propagate(instance_level_integration) + it 'creates the service containing the template attributes' do + described_class.propagate(service_template) - expect(project.pushover_service.properties).to eq(instance_level_integration.properties) + expect(project.pushover_service.properties).to eq(service_template.properties) end describe 'bulk update', :use_sql_query_cache do @@ -80,7 +80,7 @@ describe Projects::PropagateInstanceLevelService do project_total.times { create(:project) } - described_class.propagate(instance_level_integration) + described_class.propagate(service_template) end it 'creates services for all projects' do @@ -90,18 +90,18 @@ describe Projects::PropagateInstanceLevelService do describe 'external tracker' do it 'updates the project external tracker' do - instance_level_integration.update!(category: 'issue_tracker', default: false) + service_template.update!(category: 'issue_tracker', default: false) - expect { described_class.propagate(instance_level_integration) } + expect { described_class.propagate(service_template) } .to change { project.reload.has_external_issue_tracker }.to(true) end end describe 'external wiki' do it 'updates the project external tracker' do - instance_level_integration.update!(type: 'ExternalWikiService') + service_template.update!(type: 'ExternalWikiService') - expect { described_class.propagate(instance_level_integration) } + expect { described_class.propagate(service_template) } .to change { project.reload.has_external_wiki }.to(true) end end diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index 0d312575e91..e2d96db02be 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -557,7 +557,7 @@ module KubernetesHelpers end # noinspection RubyStringKeysInHashInspection - def knative_06_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') + def knative_06_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 9) { "apiVersion" => "serving.knative.dev/v1alpha1", "kind" => "Service", "metadata" => @@ -612,12 +612,12 @@ module KubernetesHelpers "url" => "http://#{name}.#{namespace}.#{domain}" }, "environment_scope" => environment, - "cluster_id" => 9, + "cluster_id" => cluster_id, "podcount" => 0 } end # noinspection RubyStringKeysInHashInspection - def knative_07_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') + def knative_07_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 5) { "apiVersion" => "serving.knative.dev/v1alpha1", "kind" => "Service", "metadata" => @@ -664,12 +664,12 @@ module KubernetesHelpers "traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }], "url" => "http://#{name}.#{namespace}.#{domain}" }, "environment_scope" => environment, - "cluster_id" => 5, + "cluster_id" => cluster_id, "podcount" => 0 } end # noinspection RubyStringKeysInHashInspection - def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') + def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 5) { "apiVersion" => "serving.knative.dev/v1alpha1", "kind" => "Service", "metadata" => @@ -716,12 +716,12 @@ module KubernetesHelpers "traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }], "url" => "http://#{name}.#{namespace}.#{domain}" }, "environment_scope" => environment, - "cluster_id" => 5, + "cluster_id" => cluster_id, "podcount" => 0 } end # noinspection RubyStringKeysInHashInspection - def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') + def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production', cluster_id: 8) { "apiVersion" => "serving.knative.dev/v1alpha1", "kind" => "Service", "metadata" => @@ -771,7 +771,7 @@ module KubernetesHelpers "observedGeneration" => 1, "traffic" => [{ "percent" => 100, "revisionName" => "#{name}-58qgr" }] }, "environment_scope" => environment, - "cluster_id" => 8, + "cluster_id" => cluster_id, "podcount" => 0 } end diff --git a/spec/support/shared_examples/workers/pages_domain_cron_worker_shared_examples.rb b/spec/support/shared_examples/workers/pages_domain_cron_worker_shared_examples.rb new file mode 100644 index 00000000000..9e8102aea53 --- /dev/null +++ b/spec/support/shared_examples/workers/pages_domain_cron_worker_shared_examples.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'a pages cronjob scheduling jobs with context' do |scheduled_worker_class| + let(:worker) { described_class.new } + + it 'does not cause extra queries for multiple domains' do + control = ActiveRecord::QueryRecorder.new { worker.perform } + + extra_domain + + expect { worker.perform }.not_to exceed_query_limit(control) + end + + it 'schedules the renewal with a context' do + extra_domain + + worker.perform + + expect(scheduled_worker_class.jobs.last).to include("meta.project" => extra_domain.project.full_path) + end +end diff --git a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb index 10c23cbb6d4..736acc40371 100644 --- a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb +++ b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb @@ -12,7 +12,7 @@ describe PagesDomainSslRenewalCronWorker do end describe '#perform' do - let(:project) { create :project } + let_it_be(:project) { create :project } let!(:domain) { create(:pages_domain, project: project, auto_ssl_enabled: false) } let!(:domain_with_enabled_auto_ssl) { create(:pages_domain, project: project, auto_ssl_enabled: true) } let!(:domain_with_obtained_letsencrypt) do @@ -35,12 +35,16 @@ describe PagesDomainSslRenewalCronWorker do [domain, domain_with_obtained_letsencrypt].each do |domain| - expect(PagesDomainVerificationWorker).not_to receive(:perform_async).with(domain.id) + expect(PagesDomainSslRenewalWorker).not_to receive(:perform_async).with(domain.id) end worker.perform end + it_behaves_like 'a pages cronjob scheduling jobs with context', PagesDomainSslRenewalWorker do + let(:extra_domain) { create(:pages_domain, :with_project, auto_ssl_enabled: true) } + end + shared_examples 'does nothing' do it 'does nothing' do expect(PagesDomainSslRenewalWorker).not_to receive(:perform_async) diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb index 3fb86adee11..6dd6c33f5fe 100644 --- a/spec/workers/pages_domain_verification_cron_worker_spec.rb +++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb @@ -5,9 +5,9 @@ require 'spec_helper' describe PagesDomainVerificationCronWorker do subject(:worker) { described_class.new } - describe '#perform' do + describe '#perform', :sidekiq do let!(:verified) { create(:pages_domain) } - let!(:reverify) { create(:pages_domain, :reverify) } + let!(:reverify) { create(:pages_domain, :reverify, :with_project) } let!(:disabled) { create(:pages_domain, :disabled) } it 'does nothing if the database is read-only' do @@ -26,5 +26,9 @@ describe PagesDomainVerificationCronWorker do worker.perform end + + it_behaves_like 'a pages cronjob scheduling jobs with context', PagesDomainVerificationWorker do + let(:extra_domain) { create(:pages_domain, :reverify, :with_project) } + end end end diff --git a/spec/workers/propagate_instance_level_service_worker_spec.rb b/spec/workers/propagate_instance_level_service_worker_spec.rb deleted file mode 100644 index 6552b198181..00000000000 --- a/spec/workers/propagate_instance_level_service_worker_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe PropagateInstanceLevelServiceWorker do - include ExclusiveLeaseHelpers - - describe '#perform' do - it 'calls the propagate service with the instance level service' do - instance_level_service = PushoverService.create( - instance: true, - active: true, - properties: { - device: 'MyDevice', - sound: 'mic', - priority: 4, - user_key: 'asdf', - api_key: '123456789' - }) - - stub_exclusive_lease("propagate_instance_level_service_worker:#{instance_level_service.id}", - timeout: PropagateInstanceLevelServiceWorker::LEASE_TIMEOUT) - - expect(Projects::PropagateInstanceLevelService) - .to receive(:propagate) - .with(instance_level_service) - - subject.perform(instance_level_service.id) - end - end -end diff --git a/spec/workers/propagate_service_template_worker_spec.rb b/spec/workers/propagate_service_template_worker_spec.rb new file mode 100644 index 00000000000..fb4ced77832 --- /dev/null +++ b/spec/workers/propagate_service_template_worker_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PropagateServiceTemplateWorker do + include ExclusiveLeaseHelpers + + describe '#perform' do + it 'calls the propagate service with the template' do + template = PushoverService.create( + template: true, + active: true, + properties: { + device: 'MyDevice', + sound: 'mic', + priority: 4, + user_key: 'asdf', + api_key: '123456789' + }) + + stub_exclusive_lease("propagate_service_template_worker:#{template.id}", + timeout: PropagateServiceTemplateWorker::LEASE_TIMEOUT) + + expect(Projects::PropagateServiceTemplate) + .to receive(:propagate) + .with(template) + + subject.perform(template.id) + end + end +end |