diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 2e88b7aa0a9..793a52e359d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,3 +1,3 @@ We’re closing our issue tracker on GitHub so we can focus on the GitLab.com project and respond to issues more quickly. -We encourage you to open an issue on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues). You can log into GitLab.com using your GitHub account. +We encourage you to open an issue on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab/issues). You can log into GitLab.com using your GitHub account. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c3b04026440..40984c451c4 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,3 @@ Thank you for taking the time to contribute back to GitLab! -Please open a merge request [on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests), we look forward to reviewing your contribution! You can log into GitLab.com using your GitHub account. +Please open a merge request [on GitLab.com](https://gitlab.com/gitlab-org/gitlab/merge_requests), we look forward to reviewing your contribution! You can log into GitLab.com using your GitHub account. diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index d07b9a1055c..6e6939824ec 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -24,17 +24,6 @@ package-and-qa-manual: when: manual needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] -package-and-qa-manual:master: - extends: - - .package-and-qa-base - - .only-code-qa-changes - only: - refs: - - master@gitlab-org/gitlab-foss - - master@gitlab-org/gitlab - when: manual - needs: ["build-qa-image", "gitlab:assets:compile"] - package-and-qa: extends: - .package-and-qa-base @@ -44,3 +33,14 @@ package-and-qa: - master needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] allow_failure: true + +schedule:package-and-qa: + extends: + - .package-and-qa-base + - .only-code-qa-changes + only: + refs: + - schedules@gitlab-org/gitlab + - schedules@gitlab-org/gitlab-foss + needs: ["build-qa-image", "gitlab:assets:compile"] + allow_failure: true diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md index 3adea22b33a..0d9990657e4 100644 --- a/.gitlab/issue_templates/Bug.md +++ b/.gitlab/issue_templates/Bug.md @@ -2,17 +2,10 @@ Please read this! Before opening a new issue, make sure to search for keywords in the issues -filtered by the "regression" or "bug" label. +filtered by the "regression" or "bug" label: -For the Community Edition issue tracker: - -- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=regression -- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=bug - -For the Enterprise Edition issue tracker: - -- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=regression -- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=bug +- https://gitlab.com/gitlab-org/gitlab/issues?label_name%5B%5D=regression +- https://gitlab.com/gitlab-org/gitlab/issues?label_name%5B%5D=bug and verify the issue you're about to submit isn't a duplicate. ---> diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md index 0cac769bd55..90c56417dbc 100644 --- a/.gitlab/issue_templates/Feature Flag Roll Out.md +++ b/.gitlab/issue_templates/Feature Flag Roll Out.md @@ -24,7 +24,7 @@ Remove the `:feature_name` feature flag ... If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example. -- `gitlab-org/gitlab-ce`/`gitlab-org/gitlab-ee` projects +- `gitlab-org/gitlab` project - `gitlab-org`/`gitlab-com` groups - ... diff --git a/.gitlab/issue_templates/Problem_Validation.md b/.gitlab/issue_templates/Problem_Validation.md index d2bab21eb06..7440b41cf0b 100644 --- a/.gitlab/issue_templates/Problem_Validation.md +++ b/.gitlab/issue_templates/Problem_Validation.md @@ -26,7 +26,7 @@ ## Confidence - diff --git a/.gitlab/issue_templates/Security Release.md b/.gitlab/issue_templates/Security Release.md index 3e60274623e..e6e5d731d96 100644 --- a/.gitlab/issue_templates/Security Release.md +++ b/.gitlab/issue_templates/Security Release.md @@ -18,13 +18,7 @@ Set the title to: `Security Release: 12.2.X, 12.1.X, and 12.0.X` ## Security Issues: -### CE - -* {https://gitlab.com/gitlab-org/gitlab-ce/issues link} - -### EE - -* {https://gitlab.com/gitlab-org/gitlab-ee/issues link} +* {https://gitlab.com/gitlab-org/gitlab/issues link} ## Security Issues in dev.gitlab.org: diff --git a/.gitlab/issue_templates/Test plan.md b/.gitlab/issue_templates/Test plan.md index f194adebc87..a202c0bf546 100644 --- a/.gitlab/issue_templates/Test plan.md +++ b/.gitlab/issue_templates/Test plan.md @@ -2,7 +2,7 @@ @@ -63,7 +63,7 @@ intersection of Components and Attributes. Some features might be simple enough that they only involve one Component, while more complex features could involve multiple or even all. -Example (from https://gitlab.com/gitlab-org/gitlab-ce/issues/50353): +Example (from https://gitlab.com/gitlab-org/gitlab-foss/issues/50353): * Repository is * Intuitive * It's easy to select the desired file template diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue index 346dc470a59..60aaef656a0 100644 --- a/app/assets/javascripts/registry/components/app.vue +++ b/app/assets/javascripts/registry/components/app.vue @@ -47,7 +47,7 @@ export default { dockerConnectionErrorText() { return sprintf( s__(`ContainerRegistry|We are having trouble connecting to Docker, which could be due to an - issue with your project name or path. + issue with your project name or path. %{docLinkStart}More Information%{docLinkEnd}`), { docLinkStart: ``, @@ -58,8 +58,8 @@ export default { }, introText() { return sprintf( - s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every - project can have its own space to store its Docker images. + s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every + project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`), { docLinkStart: ``, @@ -109,7 +109,7 @@ export default { :svg-path="containersErrorImage" > diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index bfb2305c48c..41bd9225c6e 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -49,7 +49,7 @@ export default { } }, handleDeleteRepository() { - this.deleteItem(this.repo) + return this.deleteItem(this.repo) .then(() => { createFlash(__('This container registry has been scheduled for deletion.'), 'notice'); this.fetchRepos(); @@ -67,7 +67,8 @@ export default {
- {{ repo.name }} + + {{ repo.name }} + > + + @@ -223,9 +224,9 @@ export default { /> - - {{ item.shortRevision }} - + {{ + item.shortRevision + }} {{ formatSize(item.size) }} @@ -236,9 +237,9 @@ export default { - - {{ timeFormated(item.createdAt) }} - + {{ + timeFormated(item.createdAt) + }} @@ -262,6 +263,7 @@ export default { v-if="shouldRenderPagination" :change="onPageChange" :page-info="repo.pagination" + class="js-registry-pagination" /> diff --git a/app/finders/clusters/kubernetes_namespace_finder.rb b/app/finders/clusters/kubernetes_namespace_finder.rb index e947796c1e7..82df96ed79e 100644 --- a/app/finders/clusters/kubernetes_namespace_finder.rb +++ b/app/finders/clusters/kubernetes_namespace_finder.rb @@ -2,12 +2,12 @@ module Clusters class KubernetesNamespaceFinder - attr_reader :cluster, :project, :environment_slug + attr_reader :cluster, :project, :environment_name - def initialize(cluster, project:, environment_slug:, allow_blank_token: false) + def initialize(cluster, project:, environment_name:, allow_blank_token: false) @cluster = cluster @project = project - @environment_slug = environment_slug + @environment_name = environment_name @allow_blank_token = allow_blank_token end @@ -20,7 +20,11 @@ module Clusters attr_reader :allow_blank_token def find_namespace(with_environment:) - relation = with_environment ? namespaces.with_environment_slug(environment_slug) : namespaces + relation = if with_environment + namespaces.with_environment_name(environment_name) + else + namespaces + end relation.find_by_project_id(project.id) end diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 2df30e8ac36..49bed479c02 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -172,7 +172,7 @@ module Clusters persisted_namespace = Clusters::KubernetesNamespaceFinder.new( self, project: project, - environment_slug: environment.slug + environment_name: environment.name ).execute persisted_namespace&.namespace || Gitlab::Kubernetes::DefaultNamespace.new(self, project: project).from_environment_slug(environment.slug) diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb index 69a2b99fcb6..42332bdc193 100644 --- a/app/models/clusters/kubernetes_namespace.rb +++ b/app/models/clusters/kubernetes_namespace.rb @@ -27,7 +27,7 @@ module Clusters algorithm: 'aes-256-cbc' scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) } - scope :with_environment_slug, -> (slug) { joins(:environment).where(environments: { slug: slug }) } + scope :with_environment_name, -> (name) { joins(:environment).where(environments: { name: name }) } def token_name "#{namespace}-token" diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 89b50d8e8ff..aa2a7f3d7f1 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -105,19 +105,11 @@ module Clusters private - ## - # Environment slug can be predicted given an environment - # name, so even if the environment isn't persisted yet we - # still know what to look for. - def environment_slug(name) - Gitlab::Slug::Environment.new(name).generate - end - def find_persisted_namespace(project, environment_name:) Clusters::KubernetesNamespaceFinder.new( cluster, project: project, - environment_slug: environment_slug(environment_name) + environment_name: environment_name ).execute end diff --git a/16790-render-xml-artifacts.yml b/changelogs/unreleased/16790-render-xml-artifacts.yml similarity index 100% rename from 16790-render-xml-artifacts.yml rename to changelogs/unreleased/16790-render-xml-artifacts.yml diff --git a/changelogs/unreleased/sh-fix-any-approver-handling.yml b/changelogs/unreleased/sh-fix-any-approver-handling.yml new file mode 100644 index 00000000000..4230dce3b74 --- /dev/null +++ b/changelogs/unreleased/sh-fix-any-approver-handling.yml @@ -0,0 +1,5 @@ +--- +title: Fix bug that caused a merge to show an error message +merge_request: 17466 +author: +type: fixed diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index 448fb0f9f5a..f01b83f4bd2 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -134,7 +134,7 @@ graph RL; M[coverage]; N[pages]; O[static-analysis]; - P["package-and-qa-manual:master
(master schedule only)"]; + P["schedule:package-and-qa
(master schedule only)"]; Q[package-and-qa]; R[package-and-qa-manual]; diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb index f448d55f00a..9950e1dec55 100644 --- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb +++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb @@ -36,7 +36,7 @@ module Gitlab Clusters::KubernetesNamespaceFinder.new( deployment_cluster, project: environment.project, - environment_slug: environment.slug, + environment_name: environment.name, allow_blank_token: true ).execute end diff --git a/spec/finders/clusters/kubernetes_namespace_finder_spec.rb b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb index 8beba0b99a4..7d9c4daa0fe 100644 --- a/spec/finders/clusters/kubernetes_namespace_finder_spec.rb +++ b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Clusters::KubernetesNamespaceFinder do described_class.new( cluster, project: project, - environment_slug: 'production', + environment_name: 'production', allow_blank_token: allow_blank_token ) end @@ -22,8 +22,8 @@ RSpec.describe Clusters::KubernetesNamespaceFinder do end describe '#execute' do - let(:production) { create(:environment, project: project, slug: 'production') } - let(:staging) { create(:environment, project: project, slug: 'staging') } + let(:production) { create(:environment, project: project, name: 'production') } + let(:staging) { create(:environment, project: project, name: 'staging') } let(:cluster) { create(:cluster, :group, :provided_by_user) } let(:project) { create(:project) } diff --git a/spec/frontend/registry/components/app_spec.js b/spec/frontend/registry/components/app_spec.js new file mode 100644 index 00000000000..190af5c11cd --- /dev/null +++ b/spec/frontend/registry/components/app_spec.js @@ -0,0 +1,121 @@ +import registry from '~/registry/components/app.vue'; +import { mount } from '@vue/test-utils'; +import { TEST_HOST } from '../../helpers/test_constants'; +import { reposServerResponse, parsedReposServerResponse } from '../mock_data'; + +describe('Registry List', () => { + let wrapper; + + const findCollapsibleContainer = w => w.findAll({ name: 'CollapsibeContainerRegisty' }); + const findNoContainerImagesText = w => w.find('.js-no-container-images-text'); + const findSpinner = w => w.find('.gl-spinner'); + const findCharacterErrorText = w => w.find('.js-character-error-text'); + + const propsData = { + endpoint: `${TEST_HOST}/foo`, + helpPagePath: 'foo', + noContainersImage: 'foo', + containersErrorImage: 'foo', + repositoryUrl: 'foo', + }; + + const setMainEndpoint = jest.fn(); + const fetchRepos = jest.fn(); + + const methods = { + setMainEndpoint, + fetchRepos, + }; + + beforeEach(() => { + wrapper = mount(registry, { + propsData, + computed: { + repos() { + return parsedReposServerResponse; + }, + }, + methods, + }); + }); + + describe('with data', () => { + it('should render a list of CollapsibeContainerRegisty', () => { + const containers = findCollapsibleContainer(wrapper); + expect(wrapper.vm.repos.length).toEqual(reposServerResponse.length); + expect(containers.length).toEqual(reposServerResponse.length); + }); + }); + + describe('without data', () => { + let localWrapper; + beforeEach(() => { + localWrapper = mount(registry, { + propsData, + computed: { + repos() { + return []; + }, + }, + methods, + }); + }); + + it('should render empty message', () => { + const noContainerImagesText = findNoContainerImagesText(localWrapper); + expect(noContainerImagesText.text()).toEqual( + 'With the Container Registry, every project can have its own space to store its Docker images. More Information', + ); + }); + }); + + describe('while loading data', () => { + let localWrapper; + + beforeEach(() => { + localWrapper = mount(registry, { + propsData, + computed: { + repos() { + return []; + }, + isLoading() { + return true; + }, + }, + methods, + }); + }); + + it('should render a loading spinner', () => { + const spinner = findSpinner(localWrapper); + expect(spinner.exists()).toBe(true); + }); + }); + + describe('invalid characters in path', () => { + let localWrapper; + + beforeEach(() => { + localWrapper = mount(registry, { + propsData: { + ...propsData, + characterError: true, + }, + computed: { + repos() { + return []; + }, + }, + methods, + }); + }); + + it('should render invalid characters error message', () => { + const characterErrorText = findCharacterErrorText(localWrapper); + expect(characterErrorText.text()).toEqual( + 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More Information', + ); + }); + }); +}); diff --git a/spec/frontend/registry/components/collapsible_container_spec.js b/spec/frontend/registry/components/collapsible_container_spec.js new file mode 100644 index 00000000000..0fe4338f1ba --- /dev/null +++ b/spec/frontend/registry/components/collapsible_container_spec.js @@ -0,0 +1,89 @@ +import Vue from 'vue'; +import { mount } from '@vue/test-utils'; +import collapsibleComponent from '~/registry/components/collapsible_container.vue'; +import { repoPropsData } from '../mock_data'; +import createFlash from '~/flash'; + +jest.mock('~/flash.js'); + +describe('collapsible registry container', () => { + let wrapper; + + const findDeleteBtn = w => w.find('.js-remove-repo'); + const findContainerImageTags = w => w.find('.container-image-tags'); + const findToggleRepos = w => w.findAll('.js-toggle-repo'); + + beforeEach(() => { + createFlash.mockClear(); + // This is needed due to console.error called by vue to emit a warning that stop the tests + // see https://github.com/vuejs/vue-test-utils/issues/532 + Vue.config.silent = true; + wrapper = mount(collapsibleComponent, { + propsData: { + repo: repoPropsData, + }, + }); + }); + + afterEach(() => { + Vue.config.silent = false; + }); + + describe('toggle', () => { + beforeEach(() => { + const fetchList = jest.fn(); + wrapper.setMethods({ fetchList }); + }); + + const expectIsClosed = () => { + const container = findContainerImageTags(wrapper); + expect(container.exists()).toBe(false); + expect(wrapper.vm.iconName).toEqual('angle-right'); + }; + + it('should be closed by default', () => { + expectIsClosed(); + }); + it('should be open when user clicks on closed repo', () => { + const toggleRepos = findToggleRepos(wrapper); + toggleRepos.at(0).trigger('click'); + const container = findContainerImageTags(wrapper); + expect(container.exists()).toBe(true); + expect(wrapper.vm.fetchList).toHaveBeenCalled(); + }); + it('should be closed when the user clicks on an opened repo', done => { + const toggleRepos = findToggleRepos(wrapper); + toggleRepos.at(0).trigger('click'); + Vue.nextTick(() => { + toggleRepos.at(0).trigger('click'); + Vue.nextTick(() => { + expectIsClosed(); + done(); + }); + }); + }); + }); + + describe('delete repo', () => { + it('should be possible to delete a repo', () => { + const deleteBtn = findDeleteBtn(wrapper); + expect(deleteBtn.exists()).toBe(true); + }); + + it('should call deleteItem when confirming deletion', () => { + const deleteItem = jest.fn().mockResolvedValue(); + const fetchRepos = jest.fn().mockResolvedValue(); + wrapper.setMethods({ deleteItem, fetchRepos }); + wrapper.vm.handleDeleteRepository(); + expect(wrapper.vm.deleteItem).toHaveBeenCalledWith(wrapper.vm.repo); + }); + + it('should show an error when there is API error', () => { + const deleteItem = jest.fn().mockRejectedValue('error'); + wrapper.setMethods({ deleteItem }); + return wrapper.vm.handleDeleteRepository().then(() => { + expect(createFlash).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/frontend/registry/components/table_registry_spec.js b/spec/frontend/registry/components/table_registry_spec.js new file mode 100644 index 00000000000..a2eee4aada7 --- /dev/null +++ b/spec/frontend/registry/components/table_registry_spec.js @@ -0,0 +1,211 @@ +import Vue from 'vue'; +import tableRegistry from '~/registry/components/table_registry.vue'; +import { mount } from '@vue/test-utils'; +import { repoPropsData } from '../mock_data'; + +const [firstImage, secondImage] = repoPropsData.list; + +describe('table registry', () => { + let wrapper; + + const findSelectAllCheckbox = w => w.find('.js-select-all-checkbox > input'); + const findSelectCheckboxes = w => w.findAll('.js-select-checkbox > input'); + const findDeleteButton = w => w.find('.js-delete-registry'); + const findDeleteButtonsRow = w => w.findAll('.js-delete-registry-row'); + const findPagination = w => w.find('.js-registry-pagination'); + const bulkDeletePath = 'path'; + + beforeEach(() => { + // This is needed due to console.error called by vue to emit a warning that stop the tests + // see https://github.com/vuejs/vue-test-utils/issues/532 + Vue.config.silent = true; + wrapper = mount(tableRegistry, { + propsData: { + repo: repoPropsData, + }, + }); + }); + + afterEach(() => { + Vue.config.silent = false; + }); + + describe('rendering', () => { + it('should render a table with the registry list', () => { + expect(wrapper.findAll('.registry-image-row').length).toEqual(repoPropsData.list.length); + }); + + it('should render registry tag', () => { + const tds = wrapper.findAll('.registry-image-row td'); + expect(tds.at(0).classes()).toContain('check'); + expect(tds.at(1).html()).toContain(repoPropsData.list[0].tag); + expect(tds.at(2).html()).toContain(repoPropsData.list[0].shortRevision); + expect(tds.at(3).html()).toContain(repoPropsData.list[0].layers); + expect(tds.at(3).html()).toContain(repoPropsData.list[0].size); + expect(tds.at(4).html()).toContain(wrapper.vm.timeFormated(repoPropsData.list[0].createdAt)); + }); + }); + + describe('multi select', () => { + it('selecting a row should enable delete button', done => { + const deleteBtn = findDeleteButton(wrapper); + const checkboxes = findSelectCheckboxes(wrapper); + + expect(deleteBtn.attributes('disabled')).toBe('disabled'); + + checkboxes.at(0).trigger('click'); + Vue.nextTick(() => { + expect(deleteBtn.attributes('disabled')).toEqual(undefined); + done(); + }); + }); + + it('selecting all checkbox should select all rows and enable delete button', done => { + const selectAll = findSelectAllCheckbox(wrapper); + const checkboxes = findSelectCheckboxes(wrapper); + selectAll.trigger('click'); + + Vue.nextTick(() => { + const checked = checkboxes.filter(w => w.element.checked); + expect(checked.length).toBe(checkboxes.length); + done(); + }); + }); + + it('deselecting select all checkbox should deselect all rows and disable delete button', done => { + const checkboxes = findSelectCheckboxes(wrapper); + const selectAll = findSelectAllCheckbox(wrapper); + selectAll.trigger('click'); + selectAll.trigger('click'); + + Vue.nextTick(() => { + const checked = checkboxes.filter(w => !w.element.checked); + expect(checked.length).toBe(checkboxes.length); + done(); + }); + }); + + it('should delete multiple items when multiple items are selected', done => { + const multiDeleteItems = jest.fn().mockResolvedValue(); + wrapper.setMethods({ multiDeleteItems }); + const selectAll = findSelectAllCheckbox(wrapper); + selectAll.trigger('click'); + + Vue.nextTick(() => { + const deleteBtn = findDeleteButton(wrapper); + expect(wrapper.vm.itemsToBeDeleted).toEqual([0, 1]); + expect(deleteBtn.attributes('disabled')).toEqual(undefined); + wrapper.vm.handleMultipleDelete(); + + Vue.nextTick(() => { + expect(wrapper.vm.itemsToBeDeleted).toEqual([]); + expect(wrapper.vm.multiDeleteItems).toHaveBeenCalledWith({ + path: bulkDeletePath, + items: [firstImage.tag, secondImage.tag], + }); + done(); + }); + }); + }); + + it('should show an error message if bulkDeletePath is not set', () => { + const showError = jest.fn(); + wrapper.setMethods({ showError }); + wrapper.setProps({ + repo: { + ...repoPropsData, + tagsPath: null, + }, + }); + wrapper.vm.handleMultipleDelete(); + expect(wrapper.vm.showError).toHaveBeenCalled(); + }); + }); + + describe('delete registry', () => { + beforeEach(() => { + wrapper.setData({ itemsToBeDeleted: [0] }); + }); + + it('should be possible to delete a registry', () => { + const deleteBtn = findDeleteButton(wrapper); + const deleteBtns = findDeleteButtonsRow(wrapper); + expect(wrapper.vm.itemsToBeDeleted).toEqual([0]); + expect(deleteBtn).toBeDefined(); + expect(deleteBtn.attributes('disable')).toBe(undefined); + expect(deleteBtns.is('button')).toBe(true); + }); + + it('should allow deletion row by row', () => { + const deleteBtns = findDeleteButtonsRow(wrapper); + const deleteSingleItem = jest.fn(); + const deleteItem = jest.fn().mockResolvedValue(); + wrapper.setMethods({ deleteSingleItem, deleteItem }); + deleteBtns.at(0).trigger('click'); + expect(wrapper.vm.deleteSingleItem).toHaveBeenCalledWith(0); + wrapper.vm.handleSingleDelete(1); + expect(wrapper.vm.deleteItem).toHaveBeenCalledWith(1); + }); + }); + + describe('pagination', () => { + let localWrapper = null; + const repo = { + repoPropsData, + pagination: { + total: 20, + perPage: 2, + nextPage: 2, + }, + }; + + beforeEach(() => { + localWrapper = mount(tableRegistry, { + propsData: { + repo, + }, + }); + }); + + it('should exist', () => { + const pagination = findPagination(localWrapper); + expect(pagination.exists()).toBe(true); + }); + it('should be visible when pagination is needed', () => { + const pagination = findPagination(localWrapper); + expect(pagination.isVisible()).toBe(true); + localWrapper.setProps({ + repo: { + pagination: { + total: 0, + perPage: 10, + }, + }, + }); + expect(localWrapper.vm.shouldRenderPagination).toBe(false); + }); + it('should have a change function that update the list when run', () => { + const fetchList = jest.fn().mockResolvedValue(); + localWrapper.setMethods({ fetchList }); + localWrapper.vm.onPageChange(1); + expect(localWrapper.vm.fetchList).toHaveBeenCalledWith({ repo, page: 1 }); + }); + }); + + describe('modal content', () => { + it('should show the singular title and image name when deleting a single image', () => { + wrapper.setData({ itemsToBeDeleted: [1] }); + wrapper.vm.setModalDescription(0); + expect(wrapper.vm.modalTitle).toBe('Remove image'); + expect(wrapper.vm.modalDescription).toContain(firstImage.tag); + }); + + it('should show the plural title and image count when deleting more than one image', () => { + wrapper.setData({ itemsToBeDeleted: [1, 2] }); + wrapper.vm.setModalDescription(); + + expect(wrapper.vm.modalTitle).toBe('Remove images'); + expect(wrapper.vm.modalDescription).toContain('2 images'); + }); + }); +}); diff --git a/spec/javascripts/registry/mock_data.js b/spec/frontend/registry/mock_data.js similarity index 100% rename from spec/javascripts/registry/mock_data.js rename to spec/frontend/registry/mock_data.js diff --git a/spec/frontend/registry/stores/actions_spec.js b/spec/frontend/registry/stores/actions_spec.js new file mode 100644 index 00000000000..bf335904d23 --- /dev/null +++ b/spec/frontend/registry/stores/actions_spec.js @@ -0,0 +1,189 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import * as actions from '~/registry/stores/actions'; +import * as types from '~/registry/stores/mutation_types'; +import { TEST_HOST } from '../../helpers/test_constants'; +import testAction from '../../helpers/vuex_action_helper'; +import createFlash from '~/flash'; + +import { + reposServerResponse, + registryServerResponse, + parsedReposServerResponse, +} from '../mock_data'; + +jest.mock('~/flash.js'); + +describe('Actions Registry Store', () => { + let mock; + let state; + + beforeEach(() => { + mock = new MockAdapter(axios); + state = { + endpoint: `${TEST_HOST}/endpoint.json`, + }; + }); + + afterEach(() => { + mock.restore(); + }); + + describe('fetchRepos', () => { + beforeEach(() => { + mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, reposServerResponse, {}); + }); + + it('should set receveived repos', done => { + testAction( + actions.fetchRepos, + null, + state, + [ + { type: types.TOGGLE_MAIN_LOADING }, + { type: types.TOGGLE_MAIN_LOADING }, + { type: types.SET_REPOS_LIST, payload: reposServerResponse }, + ], + [], + done, + ); + }); + + it('should create flash on API error', done => { + testAction( + actions.fetchRepos, + null, + { + endpoint: null, + }, + [{ type: types.TOGGLE_MAIN_LOADING }, { type: types.TOGGLE_MAIN_LOADING }], + [], + () => { + expect(createFlash).toHaveBeenCalled(); + done(); + }, + ); + }); + }); + + describe('fetchList', () => { + let repo; + beforeEach(() => { + state.repos = parsedReposServerResponse; + [, repo] = state.repos; + mock.onGet(repo.tagsPath).replyOnce(200, registryServerResponse, {}); + }); + + it('should set received list', done => { + testAction( + actions.fetchList, + { repo }, + state, + [ + { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo }, + { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo }, + { + type: types.SET_REGISTRY_LIST, + payload: { + repo, + resp: registryServerResponse, + headers: expect.anything(), + }, + }, + ], + [], + done, + ); + }); + + it('should create flash on API error', done => { + const updatedRepo = { + ...repo, + tagsPath: null, + }; + testAction( + actions.fetchList, + { + repo: updatedRepo, + }, + state, + [ + { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: updatedRepo }, + { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: updatedRepo }, + ], + [], + () => { + expect(createFlash).toHaveBeenCalled(); + done(); + }, + ); + }); + }); + + describe('setMainEndpoint', () => { + it('should commit set main endpoint', done => { + testAction( + actions.setMainEndpoint, + 'endpoint', + state, + [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }], + [], + done, + ); + }); + }); + + describe('toggleLoading', () => { + it('should commit toggle main loading', done => { + testAction( + actions.toggleLoading, + null, + state, + [{ type: types.TOGGLE_MAIN_LOADING }], + [], + done, + ); + }); + }); + + describe('deleteItem and multiDeleteItems', () => { + let deleted; + const destroyPath = `${TEST_HOST}/mygroup/myproject/container_registry/1.json`; + + const expectDelete = done => { + expect(mock.history.delete.length).toBe(1); + expect(deleted).toBe(true); + done(); + }; + + beforeEach(() => { + deleted = false; + mock.onDelete(destroyPath).replyOnce(() => { + deleted = true; + return [200]; + }); + }); + + it('deleteItem should perform DELETE request on destroyPath', done => { + testAction( + actions.deleteItem, + { + destroyPath, + }, + state, + ) + .then(() => { + expectDelete(done); + }) + .catch(done.fail); + }); + + it('multiDeleteItems should perform DELETE request on path', done => { + testAction(actions.multiDeleteItems, { path: destroyPath, items: [1] }, state) + .then(() => { + expectDelete(done); + }) + .catch(done.fail); + }); + }); +}); diff --git a/spec/frontend/registry/getters_spec.js b/spec/frontend/registry/stores/getters_spec.js similarity index 100% rename from spec/frontend/registry/getters_spec.js rename to spec/frontend/registry/stores/getters_spec.js diff --git a/spec/javascripts/registry/stores/mutations_spec.js b/spec/frontend/registry/stores/mutations_spec.js similarity index 100% rename from spec/javascripts/registry/stores/mutations_spec.js rename to spec/frontend/registry/stores/mutations_spec.js diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js deleted file mode 100644 index 5ea3f85a247..00000000000 --- a/spec/javascripts/registry/components/app_spec.js +++ /dev/null @@ -1,129 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import Vue from 'vue'; -import registry from '~/registry/components/app.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { TEST_HOST } from 'spec/test_constants'; -import { reposServerResponse } from '../mock_data'; - -describe('Registry List', () => { - const Component = Vue.extend(registry); - const props = { - endpoint: `${TEST_HOST}/foo`, - helpPagePath: 'foo', - noContainersImage: 'foo', - containersErrorImage: 'foo', - repositoryUrl: 'foo', - }; - let vm; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - vm.$destroy(); - }); - - describe('with data', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, reposServerResponse); - - vm = mountComponent(Component, { ...props }); - }); - - it('should render a list of repos', done => { - setTimeout(() => { - expect(vm.$store.state.repos.length).toEqual(reposServerResponse.length); - - Vue.nextTick(() => { - expect(vm.$el.querySelectorAll('.container-image').length).toEqual( - reposServerResponse.length, - ); - done(); - }); - }, 0); - }); - - describe('delete repository', () => { - it('should be possible to delete a repo', done => { - setTimeout(() => { - Vue.nextTick(() => { - expect(vm.$el.querySelector('.container-image-head .js-remove-repo')).toBeDefined(); - done(); - }); - }, 0); - }); - }); - - describe('toggle repository', () => { - it('should open the container', done => { - setTimeout(() => { - Vue.nextTick(() => { - vm.$el.querySelector('.js-toggle-repo').click(); - Vue.nextTick(() => { - expect( - vm.$el.querySelector('.js-toggle-repo use').getAttribute('xlink:href'), - ).toContain('angle-up'); - done(); - }); - }); - }, 0); - }); - }); - }); - - describe('without data', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []); - - vm = mountComponent(Component, { ...props }); - }); - - it('should render empty message', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-no-container-images-text').textContent).toEqual( - 'With the Container Registry, every project can have its own space to store its Docker images. More Information', - ); - done(); - }, 0); - }); - }); - - describe('while loading data', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []); - - vm = mountComponent(Component, { ...props }); - }); - - it('should render a loading spinner', done => { - Vue.nextTick(() => { - expect(vm.$el.querySelector('.gl-spinner')).not.toBe(null); - done(); - }); - }); - }); - - describe('invalid characters in path', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []); - - vm = mountComponent(Component, { - ...props, - characterError: true, - }); - }); - - it('should render invalid characters error message', done => { - setTimeout(() => { - expect(vm.$el.querySelector('p')).not.toContain( - 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More information', - ); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js deleted file mode 100644 index 2a5d8dd11da..00000000000 --- a/spec/javascripts/registry/components/collapsible_container_spec.js +++ /dev/null @@ -1,87 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import Vue from 'vue'; -import collapsibleComponent from '~/registry/components/collapsible_container.vue'; -import store from '~/registry/stores'; -import * as types from '~/registry/stores/mutation_types'; - -import { repoPropsData, registryServerResponse, reposServerResponse } from '../mock_data'; - -describe('collapsible registry container', () => { - let vm; - let mock; - const Component = Vue.extend(collapsibleComponent); - - const findDeleteBtn = () => vm.$el.querySelector('.js-remove-repo'); - - beforeEach(() => { - mock = new MockAdapter(axios); - - mock.onGet(repoPropsData.tagsPath).replyOnce(200, registryServerResponse, {}); - - store.commit(types.SET_REPOS_LIST, reposServerResponse); - - vm = new Component({ - store, - propsData: { - repo: repoPropsData, - }, - }).$mount(); - }); - - afterEach(() => { - mock.restore(); - vm.$destroy(); - }); - - describe('toggle', () => { - it('should be closed by default', () => { - expect(vm.$el.querySelector('.container-image-tags')).toBe(null); - expect(vm.iconName).toEqual('angle-right'); - }); - - it('should be open when user clicks on closed repo', done => { - vm.$el.querySelector('.js-toggle-repo').click(); - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.container-image-tags')).not.toBeNull(); - expect(vm.iconName).toEqual('angle-up'); - - done(); - }); - }); - - it('should be closed when the user clicks on an opened repo', done => { - vm.$el.querySelector('.js-toggle-repo').click(); - - Vue.nextTick(() => { - vm.$el.querySelector('.js-toggle-repo').click(); - setTimeout(() => { - Vue.nextTick(() => { - expect(vm.$el.querySelector('.container-image-tags')).toBe(null); - expect(vm.iconName).toEqual('angle-right'); - done(); - }); - }); - }); - }); - }); - - describe('delete repo', () => { - it('should be possible to delete a repo', () => { - expect(findDeleteBtn()).not.toBeNull(); - }); - - it('should call deleteItem when confirming deletion', done => { - findDeleteBtn().click(); - spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve()); - - Vue.nextTick(() => { - document.querySelector(`#${vm.modalId} .btn-danger`).click(); - - expect(vm.deleteItem).toHaveBeenCalledWith(vm.repo); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js deleted file mode 100644 index 9c7439206ef..00000000000 --- a/spec/javascripts/registry/components/table_registry_spec.js +++ /dev/null @@ -1,189 +0,0 @@ -import Vue from 'vue'; -import tableRegistry from '~/registry/components/table_registry.vue'; -import store from '~/registry/stores'; -import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { repoPropsData } from '../mock_data'; - -const [firstImage, secondImage] = repoPropsData.list; - -describe('table registry', () => { - let vm; - const Component = Vue.extend(tableRegistry); - const bulkDeletePath = 'path'; - - const findDeleteBtn = () => vm.$el.querySelector('.js-delete-registry'); - const findDeleteBtnRow = () => vm.$el.querySelector('.js-delete-registry-row'); - const findSelectAllCheckbox = () => vm.$el.querySelector('.js-select-all-checkbox > input'); - const findAllRowCheckboxes = () => - Array.from(vm.$el.querySelectorAll('.js-select-checkbox input')); - const confirmationModal = (child = '') => document.querySelector(`#${vm.modalId} ${child}`); - - const createComponent = () => { - vm = mountComponentWithStore(Component, { - store, - props: { - repo: repoPropsData, - }, - }); - }; - - const selectAllCheckboxes = () => vm.selectAll(); - const deselectAllCheckboxes = () => vm.deselectAll(); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('rendering', () => { - it('should render a table with the registry list', () => { - expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length); - }); - - it('should render registry tag', () => { - const textRendered = vm.$el - .querySelector('.table tbody tr') - .textContent.trim() - // replace additional whitespace characters (e.g. new lines) with a single empty space - .replace(/\s\s+/g, ' '); - - expect(textRendered).toContain(repoPropsData.list[0].tag); - expect(textRendered).toContain(repoPropsData.list[0].shortRevision); - expect(textRendered).toContain(repoPropsData.list[0].layers); - expect(textRendered).toContain(repoPropsData.list[0].size); - }); - }); - - describe('multi select', () => { - it('should support multiselect and selecting a row should enable delete button', done => { - findSelectAllCheckbox().click(); - selectAllCheckboxes(); - - expect(findSelectAllCheckbox().checked).toBe(true); - - Vue.nextTick(() => { - expect(findDeleteBtn().disabled).toBe(false); - done(); - }); - }); - - it('selecting all checkbox should select all rows and enable delete button', done => { - selectAllCheckboxes(); - - Vue.nextTick(() => { - const checkedValues = findAllRowCheckboxes().filter(x => x.checked); - - expect(checkedValues.length).toBe(repoPropsData.list.length); - done(); - }); - }); - - it('deselecting select all checkbox should deselect all rows and disable delete button', done => { - selectAllCheckboxes(); - deselectAllCheckboxes(); - - Vue.nextTick(() => { - const checkedValues = findAllRowCheckboxes().filter(x => x.checked); - - expect(checkedValues.length).toBe(0); - done(); - }); - }); - - it('should delete multiple items when multiple items are selected', done => { - selectAllCheckboxes(); - - Vue.nextTick(() => { - expect(vm.itemsToBeDeleted).toEqual([0, 1]); - expect(findDeleteBtn().disabled).toBe(false); - - findDeleteBtn().click(); - spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve()); - - Vue.nextTick(() => { - const modal = confirmationModal(); - confirmationModal('.btn-danger').click(); - - expect(modal).toExist(); - - Vue.nextTick(() => { - expect(vm.itemsToBeDeleted).toEqual([]); - expect(vm.multiDeleteItems).toHaveBeenCalledWith({ - path: bulkDeletePath, - items: [firstImage.tag, secondImage.tag], - }); - done(); - }); - }); - }); - }); - }); - - describe('delete registry', () => { - beforeEach(() => { - vm.itemsToBeDeleted = [0]; - }); - - it('should be possible to delete a registry', done => { - Vue.nextTick(() => { - expect(vm.itemsToBeDeleted).toEqual([0]); - expect(findDeleteBtn()).toBeDefined(); - expect(findDeleteBtn().disabled).toBe(false); - expect(findDeleteBtnRow()).toBeDefined(); - done(); - }); - }); - - it('should call deleteItems and reset itemsToBeDeleted when confirming deletion', done => { - Vue.nextTick(() => { - expect(vm.itemsToBeDeleted).toEqual([0]); - expect(findDeleteBtn().disabled).toBe(false); - findDeleteBtn().click(); - spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve()); - - Vue.nextTick(() => { - confirmationModal('.btn-danger').click(); - - expect(vm.itemsToBeDeleted).toEqual([]); - expect(vm.multiDeleteItems).toHaveBeenCalledWith({ - path: bulkDeletePath, - items: [firstImage.tag], - }); - done(); - }); - }); - }); - }); - - describe('pagination', () => { - it('should be possible to change the page', () => { - expect(vm.$el.querySelector('.gl-pagination')).toBeDefined(); - }); - }); - - describe('modal content', () => { - it('should show the singular title and image name when deleting a single image', done => { - findDeleteBtnRow().click(); - - Vue.nextTick(() => { - expect(vm.modalTitle).toBe('Remove image'); - expect(vm.modalDescription).toContain(firstImage.tag); - done(); - }); - }); - - it('should show the plural title and image count when deleting more than one image', done => { - selectAllCheckboxes(); - vm.setModalDescription(); - - Vue.nextTick(() => { - expect(vm.modalTitle).toBe('Remove images'); - expect(vm.modalDescription).toContain('2 images'); - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js deleted file mode 100644 index 0613ec8e0f1..00000000000 --- a/spec/javascripts/registry/stores/actions_spec.js +++ /dev/null @@ -1,132 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import * as actions from '~/registry/stores/actions'; -import * as types from '~/registry/stores/mutation_types'; -import state from '~/registry/stores/state'; -import { TEST_HOST } from 'spec/test_constants'; -import testAction from '../../helpers/vuex_action_helper'; -import { - reposServerResponse, - registryServerResponse, - parsedReposServerResponse, -} from '../mock_data'; - -describe('Actions Registry Store', () => { - let mockedState; - let mock; - - beforeEach(() => { - mockedState = state(); - mockedState.endpoint = `${TEST_HOST}/endpoint.json`; - mock = new MockAdapter(axios); - }); - - afterEach(() => { - mock.restore(); - }); - - describe('server requests', () => { - describe('fetchRepos', () => { - beforeEach(() => { - mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, reposServerResponse, {}); - }); - - it('should set receveived repos', done => { - testAction( - actions.fetchRepos, - null, - mockedState, - [ - { type: types.TOGGLE_MAIN_LOADING }, - { type: types.TOGGLE_MAIN_LOADING }, - { type: types.SET_REPOS_LIST, payload: reposServerResponse }, - ], - [], - done, - ); - }); - }); - - describe('fetchList', () => { - let repo; - beforeEach(() => { - mockedState.repos = parsedReposServerResponse; - [, repo] = mockedState.repos; - - mock.onGet(repo.tagsPath).replyOnce(200, registryServerResponse, {}); - }); - - it('should set received list', done => { - testAction( - actions.fetchList, - { repo }, - mockedState, - [ - { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo }, - { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo }, - { - type: types.SET_REGISTRY_LIST, - payload: { - repo, - resp: registryServerResponse, - headers: jasmine.anything(), - }, - }, - ], - [], - done, - ); - }); - }); - }); - - describe('setMainEndpoint', () => { - it('should commit set main endpoint', done => { - testAction( - actions.setMainEndpoint, - 'endpoint', - mockedState, - [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }], - [], - done, - ); - }); - }); - - describe('toggleLoading', () => { - it('should commit toggle main loading', done => { - testAction( - actions.toggleLoading, - null, - mockedState, - [{ type: types.TOGGLE_MAIN_LOADING }], - [], - done, - ); - }); - }); - - describe('deleteItem', () => { - it('should perform DELETE request on destroyPath', done => { - const destroyPath = `${TEST_HOST}/mygroup/myproject/container_registry/1.json`; - let deleted = false; - mock.onDelete(destroyPath).replyOnce(() => { - deleted = true; - return [200]; - }); - testAction( - actions.deleteItem, - { - destroyPath, - }, - mockedState, - ) - .then(() => { - expect(mock.history.delete.length).toBe(1); - expect(deleted).toBe(true); - done(); - }) - .catch(done.fail); - }); - }); -}); diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index df6263eeed2..06f454808e3 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -546,7 +546,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do before do expect(Clusters::KubernetesNamespaceFinder).to receive(:new) - .with(cluster, project: environment.project, environment_slug: environment.slug) + .with(cluster, project: environment.project, environment_name: environment.name) .and_return(double(execute: persisted_namespace)) end diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb index d4e3a0ac84d..2920bbf2b58 100644 --- a/spec/models/clusters/kubernetes_namespace_spec.rb +++ b/spec/models/clusters/kubernetes_namespace_spec.rb @@ -24,13 +24,13 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do end end - describe '.with_environment_slug' do + describe '.with_environment_name' do let(:cluster) { create(:cluster, :group) } - let(:environment) { create(:environment, slug: slug) } + let(:environment) { create(:environment, name: name) } - let(:slug) { 'production' } + let(:name) { 'production' } - subject { described_class.with_environment_slug(slug) } + subject { described_class.with_environment_name(name) } context 'there is no associated environment' do let!(:namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: environment.project) } @@ -48,12 +48,12 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do ) end - context 'with a matching slug' do + context 'with a matching name' do it { is_expected.to eq [namespace] } end - context 'without a matching slug' do - let(:environment) { create(:environment, slug: 'staging') } + context 'without a matching name' do + let(:environment) { create(:environment, name: 'staging') } it { is_expected.to be_empty } end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 0c4cf291d20..64de6a8ab9b 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -218,7 +218,7 @@ describe Clusters::Platforms::Kubernetes do before do allow(Clusters::KubernetesNamespaceFinder).to receive(:new) - .with(cluster, project: project, environment_slug: environment_slug) + .with(cluster, project: project, environment_name: environment_name) .and_return(double(execute: persisted_namespace)) end