Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-07-27 21:07:34 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-27 21:07:34 +0300
commit7dd130e2cae40514f02b02922251b62302f2fdd5 (patch)
treefd2f91802a2dd2659b72d3767ea9a00786d7900b /spec
parent9979d2afd66e12d938ea55372dcf2d8105b5eaca (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/components/projects/ml/models_index_component_spec.rb41
-rw-r--r--spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js170
-rw-r--r--spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js73
-rw-r--r--spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js90
-rw-r--r--spec/frontend/usage_quotas/storage/components/usage_graph_spec.js12
-rw-r--r--spec/frontend/usage_quotas/storage/mock_data.js89
-rw-r--r--spec/frontend/usage_quotas/storage/utils_spec.js67
-rw-r--r--spec/lib/gitlab/audit/auditor_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/interpolation/block_spec.rb77
-rw-r--r--spec/lib/gitlab/ci/interpolation/functions/base_spec.rb23
-rw-r--r--spec/lib/gitlab/ci/interpolation/functions/truncate_spec.rb35
-rw-r--r--spec/lib/gitlab/ci/interpolation/functions_stack_spec.rb39
-rw-r--r--spec/presenters/ml/model_presenter_spec.rb43
-rw-r--r--spec/presenters/ml/models_index_presenter_spec.rb33
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_spec.rb10
-rw-r--r--spec/requests/projects/ml/models_controller_spec.rb7
16 files changed, 453 insertions, 400 deletions
diff --git a/spec/components/projects/ml/models_index_component_spec.rb b/spec/components/projects/ml/models_index_component_spec.rb
new file mode 100644
index 00000000000..e4599cc5eec
--- /dev/null
+++ b/spec/components/projects/ml/models_index_component_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Projects::Ml::ModelsIndexComponent, type: :component, feature_category: :mlops do
+ let_it_be(:project) { build_stubbed(:project) }
+ let_it_be(:model1) { build_stubbed(:ml_models, :with_latest_version_and_package, project: project) }
+ let_it_be(:model2) { build_stubbed(:ml_models, project: project) }
+ let_it_be(:models) { [model1, model2] }
+
+ subject(:component) do
+ described_class.new(models: models)
+ end
+
+ describe 'rendered' do
+ let(:element) { page.find("#js-index-ml-models") }
+
+ before do
+ render_inline component
+ end
+
+ it 'renders element with view_model' do
+ element = page.find("#js-index-ml-models")
+
+ expect(Gitlab::Json.parse(element['data-view-model'])).to eq({
+ 'models' => [
+ {
+ 'name' => model1.name,
+ 'version' => model1.latest_version.version,
+ 'path' => "/#{project.full_path}/-/packages/#{model1.latest_version.package_id}"
+ },
+ {
+ 'name' => model2.name,
+ 'version' => nil,
+ 'path' => nil
+ }
+ ]
+ })
+ end
+ end
+end
diff --git a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
index 576263d5418..ca5f80f331c 100644
--- a/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
+++ b/spec/frontend/ci/pipeline_editor/pipeline_editor_home_spec.js
@@ -1,19 +1,19 @@
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { GlButton, GlDrawer, GlModal } from '@gitlab/ui';
+import { GlButton, GlModal } from '@gitlab/ui';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
-import CiEditorHeader from '~/ci/pipeline_editor/components/editor/ci_editor_header.vue';
import CommitSection from '~/ci/pipeline_editor/components/commit/commit_section.vue';
import PipelineEditorDrawer from '~/ci/pipeline_editor/components/drawer/pipeline_editor_drawer.vue';
import JobAssistantDrawer from '~/ci/pipeline_editor/components/job_assistant_drawer/job_assistant_drawer.vue';
import PipelineEditorFileNav from '~/ci/pipeline_editor/components/file_nav/pipeline_editor_file_nav.vue';
import PipelineEditorFileTree from '~/ci/pipeline_editor/components/file_tree/container.vue';
-import BranchSwitcher from '~/ci/pipeline_editor/components/file_nav/branch_switcher.vue';
import PipelineEditorHeader from '~/ci/pipeline_editor/components/header/pipeline_editor_header.vue';
import PipelineEditorTabs from '~/ci/pipeline_editor/components/pipeline_editor_tabs.vue';
import {
CREATE_TAB,
+ EDITOR_APP_DRAWER_HELP,
+ EDITOR_APP_DRAWER_JOB_ASSISTANT,
+ EDITOR_APP_DRAWER_NONE,
FILE_TREE_DISPLAY_KEY,
VALIDATE_TAB,
MERGED_TAB,
@@ -29,10 +29,9 @@ jest.mock('~/lib/utils/common_utils');
describe('Pipeline editor home wrapper', () => {
let wrapper;
- const createComponent = ({ props = {}, glFeatures = {}, data = {}, stubs = {} } = {}) => {
+ const createComponent = ({ props = {}, glFeatures = {}, stubs = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(PipelineEditorHome, {
- data: () => data,
propsData: {
ciConfigData: mockLintResponse,
ciFileContent: mockCiYml,
@@ -53,7 +52,6 @@ describe('Pipeline editor home wrapper', () => {
);
};
- const findBranchSwitcher = () => wrapper.findComponent(BranchSwitcher);
const findCommitSection = () => wrapper.findComponent(CommitSection);
const findFileNav = () => wrapper.findComponent(PipelineEditorFileNav);
const findModal = () => wrapper.findComponent(GlModal);
@@ -63,8 +61,16 @@ describe('Pipeline editor home wrapper', () => {
const findPipelineEditorHeader = () => wrapper.findComponent(PipelineEditorHeader);
const findPipelineEditorTabs = () => wrapper.findComponent(PipelineEditorTabs);
const findFileTreeBtn = () => wrapper.findByTestId('file-tree-toggle');
- const findHelpBtn = () => wrapper.findByTestId('drawer-toggle');
- const findJobAssistantBtn = () => wrapper.findByTestId('job-assistant-drawer-toggle');
+
+ const clickHelpBtn = async () => {
+ await findPipelineEditorDrawer().vm.$emit('switch-drawer', EDITOR_APP_DRAWER_HELP);
+ };
+ const clickJobAssistantBtn = async () => {
+ await findJobAssistantDrawer().vm.$emit('switch-drawer', EDITOR_APP_DRAWER_JOB_ASSISTANT);
+ };
+ const closeDrawer = async (finder) => {
+ await finder().vm.$emit('switch-drawer', EDITOR_APP_DRAWER_NONE);
+ };
afterEach(() => {
localStorage.clear();
@@ -103,11 +109,9 @@ describe('Pipeline editor home wrapper', () => {
});
});
describe('when `showSwitchBranchModal` value is true', () => {
- beforeEach(() => {
- createComponent({
- data: { showSwitchBranchModal: true },
- stubs: { PipelineEditorFileNav },
- });
+ beforeEach(async () => {
+ createComponent();
+ await findFileNav().vm.$emit('select-branch');
});
it('is visible', () => {
@@ -115,11 +119,11 @@ describe('Pipeline editor home wrapper', () => {
});
it('pass down `shouldLoadNewBranch` to the branch switcher when primary is selected', async () => {
- expect(findBranchSwitcher().props('shouldLoadNewBranch')).toBe(false);
+ expect(findFileNav().props('shouldLoadNewBranch')).toBe(false);
await findModal().vm.$emit('primary');
- expect(findBranchSwitcher().props('shouldLoadNewBranch')).toBe(true);
+ expect(findFileNav().props('shouldLoadNewBranch')).toBe(true);
});
it('closes the modal when secondary action is selected', async () => {
@@ -148,9 +152,7 @@ describe('Pipeline editor home wrapper', () => {
async ({ tab, shouldShow }) => {
expect(findCommitSection().exists()).toBe(true);
- findPipelineEditorTabs().vm.$emit('set-current-tab', tab);
-
- await nextTick();
+ await findPipelineEditorTabs().vm.$emit('set-current-tab', tab);
expect(findCommitSection().isVisible()).toBe(shouldShow);
},
@@ -159,12 +161,10 @@ describe('Pipeline editor home wrapper', () => {
it('shows the commit form again when coming back to the create tab', async () => {
expect(findCommitSection().isVisible()).toBe(true);
- findPipelineEditorTabs().vm.$emit('set-current-tab', MERGED_TAB);
- await nextTick();
+ await findPipelineEditorTabs().vm.$emit('set-current-tab', MERGED_TAB);
expect(findCommitSection().isVisible()).toBe(false);
- findPipelineEditorTabs().vm.$emit('set-current-tab', CREATE_TAB);
- await nextTick();
+ await findPipelineEditorTabs().vm.$emit('set-current-tab', CREATE_TAB);
expect(findCommitSection().isVisible()).toBe(true);
});
@@ -195,7 +195,9 @@ describe('Pipeline editor home wrapper', () => {
describe('when "walkthrough-popover-cta-clicked" is emitted from pipeline editor tabs', () => {
it('passes down `scrollToCommitForm=true` to commit section', async () => {
expect(findCommitSection().props('scrollToCommitForm')).toBe(false);
+
await findPipelineEditorTabs().vm.$emit('walkthrough-popover-cta-clicked');
+
expect(findCommitSection().props('scrollToCommitForm')).toBe(true);
});
});
@@ -204,6 +206,7 @@ describe('Pipeline editor home wrapper', () => {
it('passes down `scrollToCommitForm=false` to commit section', async () => {
await findPipelineEditorTabs().vm.$emit('walkthrough-popover-cta-clicked');
expect(findCommitSection().props('scrollToCommitForm')).toBe(true);
+
await findCommitSection().vm.$emit('scrolled-to-commit-form');
expect(findCommitSection().props('scrollToCommitForm')).toBe(false);
});
@@ -211,133 +214,49 @@ describe('Pipeline editor home wrapper', () => {
});
describe('help drawer', () => {
- const clickHelpBtn = async () => {
- findHelpBtn().vm.$emit('click');
- await nextTick();
- };
-
- it('hides the drawer by default', () => {
+ beforeEach(() => {
createComponent();
+ });
+ it('hides the drawer by default', () => {
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
});
it('toggles the drawer on button click', async () => {
- createComponent({
- stubs: {
- CiEditorHeader,
- GlButton,
- GlDrawer,
- PipelineEditorTabs,
- PipelineEditorDrawer,
- },
- });
-
- await clickHelpBtn();
-
- expect(findPipelineEditorDrawer().props('isVisible')).toBe(true);
-
- await clickHelpBtn();
-
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
- });
-
- it("closes the drawer through the drawer's close button", async () => {
- createComponent({
- stubs: {
- CiEditorHeader,
- GlButton,
- GlDrawer,
- PipelineEditorTabs,
- PipelineEditorDrawer,
- },
- });
await clickHelpBtn();
-
expect(findPipelineEditorDrawer().props('isVisible')).toBe(true);
- findPipelineEditorDrawer().findComponent(GlDrawer).vm.$emit('close');
- await nextTick();
-
+ await closeDrawer(findPipelineEditorDrawer);
expect(findPipelineEditorDrawer().props('isVisible')).toBe(false);
});
});
describe('job assistant drawer', () => {
- const clickHelpBtn = async () => {
- findHelpBtn().vm.$emit('click');
- await nextTick();
- };
- const clickJobAssistantBtn = async () => {
- findJobAssistantBtn().vm.$emit('click');
- await nextTick();
- };
-
- const stubs = {
- CiEditorHeader,
- GlButton,
- GlDrawer,
- PipelineEditorTabs,
- JobAssistantDrawer,
- };
-
- it('hides the job assistant drawer by default', () => {
+ beforeEach(() => {
createComponent({
glFeatures: {
ciJobAssistantDrawer: true,
},
});
+ });
+ it('hides the job assistant drawer by default', () => {
expect(findJobAssistantDrawer().props('isVisible')).toBe(false);
});
it('toggles the job assistant drawer on button click', async () => {
- createComponent({
- stubs,
- glFeatures: {
- ciJobAssistantDrawer: true,
- },
- });
-
- await clickJobAssistantBtn();
-
- expect(findJobAssistantDrawer().props('isVisible')).toBe(true);
-
- await clickJobAssistantBtn();
-
expect(findJobAssistantDrawer().props('isVisible')).toBe(false);
- });
-
- it("closes the job assistant drawer through the drawer's close button", async () => {
- createComponent({
- stubs,
- glFeatures: {
- ciJobAssistantDrawer: true,
- },
- });
await clickJobAssistantBtn();
-
expect(findJobAssistantDrawer().props('isVisible')).toBe(true);
- findJobAssistantDrawer().findComponent(GlDrawer).vm.$emit('close');
- await nextTick();
-
+ await closeDrawer(findJobAssistantDrawer);
expect(findJobAssistantDrawer().props('isVisible')).toBe(false);
});
it('covers helper drawer when opened last', async () => {
- createComponent({
- stubs: {
- ...stubs,
- PipelineEditorDrawer,
- },
- glFeatures: {
- ciJobAssistantDrawer: true,
- },
- });
-
await clickHelpBtn();
await clickJobAssistantBtn();
@@ -348,16 +267,6 @@ describe('Pipeline editor home wrapper', () => {
});
it('covered by helper drawer when opened first', async () => {
- createComponent({
- stubs: {
- ...stubs,
- PipelineEditorDrawer,
- },
- glFeatures: {
- ciJobAssistantDrawer: true,
- },
- });
-
await clickJobAssistantBtn();
await clickHelpBtn();
@@ -370,8 +279,7 @@ describe('Pipeline editor home wrapper', () => {
describe('file tree', () => {
const toggleFileTree = async () => {
- findFileTreeBtn().vm.$emit('click');
- await nextTick();
+ await findFileTreeBtn().vm.$emit('click');
};
describe('button toggle', () => {
@@ -412,9 +320,7 @@ describe('Pipeline editor home wrapper', () => {
describe('when file tree display state is saved in local storage', () => {
beforeEach(() => {
localStorage.setItem(FILE_TREE_DISPLAY_KEY, 'true');
- createComponent({
- stubs: { PipelineEditorFileNav },
- });
+ createComponent();
});
it('shows the file tree by default', () => {
@@ -424,9 +330,7 @@ describe('Pipeline editor home wrapper', () => {
describe('when file tree display state is not saved in local storage', () => {
beforeEach(() => {
- createComponent({
- stubs: { PipelineEditorFileNav },
- });
+ createComponent();
});
it('hides the file tree by default', () => {
diff --git a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js b/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
index 1a200090805..88ab51cf135 100644
--- a/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/project_storage_app_spec.js
@@ -1,16 +1,24 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ProjectStorageApp from '~/usage_quotas/storage/components/project_storage_app.vue';
import UsageGraph from '~/usage_quotas/storage/components/usage_graph.vue';
-import { TOTAL_USAGE_DEFAULT_TEXT } from '~/usage_quotas/storage/constants';
+import {
+ descendingStorageUsageSort,
+ getStorageTypesFromProjectStatistics,
+} from '~/usage_quotas/storage/utils';
+import {
+ storageTypeHelpPaths,
+ PROJECT_STORAGE_TYPES,
+ NAMESPACE_STORAGE_TYPES,
+ TOTAL_USAGE_DEFAULT_TEXT,
+} from '~/usage_quotas/storage/constants';
import getProjectStorageStatistics from '~/usage_quotas/storage/queries/project_storage.query.graphql';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
import {
- projectData,
mockGetProjectStorageStatisticsGraphQLResponse,
mockEmptyResponse,
defaultProjectProvideValues,
@@ -36,25 +44,26 @@ describe('ProjectStorageApp', () => {
};
const createComponent = ({ provide = {}, mockApollo } = {}) => {
- wrapper = extendedWrapper(
- shallowMount(ProjectStorageApp, {
- apolloProvider: mockApollo,
- provide: {
- ...defaultProjectProvideValues,
- ...provide,
- },
- }),
- );
+ wrapper = shallowMountExtended(ProjectStorageApp, {
+ apolloProvider: mockApollo,
+ provide: {
+ ...defaultProjectProvideValues,
+ ...provide,
+ },
+ });
};
const findAlert = () => wrapper.findComponent(GlAlert);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findUsagePercentage = () => wrapper.findByTestId('total-usage');
- const findUsageQuotasHelpLink = () => wrapper.findByTestId('usage-quotas-help-link');
const findUsageGraph = () => wrapper.findComponent(UsageGraph);
+ const findProjectDetailsTable = () => wrapper.findByTestId('usage-quotas-project-usage-details');
+ const findNamespaceDetailsTable = () =>
+ wrapper.findByTestId('usage-quotas-namespace-usage-details');
describe('with apollo fetching successful', () => {
let mockApollo;
+ const mockProjectData = mockGetProjectStorageStatisticsGraphQLResponse.data.project;
beforeEach(async () => {
mockApollo = createMockApolloProvider({
@@ -65,13 +74,33 @@ describe('ProjectStorageApp', () => {
});
it('renders correct total usage', () => {
- expect(findUsagePercentage().text()).toBe(projectData.storage.totalUsage);
+ const expectedValue = numberToHumanSize(
+ mockGetProjectStorageStatisticsGraphQLResponse.data.project.statistics.storageSize,
+ 1,
+ );
+ expect(findUsagePercentage().text()).toBe(expectedValue);
+ });
+
+ it('passes project storage entities to project details table', () => {
+ const expectedValue = getStorageTypesFromProjectStatistics(
+ PROJECT_STORAGE_TYPES,
+ mockProjectData.statistics,
+ mockProjectData.statisticsDetailsPaths,
+ storageTypeHelpPaths,
+ ).sort(descendingStorageUsageSort('value'));
+
+ expect(findProjectDetailsTable().props('storageTypes')).toStrictEqual(expectedValue);
});
- it('renders correct usage quotas help link', () => {
- expect(findUsageQuotasHelpLink().attributes('href')).toBe(
- defaultProjectProvideValues.helpLinks.usageQuotas,
+ it('passes namespace storage entities to namespace details table', () => {
+ const expectedValue = getStorageTypesFromProjectStatistics(
+ NAMESPACE_STORAGE_TYPES,
+ mockProjectData.statistics,
+ mockProjectData.statisticsDetailsPaths,
+ storageTypeHelpPaths,
);
+
+ expect(findNamespaceDetailsTable().props('storageTypes')).toStrictEqual(expectedValue);
});
});
@@ -104,6 +133,14 @@ describe('ProjectStorageApp', () => {
it('shows default text for total usage', () => {
expect(findUsagePercentage().text()).toBe(TOTAL_USAGE_DEFAULT_TEXT);
});
+
+ it('passes empty array to project details table', () => {
+ expect(findProjectDetailsTable().props('storageTypes')).toStrictEqual([]);
+ });
+
+ it('passes empty array to namespace details table', () => {
+ expect(findNamespaceDetailsTable().props('storageTypes')).toStrictEqual([]);
+ });
});
describe('with apollo fetching error', () => {
diff --git a/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js b/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js
index 37fc9602315..364517a474f 100644
--- a/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/project_storage_detail_spec.js
@@ -1,15 +1,36 @@
-import { GlTableLite, GlPopover } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { GlTableLite } from '@gitlab/ui';
+import { mount, Wrapper } from '@vue/test-utils'; // eslint-disable-line no-unused-vars
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ProjectStorageDetail from '~/usage_quotas/storage/components/project_storage_detail.vue';
-import { containerRegistryPopoverId, containerRegistryId } from '~/usage_quotas/storage/constants';
import { numberToHumanSize } from '~/lib/utils/number_utils';
-import { projectData, projectHelpLinks } from '../mock_data';
describe('ProjectStorageDetail', () => {
+ /** @type { Wrapper } */
let wrapper;
- const { storageTypes } = projectData.storage;
+ const generateStorageType = (props) => {
+ return {
+ id: 'id',
+ name: 'name',
+ description: 'description',
+ helpPath: '/help-path',
+ detailsPath: '/details-link',
+ value: 42,
+ ...props,
+ };
+ };
+
+ const storageTypes = [
+ generateStorageType({ id: 'one' }),
+ generateStorageType({ id: 'two' }),
+ generateStorageType({
+ id: 'three',
+ warning: {
+ content: 'warning message',
+ },
+ }),
+ ];
+
const defaultProps = { storageTypes };
const createComponent = (props = {}) => {
@@ -26,23 +47,7 @@ describe('ProjectStorageDetail', () => {
);
};
- const generateStorageType = (id = 'buildArtifacts') => {
- return {
- storageType: {
- id,
- name: 'Test Name',
- description: 'Test Description',
- helpPath: '/test-type',
- },
- value: 400000,
- };
- };
-
const findTable = () => wrapper.findComponent(GlTableLite);
- const findPopoverById = (id) =>
- wrapper.findAllComponents(GlPopover).filter((p) => p.attributes('data-testid') === id);
- const findContainerRegistryPopover = () => findPopoverById(containerRegistryPopoverId);
- const findContainerRegistryWarningIcon = () => wrapper.find(`#${containerRegistryPopoverId}`);
beforeEach(() => {
createComponent();
@@ -51,33 +56,23 @@ describe('ProjectStorageDetail', () => {
describe('with storage types', () => {
it.each(storageTypes)(
'renders table row correctly %o',
- ({ storageType: { id, name, description } }) => {
+ ({ id, name, value, description, helpPath, warning }) => {
expect(wrapper.findByTestId(`${id}-name`).text()).toBe(name);
expect(wrapper.findByTestId(`${id}-description`).text()).toBe(description);
expect(wrapper.findByTestId(`${id}-icon`).props('name')).toBe(id);
- expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe(
- projectHelpLinks[id],
- );
+ expect(wrapper.findByTestId(`${id}-help-link`).attributes('href')).toBe(helpPath);
+ expect(wrapper.findByTestId(`${id}-value`).text()).toContain(numberToHumanSize(value, 1));
+
+ expect(wrapper.findByTestId(`${id}-warning-icon`).exists()).toBe(Boolean(warning));
+ expect(wrapper.findByTestId(`${id}-popover`).exists()).toBe(Boolean(warning));
},
);
-
- it('should render items in order from the biggest usage size to the smallest', () => {
- const rows = findTable().find('tbody').findAll('tr');
- // Cloning array not to mutate the source
- const sortedStorageTypes = [...storageTypes].sort((a, b) => b.value - a.value);
-
- sortedStorageTypes.forEach((storageType, i) => {
- const rowUsageAmount = rows.wrappers[i].find('td:last-child').text();
- const expectedUsageAmount = numberToHumanSize(storageType.value, 1);
- expect(rowUsageAmount).toBe(expectedUsageAmount);
- });
- });
});
describe('with details links', () => {
it.each(storageTypes)('each $storageType.id', (item) => {
- const shouldExist = Boolean(item.storageType.detailsPath && item.value);
- const detailsLink = wrapper.findByTestId(`${item.storageType.id}-details-link`);
+ const shouldExist = Boolean(item.detailsPath && item.value);
+ const detailsLink = wrapper.findByTestId(`${item.id}-details-link`);
expect(detailsLink.exists()).toBe(shouldExist);
});
});
@@ -95,21 +90,4 @@ describe('ProjectStorageDetail', () => {
expect(findTable().find('td').exists()).toBe(false);
});
});
-
- describe.each`
- description | mockStorageTypes | rendersContainerRegistryPopover
- ${'without any storage type that has popover'} | ${[generateStorageType()]} | ${false}
- ${'with container registry storage type'} | ${[generateStorageType(containerRegistryId)]} | ${true}
- `('$description', ({ mockStorageTypes, rendersContainerRegistryPopover }) => {
- beforeEach(() => {
- createComponent({ storageTypes: mockStorageTypes });
- });
-
- it(`does ${
- rendersContainerRegistryPopover ? '' : ' not'
- } render container registry warning icon and popover`, () => {
- expect(findContainerRegistryWarningIcon().exists()).toBe(rendersContainerRegistryPopover);
- expect(findContainerRegistryPopover().exists()).toBe(rendersContainerRegistryPopover);
- });
- });
});
diff --git a/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js b/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js
index 7fef20c900e..fc116211bf0 100644
--- a/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js
+++ b/spec/frontend/usage_quotas/storage/components/usage_graph_spec.js
@@ -44,7 +44,6 @@ describe('UsageGraph', () => {
buildArtifactsSize,
lfsObjectsSize,
packagesSize,
- containerRegistrySize,
repositorySize,
wikiSize,
snippetsSize,
@@ -57,14 +56,11 @@ describe('UsageGraph', () => {
expect(types.at(2).text()).toMatchInterpolatedText(
`Packages ${numberToHumanSize(packagesSize)}`,
);
- expect(types.at(3).text()).toMatchInterpolatedText(
- `Container Registry ${numberToHumanSize(containerRegistrySize)}`,
- );
- expect(types.at(4).text()).toMatchInterpolatedText(`LFS ${numberToHumanSize(lfsObjectsSize)}`);
- expect(types.at(5).text()).toMatchInterpolatedText(
+ expect(types.at(3).text()).toMatchInterpolatedText(`LFS ${numberToHumanSize(lfsObjectsSize)}`);
+ expect(types.at(4).text()).toMatchInterpolatedText(
`Snippets ${numberToHumanSize(snippetsSize)}`,
);
- expect(types.at(6).text()).toMatchInterpolatedText(
+ expect(types.at(5).text()).toMatchInterpolatedText(
`Job artifacts ${numberToHumanSize(buildArtifactsSize)}`,
);
});
@@ -102,7 +98,6 @@ describe('UsageGraph', () => {
'0.29411764705882354',
'0.23529411764705882',
'0.17647058823529413',
- '0.14705882352941177',
'0.11764705882352941',
'0.11764705882352941',
'0.041176470588235294',
@@ -121,7 +116,6 @@ describe('UsageGraph', () => {
'0.29411764705882354',
'0.23529411764705882',
'0.17647058823529413',
- '0.14705882352941177',
'0.11764705882352941',
'0.11764705882352941',
'0.041176470588235294',
diff --git a/spec/frontend/usage_quotas/storage/mock_data.js b/spec/frontend/usage_quotas/storage/mock_data.js
index 452fa83b9a7..16c03a13028 100644
--- a/spec/frontend/usage_quotas/storage/mock_data.js
+++ b/spec/frontend/usage_quotas/storage/mock_data.js
@@ -3,95 +3,6 @@ import mockGetProjectStorageStatisticsGraphQLResponse from 'test_fixtures/graphq
export { mockGetProjectStorageStatisticsGraphQLResponse };
export const mockEmptyResponse = { data: { project: null } };
-export const projectData = {
- storage: {
- totalUsage: '13.4 MiB',
- storageTypes: [
- {
- storageType: {
- id: 'containerRegistry',
- name: 'Container Registry',
- description: 'Gitlab-integrated Docker Container Registry for storing Docker Images.',
- helpPath: '/container_registry',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/container_registry',
- },
- value: 3900000,
- },
- {
- storageType: {
- id: 'buildArtifacts',
- name: 'Job artifacts',
- description: 'Job artifacts created by CI/CD.',
- helpPath: '/build-artifacts',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/artifacts',
- },
- value: 400000,
- },
- {
- storageType: {
- id: 'lfsObjects',
- name: 'LFS',
- description: 'Audio samples, videos, datasets, and graphics.',
- helpPath: '/lsf-objects',
- },
- value: 4800000,
- },
- {
- storageType: {
- id: 'packages',
- name: 'Packages',
- description: 'Code packages and container images.',
- helpPath: '/packages',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/packages',
- },
- value: 3800000,
- },
- {
- storageType: {
- id: 'repository',
- name: 'Repository',
- description: 'Git repository.',
- helpPath: '/repository',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/tree/master',
- },
- value: 3900000,
- },
- {
- storageType: {
- id: 'snippets',
- name: 'Snippets',
- description: 'Shared bits of code and text.',
- helpPath: '/snippets',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/snippets',
- },
- value: 0,
- },
- {
- storageType: {
- id: 'wiki',
- name: 'Wiki',
- description: 'Wiki content.',
- helpPath: '/wiki',
- detailsPath: 'http://localhost/frontend-fixtures/builds-project/-/wikis/pages',
- },
- value: 300000,
- },
- ],
- },
-};
-
-export const projectHelpLinks = {
- containerRegistry: '/container_registry',
- usageQuotas: '/usage-quotas',
- buildArtifacts: '/build-artifacts',
- lfsObjects: '/lsf-objects',
- packages: '/packages',
- repository: '/repository',
- snippets: '/snippets',
- wiki: '/wiki',
-};
-
export const defaultProjectProvideValues = {
projectPath: '/project-path',
- helpLinks: projectHelpLinks,
};
diff --git a/spec/frontend/usage_quotas/storage/utils_spec.js b/spec/frontend/usage_quotas/storage/utils_spec.js
index e3a271adc57..dd05e105c26 100644
--- a/spec/frontend/usage_quotas/storage/utils_spec.js
+++ b/spec/frontend/usage_quotas/storage/utils_spec.js
@@ -1,15 +1,9 @@
-import cloneDeep from 'lodash/cloneDeep';
import { PROJECT_STORAGE_TYPES } from '~/usage_quotas/storage/constants';
import {
- parseGetProjectStorageResults,
getStorageTypesFromProjectStatistics,
descendingStorageUsageSort,
} from '~/usage_quotas/storage/utils';
-import {
- mockGetProjectStorageStatisticsGraphQLResponse,
- defaultProjectProvideValues,
- projectData,
-} from './mock_data';
+import { mockGetProjectStorageStatisticsGraphQLResponse } from './mock_data';
describe('getStorageTypesFromProjectStatistics', () => {
const {
@@ -18,15 +12,18 @@ describe('getStorageTypesFromProjectStatistics', () => {
} = mockGetProjectStorageStatisticsGraphQLResponse.data.project;
describe('matches project statistics value with matching storage type', () => {
- const typesWithStats = getStorageTypesFromProjectStatistics(projectStatistics);
+ const typesWithStats = getStorageTypesFromProjectStatistics(
+ PROJECT_STORAGE_TYPES,
+ projectStatistics,
+ );
it.each(PROJECT_STORAGE_TYPES)('storage type: $id', ({ id }) => {
- expect(typesWithStats).toContainEqual({
- storageType: expect.objectContaining({
+ expect(typesWithStats).toContainEqual(
+ expect.objectContaining({
id,
+ value: projectStatistics[`${id}Size`],
}),
- value: projectStatistics[`${id}Size`],
- });
+ );
});
});
@@ -38,57 +35,31 @@ describe('getStorageTypesFromProjectStatistics', () => {
};
}, {});
- const typesWithStats = getStorageTypesFromProjectStatistics(projectStatistics, helpLinks);
+ const typesWithStats = getStorageTypesFromProjectStatistics(
+ PROJECT_STORAGE_TYPES,
+ projectStatistics,
+ {},
+ helpLinks,
+ );
typesWithStats.forEach((type) => {
- const key = type.storageType.id;
- expect(type.storageType.helpPath).toBe(helpLinks[key]);
+ expect(type.helpPath).toBe(helpLinks[type.id]);
});
});
it('adds details page path', () => {
const typesWithStats = getStorageTypesFromProjectStatistics(
+ PROJECT_STORAGE_TYPES,
projectStatistics,
- {},
statisticsDetailsPaths,
+ {},
);
typesWithStats.forEach((type) => {
- expect(type.storageType.detailsPath).toBe(statisticsDetailsPaths[type.storageType.id]);
+ expect(type.detailsPath).toBe(statisticsDetailsPaths[type.id]);
});
});
});
-describe('parseGetProjectStorageResults', () => {
- it('parses project statistics correctly', () => {
- expect(
- parseGetProjectStorageResults(
- mockGetProjectStorageStatisticsGraphQLResponse.data,
- defaultProjectProvideValues.helpLinks,
- ),
- ).toMatchObject(projectData);
- });
-
- it('includes storage type with size of 0 in returned value', () => {
- const mockedResponse = cloneDeep(mockGetProjectStorageStatisticsGraphQLResponse.data);
- // ensuring a specific storage type item has size of 0
- mockedResponse.project.statistics.repositorySize = 0;
-
- const response = parseGetProjectStorageResults(
- mockedResponse,
- defaultProjectProvideValues.helpLinks,
- );
-
- expect(response.storage.storageTypes).toEqual(
- expect.arrayContaining([
- {
- storageType: expect.any(Object),
- value: 0,
- },
- ]),
- );
- });
-});
-
describe('descendingStorageUsageSort', () => {
it('sorts items by a given key in descending order', () => {
const items = [{ k: 1 }, { k: 3 }, { k: 2 }];
diff --git a/spec/lib/gitlab/audit/auditor_spec.rb b/spec/lib/gitlab/audit/auditor_spec.rb
index 386d4157e90..1a45235a4e7 100644
--- a/spec/lib/gitlab/audit/auditor_spec.rb
+++ b/spec/lib/gitlab/audit/auditor_spec.rb
@@ -25,22 +25,48 @@ RSpec.describe Gitlab::Audit::Auditor, feature_category: :audit_events do
describe '.audit' do
let(:audit!) { auditor.audit(context) }
+ before do
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_call_original
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).with(name).and_return(true)
+ end
+
context 'when yaml definition is not defined' do
before do
- allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_return(false)
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).and_call_original
+ allow(Gitlab::Audit::Type::Definition).to receive(:defined?).with(name).and_return(false)
allow(Gitlab::AppLogger).to receive(:warn).and_return(app_logger)
end
- it 'logs a warning when YAML is not defined' do
- expected_warning = {
- message: 'Logging audit events without an event type definition will be deprecated soon ' \
- '(https://docs.gitlab.com/ee/development/audit_event_guide/#event-type-definitions)',
- event_type: name
- }
+ context 'when feature flag raise_error_for_missing_audit_event_yml is enabled' do
+ before do
+ stub_feature_flags(raise_error_for_missing_audit_event_yml: true)
+ end
- audit!
+ it 'raises an error' do
+ expected_error = "Audit event type YML file is not defined for audit_operation. " \
+ "Please read https://docs.gitlab.com/ee/development/audit_event_guide/" \
+ "#how-to-instrument-new-audit-events for adding a new audit event"
+
+ expect { audit! }.to raise_error(StandardError, expected_error)
+ end
+ end
- expect(Gitlab::AppLogger).to have_received(:warn).with(expected_warning)
+ context 'when feature flag raise_error_for_missing_audit_event_yml is disabled' do
+ before do
+ stub_feature_flags(raise_error_for_missing_audit_event_yml: false)
+ end
+
+ it 'logs a warning when YAML is not defined' do
+ expected_warning = {
+ message: 'Logging audit events without an event type definition will be deprecated soon ' \
+ '(https://docs.gitlab.com/ee/development/audit_event_guide/#event-type-definitions)',
+ event_type: name
+ }
+
+ audit!
+
+ expect(Gitlab::AppLogger).to have_received(:warn).with(expected_warning)
+ end
end
end
diff --git a/spec/lib/gitlab/ci/interpolation/block_spec.rb b/spec/lib/gitlab/ci/interpolation/block_spec.rb
index 4a8709df3dc..e4ccfbdfd63 100644
--- a/spec/lib/gitlab/ci/interpolation/block_spec.rb
+++ b/spec/lib/gitlab/ci/interpolation/block_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Gitlab::Ci::Interpolation::Block, feature_category: :pipeline_com
end
let(:ctx) do
- { inputs: { data: 'abc' }, env: { 'ENV' => 'dev' } }
+ { inputs: { data: 'abcdef' }, env: { 'ENV' => 'dev' } }
end
it 'knows its content' do
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::Ci::Interpolation::Block, feature_category: :pipeline_com
end
it 'properly evaluates the access pattern' do
- expect(subject.value).to eq 'abc'
+ expect(subject.value).to eq 'abcdef'
end
describe '.match' do
@@ -35,5 +35,78 @@ RSpec.describe Gitlab::Ci::Interpolation::Block, feature_category: :pipeline_com
expect { |b| described_class.match('$[[]]', &b) }
.to yield_with_args('$[[]]', '')
end
+
+ context 'when functions are specified in the block' do
+ it 'matches each block in a string' do
+ expect { |b| described_class.match('$[[ access1 | func1 ]] $[[ access2 | func1 | func2(0,1) ]]', &b) }
+ .to yield_successive_args(['$[[ access1 | func1 ]]', 'access1 | func1'],
+ ['$[[ access2 | func1 | func2(0,1) ]]', 'access2 | func1 | func2(0,1)'])
+ end
+ end
+ end
+
+ describe 'when functions are specified in the block' do
+ let(:function_string1) { 'truncate(1,5)' }
+ let(:data) { "inputs.data | #{function_string1}" }
+ let(:access_value) { 'abcdef' }
+
+ it 'returns the modified value' do
+ expect(subject).to be_valid
+ expect(subject.value).to eq('bcdef')
+ end
+
+ context 'when there is an access error' do
+ let(:data) { "inputs.undefined | #{function_string1}" }
+
+ it 'returns the access error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('unknown interpolation key: `undefined`')
+ end
+ end
+
+ context 'when there is a function error' do
+ let(:data) { 'inputs.data | undefined' }
+
+ it 'returns the function error' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to match(/no function matching `undefined`/)
+ end
+ end
+
+ context 'when multiple functions are specified' do
+ let(:function_string2) { 'truncate(2,2)' }
+ let(:data) { "inputs.data | #{function_string1} | #{function_string2}" }
+
+ it 'executes each function in the specified order' do
+ expect(subject.value).to eq('de')
+ end
+
+ context 'when the data has inconsistent spacing' do
+ let(:data) { "inputs.data|#{function_string1} | #{function_string2} " }
+
+ it 'executes each function in the specified order' do
+ expect(subject.value).to eq('de')
+ end
+ end
+
+ context 'when a stack of functions errors in the middle' do
+ let(:function_string2) { 'truncate(2)' }
+
+ it 'does not modify the value' do
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to match(/no function matching `truncate\(2\)`/)
+ expect(subject.instance_variable_get(:@value)).to be_nil
+ end
+ end
+
+ context 'when too many functions are specified' do
+ it 'returns error' do
+ stub_const('Gitlab::Ci::Interpolation::Block::MAX_FUNCTIONS', 1)
+
+ expect(subject).not_to be_valid
+ expect(subject.errors.first).to eq('too many functions in interpolation block')
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/interpolation/functions/base_spec.rb b/spec/lib/gitlab/ci/interpolation/functions/base_spec.rb
new file mode 100644
index 00000000000..1a15938b988
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/functions/base_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::Functions::Base, feature_category: :pipeline_composition do
+ let(:custom_function_klass) do
+ Class.new(described_class) do
+ def self.function_expression_pattern
+ /.*/
+ end
+
+ def self.name
+ 'test_function'
+ end
+ end
+ end
+
+ it 'defines an expected interface for child classes' do
+ expect { described_class.function_expression_pattern }.to raise_error(NotImplementedError)
+ expect { described_class.name }.to raise_error(NotImplementedError)
+ expect { custom_function_klass.new('test').execute('input') }.to raise_error(NotImplementedError)
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/functions/truncate_spec.rb b/spec/lib/gitlab/ci/interpolation/functions/truncate_spec.rb
new file mode 100644
index 00000000000..69ce30c51ec
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/functions/truncate_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::Functions::Truncate, feature_category: :pipeline_composition do
+ it 'matches exactly the truncate function with 2 numeric arguments' do
+ expect(described_class.matches?('truncate(1,2)')).to be_truthy
+ expect(described_class.matches?('truncate( 11 , 222 )')).to be_truthy
+ expect(described_class.matches?('truncate( string , 222 )')).to be_falsey
+ expect(described_class.matches?('truncate(222)')).to be_falsey
+ expect(described_class.matches?('unknown(1,2)')).to be_falsey
+ end
+
+ it 'truncates the given input' do
+ function = described_class.new('truncate(1,2)')
+
+ output = function.execute('test')
+
+ expect(function).to be_valid
+ expect(output).to eq('es')
+ end
+
+ context 'when given a non-string input' do
+ it 'returns an error' do
+ function = described_class.new('truncate(1,2)')
+
+ function.execute(100)
+
+ expect(function).not_to be_valid
+ expect(function.errors).to contain_exactly(
+ 'error in `truncate` function: invalid input type: truncate can only be used with string inputs'
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/interpolation/functions_stack_spec.rb b/spec/lib/gitlab/ci/interpolation/functions_stack_spec.rb
new file mode 100644
index 00000000000..6e31d23f764
--- /dev/null
+++ b/spec/lib/gitlab/ci/interpolation/functions_stack_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Ci::Interpolation::FunctionsStack, feature_category: :pipeline_composition do
+ let(:functions) { ['truncate(0,4)', 'truncate(1,2)'] }
+ let(:input_value) { 'test_input_value' }
+ let(:function_klass) { Gitlab::Ci::Interpolation::Functions::Truncate }
+
+ subject { described_class.new(functions).evaluate(input_value) }
+
+ it 'modifies the given input value according to the function expressions' do
+ expect(subject).to be_success
+ expect(subject.value).to eq('es')
+ end
+
+ context 'when applying a function fails' do
+ let(:input_value) { 666 }
+
+ it 'returns the error given by the failure' do
+ expect(subject).not_to be_success
+ expect(subject.errors).to contain_exactly(
+ 'error in `truncate` function: invalid input type: truncate can only be used with string inputs'
+ )
+ end
+ end
+
+ context 'when function expressions do not match any function' do
+ let(:functions) { ['truncate(0)', 'unknown'] }
+
+ it 'returns an error' do
+ expect(subject).not_to be_success
+ expect(subject.errors).to contain_exactly(
+ 'no function matching `truncate(0)`: check that the function name, arguments, and types are correct',
+ 'no function matching `unknown`: check that the function name, arguments, and types are correct'
+ )
+ end
+ end
+end
diff --git a/spec/presenters/ml/model_presenter_spec.rb b/spec/presenters/ml/model_presenter_spec.rb
new file mode 100644
index 00000000000..dbbd3b57033
--- /dev/null
+++ b/spec/presenters/ml/model_presenter_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ml::ModelPresenter, feature_category: :mlops do
+ let_it_be(:project) { build_stubbed(:project) }
+ let_it_be(:model1) { build_stubbed(:ml_models, project: project) }
+ let_it_be(:model2) { build_stubbed(:ml_models, :with_latest_version_and_package, project: project) }
+
+ describe '#latest_version_name' do
+ subject { model.present.latest_version_name }
+
+ context 'when model has version' do
+ let(:model) { model2 }
+
+ it 'is the version of latest_version' do
+ is_expected.to eq(model2.latest_version.version)
+ end
+ end
+
+ context 'when model has no versions' do
+ let(:model) { model1 }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#latest_package_path' do
+ subject { model.present.latest_package_path }
+
+ context 'when model version does not have package' do
+ let(:model) { model1 }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when latest model version has package' do
+ let(:model) { model2 }
+
+ it { is_expected.to eq("/#{project.full_path}/-/packages/#{model.latest_version.package_id}") }
+ end
+ end
+end
diff --git a/spec/presenters/ml/models_index_presenter_spec.rb b/spec/presenters/ml/models_index_presenter_spec.rb
deleted file mode 100644
index 549700cdd84..00000000000
--- a/spec/presenters/ml/models_index_presenter_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ml::ModelsIndexPresenter, feature_category: :mlops do
- let_it_be(:project) { build_stubbed(:project) }
- let_it_be(:model1) { build_stubbed(:ml_models, :with_latest_version_and_package, project: project) }
- let_it_be(:model2) { build_stubbed(:ml_models, project: project) }
- let_it_be(:models) do
- [model1, model2]
- end
-
- describe '#execute' do
- subject { Gitlab::Json.parse(described_class.new(models).present)['models'] }
-
- it 'presents models correctly' do
- expected_models = [
- {
- 'name' => model1.name,
- 'version' => model1.latest_version.version,
- 'path' => "/#{project.full_path}/-/packages/#{model1.latest_version.package_id}"
- },
- {
- 'name' => model2.name,
- 'version' => nil,
- 'path' => nil
- }
- ]
-
- is_expected.to match_array(expected_models)
- end
- end
-end
diff --git a/spec/requests/api/graphql/mutations/work_items/create_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
index fca3c84e534..6055390c8d5 100644
--- a/spec/requests/api/graphql/mutations/work_items/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
@@ -272,6 +272,16 @@ RSpec.describe 'Create a work item', feature_category: :team_planning do
let(:mutation) { graphql_mutation(:workItemCreate, input.merge(namespacePath: group.full_path), fields) }
it_behaves_like 'creates work item'
+
+ context 'when the namespace_level_work_items feature flag is disabled' do
+ before do
+ stub_feature_flags(namespace_level_work_items: false)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: [
+ Mutations::WorkItems::Create::DISABLED_FF_ERROR
+ ]
+ end
end
context 'when both projectPath and namespacePath are passed' do
diff --git a/spec/requests/projects/ml/models_controller_spec.rb b/spec/requests/projects/ml/models_controller_spec.rb
index b57f5db9b68..8569f2396d3 100644
--- a/spec/requests/projects/ml/models_controller_spec.rb
+++ b/spec/requests/projects/ml/models_controller_spec.rb
@@ -7,6 +7,7 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
let_it_be(:user) { project.first_owner }
let_it_be(:model1) { create(:ml_models, :with_versions, project: project) }
let_it_be(:model2) { create(:ml_models, project: project) }
+ let_it_be(:model_in_different_project) { create(:ml_models) }
let(:model_registry_enabled) { true }
@@ -35,10 +36,10 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
index_request
end
- it 'prepares model view using the presenter' do
- expect(::Ml::ModelsIndexPresenter).to receive(:new).and_call_original
-
+ it 'fetches the correct models' do
index_request
+
+ expect(assigns(:models)).to match_array([model1, model2])
end
it 'does not perform N+1 sql queries' do