diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-30 15:09:03 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-30 15:09:03 +0300 |
commit | b0139a824fba85e5b71e69f2c99d423700ff76cc (patch) | |
tree | 0be37882f9bdaf64d6bc8bcd486d0e1066124513 /spec | |
parent | 312ac59328577a230a049eb71c56f648508d209f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
14 files changed, 463 insertions, 81 deletions
diff --git a/spec/factories/projects/import_export/export_relation.rb b/spec/factories/projects/import_export/export_relation.rb new file mode 100644 index 00000000000..2b6419dcecb --- /dev/null +++ b/spec/factories/projects/import_export/export_relation.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :project_relation_export, class: 'Projects::ImportExport::RelationExport' do + project_export_job factory: :project_export_job + + relation { 'labels' } + status { 0 } + sequence(:jid) { |n| "project_relation_export_#{n}" } + end +end diff --git a/spec/fixtures/gitlab/import_export/labels.tar.gz b/spec/fixtures/gitlab/import_export/labels.tar.gz Binary files differnew file mode 100644 index 00000000000..8329dcf3b4a --- /dev/null +++ b/spec/fixtures/gitlab/import_export/labels.tar.gz diff --git a/spec/frontend/clusters_list/components/agent_table_spec.js b/spec/frontend/clusters_list/components/agent_table_spec.js index 2a43b45a2f5..b78f0a3686c 100644 --- a/spec/frontend/clusters_list/components/agent_table_spec.js +++ b/spec/frontend/clusters_list/components/agent_table_spec.js @@ -70,10 +70,10 @@ describe('AgentTable', () => { }); it.each` - status | iconName | lineNumber - ${'Never connected'} | ${'status-neutral'} | ${0} - ${'Connected'} | ${'status-success'} | ${1} - ${'Not connected'} | ${'severity-critical'} | ${2} + status | iconName | lineNumber + ${'Never connected'} | ${'status-neutral'} | ${0} + ${'Connected'} | ${'status-success'} | ${1} + ${'Not connected'} | ${'status-alert'} | ${2} `( 'displays agent connection status as "$status" at line $lineNumber', ({ status, iconName, lineNumber }) => { diff --git a/spec/frontend/google_cloud/components/cloudsql/create_instance_form_spec.js b/spec/frontend/google_cloud/components/cloudsql/create_instance_form_spec.js new file mode 100644 index 00000000000..de644a33b50 --- /dev/null +++ b/spec/frontend/google_cloud/components/cloudsql/create_instance_form_spec.js @@ -0,0 +1,103 @@ +import { GlFormCheckbox } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import InstanceForm from '~/google_cloud/components/cloudsql/create_instance_form.vue'; + +describe('google_cloud::cloudsql::create_instance_form component', () => { + let wrapper; + + const findByTestId = (id) => wrapper.findByTestId(id); + const findCancelButton = () => findByTestId('cancel-button'); + const findCheckbox = () => wrapper.findComponent(GlFormCheckbox); + const findHeader = () => wrapper.find('header'); + const findSubmitButton = () => findByTestId('submit-button'); + + const propsData = { + gcpProjects: [], + refs: [], + cancelPath: '#cancel-url', + formTitle: 'mock form title', + formDescription: 'mock form description', + databaseVersions: [], + tiers: [], + }; + + beforeEach(() => { + wrapper = shallowMountExtended(InstanceForm, { propsData, stubs: { GlFormCheckbox } }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('contains header', () => { + expect(findHeader().exists()).toBe(true); + }); + + it('contains GCP project form group', () => { + const formGroup = findByTestId('form_group_gcp_project'); + expect(formGroup.exists()).toBe(true); + expect(formGroup.attributes('label')).toBe(InstanceForm.i18n.gcpProjectLabel); + expect(formGroup.attributes('description')).toBe(InstanceForm.i18n.gcpProjectDescription); + }); + + it('contains GCP project dropdown', () => { + const select = findByTestId('select_gcp_project'); + expect(select.exists()).toBe(true); + }); + + it('contains Environments form group', () => { + const formGroup = findByTestId('form_group_environments'); + expect(formGroup.exists()).toBe(true); + expect(formGroup.attributes('label')).toBe(InstanceForm.i18n.refsLabel); + expect(formGroup.attributes('description')).toBe(InstanceForm.i18n.refsDescription); + }); + + it('contains Environments dropdown', () => { + const select = findByTestId('select_environments'); + expect(select.exists()).toBe(true); + }); + + it('contains Tier form group', () => { + const formGroup = findByTestId('form_group_tier'); + expect(formGroup.exists()).toBe(true); + expect(formGroup.attributes('label')).toBe(InstanceForm.i18n.tierLabel); + expect(formGroup.attributes('description')).toBe(InstanceForm.i18n.tierDescription); + }); + + it('contains Tier dropdown', () => { + const select = findByTestId('select_tier'); + expect(select.exists()).toBe(true); + }); + + it('contains Database Version form group', () => { + const formGroup = findByTestId('form_group_database_version'); + expect(formGroup.exists()).toBe(true); + expect(formGroup.attributes('label')).toBe(InstanceForm.i18n.databaseVersionLabel); + }); + + it('contains Database Version dropdown', () => { + const select = findByTestId('select_database_version'); + expect(select.exists()).toBe(true); + }); + + it('contains Submit button', () => { + expect(findSubmitButton().exists()).toBe(true); + expect(findSubmitButton().text()).toBe(InstanceForm.i18n.submitLabel); + }); + + it('contains Cancel button', () => { + expect(findCancelButton().exists()).toBe(true); + expect(findCancelButton().text()).toBe(InstanceForm.i18n.cancelLabel); + expect(findCancelButton().attributes('href')).toBe('#cancel-url'); + }); + + it('contains Confirmation checkbox', () => { + const checkbox = findCheckbox(); + expect(checkbox.text()).toBe(InstanceForm.i18n.checkboxLabel); + }); + + it('checkbox must be required', () => { + const checkbox = findCheckbox(); + expect(checkbox.attributes('required')).toBe('true'); + }); +}); diff --git a/spec/frontend/google_cloud/components/cloudsql/instance_table_spec.js b/spec/frontend/google_cloud/components/cloudsql/instance_table_spec.js new file mode 100644 index 00000000000..286f2b8e379 --- /dev/null +++ b/spec/frontend/google_cloud/components/cloudsql/instance_table_spec.js @@ -0,0 +1,65 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlEmptyState, GlTable } from '@gitlab/ui'; +import InstanceTable from '~/google_cloud/components/cloudsql/instance_table.vue'; + +describe('google_cloud::databases::service_table component', () => { + let wrapper; + + const findEmptyState = () => wrapper.findComponent(GlEmptyState); + const findTable = () => wrapper.findComponent(GlTable); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when there are no instances', () => { + beforeEach(() => { + const propsData = { + cloudsqlInstances: [], + emptyIllustrationUrl: '#empty-illustration-url', + }; + wrapper = shallowMount(InstanceTable, { propsData }); + }); + + it('should depict empty state', () => { + const emptyState = findEmptyState(); + expect(emptyState.exists()).toBe(true); + expect(emptyState.attributes('title')).toBe(InstanceTable.i18n.noInstancesTitle); + expect(emptyState.attributes('description')).toBe(InstanceTable.i18n.noInstancesDescription); + }); + }); + + describe('when there are three instances', () => { + beforeEach(() => { + const propsData = { + cloudsqlInstances: [ + { + ref: '*', + gcp_project: 'test-gcp-project', + instance_name: 'postgres-14-instance', + version: 'POSTGRES_14', + }, + { + ref: 'production', + gcp_project: 'prod-gcp-project', + instance_name: 'postgres-14-instance', + version: 'POSTGRES_14', + }, + { + ref: 'staging', + gcp_project: 'test-gcp-project', + instance_name: 'postgres-14-instance', + version: 'POSTGRES_14', + }, + ], + emptyIllustrationUrl: '#empty-illustration-url', + }; + wrapper = shallowMount(InstanceTable, { propsData }); + }); + + it('should contain a table', () => { + const table = findTable(); + expect(table.exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/google_cloud/components/databases/service_table_spec.js b/spec/frontend/google_cloud/components/databases/service_table_spec.js new file mode 100644 index 00000000000..142e32c1a4b --- /dev/null +++ b/spec/frontend/google_cloud/components/databases/service_table_spec.js @@ -0,0 +1,44 @@ +import { GlTable } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import ServiceTable from '~/google_cloud/components/databases/service_table.vue'; + +describe('google_cloud::databases::service_table component', () => { + let wrapper; + + const findTable = () => wrapper.findComponent(GlTable); + + beforeEach(() => { + const propsData = { + cloudsqlPostgresUrl: '#url-cloudsql-postgres', + cloudsqlMysqlUrl: '#url-cloudsql-mysql', + cloudsqlSqlserverUrl: '#url-cloudsql-sqlserver', + alloydbPostgresUrl: '#url-alloydb-postgres', + memorystoreRedisUrl: '#url-memorystore-redis', + firestoreUrl: '#url-firestore', + }; + wrapper = mountExtended(ServiceTable, { propsData }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('should contain a table', () => { + expect(findTable().exists()).toBe(true); + }); + + it.each` + name | testId | url + ${'cloudsql-postgres'} | ${'button-cloudsql-postgres'} | ${'#url-cloudsql-postgres'} + ${'cloudsql-mysql'} | ${'button-cloudsql-mysql'} | ${'#url-cloudsql-mysql'} + ${'cloudsql-sqlserver'} | ${'button-cloudsql-sqlserver'} | ${'#url-cloudsql-sqlserver'} + ${'alloydb-postgres'} | ${'button-alloydb-postgres'} | ${'#url-alloydb-postgres'} + ${'memorystore-redis'} | ${'button-memorystore-redis'} | ${'#url-memorystore-redis'} + ${'firestore'} | ${'button-firestore'} | ${'#url-firestore'} + `('renders $name button with correct url', ({ testId, url }) => { + const button = wrapper.findByTestId(testId); + + expect(button.exists()).toBe(true); + expect(button.attributes('href')).toBe(url); + }); +}); diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js index 97b6a1e8b74..c3824ad9701 100644 --- a/spec/frontend/security_configuration/components/app_spec.js +++ b/spec/frontend/security_configuration/components/app_spec.js @@ -42,12 +42,36 @@ describe('App component', () => { let wrapper; let userCalloutDismissSpy; - const createComponent = ({ shouldShowCallout = true, ...propsData }) => { + const securityFeaturesMock = [ + { + name: SAST_NAME, + shortName: SAST_SHORT_NAME, + description: SAST_DESCRIPTION, + helpPath: SAST_HELP_PATH, + configurationHelpPath: SAST_CONFIG_HELP_PATH, + type: REPORT_TYPE_SAST, + available: true, + }, + ]; + + const complianceFeaturesMock = [ + { + name: LICENSE_COMPLIANCE_NAME, + description: LICENSE_COMPLIANCE_DESCRIPTION, + helpPath: LICENSE_COMPLIANCE_HELP_PATH, + type: REPORT_TYPE_LICENSE_COMPLIANCE, + configurationHelpPath: LICENSE_COMPLIANCE_HELP_PATH, + }, + ]; + + const createComponent = ({ shouldShowCallout = true, ...propsData } = {}) => { userCalloutDismissSpy = jest.fn(); wrapper = extendedWrapper( mount(SecurityConfigurationApp, { propsData: { + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock, securityTrainingEnabled: true, ...propsData, }, @@ -108,38 +132,13 @@ describe('App component', () => { const findAutoDevopsEnabledAlert = () => wrapper.findComponent(AutoDevopsEnabledAlert); const findVulnerabilityManagementTab = () => wrapper.findByTestId('vulnerability-management-tab'); - const securityFeaturesMock = [ - { - name: SAST_NAME, - shortName: SAST_SHORT_NAME, - description: SAST_DESCRIPTION, - helpPath: SAST_HELP_PATH, - configurationHelpPath: SAST_CONFIG_HELP_PATH, - type: REPORT_TYPE_SAST, - available: true, - }, - ]; - - const complianceFeaturesMock = [ - { - name: LICENSE_COMPLIANCE_NAME, - description: LICENSE_COMPLIANCE_DESCRIPTION, - helpPath: LICENSE_COMPLIANCE_HELP_PATH, - type: REPORT_TYPE_LICENSE_COMPLIANCE, - configurationHelpPath: LICENSE_COMPLIANCE_HELP_PATH, - }, - ]; - afterEach(() => { wrapper.destroy(); }); describe('basic structure', () => { - beforeEach(async () => { - createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, - }); + beforeEach(() => { + createComponent(); }); it('renders main-heading with correct text', () => { @@ -199,10 +198,7 @@ describe('App component', () => { describe('Manage via MR Error Alert', () => { beforeEach(() => { - createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, - }); + createComponent(); }); describe('on initial load', () => { @@ -238,8 +234,6 @@ describe('App component', () => { describe('given the right props', () => { beforeEach(() => { createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, autoDevopsEnabled: false, gitlabCiPresent: false, canEnableAutoDevops: true, @@ -261,10 +255,7 @@ describe('App component', () => { describe('given the wrong props', () => { beforeEach(() => { - createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, - }); + createComponent(); }); it('should not show AutoDevopsAlert', () => { expect(findAutoDevopsAlert().exists()).toBe(false); @@ -289,8 +280,6 @@ describe('App component', () => { } createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, autoDevopsEnabled, }); }); @@ -348,7 +337,6 @@ describe('App component', () => { describe('given at least one unavailable feature', () => { beforeEach(() => { createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)), }); }); @@ -369,7 +357,6 @@ describe('App component', () => { describe('given at least one unavailable feature, but banner is already dismissed', () => { beforeEach(() => { createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, augmentedComplianceFeatures: complianceFeaturesMock.map(makeAvailable(false)), shouldShowCallout: false, }); @@ -397,8 +384,6 @@ describe('App component', () => { describe('when given latestPipelinePath props', () => { beforeEach(() => { createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, latestPipelinePath: 'test/path', }); }); @@ -425,8 +410,6 @@ describe('App component', () => { describe('given gitlabCiPresent & gitlabCiHistoryPath props', () => { beforeEach(() => { createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, gitlabCiPresent: true, gitlabCiHistoryPath, }); @@ -446,8 +429,6 @@ describe('App component', () => { beforeEach(async () => { createComponent({ - augmentedSecurityFeatures: securityFeaturesMock, - augmentedComplianceFeatures: complianceFeaturesMock, ...props, }); }); diff --git a/spec/frontend/work_items/components/item_title_spec.js b/spec/frontend/work_items/components/item_title_spec.js index 2c3f6ef8634..a55f448c9a2 100644 --- a/spec/frontend/work_items/components/item_title_spec.js +++ b/spec/frontend/work_items/components/item_title_spec.js @@ -1,5 +1,4 @@ import { shallowMount } from '@vue/test-utils'; -import { escape } from 'lodash'; import ItemTitle from '~/work_items/components/item_title.vue'; jest.mock('lodash/escape', () => jest.fn((fn) => fn)); @@ -51,6 +50,5 @@ describe('ItemTitle', () => { await findInputEl().trigger(sourceEvent); expect(wrapper.emitted(eventName)).toBeTruthy(); - expect(escape).toHaveBeenCalledWith(mockUpdatedTitle); }); }); diff --git a/spec/frontend/work_items/components/work_item_weight_spec.js b/spec/frontend/work_items/components/work_item_weight_spec.js index 80a1d032ad7..c3bbea26cda 100644 --- a/spec/frontend/work_items/components/work_item_weight_spec.js +++ b/spec/frontend/work_items/components/work_item_weight_spec.js @@ -1,21 +1,51 @@ -import { shallowMount } from '@vue/test-utils'; +import { GlForm, GlFormInput } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import { mockTracking } from 'helpers/tracking_helper'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { __ } from '~/locale'; import WorkItemWeight from '~/work_items/components/work_item_weight.vue'; +import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants'; +import localUpdateWorkItemMutation from '~/work_items/graphql/local_update_work_item.mutation.graphql'; -describe('WorkItemAssignees component', () => { +describe('WorkItemWeight component', () => { let wrapper; - const createComponent = ({ weight, hasIssueWeightsFeature = true } = {}) => { - wrapper = shallowMount(WorkItemWeight, { + const mutateSpy = jest.fn(); + const workItemId = 'gid://gitlab/WorkItem/1'; + const workItemType = 'Task'; + + const findForm = () => wrapper.findComponent(GlForm); + const findInput = () => wrapper.findComponent(GlFormInput); + + const createComponent = ({ + canUpdate = false, + hasIssueWeightsFeature = true, + isEditing = false, + weight, + } = {}) => { + wrapper = mountExtended(WorkItemWeight, { propsData: { + canUpdate, weight, + workItemId, + workItemType, }, provide: { hasIssueWeightsFeature, }, + mocks: { + $apollo: { + mutate: mutateSpy, + }, + }, }); + + if (isEditing) { + findInput().vm.$emit('focus'); + } }; - describe('weight licensed feature', () => { + describe('`issue_weights` licensed feature', () => { describe.each` description | hasIssueWeightsFeature | exists ${'when available'} | ${true} | ${true} @@ -24,23 +54,111 @@ describe('WorkItemAssignees component', () => { it(hasIssueWeightsFeature ? 'renders component' : 'does not render component', () => { createComponent({ hasIssueWeightsFeature }); - expect(wrapper.find('div').exists()).toBe(exists); + expect(findForm().exists()).toBe(exists); }); }); }); - describe('weight text', () => { - describe.each` - description | weight | text - ${'renders 1'} | ${1} | ${'1'} - ${'renders 0'} | ${0} | ${'0'} - ${'renders None'} | ${null} | ${'None'} - ${'renders None'} | ${undefined} | ${'None'} - `('when weight is $weight', ({ description, weight, text }) => { - it(description, () => { - createComponent({ weight }); - - expect(wrapper.text()).toContain(text); + describe('weight input', () => { + it('has "Weight" label', () => { + createComponent(); + + expect(wrapper.findByLabelText(__('Weight')).exists()).toBe(true); + }); + + describe('placeholder attribute', () => { + describe.each` + description | isEditing | canUpdate | value + ${'when not editing and cannot update'} | ${false} | ${false} | ${__('None')} + ${'when editing and cannot update'} | ${true} | ${false} | ${__('None')} + ${'when not editing and can update'} | ${false} | ${true} | ${__('None')} + ${'when editing and can update'} | ${true} | ${true} | ${__('Enter a number')} + `('$description', ({ isEditing, canUpdate, value }) => { + it(`has a value of "${value}"`, async () => { + createComponent({ canUpdate, isEditing }); + await nextTick(); + + expect(findInput().attributes('placeholder')).toBe(value); + }); + }); + }); + + describe('readonly attribute', () => { + describe.each` + description | canUpdate | value + ${'when cannot update'} | ${false} | ${'readonly'} + ${'when can update'} | ${true} | ${undefined} + `('$description', ({ canUpdate, value }) => { + it(`renders readonly=${value}`, () => { + createComponent({ canUpdate }); + + expect(findInput().attributes('readonly')).toBe(value); + }); + }); + }); + + describe('type attribute', () => { + describe.each` + description | isEditing | canUpdate | type + ${'when not editing and cannot update'} | ${false} | ${false} | ${'text'} + ${'when editing and cannot update'} | ${true} | ${false} | ${'text'} + ${'when not editing and can update'} | ${false} | ${true} | ${'text'} + ${'when editing and can update'} | ${true} | ${true} | ${'number'} + `('$description', ({ isEditing, canUpdate, type }) => { + it(`has a value of "${type}"`, async () => { + createComponent({ canUpdate, isEditing }); + await nextTick(); + + expect(findInput().attributes('type')).toBe(type); + }); + }); + }); + + describe('value attribute', () => { + describe.each` + weight | value + ${1} | ${'1'} + ${0} | ${'0'} + ${null} | ${''} + ${undefined} | ${''} + `('when `weight` prop is "$weight"', ({ weight, value }) => { + it(`value is "${value}"`, () => { + createComponent({ weight }); + + expect(findInput().element.value).toBe(value); + }); + }); + }); + + describe('when blurred', () => { + it('calls a mutation to update the weight', () => { + const weight = 0; + createComponent({ isEditing: true, weight }); + + findInput().trigger('blur'); + + expect(mutateSpy).toHaveBeenCalledWith({ + mutation: localUpdateWorkItemMutation, + variables: { + input: { + id: workItemId, + weight, + }, + }, + }); + }); + + it('tracks updating the weight', () => { + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + createComponent(); + + findInput().trigger('blur'); + + expect(trackingSpy).toHaveBeenCalledWith(TRACKING_CATEGORY_SHOW, 'updated_weight', { + category: TRACKING_CATEGORY_SHOW, + label: 'item_weight', + property: 'type_Task', + }); }); }); }); diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index 34f4bdde3b5..28557aab830 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -129,6 +129,14 @@ RSpec.describe Gitlab::Diff::File do expect(diff_file.rendered).to be_kind_of(Gitlab::Diff::Rendered::Notebook::DiffFile) end + context 'when collapsed' do + it 'is nil' do + expect(diff).to receive(:collapsed?).and_return(true) + + expect(diff_file.rendered).to be_nil + end + end + context 'when too large' do it 'is nil' do expect(diff).to receive(:too_large?).and_return(true) diff --git a/spec/models/project_export_job_spec.rb b/spec/models/project_export_job_spec.rb index 5a2b1443f8b..653d4d2df27 100644 --- a/spec/models/project_export_job_spec.rb +++ b/spec/models/project_export_job_spec.rb @@ -3,17 +3,14 @@ require 'spec_helper' RSpec.describe ProjectExportJob, type: :model do - let(:project) { create(:project) } - let!(:job1) { create(:project_export_job, project: project, status: 0) } - let!(:job2) { create(:project_export_job, project: project, status: 2) } - describe 'associations' do - it { expect(job1).to belong_to(:project) } + it { is_expected.to belong_to(:project) } + it { is_expected.to have_many(:relation_exports) } end describe 'validations' do - it { expect(job1).to validate_presence_of(:project) } - it { expect(job1).to validate_presence_of(:jid) } - it { expect(job1).to validate_presence_of(:status) } + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:jid) } + it { is_expected.to validate_presence_of(:status) } end end diff --git a/spec/models/projects/import_export/relation_export_spec.rb b/spec/models/projects/import_export/relation_export_spec.rb new file mode 100644 index 00000000000..c74ca82e161 --- /dev/null +++ b/spec/models/projects/import_export/relation_export_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::ImportExport::RelationExport, type: :model do + subject { create(:project_relation_export) } + + describe 'associations' do + it { is_expected.to belong_to(:project_export_job) } + it { is_expected.to have_one(:upload) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project_export_job) } + it { is_expected.to validate_presence_of(:relation) } + it { is_expected.to validate_uniqueness_of(:relation).scoped_to(:project_export_job_id) } + it { is_expected.to validate_presence_of(:status) } + it { is_expected.to validate_numericality_of(:status).only_integer } + it { is_expected.to validate_length_of(:relation).is_at_most(255) } + it { is_expected.to validate_length_of(:jid).is_at_most(255) } + it { is_expected.to validate_length_of(:export_error).is_at_most(300) } + end +end diff --git a/spec/models/projects/import_export/relation_export_upload_spec.rb b/spec/models/projects/import_export/relation_export_upload_spec.rb new file mode 100644 index 00000000000..c0014c5a14c --- /dev/null +++ b/spec/models/projects/import_export/relation_export_upload_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::ImportExport::RelationExportUpload, type: :model do + subject { described_class.new(relation_export: project_relation_export) } + + let_it_be(:project_relation_export) { create(:project_relation_export) } + + describe 'associations' do + it { is_expected.to belong_to(:relation_export) } + end + + it 'stores export file' do + stub_uploads_object_storage(ImportExportUploader, enabled: false) + + filename = 'labels.tar.gz' + subject.export_file = fixture_file_upload("spec/fixtures/gitlab/import_export/#{filename}") + + subject.save! + + url = "/uploads/-/system/projects/import_export/relation_export_upload/export_file/#{subject.id}/#{filename}" + expect(subject.export_file.url).to eq(url) + end +end diff --git a/spec/support/matchers/event_store.rb b/spec/support/matchers/event_store.rb index eb5b37f39e5..14f6a42d7f4 100644 --- a/spec/support/matchers/event_store.rb +++ b/spec/support/matchers/event_store.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true RSpec::Matchers.define :publish_event do |expected_event_class| + include RSpec::Matchers::Composable + supports_block_expectations match do |proc| @@ -15,10 +17,17 @@ RSpec::Matchers.define :publish_event do |expected_event_class| proc.call @events.any? do |event| - event.instance_of?(expected_event_class) && event.data == @expected_data + event.instance_of?(expected_event_class) && match_data?(event.data, @expected_data) end end + def match_data?(actual, expected) + values_match?(actual.keys, expected.keys) && + actual.keys.each do |key| + values_match?(actual[key], expected[key]) + end + end + chain :with do |expected_data| @expected_data = expected_data end |