diff options
Diffstat (limited to 'spec')
19 files changed, 386 insertions, 75 deletions
diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb index 9af9baeb5bb..ab24162ad5a 100644 --- a/spec/features/group_variables_spec.rb +++ b/spec/features/group_variables_spec.rb @@ -23,7 +23,11 @@ RSpec.describe 'Group variables', :js do it_behaves_like 'variable list' end - # TODO: Uncomment when the new graphQL app for variable settings - # is enabled. - # it_behaves_like 'variable list' + context 'with enabled ff `ci_variable_settings_graphql' do + before do + visit page_path + end + + it_behaves_like 'variable list' + end end diff --git a/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js b/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js new file mode 100644 index 00000000000..e45656acfd8 --- /dev/null +++ b/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js @@ -0,0 +1,183 @@ +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlLoadingIcon, GlTable } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; +import { resolvers } from '~/ci_variable_list/graphql/resolvers'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; + +import ciGroupVariables from '~/ci_variable_list/components/ci_group_variables.vue'; +import ciVariableSettings from '~/ci_variable_list/components/ci_variable_settings.vue'; +import ciVariableTable from '~/ci_variable_list/components/ci_variable_table.vue'; +import getGroupVariables from '~/ci_variable_list/graphql/queries/group_variables.query.graphql'; + +import addGroupVariable from '~/ci_variable_list/graphql/mutations/group_add_variable.mutation.graphql'; +import deleteGroupVariable from '~/ci_variable_list/graphql/mutations/group_delete_variable.mutation.graphql'; +import updateGroupVariable from '~/ci_variable_list/graphql/mutations/group_update_variable.mutation.graphql'; + +import { genericMutationErrorText, variableFetchErrorText } from '~/ci_variable_list/constants'; + +import { mockGroupVariables, newVariable } from '../mocks'; + +jest.mock('~/flash'); + +Vue.use(VueApollo); + +const mockProvide = { + endpoint: '/variables', + groupPath: '/namespace/group', + groupId: 1, +}; + +describe('Ci Group Variable list', () => { + let wrapper; + + let mockApollo; + let mockVariables; + + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findCiTable = () => wrapper.findComponent(GlTable); + const findCiSettings = () => wrapper.findComponent(ciVariableSettings); + + // eslint-disable-next-line consistent-return + const createComponentWithApollo = async ({ isLoading = false } = {}) => { + const handlers = [[getGroupVariables, mockVariables]]; + + mockApollo = createMockApollo(handlers, resolvers); + + wrapper = shallowMount(ciGroupVariables, { + provide: mockProvide, + apolloProvider: mockApollo, + stubs: { ciVariableSettings, ciVariableTable }, + }); + + if (!isLoading) { + return waitForPromises(); + } + }; + + beforeEach(() => { + mockVariables = jest.fn(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('while queries are being fetch', () => { + beforeEach(() => { + createComponentWithApollo({ isLoading: true }); + }); + + it('shows a loading icon', () => { + expect(findLoadingIcon().exists()).toBe(true); + expect(findCiTable().exists()).toBe(false); + }); + }); + + describe('when queries are resolved', () => { + describe('successfuly', () => { + beforeEach(async () => { + mockVariables.mockResolvedValue(mockGroupVariables); + + await createComponentWithApollo(); + }); + + it('passes down the expected environments as props', () => { + expect(findCiSettings().props('environments')).toEqual([]); + }); + + it('passes down the expected variables as props', () => { + expect(findCiSettings().props('variables')).toEqual( + mockGroupVariables.data.group.ciVariables.nodes, + ); + }); + + it('createFlash was not called', () => { + expect(createFlash).not.toHaveBeenCalled(); + }); + }); + + describe('with an error for variables', () => { + beforeEach(async () => { + mockVariables.mockRejectedValue(); + + await createComponentWithApollo(); + }); + + it('calls createFlash with the expected error message', () => { + expect(createFlash).toHaveBeenCalledWith({ message: variableFetchErrorText }); + }); + }); + }); + + describe('mutations', () => { + beforeEach(async () => { + mockVariables.mockResolvedValue(mockGroupVariables); + + await createComponentWithApollo(); + }); + it.each` + actionName | mutation | event + ${'add'} | ${addGroupVariable} | ${'add-variable'} + ${'update'} | ${updateGroupVariable} | ${'update-variable'} + ${'delete'} | ${deleteGroupVariable} | ${'delete-variable'} + `( + 'calls the right mutation when user performs $actionName variable', + async ({ event, mutation }) => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(); + await findCiSettings().vm.$emit(event, newVariable); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation, + variables: { + endpoint: mockProvide.endpoint, + fullPath: mockProvide.groupPath, + groupId: convertToGraphQLId('Group', mockProvide.groupId), + variable: newVariable, + }, + }); + }, + ); + + it.each` + actionName | event | mutationName + ${'add'} | ${'add-variable'} | ${'addGroupVariable'} + ${'update'} | ${'update-variable'} | ${'updateGroupVariable'} + ${'delete'} | ${'delete-variable'} | ${'deleteGroupVariable'} + `( + 'throws with the specific graphql error if present when user performs $actionName variable', + async ({ event, mutationName }) => { + const graphQLErrorMessage = 'There is a problem with this graphQL action'; + jest + .spyOn(wrapper.vm.$apollo, 'mutate') + .mockResolvedValue({ data: { [mutationName]: { errors: [graphQLErrorMessage] } } }); + await findCiSettings().vm.$emit(event, newVariable); + await nextTick(); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled(); + expect(createFlash).toHaveBeenCalledWith({ message: graphQLErrorMessage }); + }, + ); + + it.each` + actionName | event + ${'add'} | ${'add-variable'} + ${'update'} | ${'update-variable'} + ${'delete'} | ${'delete-variable'} + `( + 'throws generic error when the mutation fails with no graphql errors and user performs $actionName variable', + async ({ event }) => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockImplementationOnce(() => { + throw new Error(); + }); + await findCiSettings().vm.$emit(event, newVariable); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled(); + expect(createFlash).toHaveBeenCalledWith({ message: genericMutationErrorText }); + }, + ); + }); +}); diff --git a/spec/frontend/ci_variable_list/mocks.js b/spec/frontend/ci_variable_list/mocks.js index 07dc7a8c91f..89ba77858dc 100644 --- a/spec/frontend/ci_variable_list/mocks.js +++ b/spec/frontend/ci_variable_list/mocks.js @@ -1,4 +1,4 @@ -import { variableTypes, instanceString } from '~/ci_variable_list/constants'; +import { variableTypes, groupString, instanceString } from '~/ci_variable_list/constants'; export const devName = 'dev'; export const prodName = 'prod'; @@ -82,22 +82,12 @@ export const mockProjectVariables = { }, }; -export const mockGroupEnvironments = { - data: { - group: { - __typename: 'Group', - id: 1, - environments: defaultEnvs, - }, - }, -}; - export const mockGroupVariables = { data: { group: { __typename: 'Group', id: 1, - ciVariables: createDefaultVars(), + ciVariables: createDefaultVars({ kind: groupString }), }, }, }; diff --git a/spec/frontend/header_search/components/app_spec.js b/spec/frontend/header_search/components/app_spec.js index d89218f5542..6a138f9a247 100644 --- a/spec/frontend/header_search/components/app_spec.js +++ b/spec/frontend/header_search/components/app_spec.js @@ -15,6 +15,10 @@ import { ICON_GROUP, ICON_SUBGROUP, SCOPE_TOKEN_MAX_LENGTH, + IS_SEARCHING, + IS_NOT_FOCUSED, + IS_FOCUSED, + SEARCH_SHORTCUTS_MIN_CHARACTERS, } from '~/header_search/constants'; import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue'; import { ENTER_KEY } from '~/lib/utils/keys'; @@ -170,6 +174,14 @@ describe('HeaderSearchApp', () => { it(`should render the Dropdown Navigation Component`, () => { expect(findDropdownKeyboardNavigation().exists()).toBe(true); }); + + it(`should close the dropdown when press escape key`, async () => { + findHeaderSearchInput().vm.$emit('keydown', new KeyboardEvent({ key: 27 })); + await nextTick(); + expect(findHeaderSearchDropdown().exists()).toBe(false); + // only one event emmited from findHeaderSearchInput().vm.$emit('click'); + expect(wrapper.emitted().expandSearchBar.length).toBe(1); + }); }); }); @@ -245,6 +257,7 @@ describe('HeaderSearchApp', () => { searchOptions: () => searchOptions, }, ); + findHeaderSearchInput().vm.$emit('click'); }); it(`${hasToken ? 'is' : 'is NOT'} rendered when data set has type "${ @@ -263,47 +276,43 @@ describe('HeaderSearchApp', () => { }); }); - describe('form wrapper', () => { + describe('form', () => { describe.each` - searchContext | search | searchOptions - ${MOCK_SEARCH_CONTEXT_FULL} | ${null} | ${[]} - ${MOCK_SEARCH_CONTEXT_FULL} | ${MOCK_SEARCH} | ${[]} - ${MOCK_SEARCH_CONTEXT_FULL} | ${MOCK_SEARCH} | ${MOCK_SCOPED_SEARCH_OPTIONS} - ${null} | ${MOCK_SEARCH} | ${MOCK_SCOPED_SEARCH_OPTIONS} - ${null} | ${null} | ${MOCK_SCOPED_SEARCH_OPTIONS} - ${null} | ${null} | ${[]} - `('', ({ searchContext, search, searchOptions }) => { + searchContext | search | searchOptions | isFocused + ${MOCK_SEARCH_CONTEXT_FULL} | ${null} | ${[]} | ${true} + ${MOCK_SEARCH_CONTEXT_FULL} | ${MOCK_SEARCH} | ${[]} | ${true} + ${MOCK_SEARCH_CONTEXT_FULL} | ${MOCK_SEARCH} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${true} + ${MOCK_SEARCH_CONTEXT_FULL} | ${MOCK_SEARCH} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${false} + ${null} | ${MOCK_SEARCH} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${true} + ${null} | ${null} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${true} + ${null} | ${null} | ${[]} | ${true} + `('wrapper', ({ searchContext, search, searchOptions, isFocused }) => { beforeEach(() => { window.gon.current_username = MOCK_USERNAME; - createComponent({ search, searchContext }, { searchOptions: () => searchOptions }); - - findHeaderSearchInput().vm.$emit('click'); + if (isFocused) { + findHeaderSearchInput().vm.$emit('click'); + } }); - const hasIcon = Boolean(searchContext?.group); - const isSearching = Boolean(search); - const isActive = Boolean(searchOptions.length > 0); + const isSearching = search?.length > SEARCH_SHORTCUTS_MIN_CHARACTERS; - it(`${hasIcon ? 'with' : 'without'} search context classes contain "${ - hasIcon ? 'has-icon' : 'has-no-icon' - }"`, () => { - const iconClassRegex = hasIcon ? 'has-icon' : 'has-no-icon'; - expect(findHeaderSearchForm().classes()).toContain(iconClassRegex); + it(`classes ${isSearching ? 'contain' : 'do not contain'} "${IS_SEARCHING}"`, () => { + if (isSearching) { + expect(findHeaderSearchForm().classes()).toContain(IS_SEARCHING); + return; + } + if (!isSearching) { + expect(findHeaderSearchForm().classes()).not.toContain(IS_SEARCHING); + } }); - it(`${isSearching ? 'with' : 'without'} search string classes contain "${ - isSearching ? 'is-searching' : 'is-not-searching' + it(`classes ${isSearching ? 'contain' : 'do not contain'} "${ + isFocused ? IS_FOCUSED : IS_NOT_FOCUSED }"`, () => { - const iconClassRegex = isSearching ? 'is-searching' : 'is-not-searching'; - expect(findHeaderSearchForm().classes()).toContain(iconClassRegex); - }); - - it(`${isActive ? 'with' : 'without'} search results classes contain "${ - isActive ? 'is-active' : 'is-not-active' - }"`, () => { - const iconClassRegex = isActive ? 'is-active' : 'is-not-active'; - expect(findHeaderSearchForm().classes()).toContain(iconClassRegex); + expect(findHeaderSearchForm().classes()).toContain( + isFocused ? IS_FOCUSED : IS_NOT_FOCUSED, + ); }); }); }); @@ -323,6 +332,7 @@ describe('HeaderSearchApp', () => { searchOptions: () => searchOptions, }, ); + findHeaderSearchInput().vm.$emit('click'); }); it(`icon for data set type "${searchOptions[0]?.html_id}" ${ diff --git a/spec/frontend/ide/components/repo_tab_spec.js b/spec/frontend/ide/components/repo_tab_spec.js index 0dda176b47c..b26edc5a85b 100644 --- a/spec/frontend/ide/components/repo_tab_spec.js +++ b/spec/frontend/ide/components/repo_tab_spec.js @@ -164,7 +164,7 @@ describe('RepoTab', () => { await wrapper.find('.multi-file-tab-close').trigger('click'); - expect(tab.opened).toBeFalsy(); + expect(tab.opened).toBe(false); expect(wrapper.vm.$store.state.changedFiles).toHaveLength(1); }); @@ -180,7 +180,7 @@ describe('RepoTab', () => { await wrapper.find('.multi-file-tab-close').trigger('click'); - expect(tab.opened).toBeFalsy(); + expect(tab.opened).toBe(false); }); }); }); diff --git a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js index 8f79c74368f..ed0abaaf576 100644 --- a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js +++ b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js @@ -128,7 +128,7 @@ describe('SignInOauthButton', () => { }); it('does not emit `sign-in` event', () => { - expect(wrapper.emitted('sign-in')).toBeFalsy(); + expect(wrapper.emitted('sign-in')).toBeUndefined(); }); it('sets `loading` prop of button to `false`', () => { @@ -179,7 +179,7 @@ describe('SignInOauthButton', () => { }); it('emits `sign-in` event with user data', () => { - expect(wrapper.emitted('sign-in')[0]).toBeTruthy(); + expect(wrapper.emitted('sign-in')).toHaveLength(1); }); }); @@ -200,7 +200,7 @@ describe('SignInOauthButton', () => { }); it('does not emit `sign-in` event', () => { - expect(wrapper.emitted('sign-in')).toBeFalsy(); + expect(wrapper.emitted('sign-in')).toBeUndefined(); }); it('sets `loading` prop of button to `false`', () => { diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js index ef6c4a1fa32..b163557618e 100644 --- a/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js +++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/details_page/tags_list_spec.js @@ -4,7 +4,6 @@ import { GlEmptyState } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { stripTypenames } from 'helpers/graphql_helpers'; import component from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue'; import TagsListRow from '~/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue'; @@ -96,8 +95,8 @@ describe('Tags List', () => { it('binds the correct props', () => { expect(findRegistryList().props()).toMatchObject({ title: '2 tags', - pagination: stripTypenames(tagsPageInfo), - items: stripTypenames(tags), + pagination: tagsPageInfo, + items: tags, idProperty: 'name', }); }); diff --git a/spec/frontend/releases/__snapshots__/util_spec.js.snap b/spec/frontend/releases/__snapshots__/util_spec.js.snap index 2a12d945792..55e3dda60a0 100644 --- a/spec/frontend/releases/__snapshots__/util_spec.js.snap +++ b/spec/frontend/releases/__snapshots__/util_spec.js.snap @@ -210,12 +210,15 @@ exports[`releases/util.js convertOneReleaseForEditingGraphQLResponse matches sna Object { "data": Object { "_links": Object { + "__typename": "ReleaseLinks", "self": "http://localhost/releases-namespace/releases-project/-/releases/v1.1", "selfUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1", }, "assets": Object { + "count": undefined, "links": Array [ Object { + "__typename": "ReleaseAssetLink", "directAssetPath": "/binaries/awesome-app-3", "id": "gid://gitlab/Releases::Link/13", "linkType": "image", @@ -223,6 +226,7 @@ Object { "url": "https://example.com/image", }, Object { + "__typename": "ReleaseAssetLink", "directAssetPath": "/binaries/awesome-app-2", "id": "gid://gitlab/Releases::Link/12", "linkType": "package", @@ -230,6 +234,7 @@ Object { "url": "https://example.com/package", }, Object { + "__typename": "ReleaseAssetLink", "directAssetPath": "/binaries/awesome-app-1", "id": "gid://gitlab/Releases::Link/11", "linkType": "runbook", @@ -237,6 +242,7 @@ Object { "url": "http://localhost/releases-namespace/releases-project/runbook", }, Object { + "__typename": "ReleaseAssetLink", "directAssetPath": "/binaries/linux-amd64", "id": "gid://gitlab/Releases::Link/10", "linkType": "other", @@ -246,22 +252,31 @@ Object { ], "sources": Array [], }, + "author": undefined, "description": "Best. Release. **Ever.** :rocket:", "evidences": Array [], "milestones": Array [ Object { + "__typename": "Milestone", "id": "gid://gitlab/Milestone/123", "issueStats": Object {}, + "stats": undefined, "title": "12.3", + "webPath": undefined, + "webUrl": undefined, }, Object { + "__typename": "Milestone", "id": "gid://gitlab/Milestone/124", "issueStats": Object {}, + "stats": undefined, "title": "12.4", + "webPath": undefined, + "webUrl": undefined, }, ], "name": "The first release", - "releasedAt": "2018-12-10T00:00:00.000Z", + "releasedAt": 2018-12-10T00:00:00.000Z, "tagName": "v1.1", "tagPath": "/releases-namespace/releases-project/-/tags/v1.1", }, diff --git a/spec/frontend/releases/util_spec.js b/spec/frontend/releases/util_spec.js index dfea3fc0037..055c8e8b39f 100644 --- a/spec/frontend/releases/util_spec.js +++ b/spec/frontend/releases/util_spec.js @@ -7,7 +7,6 @@ import { convertAllReleasesGraphQLResponse, convertOneReleaseGraphQLResponse, } from '~/releases/util'; -import { stripTypenames } from 'helpers/graphql_helpers'; describe('releases/util.js', () => { describe('convertGraphQLRelease', () => { @@ -137,7 +136,7 @@ describe('releases/util.js', () => { describe('convertOneReleaseForEditingGraphQLResponse', () => { it('matches snapshot', () => { expect( - stripTypenames(convertOneReleaseGraphQLResponse(originalOneReleaseForEditingQueryResponse)), + convertOneReleaseGraphQLResponse(originalOneReleaseForEditingQueryResponse), ).toMatchSnapshot(); }); }); diff --git a/spec/frontend/work_items/components/work_item_assignees_spec.js b/spec/frontend/work_items/components/work_item_assignees_spec.js index ee69e3bee83..f0ef8aee7a9 100644 --- a/spec/frontend/work_items/components/work_item_assignees_spec.js +++ b/spec/frontend/work_items/components/work_item_assignees_spec.js @@ -5,7 +5,6 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import { mockTracking } from 'helpers/tracking_helper'; -import { stripTypenames } from 'helpers/graphql_helpers'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import userSearchQuery from '~/graphql_shared/queries/users_search.query.graphql'; import currentUserQuery from '~/graphql_shared/queries/current_user.query.graphql'; @@ -311,9 +310,7 @@ describe('WorkItemAssignees component', () => { findAssignSelfButton().vm.$emit('click', new MouseEvent('click')); await nextTick(); - expect(findTokenSelector().props('selectedTokens')).toMatchObject([ - stripTypenames(currentUser), - ]); + expect(findTokenSelector().props('selectedTokens')).toMatchObject([currentUser]); expect(successUpdateWorkItemMutationHandler).toHaveBeenCalledWith({ input: { id: workItemId, @@ -330,9 +327,7 @@ describe('WorkItemAssignees component', () => { await waitForPromises(); expect(findTokenSelector().props('dropdownItems')[0]).toEqual( - expect.objectContaining({ - ...stripTypenames(currentUserResponse.data.currentUser), - }), + expect.objectContaining(currentUserResponse.data.currentUser), ); }); diff --git a/spec/frontend/work_items/pages/work_item_detail_spec.js b/spec/frontend/work_items/pages/work_item_detail_spec.js index 5fef151be81..823981df880 100644 --- a/spec/frontend/work_items/pages/work_item_detail_spec.js +++ b/spec/frontend/work_items/pages/work_item_detail_spec.js @@ -247,6 +247,9 @@ describe('WorkItemDetail component', () => { variant: 'warning', icon: 'eye-slash', }); + expect(confidentialBadge.attributes('title')).toBe( + 'Only project members with at least the Reporter role, the author, and assignees can view or be notified about this task.', + ); expect(confidentialBadge.text()).toBe('Confidential'); }); diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb index 0fa6d4f8804..6121c28070f 100644 --- a/spec/lib/gitlab/ci/config/entry/image_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'support/helpers/stubbed_feature' -require 'support/helpers/stub_feature_flags' +require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Image do - include StubFeatureFlags - before do stub_feature_flags(ci_docker_image_pull_policy: true) diff --git a/spec/lib/gitlab/ci/config/entry/imageable_spec.rb b/spec/lib/gitlab/ci/config/entry/imageable_spec.rb new file mode 100644 index 00000000000..88f8e260611 --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/imageable_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Config::Entry::Imageable do + let(:node_class) do + Class.new(::Gitlab::Config::Entry::Node) do + include ::Gitlab::Ci::Config::Entry::Imageable + + validations do + validates :config, allowed_keys: ::Gitlab::Ci::Config::Entry::Imageable::IMAGEABLE_ALLOWED_KEYS + end + + def self.name + 'node' + end + + def value + if string? + { name: @config } + elsif hash? + { + name: @config[:name] + }.compact + else + {} + end + end + end + end + + subject(:entry) { node_class.new(config) } + + before do + entry.compose! + end + + context 'when entry value is correct' do + let(:config) { 'image:1.0' } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + let(:config) { ['image:1.0'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors.first) + .to match /config should be a hash or a string/ + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + + context 'when unexpected key is specified' do + let(:config) { { name: 'image:1.0', non_existing: 'test' } } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors.first) + .to match /config contains unknown keys: non_existing/ + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb index 10b4cba3bbb..c85fe366da6 100644 --- a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb @@ -2,7 +2,6 @@ require 'fast_spec_helper' require 'gitlab_chronic_duration' -require 'support/helpers/stub_feature_flags' require_dependency 'active_model' RSpec.describe Gitlab::Ci::Config::Entry::Rules::Rule do diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb index 3c000fd09ed..821ab442d61 100644 --- a/spec/lib/gitlab/ci/config/entry/service_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'support/helpers/stubbed_feature' -require 'support/helpers/stub_feature_flags' +require 'spec_helper' RSpec.describe Gitlab::Ci::Config::Entry::Service do - include StubFeatureFlags - before do stub_feature_flags(ci_docker_image_pull_policy: true) entry.compose! diff --git a/spec/models/ml/candidate_metric_spec.rb b/spec/models/ml/candidate_metric_spec.rb new file mode 100644 index 00000000000..5ee6030fb8e --- /dev/null +++ b/spec/models/ml/candidate_metric_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ml::CandidateMetric do + describe 'associations' do + it { is_expected.to belong_to(:candidate) } + end +end diff --git a/spec/models/ml/candidate_param_spec.rb b/spec/models/ml/candidate_param_spec.rb new file mode 100644 index 00000000000..ff38e471219 --- /dev/null +++ b/spec/models/ml/candidate_param_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ml::CandidateParam do + describe 'associations' do + it { is_expected.to belong_to(:candidate) } + end +end diff --git a/spec/models/ml/candidate_spec.rb b/spec/models/ml/candidate_spec.rb new file mode 100644 index 00000000000..a48e291fa55 --- /dev/null +++ b/spec/models/ml/candidate_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ml::Candidate do + describe 'associations' do + it { is_expected.to belong_to(:experiment) } + it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:params) } + it { is_expected.to have_many(:metrics) } + end +end diff --git a/spec/models/ml/experiment_spec.rb b/spec/models/ml/experiment_spec.rb new file mode 100644 index 00000000000..dca5280a8fe --- /dev/null +++ b/spec/models/ml/experiment_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ml::Experiment do + describe 'associations' do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:user) } + it { is_expected.to have_many(:candidates) } + end +end |