diff options
Diffstat (limited to 'spec')
21 files changed, 382 insertions, 323 deletions
diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb index 48297e9049e..00fdea69b60 100644 --- a/spec/features/issues/user_sorts_issues_spec.rb +++ b/spec/features/issues/user_sorts_issues_spec.rb @@ -141,7 +141,7 @@ RSpec.describe "User sorts issues" do page.within '.issues-list' do expect(page).to have_content('foo') expect(page).to have_content('bar') - expect(page).to have_content('baz') + expect(page).not_to have_content('baz') end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 31563a6326d..c22e56c3b9e 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -1018,6 +1018,8 @@ RSpec.describe IssuesFinder do end context 'filtering by due date' do + let_it_be(:issue_due_today) { create(:issue, project: project1, due_date: Date.current) } + let_it_be(:issue_due_tomorrow) { create(:issue, project: project1, due_date: 1.day.from_now) } let_it_be(:issue_overdue) { create(:issue, project: project1, due_date: 2.days.ago) } let_it_be(:issue_due_soon) { create(:issue, project: project1, due_date: 2.days.from_now) } @@ -1032,6 +1034,30 @@ RSpec.describe IssuesFinder do end end + context 'with param set to any due date' do + let(:params) { base_params.merge(due_date: Issue::AnyDueDate.name) } + + it 'returns issues with any due date' do + expect(issues).to contain_exactly(issue_due_today, issue_due_tomorrow, issue_overdue, issue_due_soon) + end + end + + context 'with param set to due today' do + let(:params) { base_params.merge(due_date: Issue::DueToday.name) } + + it 'returns issues due today' do + expect(issues).to contain_exactly(issue_due_today) + end + end + + context 'with param set to due tomorrow' do + let(:params) { base_params.merge(due_date: Issue::DueTomorrow.name) } + + it 'returns issues due today' do + expect(issues).to contain_exactly(issue_due_tomorrow) + end + end + context 'with param set to overdue' do let(:params) { base_params.merge(due_date: Issue::Overdue.name) } @@ -1043,8 +1069,8 @@ RSpec.describe IssuesFinder do context 'with param set to next month and previous two weeks' do let(:params) { base_params.merge(due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name) } - it 'returns issues from the previous two weeks and next month' do - expect(issues).to contain_exactly(issue_overdue, issue_due_soon) + it 'returns issues due in the previous two weeks and next month' do + expect(issues).to contain_exactly(issue_due_today, issue_due_tomorrow, issue_overdue, issue_due_soon) end end diff --git a/spec/frontend/environments/deployment_spec.js b/spec/frontend/environments/deployment_spec.js index a68e17fe6ac..a1b232d783e 100644 --- a/spec/frontend/environments/deployment_spec.js +++ b/spec/frontend/environments/deployment_spec.js @@ -1,11 +1,15 @@ import { mountExtended } from 'helpers/vue_test_utils_helper'; import { __, s__ } from '~/locale'; +import { formatDate } from '~/lib/utils/datetime_utility'; +import { useFakeDate } from 'helpers/fake_date'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import Deployment from '~/environments/components/deployment.vue'; import DeploymentStatusBadge from '~/environments/components/deployment_status_badge.vue'; import { resolvedEnvironment } from './graphql/mock_data'; describe('~/environments/components/deployment.vue', () => { + useFakeDate(2022, 0, 8, 16); + const deployment = resolvedEnvironment.lastDeployment; let wrapper; @@ -125,4 +129,23 @@ describe('~/environments/components/deployment.vue', () => { }); }); }); + + describe('created at time', () => { + describe('is present', () => { + it('shows the timestamp the deployment was deployed at', () => { + wrapper = createWrapper(); + const date = wrapper.findByTitle(formatDate(deployment.createdAt)); + + expect(date.text()).toBe('1 day ago'); + }); + }); + describe('is not present', () => { + it('does not show the timestamp', () => { + wrapper = createWrapper({ propsData: { deployment: { createdAt: null } } }); + const date = wrapper.findByTitle(formatDate(deployment.createdAt)); + + expect(date.exists()).toBe(false); + }); + }); + }); }); diff --git a/spec/frontend/google_cloud/components/service_accounts_form_spec.js b/spec/frontend/google_cloud/components/service_accounts_form_spec.js index 5394d0cdaef..7262e12c84d 100644 --- a/spec/frontend/google_cloud/components/service_accounts_form_spec.js +++ b/spec/frontend/google_cloud/components/service_accounts_form_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlButton, GlFormGroup, GlFormSelect } from '@gitlab/ui'; +import { GlButton, GlFormGroup, GlFormSelect, GlFormCheckbox } from '@gitlab/ui'; import ServiceAccountsForm from '~/google_cloud/components/service_accounts_form.vue'; describe('ServiceAccountsForm component', () => { @@ -9,11 +9,12 @@ describe('ServiceAccountsForm component', () => { const findAllFormGroups = () => wrapper.findAllComponents(GlFormGroup); const findAllFormSelects = () => wrapper.findAllComponents(GlFormSelect); const findAllButtons = () => wrapper.findAllComponents(GlButton); + const findCheckbox = () => wrapper.findComponent(GlFormCheckbox); const propsData = { gcpProjects: [], environments: [], cancelPath: '#cancel-url' }; beforeEach(() => { - wrapper = shallowMount(ServiceAccountsForm, { propsData }); + wrapper = shallowMount(ServiceAccountsForm, { propsData, stubs: { GlFormCheckbox } }); }); afterEach(() => { @@ -35,8 +36,8 @@ describe('ServiceAccountsForm component', () => { }); it('contains Environments form group', () => { - const formGorup = findAllFormGroups().at(1); - expect(formGorup.exists()).toBe(true); + const formGroup = findAllFormGroups().at(1); + expect(formGroup.exists()).toBe(true); }); it('contains Environments dropdown', () => { @@ -56,4 +57,14 @@ describe('ServiceAccountsForm component', () => { expect(button.text()).toBe(ServiceAccountsForm.i18n.cancelLabel); expect(button.attributes('href')).toBe('#cancel-url'); }); + + it('contains Confirmation checkbox', () => { + const checkbox = findCheckbox(); + expect(checkbox.text()).toBe(ServiceAccountsForm.i18n.checkboxLabel); + }); + + it('checkbox must be required', () => { + const checkbox = findCheckbox(); + expect(checkbox.attributes('required')).toBe('true'); + }); }); diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js index e743678ea90..dc4aa0ea5ed 100644 --- a/spec/frontend/lib/utils/number_utility_spec.js +++ b/spec/frontend/lib/utils/number_utility_spec.js @@ -4,6 +4,7 @@ import { bytesToMiB, bytesToGiB, numberToHumanSize, + numberToMetricPrefix, sum, isOdd, median, @@ -99,6 +100,21 @@ describe('Number Utils', () => { }); }); + describe('numberToMetricPrefix', () => { + it.each` + number | expected + ${123} | ${'123'} + ${1234} | ${'1.2k'} + ${12345} | ${'12.3k'} + ${123456} | ${'123.5k'} + ${1234567} | ${'1.2m'} + ${12345678} | ${'12.3m'} + ${123456789} | ${'123.5m'} + `('returns $expected given $number', ({ number, expected }) => { + expect(numberToMetricPrefix(number)).toBe(expected); + }); + }); + describe('sum', () => { it('should add up two values', () => { expect(sum(1, 2)).toEqual(3); diff --git a/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js b/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js index 23f1753c4bf..2fef3ab9293 100644 --- a/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js +++ b/spec/frontend/sidebar/components/todo_toggle/sidebar_todo_widget_spec.js @@ -1,4 +1,4 @@ -import { GlIcon } from '@gitlab/ui'; +import { GlIcon, GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; @@ -103,7 +103,7 @@ describe('Sidebar Todo Widget', () => { }); it('sets default tooltip title', () => { - expect(wrapper.find(GlIcon).attributes('title')).toBe('Add a to do'); + expect(wrapper.find(GlButton).attributes('title')).toBe('Add a to do'); }); it('when user has a to do', async () => { @@ -113,7 +113,7 @@ describe('Sidebar Todo Widget', () => { await waitForPromises(); expect(wrapper.find(GlIcon).props('name')).toBe('todo-done'); - expect(wrapper.find(GlIcon).attributes('title')).toBe('Mark as done'); + expect(wrapper.find(GlButton).attributes('title')).toBe('Mark as done'); }); it('emits `todoUpdated` event on click on icon', async () => { diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_tabs_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_tabs_spec.js index 5723e2da586..27985895c62 100644 --- a/spec/frontend/vue_shared/issuable/list/components/issuable_tabs_spec.js +++ b/spec/frontend/vue_shared/issuable/list/components/issuable_tabs_spec.js @@ -1,5 +1,6 @@ import { GlTab, GlBadge } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import { mount, shallowMount } from '@vue/test-utils'; import { setLanguage } from 'helpers/locale_helper'; import IssuableTabs from '~/vue_shared/issuable/list/components/issuable_tabs.vue'; @@ -10,17 +11,18 @@ const createComponent = ({ tabs = mockIssuableListProps.tabs, tabCounts = mockIssuableListProps.tabCounts, currentTab = mockIssuableListProps.currentTab, + truncateCounts = false, + mountFn = shallowMount, } = {}) => - mount(IssuableTabs, { + mountFn(IssuableTabs, { propsData: { tabs, tabCounts, currentTab, + truncateCounts, }, slots: { - 'nav-actions': ` - <button class="js-new-issuable">New issuable</button> - `, + 'nav-actions': `<button class="js-new-issuable">New issuable</button>`, }, }); @@ -29,7 +31,6 @@ describe('IssuableTabs', () => { beforeEach(() => { setLanguage('en'); - wrapper = createComponent(); }); afterEach(() => { @@ -40,60 +41,71 @@ describe('IssuableTabs', () => { const findAllGlBadges = () => wrapper.findAllComponents(GlBadge); const findAllGlTabs = () => wrapper.findAllComponents(GlTab); - describe('methods', () => { - describe('isTabActive', () => { - it.each` - tabName | currentTab | returnValue - ${'opened'} | ${'opened'} | ${true} - ${'opened'} | ${'closed'} | ${false} - `( - 'returns $returnValue when tab name is "$tabName" is current tab is "$currentTab"', - async ({ tabName, currentTab, returnValue }) => { - wrapper.setProps({ - currentTab, - }); - - await wrapper.vm.$nextTick(); - - expect(wrapper.vm.isTabActive(tabName)).toBe(returnValue); - }, - ); - }); + describe('tabs', () => { + it.each` + currentTab | returnValue + ${'opened'} | ${'true'} + ${'closed'} | ${undefined} + `( + 'when "$currentTab" is the selected tab, the Open tab is active=$returnValue', + ({ currentTab, returnValue }) => { + wrapper = createComponent({ currentTab }); + + const openTab = findAllGlTabs().at(0); + + expect(openTab.attributes('active')).toBe(returnValue); + }, + ); }); describe('template', () => { it('renders gl-tab for each tab within `tabs` array', () => { - const tabsEl = findAllGlTabs(); + wrapper = createComponent(); + + const tabs = findAllGlTabs(); - expect(tabsEl.exists()).toBe(true); - expect(tabsEl).toHaveLength(mockIssuableListProps.tabs.length); + expect(tabs).toHaveLength(mockIssuableListProps.tabs.length); }); - it('renders gl-badge component within a tab', () => { + it('renders gl-badge component within a tab', async () => { + wrapper = createComponent({ mountFn: mount }); + await nextTick(); + const badges = findAllGlBadges(); // Does not render `All` badge since it has an undefined count expect(badges).toHaveLength(2); - expect(badges.at(0).text()).toBe('5,000'); + expect(badges.at(0).text()).toBe('5,678'); expect(badges.at(1).text()).toBe(`${mockIssuableListProps.tabCounts.closed}`); }); it('renders contents for slot "nav-actions"', () => { - const buttonEl = wrapper.find('button.js-new-issuable'); + wrapper = createComponent(); - expect(buttonEl.exists()).toBe(true); - expect(buttonEl.text()).toBe('New issuable'); + const button = wrapper.find('button.js-new-issuable'); + + expect(button.text()).toBe('New issuable'); + }); + }); + + describe('counts', () => { + it('can display as truncated', async () => { + wrapper = createComponent({ truncateCounts: true, mountFn: mount }); + await nextTick(); + + expect(findAllGlBadges().at(0).text()).toBe('5.7k'); }); }); describe('events', () => { it('gl-tab component emits `click` event on `click` event', () => { - const tabEl = findAllGlTabs().at(0); + wrapper = createComponent(); + + const openTab = findAllGlTabs().at(0); - tabEl.vm.$emit('click', 'opened'); + openTab.vm.$emit('click', 'opened'); - expect(wrapper.emitted('click')).toBeTruthy(); - expect(wrapper.emitted('click')[0]).toEqual(['opened']); + expect(wrapper.emitted('click')).toEqual([['opened']]); }); }); }); diff --git a/spec/frontend/vue_shared/issuable/list/mock_data.js b/spec/frontend/vue_shared/issuable/list/mock_data.js index cfc7937b412..8640f4a2cd5 100644 --- a/spec/frontend/vue_shared/issuable/list/mock_data.js +++ b/spec/frontend/vue_shared/issuable/list/mock_data.js @@ -133,7 +133,7 @@ export const mockTabs = [ ]; export const mockTabCounts = { - opened: 5000, + opened: 5678, closed: 0, all: undefined, }; diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb index 1d8d3dd88bb..3f89058f44a 100644 --- a/spec/graphql/types/ci/runner_type_spec.rb +++ b/spec/graphql/types/ci/runner_type_spec.rb @@ -12,7 +12,7 @@ RSpec.describe GitlabSchema.types['CiRunner'] do id description created_at contacted_at maximum_timeout access_level active status version short_sha revision locked run_untagged ip_address runner_type tag_list project_count job_count admin_url edit_admin_url user_permissions executor_name - groups + groups projects ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/lib/bitbucket_server/representation/repo_spec.rb b/spec/lib/bitbucket_server/representation/repo_spec.rb index 7a773f47ca5..5de4360bbd0 100644 --- a/spec/lib/bitbucket_server/representation/repo_spec.rb +++ b/spec/lib/bitbucket_server/representation/repo_spec.rb @@ -9,6 +9,7 @@ RSpec.describe BitbucketServer::Representation::Repo do "slug": "rouge", "id": 1, "name": "rouge", + "description": "Rogue Repo", "scmId": "git", "state": "AVAILABLE", "statusMessage": "Available", @@ -17,7 +18,7 @@ RSpec.describe BitbucketServer::Representation::Repo do "key": "TEST", "id": 1, "name": "test", - "description": "Test", + "description": "Test Project", "public": false, "type": "NORMAL", "links": { @@ -73,7 +74,7 @@ RSpec.describe BitbucketServer::Representation::Repo do end describe '#description' do - it { expect(subject.description).to eq('Test') } + it { expect(subject.description).to eq('Rogue Repo') } end describe '#full_name' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb index 0a592395c3a..375841ce236 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_deployments_spec.rb @@ -47,18 +47,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::CreateDeployments do expect(job.deployment).to be_nil end end - - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it 'does not create a deployment record' do - expect { subject }.not_to change { Deployment.count } - - expect(job.deployment).to be_nil - end - end end context 'when a pipeline contains a teardown job' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb index 253928e1a19..6a7d9b58a05 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_environments_spec.rb @@ -57,18 +57,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureEnvironments do expect(job.persisted_environment).to be_nil end end - - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it 'does not create any environments' do - expect { subject }.not_to change { Environment.count } - - expect(job.persisted_environment).to be_nil - end - end end context 'when a pipeline contains a teardown job' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb index 87df5a3e21b..571455d6279 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/ensure_resource_groups_spec.rb @@ -60,18 +60,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::EnsureResourceGroups do expect(job.resource_group).to be_nil end end - - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it 'does not create any resource groups' do - expect { subject }.not_to change { Ci::ResourceGroup.count } - - expect(job.resource_group).to be_nil - end - end end context 'when a pipeline does not contain a job that requires a resource group' do diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index 2f9fcd7caac..49505d397c2 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -411,171 +411,6 @@ RSpec.describe Gitlab::Ci::Pipeline::Seed::Build do describe '#to_resource' do subject { seed_build.to_resource } - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - context 'when job is Ci::Build' do - it { is_expected.to be_a(::Ci::Build) } - it { is_expected.to be_valid } - - shared_examples_for 'deployment job' do - it 'returns a job with deployment' do - expect { subject }.to change { Environment.count }.by(1) - - expect(subject.deployment).not_to be_nil - expect(subject.deployment.deployable).to eq(subject) - expect(subject.deployment.environment.name).to eq(expected_environment_name) - end - end - - shared_examples_for 'non-deployment job' do - it 'returns a job without deployment' do - expect(subject.deployment).to be_nil - end - end - - shared_examples_for 'ensures environment existence' do - it 'has environment' do - expect { subject }.to change { Environment.count }.by(1) - - expect(subject).to be_has_environment - expect(subject.environment).to eq(environment_name) - expect(subject.metadata.expanded_environment_name).to eq(expected_environment_name) - expect(Environment.exists?(name: expected_environment_name)).to eq(true) - end - end - - shared_examples_for 'ensures environment inexistence' do - it 'does not have environment' do - expect { subject }.not_to change { Environment.count } - - expect(subject).not_to be_has_environment - expect(subject.environment).to be_nil - expect(subject.metadata&.expanded_environment_name).to be_nil - expect(Environment.exists?(name: expected_environment_name)).to eq(false) - end - end - - context 'when job deploys to production' do - let(:environment_name) { 'production' } - let(:expected_environment_name) { 'production' } - let(:attributes) { { name: 'deploy', ref: 'master', environment: 'production' } } - - it_behaves_like 'deployment job' - it_behaves_like 'ensures environment existence' - - context 'when create_deployment_in_separate_transaction feature flag is enabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: true) - end - - it 'does not create any deployments nor environments' do - expect(subject.deployment).to be_nil - expect(Environment.count).to eq(0) - expect(Deployment.count).to eq(0) - end - end - - context 'when the environment name is invalid' do - let(:attributes) { { name: 'deploy', ref: 'master', environment: '!!!' } } - - it 'fails the job with a failure reason and does not create an environment' do - expect(subject).to be_failed - expect(subject).to be_environment_creation_failure - expect(subject.metadata.expanded_environment_name).to be_nil - expect(Environment.exists?(name: expected_environment_name)).to eq(false) - end - end - end - - context 'when job starts a review app' do - let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' } - let(:expected_environment_name) { "review/#{pipeline.ref}" } - - let(:attributes) do - { - name: 'deploy', ref: 'master', environment: environment_name, - options: { environment: { name: environment_name } } - } - end - - it_behaves_like 'deployment job' - it_behaves_like 'ensures environment existence' - end - - context 'when job stops a review app' do - let(:environment_name) { 'review/$CI_COMMIT_REF_NAME' } - let(:expected_environment_name) { "review/#{pipeline.ref}" } - - let(:attributes) do - { - name: 'deploy', ref: 'master', environment: environment_name, - options: { environment: { name: environment_name, action: 'stop' } } - } - end - - it 'returns a job without deployment' do - expect(subject.deployment).to be_nil - end - - it_behaves_like 'non-deployment job' - it_behaves_like 'ensures environment existence' - end - - context 'when job belongs to a resource group' do - let(:resource_group) { 'iOS' } - let(:attributes) { { name: 'rspec', ref: 'master', resource_group_key: resource_group, environment: 'production' }} - - it 'returns a job with resource group' do - expect(subject.resource_group).not_to be_nil - expect(subject.resource_group.key).to eq('iOS') - expect(Ci::ResourceGroup.count).to eq(1) - end - - context 'when create_deployment_in_separate_transaction feature flag is enabled' do - before do - stub_feature_flags(create_deployment_in_separate_transaction: true) - end - - it 'does not create any resource groups' do - expect(subject.resource_group).to be_nil - expect(Ci::ResourceGroup.count).to eq(0) - end - end - - context 'when resource group has $CI_ENVIRONMENT_NAME in it' do - let(:resource_group) { 'test/$CI_ENVIRONMENT_NAME' } - - it 'expands environment name' do - expect(subject.resource_group.key).to eq('test/production') - end - end - end - end - - context 'when job is a bridge' do - let(:base_attributes) do - { - name: 'rspec', ref: 'master', options: { trigger: 'my/project' }, scheduling_type: :stage - } - end - - let(:attributes) { base_attributes } - - it { is_expected.to be_a(::Ci::Bridge) } - it { is_expected.to be_valid } - - context 'when job belongs to a resource group' do - let(:attributes) { base_attributes.merge(resource_group_key: 'iOS') } - - it 'returns a job with resource group' do - expect(subject.resource_group).not_to be_nil - expect(subject.resource_group.key).to eq('iOS') - end - end - end - it 'memoizes a resource object' do expect(subject.object_id).to eq seed_build.to_resource.object_id end diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb index 8f7c13d7ae6..6cfd7def013 100644 --- a/spec/models/container_repository_spec.rb +++ b/spec/models/container_repository_spec.rb @@ -330,6 +330,52 @@ RSpec.describe ContainerRepository do end end + describe '.find_by_path' do + let_it_be(:container_repository) { create(:container_repository) } + let_it_be(:repository_path) { container_repository.project.full_path } + + let(:path) { ContainerRegistry::Path.new(repository_path + '/' + container_repository.name) } + + subject { described_class.find_by_path(path) } + + context 'when repository exists' do + it 'finds the repository' do + expect(subject).to eq(container_repository) + end + end + + context 'when repository does not exist' do + let(:path) { ContainerRegistry::Path.new(repository_path + '/some/image') } + + it 'returns nil' do + expect(subject).to be_nil + end + end + end + + describe '.find_by_path!' do + let_it_be(:container_repository) { create(:container_repository) } + let_it_be(:repository_path) { container_repository.project.full_path } + + let(:path) { ContainerRegistry::Path.new(repository_path + '/' + container_repository.name) } + + subject { described_class.find_by_path!(path) } + + context 'when repository exists' do + it 'finds the repository' do + expect(subject).to eq(container_repository) + end + end + + context 'when repository does not exist' do + let(:path) { ContainerRegistry::Path.new(repository_path + '/some/image') } + + it 'raises an exception' do + expect { subject }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + describe '.build_root_repository' do let(:repository) do described_class.build_root_repository(project) @@ -458,6 +504,24 @@ RSpec.describe ContainerRepository do it { is_expected.to eq([repository]) } end + describe '#migration_importing?' do + let_it_be_with_reload(:container_repository) { create(:container_repository, migration_state: 'importing')} + + subject { container_repository.migration_importing? } + + it { is_expected.to eq(true) } + + context 'when not importing' do + before do + # Technical debt: when the state machine is added, we should iterate through all possible states + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78499 + container_repository.update_column(:migration_state, 'default') + end + + it { is_expected.to eq(false) } + end + end + context 'with repositories' do let_it_be_with_reload(:repository) { create(:container_repository, :cleanup_unscheduled) } let_it_be(:other_repository) { create(:container_repository, :cleanup_unscheduled) } diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb index 61f11952ddc..eb1f84e3ef9 100644 --- a/spec/requests/api/graphql/ci/runner_spec.rb +++ b/spec/requests/api/graphql/ci/runner_spec.rb @@ -25,6 +25,8 @@ RSpec.describe 'Query.runner(id)' do access_level: 0, tag_list: %w[tag1 tag2], run_untagged: true, executor_type: :shell) end + let_it_be(:active_project_runner) { create(:ci_runner, :project) } + def get_runner(id) case id when :active_instance_runner @@ -33,6 +35,8 @@ RSpec.describe 'Query.runner(id)' do inactive_instance_runner when :active_group_runner active_group_runner + when :active_project_runner + active_project_runner end end @@ -55,7 +59,7 @@ RSpec.describe 'Query.runner(id)' do runner = get_runner(runner_id) expect(runner_data).to match a_hash_including( - 'id' => "gid://gitlab/Ci::Runner/#{runner.id}", + 'id' => runner.to_global_id.to_s, 'description' => runner.description, 'createdAt' => runner.created_at&.iso8601, 'contactedAt' => runner.contacted_at&.iso8601, @@ -103,7 +107,7 @@ RSpec.describe 'Query.runner(id)' do runner = get_runner(runner_id) expect(runner_data).to match a_hash_including( - 'id' => "gid://gitlab/Ci::Runner/#{runner.id}", + 'id' => runner.to_global_id.to_s, 'adminUrl' => nil ) expect(runner_data['tagList']).to match_array runner.tag_list @@ -179,7 +183,7 @@ RSpec.describe 'Query.runner(id)' do runner_data = graphql_data_at(:runner) expect(runner_data).to match a_hash_including( - 'id' => "gid://gitlab/Ci::Runner/#{project_runner.id}", + 'id' => project_runner.to_global_id.to_s, 'locked' => is_locked ) end @@ -216,7 +220,7 @@ RSpec.describe 'Query.runner(id)' do a_hash_including( 'webUrl' => "http://localhost/groups/#{group.full_path}/-/runners/#{active_group_runner.id}", 'node' => { - 'id' => "gid://gitlab/Ci::Runner/#{active_group_runner.id}" + 'id' => active_group_runner.to_global_id.to_s } ) ] @@ -227,7 +231,7 @@ RSpec.describe 'Query.runner(id)' do let(:query) do %( query { - runner(id: "gid://gitlab/Ci::Runner/#{active_group_runner.id}") { + runner(id: "#{active_group_runner.to_global_id}") { groups { nodes { id @@ -302,21 +306,36 @@ RSpec.describe 'Query.runner(id)' do let!(:job) { create(:ci_build, runner: project_runner1) } - context 'requesting project and job counts' do + context 'requesting projects and counts for projects and jobs' do let(:query) do %( query { projectRunner1: runner(id: "#{project_runner1.to_global_id}") { projectCount jobCount + projects { + nodes { + id + } + } } projectRunner2: runner(id: "#{project_runner2.to_global_id}") { projectCount jobCount + projects { + nodes { + id + } + } } activeInstanceRunner: runner(id: "#{active_instance_runner.to_global_id}") { projectCount jobCount + projects { + nodes { + id + } + } } } ) @@ -335,13 +354,23 @@ RSpec.describe 'Query.runner(id)' do expect(runner1_data).to match a_hash_including( 'jobCount' => 1, - 'projectCount' => 2) + 'projectCount' => 2, + 'projects' => { + 'nodes' => [ + { 'id' => project1.to_global_id.to_s }, + { 'id' => project2.to_global_id.to_s } + ] + }) expect(runner2_data).to match a_hash_including( 'jobCount' => 0, - 'projectCount' => 0) + 'projectCount' => 0, + 'projects' => { + 'nodes' => [] + }) expect(runner3_data).to match a_hash_including( 'jobCount' => 0, - 'projectCount' => nil) + 'projectCount' => nil, + 'projects' => nil) end end end @@ -356,6 +385,10 @@ RSpec.describe 'Query.runner(id)' do context 'on group runner' do it_behaves_like 'retrieval by unauthorized user', :active_group_runner end + + context 'on project runner' do + it_behaves_like 'retrieval by unauthorized user', :active_project_runner + end end describe 'by non-admin user' do diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb index 9204ee4d7f0..e15f847d866 100644 --- a/spec/requests/api/issues/issues_spec.rb +++ b/spec/requests/api/issues/issues_spec.rb @@ -488,6 +488,8 @@ RSpec.describe API::Issues do let_it_be(:issue3) { create(:issue, project: project, author: user, due_date: frozen_time + 10.days) } let_it_be(:issue4) { create(:issue, project: project, author: user, due_date: frozen_time + 34.days) } let_it_be(:issue5) { create(:issue, project: project, author: user, due_date: frozen_time - 8.days) } + let_it_be(:issue6) { create(:issue, project: project, author: user, due_date: frozen_time) } + let_it_be(:issue7) { create(:issue, project: project, author: user, due_date: frozen_time + 1.day) } before do travel_to(frozen_time) @@ -500,7 +502,13 @@ RSpec.describe API::Issues do it 'returns them all when argument is empty' do get api('/issues?due_date=', user) - expect_paginated_array_response(issue5.id, issue4.id, issue3.id, issue2.id, issue.id, closed_issue.id) + expect_paginated_array_response(issue7.id, issue6.id, issue5.id, issue4.id, issue3.id, issue2.id, issue.id, closed_issue.id) + end + + it 'returns issues with due date' do + get api('/issues?due_date=any', user) + + expect_paginated_array_response(issue7.id, issue6.id, issue5.id, issue4.id, issue3.id, issue2.id) end it 'returns issues without due date' do @@ -512,19 +520,31 @@ RSpec.describe API::Issues do it 'returns issues due for this week' do get api('/issues?due_date=week', user) - expect_paginated_array_response(issue2.id) + expect_paginated_array_response(issue7.id, issue6.id, issue2.id) end it 'returns issues due for this month' do get api('/issues?due_date=month', user) - expect_paginated_array_response(issue3.id, issue2.id) + expect_paginated_array_response(issue7.id, issue6.id, issue3.id, issue2.id) end it 'returns issues that are due previous two weeks and next month' do get api('/issues?due_date=next_month_and_previous_two_weeks', user) - expect_paginated_array_response(issue5.id, issue4.id, issue3.id, issue2.id) + expect_paginated_array_response(issue7.id, issue6.id, issue5.id, issue4.id, issue3.id, issue2.id) + end + + it 'returns issues that are due today' do + get api('/issues?due_date=today', user) + + expect_paginated_array_response(issue6.id) + end + + it 'returns issues that are due tomorrow' do + get api('/issues?due_date=tomorrow', user) + + expect_paginated_array_response(issue7.id) end it 'returns issues that are overdue' do diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 4e8e41ca6e6..0f24883bd7b 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -370,23 +370,6 @@ RSpec.describe Ci::RetryBuildService do it_behaves_like 'when build with deployment is retried' it_behaves_like 'when build with dynamic environment is retried' - context 'when create_deployment_in_separate_transaction feature flag is disabled' do - let(:new_build) do - travel_to(1.second.from_now) do - ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/345668') do - service.clone!(build) - end - end - end - - before do - stub_feature_flags(create_deployment_in_separate_transaction: false) - end - - it_behaves_like 'when build with deployment is retried' - it_behaves_like 'when build with dynamic environment is retried' - end - context 'when build has needs' do before do create(:ci_build_need, build: build, name: 'build1') diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb index 87bf134eeb8..4713e2fc789 100644 --- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb +++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb @@ -167,7 +167,7 @@ RSpec.shared_examples 'a container registry auth service' do stub_feature_flags(container_registry_cdn_redirect: false) end - describe '#full_access_token' do + describe '.full_access_token' do let_it_be(:project) { create(:project) } let(:token) { described_class.full_access_token(project.full_path) } @@ -181,7 +181,21 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'not a container repository factory' end - describe '#pull_access_token' do + describe '.import_access_token' do + let_it_be(:project) { create(:project) } + + let(:token) { described_class.import_access_token(project.full_path) } + + subject { { token: token } } + + it_behaves_like 'an accessible' do + let(:actions) { ['import'] } + end + + it_behaves_like 'not a container repository factory' + end + + describe '.pull_access_token' do let_it_be(:project) { create(:project) } let(:token) { described_class.pull_access_token(project.full_path) } @@ -1126,4 +1140,72 @@ RSpec.shared_examples 'a container registry auth service' do end end end + + context 'when importing' do + let_it_be(:container_repository) { create(:container_repository, :root, migration_state: 'importing') } + let_it_be(:current_project) { container_repository.project } + let_it_be(:current_user) { create(:user) } + + before do + current_project.add_developer(current_user) + end + + shared_examples 'containing the import error' do + it 'includes a helpful error message' do + expect(subject[:errors].first).to include(message: /Your repository is currently being migrated/) + end + end + + context 'push request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:push"] } + end + + it_behaves_like 'a forbidden' do + it_behaves_like 'containing the import error' + end + end + + context 'delete request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:delete"] } + end + + it_behaves_like 'a forbidden' do + it_behaves_like 'containing the import error' + end + end + + context '* request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:*"] } + end + + it_behaves_like 'a forbidden' do + it_behaves_like 'containing the import error' + end + end + + context 'pull request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:pull"] } + end + + let(:project) { current_project } + + it_behaves_like 'a pullable' + end + + context 'mixed request' do + let(:current_params) do + { scopes: ["repository:#{container_repository.path}:pull,push"] } + end + + let(:project) { current_project } + + it_behaves_like 'a forbidden' do + it_behaves_like 'containing the import error' + end + end + end end diff --git a/spec/uploaders/import_export_uploader_spec.rb b/spec/uploaders/import_export_uploader_spec.rb index cb7a89193e6..64e92f5d60e 100644 --- a/spec/uploaders/import_export_uploader_spec.rb +++ b/spec/uploaders/import_export_uploader_spec.rb @@ -10,6 +10,20 @@ RSpec.describe ImportExportUploader do subject { described_class.new(model, :import_file) } context 'local store' do + describe '#move_to_cache' do + it 'returns false' do + expect(subject.move_to_cache).to be false + end + + context 'with project export' do + subject { described_class.new(model, :export_file) } + + it 'returns true' do + expect(subject.move_to_cache).to be true + end + end + end + describe '#move_to_store' do it 'returns true' do expect(subject.move_to_store).to be true @@ -33,6 +47,20 @@ RSpec.describe ImportExportUploader do let(:fixture) { File.join('spec', 'fixtures', 'group_export.tar.gz') } end + describe '#move_to_cache' do + it 'returns false' do + expect(subject.move_to_cache).to be false + end + + context 'with project export' do + subject { described_class.new(model, :export_file) } + + it 'returns true' do + expect(subject.move_to_cache).to be false + end + end + end + describe '#move_to_store' do it 'returns false' do expect(subject.move_to_store).to be false diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb index 85731de2a45..95d9b982fc4 100644 --- a/spec/workers/concerns/application_worker_spec.rb +++ b/spec/workers/concerns/application_worker_spec.rb @@ -247,45 +247,6 @@ RSpec.describe ApplicationWorker do end end - describe '.perform_async' do - using RSpec::Parameterized::TableSyntax - - where(:primary_only?, :skip_scheduling_ff, :data_consistency, :schedules_job?) do - true | false | :sticky | false - true | false | :delayed | false - true | false | :always | false - true | true | :sticky | false - true | true | :delayed | false - true | true | :always | false - false | false | :sticky | true - false | false | :delayed | true - false | false | :always | false - false | true | :sticky | false - false | true | :delayed | false - false | true | :always | false - end - - before do - stub_const(worker.name, worker) - worker.data_consistency(data_consistency) - - allow(Gitlab::Database::LoadBalancing).to receive(:primary_only?).and_return(primary_only?) - stub_feature_flags(skip_scheduling_workers_for_replicas: skip_scheduling_ff) - end - - with_them do - it 'schedules or enqueues the job correctly' do - if schedules_job? - expect(worker).to receive(:perform_in).with(described_class::DEFAULT_DELAY_INTERVAL.seconds, 123) - else - expect(worker).not_to receive(:perform_in) - end - - worker.perform_async(123) - end - end - end - context 'different kinds of push_bulk' do shared_context 'set safe limit beyond the number of jobs to be enqueued' do before do |