diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-16 12:09:06 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-16 12:09:06 +0000 |
commit | 0045970352e8729b2797591beb88a7df884d84f4 (patch) | |
tree | b9cd4c5aaaa26ce4a3c944ec5cfdbd7ad44b796d /spec | |
parent | 613868af23d7c0e09210857518895edd6678f5e9 (diff) | |
download | gitlab-ce-0045970352e8729b2797591beb88a7df884d84f4.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
20 files changed, 534 insertions, 1247 deletions
diff --git a/spec/factories/alert_management/http_integrations.rb b/spec/factories/alert_management/http_integrations.rb index 405ec09251f..43cf8b3c6db 100644 --- a/spec/factories/alert_management/http_integrations.rb +++ b/spec/factories/alert_management/http_integrations.rb @@ -19,6 +19,12 @@ FactoryBot.define do endpoint_identifier { 'legacy' } end + trait :prometheus do + type_identifier { :prometheus } + end + initialize_with { new(**attributes) } + + factory :alert_management_prometheus_integration, traits: [:prometheus] end end diff --git a/spec/finders/alert_management/http_integrations_finder_spec.rb b/spec/finders/alert_management/http_integrations_finder_spec.rb index d65de2cdbbd..eb3d24f8653 100644 --- a/spec/finders/alert_management/http_integrations_finder_spec.rb +++ b/spec/finders/alert_management/http_integrations_finder_spec.rb @@ -2,10 +2,12 @@ require 'spec_helper' -RSpec.describe AlertManagement::HttpIntegrationsFinder do +RSpec.describe AlertManagement::HttpIntegrationsFinder, feature_category: :incident_management do let_it_be(:project) { create(:project) } let_it_be_with_reload(:integration) { create(:alert_management_http_integration, project: project ) } let_it_be(:extra_integration) { create(:alert_management_http_integration, project: project ) } + let_it_be(:prometheus_integration) { create(:alert_management_prometheus_integration, :inactive, project: project ) } + let_it_be(:extra_prometheus_integration) { create(:alert_management_prometheus_integration, project: project ) } let_it_be(:alt_project_integration) { create(:alert_management_http_integration) } let(:params) { {} } @@ -14,7 +16,7 @@ RSpec.describe AlertManagement::HttpIntegrationsFinder do subject(:execute) { described_class.new(project, params).execute } context 'empty params' do - it { is_expected.to contain_exactly(integration) } + it { is_expected.to contain_exactly(integration, prometheus_integration) } end context 'endpoint_identifier param given' do @@ -37,7 +39,7 @@ RSpec.describe AlertManagement::HttpIntegrationsFinder do context 'but blank' do let(:params) { { endpoint_identifier: nil } } - it { is_expected.to contain_exactly(integration) } + it { is_expected.to contain_exactly(integration, prometheus_integration) } end end @@ -46,18 +48,34 @@ RSpec.describe AlertManagement::HttpIntegrationsFinder do it { is_expected.to contain_exactly(integration) } - context 'when integration is disabled' do - before do - integration.update!(active: false) - end + context 'but blank' do + let(:params) { { active: nil } } - it { is_expected.to be_empty } + it { is_expected.to contain_exactly(integration, prometheus_integration) } + end + end + + context 'type_identifier param given' do + let(:params) { { type_identifier: extra_integration.type_identifier } } + + it { is_expected.to contain_exactly(integration) } + + context 'matches an unavailable integration' do + let(:params) { { type_identifier: extra_prometheus_integration.type_identifier } } + + it { is_expected.to contain_exactly(prometheus_integration) } + end + + context 'but unknown' do + let(:params) { { type_identifier: :unknown } } + + it { is_expected.to contain_exactly(integration, prometheus_integration) } end context 'but blank' do - let(:params) { { active: nil } } + let(:params) { { type_identifier: nil } } - it { is_expected.to contain_exactly(integration) } + it { is_expected.to contain_exactly(integration, prometheus_integration) } end end diff --git a/spec/frontend/fixtures/pipeline_details.rb b/spec/frontend/fixtures/pipeline_details.rb new file mode 100644 index 00000000000..af9b11b0841 --- /dev/null +++ b/spec/frontend/fixtures/pipeline_details.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "GraphQL Pipeline details", '(JavaScript fixtures)', type: :request, feature_category: :pipeline_composition do + include ApiHelpers + include GraphqlHelpers + include JavaScriptFixturesHelpers + + let_it_be(:namespace) { create(:namespace, name: 'frontend-fixtures') } + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:admin) { project.first_owner } + let_it_be(:commit) { create(:commit, project: project) } + let_it_be(:pipeline) do + create(:ci_pipeline, project: project, sha: commit.id, ref: 'master', user: admin, status: :success) + end + + let_it_be(:build_success) do + create(:ci_build, :dependent, name: 'build_my_app', pipeline: pipeline, stage: 'build', status: :success) + end + + let_it_be(:build_test) { create(:ci_build, :dependent, name: 'test_my_app', pipeline: pipeline, stage: 'test') } + let_it_be(:build_deploy_failed) do + create(:ci_build, :dependent, name: 'deploy_my_app', status: :failed, pipeline: pipeline, stage: 'deploy') + end + + let_it_be(:bridge) { create(:ci_bridge, pipeline: pipeline) } + + let(:pipeline_details_query_path) { 'app/graphql/queries/pipelines/get_pipeline_details.query.graphql' } + + it "pipelines/pipeline_details.json" do + query = get_graphql_query_as_string(pipeline_details_query_path, with_base_path: false) + + post_graphql(query, current_user: admin, variables: { projectPath: project.full_path, iid: pipeline.iid }) + + expect_graphql_errors_to_be_empty + end +end diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index 0799bc87c8c..4bf3a779f00 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -397,6 +397,44 @@ describe('URL utility', () => { }); }); + describe('visitUrl', () => { + let originalLocation; + const mockUrl = 'http://example.com/page'; + + beforeAll(() => { + originalLocation = window.location; + + Object.defineProperty(window, 'location', { + writable: true, + value: new URL(TEST_HOST), + }); + }); + + afterAll(() => { + window.location = originalLocation; + }); + + it('navigates to a page', () => { + urlUtils.visitUrl(mockUrl); + + expect(window.location.href).toBe(mockUrl); + }); + + it('navigates to a new page', () => { + const otherWindow = {}; + + Object.defineProperty(window, 'open', { + writable: true, + value: jest.fn().mockReturnValue(otherWindow), + }); + + urlUtils.visitUrl(mockUrl, true); + + expect(otherWindow.opener).toBe(null); + expect(otherWindow.location).toBe(mockUrl); + }); + }); + describe('updateHistory', () => { const state = { key: 'prop' }; const title = 'TITLE'; diff --git a/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap b/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap deleted file mode 100644 index 724ec7366d3..00000000000 --- a/spec/frontend/pipelines/__snapshots__/utils_spec.js.snap +++ /dev/null @@ -1,471 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DAG visualization parsing utilities generateColumnsFromLayersList matches the snapshot 1`] = ` -Array [ - Object { - "groups": Array [ - Object { - "__typename": "CiGroup", - "id": "4", - "jobs": Array [ - Object { - "__typename": "CiJob", - "id": "6", - "kind": "BUILD", - "name": "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", - "needs": Array [], - "previousStageJobsOrNeeds": Array [], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": Object { - "__typename": "StatusAction", - "buttonTitle": "Retry this job", - "icon": "retry", - "id": "8", - "path": "/root/abcd-dag/-/jobs/1482/retry", - "title": "Retry", - }, - "detailsPath": "/root/abcd-dag/-/jobs/1482", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "7", - "label": "passed", - "tooltip": "passed", - }, - }, - ], - "name": "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", - "size": 1, - "stageName": "build", - "status": Object { - "__typename": "DetailedStatus", - "group": "success", - "icon": "status_success", - "id": "5", - "label": "passed", - }, - }, - Object { - "__typename": "CiGroup", - "id": "9", - "jobs": Array [ - Object { - "__typename": "CiJob", - "id": "11", - "kind": "BUILD", - "name": "build_b", - "needs": Array [], - "previousStageJobsOrNeeds": Array [], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": Object { - "__typename": "StatusAction", - "buttonTitle": "Retry this job", - "icon": "retry", - "id": "13", - "path": "/root/abcd-dag/-/jobs/1515/retry", - "title": "Retry", - }, - "detailsPath": "/root/abcd-dag/-/jobs/1515", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "12", - "label": "passed", - "tooltip": "passed", - }, - }, - ], - "name": "build_b", - "size": 1, - "stageName": "build", - "status": Object { - "__typename": "DetailedStatus", - "group": "success", - "icon": "status_success", - "id": "10", - "label": "passed", - }, - }, - Object { - "__typename": "CiGroup", - "id": "14", - "jobs": Array [ - Object { - "__typename": "CiJob", - "id": "16", - "kind": "BUILD", - "name": "build_c", - "needs": Array [], - "previousStageJobsOrNeeds": Array [], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": Object { - "__typename": "StatusAction", - "buttonTitle": "Retry this job", - "icon": "retry", - "id": "18", - "path": "/root/abcd-dag/-/jobs/1484/retry", - "title": "Retry", - }, - "detailsPath": "/root/abcd-dag/-/jobs/1484", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "17", - "label": "passed", - "tooltip": "passed", - }, - }, - ], - "name": "build_c", - "size": 1, - "stageName": "build", - "status": Object { - "__typename": "DetailedStatus", - "group": "success", - "icon": "status_success", - "id": "15", - "label": "passed", - }, - }, - Object { - "__typename": "CiGroup", - "id": "19", - "jobs": Array [ - Object { - "__typename": "CiJob", - "id": "21", - "kind": "BUILD", - "name": "build_d 1/3", - "needs": Array [], - "previousStageJobsOrNeeds": Array [], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": Object { - "__typename": "StatusAction", - "buttonTitle": "Retry this job", - "icon": "retry", - "id": "23", - "path": "/root/abcd-dag/-/jobs/1485/retry", - "title": "Retry", - }, - "detailsPath": "/root/abcd-dag/-/jobs/1485", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "22", - "label": "passed", - "tooltip": "passed", - }, - }, - Object { - "__typename": "CiJob", - "id": "24", - "kind": "BUILD", - "name": "build_d 2/3", - "needs": Array [], - "previousStageJobsOrNeeds": Array [], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": Object { - "__typename": "StatusAction", - "buttonTitle": "Retry this job", - "icon": "retry", - "id": "26", - "path": "/root/abcd-dag/-/jobs/1486/retry", - "title": "Retry", - }, - "detailsPath": "/root/abcd-dag/-/jobs/1486", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "25", - "label": "passed", - "tooltip": "passed", - }, - }, - Object { - "__typename": "CiJob", - "id": "27", - "kind": "BUILD", - "name": "build_d 3/3", - "needs": Array [], - "previousStageJobsOrNeeds": Array [], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": Object { - "__typename": "StatusAction", - "buttonTitle": "Retry this job", - "icon": "retry", - "id": "29", - "path": "/root/abcd-dag/-/jobs/1487/retry", - "title": "Retry", - }, - "detailsPath": "/root/abcd-dag/-/jobs/1487", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "28", - "label": "passed", - "tooltip": "passed", - }, - }, - ], - "name": "build_d", - "size": 3, - "stageName": "build", - "status": Object { - "__typename": "DetailedStatus", - "group": "success", - "icon": "status_success", - "id": "20", - "label": "passed", - }, - }, - Object { - "__typename": "CiGroup", - "id": "57", - "jobs": Array [ - Object { - "__typename": "CiJob", - "id": "59", - "kind": "BUILD", - "name": "test_c", - "needs": Array [], - "previousStageJobsOrNeeds": Array [], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": null, - "detailsPath": "/root/kinder-pipe/-/pipelines/154", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "60", - "label": null, - "tooltip": null, - }, - }, - ], - "name": "test_c", - "size": 1, - "stageName": "test", - "status": Object { - "__typename": "DetailedStatus", - "group": "success", - "icon": "status_success", - "id": "58", - "label": null, - }, - }, - ], - "id": "layer-0", - "name": "", - "status": Object { - "action": null, - }, - }, - Object { - "groups": Array [ - Object { - "__typename": "CiGroup", - "id": "32", - "jobs": Array [ - Object { - "__typename": "CiJob", - "id": "34", - "kind": "BUILD", - "name": "test_a", - "needs": Array [ - "build_c", - "build_b", - "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", - ], - "previousStageJobsOrNeeds": Array [ - "build_c", - "build_b", - "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", - ], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": Object { - "__typename": "StatusAction", - "buttonTitle": "Retry this job", - "icon": "retry", - "id": "36", - "path": "/root/abcd-dag/-/jobs/1514/retry", - "title": "Retry", - }, - "detailsPath": "/root/abcd-dag/-/jobs/1514", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "35", - "label": "passed", - "tooltip": "passed", - }, - }, - ], - "name": "test_a", - "size": 1, - "stageName": "test", - "status": Object { - "__typename": "DetailedStatus", - "group": "success", - "icon": "status_success", - "id": "33", - "label": "passed", - }, - }, - Object { - "__typename": "CiGroup", - "id": "40", - "jobs": Array [ - Object { - "__typename": "CiJob", - "id": "42", - "kind": "BUILD", - "name": "test_b 1/2", - "needs": Array [ - "build_d 3/3", - "build_d 2/3", - "build_d 1/3", - "build_b", - "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", - ], - "previousStageJobsOrNeeds": Array [ - "build_d 3/3", - "build_d 2/3", - "build_d 1/3", - "build_b", - "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", - ], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": Object { - "__typename": "StatusAction", - "buttonTitle": "Retry this job", - "icon": "retry", - "id": "44", - "path": "/root/abcd-dag/-/jobs/1489/retry", - "title": "Retry", - }, - "detailsPath": "/root/abcd-dag/-/jobs/1489", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "43", - "label": "passed", - "tooltip": "passed", - }, - }, - Object { - "__typename": "CiJob", - "id": "67", - "kind": "BUILD", - "name": "test_b 2/2", - "needs": Array [ - "build_d 3/3", - "build_d 2/3", - "build_d 1/3", - "build_b", - "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", - ], - "previousStageJobsOrNeeds": Array [ - "build_d 3/3", - "build_d 2/3", - "build_d 1/3", - "build_b", - "build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl", - ], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": Object { - "__typename": "StatusAction", - "buttonTitle": "Retry this job", - "icon": "retry", - "id": "51", - "path": "/root/abcd-dag/-/jobs/1490/retry", - "title": "Retry", - }, - "detailsPath": "/root/abcd-dag/-/jobs/1490", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "50", - "label": "passed", - "tooltip": "passed", - }, - }, - ], - "name": "test_b", - "size": 2, - "stageName": "test", - "status": Object { - "__typename": "DetailedStatus", - "group": "success", - "icon": "status_success", - "id": "41", - "label": "passed", - }, - }, - Object { - "__typename": "CiGroup", - "id": "61", - "jobs": Array [ - Object { - "__typename": "CiJob", - "id": "53", - "kind": "BUILD", - "name": "test_d", - "needs": Array [ - "build_b", - ], - "previousStageJobsOrNeeds": Array [ - "build_b", - ], - "scheduledAt": null, - "status": Object { - "__typename": "DetailedStatus", - "action": null, - "detailsPath": "/root/abcd-dag/-/pipelines/153", - "group": "success", - "hasDetails": true, - "icon": "status_success", - "id": "64", - "label": null, - "tooltip": null, - }, - }, - ], - "name": "test_d", - "size": 1, - "stageName": "test", - "status": Object { - "__typename": "DetailedStatus", - "group": "success", - "icon": "status_success", - "id": "62", - "label": null, - }, - }, - ], - "id": "layer-1", - "name": "", - "status": Object { - "action": null, - }, - }, -] -`; diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js index 95207fd59ff..e9bce037800 100644 --- a/spec/frontend/pipelines/graph/graph_component_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import mockPipelineResponse from 'test_fixtures/pipelines/pipeline_details.json'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { LAYER_VIEW, STAGE_VIEW } from '~/pipelines/components/graph/constants'; import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; @@ -7,11 +8,8 @@ import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; import { calculatePipelineLayersInfo } from '~/pipelines/components/graph/utils'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; -import { - generateResponse, - mockPipelineResponse, - pipelineWithUpstreamDownstream, -} from './mock_data'; + +import { generateResponse, pipelineWithUpstreamDownstream } from './mock_data'; describe('graph component', () => { let wrapper; diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js index cc952eac1d7..9599b5e6b7b 100644 --- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js @@ -2,6 +2,7 @@ import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon, GlToggle } from '@gitl import MockAdapter from 'axios-mock-adapter'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import mockPipelineResponse from 'test_fixtures/pipelines/pipeline_details.json'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -26,7 +27,6 @@ import { import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue'; import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.vue'; -import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; import * as Api from '~/pipelines/components/graph_shared/api'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; import * as parsingUtils from '~/pipelines/components/parsing_utils'; @@ -34,7 +34,7 @@ import getPipelineHeaderData from '~/pipelines/graphql/queries/get_pipeline_head import * as sentryUtils from '~/pipelines/utils'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import { mockRunningPipelineHeaderData } from '../mock_data'; -import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data'; +import { mapCallouts, mockCalloutsResponse } from './mock_data'; const defaultProvide = { graphqlResourceEtag: 'frog/amphibirama/etag/', @@ -55,8 +55,6 @@ describe('Pipeline graph wrapper', () => { const findLinksLayer = () => wrapper.findComponent(LinksLayer); const findGraph = () => wrapper.findComponent(PipelineGraph); const findStageColumnTitle = () => wrapper.findByTestId('stage-column-title'); - const findAllStageColumnGroupsInColumn = () => - wrapper.findComponent(StageColumnComponent).findAll('[data-testid="stage-column-group"]'); const findViewSelector = () => wrapper.findComponent(GraphViewSelector); const findViewSelectorToggle = () => findViewSelector().findComponent(GlToggle); const findViewSelectorTrip = () => findViewSelector().findComponent(GlAlert); @@ -316,12 +314,10 @@ describe('Pipeline graph wrapper', () => { }); it('switches between views', async () => { - const groupsInFirstColumn = - mockPipelineResponse.data.project.pipeline.stages.nodes[0].groups.nodes.length; - expect(findAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn); - expect(findStageColumnTitle().text()).toBe('build'); + expect(findStageColumnTitle().text()).toBe('deploy'); + await findViewSelector().vm.$emit('updateViewType', LAYER_VIEW); - expect(findAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn + 1); + expect(findStageColumnTitle().text()).toBe(''); }); @@ -507,9 +503,9 @@ describe('Pipeline graph wrapper', () => { }); describe('with metrics path', () => { - const duration = 875; - const numLinks = 7; - const totalGroups = 8; + const duration = 500; + const numLinks = 3; + const totalGroups = 7; const metricsData = { histograms: [ { name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 }, @@ -559,9 +555,6 @@ describe('Pipeline graph wrapper', () => { createComponentWithApollo({ provide: { metricsPath, - glFeatures: { - pipelineGraphLayersView: true, - }, }, data: { currentViewType: LAYER_VIEW, diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js index 6e4b9498918..bcea140f2dd 100644 --- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js @@ -1,6 +1,7 @@ import { mount, shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; +import mockPipelineResponse from 'test_fixtures/pipelines/pipeline_details.json'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql'; @@ -15,11 +16,8 @@ import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue'; import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue'; import * as parsingUtils from '~/pipelines/components/parsing_utils'; import { LOAD_FAILURE } from '~/pipelines/constants'; -import { - mockPipelineResponse, - pipelineWithUpstreamDownstream, - wrappedPipelineReturn, -} from './mock_data'; + +import { pipelineWithUpstreamDownstream, wrappedPipelineReturn } from './mock_data'; const processedPipeline = pipelineWithUpstreamDownstream(mockPipelineResponse); diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js index 08624cc511d..b012e7f66e1 100644 --- a/spec/frontend/pipelines/graph/mock_data.js +++ b/spec/frontend/pipelines/graph/mock_data.js @@ -5,710 +5,6 @@ import { RETRY_ACTION_TITLE, } from '~/pipelines/components/graph/constants'; -export const mockPipelineResponse = { - data: { - project: { - __typename: 'Project', - id: '1', - pipeline: { - __typename: 'Pipeline', - id: 163, - iid: '22', - complete: true, - usesNeeds: true, - downstream: null, - upstream: null, - userPermissions: { - __typename: 'PipelinePermissions', - updatePipeline: true, - }, - stages: { - __typename: 'CiStageConnection', - nodes: [ - { - __typename: 'CiStage', - id: '2', - name: 'build', - status: { - __typename: 'DetailedStatus', - id: '3', - action: null, - }, - groups: { - __typename: 'CiGroupConnection', - nodes: [ - { - __typename: 'CiGroup', - id: '4', - name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', - size: 1, - status: { - __typename: 'DetailedStatus', - id: '5', - label: 'passed', - group: 'success', - icon: 'status_success', - }, - jobs: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiJob', - id: '6', - kind: BUILD_KIND, - name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '7', - icon: 'status_success', - tooltip: 'passed', - label: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1482', - group: 'success', - action: { - __typename: 'StatusAction', - id: '8', - buttonTitle: 'Retry this job', - icon: 'retry', - path: '/root/abcd-dag/-/jobs/1482/retry', - title: 'Retry', - }, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [], - }, - }, - ], - }, - }, - { - __typename: 'CiGroup', - name: 'build_b', - id: '9', - size: 1, - status: { - __typename: 'DetailedStatus', - id: '10', - label: 'passed', - group: 'success', - icon: 'status_success', - }, - jobs: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiJob', - id: '11', - name: 'build_b', - kind: BUILD_KIND, - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '12', - icon: 'status_success', - tooltip: 'passed', - label: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1515', - group: 'success', - action: { - __typename: 'StatusAction', - id: '13', - buttonTitle: 'Retry this job', - icon: 'retry', - path: '/root/abcd-dag/-/jobs/1515/retry', - title: 'Retry', - }, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [], - }, - }, - ], - }, - }, - { - __typename: 'CiGroup', - id: '14', - name: 'build_c', - size: 1, - status: { - __typename: 'DetailedStatus', - id: '15', - label: 'passed', - group: 'success', - icon: 'status_success', - }, - jobs: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiJob', - id: '16', - name: 'build_c', - kind: BUILD_KIND, - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '17', - icon: 'status_success', - tooltip: 'passed', - label: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1484', - group: 'success', - action: { - __typename: 'StatusAction', - id: '18', - buttonTitle: 'Retry this job', - icon: 'retry', - path: '/root/abcd-dag/-/jobs/1484/retry', - title: 'Retry', - }, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [], - }, - }, - ], - }, - }, - { - __typename: 'CiGroup', - id: '19', - name: 'build_d', - size: 3, - status: { - __typename: 'DetailedStatus', - id: '20', - label: 'passed', - group: 'success', - icon: 'status_success', - }, - jobs: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiJob', - id: '21', - kind: BUILD_KIND, - name: 'build_d 1/3', - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '22', - icon: 'status_success', - tooltip: 'passed', - label: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1485', - group: 'success', - action: { - __typename: 'StatusAction', - id: '23', - buttonTitle: 'Retry this job', - icon: 'retry', - path: '/root/abcd-dag/-/jobs/1485/retry', - title: 'Retry', - }, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [], - }, - }, - { - __typename: 'CiJob', - id: '24', - kind: BUILD_KIND, - name: 'build_d 2/3', - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '25', - icon: 'status_success', - tooltip: 'passed', - label: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1486', - group: 'success', - action: { - __typename: 'StatusAction', - id: '26', - buttonTitle: 'Retry this job', - icon: 'retry', - path: '/root/abcd-dag/-/jobs/1486/retry', - title: 'Retry', - }, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [], - }, - }, - { - __typename: 'CiJob', - id: '27', - kind: BUILD_KIND, - name: 'build_d 3/3', - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '28', - icon: 'status_success', - tooltip: 'passed', - label: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1487', - group: 'success', - action: { - __typename: 'StatusAction', - id: '29', - buttonTitle: 'Retry this job', - icon: 'retry', - path: '/root/abcd-dag/-/jobs/1487/retry', - title: 'Retry', - }, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [], - }, - }, - ], - }, - }, - ], - }, - }, - { - __typename: 'CiStage', - id: '30', - name: 'test', - status: { - __typename: 'DetailedStatus', - id: '31', - action: null, - }, - groups: { - __typename: 'CiGroupConnection', - nodes: [ - { - __typename: 'CiGroup', - id: '32', - name: 'test_a', - size: 1, - status: { - __typename: 'DetailedStatus', - id: '33', - label: 'passed', - group: 'success', - icon: 'status_success', - }, - jobs: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiJob', - id: '34', - kind: BUILD_KIND, - name: 'test_a', - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '35', - icon: 'status_success', - tooltip: 'passed', - label: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1514', - group: 'success', - action: { - __typename: 'StatusAction', - id: '36', - buttonTitle: 'Retry this job', - icon: 'retry', - path: '/root/abcd-dag/-/jobs/1514/retry', - title: 'Retry', - }, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [ - { - __typename: 'CiBuildNeed', - id: '37', - name: 'build_c', - }, - { - __typename: 'CiBuildNeed', - id: '38', - name: 'build_b', - }, - { - __typename: 'CiBuildNeed', - id: '39', - name: - 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', - }, - ], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiBuildNeed', - id: '37', - name: 'build_c', - }, - { - __typename: 'CiBuildNeed', - id: '38', - name: 'build_b', - }, - { - __typename: 'CiBuildNeed', - id: '39', - name: - 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', - }, - ], - }, - }, - ], - }, - }, - { - __typename: 'CiGroup', - id: '40', - name: 'test_b', - size: 2, - status: { - __typename: 'DetailedStatus', - id: '41', - label: 'passed', - group: 'success', - icon: 'status_success', - }, - jobs: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiJob', - id: '42', - kind: BUILD_KIND, - name: 'test_b 1/2', - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '43', - icon: 'status_success', - tooltip: 'passed', - label: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1489', - group: 'success', - action: { - __typename: 'StatusAction', - id: '44', - buttonTitle: 'Retry this job', - icon: 'retry', - path: '/root/abcd-dag/-/jobs/1489/retry', - title: 'Retry', - }, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [ - { - __typename: 'CiBuildNeed', - id: '45', - name: 'build_d 3/3', - }, - { - __typename: 'CiBuildNeed', - id: '46', - name: 'build_d 2/3', - }, - { - __typename: 'CiBuildNeed', - id: '47', - name: 'build_d 1/3', - }, - { - __typename: 'CiBuildNeed', - id: '48', - name: 'build_b', - }, - { - __typename: 'CiBuildNeed', - id: '49', - name: - 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', - }, - ], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiBuildNeed', - id: '45', - name: 'build_d 3/3', - }, - { - __typename: 'CiBuildNeed', - id: '46', - name: 'build_d 2/3', - }, - { - __typename: 'CiBuildNeed', - id: '47', - name: 'build_d 1/3', - }, - { - __typename: 'CiBuildNeed', - id: '48', - name: 'build_b', - }, - { - __typename: 'CiBuildNeed', - id: '49', - name: - 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', - }, - ], - }, - }, - { - __typename: 'CiJob', - id: '67', - kind: BUILD_KIND, - name: 'test_b 2/2', - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '50', - icon: 'status_success', - tooltip: 'passed', - label: 'passed', - hasDetails: true, - detailsPath: '/root/abcd-dag/-/jobs/1490', - group: 'success', - action: { - __typename: 'StatusAction', - id: '51', - buttonTitle: 'Retry this job', - icon: 'retry', - path: '/root/abcd-dag/-/jobs/1490/retry', - title: 'Retry', - }, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [ - { - __typename: 'CiBuildNeed', - id: '52', - name: 'build_d 3/3', - }, - { - __typename: 'CiBuildNeed', - id: '53', - name: 'build_d 2/3', - }, - { - __typename: 'CiBuildNeed', - id: '54', - name: 'build_d 1/3', - }, - { - __typename: 'CiBuildNeed', - id: '55', - name: 'build_b', - }, - { - __typename: 'CiBuildNeed', - id: '56', - name: - 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', - }, - ], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiBuildNeed', - id: '52', - name: 'build_d 3/3', - }, - { - __typename: 'CiBuildNeed', - id: '53', - name: 'build_d 2/3', - }, - { - __typename: 'CiBuildNeed', - id: '54', - name: 'build_d 1/3', - }, - { - __typename: 'CiBuildNeed', - id: '55', - name: 'build_b', - }, - { - __typename: 'CiBuildNeed', - id: '56', - name: - 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', - }, - ], - }, - }, - ], - }, - }, - { - __typename: 'CiGroup', - name: 'test_c', - id: '57', - size: 1, - status: { - __typename: 'DetailedStatus', - id: '58', - label: null, - group: 'success', - icon: 'status_success', - }, - jobs: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiJob', - id: '59', - kind: BUILD_KIND, - name: 'test_c', - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '60', - icon: 'status_success', - tooltip: null, - label: null, - hasDetails: true, - detailsPath: '/root/kinder-pipe/-/pipelines/154', - group: 'success', - action: null, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [], - }, - }, - ], - }, - }, - { - __typename: 'CiGroup', - id: '61', - name: 'test_d', - size: 1, - status: { - id: '62', - __typename: 'DetailedStatus', - label: null, - group: 'success', - icon: 'status_success', - }, - jobs: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiJob', - id: '53', - kind: BUILD_KIND, - name: 'test_d', - scheduledAt: null, - status: { - __typename: 'DetailedStatus', - id: '64', - icon: 'status_success', - tooltip: null, - label: null, - hasDetails: true, - detailsPath: '/root/abcd-dag/-/pipelines/153', - group: 'success', - action: null, - }, - needs: { - __typename: 'CiBuildNeedConnection', - nodes: [ - { - __typename: 'CiBuildNeed', - id: '65', - name: 'build_b', - }, - ], - }, - previousStageJobsOrNeeds: { - __typename: 'CiJobConnection', - nodes: [ - { - __typename: 'CiBuildNeed', - id: '65', - name: 'build_b', - }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - }, - }, -}; - export const downstream = { nodes: [ { diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js index 9d39c86ed5e..88ba84c395a 100644 --- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js +++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js @@ -1,7 +1,9 @@ import { shallowMount } from '@vue/test-utils'; +import mockPipelineResponse from 'test_fixtures/pipelines/pipeline_details.json'; import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; -import { generateResponse, mockPipelineResponse } from '../graph/mock_data'; + +import { generateResponse } from '../graph/mock_data'; describe('links layer component', () => { let wrapper; diff --git a/spec/frontend/pipelines/utils_spec.js b/spec/frontend/pipelines/utils_spec.js index 51e0e0705ff..286d79edc6c 100644 --- a/spec/frontend/pipelines/utils_spec.js +++ b/spec/frontend/pipelines/utils_spec.js @@ -1,3 +1,4 @@ +import mockPipelineResponse from 'test_fixtures/pipelines/pipeline_details.json'; import { createSankey } from '~/pipelines/components/dag/drawing_utils'; import { makeLinksFromNodes, @@ -14,7 +15,7 @@ import { createNodeDict } from '~/pipelines/utils'; import { mockDownstreamPipelinesRest } from '../vue_merge_request_widget/mock_data'; import { mockDownstreamPipelinesGraphql } from '../commit/mock_data'; import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data'; -import { generateResponse, mockPipelineResponse } from './graph/mock_data'; +import { generateResponse } from './graph/mock_data'; describe('DAG visualization parsing utilities', () => { const nodeDict = createNodeDict(mockParsedGraphQLNodes); @@ -152,14 +153,6 @@ describe('DAG visualization parsing utilities', () => { }); }); }); - - /* - Just as a fallback in case multiple functions change, so tests pass - but the implementation moves away from case. - */ - it('matches the snapshot', () => { - expect(columns).toMatchSnapshot(); - }); }); }); diff --git a/spec/lib/api/helpers/packages/npm_spec.rb b/spec/lib/api/helpers/packages/npm_spec.rb index e1316a10fb1..cfb68d2c53e 100644 --- a/spec/lib/api/helpers/packages/npm_spec.rb +++ b/spec/lib/api/helpers/packages/npm_spec.rb @@ -17,20 +17,9 @@ RSpec.describe ::API::Helpers::Packages::Npm, feature_category: :package_registr let_it_be(:project) { create(:project, :public, namespace: namespace) } let_it_be(:package) { create(:npm_package, project: project) } - describe '#endpoint_scope' do - subject { object.endpoint_scope } - - context 'when params includes an id' do - let(:params) { { id: 42, package_name: 'foo' } } - - it { is_expected.to eq(:project) } - end - - context 'when params does not include an id' do - let(:params) { { package_name: 'foo' } } - - it { is_expected.to eq(:instance) } - end + before do + allow(object).to receive(:endpoint_scope).and_return(endpoint_scope) + allow(object).to receive(:current_user).and_return(user) end describe '#finder_for_endpoint_scope' do @@ -40,6 +29,7 @@ RSpec.describe ::API::Helpers::Packages::Npm, feature_category: :package_registr context 'when called with project scope' do let(:params) { { id: project.id } } + let(:endpoint_scope) { :project } it 'returns a PackageFinder for project scope' do expect(::Packages::Npm::PackageFinder).to receive(:new).with(package_name, project: project) @@ -50,6 +40,7 @@ RSpec.describe ::API::Helpers::Packages::Npm, feature_category: :package_registr context 'when called with instance scope' do let(:params) { { package_name: package_name } } + let(:endpoint_scope) { :instance } it 'returns a PackageFinder for namespace scope' do expect(::Packages::Npm::PackageFinder).to receive(:new).with(package_name, namespace: group) @@ -57,6 +48,17 @@ RSpec.describe ::API::Helpers::Packages::Npm, feature_category: :package_registr subject end end + + context 'when called with group scope' do + let(:params) { { id: group.id } } + let(:endpoint_scope) { :group } + + it 'returns a PackageFinder for group scope' do + expect(::Packages::Npm::PackageFinder).to receive(:new).with(package_name, namespace: group) + + subject + end + end end describe '#project_id_or_nil' do @@ -64,11 +66,21 @@ RSpec.describe ::API::Helpers::Packages::Npm, feature_category: :package_registr context 'when called with project scope' do let(:params) { { id: project.id } } + let(:endpoint_scope) { :project } it { is_expected.to eq(project.id) } end - context 'when called with namespace scope' do + context 'when called with group scope' do + let(:params) { { id: group.id, package_name: package.name } } + let(:endpoint_scope) { :group } + + it { is_expected.to eq(project.id) } + end + + context 'when called with instance scope' do + let(:endpoint_scope) { :instance } + context 'when given an unscoped name' do let(:params) { { package_name: 'foo' } } diff --git a/spec/migrations/add_type_to_http_integrations_spec.rb b/spec/migrations/add_type_to_http_integrations_spec.rb new file mode 100644 index 00000000000..8238c1594dc --- /dev/null +++ b/spec/migrations/add_type_to_http_integrations_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddTypeToHttpIntegrations, feature_category: :incident_management do + let(:integrations) { table(:alert_management_http_integrations) } + + it 'correctly migrates up and down' do + reversible_migration do |migration| + migration.before -> { + expect(integrations.column_names).not_to include('type_identifier') + } + + migration.after -> { + integrations.reset_column_information + expect(integrations.column_names).to include('type_identifier') + } + end + end +end diff --git a/spec/models/alert_management/http_integration_spec.rb b/spec/models/alert_management/http_integration_spec.rb index b453b3a82e0..606b53aeacd 100644 --- a/spec/models/alert_management/http_integration_spec.rb +++ b/spec/models/alert_management/http_integration_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe AlertManagement::HttpIntegration do +RSpec.describe AlertManagement::HttpIntegration, feature_category: :incident_management do include ::Gitlab::Routing.url_helpers let_it_be(:project) { create(:project) } @@ -21,6 +21,7 @@ RSpec.describe AlertManagement::HttpIntegration do describe 'validations' do it { is_expected.to validate_presence_of(:project) } it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_presence_of(:type_identifier) } it { is_expected.to validate_length_of(:name).is_at_most(255) } context 'when active' do @@ -86,6 +87,66 @@ RSpec.describe AlertManagement::HttpIntegration do end end + describe 'scopes' do + let_it_be(:integration_1) { create(:alert_management_http_integration) } + let_it_be(:integration_2) { create(:alert_management_http_integration, :inactive, project: project) } + let_it_be(:integration_3) { create(:alert_management_http_integration, :prometheus, project: project) } + let_it_be(:integration_4) { create(:alert_management_http_integration, :legacy, :inactive) } + + describe '.for_endpoint_identifier' do + let(:identifier) { integration_1.endpoint_identifier } + + subject { described_class.for_endpoint_identifier(identifier) } + + it { is_expected.to contain_exactly(integration_1) } + end + + describe '.for_type' do + let(:type) { :prometheus } + + subject { described_class.for_type(type) } + + it { is_expected.to contain_exactly(integration_3) } + end + + describe '.for_project' do + let(:project) { integration_2.project } + + subject { described_class.for_project(project) } + + it { is_expected.to contain_exactly(integration_2, integration_3) } + + context 'with project_ids array' do + let(:project) { [integration_1.project_id] } + + it { is_expected.to contain_exactly(integration_1) } + end + end + + describe '.active' do + subject { described_class.active } + + it { is_expected.to contain_exactly(integration_1, integration_3) } + end + + describe '.legacy' do + subject { described_class.legacy } + + it { is_expected.to contain_exactly(integration_4) } + end + + describe '.ordered_by_type_and_id' do + before do + # Rearrange cache by saving to avoid false-positives + integration_2.touch + end + + subject { described_class.ordered_by_type_and_id } + + it { is_expected.to eq([integration_1, integration_2, integration_4, integration_3]) } + end + end + describe 'before validation' do describe '#ensure_payload_example_not_nil' do subject(:integration) { build(:alert_management_http_integration, payload_example: payload_example) } @@ -230,5 +291,33 @@ RSpec.describe AlertManagement::HttpIntegration do ) end end + + context 'for a prometheus integration' do + let(:integration) { build(:alert_management_http_integration, :prometheus) } + + it do + is_expected.to eq( + project_alert_http_integration_url( + integration.project, + 'datadog', + integration.endpoint_identifier, + format: :json + ) + ) + end + + context 'for a legacy integration' do + let(:integration) { build(:alert_management_http_integration, :prometheus, :legacy) } + + it do + is_expected.to eq( + notify_project_prometheus_alerts_url( + integration.project, + format: :json + ) + ) + end + end + end end end diff --git a/spec/requests/api/npm_group_packages_spec.rb b/spec/requests/api/npm_group_packages_spec.rb new file mode 100644 index 00000000000..888ce548e6d --- /dev/null +++ b/spec/requests/api/npm_group_packages_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::NpmGroupPackages, feature_category: :package_registry do + using RSpec::Parameterized::TableSyntax + + include_context 'npm api setup' + + describe 'GET /api/v4/groups/:id/-/packages/npm/*package_name' do + let(:url) { api("/groups/#{group.id}/-/packages/npm/#{package_name}") } + + it_behaves_like 'handling get metadata requests', scope: :group + + context 'with a duplicate package name in another project' do + subject { get(url) } + + before do + group.add_developer(user) + end + + let_it_be(:project2) { create(:project, :public, namespace: namespace) } + let_it_be(:package2) do + create(:npm_package, + project: project2, + name: "@#{group.path}/scoped_package", + version: '1.2.0') + end + + it 'includes all matching package versions in the response' do + subject + + expect(json_response['versions'].keys).to match_array([package.version, package2.version]) + end + + context 'with the feature flag disabled' do + before do + stub_feature_flags(npm_allow_packages_in_multiple_projects: false) + end + + it 'returns matching package versions from only one project' do + subject + + expect(json_response['versions'].keys).to match_array([package2.version]) + end + end + end + + context 'with mixed group and project visibilities' do + subject { get(url, headers: headers) } + + where(:auth, :group_visibility, :project_visibility, :user_role, :expected_status) do + nil | :public | :public | nil | :ok + nil | :public | :internal | nil | :not_found + nil | :public | :private | nil | :not_found + nil | :internal | :internal | nil | :not_found + nil | :internal | :private | nil | :not_found + nil | :private | :private | nil | :not_found + + :oauth | :public | :public | :guest | :ok + :oauth | :public | :internal | :guest | :ok + :oauth | :public | :private | :guest | :forbidden + :oauth | :internal | :internal | :guest | :ok + :oauth | :internal | :private | :guest | :forbidden + :oauth | :private | :private | :guest | :forbidden + :oauth | :public | :public | :reporter | :ok + :oauth | :public | :internal | :reporter | :ok + :oauth | :public | :private | :reporter | :ok + :oauth | :internal | :internal | :reporter | :ok + :oauth | :internal | :private | :reporter | :ok + :oauth | :private | :private | :reporter | :ok + + :personal_access_token | :public | :public | :guest | :ok + :personal_access_token | :public | :internal | :guest | :ok + :personal_access_token | :public | :private | :guest | :forbidden + :personal_access_token | :internal | :internal | :guest | :ok + :personal_access_token | :internal | :private | :guest | :forbidden + :personal_access_token | :private | :private | :guest | :forbidden + :personal_access_token | :public | :public | :reporter | :ok + :personal_access_token | :public | :internal | :reporter | :ok + :personal_access_token | :public | :private | :reporter | :ok + :personal_access_token | :internal | :internal | :reporter | :ok + :personal_access_token | :internal | :private | :reporter | :ok + :personal_access_token | :private | :private | :reporter | :ok + + :job_token | :public | :public | :developer | :ok + :job_token | :public | :internal | :developer | :ok + :job_token | :public | :private | :developer | :ok + :job_token | :internal | :internal | :developer | :ok + :job_token | :internal | :private | :developer | :ok + :job_token | :private | :private | :developer | :ok + + :deploy_token | :public | :public | nil | :ok + :deploy_token | :public | :internal | nil | :ok + :deploy_token | :public | :private | nil | :ok + :deploy_token | :internal | :internal | nil | :ok + :deploy_token | :internal | :private | nil | :ok + :deploy_token | :private | :private | nil | :ok + end + + with_them do + let(:headers) do + case auth + when :oauth + build_token_auth_header(token.plaintext_token) + when :personal_access_token + build_token_auth_header(personal_access_token.token) + when :job_token + build_token_auth_header(job.token) + when :deploy_token + build_token_auth_header(deploy_token.token) + else + {} + end + end + + before do + project.update!(visibility: project_visibility.to_s) + project.send("add_#{user_role}", user) if user_role + group.update!(visibility: group_visibility.to_s) + group.send("add_#{user_role}", user) if user_role + end + + it_behaves_like 'returning response status', params[:expected_status] + end + end + + context 'when user is a reporter of project but is not a direct member of group' do + subject { get(url, headers: headers) } + + where(:group_visibility, :project_visibility, :expected_status) do + :public | :public | :ok + :public | :internal | :ok + :public | :private | :ok + :internal | :internal | :ok + :internal | :private | :ok + :private | :private | :ok + end + + with_them do + let(:headers) { build_token_auth_header(personal_access_token.token) } + + before do + project.update!(visibility: project_visibility.to_s) + project.add_reporter(user) + + group.update!(visibility: group_visibility.to_s) + end + + it_behaves_like 'returning response status', params[:expected_status] + end + end + end + + describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do + let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags") } + + subject { get(url) } + + it_behaves_like 'returning response status', :not_found + end + + describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do + let(:tag_name) { 'test' } + let(:headers) { build_token_auth_header(personal_access_token.token) } + let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") } + + subject { put(url, headers: headers) } + + it_behaves_like 'returning response status', :not_found + end + + describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do + let(:tag_name) { 'test' } + let(:headers) { build_token_auth_header(personal_access_token.token) } + let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") } + + subject { delete(url, headers: headers) } + + it_behaves_like 'returning response status', :not_found + end + + describe 'POST /api/v4/groups/:id/-/packages/npm/-/npm/v1/security/advisories/bulk' do + let(:url) { api("/groups/#{group.id}/-/packages/npm/-/npm/v1/security/advisories/bulk") } + + subject { post(url) } + + it_behaves_like 'returning response status', :not_found + end + + describe 'POST /api/v4/groups/:id/-/packages/npm/-/npm/v1/security/audits/quick' do + let(:url) { api("/groups/#{group.id}/-/packages/npm/-/npm/v1/security/audits/quick") } + + subject { post(url) } + + it_behaves_like 'returning response status', :not_found + end +end diff --git a/spec/services/alert_management/http_integrations/create_service_spec.rb b/spec/services/alert_management/http_integrations/create_service_spec.rb index 5200ec27dd1..bced09044eb 100644 --- a/spec/services/alert_management/http_integrations/create_service_spec.rb +++ b/spec/services/alert_management/http_integrations/create_service_spec.rb @@ -38,12 +38,6 @@ RSpec.describe AlertManagement::HttpIntegrations::CreateService, feature_categor it_behaves_like 'error response', 'You have insufficient permissions to create an HTTP integration for this project' end - context 'when an integration already exists' do - let_it_be(:existing_integration) { create(:alert_management_http_integration, project: project) } - - it_behaves_like 'error response', 'Multiple HTTP integrations are not supported for this project' - end - context 'when an error occurs during update' do it_behaves_like 'error response', "Name can't be blank" end @@ -61,6 +55,38 @@ RSpec.describe AlertManagement::HttpIntegrations::CreateService, feature_categor expect(integration.token).to be_present expect(integration.endpoint_identifier).to be_present end + + context 'with an existing HTTP integration' do + let_it_be(:http_integration) { create(:alert_management_http_integration, project: project) } + + it_behaves_like 'error response', 'Multiple integrations of a single type are not supported for this project' + + context 'when creating a different type of integration' do + let(:params) { { type_identifier: :prometheus, name: 'Prometheus' } } + + it 'is successful' do + expect(response).to be_success + expect(response.payload[:integration]).to be_a(::AlertManagement::HttpIntegration) + end + end + end + + context 'with an existing Prometheus integration' do + let_it_be(:http_integration) { create(:alert_management_prometheus_integration, project: project) } + + context 'when creating a different type of integration' do + it 'is successful' do + expect(response).to be_success + expect(response.payload[:integration]).to be_a(::AlertManagement::HttpIntegration) + end + end + + context 'when creating the same time of integration' do + let(:params) { { type_identifier: :prometheus, name: 'Prometheus' } } + + it_behaves_like 'error response', 'Multiple integrations of a single type are not supported for this project' + end + end end end end diff --git a/spec/services/alert_management/http_integrations/destroy_service_spec.rb b/spec/services/alert_management/http_integrations/destroy_service_spec.rb index a8e9746cb85..e3d9ddfbad8 100644 --- a/spec/services/alert_management/http_integrations/destroy_service_spec.rb +++ b/spec/services/alert_management/http_integrations/destroy_service_spec.rb @@ -47,6 +47,13 @@ RSpec.describe AlertManagement::HttpIntegrations::DestroyService, feature_catego it_behaves_like 'error response', 'Name cannot be removed' end + context 'when destroying a legacy Prometheus integration' do + let_it_be(:existing_integration) { create(:alert_management_prometheus_integration, :legacy, project: project) } + let!(:integration) { existing_integration } + + it_behaves_like 'error response', 'Legacy Prometheus integrations cannot currently be removed' + end + it 'successfully returns the integration' do expect(response).to be_success diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb index 0feac6c3e72..24affa45aa5 100644 --- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb +++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Projects::Prometheus::Alerts::NotifyService, feature_category: :metrics do +RSpec.describe Projects::Prometheus::Alerts::NotifyService, feature_category: :incident_management do include PrometheusHelpers using RSpec::Parameterized::TableSyntax @@ -163,6 +163,24 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService, feature_category: :m raise "invalid result: #{result.inspect}" end end + + context 'with simultaneous manual configuration' do + let_it_be(:integration) { create(:alert_management_prometheus_integration, :legacy, project: project) } + let_it_be(:old_prometheus_integration) { create(:prometheus_integration, project: project) } + let_it_be(:alerting_setting) { create(:project_alerting_setting, project: project, token: integration.token) } + + subject { service.execute(integration.token, integration) } + + it_behaves_like 'processes one firing and one resolved prometheus alerts' + + context 'when HTTP integration is inactive' do + before do + integration.update!(active: false) + end + + it_behaves_like 'alerts service responds with an error and takes no actions', :unauthorized + end + end end context 'incident settings' do diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb index 403456fa48e..417bf4366c5 100644 --- a/spec/support/helpers/javascript_fixtures_helpers.rb +++ b/spec/support/helpers/javascript_fixtures_helpers.rb @@ -46,9 +46,8 @@ module JavaScriptFixturesHelpers # # query_path - file path to the GraphQL query, relative to `app/assets/javascripts`. # ee - boolean, when true `query_path` will be looked up in `/ee`. - def get_graphql_query_as_string(query_path, ee: false) - base = (ee ? 'ee/' : '') + 'app/assets/javascripts' - + def get_graphql_query_as_string(query_path, ee: false, with_base_path: true) + base = (ee ? 'ee/' : '') + (with_base_path ? 'app/assets/javascripts' : '') path = Rails.root / base / query_path queries = Gitlab::Graphql::Queries.find(path) if queries.length == 1 diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb index f53532d00d7..d1712a3c02a 100644 --- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb @@ -259,8 +259,13 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project| before do project.send("add_#{user_role}", user) if user_role project.update!(visibility: visibility.to_s) + + group.send("add_#{user_role}", user) if user_role && scope == :group + group.update!(visibility: visibility.to_s) if scope == :group + package.update!(name: package_name) unless package_name == 'non-existing-package' - if scope == :instance + + if %i[instance group].include?(scope) allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward) else allow_fetch_cascade_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward) @@ -280,6 +285,8 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project| end end + status = :not_found if scope == :group && params[:package_name_type] == :non_existing && !params[:request_forward] + it_behaves_like example_name, status: status end end @@ -300,6 +307,7 @@ RSpec.shared_examples 'handling get metadata requests' do |scope: :project| let(:headers) { build_token_auth_header(personal_access_token.token) } before do + group.add_developer(user) if scope == :group project.add_developer(user) end |