diff options
author | Sean McGivern <sean@gitlab.com> | 2019-07-03 10:28:13 +0100 |
---|---|---|
committer | Sean McGivern <sean@gitlab.com> | 2019-07-03 10:28:13 +0100 |
commit | b94daa35a4be584623792653b537d6ab68bdabdd (patch) | |
tree | cd620ef82dc85cec1cb401c7a5cf880c47418708 /spec | |
parent | 83330822d6ee3c59a057adeda35b23ab0021b63a (diff) | |
parent | f90a7601c40c82bd230f9c014bc4f64744e77b5e (diff) | |
download | gitlab-ce-b94daa35a4be584623792653b537d6ab68bdabdd.tar.gz |
Merge branch 'master' into michel.engelen/gitlab-ce-issue/55953
Diffstat (limited to 'spec')
112 files changed, 2028 insertions, 4127 deletions
diff --git a/spec/controllers/projects/settings/repository_controller_spec.rb b/spec/controllers/projects/settings/repository_controller_spec.rb index b34053fc993..7f67f67e775 100644 --- a/spec/controllers/projects/settings/repository_controller_spec.rb +++ b/spec/controllers/projects/settings/repository_controller_spec.rb @@ -32,4 +32,24 @@ describe Projects::Settings::RepositoryController do expect(RepositoryCleanupWorker).to have_received(:perform_async).once end end + + describe 'POST create_deploy_token' do + let(:deploy_token_params) do + { + name: 'deployer_token', + expires_at: 1.month.from_now.to_date.to_s, + username: 'deployer', + read_repository: '1' + } + end + + subject(:create_deploy_token) { post :create_deploy_token, params: { namespace_id: project.namespace, project_id: project, deploy_token: deploy_token_params } } + + it 'creates deploy token' do + expect { create_deploy_token }.to change { DeployToken.active.count }.by(1) + + expect(response).to have_gitlab_http_status(200) + expect(response).to render_template(:show) + end + end end diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb index 2d0f698475d..3d4174eb852 100644 --- a/spec/factories/project_statistics.rb +++ b/spec/factories/project_statistics.rb @@ -6,5 +6,20 @@ FactoryBot.define do # statistics are automatically created when a project is created project&.statistics || new end + + transient do + with_data { false } + size_multiplier { 1 } + end + + after(:build) do |project_statistics, evaluator| + if evaluator.with_data + project_statistics.repository_size = evaluator.size_multiplier + project_statistics.wiki_size = evaluator.size_multiplier * 2 + project_statistics.lfs_objects_size = evaluator.size_multiplier * 3 + project_statistics.build_artifacts_size = evaluator.size_multiplier * 4 + project_statistics.packages_size = evaluator.size_multiplier * 5 + end + end end end diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index cab6b4a811f..4cacc77c182 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -6,6 +6,7 @@ FactoryBot.define do description "Awesome release" project author + released_at { Time.zone.parse('2018-10-20T18:00:00Z') } trait :legacy do sha nil diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 763909f30bd..ecb481ed84a 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -6,8 +6,6 @@ FactoryBot.define do factory :custom_issue_tracker_service, class: CustomIssueTrackerService do project - type 'CustomIssueTrackerService' - category 'issue_tracker' active true properties( project_url: 'https://project.url.com', @@ -54,6 +52,38 @@ FactoryBot.define do ) end + factory :bugzilla_service do + project + active true + issue_tracker + end + + factory :redmine_service do + project + active true + issue_tracker + end + + factory :youtrack_service do + project + active true + issue_tracker + end + + factory :gitlab_issue_tracker_service do + project + active true + issue_tracker + end + + trait :issue_tracker do + properties( + project_url: 'http://issue-tracker.example.com', + issues_url: 'http://issue-tracker.example.com', + new_issue_url: 'http://issue-tracker.example.com' + ) + end + factory :jira_cloud_service, class: JiraService do project active true diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb index 21d97aba0c5..1b5943bd5d8 100644 --- a/spec/features/container_registry_spec.rb +++ b/spec/features/container_registry_spec.rb @@ -19,7 +19,7 @@ describe "Container Registry", :js do it 'user visits container registry main page' do visit_container_registry - expect(page).to have_content 'No container images' + expect(page).to have_content 'no container images' end end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index c000165ccd9..0ada530781c 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -150,6 +150,25 @@ describe 'Group issues page' do check_issue_order end + it 'issues should not be draggable when user is not logged in', :js do + sign_out(user_in_group) + + visit issues_group_path(group, sort: 'relative_position') + + drag_to(selector: '.manual-ordering', + from_index: 0, + to_index: 2) + + wait_for_requests + + # Issue order should remain the same + page.within('.manual-ordering') do + expect(find('.issue:nth-child(1) .title')).to have_content('Issue #1') + expect(find('.issue:nth-child(2) .title')).to have_content('Issue #2') + expect(find('.issue:nth-child(3) .title')).to have_content('Issue #3') + end + end + def check_issue_order page.within('.manual-ordering') do expect(find('.issue:nth-child(1) .title')).to have_content('Issue #2') diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb index 0d946182c6f..1b1a31d0723 100644 --- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb +++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb @@ -54,7 +54,7 @@ describe 'Resolve an open thread in a merge request by creating an issue', :js d context 'creating the issue' do before do - find(resolve_discussion_selector).click + find(resolve_discussion_selector, match: :first).click end it 'has a hidden field for the thread' do diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb index a6b846edd54..10fe60cb075 100644 --- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb +++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb @@ -368,8 +368,8 @@ describe 'Merge request > User resolves diff notes and threads', :js do all_discussion_replies = page.all('.discussion-reply-holder') expect(all_discussion_replies.count).to eq(2) - expect(all_discussion_replies.first.all('.discussion-next-btn').count).to eq(1) - expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(1) + expect(all_discussion_replies.first.all('.discussion-next-btn').count).to eq(2) + expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(2) end it 'displays next thread even if hidden' do diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb index 527508b3519..c75259d1b0c 100644 --- a/spec/features/projects/clusters/applications_spec.rb +++ b/spec/features/projects/clusters/applications_spec.rb @@ -21,8 +21,7 @@ describe 'Clusters Applications', :js do it 'user is unable to install applications' do page.within('.js-cluster-application-row-helm') do - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Install') + expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Install') end end end @@ -53,19 +52,16 @@ describe 'Clusters Applications', :js do it 'they see status transition' do page.within('.js-cluster-application-row-helm') do # FE sends request and gets the response, then the buttons is "Installing" - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing') + expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing') Clusters::Cluster.last.application_helm.make_installing! # FE starts polling and update the buttons to "Installing" - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing') + expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing') Clusters::Cluster.last.application_helm.make_installed! - expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true') - expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed') + expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installed') end expect(page).to have_content('Helm Tiller was successfully installed on your Kubernetes cluster') @@ -212,26 +208,25 @@ describe 'Clusters Applications', :js do it 'they see status transition' do page.within('.js-cluster-application-row-ingress') do # FE sends request and gets the response, then the buttons is "Installing" - expect(page).to have_css('.js-cluster-application-install-button[disabled]') - expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing') + expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing') Clusters::Cluster.last.application_ingress.make_installing! # FE starts polling and update the buttons to "Installing" - expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing') - expect(page).to have_css('.js-cluster-application-install-button[disabled]') + expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installing') # The application becomes installed but we keep waiting for external IP address Clusters::Cluster.last.application_ingress.make_installed! - expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed') - expect(page).to have_css('.js-cluster-application-install-button[disabled]') + expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installed') expect(page).to have_selector('.js-no-endpoint-message') expect(page).to have_selector('.js-ingress-ip-loading-icon') # We receive the external IP address and display Clusters::Cluster.last.application_ingress.update!(external_ip: '192.168.1.100') + expect(page).not_to have_css('.js-cluster-application-install-button') + expect(page).to have_css('.js-cluster-application-uninstall-button:not([disabled])', exact_text: 'Uninstall') expect(page).not_to have_selector('.js-no-endpoint-message') expect(page.find('.js-endpoint').value).to eq('192.168.1.100') end diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb index 317ffb6a2ff..725d7173bce 100644 --- a/spec/features/projects/releases/user_views_releases_spec.rb +++ b/spec/features/projects/releases/user_views_releases_spec.rb @@ -16,6 +16,7 @@ describe 'User views releases', :js do expect(page).to have_content(release.name) expect(page).to have_content(release.tag) + expect(page).not_to have_content('Upcoming Release') end context 'when there is a link as an asset' do @@ -43,4 +44,15 @@ describe 'User views releases', :js do end end end + + context 'with an upcoming release' do + let(:tomorrow) { Time.zone.now + 1.day } + let!(:release) { create(:release, project: project, released_at: tomorrow ) } + + it 'sees the upcoming tag' do + visit project_releases_path(project) + + expect(page).to have_content('Upcoming Release') + end + end end diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index 8c7bc192c50..1edfee705c8 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -112,11 +112,17 @@ describe 'Projects > Settings > Repository settings' do it 'add a new deploy token' do fill_in 'deploy_token_name', with: 'new_deploy_key' fill_in 'deploy_token_expires_at', with: (Date.today + 1.month).to_s + fill_in 'deploy_token_username', with: 'deployer' check 'deploy_token_read_repository' check 'deploy_token_read_registry' click_button 'Create deploy token' expect(page).to have_content('Your new project deploy token has been created') + + within('.created-deploy-token-container') do + expect(page).to have_selector("input[name='deploy-token-user'][value='deployer']") + expect(page).to have_selector("input[name='deploy-token'][readonly='readonly']") + end end end diff --git a/spec/finders/releases_finder_spec.rb b/spec/finders/releases_finder_spec.rb index 32ee15134a2..5ffb8c74bf5 100644 --- a/spec/finders/releases_finder_spec.rb +++ b/spec/finders/releases_finder_spec.rb @@ -12,8 +12,8 @@ describe ReleasesFinder do subject { described_class.new(project, user)} before do - v1_0_0.update_attribute(:created_at, 2.days.ago) - v1_1_0.update_attribute(:created_at, 1.day.ago) + v1_0_0.update_attribute(:released_at, 2.days.ago) + v1_1_0.update_attribute(:released_at, 1.day.ago) end describe '#execute' do @@ -30,7 +30,7 @@ describe ReleasesFinder do project.add_developer(user) end - it 'sorts by creation date' do + it 'sorts by release date' do releases = subject.execute expect(releases).to be_present diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json index 6ea0781c1ed..ec3fa59cdb1 100644 --- a/spec/fixtures/api/schemas/public_api/v4/release.json +++ b/spec/fixtures/api/schemas/public_api/v4/release.json @@ -1,12 +1,14 @@ { "type": "object", - "required": ["name", "tag_name", "commit"], + "required": ["name", "tag_name", "commit", "released_at"], "properties": { "name": { "type": "string" }, "tag_name": { "type": "string" }, "description": { "type": "string" }, "description_html": { "type": "string" }, "created_at": { "type": "date" }, + "released_at": { "type": "date" }, + "upcoming_release": { "type": "boolean" }, "commit": { "oneOf": [{ "type": "null" }, { "$ref": "commit/basic.json" }] }, diff --git a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json index e78398ad1d5..0c1e8fd5fb3 100644 --- a/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json +++ b/spec/fixtures/api/schemas/public_api/v4/release/release_for_guest.json @@ -1,11 +1,13 @@ { "type": "object", - "required": ["name"], + "required": ["name", "released_at"], "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "description_html": { "type": "string" }, "created_at": { "type": "date" }, + "released_at": { "type": "date" }, + "upcoming_release": { "type": "boolean" }, "author": { "oneOf": [{ "type": "null" }, { "$ref": "../user/basic.json" }] }, diff --git a/spec/frontend/diffs/components/diff_discussion_reply_spec.js b/spec/frontend/diffs/components/diff_discussion_reply_spec.js new file mode 100644 index 00000000000..28689ab07de --- /dev/null +++ b/spec/frontend/diffs/components/diff_discussion_reply_spec.js @@ -0,0 +1,90 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import DiffDiscussionReply from '~/diffs/components/diff_discussion_reply.vue'; +import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue'; +import NoteSignedOutWidget from '~/notes/components/note_signed_out_widget.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('DiffDiscussionReply', () => { + let wrapper; + let getters; + let store; + + const createComponent = (props = {}, slots = {}) => { + wrapper = shallowMount(DiffDiscussionReply, { + store, + localVue, + sync: false, + propsData: { + ...props, + }, + slots: { + ...slots, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('if user can reply', () => { + beforeEach(() => { + getters = { + userCanReply: () => true, + getUserData: () => ({ + path: 'test-path', + avatar_url: 'avatar_url', + name: 'John Doe', + }), + }; + + store = new Vuex.Store({ + getters, + }); + }); + + it('should render a form if component has form', () => { + createComponent( + { + renderReplyPlaceholder: false, + hasForm: true, + }, + { + form: `<div id="test-form"></div>`, + }, + ); + + expect(wrapper.find('#test-form').exists()).toBe(true); + }); + + it('should render a reply placeholder if there is no form', () => { + createComponent({ + renderReplyPlaceholder: true, + hasForm: false, + }); + + expect(wrapper.find(ReplyPlaceholder).exists()).toBe(true); + }); + }); + + it('renders a signed out widget when user is not logged in', () => { + getters = { + userCanReply: () => false, + getUserData: () => null, + }; + + store = new Vuex.Store({ + getters, + }); + + createComponent({ + renderReplyPlaceholder: false, + hasForm: false, + }); + + expect(wrapper.find(NoteSignedOutWidget).exists()).toBe(true); + }); +}); diff --git a/spec/frontend/diffs/components/diff_gutter_avatars_spec.js b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js new file mode 100644 index 00000000000..48ee5c63f35 --- /dev/null +++ b/spec/frontend/diffs/components/diff_gutter_avatars_spec.js @@ -0,0 +1,113 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue'; +import discussionsMockData from '../mock_data/diff_discussions'; + +const localVue = createLocalVue(); +const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; + +describe('DiffGutterAvatars', () => { + let wrapper; + + const findCollapseButton = () => wrapper.find('.diff-notes-collapse'); + const findMoreCount = () => wrapper.find('.diff-comments-more-count'); + const findUserAvatars = () => wrapper.findAll('.diff-comment-avatar'); + + const createComponent = (props = {}) => { + wrapper = shallowMount(DiffGutterAvatars, { + localVue, + sync: false, + propsData: { + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + describe('when expanded', () => { + beforeEach(() => { + createComponent({ + discussions: getDiscussionsMockData(), + discussionsExpanded: true, + }); + }); + + it('renders a collapse button when discussions are expanded', () => { + expect(findCollapseButton().exists()).toBe(true); + }); + + it('should emit toggleDiscussions event on button click', () => { + findCollapseButton().trigger('click'); + + expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy(); + }); + }); + + describe('when collapsed', () => { + beforeEach(() => { + createComponent({ + discussions: getDiscussionsMockData(), + discussionsExpanded: false, + }); + }); + + it('renders user avatars and moreCount text', () => { + expect(findUserAvatars().exists()).toBe(true); + expect(findMoreCount().exists()).toBe(true); + }); + + it('renders correct amount of user avatars', () => { + expect(findUserAvatars().length).toBe(3); + }); + + it('renders correct moreCount number', () => { + expect(findMoreCount().text()).toBe('+2'); + }); + + it('should emit toggleDiscussions event on avatars click', () => { + findUserAvatars() + .at(0) + .trigger('click'); + + expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy(); + }); + + it('should emit toggleDiscussions event on more count text click', () => { + findMoreCount().trigger('click'); + + expect(wrapper.emitted().toggleLineDiscussions).toBeTruthy(); + }); + }); + + it('renders an empty more count string if there are no discussions', () => { + createComponent({ + discussions: [], + discussionsExpanded: false, + }); + + expect(findMoreCount().exists()).toBe(false); + }); + + describe('tooltip text', () => { + beforeEach(() => { + createComponent({ + discussions: getDiscussionsMockData(), + discussionsExpanded: false, + }); + }); + + it('returns original comment if it is shorter than max length', () => { + const note = wrapper.vm.discussions[0].notes[0]; + + expect(wrapper.vm.getTooltipText(note)).toEqual('Administrator: comment 1'); + }); + + it('returns truncated version of comment if it is longer than max length', () => { + const note = wrapper.vm.discussions[0].notes[1]; + + expect(wrapper.vm.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...'); + }); + }); +}); diff --git a/spec/frontend/diffs/mock_data/diff_discussions.js b/spec/frontend/diffs/mock_data/diff_discussions.js new file mode 100644 index 00000000000..711ab543411 --- /dev/null +++ b/spec/frontend/diffs/mock_data/diff_discussions.js @@ -0,0 +1,529 @@ +export default { + id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + reply_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + position: { + old_line: null, + new_line: 2, + old_path: 'CHANGELOG', + new_path: 'CHANGELOG', + base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', + start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', + head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', + }, + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', + expanded: true, + notes: [ + { + id: '1749', + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + created_at: '2018-04-03T21:06:21.521Z', + updated_at: '2018-04-08T08:50:41.762Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 1', + note_html: '<p dir="auto">comment 1</p>', + last_edited_at: '2018-04-08T08:50:41.762Z', + last_edited_by: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1749/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1749&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1749', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1749', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + { + id: '1753', + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Fatih Acet', + username: 'fatihacet', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/fatihacevt', + }, + created_at: '2018-04-08T08:49:35.804Z', + updated_at: '2018-04-08T08:50:45.915Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 2 is really long one', + note_html: '<p dir="auto">comment 2 is really long one</p>', + last_edited_at: '2018-04-08T08:50:45.915Z', + last_edited_by: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1753/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1753&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1753', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1753', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + { + id: '1754', + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + created_at: '2018-04-08T08:50:48.294Z', + updated_at: '2018-04-08T08:50:48.294Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 3', + note_html: '<p dir="auto">comment 3</p>', + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1754/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1754&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1754', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1754', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + { + id: '1755', + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + created_at: '2018-04-08T08:50:50.911Z', + updated_at: '2018-04-08T08:50:50.911Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 4', + note_html: '<p dir="auto">comment 4</p>', + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1755/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1755&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1755', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1755', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + { + id: '1756', + type: 'DiffNote', + attachment: null, + author: { + id: 1, + name: 'Administrator', + username: 'root', + state: 'active', + avatar_url: + 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + path: '/root', + }, + created_at: '2018-04-08T08:50:53.895Z', + updated_at: '2018-04-08T08:50:53.895Z', + system: false, + noteable_id: 51, + noteable_type: 'MergeRequest', + noteable_iid: 20, + human_access: 'Owner', + note: 'comment 5', + note_html: '<p dir="auto">comment 5</p>', + current_user: { + can_edit: true, + can_award_emoji: true, + }, + resolved: false, + resolvable: true, + resolved_by: null, + discussion_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', + emoji_awardable: true, + award_emoji: [], + toggle_award_path: '/gitlab-org/gitlab-test/notes/1756/toggle_award_emoji', + report_abuse_path: + '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-test%2Fmerge_requests%2F20%23note_1756&user_id=1', + path: '/gitlab-org/gitlab-test/notes/1756', + noteable_note_url: 'http://localhost:3000/gitlab-org/gitlab-test/merge_requests/20#note_1756', + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + }, + ], + individual_note: false, + resolvable: true, + resolved: false, + resolve_path: + '/gitlab-org/gitlab-test/merge_requests/20/discussions/6b232e05bea388c6b043ccc243ba505faac04ea8/resolve', + resolve_with_issue_path: + '/gitlab-org/gitlab-test/issues/new?discussion_to_resolve=6b232e05bea388c6b043ccc243ba505faac04ea8&merge_request_to_resolve_discussions_of=20', + diff_file: { + submodule: false, + submodule_link: null, + blob: { + id: '9e10516ca50788acf18c518a231914a21e5f16f7', + path: 'CHANGELOG', + name: 'CHANGELOG', + mode: '100644', + readable_text: true, + icon: 'file-text-o', + }, + blob_path: 'CHANGELOG', + blob_name: 'CHANGELOG', + blob_icon: '<i aria-hidden="true" data-hidden="true" class="fa fa-file-text-o fa-fw"></i>', + file_hash: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a', + file_path: 'CHANGELOG.rb', + new_file: false, + deleted_file: false, + renamed_file: false, + old_path: 'CHANGELOG', + new_path: 'CHANGELOG', + mode_changed: false, + a_mode: '100644', + b_mode: '100644', + text: true, + added_lines: 2, + removed_lines: 0, + diff_refs: { + base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', + start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', + head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', + }, + content_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', + stored_externally: null, + external_storage: null, + old_path_html: 'CHANGELOG_OLD', + new_path_html: 'CHANGELOG', + is_fully_expanded: true, + context_lines_path: + '/gitlab-org/gitlab-test/blob/c48ee0d1bf3b30453f5b32250ce03134beaa6d13/CHANGELOG/diff', + highlighted_diff_lines: [ + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1', + type: 'new', + old_line: null, + new_line: 1, + text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + rich_text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', + type: 'new', + old_line: null, + new_line: 2, + text: '<span id="LC2" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + type: null, + old_line: 1, + new_line: 3, + text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4', + type: null, + old_line: 2, + new_line: 4, + text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5', + type: null, + old_line: 3, + new_line: 5, + text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + meta_data: null, + }, + { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + ], + parallel_diff_lines: [ + { + left: null, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_1', + type: 'new', + old_line: null, + new_line: 1, + text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + rich_text: '<span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n', + meta_data: null, + }, + }, + { + left: null, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', + type: 'new', + old_line: null, + new_line: 2, + text: '<span id="LC2" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC2" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + }, + { + left: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + type: null, + old_line: 1, + new_line: 3, + text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + meta_data: null, + }, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_3', + type: null, + old_line: 1, + new_line: 3, + text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + rich_text: '<span id="LC3" class="line" lang="plaintext">v6.8.0</span>\n', + meta_data: null, + }, + }, + { + left: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4', + type: null, + old_line: 2, + new_line: 4, + text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_2_4', + type: null, + old_line: 2, + new_line: 4, + text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + rich_text: '<span id="LC4" class="line" lang="plaintext"></span>\n', + meta_data: null, + }, + }, + { + left: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5', + type: null, + old_line: 3, + new_line: 5, + text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + meta_data: null, + }, + right: { + line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_3_5', + type: null, + old_line: 3, + new_line: 5, + text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + rich_text: ' <span id="LC5" class="line" lang="plaintext">v6.7.0</span>\n', + meta_data: null, + }, + }, + { + left: { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + right: { + line_code: null, + type: 'match', + old_line: null, + new_line: null, + text: '', + rich_text: '', + meta_data: { + old_pos: 3, + new_pos: 5, + }, + }, + }, + ], + viewer: { + name: 'text', + error: null, + }, + }, + diff_discussion: true, + truncated_diff_lines: [ + { + text: 'line', + rich_text: + '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n', + can_receive_suggestion: true, + line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1', + type: 'new', + old_line: null, + new_line: 1, + meta_data: null, + }, + ], +}; + +export const imageDiffDiscussions = [ + { + id: '1', + position: { + x: 10, + y: 10, + width: 100, + height: 200, + }, + }, + { + id: '2', + position: { + x: 5, + y: 5, + width: 100, + height: 200, + }, + }, +]; diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js index 9e920d59093..dc886d0db3b 100644 --- a/spec/frontend/lib/utils/text_utility_spec.js +++ b/spec/frontend/lib/utils/text_utility_spec.js @@ -55,9 +55,24 @@ describe('text_utility', () => { }); }); - describe('slugifyWithHyphens', () => { + describe('slugify', () => { + it('should remove accents and convert to lower case', () => { + expect(textUtils.slugify('João')).toEqual('jo-o'); + }); it('should replaces whitespaces with hyphens and convert to lower case', () => { - expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string'); + expect(textUtils.slugify('My Input String')).toEqual('my-input-string'); + }); + it('should remove trailing whitespace and replace whitespaces within string with a hyphen', () => { + expect(textUtils.slugify(' a new project ')).toEqual('a-new-project'); + }); + it('should only remove non-allowed special characters', () => { + expect(textUtils.slugify('test!_pro-ject~')).toEqual('test-_pro-ject-'); + }); + it('should squash multiple hypens', () => { + expect(textUtils.slugify('test!!!!_pro-ject~')).toEqual('test-_pro-ject-'); + }); + it('should return empty string if only non-allowed characters', () => { + expect(textUtils.slugify('здрасти')).toEqual(''); }); }); diff --git a/spec/frontend/notes/components/discussion_notes_spec.js b/spec/frontend/notes/components/discussion_notes_spec.js index 394666403ee..58d367077e8 100644 --- a/spec/frontend/notes/components/discussion_notes_spec.js +++ b/spec/frontend/notes/components/discussion_notes_spec.js @@ -87,7 +87,7 @@ describe('DiscussionNotes', () => { discussion.notes[0], ]; discussion.notes = notesData; - createComponent({ discussion }); + createComponent({ discussion, shouldRenderDiffs: true }); const notes = wrapper.findAll('.notes > li'); expect(notes.at(0).is(PlaceholderSystemNote)).toBe(true); diff --git a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js index 07a366cf339..e008f4ed093 100644 --- a/spec/frontend/notes/components/discussion_reply_placeholder_spec.js +++ b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js @@ -2,13 +2,19 @@ import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vu import { shallowMount, createLocalVue } from '@vue/test-utils'; const localVue = createLocalVue(); +const buttonText = 'Test Button Text'; describe('ReplyPlaceholder', () => { let wrapper; + const findButton = () => wrapper.find({ ref: 'button' }); + beforeEach(() => { wrapper = shallowMount(ReplyPlaceholder, { localVue, + propsData: { + buttonText, + }, }); }); @@ -17,9 +23,7 @@ describe('ReplyPlaceholder', () => { }); it('emits onClick even on button click', () => { - const button = wrapper.find({ ref: 'button' }); - - button.trigger('click'); + findButton().trigger('click'); expect(wrapper.emitted()).toEqual({ onClick: [[]], @@ -27,8 +31,6 @@ describe('ReplyPlaceholder', () => { }); it('should render reply button', () => { - const button = wrapper.find({ ref: 'button' }); - - expect(button.text()).toEqual('Reply...'); + expect(findButton().text()).toEqual(buttonText); }); }); diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js index 596a1ba5ad2..d4280d3ec2c 100644 --- a/spec/javascripts/diffs/components/diff_file_header_spec.js +++ b/spec/javascripts/diffs/components/diff_file_header_spec.js @@ -521,7 +521,7 @@ describe('diff_file_header', () => { }); describe('with discussions', () => { - it('dispatches toggleFileDiscussions when user clicks on toggle discussions button', () => { + it('dispatches toggleFileDiscussionWrappers when user clicks on toggle discussions button', () => { const propsCopy = Object.assign({}, props); propsCopy.diffFile.submodule = false; propsCopy.diffFile.blob = { @@ -552,11 +552,11 @@ describe('diff_file_header', () => { }), }); - spyOn(vm, 'toggleFileDiscussions'); + spyOn(vm, 'toggleFileDiscussionWrappers'); vm.$el.querySelector('.js-btn-vue-toggle-comments').click(); - expect(vm.toggleFileDiscussions).toHaveBeenCalled(); + expect(vm.toggleFileDiscussionWrappers).toHaveBeenCalled(); }); }); }); diff --git a/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js b/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js deleted file mode 100644 index cdd30919b09..00000000000 --- a/spec/javascripts/diffs/components/diff_gutter_avatars_spec.js +++ /dev/null @@ -1,146 +0,0 @@ -import Vue from 'vue'; -import DiffGutterAvatarsComponent from '~/diffs/components/diff_gutter_avatars.vue'; -import { COUNT_OF_AVATARS_IN_GUTTER } from '~/diffs/constants'; -import store from '~/mr_notes/stores'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import discussionsMockData from '../mock_data/diff_discussions'; - -describe('DiffGutterAvatars', () => { - let component; - const getDiscussionsMockData = () => [Object.assign({}, discussionsMockData)]; - - beforeEach(() => { - component = createComponentWithStore(Vue.extend(DiffGutterAvatarsComponent), store, { - discussions: getDiscussionsMockData(), - }).$mount(); - }); - - describe('computed', () => { - describe('discussionsExpanded', () => { - it('should return true when all discussions are expanded', () => { - expect(component.discussionsExpanded).toEqual(true); - }); - - it('should return false when all discussions are not expanded', () => { - component.discussions[0].expanded = false; - - expect(component.discussionsExpanded).toEqual(false); - }); - }); - - describe('allDiscussions', () => { - it('should return an array of notes', () => { - expect(component.allDiscussions).toEqual([...component.discussions[0].notes]); - }); - }); - - describe('notesInGutter', () => { - it('should return a subset of discussions to show in gutter', () => { - expect(component.notesInGutter.length).toEqual(COUNT_OF_AVATARS_IN_GUTTER); - expect(component.notesInGutter[0]).toEqual({ - note: component.discussions[0].notes[0].note, - author: component.discussions[0].notes[0].author, - }); - }); - }); - - describe('moreCount', () => { - it('should return count of remaining discussions from gutter', () => { - expect(component.moreCount).toEqual(2); - }); - }); - - describe('moreText', () => { - it('should return proper text if moreCount > 0', () => { - expect(component.moreText).toEqual('2 more comments'); - }); - - it('should return empty string if there is no discussion', () => { - component.discussions = []; - - expect(component.moreText).toEqual(''); - }); - }); - }); - - describe('methods', () => { - describe('getTooltipText', () => { - it('should return original comment if it is shorter than max length', () => { - const note = component.discussions[0].notes[0]; - - expect(component.getTooltipText(note)).toEqual('Administrator: comment 1'); - }); - - it('should return truncated version of comment', () => { - const note = component.discussions[0].notes[1]; - - expect(component.getTooltipText(note)).toEqual('Fatih Acet: comment 2 is r...'); - }); - }); - - describe('toggleDiscussions', () => { - it('should toggle all discussions', () => { - expect(component.discussions[0].expanded).toEqual(true); - - component.$store.dispatch('setInitialNotes', getDiscussionsMockData()); - component.discussions = component.$store.state.notes.discussions; - component.toggleDiscussions(); - - expect(component.discussions[0].expanded).toEqual(false); - component.$store.dispatch('setInitialNotes', []); - }); - - it('forces expansion of all discussions', () => { - spyOn(component.$store, 'dispatch'); - - component.discussions[0].expanded = true; - component.discussions.push({ - ...component.discussions[0], - id: '123test', - expanded: false, - }); - - component.toggleDiscussions(); - - expect(component.$store.dispatch.calls.argsFor(0)).toEqual([ - 'toggleDiscussion', - { - discussionId: component.discussions[0].id, - forceExpanded: true, - }, - ]); - - expect(component.$store.dispatch.calls.argsFor(1)).toEqual([ - 'toggleDiscussion', - { - discussionId: component.discussions[1].id, - forceExpanded: true, - }, - ]); - }); - }); - }); - - describe('template', () => { - const buttonSelector = '.js-diff-comment-button'; - const svgSelector = `${buttonSelector} svg`; - const avatarSelector = '.js-diff-comment-avatar'; - const plusCountSelector = '.js-diff-comment-plus'; - - it('should have button to collapse discussions when the discussions expanded', () => { - expect(component.$el.querySelector(buttonSelector)).toBeDefined(); - expect(component.$el.querySelector(svgSelector)).toBeDefined(); - }); - - it('should have user avatars when discussions collapsed', () => { - component.discussions[0].expanded = false; - - Vue.nextTick(() => { - expect(component.$el.querySelector(buttonSelector)).toBeNull(); - expect(component.$el.querySelectorAll(avatarSelector).length).toEqual(4); - expect(component.$el.querySelector(plusCountSelector)).toBeDefined(); - expect(component.$el.querySelector(plusCountSelector).textContent).toEqual('+2'); - }); - }); - }); -}); diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js index 4452106580a..0b3890b68d6 100644 --- a/spec/javascripts/diffs/components/inline_diff_view_spec.js +++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js @@ -36,10 +36,11 @@ describe('InlineDiffView', () => { it('should render discussions', done => { const el = component.$el; component.diffLines[1].discussions = getDiscussionsMockData(); + component.diffLines[1].discussionsExpanded = true; Vue.nextTick(() => { expect(el.querySelectorAll('.notes_holder').length).toEqual(1); - expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(5); + expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(6); expect(el.innerText.indexOf('comment 5')).toBeGreaterThan(-1); component.$store.dispatch('setInitialNotes', []); diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index f973728cfe1..f8872a3eb13 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -206,6 +206,7 @@ describe('DiffsStoreActions', () => { position_type: 'text', }, }, + hash: 'diff-content-1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a', }, }, ], diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js index c98366dd54f..74805ca8c00 100644 --- a/spec/javascripts/notes/components/noteable_discussion_spec.js +++ b/spec/javascripts/notes/components/noteable_discussion_spec.js @@ -36,6 +36,12 @@ describe('noteable_discussion component', () => { }); it('should render user avatar', () => { + const discussion = { ...discussionMock }; + discussion.diff_file = mockDiffFile; + discussion.diff_discussion = true; + + wrapper.setProps({ discussion, renderDiffFile: true }); + expect(wrapper.find('.user-avatar-link').exists()).toBe(true); }); diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js index 7a9f32ddcff..65f72a135aa 100644 --- a/spec/javascripts/notes/stores/actions_spec.js +++ b/spec/javascripts/notes/stores/actions_spec.js @@ -373,7 +373,7 @@ describe('Actions Notes Store', () => { type: 'updateMergeRequestWidget', }, { - type: 'updateResolvableDiscussonsCounts', + type: 'updateResolvableDiscussionsCounts', }, ], done, @@ -400,7 +400,7 @@ describe('Actions Notes Store', () => { type: 'updateMergeRequestWidget', }, { - type: 'updateResolvableDiscussonsCounts', + type: 'updateResolvableDiscussionsCounts', }, { type: 'diffs/removeDiscussionsFromDiff', @@ -452,7 +452,7 @@ describe('Actions Notes Store', () => { type: 'startTaskList', }, { - type: 'updateResolvableDiscussonsCounts', + type: 'updateResolvableDiscussionsCounts', }, ], done, @@ -527,7 +527,7 @@ describe('Actions Notes Store', () => { ], [ { - type: 'updateResolvableDiscussonsCounts', + type: 'updateResolvableDiscussionsCounts', }, { type: 'updateMergeRequestWidget', @@ -552,7 +552,7 @@ describe('Actions Notes Store', () => { ], [ { - type: 'updateResolvableDiscussonsCounts', + type: 'updateResolvableDiscussionsCounts', }, { type: 'updateMergeRequestWidget', @@ -587,10 +587,10 @@ describe('Actions Notes Store', () => { }); }); - describe('updateResolvableDiscussonsCounts', () => { + describe('updateResolvableDiscussionsCounts', () => { it('commits UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS', done => { testAction( - actions.updateResolvableDiscussonsCounts, + actions.updateResolvableDiscussionsCounts, null, {}, [{ type: 'UPDATE_RESOLVABLE_DISCUSSIONS_COUNTS' }], @@ -712,7 +712,7 @@ describe('Actions Notes Store', () => { [ { type: 'updateMergeRequestWidget' }, { type: 'startTaskList' }, - { type: 'updateResolvableDiscussonsCounts' }, + { type: 'updateResolvableDiscussionsCounts' }, ], done, ); diff --git a/spec/javascripts/performance_bar/components/simple_metric_spec.js b/spec/javascripts/performance_bar/components/simple_metric_spec.js deleted file mode 100644 index 98b843e9711..00000000000 --- a/spec/javascripts/performance_bar/components/simple_metric_spec.js +++ /dev/null @@ -1,47 +0,0 @@ -import Vue from 'vue'; -import simpleMetric from '~/performance_bar/components/simple_metric.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; - -describe('simpleMetric', () => { - let vm; - - afterEach(() => { - vm.$destroy(); - }); - - describe('when the current request has no details', () => { - beforeEach(() => { - vm = mountComponent(Vue.extend(simpleMetric), { - currentRequest: {}, - metric: 'gitaly', - }); - }); - - it('does not display details', () => { - expect(vm.$el.innerText).not.toContain('/'); - }); - - it('displays the metric name', () => { - expect(vm.$el.innerText).toContain('gitaly'); - }); - }); - - describe('when the current request has details', () => { - beforeEach(() => { - vm = mountComponent(Vue.extend(simpleMetric), { - currentRequest: { - details: { gitaly: { duration: '123ms', calls: '456' } }, - }, - metric: 'gitaly', - }); - }); - - it('diplays details', () => { - expect(vm.$el.innerText.replace(/\s+/g, ' ')).toContain('123ms / 456'); - }); - - it('displays the metric name', () => { - expect(vm.$el.innerText).toContain('gitaly'); - }); - }); -}); diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js index 76a17e6fb31..87237d2853d 100644 --- a/spec/javascripts/registry/components/app_spec.js +++ b/spec/javascripts/registry/components/app_spec.js @@ -8,6 +8,13 @@ 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; @@ -24,7 +31,7 @@ describe('Registry List', () => { beforeEach(() => { mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, reposServerResponse); - vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` }); + vm = mountComponent(Component, { ...props }); }); it('should render a list of repos', done => { @@ -72,7 +79,7 @@ describe('Registry List', () => { beforeEach(() => { mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []); - vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` }); + vm = mountComponent(Component, { ...props }); }); it('should render empty message', done => { @@ -83,7 +90,7 @@ describe('Registry List', () => { .textContent.trim() .replace(/[\r\n]+/g, ' '), ).toEqual( - 'No container images stored for this project. Add one by following the instructions above.', + 'With the Container Registry, every project can have its own space to store its Docker images. Learn more about the Container Registry.', ); done(); }, 0); @@ -94,7 +101,7 @@ describe('Registry List', () => { beforeEach(() => { mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []); - vm = mountComponent(Component, { endpoint: `${TEST_HOST}/foo` }); + vm = mountComponent(Component, { ...props }); }); it('should render a loading spinner', done => { @@ -104,4 +111,22 @@ describe('Registry List', () => { }); }); }); + + 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('.container-message')).not.toBe(null); + done(); + }); + }); + }); }); diff --git a/spec/javascripts/releases/components/release_block_spec.js b/spec/javascripts/releases/components/release_block_spec.js index e98c665f99d..f761a18e326 100644 --- a/spec/javascripts/releases/components/release_block_spec.js +++ b/spec/javascripts/releases/components/release_block_spec.js @@ -14,7 +14,7 @@ describe('Release block', () => { description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', author_name: 'Release bot', author_email: 'release-bot@example.com', - created_at: '2012-05-28T05:00:00-07:00', + released_at: '2012-05-28T05:00:00-07:00', author: { avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', id: 482476, @@ -101,7 +101,7 @@ describe('Release block', () => { }); it('renders release date', () => { - expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.created_at)); + expect(vm.$el.textContent).toContain(timeagoMixin.methods.timeFormated(release.released_at)); }); it('renders number of assets provided', () => { @@ -152,13 +152,13 @@ describe('Release block', () => { }); }); - describe('with pre_release flag', () => { + describe('with upcoming_release flag', () => { beforeEach(() => { - vm = factory(Object.assign({}, release, { pre_release: true })); + vm = factory(Object.assign({}, release, { upcoming_release: true })); }); - it('renders pre-release badge', () => { - expect(vm.$el.textContent).toContain('Pre-release'); + it('renders upcoming release badge', () => { + expect(vm.$el.textContent).toContain('Upcoming Release'); }); }); }); diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb index 6f05914f915..403e0785d1b 100644 --- a/spec/lib/feature_spec.rb +++ b/spec/lib/feature_spec.rb @@ -40,7 +40,7 @@ describe Feature do .once .and_call_original - expect(Rails.cache) + expect(Gitlab::ThreadMemoryCache.cache_backend) .to receive(:fetch) .once .with('flipper:persisted_names', expires_in: 1.minute) diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 3b5ca7c950c..d9c73cff01e 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -309,6 +309,15 @@ describe Gitlab::Auth do .to eq(auth_success) end + it 'succeeds when custom login and token are valid' do + deploy_token = create(:deploy_token, username: 'deployer', read_registry: false, projects: [project]) + auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code]) + + expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer') + expect(gl_auth.find_for_git_client('deployer', deploy_token.token, project: project, ip: 'ip')) + .to eq(auth_success) + end + it 'fails when login is not valid' do expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'random_login') expect(gl_auth.find_for_git_client('random_login', deploy_token.token, project: project, ip: 'ip')) diff --git a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb b/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb deleted file mode 100644 index 5076996474f..00000000000 --- a/spec/lib/gitlab/background_migration/create_fork_network_memberships_range_spec.rb +++ /dev/null @@ -1,125 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange, :migration, schema: 20170929131201 do - let(:migration) { described_class.new } - let(:projects) { table(:projects) } - - let(:base1) { projects.create } - let(:base1_fork1) { projects.create } - let(:base1_fork2) { projects.create } - - let(:base2) { projects.create } - let(:base2_fork1) { projects.create } - let(:base2_fork2) { projects.create } - - let(:fork_of_fork) { projects.create } - let(:fork_of_fork2) { projects.create } - let(:second_level_fork) { projects.create } - let(:third_level_fork) { projects.create } - - let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) } - let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) } - - let!(:forked_project_links) { table(:forked_project_links) } - let!(:fork_networks) { table(:fork_networks) } - let!(:fork_network_members) { table(:fork_network_members) } - - before do - # The fork-network relation created for the forked project - fork_networks.create(id: 1, root_project_id: base1.id) - fork_network_members.create(project_id: base1.id, fork_network_id: 1) - fork_networks.create(id: 2, root_project_id: base2.id) - fork_network_members.create(project_id: base2.id, fork_network_id: 2) - - # Normal fork links - forked_project_links.create(id: 1, forked_from_project_id: base1.id, forked_to_project_id: base1_fork1.id) - forked_project_links.create(id: 2, forked_from_project_id: base1.id, forked_to_project_id: base1_fork2.id) - forked_project_links.create(id: 3, forked_from_project_id: base2.id, forked_to_project_id: base2_fork1.id) - forked_project_links.create(id: 4, forked_from_project_id: base2.id, forked_to_project_id: base2_fork2.id) - - # Fork links - forked_project_links.create(id: 5, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork.id) - forked_project_links.create(id: 6, forked_from_project_id: base1_fork1.id, forked_to_project_id: fork_of_fork2.id) - - # Forks 3 levels down - forked_project_links.create(id: 7, forked_from_project_id: fork_of_fork.id, forked_to_project_id: second_level_fork.id) - forked_project_links.create(id: 8, forked_from_project_id: second_level_fork.id, forked_to_project_id: third_level_fork.id) - - migration.perform(1, 8) - end - - it 'creates a memberships for the direct forks' do - base1_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id, - project_id: base1_fork1.id) - base1_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network1.id, - project_id: base1_fork2.id) - base2_fork1_membership = fork_network_members.find_by(fork_network_id: fork_network2.id, - project_id: base2_fork1.id) - base2_fork2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id, - project_id: base2_fork2.id) - - expect(base1_fork1_membership.forked_from_project_id).to eq(base1.id) - expect(base1_fork2_membership.forked_from_project_id).to eq(base1.id) - expect(base2_fork1_membership.forked_from_project_id).to eq(base2.id) - expect(base2_fork2_membership.forked_from_project_id).to eq(base2.id) - end - - it 'adds the fork network members for forks of forks' do - fork_of_fork_membership = fork_network_members.find_by(project_id: fork_of_fork.id, - fork_network_id: fork_network1.id) - fork_of_fork2_membership = fork_network_members.find_by(project_id: fork_of_fork2.id, - fork_network_id: fork_network1.id) - second_level_fork_membership = fork_network_members.find_by(project_id: second_level_fork.id, - fork_network_id: fork_network1.id) - third_level_fork_membership = fork_network_members.find_by(project_id: third_level_fork.id, - fork_network_id: fork_network1.id) - - expect(fork_of_fork_membership.forked_from_project_id).to eq(base1_fork1.id) - expect(fork_of_fork2_membership.forked_from_project_id).to eq(base1_fork1.id) - expect(second_level_fork_membership.forked_from_project_id).to eq(fork_of_fork.id) - expect(third_level_fork_membership.forked_from_project_id).to eq(second_level_fork.id) - end - - it 'reschedules itself when there are missing members' do - allow(migration).to receive(:missing_members?).and_return(true) - - expect(BackgroundMigrationWorker) - .to receive(:perform_in).with(described_class::RESCHEDULE_DELAY, "CreateForkNetworkMembershipsRange", [1, 3]) - - migration.perform(1, 3) - end - - it 'can be repeated without effect' do - expect { fork_network_members.count }.not_to change { migration.perform(1, 7) } - end - - it 'knows it is finished for this range' do - expect(migration.missing_members?(1, 8)).to be_falsy - end - - it 'does not miss members for forks of forks for which the root was deleted' do - forked_project_links.create(id: 9, forked_from_project_id: base1_fork1.id, forked_to_project_id: projects.create.id) - base1.destroy - - expect(migration.missing_members?(7, 10)).to be_falsy - end - - context 'with more forks' do - before do - forked_project_links.create(id: 9, forked_from_project_id: fork_of_fork.id, forked_to_project_id: projects.create.id) - forked_project_links.create(id: 10, forked_from_project_id: fork_of_fork.id, forked_to_project_id: projects.create.id) - end - - it 'only processes a single batch of links at a time' do - expect(fork_network_members.count).to eq(10) - - migration.perform(8, 10) - - expect(fork_network_members.count).to eq(12) - end - - it 'knows when not all memberships within a batch have been created' do - expect(migration.missing_members?(8, 10)).to be_truthy - end - end -end diff --git a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb deleted file mode 100644 index 9bae7e53b71..00000000000 --- a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::DeleteConflictingRedirectRoutesRange, :migration, schema: 20170907170235 do - let!(:redirect_routes) { table(:redirect_routes) } - let!(:routes) { table(:routes) } - - before do - routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1') - routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2') - routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3') - routes.create!(id: 4, source_id: 4, source_type: 'Namespace', path: 'foo4') - routes.create!(id: 5, source_id: 5, source_type: 'Namespace', path: 'foo5') - - # Valid redirects - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar2') - redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'bar3') - - # Conflicting redirects - redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'foo1') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo2') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo3') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo4') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5') - end - - # No-op. See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 - it 'NO-OP: does not delete any redirect_routes' do - expect(redirect_routes.count).to eq(8) - - described_class.new.perform(1, 5) - - expect(redirect_routes.count).to eq(8) - end -end diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb deleted file mode 100644 index 188969951a6..00000000000 --- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb +++ /dev/null @@ -1,433 +0,0 @@ -require 'spec_helper' - -# rubocop:disable RSpec/FactoriesInMigrationSpecs -describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event, :migration, schema: 20170608152748 do - describe '#commit_title' do - it 'returns nil when there are no commits' do - expect(described_class.new.commit_title).to be_nil - end - - it 'returns nil when there are commits without commit messages' do - event = described_class.new - - allow(event).to receive(:commits).and_return([{ id: '123' }]) - - expect(event.commit_title).to be_nil - end - - it 'returns the commit message when it is less than 70 characters long' do - event = described_class.new - - allow(event).to receive(:commits).and_return([{ message: 'Hello world' }]) - - expect(event.commit_title).to eq('Hello world') - end - - it 'returns the first line of a commit message if multiple lines are present' do - event = described_class.new - - allow(event).to receive(:commits).and_return([{ message: "Hello\n\nworld" }]) - - expect(event.commit_title).to eq('Hello') - end - - it 'truncates the commit to 70 characters when it is too long' do - event = described_class.new - - allow(event).to receive(:commits).and_return([{ message: 'a' * 100 }]) - - expect(event.commit_title).to eq(('a' * 67) + '...') - end - end - - describe '#commit_from_sha' do - it 'returns nil when pushing to a new ref' do - event = described_class.new - - allow(event).to receive(:create?).and_return(true) - - expect(event.commit_from_sha).to be_nil - end - - it 'returns the ID of the first commit when pushing to an existing ref' do - event = described_class.new - - allow(event).to receive(:create?).and_return(false) - allow(event).to receive(:data).and_return(before: '123') - - expect(event.commit_from_sha).to eq('123') - end - end - - describe '#commit_to_sha' do - it 'returns nil when removing an existing ref' do - event = described_class.new - - allow(event).to receive(:remove?).and_return(true) - - expect(event.commit_to_sha).to be_nil - end - - it 'returns the ID of the last commit when pushing to an existing ref' do - event = described_class.new - - allow(event).to receive(:remove?).and_return(false) - allow(event).to receive(:data).and_return(after: '123') - - expect(event.commit_to_sha).to eq('123') - end - end - - describe '#data' do - it 'returns the deserialized data' do - event = described_class.new(data: { before: '123' }) - - expect(event.data).to eq(before: '123') - end - - it 'returns an empty hash when no data is present' do - event = described_class.new - - expect(event.data).to eq({}) - end - end - - describe '#commits' do - it 'returns an Array of commits' do - event = described_class.new(data: { commits: [{ id: '123' }] }) - - expect(event.commits).to eq([{ id: '123' }]) - end - - it 'returns an empty array when no data is present' do - event = described_class.new - - expect(event.commits).to eq([]) - end - end - - describe '#commit_count' do - it 'returns the number of commits' do - event = described_class.new(data: { total_commits_count: 2 }) - - expect(event.commit_count).to eq(2) - end - - it 'returns 0 when no data is present' do - event = described_class.new - - expect(event.commit_count).to eq(0) - end - end - - describe '#ref' do - it 'returns the name of the ref' do - event = described_class.new(data: { ref: 'refs/heads/master' }) - - expect(event.ref).to eq('refs/heads/master') - end - end - - describe '#trimmed_ref_name' do - it 'returns the trimmed ref name for a branch' do - event = described_class.new(data: { ref: 'refs/heads/master' }) - - expect(event.trimmed_ref_name).to eq('master') - end - - it 'returns the trimmed ref name for a tag' do - event = described_class.new(data: { ref: 'refs/tags/v1.2' }) - - expect(event.trimmed_ref_name).to eq('v1.2') - end - end - - describe '#create?' do - it 'returns true when creating a new ref' do - event = described_class.new(data: { before: described_class::BLANK_REF }) - - expect(event.create?).to eq(true) - end - - it 'returns false when pushing to an existing ref' do - event = described_class.new(data: { before: '123' }) - - expect(event.create?).to eq(false) - end - end - - describe '#remove?' do - it 'returns true when removing an existing ref' do - event = described_class.new(data: { after: described_class::BLANK_REF }) - - expect(event.remove?).to eq(true) - end - - it 'returns false when pushing to an existing ref' do - event = described_class.new(data: { after: '123' }) - - expect(event.remove?).to eq(false) - end - end - - describe '#push_action' do - let(:event) { described_class.new } - - it 'returns :created when creating a new ref' do - allow(event).to receive(:create?).and_return(true) - - expect(event.push_action).to eq(:created) - end - - it 'returns :removed when removing an existing ref' do - allow(event).to receive(:create?).and_return(false) - allow(event).to receive(:remove?).and_return(true) - - expect(event.push_action).to eq(:removed) - end - - it 'returns :pushed when pushing to an existing ref' do - allow(event).to receive(:create?).and_return(false) - allow(event).to receive(:remove?).and_return(false) - - expect(event.push_action).to eq(:pushed) - end - end - - describe '#ref_type' do - let(:event) { described_class.new } - - it 'returns :tag for a tag' do - allow(event).to receive(:ref).and_return('refs/tags/1.2') - - expect(event.ref_type).to eq(:tag) - end - - it 'returns :branch for a branch' do - allow(event).to receive(:ref).and_return('refs/heads/1.2') - - expect(event.ref_type).to eq(:branch) - end - end -end - -## -# The background migration relies on a temporary table, hence we're migrating -# to a specific version of the database where said table is still present. -# -describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170825154015 do - let(:user_class) do - Class.new(ActiveRecord::Base) do - self.table_name = 'users' - end - end - - let(:migration) { described_class.new } - let(:user_class) { table(:users) } - let(:author) { build(:user).becomes(user_class).tap(&:save!).becomes(User) } - let(:namespace) { create(:namespace, owner: author) } - let(:projects) { table(:projects) } - let(:project) { projects.create(namespace_id: namespace.id, creator_id: author.id) } - - # We can not rely on FactoryBot as the state of Event may change in ways that - # the background migration does not expect, hence we use the Event class of - # the migration itself. - def create_push_event(project, author, data = nil) - klass = Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event - - klass.create!( - action: klass::PUSHED, - project_id: project.id, - author_id: author.id, - data: data - ) - end - - describe '#perform' do - it 'returns if data should not be migrated' do - allow(migration).to receive(:migrate?).and_return(false) - - expect(migration).not_to receive(:find_events) - - migration.perform(1, 10) - end - - it 'migrates the range of events if data is to be migrated' do - event1 = create_push_event(project, author, { commits: [] }) - event2 = create_push_event(project, author, { commits: [] }) - - allow(migration).to receive(:migrate?).and_return(true) - - expect(migration).to receive(:process_event).twice - - migration.perform(event1.id, event2.id) - end - end - - describe '#process_event' do - it 'processes a regular event' do - event = double(:event, push_event?: false) - - expect(migration).to receive(:replicate_event) - expect(migration).not_to receive(:create_push_event_payload) - - migration.process_event(event) - end - - it 'processes a push event' do - event = double(:event, push_event?: true) - - expect(migration).to receive(:replicate_event) - expect(migration).to receive(:create_push_event_payload) - - migration.process_event(event) - end - - it 'handles an error gracefully' do - event1 = create_push_event(project, author, { commits: [] }) - - expect(migration).to receive(:replicate_event).and_call_original - expect(migration).to receive(:create_push_event_payload).and_raise(ActiveRecord::InvalidForeignKey, 'invalid foreign key') - - migration.process_event(event1) - - expect(described_class::EventForMigration.all.count).to eq(0) - end - end - - describe '#replicate_event' do - it 'replicates the event to the "events_for_migration" table' do - event = create_push_event( - project, - author, - data: { commits: [] }, - title: 'bla' - ) - - attributes = event - .attributes.with_indifferent_access.except(:title, :data) - - expect(described_class::EventForMigration) - .to receive(:create!) - .with(attributes) - - migration.replicate_event(event) - end - end - - describe '#create_push_event_payload' do - let(:push_data) do - { - commits: [], - ref: 'refs/heads/master', - before: '156e0e9adc587a383a7eeb5b21ddecb9044768a8', - after: '0' * 40, - total_commits_count: 1 - } - end - - let(:event) do - create_push_event(project, author, push_data) - end - - before do - # The foreign key in push_event_payloads at this point points to the - # "events_for_migration" table so we need to make sure a row exists in - # said table. - migration.replicate_event(event) - end - - it 'creates a push event payload for an event' do - payload = migration.create_push_event_payload(event) - - expect(PushEventPayload.count).to eq(1) - expect(payload.valid?).to eq(true) - end - - it 'does not create push event payloads for removed events' do - allow(event).to receive(:id).and_return(-1) - - expect { migration.create_push_event_payload(event) }.to raise_error(ActiveRecord::InvalidForeignKey) - - expect(PushEventPayload.count).to eq(0) - end - - it 'encodes and decodes the commit IDs from and to binary data' do - payload = migration.create_push_event_payload(event) - packed = migration.pack(push_data[:before]) - - expect(payload.commit_from).to eq(packed) - expect(payload.commit_to).to be_nil - end - end - - describe '#find_events' do - it 'returns the events for the given ID range' do - event1 = create_push_event(project, author, { commits: [] }) - event2 = create_push_event(project, author, { commits: [] }) - event3 = create_push_event(project, author, { commits: [] }) - events = migration.find_events(event1.id, event2.id) - - expect(events.length).to eq(2) - expect(events.pluck(:id)).not_to include(event3.id) - end - end - - describe '#migrate?' do - it 'returns true when data should be migrated' do - allow(described_class::Event) - .to receive(:table_exists?).and_return(true) - - allow(described_class::PushEventPayload) - .to receive(:table_exists?).and_return(true) - - allow(described_class::EventForMigration) - .to receive(:table_exists?).and_return(true) - - expect(migration.migrate?).to eq(true) - end - - it 'returns false if the "events" table does not exist' do - allow(described_class::Event) - .to receive(:table_exists?).and_return(false) - - expect(migration.migrate?).to eq(false) - end - - it 'returns false if the "push_event_payloads" table does not exist' do - allow(described_class::Event) - .to receive(:table_exists?).and_return(true) - - allow(described_class::PushEventPayload) - .to receive(:table_exists?).and_return(false) - - expect(migration.migrate?).to eq(false) - end - - it 'returns false when the "events_for_migration" table does not exist' do - allow(described_class::Event) - .to receive(:table_exists?).and_return(true) - - allow(described_class::PushEventPayload) - .to receive(:table_exists?).and_return(true) - - allow(described_class::EventForMigration) - .to receive(:table_exists?).and_return(false) - - expect(migration.migrate?).to eq(false) - end - end - - describe '#pack' do - it 'packs a SHA1 into a 20 byte binary string' do - packed = migration.pack('156e0e9adc587a383a7eeb5b21ddecb9044768a8') - - expect(packed.bytesize).to eq(20) - end - - it 'returns nil if the input value is nil' do - expect(migration.pack(nil)).to be_nil - end - end -end -# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb deleted file mode 100644 index 89b56906ed0..00000000000 --- a/spec/lib/gitlab/background_migration/migrate_stage_status_spec.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Gitlab::BackgroundMigration::MigrateStageStatus, :migration, schema: 20170711145320 do - let(:projects) { table(:projects) } - let(:pipelines) { table(:ci_pipelines) } - let(:stages) { table(:ci_stages) } - let(:jobs) { table(:ci_builds) } - - let(:statuses) do - { - created: 0, - pending: 1, - running: 2, - success: 3, - failed: 4, - canceled: 5, - skipped: 6, - manual: 7 - } - end - - before do - projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1') - pipelines.create!(id: 1, project_id: 1, ref: 'master', sha: 'adf43c3a') - stages.create!(id: 1, pipeline_id: 1, project_id: 1, name: 'test', status: nil) - stages.create!(id: 2, pipeline_id: 1, project_id: 1, name: 'deploy', status: nil) - end - - context 'when stage status is known' do - before do - create_job(project: 1, pipeline: 1, stage: 'test', status: 'success') - create_job(project: 1, pipeline: 1, stage: 'test', status: 'running') - create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'failed') - end - - it 'sets a correct stage status' do - described_class.new.perform(1, 2) - - expect(stages.first.status).to eq statuses[:running] - expect(stages.second.status).to eq statuses[:failed] - end - end - - context 'when stage status is not known' do - it 'sets a skipped stage status' do - described_class.new.perform(1, 2) - - expect(stages.first.status).to eq statuses[:skipped] - expect(stages.second.status).to eq statuses[:skipped] - end - end - - context 'when stage status includes status of a retried job' do - before do - create_job(project: 1, pipeline: 1, stage: 'test', status: 'canceled') - create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'failed', retried: true) - create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'success') - end - - it 'sets a correct stage status' do - described_class.new.perform(1, 2) - - expect(stages.first.status).to eq statuses[:canceled] - expect(stages.second.status).to eq statuses[:success] - end - end - - context 'when some job in the stage is blocked / manual' do - before do - create_job(project: 1, pipeline: 1, stage: 'test', status: 'failed') - create_job(project: 1, pipeline: 1, stage: 'test', status: 'manual') - create_job(project: 1, pipeline: 1, stage: 'deploy', status: 'success', when: 'manual') - end - - it 'sets a correct stage status' do - described_class.new.perform(1, 2) - - expect(stages.first.status).to eq statuses[:manual] - expect(stages.second.status).to eq statuses[:success] - end - end - - def create_job(project:, pipeline:, stage:, status:, **opts) - stages = { test: 1, build: 2, deploy: 3 } - - jobs.create!(project_id: project, commit_id: pipeline, - stage_idx: stages[stage.to_sym], stage: stage, - status: status, **opts) - end -end diff --git a/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb b/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb deleted file mode 100644 index dfbf1bb681a..00000000000 --- a/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::NormalizeLdapExternUidsRange, :migration, schema: 20170921101004 do - let!(:identities) { table(:identities) } - - before do - # LDAP identities - (1..4).each do |i| - identities.create!(id: i, provider: 'ldapmain', extern_uid: " uid = foo #{i}, ou = People, dc = example, dc = com ", user_id: i) - end - - # Non-LDAP identity - identities.create!(id: 5, provider: 'foo', extern_uid: " uid = foo 5, ou = People, dc = example, dc = com ", user_id: 5) - - # Another LDAP identity - identities.create!(id: 6, provider: 'ldapmain', extern_uid: " uid = foo 6, ou = People, dc = example, dc = com ", user_id: 6) - end - - it 'normalizes the LDAP identities in the range' do - described_class.new.perform(1, 3) - expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com") - expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com") - expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com") - expect(identities.find(4).extern_uid).to eq(" uid = foo 4, ou = People, dc = example, dc = com ") - expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ") - expect(identities.find(6).extern_uid).to eq(" uid = foo 6, ou = People, dc = example, dc = com ") - - described_class.new.perform(4, 6) - expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com") - expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com") - expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com") - expect(identities.find(4).extern_uid).to eq("uid=foo 4,ou=people,dc=example,dc=com") - expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ") - expect(identities.find(6).extern_uid).to eq("uid=foo 6,ou=people,dc=example,dc=com") - end -end diff --git a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb b/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb deleted file mode 100644 index 0e73c8c59c9..00000000000 --- a/spec/lib/gitlab/background_migration/populate_fork_networks_range_spec.rb +++ /dev/null @@ -1,97 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::PopulateForkNetworksRange, :migration, schema: 20170929131201 do - let(:migration) { described_class.new } - let(:projects) { table(:projects) } - let(:base1) { projects.create } - - let(:base2) { projects.create } - let(:base2_fork1) { projects.create } - - let!(:forked_project_links) { table(:forked_project_links) } - let!(:fork_networks) { table(:fork_networks) } - let!(:fork_network_members) { table(:fork_network_members) } - - let(:fork_network1) { fork_networks.find_by(root_project_id: base1.id) } - let(:fork_network2) { fork_networks.find_by(root_project_id: base2.id) } - - before do - # A normal fork link - forked_project_links.create(id: 1, - forked_from_project_id: base1.id, - forked_to_project_id: projects.create.id) - forked_project_links.create(id: 2, - forked_from_project_id: base1.id, - forked_to_project_id: projects.create.id) - forked_project_links.create(id: 3, - forked_from_project_id: base2.id, - forked_to_project_id: base2_fork1.id) - - # create a fork of a fork - forked_project_links.create(id: 4, - forked_from_project_id: base2_fork1.id, - forked_to_project_id: projects.create.id) - forked_project_links.create(id: 5, - forked_from_project_id: projects.create.id, - forked_to_project_id: projects.create.id) - - # Stub out the calls to the other migrations - allow(BackgroundMigrationWorker).to receive(:perform_in) - - migration.perform(1, 3) - end - - it 'creates the fork network' do - expect(fork_network1).not_to be_nil - expect(fork_network2).not_to be_nil - end - - it 'does not create a fork network for a fork-of-fork' do - # perfrom the entire batch - migration.perform(1, 5) - - expect(fork_networks.find_by(root_project_id: base2_fork1.id)).to be_nil - end - - it 'creates memberships for the root of fork networks' do - base1_membership = fork_network_members.find_by(fork_network_id: fork_network1.id, - project_id: base1.id) - base2_membership = fork_network_members.find_by(fork_network_id: fork_network2.id, - project_id: base2.id) - - expect(base1_membership).not_to be_nil - expect(base2_membership).not_to be_nil - end - - it 'creates a fork network for the fork of which the source was deleted' do - fork = projects.create - forked_project_links.create(id: 6, forked_from_project_id: 99999, forked_to_project_id: fork.id) - - migration.perform(5, 8) - - expect(fork_networks.find_by(root_project_id: 99999)).to be_nil - expect(fork_networks.find_by(root_project_id: fork.id)).not_to be_nil - expect(fork_network_members.find_by(project_id: fork.id)).not_to be_nil - end - - it 'schedules a job for inserting memberships for forks-of-forks' do - delay = Gitlab::BackgroundMigration::CreateForkNetworkMembershipsRange::RESCHEDULE_DELAY - - expect(BackgroundMigrationWorker) - .to receive(:perform_in).with(delay, "CreateForkNetworkMembershipsRange", [1, 3]) - - migration.perform(1, 3) - end - - it 'only processes a single batch of links at a time' do - expect(fork_networks.count).to eq(2) - - migration.perform(3, 5) - - expect(fork_networks.count).to eq(3) - end - - it 'can be repeated without effect' do - expect { migration.perform(1, 3) }.not_to change { fork_network_members.count } - end -end diff --git a/spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb deleted file mode 100644 index 0cb753c5853..00000000000 --- a/spec/lib/gitlab/background_migration/populate_merge_requests_latest_merge_request_diff_id_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::PopulateMergeRequestsLatestMergeRequestDiffId, :migration, schema: 20171026082505 do - let(:projects_table) { table(:projects) } - let(:merge_requests_table) { table(:merge_requests) } - let(:merge_request_diffs_table) { table(:merge_request_diffs) } - - let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') } - - def create_mr!(name, diffs: 0) - merge_request = - merge_requests_table.create!(target_project_id: project.id, - target_branch: 'master', - source_project_id: project.id, - source_branch: name, - title: name) - - diffs.times do - merge_request_diffs_table.create!(merge_request_id: merge_request.id) - end - - merge_request - end - - def diffs_for(merge_request) - merge_request_diffs_table.where(merge_request_id: merge_request.id) - end - - describe '#perform' do - it 'ignores MRs without diffs' do - merge_request_without_diff = create_mr!('without_diff') - mr_id = merge_request_without_diff.id - - expect(merge_request_without_diff.latest_merge_request_diff_id).to be_nil - - expect { subject.perform(mr_id, mr_id) } - .not_to change { merge_request_without_diff.reload.latest_merge_request_diff_id } - end - - it 'ignores MRs that have a diff ID already set' do - merge_request_with_multiple_diffs = create_mr!('with_multiple_diffs', diffs: 3) - diff_id = diffs_for(merge_request_with_multiple_diffs).minimum(:id) - mr_id = merge_request_with_multiple_diffs.id - - merge_request_with_multiple_diffs.update!(latest_merge_request_diff_id: diff_id) - - expect { subject.perform(mr_id, mr_id) } - .not_to change { merge_request_with_multiple_diffs.reload.latest_merge_request_diff_id } - end - - it 'migrates multiple MR diffs to the correct values' do - merge_requests = Array.new(3).map.with_index { |_, i| create_mr!(i, diffs: 3) } - - subject.perform(merge_requests.first.id, merge_requests.last.id) - - merge_requests.each do |merge_request| - expect(merge_request.reload.latest_merge_request_diff_id) - .to eq(diffs_for(merge_request).maximum(:id)) - end - end - end -end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index a406c25b1d8..28b187c3676 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -123,6 +123,7 @@ Release: - project_id - created_at - updated_at +- released_at Releases::Link: - id - release_id @@ -429,6 +430,7 @@ Service: - confidential_issues_events - confidential_note_events - deployment_events +- description ProjectHook: - id - url diff --git a/spec/lib/gitlab/legacy_github_import/importer_spec.rb b/spec/lib/gitlab/legacy_github_import/importer_spec.rb index a0c664da185..9163019514b 100644 --- a/spec/lib/gitlab/legacy_github_import/importer_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/importer_spec.rb @@ -132,6 +132,7 @@ describe Gitlab::LegacyGithubImport::Importer do body: 'Release v1.0.0', draft: false, created_at: created_at, + published_at: created_at, updated_at: updated_at, url: "#{api_root}/repos/octocat/Hello-World/releases/1" ) @@ -144,6 +145,7 @@ describe Gitlab::LegacyGithubImport::Importer do body: nil, draft: false, created_at: created_at, + published_at: created_at, updated_at: updated_at, url: "#{api_root}/repos/octocat/Hello-World/releases/2" ) diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb index c57b96fb00d..534cf219520 100644 --- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb +++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb @@ -4,6 +4,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) } let(:octocat) { double(id: 123456, login: 'octocat') } let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') } + let(:published_at) { DateTime.strptime('2011-01-26T20:00:00Z') } let(:base_data) do { @@ -11,7 +12,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do name: 'First release', draft: false, created_at: created_at, - published_at: created_at, + published_at: published_at, body: 'Release v1.0.0' } end @@ -28,6 +29,7 @@ describe Gitlab::LegacyGithubImport::ReleaseFormatter do name: 'First release', description: 'Release v1.0.0', created_at: created_at, + released_at: published_at, updated_at: created_at } diff --git a/spec/migrations/add_foreign_key_to_merge_requests_spec.rb b/spec/migrations/add_foreign_key_to_merge_requests_spec.rb deleted file mode 100644 index d9ad9a585f0..00000000000 --- a/spec/migrations/add_foreign_key_to_merge_requests_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20170713104829_add_foreign_key_to_merge_requests.rb') - -describe AddForeignKeyToMergeRequests, :migration do - let(:projects) { table(:projects) } - let(:merge_requests) { table(:merge_requests) } - let(:pipelines) { table(:ci_pipelines) } - - before do - projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') - pipelines.create!(project_id: projects.first.id, - ref: 'some-branch', - sha: 'abc12345') - - # merge request without a pipeline - create_merge_request(head_pipeline_id: nil) - - # merge request with non-existent pipeline - create_merge_request(head_pipeline_id: 1234) - - # merge reqeust with existing pipeline assigned - create_merge_request(head_pipeline_id: pipelines.first.id) - end - - it 'correctly adds a foreign key to head_pipeline_id' do - migrate! - - expect(merge_requests.first.head_pipeline_id).to be_nil - expect(merge_requests.second.head_pipeline_id).to be_nil - expect(merge_requests.third.head_pipeline_id).to eq pipelines.first.id - end - - def create_merge_request(**opts) - merge_requests.create!(source_project_id: projects.first.id, - target_project_id: projects.first.id, - source_branch: 'some-branch', - target_branch: 'master', **opts) - end -end diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb deleted file mode 100644 index 13dc62595b5..00000000000 --- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb') - -describe AddHeadPipelineForEachMergeRequest, :migration do - let(:migration) { described_class.new } - - let!(:project) { table(:projects).create! } - let!(:other_project) { table(:projects).create! } - - let!(:pipeline_1) { table(:ci_pipelines).create!(project_id: project.id, ref: "branch_1") } - let!(:pipeline_2) { table(:ci_pipelines).create!(project_id: other_project.id, ref: "branch_1") } - let!(:pipeline_3) { table(:ci_pipelines).create!(project_id: other_project.id, ref: "branch_1") } - let!(:pipeline_4) { table(:ci_pipelines).create!(project_id: project.id, ref: "branch_2") } - - let!(:mr_1) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_1", target_branch: "target_1") } - let!(:mr_2) { table(:merge_requests).create!(source_project_id: other_project.id, target_project_id: project.id, source_branch: "branch_1", target_branch: "target_2") } - let!(:mr_3) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_2", target_branch: "master") } - let!(:mr_4) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_3", target_branch: "master") } - - context "#up" do - context "when source_project and source_branch of pipeline are the same of merge request" do - it "sets head_pipeline_id of given merge requests" do - migration.up - - expect(mr_1.reload.head_pipeline_id).to eq(pipeline_1.id) - expect(mr_2.reload.head_pipeline_id).to eq(pipeline_3.id) - expect(mr_3.reload.head_pipeline_id).to eq(pipeline_4.id) - expect(mr_4.reload.head_pipeline_id).to be_nil - end - end - end -end diff --git a/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb new file mode 100644 index 00000000000..9cae1daacea --- /dev/null +++ b/spec/migrations/backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20190628185004_backfill_and_add_not_null_constraint_to_released_at_column_on_releases_table.rb') + +describe BackfillAndAddNotNullConstraintToReleasedAtColumnOnReleasesTable, :migration do + let(:releases) { table(:releases) } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + + subject(:migration) { described_class.new } + + it 'fills released_at with the value of created_at' do + created_at_a = Time.zone.parse('2019-02-10T08:00:00Z') + created_at_b = Time.zone.parse('2019-03-10T18:00:00Z') + namespace = namespaces.create(name: 'foo', path: 'foo') + project = projects.create!(namespace_id: namespace.id) + release_a = releases.create!(project_id: project.id, created_at: created_at_a) + release_b = releases.create!(project_id: project.id, created_at: created_at_b) + + disable_migrations_output { migration.up } + + release_a.reload + release_b.reload + expect(release_a.released_at).to eq(created_at_a) + expect(release_b.released_at).to eq(created_at_b) + end +end diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb deleted file mode 100644 index 09c78d02890..00000000000 --- a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev_index_percentages.rb') - -describe CalculateConvDevIndexPercentages, :migration do - let(:migration) { described_class.new } - let!(:conv_dev_index) do - table(:conversational_development_index_metrics).create!( - leader_issues: 9.256, - leader_notes: 0, - leader_milestones: 16.2456, - leader_boards: 5.2123, - leader_merge_requests: 1.2, - leader_ci_pipelines: 12.1234, - leader_environments: 3.3333, - leader_deployments: 1.200, - leader_projects_prometheus_active: 0.111, - leader_service_desk_issues: 15.891, - instance_issues: 1.234, - instance_notes: 28.123, - instance_milestones: 0, - instance_boards: 3.254, - instance_merge_requests: 0.6, - instance_ci_pipelines: 2.344, - instance_environments: 2.2222, - instance_deployments: 0.771, - instance_projects_prometheus_active: 0.109, - instance_service_desk_issues: 13.345, - percentage_issues: 0, - percentage_notes: 0, - percentage_milestones: 0, - percentage_boards: 0, - percentage_merge_requests: 0, - percentage_ci_pipelines: 0, - percentage_environments: 0, - percentage_deployments: 0, - percentage_projects_prometheus_active: 0, - percentage_service_desk_issues: 0) - end - - describe '#up' do - it 'calculates percentages correctly' do - migration.up - conv_dev_index.reload - - expect(conv_dev_index.percentage_issues).to be_within(0.1).of(13.3) - expect(conv_dev_index.percentage_notes).to be_zero # leader 0 - expect(conv_dev_index.percentage_milestones).to be_zero # instance 0 - expect(conv_dev_index.percentage_boards).to be_within(0.1).of(62.4) - expect(conv_dev_index.percentage_merge_requests).to eq(50.0) - expect(conv_dev_index.percentage_ci_pipelines).to be_within(0.1).of(19.3) - expect(conv_dev_index.percentage_environments).to be_within(0.1).of(66.7) - expect(conv_dev_index.percentage_deployments).to be_within(0.1).of(64.2) - expect(conv_dev_index.percentage_projects_prometheus_active).to be_within(0.1).of(98.2) - expect(conv_dev_index.percentage_service_desk_issues).to be_within(0.1).of(84.0) - end - end -end diff --git a/spec/migrations/clean_appearance_symlinks_spec.rb b/spec/migrations/clean_appearance_symlinks_spec.rb deleted file mode 100644 index 9225dc0d894..00000000000 --- a/spec/migrations/clean_appearance_symlinks_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170613111224_clean_appearance_symlinks.rb') - -describe CleanAppearanceSymlinks do - let(:migration) { described_class.new } - let(:test_dir) { File.join(Rails.root, "tmp", "tests", "clean_appearance_test") } - let(:uploads_dir) { File.join(test_dir, "public", "uploads") } - let(:new_uploads_dir) { File.join(uploads_dir, "system") } - let(:original_path) { File.join(new_uploads_dir, 'appearance') } - let(:symlink_path) { File.join(uploads_dir, 'appearance') } - - before do - FileUtils.remove_dir(test_dir) if File.directory?(test_dir) - FileUtils.mkdir_p(uploads_dir) - allow(migration).to receive(:base_directory).and_return(test_dir) - allow(migration).to receive(:say) - end - - describe "#up" do - before do - FileUtils.mkdir_p(original_path) - FileUtils.ln_s(original_path, symlink_path) - end - - it 'removes the symlink' do - migration.up - - expect(File.symlink?(symlink_path)).to be(false) - end - end - - describe '#down' do - before do - FileUtils.mkdir_p(File.join(original_path)) - FileUtils.touch(File.join(original_path, 'dummy.file')) - end - - it 'creates a symlink' do - expected_path = File.join(symlink_path, "dummy.file") - migration.down - - expect(File.exist?(expected_path)).to be(true) - expect(File.symlink?(symlink_path)).to be(true) - end - end -end diff --git a/spec/migrations/clean_stage_id_reference_migration_spec.rb b/spec/migrations/clean_stage_id_reference_migration_spec.rb deleted file mode 100644 index 9a581df28a2..00000000000 --- a/spec/migrations/clean_stage_id_reference_migration_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20170710083355_clean_stage_id_reference_migration.rb') - -describe CleanStageIdReferenceMigration, :migration, :sidekiq, :redis do - let(:migration_class) { 'MigrateBuildStageIdReference' } - let(:migration) { spy('migration') } - - before do - allow(Gitlab::BackgroundMigration.const_get(migration_class)) - .to receive(:new).and_return(migration) - end - - context 'when there are pending background migrations' do - it 'processes pending jobs synchronously' do - Sidekiq::Testing.disable! do - BackgroundMigrationWorker.perform_in(2.minutes, migration_class, [1, 1]) - BackgroundMigrationWorker.perform_async(migration_class, [1, 1]) - - migrate! - - expect(migration).to have_received(:perform).with(1, 1).twice - end - end - end - context 'when there are no background migrations pending' do - it 'does nothing' do - Sidekiq::Testing.disable! do - migrate! - - expect(migration).not_to have_received(:perform) - end - end - end -end diff --git a/spec/migrations/clean_stages_statuses_migration_spec.rb b/spec/migrations/clean_stages_statuses_migration_spec.rb deleted file mode 100644 index 38705f8eaae..00000000000 --- a/spec/migrations/clean_stages_statuses_migration_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20170912113435_clean_stages_statuses_migration.rb') - -describe CleanStagesStatusesMigration, :migration, :sidekiq, :redis do - let(:migration) { spy('migration') } - - before do - allow(Gitlab::BackgroundMigration::MigrateStageStatus) - .to receive(:new).and_return(migration) - end - - context 'when there are pending background migrations' do - it 'processes pending jobs synchronously' do - Sidekiq::Testing.disable! do - BackgroundMigrationWorker - .perform_in(2.minutes, 'MigrateStageStatus', [1, 1]) - BackgroundMigrationWorker - .perform_async('MigrateStageStatus', [1, 1]) - - migrate! - - expect(migration).to have_received(:perform).with(1, 1).twice - end - end - end - - context 'when there are no background migrations pending' do - it 'does nothing' do - Sidekiq::Testing.disable! do - migrate! - - expect(migration).not_to have_received(:perform) - end - end - end - - context 'when there are still unmigrated stages afterwards' do - let(:stages) { table('ci_stages') } - - before do - stages.create!(status: nil, name: 'build') - stages.create!(status: nil, name: 'test') - end - - it 'migrates statuses sequentially in batches' do - migrate! - - expect(migration).to have_received(:perform).once - end - end -end diff --git a/spec/migrations/clean_upload_symlinks_spec.rb b/spec/migrations/clean_upload_symlinks_spec.rb deleted file mode 100644 index 26653b9c008..00000000000 --- a/spec/migrations/clean_upload_symlinks_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170406111121_clean_upload_symlinks.rb') - -describe CleanUploadSymlinks do - let(:migration) { described_class.new } - let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_uploads_test") } - let(:uploads_dir) { File.join(test_dir, "public", "uploads") } - let(:new_uploads_dir) { File.join(uploads_dir, "-", "system") } - let(:original_path) { File.join(new_uploads_dir, 'user') } - let(:symlink_path) { File.join(uploads_dir, 'user') } - - before do - FileUtils.remove_dir(test_dir) if File.directory?(test_dir) - FileUtils.mkdir_p(uploads_dir) - allow(migration).to receive(:base_directory).and_return(test_dir) - allow(migration).to receive(:say) - end - - describe "#up" do - before do - FileUtils.mkdir_p(original_path) - FileUtils.ln_s(original_path, symlink_path) - end - - it 'removes the symlink' do - migration.up - - expect(File.symlink?(symlink_path)).to be(false) - end - end - - describe '#down' do - before do - FileUtils.mkdir_p(File.join(original_path)) - FileUtils.touch(File.join(original_path, 'dummy.file')) - end - - it 'creates a symlink' do - expected_path = File.join(symlink_path, "dummy.file") - migration.down - - expect(File.exist?(expected_path)).to be(true) - expect(File.symlink?(symlink_path)).to be(true) - end - end -end diff --git a/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb b/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb deleted file mode 100644 index 3a9fa8c7113..00000000000 --- a/spec/migrations/cleanup_move_system_upload_folder_symlink_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' -require Rails.root.join("db", "post_migrate", "20170717111152_cleanup_move_system_upload_folder_symlink.rb") - -describe CleanupMoveSystemUploadFolderSymlink do - let(:migration) { described_class.new } - let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') } - let(:test_folder) { File.join(test_base, '-', 'system') } - - before do - allow(migration).to receive(:base_directory).and_return(test_base) - FileUtils.rm_rf(test_base) - FileUtils.mkdir_p(test_folder) - allow(migration).to receive(:say) - end - - describe '#up' do - before do - FileUtils.ln_s(test_folder, File.join(test_base, 'system')) - end - - it 'removes the symlink' do - migration.up - - expect(File.exist?(File.join(test_base, 'system'))).to be_falsey - end - end - - describe '#down' do - it 'creates the symlink' do - migration.down - - expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy - end - end -end diff --git a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb deleted file mode 100644 index 0e6bded29b4..00000000000 --- a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170816102555_cleanup_nonexisting_namespace_pending_delete_projects.rb') - -describe CleanupNonexistingNamespacePendingDeleteProjects, :migration do - let(:projects) { table(:projects) } - let(:namespaces) { table(:namespaces) } - - describe '#up' do - let!(:some_project) { projects.create! } - let(:namespace) { namespaces.create!(name: 'test', path: 'test') } - - it 'only cleans up when namespace does not exist' do - projects.create!(pending_delete: true, namespace_id: namespace.id) - project = projects.create!(pending_delete: true, namespace_id: 0) - - expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]]) - - described_class.new.up - end - - it 'does nothing when no pending delete projects without namespace found' do - projects.create!(pending_delete: true, namespace_id: namespace.id) - - expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async) - - described_class.new.up - end - end -end diff --git a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb deleted file mode 100644 index d1bf6bdf9d6..00000000000 --- a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170607121233_convert_custom_notification_settings_to_columns') - -describe ConvertCustomNotificationSettingsToColumns, :migration do - let(:user_class) { table(:users) } - - let(:settings_params) do - [ - { level: 0, events: [:new_note] }, # disabled, single event - { level: 3, events: [:new_issue, :reopen_issue, :close_issue, :reassign_issue] }, # global, multiple events - { level: 5, events: described_class::EMAIL_EVENTS }, # custom, all events - { level: 5, events: [] } # custom, no events - ] - end - - let(:notification_settings_before) do - settings_params.map do |params| - events = {} - - params[:events].each do |event| - events[event] = true - end - - user = user_class.create!(email: "user-#{SecureRandom.hex}@example.org", username: "user-#{SecureRandom.hex}", encrypted_password: '12345678') - create_params = { user_id: user.id, level: params[:level], events: events } - notification_setting = described_class::NotificationSetting.create(create_params) - - [notification_setting, params] - end - end - - let(:notification_settings_after) do - settings_params.map do |params| - events = {} - - params[:events].each do |event| - events[event] = true - end - - user = user_class.create!(email: "user-#{SecureRandom.hex}@example.org", username: "user-#{SecureRandom.hex}", encrypted_password: '12345678') - create_params = events.merge(user_id: user.id, level: params[:level]) - notification_setting = described_class::NotificationSetting.create(create_params) - - [notification_setting, params] - end - end - - describe '#up' do - it 'migrates all settings where a custom event is enabled, even if they are not currently using the custom level' do - notification_settings_before - - described_class.new.up - - notification_settings_before.each do |(notification_setting, params)| - notification_setting.reload - - expect(notification_setting.read_attribute_before_type_cast(:events)).to be_nil - expect(notification_setting.level).to eq(params[:level]) - - described_class::EMAIL_EVENTS.each do |event| - # We don't set the others to false, just let them default to nil - expected = params[:events].include?(event) || nil - - expect(notification_setting.read_attribute(event)).to eq(expected) - end - end - end - end - - describe '#down' do - it 'creates a custom events hash for all settings where at least one event is enabled' do - notification_settings_after - - described_class.new.down - - notification_settings_after.each do |(notification_setting, params)| - notification_setting.reload - - expect(notification_setting.level).to eq(params[:level]) - - if params[:events].empty? - # We don't migrate empty settings - expect(notification_setting.events).to eq({}) - else - described_class::EMAIL_EVENTS.each do |event| - expected = params[:events].include?(event) - - expect(notification_setting.events[event]).to eq(expected) - expect(notification_setting.read_attribute(event)).to be_nil - end - end - end - end - - it 'reverts the database to the state it was in before' do - notification_settings_before - - described_class.new.up - described_class.new.down - - notification_settings_before.each do |(notification_setting, params)| - notification_setting.reload - - expect(notification_setting.level).to eq(params[:level]) - - if params[:events].empty? - # We don't migrate empty settings - expect(notification_setting.events).to eq({}) - else - described_class::EMAIL_EVENTS.each do |event| - expected = params[:events].include?(event) - - expect(notification_setting.events[event]).to eq(expected) - expect(notification_setting.read_attribute(event)).to be_nil - end - end - end - end - end -end diff --git a/spec/migrations/delete_conflicting_redirect_routes_spec.rb b/spec/migrations/delete_conflicting_redirect_routes_spec.rb deleted file mode 100644 index 8a191bd7139..00000000000 --- a/spec/migrations/delete_conflicting_redirect_routes_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170907170235_delete_conflicting_redirect_routes') - -describe DeleteConflictingRedirectRoutes, :migration, :sidekiq do - let!(:redirect_routes) { table(:redirect_routes) } - let!(:routes) { table(:routes) } - - around do |example| - Timecop.freeze { example.run } - end - - before do - routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1') - routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2') - routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3') - routes.create!(id: 4, source_id: 4, source_type: 'Namespace', path: 'foo4') - routes.create!(id: 5, source_id: 5, source_type: 'Namespace', path: 'foo5') - - # Valid redirects - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'bar2') - redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'bar3') - - # Conflicting redirects - redirect_routes.create!(source_id: 2, source_type: 'Namespace', path: 'foo1') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo2') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo3') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo4') - redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5') - end - - # No-op. See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 - it 'NO-OP: does not schedule any background migrations' do - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(BackgroundMigrationWorker.jobs.size).to eq 0 - end - end - end -end diff --git a/spec/migrations/fix_wrongly_renamed_routes_spec.rb b/spec/migrations/fix_wrongly_renamed_routes_spec.rb deleted file mode 100644 index 543cf55f076..00000000000 --- a/spec/migrations/fix_wrongly_renamed_routes_spec.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170518231126_fix_wrongly_renamed_routes.rb') - -describe FixWronglyRenamedRoutes, :migration do - let(:subject) { described_class.new } - let(:namespaces_table) { table(:namespaces) } - let(:projects_table) { table(:projects) } - let(:routes_table) { table(:routes) } - let(:broken_namespace) do - namespaces_table.create!(name: 'apiis', path: 'apiis').tap do |namespace| - routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'api0is', path: 'api0is') - end - end - let(:broken_namespace_route) { routes_table.where(source_type: 'Namespace', source_id: broken_namespace.id).first } - - describe '#wrongly_renamed' do - it "includes routes that have names that don't match their namespace" do - broken_namespace - other_namespace = namespaces_table.create!(name: 'api0', path: 'api0') - routes_table.create!(source_type: 'Namespace', source_id: other_namespace.id, name: 'api0', path: 'api0') - - expect(subject.wrongly_renamed.map(&:id)) - .to contain_exactly(broken_namespace_route.id) - end - end - - describe "#paths_and_corrections" do - it 'finds the wrong path and gets the correction from the namespace' do - broken_namespace - namespaces_table.create!(name: 'uploads-test', path: 'uploads-test').tap do |namespace| - routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'uploads-test', path: 'uploads0-test') - end - - expected_result = [ - { 'namespace_path' => 'apiis', 'path' => 'api0is' }, - { 'namespace_path' => 'uploads-test', 'path' => 'uploads0-test' } - ] - - expect(subject.paths_and_corrections).to include(*expected_result) - end - end - - describe '#routes_in_namespace_query' do - it 'includes only the required routes' do - namespace = namespaces_table.create!(name: 'hello', path: 'hello') - namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'hello') - project = projects_table.new(name: 'my-project', path: 'my-project', namespace_id: namespace.id).tap do |project| - project.save!(validate: false) - end - routes_table.create!(source_type: 'Project', source_id: project.id, name: 'my-project', path: 'hello/my-project') - _other_namespace = namespaces_table.create!(name: 'hello0', path: 'hello0') - - result = routes_table.where(subject.routes_in_namespace_query('hello')) - project_route = routes_table.where(source_type: 'Project', source_id: project.id).first - - expect(result).to contain_exactly(namespace_route, project_route) - end - end - - describe '#up' do - it 'renames incorrectly named routes' do - broken_project = - projects_table.new(name: 'broken-project', path: 'broken-project', namespace_id: broken_namespace.id).tap do |project| - project.save!(validate: false) - routes_table.create!(source_type: 'Project', source_id: project.id, name: 'broken-project', path: 'api0is/broken-project') - end - - subject.up - - broken_project_route = routes_table.where(source_type: 'Project', source_id: broken_project.id).first - - expect(broken_project_route.path).to eq('apiis/broken-project') - expect(broken_namespace_route.reload.path).to eq('apiis') - end - - it "doesn't touch namespaces that look like something that should be renamed" do - namespaces_table.create!(name: 'apiis', path: 'apiis') - namespace = namespaces_table.create!(name: 'hello', path: 'api0') - namespace_route = routes_table.create!(source_type: 'Namespace', source_id: namespace.id, name: 'hello', path: 'api0') - - subject.up - - expect(namespace_route.reload.path).to eq('api0') - end - end -end diff --git a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb deleted file mode 100644 index 71a4e71ac8a..00000000000 --- a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20171106151218_issues_moved_to_id_foreign_key.rb') - -describe IssuesMovedToIdForeignKey, :migration do - let(:issues) { table(:issues) } - - let!(:issue_third) { issues.create! } - let!(:issue_second) { issues.create!(moved_to_id: issue_third.id) } - let!(:issue_first) { issues.create!(moved_to_id: issue_second.id) } - - subject { described_class.new } - - it 'removes the orphaned moved_to_id' do - subject.down - - issue_third.update!(moved_to_id: 0) - - subject.up - - expect(issue_first.reload.moved_to_id).to eq(issue_second.id) - expect(issue_second.reload.moved_to_id).to eq(issue_third.id) - expect(issue_third.reload.moved_to_id).to be_nil - end -end diff --git a/spec/migrations/migrate_build_stage_reference_again_spec.rb b/spec/migrations/migrate_build_stage_reference_again_spec.rb deleted file mode 100644 index 6be480ce58e..00000000000 --- a/spec/migrations/migrate_build_stage_reference_again_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170526190000_migrate_build_stage_reference_again.rb') - -describe MigrateBuildStageReferenceAgain, :migration do - ## - # Create test data - pipeline and CI/CD jobs. - # - - let(:jobs) { table(:ci_builds) } - let(:stages) { table(:ci_stages) } - let(:pipelines) { table(:ci_pipelines) } - let(:projects) { table(:projects) } - - before do - # Create projects - # - projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1') - projects.create!(id: 456, name: 'gitlab2', path: 'gitlab2') - - # Create CI/CD pipelines - # - pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a') - pipelines.create!(id: 2, project_id: 456, ref: 'feature', sha: '21a3deb') - - # Create CI/CD jobs - # - jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build') - jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build') - jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test') - jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy') - jobs.create!(id: 5, commit_id: 2, project_id: 456, stage_idx: 2, stage: 'test:2') - jobs.create!(id: 6, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1') - jobs.create!(id: 7, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1') - jobs.create!(id: 8, commit_id: 3, project_id: 789, stage_idx: 3, stage: 'deploy') - - # Create CI/CD stages - # - stages.create(id: 101, pipeline_id: 1, project_id: 123, name: 'test') - stages.create(id: 102, pipeline_id: 1, project_id: 123, name: 'build') - stages.create(id: 103, pipeline_id: 1, project_id: 123, name: 'deploy') - stages.create(id: 104, pipeline_id: 2, project_id: 456, name: 'test:1') - stages.create(id: 105, pipeline_id: 2, project_id: 456, name: 'test:2') - stages.create(id: 106, pipeline_id: 2, project_id: 456, name: 'deploy') - end - - it 'correctly migrate build stage references' do - expect(jobs.where(stage_id: nil).count).to eq 8 - - migrate! - - expect(jobs.where(stage_id: nil).count).to eq 1 - - expect(jobs.find(1).stage_id).to eq 102 - expect(jobs.find(2).stage_id).to eq 102 - expect(jobs.find(3).stage_id).to eq 101 - expect(jobs.find(4).stage_id).to eq 103 - expect(jobs.find(5).stage_id).to eq 105 - expect(jobs.find(6).stage_id).to eq 104 - expect(jobs.find(7).stage_id).to eq 104 - expect(jobs.find(8).stage_id).to eq nil - end -end diff --git a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb b/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb deleted file mode 100644 index ba4c66057d4..00000000000 --- a/spec/migrations/migrate_gcp_clusters_to_new_clusters_architectures_spec.rb +++ /dev/null @@ -1,181 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20171013104327_migrate_gcp_clusters_to_new_clusters_architectures.rb') - -describe MigrateGcpClustersToNewClustersArchitectures, :migration do - let(:projects) { table(:projects) } - let(:project) { projects.create } - let(:users) { table(:users) } - let(:user) { users.create! } - let(:service) { GcpMigrationSpec::KubernetesService.create!(project_id: project.id) } - - module GcpMigrationSpec - class KubernetesService < ActiveRecord::Base - self.table_name = 'services' - - serialize :properties, JSON - - default_value_for :active, true - default_value_for :type, 'KubernetesService' - default_value_for :properties, { - api_url: 'https://kubernetes.example.com', - token: 'a' * 40 - } - end - end - - context 'when cluster is being created' do - let(:project_id) { project.id } - let(:user_id) { user.id } - let(:service_id) { service.id } - let(:status) { 2 } # creating - let(:gcp_cluster_size) { 1 } - let(:created_at) { "'2017-10-17 20:24:02'" } - let(:updated_at) { "'2017-10-17 20:28:44'" } - let(:enabled) { true } - let(:status_reason) { "''" } - let(:project_namespace) { "'sample-app'" } - let(:endpoint) { 'NULL' } - let(:ca_cert) { 'NULL' } - let(:encrypted_kubernetes_token) { 'NULL' } - let(:encrypted_kubernetes_token_iv) { 'NULL' } - let(:username) { 'NULL' } - let(:encrypted_password) { 'NULL' } - let(:encrypted_password_iv) { 'NULL' } - let(:gcp_project_id) { "'gcp_project_id'" } - let(:gcp_cluster_zone) { "'gcp_cluster_zone'" } - let(:gcp_cluster_name) { "'gcp_cluster_name'" } - let(:gcp_machine_type) { "'gcp_machine_type'" } - let(:gcp_operation_id) { 'NULL' } - let(:encrypted_gcp_token) { "'encrypted_gcp_token'" } - let(:encrypted_gcp_token_iv) { "'encrypted_gcp_token_iv'" } - - let(:cluster) { described_class::Cluster.last } - let(:cluster_id) { cluster.id } - - before do - ActiveRecord::Base.connection.execute <<-SQL - INSERT INTO gcp_clusters (project_id, user_id, service_id, status, gcp_cluster_size, created_at, updated_at, enabled, status_reason, project_namespace, endpoint, ca_cert, encrypted_kubernetes_token, encrypted_kubernetes_token_iv, username, encrypted_password, encrypted_password_iv, gcp_project_id, gcp_cluster_zone, gcp_cluster_name, gcp_machine_type, gcp_operation_id, encrypted_gcp_token, encrypted_gcp_token_iv) - VALUES (#{project_id}, #{user_id}, #{service_id}, #{status}, #{gcp_cluster_size}, #{created_at}, #{updated_at}, #{enabled}, #{status_reason}, #{project_namespace}, #{endpoint}, #{ca_cert}, #{encrypted_kubernetes_token}, #{encrypted_kubernetes_token_iv}, #{username}, #{encrypted_password}, #{encrypted_password_iv}, #{gcp_project_id}, #{gcp_cluster_zone}, #{gcp_cluster_name}, #{gcp_machine_type}, #{gcp_operation_id}, #{encrypted_gcp_token}, #{encrypted_gcp_token_iv}); - SQL - end - - it 'correctly migrate to new clusters architectures' do - migrate! - - expect(described_class::Cluster.count).to eq(1) - expect(described_class::ClustersProject.count).to eq(1) - expect(described_class::ProvidersGcp.count).to eq(1) - expect(described_class::PlatformsKubernetes.count).to eq(1) - - expect(cluster.user_id).to eq(user.id) - expect(cluster.enabled).to be_truthy - expect(cluster.name).to eq(gcp_cluster_name.delete!("'")) - expect(cluster.provider_type).to eq('gcp') - expect(cluster.platform_type).to eq('kubernetes') - - expect(cluster.project_ids).to include(project.id) - - expect(cluster.provider_gcp.cluster_id).to eq(cluster.id) - expect(cluster.provider_gcp.status).to eq(status) - expect(cluster.provider_gcp.status_reason).to eq(tr(status_reason)) - expect(cluster.provider_gcp.gcp_project_id).to eq(tr(gcp_project_id)) - expect(cluster.provider_gcp.zone).to eq(tr(gcp_cluster_zone)) - expect(cluster.provider_gcp.num_nodes).to eq(gcp_cluster_size) - expect(cluster.provider_gcp.machine_type).to eq(tr(gcp_machine_type)) - expect(cluster.provider_gcp.operation_id).to be_nil - expect(cluster.provider_gcp.endpoint).to be_nil - expect(cluster.provider_gcp.encrypted_access_token).to eq(tr(encrypted_gcp_token)) - expect(cluster.provider_gcp.encrypted_access_token_iv).to eq(tr(encrypted_gcp_token_iv)) - - expect(cluster.platform_kubernetes.cluster_id).to eq(cluster.id) - expect(cluster.platform_kubernetes.api_url).to be_nil - expect(cluster.platform_kubernetes.ca_cert).to be_nil - expect(cluster.platform_kubernetes.namespace).to eq(tr(project_namespace)) - expect(cluster.platform_kubernetes.username).to be_nil - expect(cluster.platform_kubernetes.encrypted_password).to be_nil - expect(cluster.platform_kubernetes.encrypted_password_iv).to be_nil - expect(cluster.platform_kubernetes.encrypted_token).to be_nil - expect(cluster.platform_kubernetes.encrypted_token_iv).to be_nil - end - end - - context 'when cluster has been created' do - let(:project_id) { project.id } - let(:user_id) { user.id } - let(:service_id) { service.id } - let(:status) { 3 } # created - let(:gcp_cluster_size) { 1 } - let(:created_at) { "'2017-10-17 20:24:02'" } - let(:updated_at) { "'2017-10-17 20:28:44'" } - let(:enabled) { true } - let(:status_reason) { "'general error'" } - let(:project_namespace) { "'sample-app'" } - let(:endpoint) { "'111.111.111.111'" } - let(:ca_cert) { "'ca_cert'" } - let(:encrypted_kubernetes_token) { "'encrypted_kubernetes_token'" } - let(:encrypted_kubernetes_token_iv) { "'encrypted_kubernetes_token_iv'" } - let(:username) { "'username'" } - let(:encrypted_password) { "'encrypted_password'" } - let(:encrypted_password_iv) { "'encrypted_password_iv'" } - let(:gcp_project_id) { "'gcp_project_id'" } - let(:gcp_cluster_zone) { "'gcp_cluster_zone'" } - let(:gcp_cluster_name) { "'gcp_cluster_name'" } - let(:gcp_machine_type) { "'gcp_machine_type'" } - let(:gcp_operation_id) { "'gcp_operation_id'" } - let(:encrypted_gcp_token) { "'encrypted_gcp_token'" } - let(:encrypted_gcp_token_iv) { "'encrypted_gcp_token_iv'" } - - let(:cluster) { described_class::Cluster.last } - let(:cluster_id) { cluster.id } - - before do - ActiveRecord::Base.connection.execute <<-SQL - INSERT INTO gcp_clusters (project_id, user_id, service_id, status, gcp_cluster_size, created_at, updated_at, enabled, status_reason, project_namespace, endpoint, ca_cert, encrypted_kubernetes_token, encrypted_kubernetes_token_iv, username, encrypted_password, encrypted_password_iv, gcp_project_id, gcp_cluster_zone, gcp_cluster_name, gcp_machine_type, gcp_operation_id, encrypted_gcp_token, encrypted_gcp_token_iv) - VALUES (#{project_id}, #{user_id}, #{service_id}, #{status}, #{gcp_cluster_size}, #{created_at}, #{updated_at}, #{enabled}, #{status_reason}, #{project_namespace}, #{endpoint}, #{ca_cert}, #{encrypted_kubernetes_token}, #{encrypted_kubernetes_token_iv}, #{username}, #{encrypted_password}, #{encrypted_password_iv}, #{gcp_project_id}, #{gcp_cluster_zone}, #{gcp_cluster_name}, #{gcp_machine_type}, #{gcp_operation_id}, #{encrypted_gcp_token}, #{encrypted_gcp_token_iv}); - SQL - end - - it 'correctly migrate to new clusters architectures' do - migrate! - - expect(described_class::Cluster.count).to eq(1) - expect(described_class::ClustersProject.count).to eq(1) - expect(described_class::ProvidersGcp.count).to eq(1) - expect(described_class::PlatformsKubernetes.count).to eq(1) - - expect(cluster.user_id).to eq(user.id) - expect(cluster.enabled).to be_truthy - expect(cluster.name).to eq(tr(gcp_cluster_name)) - expect(cluster.provider_type).to eq('gcp') - expect(cluster.platform_type).to eq('kubernetes') - - expect(cluster.project_ids).to include(project.id) - - expect(cluster.provider_gcp.cluster_id).to eq(cluster.id) - expect(cluster.provider_gcp.status).to eq(status) - expect(cluster.provider_gcp.status_reason).to eq(tr(status_reason)) - expect(cluster.provider_gcp.gcp_project_id).to eq(tr(gcp_project_id)) - expect(cluster.provider_gcp.zone).to eq(tr(gcp_cluster_zone)) - expect(cluster.provider_gcp.num_nodes).to eq(gcp_cluster_size) - expect(cluster.provider_gcp.machine_type).to eq(tr(gcp_machine_type)) - expect(cluster.provider_gcp.operation_id).to eq(tr(gcp_operation_id)) - expect(cluster.provider_gcp.endpoint).to eq(tr(endpoint)) - expect(cluster.provider_gcp.encrypted_access_token).to eq(tr(encrypted_gcp_token)) - expect(cluster.provider_gcp.encrypted_access_token_iv).to eq(tr(encrypted_gcp_token_iv)) - - expect(cluster.platform_kubernetes.cluster_id).to eq(cluster.id) - expect(cluster.platform_kubernetes.api_url).to eq('https://' + tr(endpoint)) - expect(cluster.platform_kubernetes.ca_cert).to eq(tr(ca_cert)) - expect(cluster.platform_kubernetes.namespace).to eq(tr(project_namespace)) - expect(cluster.platform_kubernetes.username).to eq(tr(username)) - expect(cluster.platform_kubernetes.encrypted_password).to eq(tr(encrypted_password)) - expect(cluster.platform_kubernetes.encrypted_password_iv).to eq(tr(encrypted_password_iv)) - expect(cluster.platform_kubernetes.encrypted_token).to eq(tr(encrypted_kubernetes_token)) - expect(cluster.platform_kubernetes.encrypted_token_iv).to eq(tr(encrypted_kubernetes_token_iv)) - end - end - - def tr(str) - str.delete("'") - end -end diff --git a/spec/migrations/migrate_issues_to_ghost_user_spec.rb b/spec/migrations/migrate_issues_to_ghost_user_spec.rb deleted file mode 100644 index 0016f058a17..00000000000 --- a/spec/migrations/migrate_issues_to_ghost_user_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20170825104051_migrate_issues_to_ghost_user.rb') - -describe MigrateIssuesToGhostUser, :migration do - describe '#up' do - let(:projects) { table(:projects) } - let(:issues) { table(:issues) } - let(:users) { table(:users) } - - before do - project = projects.create!(name: 'gitlab', namespace_id: 1) - user = users.create(email: 'test@example.com') - issues.create(title: 'Issue 1', author_id: nil, project_id: project.id) - issues.create(title: 'Issue 2', author_id: user.id, project_id: project.id) - end - - context 'when ghost user exists' do - let!(:ghost) { users.create(ghost: true, email: 'ghost@example.com') } - - it 'does not create a new user' do - expect { migrate! }.not_to change { User.count } - end - - it 'migrates issues where author = nil to the ghost user' do - migrate! - - expect(issues.first.reload.author_id).to eq(ghost.id) - end - - it 'does not change issues authored by an existing user' do - expect { migrate! }.not_to change { issues.second.reload.author_id} - end - end - - context 'when ghost user does not exist' do - it 'creates a new user' do - expect { migrate! }.to change { User.count }.by(1) - end - - it 'migrates issues where author = nil to the ghost user' do - migrate! - - expect(issues.first.reload.author_id).to eq(User.ghost.id) - end - - it 'does not change issues authored by an existing user' do - expect { migrate! }.not_to change { issues.second.reload.author_id} - end - end - end -end diff --git a/spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb b/spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb deleted file mode 100644 index df0015b6dd3..00000000000 --- a/spec/migrations/migrate_kubernetes_service_to_new_clusters_architectures_spec.rb +++ /dev/null @@ -1,312 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb') - -describe MigrateKubernetesServiceToNewClustersArchitectures, :migration do - context 'when unique KubernetesService exists' do - shared_examples 'KubernetesService migration' do - let(:sample_num) { 2 } - - let(:projects) do - (1..sample_num).each_with_object([]) do |n, array| - array << MigrateKubernetesServiceToNewClustersArchitectures::Project.create! - end - end - - let!(:kubernetes_services) do - projects.map do |project| - MigrateKubernetesServiceToNewClustersArchitectures::Service.create!( - project: project, - active: active, - category: 'deployment', - type: 'KubernetesService', - properties: "{\"namespace\":\"prod\",\"api_url\":\"https://kubernetes#{project.id}.com\",\"ca_pem\":\"ca_pem#{project.id}\",\"token\":\"token#{project.id}\"}") - end - end - - it 'migrates the KubernetesService to Platform::Kubernetes' do - expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(sample_num) - - projects.each do |project| - project.clusters.last.tap do |cluster| - expect(cluster.enabled).to eq(active) - expect(cluster.platform_kubernetes.api_url).to eq(project.kubernetes_service.api_url) - expect(cluster.platform_kubernetes.ca_cert).to eq(project.kubernetes_service.ca_pem) - expect(cluster.platform_kubernetes.token).to eq(project.kubernetes_service.token) - expect(project.kubernetes_service).not_to be_active - end - end - end - end - - context 'when KubernetesService is active' do - let(:active) { true } - - it_behaves_like 'KubernetesService migration' - end - end - - context 'when unique KubernetesService spawned from Service Template' do - let(:sample_num) { 2 } - - let(:projects) do - (1..sample_num).each_with_object([]) do |n, array| - array << MigrateKubernetesServiceToNewClustersArchitectures::Project.create! - end - end - - let!(:kubernetes_service_template) do - MigrateKubernetesServiceToNewClustersArchitectures::Service.create!( - template: true, - category: 'deployment', - type: 'KubernetesService', - properties: "{\"namespace\":\"prod\",\"api_url\":\"https://sample.kubernetes.com\",\"ca_pem\":\"ca_pem-sample\",\"token\":\"token-sample\"}") - end - - let!(:kubernetes_services) do - projects.map do |project| - MigrateKubernetesServiceToNewClustersArchitectures::Service.create!( - project: project, - category: 'deployment', - type: 'KubernetesService', - properties: "{\"namespace\":\"prod\",\"api_url\":\"#{kubernetes_service_template.api_url}\",\"ca_pem\":\"#{kubernetes_service_template.ca_pem}\",\"token\":\"#{kubernetes_service_template.token}\"}") - end - end - - it 'migrates the KubernetesService to Platform::Kubernetes without template' do - expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(sample_num) - - projects.each do |project| - project.clusters.last.tap do |cluster| - expect(cluster.platform_kubernetes.api_url).to eq(project.kubernetes_service.api_url) - expect(cluster.platform_kubernetes.ca_cert).to eq(project.kubernetes_service.ca_pem) - expect(cluster.platform_kubernetes.token).to eq(project.kubernetes_service.token) - expect(project.kubernetes_service).not_to be_active - end - end - end - end - - context 'when managed KubernetesService exists' do - let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! } - - let(:cluster) do - MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!( - projects: [project], - name: 'sample-cluster', - platform_type: :kubernetes, - provider_type: :user, - platform_kubernetes_attributes: { - api_url: 'https://sample.kubernetes.com', - ca_cert: 'ca_pem-sample', - token: 'token-sample' - } ) - end - - let!(:kubernetes_service) do - MigrateKubernetesServiceToNewClustersArchitectures::Service.create!( - project: project, - active: cluster.enabled, - category: 'deployment', - type: 'KubernetesService', - properties: "{\"api_url\":\"#{cluster.platform_kubernetes.api_url}\"}") - end - - it 'does not migrate the KubernetesService and disables the kubernetes_service' do # Because the corresponding Platform::Kubernetes already exists - expect { migrate! }.not_to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count } - - kubernetes_service.reload - expect(kubernetes_service).not_to be_active - end - end - - context 'when production cluster has already been existed' do # i.e. There are no environment_scope conflicts - let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! } - - let(:cluster) do - MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!( - projects: [project], - name: 'sample-cluster', - platform_type: :kubernetes, - provider_type: :user, - environment_scope: 'production/*', - platform_kubernetes_attributes: { - api_url: 'https://sample.kubernetes.com', - ca_cert: 'ca_pem-sample', - token: 'token-sample' - } ) - end - - let!(:kubernetes_service) do - MigrateKubernetesServiceToNewClustersArchitectures::Service.create!( - project: project, - active: true, - category: 'deployment', - type: 'KubernetesService', - properties: "{\"api_url\":\"https://debug.kube.com\"}") - end - - it 'migrates the KubernetesService to Platform::Kubernetes' do - expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1) - - kubernetes_service.reload - project.clusters.last.tap do |cluster| - expect(cluster.environment_scope).to eq('*') - expect(cluster.platform_kubernetes.api_url).to eq(kubernetes_service.api_url) - expect(cluster.platform_kubernetes.ca_cert).to eq(kubernetes_service.ca_pem) - expect(cluster.platform_kubernetes.token).to eq(kubernetes_service.token) - expect(kubernetes_service).not_to be_active - end - end - end - - context 'when default cluster has already been existed' do - let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! } - - let!(:cluster) do - MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!( - projects: [project], - name: 'sample-cluster', - platform_type: :kubernetes, - provider_type: :user, - environment_scope: '*', - platform_kubernetes_attributes: { - api_url: 'https://sample.kubernetes.com', - ca_cert: 'ca_pem-sample', - token: 'token-sample' - } ) - end - - let!(:kubernetes_service) do - MigrateKubernetesServiceToNewClustersArchitectures::Service.create!( - project: project, - active: true, - category: 'deployment', - type: 'KubernetesService', - properties: "{\"api_url\":\"https://debug.kube.com\"}") - end - - it 'migrates the KubernetesService to Platform::Kubernetes with dedicated environment_scope' do # Because environment_scope is duplicated - expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1) - - kubernetes_service.reload - project.clusters.last.tap do |cluster| - expect(cluster.environment_scope).to eq('migrated/*') - expect(cluster.platform_kubernetes.api_url).to eq(kubernetes_service.api_url) - expect(cluster.platform_kubernetes.ca_cert).to eq(kubernetes_service.ca_pem) - expect(cluster.platform_kubernetes.token).to eq(kubernetes_service.token) - expect(kubernetes_service).not_to be_active - end - end - end - - context 'when default cluster and migrated cluster has already been existed' do - let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! } - - let!(:cluster) do - MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!( - projects: [project], - name: 'sample-cluster', - platform_type: :kubernetes, - provider_type: :user, - environment_scope: '*', - platform_kubernetes_attributes: { - api_url: 'https://sample.kubernetes.com', - ca_cert: 'ca_pem-sample', - token: 'token-sample' - } ) - end - - let!(:migrated_cluster) do - MigrateKubernetesServiceToNewClustersArchitectures::Cluster.create!( - projects: [project], - name: 'sample-cluster', - platform_type: :kubernetes, - provider_type: :user, - environment_scope: 'migrated/*', - platform_kubernetes_attributes: { - api_url: 'https://sample.kubernetes.com', - ca_cert: 'ca_pem-sample', - token: 'token-sample' - } ) - end - - let!(:kubernetes_service) do - MigrateKubernetesServiceToNewClustersArchitectures::Service.create!( - project: project, - active: true, - category: 'deployment', - type: 'KubernetesService', - properties: "{\"api_url\":\"https://debug.kube.com\"}") - end - - it 'migrates the KubernetesService to Platform::Kubernetes with dedicated environment_scope' do # Because environment_scope is duplicated - expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1) - - kubernetes_service.reload - project.clusters.last.tap do |cluster| - expect(cluster.environment_scope).to eq('migrated0/*') - expect(cluster.platform_kubernetes.api_url).to eq(kubernetes_service.api_url) - expect(cluster.platform_kubernetes.ca_cert).to eq(kubernetes_service.ca_pem) - expect(cluster.platform_kubernetes.token).to eq(kubernetes_service.token) - expect(kubernetes_service).not_to be_active - end - end - end - - context 'when KubernetesService has nullified parameters' do - let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! } - - before do - MigrateKubernetesServiceToNewClustersArchitectures::Service.create!( - project: project, - active: false, - category: 'deployment', - type: 'KubernetesService', - properties: "{}") - end - - it 'does not migrate the KubernetesService and disables the kubernetes_service' do - expect { migrate! }.not_to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count } - - expect(project.kubernetes_service).not_to be_active - end - end - - # Platforms::Kubernetes validates `token` reagdless of the activeness, - # whereas KubernetesService validates `token` if only it's activated - # However, in this migration file, there are no validations because of the re-defined model class - # therefore, we should safely add this raw to Platform::Kubernetes - context 'when KubernetesService has empty token' do - let(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! } - - before do - MigrateKubernetesServiceToNewClustersArchitectures::Service.create!( - project: project, - active: false, - category: 'deployment', - type: 'KubernetesService', - properties: "{\"namespace\":\"prod\",\"api_url\":\"http://111.111.111.111\",\"ca_pem\":\"a\",\"token\":\"\"}") - end - - it 'does not migrate the KubernetesService and disables the kubernetes_service' do - expect { migrate! }.to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count }.by(1) - - project.clusters.last.tap do |cluster| - expect(cluster.environment_scope).to eq('*') - expect(cluster.platform_kubernetes.namespace).to eq('prod') - expect(cluster.platform_kubernetes.api_url).to eq('http://111.111.111.111') - expect(cluster.platform_kubernetes.ca_cert).to eq('a') - expect(cluster.platform_kubernetes.token).to be_empty - expect(project.kubernetes_service).not_to be_active - end - end - end - - context 'when KubernetesService does not exist' do - let!(:project) { MigrateKubernetesServiceToNewClustersArchitectures::Project.create! } - - it 'does not migrate the KubernetesService' do - expect { migrate! }.not_to change { MigrateKubernetesServiceToNewClustersArchitectures::Cluster.count } - end - end -end diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb deleted file mode 100644 index bc826d91471..00000000000 --- a/spec/migrations/migrate_old_artifacts_spec.rb +++ /dev/null @@ -1,140 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170523083112_migrate_old_artifacts.rb') - -# Adding the ci_job_artifacts table (from the 20170918072948 schema) -# makes the use of model code below easier. -describe MigrateOldArtifacts, :migration, schema: 20170918072948 do - let(:migration) { described_class.new } - let!(:directory) { Dir.mktmpdir } - - before do - allow(Gitlab.config.artifacts).to receive(:path).and_return(directory) - end - - after do - FileUtils.remove_entry_secure(directory) - end - - context 'with migratable data' do - let(:projects) { table(:projects) } - let(:ci_pipelines) { table(:ci_pipelines) } - let(:ci_builds) { table(:ci_builds) } - - let!(:project1) { projects.create!(ci_id: 2) } - let!(:project2) { projects.create!(ci_id: 3) } - let!(:project3) { projects.create! } - - let!(:pipeline1) { ci_pipelines.create!(project_id: project1.id) } - let!(:pipeline2) { ci_pipelines.create!(project_id: project2.id) } - let!(:pipeline3) { ci_pipelines.create!(project_id: project3.id) } - - let!(:build_with_legacy_artifacts) { ci_builds.create!(commit_id: pipeline1.id, project_id: project1.id, type: 'Ci::Build').becomes(Ci::Build) } - let!(:build_without_artifacts) { ci_builds.create!(commit_id: pipeline1.id, project_id: project1.id, type: 'Ci::Build').becomes(Ci::Build) } - let!(:build2) { ci_builds.create!(commit_id: pipeline2.id, project_id: project2.id, type: 'Ci::Build').becomes(Ci::Build) } - let!(:build3) { ci_builds.create!(commit_id: pipeline3.id, project_id: project3.id, type: 'Ci::Build').becomes(Ci::Build) } - - before do - setup_builds(build2, build3) - - store_artifacts_in_legacy_path(build_with_legacy_artifacts) - end - - it "legacy artifacts are not accessible" do - expect(build_with_legacy_artifacts.artifacts?).to be_falsey - end - - describe '#min_id' do - subject { migration.send(:min_id) } - - it 'returns the newest build for which ci_id is not defined' do - is_expected.to eq(build3.id) - end - end - - describe '#builds_with_artifacts' do - subject { migration.send(:builds_with_artifacts).map(&:id) } - - it 'returns a list of builds that has artifacts and could be migrated' do - is_expected.to contain_exactly(build_with_legacy_artifacts.id, build2.id) - end - end - - describe '#up' do - context 'when migrating artifacts' do - before do - migration.up - end - - it 'all files do have artifacts' do - Ci::Build.with_artifacts_archive do |build| - expect(build).to have_artifacts - end - end - - it 'artifacts are no longer present on legacy path' do - expect(File.exist?(legacy_path(build_with_legacy_artifacts))).to eq(false) - end - end - - context 'when there are artifacts in old and new directory' do - before do - store_artifacts_in_legacy_path(build2) - - migration.up - end - - it 'does not move old files' do - expect(File.exist?(legacy_path(build2))).to eq(true) - end - end - end - - private - - def store_artifacts_in_legacy_path(build) - FileUtils.mkdir_p(legacy_path(build)) - - FileUtils.copy( - Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), - File.join(legacy_path(build), "ci_build_artifacts.zip")) - - FileUtils.copy( - Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), - File.join(legacy_path(build), "ci_build_artifacts_metadata.gz")) - - build.update_columns( - artifacts_file: 'ci_build_artifacts.zip', - artifacts_metadata: 'ci_build_artifacts_metadata.gz') - - build.reload - end - - def legacy_path(build) - File.join(directory, - build.created_at.utc.strftime('%Y_%m'), - build.project.ci_id.to_s, - build.id.to_s) - end - - def new_legacy_path(build) - File.join(directory, - build.created_at.utc.strftime('%Y_%m'), - build.project_id.to_s, - build.id.to_s) - end - - def setup_builds(*builds) - builds.each do |build| - FileUtils.mkdir_p(new_legacy_path(build)) - - build.update_columns( - artifacts_file: 'ci_build_artifacts.zip', - artifacts_metadata: 'ci_build_artifacts_metadata.gz') - - build.reload - end - end - end -end diff --git a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb deleted file mode 100644 index e38044ccceb..00000000000 --- a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170822101017_migrate_pipeline_sidekiq_queues.rb') - -describe MigratePipelineSidekiqQueues, :sidekiq, :redis do - include Gitlab::Database::MigrationHelpers - include StubWorker - - context 'when there are jobs in the queues' do - it 'correctly migrates queue when migrating up' do - Sidekiq::Testing.disable! do - stub_worker(queue: :pipeline).perform_async('Something', [1]) - stub_worker(queue: :build).perform_async('Something', [1]) - - described_class.new.up - - expect(sidekiq_queue_length('pipeline')).to eq 0 - expect(sidekiq_queue_length('build')).to eq 0 - expect(sidekiq_queue_length('pipeline_default')).to eq 2 - end - end - - it 'correctly migrates queue when migrating down' do - Sidekiq::Testing.disable! do - stub_worker(queue: :pipeline_default).perform_async('Class', [1]) - stub_worker(queue: :pipeline_processing).perform_async('Class', [2]) - stub_worker(queue: :pipeline_hooks).perform_async('Class', [3]) - stub_worker(queue: :pipeline_cache).perform_async('Class', [4]) - - described_class.new.down - - expect(sidekiq_queue_length('pipeline')).to eq 4 - expect(sidekiq_queue_length('pipeline_default')).to eq 0 - expect(sidekiq_queue_length('pipeline_processing')).to eq 0 - expect(sidekiq_queue_length('pipeline_hooks')).to eq 0 - expect(sidekiq_queue_length('pipeline_cache')).to eq 0 - end - end - end - - context 'when there are no jobs in the queues' do - it 'does not raise error when migrating up' do - expect { described_class.new.up }.not_to raise_error - end - - it 'does not raise error when migrating down' do - expect { described_class.new.down }.not_to raise_error - end - end -end diff --git a/spec/migrations/migrate_pipeline_stages_spec.rb b/spec/migrations/migrate_pipeline_stages_spec.rb deleted file mode 100644 index c47f2bb8ff9..00000000000 --- a/spec/migrations/migrate_pipeline_stages_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170526185842_migrate_pipeline_stages.rb') - -describe MigratePipelineStages, :migration do - ## - # Create test data - pipeline and CI/CD jobs. - # - - let(:jobs) { table(:ci_builds) } - let(:stages) { table(:ci_stages) } - let(:pipelines) { table(:ci_pipelines) } - let(:projects) { table(:projects) } - - before do - # Create projects - # - projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1') - projects.create!(id: 456, name: 'gitlab2', path: 'gitlab2') - - # Create CI/CD pipelines - # - pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a') - pipelines.create!(id: 2, project_id: 456, ref: 'feature', sha: '21a3deb') - - # Create CI/CD jobs - # - jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build') - jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build') - jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test') - jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test') - jobs.create!(id: 5, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy') - jobs.create!(id: 6, commit_id: 2, project_id: 456, stage_idx: 3, stage: 'deploy') - jobs.create!(id: 7, commit_id: 2, project_id: 456, stage_idx: 2, stage: 'test:2') - jobs.create!(id: 8, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1') - jobs.create!(id: 9, commit_id: 2, project_id: 456, stage_idx: 1, stage: 'test:1') - jobs.create!(id: 10, commit_id: 2, project_id: 456, stage_idx: 2, stage: 'test:2') - jobs.create!(id: 11, commit_id: 3, project_id: 456, stage_idx: 3, stage: 'deploy') - jobs.create!(id: 12, commit_id: 2, project_id: 789, stage_idx: 3, stage: 'deploy') - end - - it 'correctly migrates pipeline stages' do - expect(stages.count).to be_zero - - migrate! - - expect(stages.count).to eq 6 - expect(stages.all.pluck(:name)) - .to match_array %w[test build deploy test:1 test:2 deploy] - expect(stages.where(pipeline_id: 1).order(:id).pluck(:name)) - .to eq %w[test build deploy] - expect(stages.where(pipeline_id: 2).order(:id).pluck(:name)) - .to eq %w[test:1 test:2 deploy] - expect(stages.where(pipeline_id: 3).count).to be_zero - expect(stages.where(project_id: 789).count).to be_zero - end -end diff --git a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb b/spec/migrations/migrate_process_commit_worker_jobs_spec.rb deleted file mode 100644 index 6219a67c900..00000000000 --- a/spec/migrations/migrate_process_commit_worker_jobs_spec.rb +++ /dev/null @@ -1,197 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20161124141322_migrate_process_commit_worker_jobs.rb') - -describe MigrateProcessCommitWorkerJobs do - set(:project) { create(:project, :legacy_storage, :repository) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - set(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let(:commit) do - Gitlab::Git::Commit.last(project.repository.raw) - end - - describe 'Project' do - describe 'find_including_path' do - it 'returns Project instances' do - expect(described_class::Project.find_including_path(project.id)) - .to be_an_instance_of(described_class::Project) - end - - it 'selects the full path for every Project' do - migration_project = described_class::Project - .find_including_path(project.id) - - expect(migration_project[:path_with_namespace]) - .to eq(project.full_path) - end - end - - describe '#repository' do - it 'returns a mock implemention of ::Repository' do - migration_project = described_class::Project - .find_including_path(project.id) - - expect(migration_project.repository).to respond_to(:storage) - expect(migration_project.repository).to respond_to(:gitaly_repository) - end - end - end - - describe '#up', :clean_gitlab_redis_shared_state do - let(:migration) { described_class.new } - - def job_count - Sidekiq.redis { |r| r.llen('queue:process_commit') } - end - - def pop_job - JSON.parse(Sidekiq.redis { |r| r.lpop('queue:process_commit') }) - end - - before do - Sidekiq.redis do |redis| - job = JSON.dump(args: [project.id, user.id, commit.id]) - redis.lpush('queue:process_commit', job) - end - end - - it 'skips jobs using a project that no longer exists' do - allow(described_class::Project).to receive(:find_including_path) - .with(project.id) - .and_return(nil) - - migration.up - - expect(job_count).to eq(0) - end - - it 'skips jobs using commits that no longer exist' do - allow_any_instance_of(Gitlab::GitalyClient::CommitService) - .to receive(:find_commit) - .with(commit.id) - .and_return(nil) - - migration.up - - expect(job_count).to eq(0) - end - - it 'inserts migrated jobs back into the queue' do - migration.up - - expect(job_count).to eq(1) - end - - it 'encodes data to UTF-8' do - allow(commit).to receive(:body) - .and_return('김치'.force_encoding('BINARY')) - - migration.up - - job = pop_job - - # We don't care so much about what is being stored, instead we just want - # to make sure the encoding is right so that JSON encoding the data - # doesn't produce any errors. - expect(job['args'][2]['message'].encoding).to eq(Encoding::UTF_8) - end - - context 'a migrated job' do - let(:job) do - migration.up - pop_job - end - - let(:commit_hash) do - job['args'][2] - end - - it 'includes the project ID' do - expect(job['args'][0]).to eq(project.id) - end - - it 'includes the user ID' do - expect(job['args'][1]).to eq(user.id) - end - - it 'includes the commit ID' do - expect(commit_hash['id']).to eq(commit.id) - end - - it 'includes the commit message' do - expect(commit_hash['message']).to eq(commit.message) - end - - it 'includes the parent IDs' do - expect(commit_hash['parent_ids']).to eq(commit.parent_ids) - end - - it 'includes the author date' do - expect(commit_hash['authored_date']).to eq(commit.authored_date.to_s) - end - - it 'includes the author name' do - expect(commit_hash['author_name']).to eq(commit.author_name) - end - - it 'includes the author Email' do - expect(commit_hash['author_email']).to eq(commit.author_email) - end - - it 'includes the commit date' do - expect(commit_hash['committed_date']).to eq(commit.committed_date.to_s) - end - - it 'includes the committer name' do - expect(commit_hash['committer_name']).to eq(commit.committer_name) - end - - it 'includes the committer Email' do - expect(commit_hash['committer_email']).to eq(commit.committer_email) - end - end - end - - describe '#down', :clean_gitlab_redis_shared_state do - let(:migration) { described_class.new } - - def job_count - Sidekiq.redis { |r| r.llen('queue:process_commit') } - end - - before do - Sidekiq.redis do |redis| - job = JSON.dump(args: [project.id, user.id, commit.id]) - redis.lpush('queue:process_commit', job) - - migration.up - end - end - - it 'pushes migrated jobs back into the queue' do - migration.down - - expect(job_count).to eq(1) - end - - context 'a migrated job' do - let(:job) do - migration.down - - JSON.parse(Sidekiq.redis { |r| r.lpop('queue:process_commit') }) - end - - it 'includes the project ID' do - expect(job['args'][0]).to eq(project.id) - end - - it 'includes the user ID' do - expect(job['args'][1]).to eq(user.id) - end - - it 'includes the commit SHA' do - expect(job['args'][2]).to eq(commit.id) - end - end - end -end diff --git a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb b/spec/migrations/migrate_stage_id_reference_in_background_spec.rb deleted file mode 100644 index dd6f5325750..00000000000 --- a/spec/migrations/migrate_stage_id_reference_in_background_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170628080858_migrate_stage_id_reference_in_background') - -describe MigrateStageIdReferenceInBackground, :migration, :sidekiq do - let(:jobs) { table(:ci_builds) } - let(:stages) { table(:ci_stages) } - let(:pipelines) { table(:ci_pipelines) } - let(:projects) { table(:projects) } - - before do - stub_const("#{described_class.name}::BATCH_SIZE", 3) - stub_const("#{described_class.name}::RANGE_SIZE", 2) - - projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1') - projects.create!(id: 345, name: 'gitlab2', path: 'gitlab2') - - pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a') - pipelines.create!(id: 2, project_id: 345, ref: 'feature', sha: 'cdf43c3c') - - jobs.create!(id: 1, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build') - jobs.create!(id: 2, commit_id: 1, project_id: 123, stage_idx: 2, stage: 'build') - jobs.create!(id: 3, commit_id: 1, project_id: 123, stage_idx: 1, stage: 'test') - jobs.create!(id: 4, commit_id: 1, project_id: 123, stage_idx: 3, stage: 'deploy') - jobs.create!(id: 5, commit_id: 2, project_id: 345, stage_idx: 1, stage: 'test') - - stages.create(id: 101, pipeline_id: 1, project_id: 123, name: 'test') - stages.create(id: 102, pipeline_id: 1, project_id: 123, name: 'build') - stages.create(id: 103, pipeline_id: 1, project_id: 123, name: 'deploy') - - jobs.create!(id: 6, commit_id: 2, project_id: 345, stage_id: 101, stage_idx: 1, stage: 'test') - end - - it 'correctly schedules background migrations' do - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 1, 2) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, 3, 3) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, 4, 5) - expect(BackgroundMigrationWorker.jobs.size).to eq 3 - end - end - end - - it 'schedules background migrations' do - perform_enqueued_jobs do - expect(jobs.where(stage_id: nil).count).to eq 5 - - migrate! - - expect(jobs.where(stage_id: nil).count).to eq 1 - end - end -end diff --git a/spec/migrations/migrate_stages_statuses_spec.rb b/spec/migrations/migrate_stages_statuses_spec.rb deleted file mode 100644 index 5483e24fce7..00000000000 --- a/spec/migrations/migrate_stages_statuses_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170711145558_migrate_stages_statuses.rb') - -describe MigrateStagesStatuses, :sidekiq, :migration do - let(:jobs) { table(:ci_builds) } - let(:stages) { table(:ci_stages) } - let(:pipelines) { table(:ci_pipelines) } - let(:projects) { table(:projects) } - - STATUSES = { created: 0, pending: 1, running: 2, success: 3, - failed: 4, canceled: 5, skipped: 6, manual: 7 }.freeze - - before do - stub_const("#{described_class.name}::BATCH_SIZE", 2) - stub_const("#{described_class.name}::RANGE_SIZE", 1) - - projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1') - projects.create!(id: 2, name: 'gitlab2', path: 'gitlab2') - - pipelines.create!(id: 1, project_id: 1, ref: 'master', sha: 'adf43c3a') - pipelines.create!(id: 2, project_id: 2, ref: 'feature', sha: '21a3deb') - - create_job(project: 1, pipeline: 1, stage: 'test', status: 'success') - create_job(project: 1, pipeline: 1, stage: 'test', status: 'running') - create_job(project: 1, pipeline: 1, stage: 'build', status: 'success') - create_job(project: 1, pipeline: 1, stage: 'build', status: 'failed') - create_job(project: 2, pipeline: 2, stage: 'test', status: 'success') - create_job(project: 2, pipeline: 2, stage: 'test', status: 'success') - create_job(project: 2, pipeline: 2, stage: 'test', status: 'failed', retried: true) - - stages.create!(id: 1, pipeline_id: 1, project_id: 1, name: 'test', status: nil) - stages.create!(id: 2, pipeline_id: 1, project_id: 1, name: 'build', status: nil) - stages.create!(id: 3, pipeline_id: 2, project_id: 2, name: 'test', status: nil) - end - - it 'correctly migrates stages statuses' do - perform_enqueued_jobs do - expect(stages.where(status: nil).count).to eq 3 - - migrate! - - expect(stages.where(status: nil)).to be_empty - expect(stages.all.order('id ASC').pluck(:status)) - .to eq [STATUSES[:running], STATUSES[:failed], STATUSES[:success]] - end - end - - it 'correctly schedules background migrations' do - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 1, 1) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 2, 2) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 3, 3) - expect(BackgroundMigrationWorker.jobs.size).to eq 3 - end - end - end - - def create_job(project:, pipeline:, stage:, status:, **opts) - stages = { test: 1, build: 2, deploy: 3 } - - jobs.create!(project_id: project, commit_id: pipeline, - stage_idx: stages[stage.to_sym], stage: stage, - status: status, **opts) - end -end diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb deleted file mode 100644 index 88aef3b70b4..00000000000 --- a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb') - -describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :migration do - let(:migration) { described_class.new } - let!(:user_active_1) { table(:users).create!(email: 'test1', username: 'test1') } - let!(:user_active_2) { table(:users).create!(email: 'test2', username: 'test2') } - - def record_activity(user, time) - Gitlab::Redis::SharedState.with do |redis| - redis.zadd(described_class::USER_ACTIVITY_SET_KEY, time.to_i, user.username) - end - end - - around do |example| - Timecop.freeze { example.run } - end - - before do - record_activity(user_active_1, described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 2.months) - record_activity(user_active_2, described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 3.months) - mute_stdout { migration.up } - end - - describe '#up' do - it 'fills last_activity_on from the legacy Redis Sorted Set' do - expect(user_active_1.reload.last_activity_on).to eq((described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 2.months).to_date) - expect(user_active_2.reload.last_activity_on).to eq((described_class::TIME_WHEN_ACTIVITY_SET_WAS_INTRODUCED + 3.months).to_date) - end - end - - describe '#down' do - it 'sets last_activity_on to NULL for all users' do - mute_stdout { migration.down } - - expect(user_active_1.reload.last_activity_on).to be_nil - expect(user_active_2.reload.last_activity_on).to be_nil - end - end - - def mute_stdout - orig_stdout = $stdout - $stdout = StringIO.new - yield - $stdout = orig_stdout - end -end diff --git a/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb b/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb deleted file mode 100644 index b4834705011..00000000000 --- a/spec/migrations/migrate_user_authentication_token_to_personal_access_token_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20171012125712_migrate_user_authentication_token_to_personal_access_token.rb') - -describe MigrateUserAuthenticationTokenToPersonalAccessToken, :migration do - let(:users) { table(:users) } - let(:personal_access_tokens) { table(:personal_access_tokens) } - - let!(:user) { users.create!(id: 1, email: 'user@example.com', authentication_token: 'user-token', admin: false) } - let!(:admin) { users.create!(id: 2, email: 'admin@example.com', authentication_token: 'admin-token', admin: true) } - - it 'migrates private tokens to Personal Access Tokens' do - migrate! - - expect(personal_access_tokens.count).to eq(2) - - user_token = personal_access_tokens.find_by(user_id: user.id) - admin_token = personal_access_tokens.find_by(user_id: admin.id) - - expect(user_token.token).to eq('user-token') - expect(admin_token.token).to eq('admin-token') - - expect(user_token.scopes).to eq(%w[api].to_yaml) - expect(admin_token.scopes).to eq(%w[api sudo].to_yaml) - end -end diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb deleted file mode 100644 index a0179ab3ceb..00000000000 --- a/spec/migrations/migrate_user_project_view_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_project_view.rb') - -describe MigrateUserProjectView, :migration do - let(:migration) { described_class.new } - let!(:user) { table(:users).create!(project_view: User.project_views['readme']) } - - describe '#up' do - it 'updates project view setting with new value' do - migration.up - - expect(user.reload.project_view).to eq(User.project_views['files']) - end - end -end diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb deleted file mode 100644 index d94ae1e52f5..00000000000 --- a/spec/migrations/move_personal_snippets_files_spec.rb +++ /dev/null @@ -1,197 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170612071012_move_personal_snippets_files.rb') - -describe MovePersonalSnippetsFiles, :migration do - let(:migration) { described_class.new } - let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_snippet_files_test") } - let(:uploads_dir) { File.join(test_dir, 'uploads') } - let(:new_uploads_dir) { File.join(uploads_dir, '-', 'system') } - - let(:notes) { table(:notes) } - let(:snippets) { table(:snippets) } - let(:uploads) { table(:uploads) } - - let(:user) { table(:users).create!(email: 'user@example.com', projects_limit: 10) } - let(:project) { table(:projects).create!(name: 'gitlab', namespace_id: 1) } - - before do - allow(CarrierWave).to receive(:root).and_return(test_dir) - allow(migration).to receive(:base_directory).and_return(test_dir) - FileUtils.remove_dir(test_dir) if File.directory?(test_dir) - allow(migration).to receive(:say) - end - - describe "#up" do - let(:snippet) do - snippet = snippets.create!(author_id: user.id) - create_upload('picture.jpg', snippet) - snippet.update(description: markdown_linking_file('picture.jpg', snippet)) - snippet - end - - let(:snippet_with_missing_file) do - snippet = snippets.create!(author_id: user.id, project_id: project.id) - create_upload('picture.jpg', snippet, create_file: false) - snippet.update(description: markdown_linking_file('picture.jpg', snippet)) - snippet - end - - it 'moves the files' do - source_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet)) - destination_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet)) - - migration.up - - expect(File.exist?(source_path)).to be_falsy - expect(File.exist?(destination_path)).to be_truthy - end - - describe 'updating the markdown' do - it 'includes the new path when the file exists' do - secret = "secret#{snippet.id}" - file_location = "/uploads/-/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" - - migration.up - - expect(snippet.reload.description).to include(file_location) - end - - it 'does not update the markdown when the file is missing' do - secret = "secret#{snippet_with_missing_file.id}" - file_location = "/uploads/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg" - - migration.up - - expect(snippet_with_missing_file.reload.description).to include(file_location) - end - - it 'updates the note markdown' do - secret = "secret#{snippet.id}" - file_location = "/uploads/-/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" - markdown = markdown_linking_file('picture.jpg', snippet) - note = notes.create!(noteable_id: snippet.id, - noteable_type: Snippet, - note: "with #{markdown}", - author_id: user.id) - - migration.up - - expect(note.reload.note).to include(file_location) - end - end - end - - describe "#down" do - let(:snippet) do - snippet = snippets.create!(author_id: user.id) - create_upload('picture.jpg', snippet, in_new_path: true) - snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true)) - snippet - end - - let(:snippet_with_missing_file) do - snippet = snippets.create!(author_id: user.id) - create_upload('picture.jpg', snippet, create_file: false, in_new_path: true) - snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true)) - snippet - end - - it 'moves the files' do - source_path = File.join(new_uploads_dir, model_file_path('picture.jpg', snippet)) - destination_path = File.join(uploads_dir, model_file_path('picture.jpg', snippet)) - - migration.down - - expect(File.exist?(source_path)).to be_falsey - expect(File.exist?(destination_path)).to be_truthy - end - - describe 'updating the markdown' do - it 'includes the new path when the file exists' do - secret = "secret#{snippet.id}" - file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" - - migration.down - - expect(snippet.reload.description).to include(file_location) - end - - it 'keeps the markdown as is when the file is missing' do - secret = "secret#{snippet_with_missing_file.id}" - file_location = "/uploads/-/system/personal_snippet/#{snippet_with_missing_file.id}/#{secret}/picture.jpg" - - migration.down - - expect(snippet_with_missing_file.reload.description).to include(file_location) - end - - it 'updates the note markdown' do - markdown = markdown_linking_file('picture.jpg', snippet, in_new_path: true) - secret = "secret#{snippet.id}" - file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg" - note = notes.create!(noteable_id: snippet.id, - noteable_type: Snippet, - note: "with #{markdown}", - author_id: user.id) - - migration.down - - expect(note.reload.note).to include(file_location) - end - end - end - - describe '#update_markdown' do - it 'escapes sql in the snippet description' do - migration.instance_variable_set('@source_relative_location', '/uploads/personal_snippet') - migration.instance_variable_set('@destination_relative_location', '/uploads/system/personal_snippet') - - secret = '123456789' - filename = 'hello.jpg' - snippet = snippets.create!(author_id: user.id) - - path_before = "/uploads/personal_snippet/#{snippet.id}/#{secret}/#{filename}" - path_after = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/#{filename}" - description_before = "Hello world; ![image](#{path_before})'; select * from users;" - description_after = "Hello world; ![image](#{path_after})'; select * from users;" - - migration.update_markdown(snippet.id, secret, filename, description_before) - - expect(snippet.reload.description).to eq(description_after) - end - end - - def create_upload(filename, snippet, create_file: true, in_new_path: false) - secret = "secret#{snippet.id}" - absolute_path = if in_new_path - File.join(new_uploads_dir, model_file_path(filename, snippet)) - else - File.join(uploads_dir, model_file_path(filename, snippet)) - end - - if create_file - FileUtils.mkdir_p(File.dirname(absolute_path)) - FileUtils.touch(absolute_path) - end - - uploads.create!(model_id: snippet.id, - model_type: snippet.class, - path: "#{secret}/#{filename}", - uploader: PersonalFileUploader, - size: 100.kilobytes) - end - - def markdown_linking_file(filename, snippet, in_new_path: false) - markdown = "![#{filename.split('.')[0]}]" - markdown += '(/uploads' - markdown += '/-/system' if in_new_path - markdown += "/#{model_file_path(filename, snippet)})" - markdown - end - - def model_file_path(filename, snippet) - secret = "secret#{snippet.id}" - - File.join('personal_snippet', snippet.id.to_s, secret, filename) - end -end diff --git a/spec/migrations/move_system_upload_folder_spec.rb b/spec/migrations/move_system_upload_folder_spec.rb deleted file mode 100644 index d3180477db3..00000000000 --- a/spec/migrations/move_system_upload_folder_spec.rb +++ /dev/null @@ -1,80 +0,0 @@ -require 'spec_helper' -require Rails.root.join("db", "migrate", "20170717074009_move_system_upload_folder.rb") - -describe MoveSystemUploadFolder do - let(:migration) { described_class.new } - let(:test_base) { File.join(Rails.root, 'tmp', 'tests', 'move-system-upload-folder') } - - before do - allow(migration).to receive(:base_directory).and_return(test_base) - FileUtils.rm_rf(test_base) - FileUtils.mkdir_p(test_base) - allow(migration).to receive(:say) - end - - describe '#up' do - let(:test_folder) { File.join(test_base, 'system') } - let(:test_file) { File.join(test_folder, 'file') } - - before do - FileUtils.mkdir_p(test_folder) - FileUtils.touch(test_file) - end - - it 'moves the related folder' do - migration.up - - expect(File.exist?(File.join(test_base, '-', 'system', 'file'))).to be_truthy - end - - it 'creates a symlink linking making the new folder available on the old path' do - migration.up - - expect(File.symlink?(File.join(test_base, 'system'))).to be_truthy - expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy - end - - it 'does not move if the target directory already exists' do - FileUtils.mkdir_p(File.join(test_base, '-', 'system')) - - expect(FileUtils).not_to receive(:mv) - expect(migration).to receive(:say).with(/already exists. No need to redo the move/) - - migration.up - end - end - - describe '#down' do - let(:test_folder) { File.join(test_base, '-', 'system') } - let(:test_file) { File.join(test_folder, 'file') } - - before do - FileUtils.mkdir_p(test_folder) - FileUtils.touch(test_file) - end - - it 'moves the system folder back to the old location' do - migration.down - - expect(File.exist?(File.join(test_base, 'system', 'file'))).to be_truthy - end - - it 'removes the symlink if it existed' do - FileUtils.ln_s(test_folder, File.join(test_base, 'system')) - - migration.down - - expect(File.directory?(File.join(test_base, 'system'))).to be_truthy - expect(File.symlink?(File.join(test_base, 'system'))).to be_falsey - end - - it 'does not move if the old directory already exists' do - FileUtils.mkdir_p(File.join(test_base, 'system')) - - expect(FileUtils).not_to receive(:mv) - expect(migration).to receive(:say).with(/already exists and is not a symlink, no need to revert/) - - migration.down - end - end -end diff --git a/spec/migrations/move_uploads_to_system_dir_spec.rb b/spec/migrations/move_uploads_to_system_dir_spec.rb deleted file mode 100644 index ca11a2004c5..00000000000 --- a/spec/migrations/move_uploads_to_system_dir_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -require "spec_helper" -require Rails.root.join("db", "migrate", "20170316163845_move_uploads_to_system_dir.rb") - -describe MoveUploadsToSystemDir do - let(:migration) { described_class.new } - let(:test_dir) { File.join(Rails.root, "tmp", "move_uploads_test") } - let(:uploads_dir) { File.join(test_dir, "public", "uploads") } - let(:new_uploads_dir) { File.join(uploads_dir, "-", "system") } - - before do - FileUtils.remove_dir(test_dir) if File.directory?(test_dir) - FileUtils.mkdir_p(uploads_dir) - allow(migration).to receive(:base_directory).and_return(test_dir) - allow(migration).to receive(:say) - end - - describe "#up" do - before do - FileUtils.mkdir_p(File.join(uploads_dir, 'user')) - FileUtils.touch(File.join(uploads_dir, 'user', 'dummy.file')) - end - - it 'moves the directory to the new path' do - expected_path = File.join(new_uploads_dir, 'user', 'dummy.file') - - migration.up - - expect(File.exist?(expected_path)).to be(true) - end - - it 'creates a symlink in the old location' do - symlink_path = File.join(uploads_dir, 'user') - expected_path = File.join(symlink_path, 'dummy.file') - - migration.up - - expect(File.exist?(expected_path)).to be(true) - expect(File.symlink?(symlink_path)).to be(true) - end - end - - describe "#down" do - before do - FileUtils.mkdir_p(File.join(new_uploads_dir, 'user')) - FileUtils.touch(File.join(new_uploads_dir, 'user', 'dummy.file')) - end - - it 'moves the directory to the old path' do - expected_path = File.join(uploads_dir, 'user', 'dummy.file') - - migration.down - - expect(File.exist?(expected_path)).to be(true) - end - - it 'removes the symlink if it existed' do - FileUtils.ln_s(File.join(new_uploads_dir, 'user'), File.join(uploads_dir, 'user')) - - directory = File.join(uploads_dir, 'user') - expected_path = File.join(directory, 'dummy.file') - - migration.down - - expect(File.exist?(expected_path)).to be(true) - expect(File.symlink?(directory)).to be(false) - end - end -end diff --git a/spec/migrations/normalize_ldap_extern_uids_spec.rb b/spec/migrations/normalize_ldap_extern_uids_spec.rb deleted file mode 100644 index a23a5d54e0a..00000000000 --- a/spec/migrations/normalize_ldap_extern_uids_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170921101004_normalize_ldap_extern_uids') - -describe NormalizeLdapExternUids, :migration, :sidekiq do - let!(:identities) { table(:identities) } - - around do |example| - Timecop.freeze { example.run } - end - - before do - stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_BATCH_SIZE", 2) - stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE", 2) - - # LDAP identities - (1..4).each do |i| - identities.create!(id: i, provider: 'ldapmain', extern_uid: " uid = foo #{i}, ou = People, dc = example, dc = com ", user_id: i) - end - - # Non-LDAP identity - identities.create!(id: 5, provider: 'foo', extern_uid: " uid = foo 5, ou = People, dc = example, dc = com ", user_id: 5) - end - - it 'correctly schedules background migrations' do - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([described_class::MIGRATION, [1, 2]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(2.minutes.from_now.to_f) - expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([described_class::MIGRATION, [3, 4]]) - expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(4.minutes.from_now.to_f) - expect(BackgroundMigrationWorker.jobs[2]['args']).to eq([described_class::MIGRATION, [5, 5]]) - expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(6.minutes.from_now.to_f) - expect(BackgroundMigrationWorker.jobs.size).to eq 3 - end - end - end - - it 'migrates the LDAP identities' do - perform_enqueued_jobs do - migrate! - identities.where(id: 1..4).each do |identity| - expect(identity.extern_uid).to eq("uid=foo #{identity.id},ou=people,dc=example,dc=com") - end - end - end - - it 'does not modify non-LDAP identities' do - perform_enqueued_jobs do - migrate! - identity = identities.last - expect(identity.extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ") - end - end -end diff --git a/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb b/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb deleted file mode 100644 index 0ff98933d5c..00000000000 --- a/spec/migrations/populate_can_push_from_deploy_keys_projects_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20171215113714_populate_can_push_from_deploy_keys_projects.rb') - -describe PopulateCanPushFromDeployKeysProjects, :migration do - let(:migration) { described_class.new } - let(:deploy_keys) { table(:keys) } - let(:deploy_keys_projects) { table(:deploy_keys_projects) } - let(:projects) { table(:projects) } - - before do - deploy_keys.inheritance_column = nil - - projects.create!(id: 1, name: 'gitlab1', path: 'gitlab1') - (1..10).each do |index| - deploy_keys.create!(id: index, title: 'dummy', type: 'DeployKey', key: Spec::Support::Helpers::KeyGeneratorHelper.new(1024).generate + ' dummy@gitlab.com') - deploy_keys_projects.create!(id: index, deploy_key_id: index, project_id: 1) - end - end - - describe '#up' do - it 'migrates can_push from deploy_keys to deploy_keys_projects' do - deploy_keys.limit(5).update_all(can_push: true) - - expected = deploy_keys.order(:id).pluck(:id, :can_push) - - migration.up - - expect(deploy_keys_projects.order(:id).pluck(:deploy_key_id, :can_push)).to eq expected - end - end - - describe '#down' do - it 'migrates can_push from deploy_keys_projects to deploy_keys' do - deploy_keys_projects.limit(5).update_all(can_push: true) - - expected = deploy_keys_projects.order(:id).pluck(:deploy_key_id, :can_push) - - migration.down - - expect(deploy_keys.order(:id).pluck(:id, :can_push)).to eq expected - end - end -end diff --git a/spec/migrations/remove_assignee_id_from_issue_spec.rb b/spec/migrations/remove_assignee_id_from_issue_spec.rb deleted file mode 100644 index 2c6f992d3ae..00000000000 --- a/spec/migrations/remove_assignee_id_from_issue_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170523073948_remove_assignee_id_from_issue.rb') - -describe RemoveAssigneeIdFromIssue, :migration do - let(:issues) { table(:issues) } - let(:issue_assignees) { table(:issue_assignees) } - let(:users) { table(:users) } - - let!(:user_1) { users.create(email: 'email1@example.com') } - let!(:user_2) { users.create(email: 'email2@example.com') } - let!(:user_3) { users.create(email: 'email3@example.com') } - - def create_issue(assignees:) - issues.create.tap do |issue| - assignees.each do |assignee| - issue_assignees.create(issue_id: issue.id, user_id: assignee.id) - end - end - end - - let!(:issue_single_assignee) { create_issue(assignees: [user_1]) } - let!(:issue_no_assignee) { create_issue(assignees: []) } - let!(:issue_multiple_assignees) { create_issue(assignees: [user_2, user_3]) } - - describe '#down' do - it 'sets the assignee_id to a random matching assignee from the assignees table' do - migrate! - disable_migrations_output { described_class.new.down } - - expect(issue_single_assignee.reload.assignee_id).to eq(user_1.id) - expect(issue_no_assignee.reload.assignee_id).to be_nil - expect(issue_multiple_assignees.reload.assignee_id).to eq(user_2.id).or(user_3.id) - - disable_migrations_output { described_class.new.up } - end - end -end diff --git a/spec/migrations/remove_dot_git_from_usernames_spec.rb b/spec/migrations/remove_dot_git_from_usernames_spec.rb deleted file mode 100644 index f11880a83e9..00000000000 --- a/spec/migrations/remove_dot_git_from_usernames_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20161226122833_remove_dot_git_from_usernames.rb') - -describe RemoveDotGitFromUsernames do - let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let(:migration) { described_class.new } - - describe '#up' do - before do - update_namespace(user, 'test.git') - end - - it 'renames user with .git in username' do - migration.up - - expect(user.reload.username).to eq('test_git') - expect(user.namespace.reload.path).to eq('test_git') - expect(user.namespace.route.path).to eq('test_git') - end - end - - context 'when new path exists already' do - describe '#up' do - let(:user2) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - - before do - update_namespace(user, 'test.git') - update_namespace(user2, 'test_git') - - default_hash = Gitlab.config.repositories.storages.default.to_h - default_hash['path'] = 'tmp/tests/custom_repositories' - storages = { 'default' => Gitlab::GitalyClient::StorageSettings.new(default_hash) } - - allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) - allow(migration).to receive(:route_exists?).with('test_git').and_return(true) - allow(migration).to receive(:route_exists?).with('test_git1').and_return(false) - end - - it 'renames user with .git in username' do - migration.up - - expect(user.reload.username).to eq('test_git1') - expect(user.namespace.reload.path).to eq('test_git1') - expect(user.namespace.route.path).to eq('test_git1') - end - end - end - - def update_namespace(user, path) - namespace = user.namespace - namespace.path = path - namespace.save!(validate: false) - - user.update_column(:username, path) - end -end diff --git a/spec/migrations/remove_duplicate_mr_events_spec.rb b/spec/migrations/remove_duplicate_mr_events_spec.rb deleted file mode 100644 index 2509ac6afd6..00000000000 --- a/spec/migrations/remove_duplicate_mr_events_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170815060945_remove_duplicate_mr_events.rb') - -describe RemoveDuplicateMrEvents, :delete do - let(:migration) { described_class.new } - - describe '#up' do - let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let(:merge_requests) { create_list(:merge_request, 2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let(:issue) { create(:issue) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let!(:events) do - [ - create(:event, :created, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs - create(:event, :created, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs - create(:event, :updated, author: user, target: merge_requests.first), # rubocop:disable RSpec/FactoriesInMigrationSpecs - create(:event, :created, author: user, target: merge_requests.second), # rubocop:disable RSpec/FactoriesInMigrationSpecs - create(:event, :created, author: user, target: issue), # rubocop:disable RSpec/FactoriesInMigrationSpecs - create(:event, :created, author: user, target: issue) # rubocop:disable RSpec/FactoriesInMigrationSpecs - ] - end - - it 'removes duplicated merge request create records' do - expect { migration.up }.to change { Event.count }.from(6).to(5) - end - end -end diff --git a/spec/migrations/remove_empty_fork_networks_spec.rb b/spec/migrations/remove_empty_fork_networks_spec.rb deleted file mode 100644 index f6d030ab25c..00000000000 --- a/spec/migrations/remove_empty_fork_networks_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20171114104051_remove_empty_fork_networks.rb') - -describe RemoveEmptyForkNetworks, :migration do - let!(:fork_networks) { table(:fork_networks) } - let!(:projects) { table(:projects) } - let!(:fork_network_members) { table(:fork_network_members) } - - let(:deleted_project) { projects.create! } - let!(:empty_network) { fork_networks.create!(id: 1, root_project_id: deleted_project.id) } - let!(:other_network) { fork_networks.create!(id: 2, root_project_id: projects.create.id) } - - before do - fork_network_members.create(fork_network_id: empty_network.id, - project_id: empty_network.root_project_id) - fork_network_members.create(fork_network_id: other_network.id, - project_id: other_network.root_project_id) - - deleted_project.destroy! - end - - after do - Upload.reset_column_information - end - - it 'deletes only the fork network without members' do - expect(fork_networks.count).to eq(2) - - migrate! - - expect(fork_networks.find_by(id: empty_network.id)).to be_nil - expect(fork_networks.find_by(id: other_network.id)).not_to be_nil - expect(fork_networks.count).to eq(1) - end -end diff --git a/spec/migrations/rename_duplicated_variable_key_spec.rb b/spec/migrations/rename_duplicated_variable_key_spec.rb deleted file mode 100644 index 11096564dfa..00000000000 --- a/spec/migrations/rename_duplicated_variable_key_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20170622135451_rename_duplicated_variable_key.rb') - -describe RenameDuplicatedVariableKey, :migration do - let(:variables) { table(:ci_variables) } - let(:projects) { table(:projects) } - - before do - projects.create!(id: 1) - variables.create!(id: 1, key: 'key1', project_id: 1) - variables.create!(id: 2, key: 'key2', project_id: 1) - variables.create!(id: 3, key: 'keyX', project_id: 1) - variables.create!(id: 4, key: 'keyX', project_id: 1) - variables.create!(id: 5, key: 'keyY', project_id: 1) - variables.create!(id: 6, key: 'keyX', project_id: 1) - variables.create!(id: 7, key: 'key7', project_id: 1) - variables.create!(id: 8, key: 'keyY', project_id: 1) - end - - it 'correctly remove duplicated records with smaller id' do - migrate! - - expect(variables.pluck(:id, :key)).to contain_exactly( - [1, 'key1'], - [2, 'key2'], - [3, 'keyX_3'], - [4, 'keyX_4'], - [5, 'keyY_5'], - [6, 'keyX'], - [7, 'key7'], - [8, 'keyY'] - ) - end -end diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb deleted file mode 100644 index 80ae209e9d1..00000000000 --- a/spec/migrations/rename_more_reserved_project_names_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170313133418_rename_more_reserved_project_names.rb') - -# This migration uses multiple threads, and thus different transactions. This -# means data created in this spec may not be visible to some threads. To work -# around this we use the DELETE cleaning strategy. -describe RenameMoreReservedProjectNames, :delete do - let(:migration) { described_class.new } - let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - - before do - project.path = 'artifacts' - project.save!(validate: false) - end - - describe '#up' do - context 'when project repository exists' do - before do - project.create_repository - end - - context 'when no exception is raised' do - it 'renames project with reserved names' do - migration.up - - expect(project.reload.path).to eq('artifacts0') - end - end - - context 'when exception is raised during rename' do - before do - service = instance_double('service') - - allow(service) - .to receive(:execute) - .and_raise(Projects::AfterRenameService::RenameFailedError) - - expect(migration) - .to receive(:after_rename_service) - .and_return(service) - end - - it 'captures exception from project rename' do - expect { migration.up }.not_to raise_error - end - end - end - - context 'when project repository does not exist' do - it 'does not raise error' do - expect { migration.up }.not_to raise_error - end - end - end -end diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb deleted file mode 100644 index 93e5c032287..00000000000 --- a/spec/migrations/rename_reserved_project_names_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_project_names.rb') - -# This migration is using factories, which set fields that don't actually -# exist in the DB schema previous to 20161221153951. Thus we just use the -# latest schema when testing this migration. -# This is ok-ish because: -# 1. This migration is a data migration -# 2. It only relies on very stable DB fields: routes.id, routes.path, namespaces.id, projects.namespace_id -# Ideally, the test should not use factories and rely on the `table` helper instead. -describe RenameReservedProjectNames, :migration, schema: :latest do - let(:migration) { described_class.new } - let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - - before do - project.path = 'projects' - project.save!(validate: false) - end - - describe '#up' do - context 'when project repository exists' do - before do - project.create_repository - end - - context 'when no exception is raised' do - it 'renames project with reserved names' do - migration.up - - expect(project.reload.path).to eq('projects0') - end - end - - context 'when exception is raised during rename' do - before do - service = instance_double('service') - - allow(service) - .to receive(:execute) - .and_raise(Projects::AfterRenameService::RenameFailedError) - - expect(migration) - .to receive(:after_rename_service) - .and_return(service) - end - - it 'captures exception from project rename' do - expect { migration.up }.not_to raise_error - end - end - end - - context 'when project repository does not exist' do - it 'does not raise error' do - expect { migration.up }.not_to raise_error - end - end - end -end diff --git a/spec/migrations/rename_users_with_renamed_namespace_spec.rb b/spec/migrations/rename_users_with_renamed_namespace_spec.rb deleted file mode 100644 index b8a4dc2b2c0..00000000000 --- a/spec/migrations/rename_users_with_renamed_namespace_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170518200835_rename_users_with_renamed_namespace.rb') - -describe RenameUsersWithRenamedNamespace, :delete do - it 'renames a user that had their namespace renamed to the namespace path' do - other_user = create(:user, username: 'kodingu') # rubocop:disable RSpec/FactoriesInMigrationSpecs - other_user1 = create(:user, username: 'api0') # rubocop:disable RSpec/FactoriesInMigrationSpecs - - user = create(:user, username: "Users0") # rubocop:disable RSpec/FactoriesInMigrationSpecs - user.update_column(:username, 'Users') - user1 = create(:user, username: "import0") # rubocop:disable RSpec/FactoriesInMigrationSpecs - user1.update_column(:username, 'import') - - described_class.new.up - - expect(user.reload.username).to eq('Users0') - expect(user1.reload.username).to eq('import0') - - expect(other_user.reload.username).to eq('kodingu') - expect(other_user1.reload.username).to eq('api0') - end -end diff --git a/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb b/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb deleted file mode 100644 index 76fe16581ac..00000000000 --- a/spec/migrations/schedule_merge_request_latest_merge_request_diff_id_migrations_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20171026082505_schedule_merge_request_latest_merge_request_diff_id_migrations') - -describe ScheduleMergeRequestLatestMergeRequestDiffIdMigrations, :migration, :sidekiq do - let(:projects_table) { table(:projects) } - let(:merge_requests_table) { table(:merge_requests) } - let(:merge_request_diffs_table) { table(:merge_request_diffs) } - - let(:project) { projects_table.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce') } - - let!(:merge_request_1) { create_mr!('mr_1', diffs: 1) } - let!(:merge_request_2) { create_mr!('mr_2', diffs: 2) } - let!(:merge_request_migrated) { create_mr!('merge_request_migrated', diffs: 3) } - let!(:merge_request_4) { create_mr!('mr_4', diffs: 3) } - - def create_mr!(name, diffs: 0) - merge_request = - merge_requests_table.create!(target_project_id: project.id, - target_branch: 'master', - source_project_id: project.id, - source_branch: name, - title: name) - - diffs.times do - merge_request_diffs_table.create!(merge_request_id: merge_request.id) - end - - merge_request - end - - def diffs_for(merge_request) - merge_request_diffs_table.where(merge_request_id: merge_request.id) - end - - before do - stub_const("#{described_class.name}::BATCH_SIZE", 1) - - diff_id = diffs_for(merge_request_migrated).minimum(:id) - merge_request_migrated.update!(latest_merge_request_diff_id: diff_id) - end - - it 'correctly schedules background migrations' do - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, merge_request_1.id, merge_request_1.id) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, merge_request_2.id, merge_request_2.id) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(15.minutes, merge_request_4.id, merge_request_4.id) - expect(BackgroundMigrationWorker.jobs.size).to eq 3 - end - end - end - - it 'schedules background migrations' do - perform_enqueued_jobs do - expect(merge_requests_table.where(latest_merge_request_diff_id: nil).count).to eq 3 - - migrate! - - expect(merge_requests_table.where(latest_merge_request_diff_id: nil).count).to eq 0 - end - end -end diff --git a/spec/migrations/track_untracked_uploads_spec.rb b/spec/migrations/track_untracked_uploads_spec.rb deleted file mode 100644 index 2fccfb3f12c..00000000000 --- a/spec/migrations/track_untracked_uploads_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20171103140253_track_untracked_uploads') - -describe TrackUntrackedUploads, :migration, :sidekiq do - include MigrationsHelpers::TrackUntrackedUploadsHelpers - - it 'correctly schedules the follow-up background migration' do - Sidekiq::Testing.fake! do - migrate! - - expect(described_class::MIGRATION).to be_scheduled_migration - expect(BackgroundMigrationWorker.jobs.size).to eq(1) - end - end -end diff --git a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb b/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb deleted file mode 100644 index 5f5ba426d69..00000000000 --- a/spec/migrations/turn_nested_groups_into_regular_groups_for_mysql_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20170503140202_turn_nested_groups_into_regular_groups_for_mysql.rb') - -describe TurnNestedGroupsIntoRegularGroupsForMysql do - let!(:parent_group) { create(:group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let!(:child_group) { create(:group, parent: parent_group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let!(:project) { create(:project, :legacy_storage, :empty_repo, namespace: child_group) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let!(:member) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let(:migration) { described_class.new } - - before do - parent_group.add_developer(member) - - allow(migration).to receive(:run_migration?).and_return(true) - allow(migration).to receive(:verbose).and_return(false) - end - - describe '#up' do - let(:updated_project) do - # path_with_namespace is memoized in an instance variable so we retrieve a - # new row here to work around that. - Project.find(project.id) - end - - before do - migration.up - end - - it 'unsets the parent_id column' do - expect(Namespace.where('parent_id IS NOT NULL').any?).to eq(false) - end - - it 'adds members of parent groups as members to the migrated group' do - is_member = child_group.members - .where(user_id: member, access_level: Gitlab::Access::DEVELOPER).any? - - expect(is_member).to eq(true) - end - - it 'update the path of the nested group' do - child_group.reload - - expect(child_group.path).to eq("#{parent_group.name}-#{child_group.name}") - end - - it 'renames projects of the nested group' do - expect(updated_project.full_path) - .to eq("#{parent_group.name}-#{child_group.name}/#{updated_project.path}") - end - - it 'renames the repository of any projects' do - repo_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access do - updated_project.repository.path - end - - expect(repo_path) - .to end_with("#{parent_group.name}-#{child_group.name}/#{updated_project.path}.git") - - expect(File.directory?(repo_path)).to eq(true) - end - - it 'creates a redirect route for renamed projects' do - exists = RedirectRoute - .where(source_type: 'Project', source_id: project.id) - .any? - - expect(exists).to eq(true) - end - end -end diff --git a/spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb b/spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb deleted file mode 100644 index d625b60ff50..00000000000 --- a/spec/migrations/update_legacy_diff_notes_type_for_import_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170927112318_update_legacy_diff_notes_type_for_import.rb') - -describe UpdateLegacyDiffNotesTypeForImport, :migration do - let(:notes) { table(:notes) } - - before do - notes.inheritance_column = nil - - notes.create(type: 'Note') - notes.create(type: 'LegacyDiffNote') - notes.create(type: 'Github::Import::Note') - notes.create(type: 'Github::Import::LegacyDiffNote') - end - - it 'updates the notes type' do - migrate! - - expect(notes.pluck(:type)) - .to contain_exactly('Note', 'Github::Import::Note', 'LegacyDiffNote', 'LegacyDiffNote') - end -end diff --git a/spec/migrations/update_notes_type_for_import_spec.rb b/spec/migrations/update_notes_type_for_import_spec.rb deleted file mode 100644 index 06195d970d8..00000000000 --- a/spec/migrations/update_notes_type_for_import_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170927112319_update_notes_type_for_import.rb') - -describe UpdateNotesTypeForImport, :migration do - let(:notes) { table(:notes) } - - before do - notes.inheritance_column = nil - - notes.create(type: 'Note') - notes.create(type: 'LegacyDiffNote') - notes.create(type: 'Github::Import::Note') - notes.create(type: 'Github::Import::LegacyDiffNote') - end - - it 'updates the notes type' do - migrate! - - expect(notes.pluck(:type)) - .to contain_exactly('Note', 'Note', 'LegacyDiffNote', 'Github::Import::LegacyDiffNote') - end -end diff --git a/spec/migrations/update_retried_for_ci_build_spec.rb b/spec/migrations/update_retried_for_ci_build_spec.rb deleted file mode 100644 index 637dcbb8e01..00000000000 --- a/spec/migrations/update_retried_for_ci_build_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170503004427_update_retried_for_ci_build.rb') - -describe UpdateRetriedForCiBuild, :delete do - let(:pipeline) { create(:ci_pipeline) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let!(:build_old) { create(:ci_build, pipeline: pipeline, name: 'test') } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let!(:build_new) { create(:ci_build, pipeline: pipeline, name: 'test') } # rubocop:disable RSpec/FactoriesInMigrationSpecs - - before do - described_class.new.up - end - - it 'updates ci_builds.is_retried' do - expect(build_old.reload).to be_retried - expect(build_new.reload).not_to be_retried - end -end diff --git a/spec/migrations/update_upload_paths_to_system_spec.rb b/spec/migrations/update_upload_paths_to_system_spec.rb deleted file mode 100644 index 984b428a020..00000000000 --- a/spec/migrations/update_upload_paths_to_system_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170317162059_update_upload_paths_to_system.rb') - -describe UpdateUploadPathsToSystem, :migration do - let(:migration) { described_class.new } - let(:uploads_table) { table(:uploads) } - let(:base_upload_attributes) { { size: 42, uploader: 'John Doe' } } - - before do - allow(migration).to receive(:say) - end - - describe '#uploads_to_switch_to_new_path' do - it 'contains only uploads with the old path for the correct models' do - _upload_for_other_type = create_upload('Pipeline', 'uploads/ci_pipeline/avatar.jpg') - _upload_with_system_path = create_upload('Project', 'uploads/-/system/project/avatar.jpg') - _upload_with_other_path = create_upload('Project', 'thelongsecretforafileupload/avatar.jpg') - old_upload = create_upload('Project', 'uploads/project/avatar.jpg') - group_upload = create_upload('Namespace', 'uploads/group/avatar.jpg') - - expect(uploads_table.where(migration.uploads_to_switch_to_new_path)).to contain_exactly(old_upload, group_upload) - end - end - - describe '#uploads_to_switch_to_old_path' do - it 'contains only uploads with the new path for the correct models' do - _upload_for_other_type = create_upload('Pipeline', 'uploads/ci_pipeline/avatar.jpg') - upload_with_system_path = create_upload('Project', 'uploads/-/system/project/avatar.jpg') - _upload_with_other_path = create_upload('Project', 'thelongsecretforafileupload/avatar.jpg') - _old_upload = create_upload('Project', 'uploads/project/avatar.jpg') - - expect(uploads_table.where(migration.uploads_to_switch_to_old_path)).to contain_exactly(upload_with_system_path) - end - end - - describe '#up' do - it 'updates old upload records to the new path' do - old_upload = create_upload('Project', 'uploads/project/avatar.jpg') - - migration.up - - expect(old_upload.reload.path).to eq('uploads/-/system/project/avatar.jpg') - end - end - - describe '#down' do - it 'updates the new system patsh to the old paths' do - new_upload = create_upload('Project', 'uploads/-/system/project/avatar.jpg') - - migration.down - - expect(new_upload.reload.path).to eq('uploads/project/avatar.jpg') - end - end - - def create_upload(type, path) - uploads_table.create(base_upload_attributes.merge(model_type: type, path: path)) - end -end diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb index 292ddabd2d8..057517d3820 100644 --- a/spec/models/clusters/applications/ingress_spec.rb +++ b/spec/models/clusters/applications/ingress_spec.rb @@ -21,7 +21,21 @@ describe Clusters::Applications::Ingress do describe '#can_uninstall?' do subject { ingress.can_uninstall? } - it { is_expected.to be_falsey } + it 'returns true if application_jupyter_nil_or_installable? AND external_ip_or_hostname? are true' do + ingress.external_ip = 'IP' + + is_expected.to be_truthy + end + + it 'returns false if application_jupyter_nil_or_installable? is false' do + create(:clusters_applications_jupyter, :installed, cluster: ingress.cluster) + + is_expected.to be_falsey + end + + it 'returns false if external_ip_or_hostname? is false' do + is_expected.to be_falsey + end end describe '#make_installed!' do diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb index 2fe82eaa778..8d951ab6f0f 100644 --- a/spec/models/deploy_token_spec.rb +++ b/spec/models/deploy_token_spec.rb @@ -8,6 +8,15 @@ describe DeployToken do it { is_expected.to have_many :project_deploy_tokens } it { is_expected.to have_many(:projects).through(:project_deploy_tokens) } + describe 'validations' do + let(:username_format_message) { "can contain only letters, digits, '_', '-', '+', and '.'" } + + it { is_expected.to validate_length_of(:username).is_at_most(255) } + it { is_expected.to allow_value('GitLab+deploy_token-3.14').for(:username) } + it { is_expected.not_to allow_value('<script>').for(:username).with_message(username_format_message) } + it { is_expected.not_to allow_value('').for(:username).with_message(username_format_message) } + end + describe '#ensure_token' do it 'ensures a token' do deploy_token.token = nil @@ -87,8 +96,30 @@ describe DeployToken do end describe '#username' do - it 'returns a harcoded username' do - expect(deploy_token.username).to eq("gitlab+deploy-token-#{deploy_token.id}") + context 'persisted records' do + it 'returns a default username if none is set' do + expect(deploy_token.username).to eq("gitlab+deploy-token-#{deploy_token.id}") + end + + it 'returns the username provided if one is set' do + deploy_token = create(:deploy_token, username: 'deployer') + + expect(deploy_token.username).to eq('deployer') + end + end + + context 'new records' do + it 'returns nil if no username is set' do + deploy_token = build(:deploy_token) + + expect(deploy_token.username).to be_nil + end + + it 'returns the username provided if one is set' do + deploy_token = build(:deploy_token, username: 'deployer') + + expect(deploy_token.username).to eq('deployer') + end end end diff --git a/spec/models/namespace/aggregation_schedule_spec.rb b/spec/models/namespace/aggregation_schedule_spec.rb index 5ba7547ff4d..8ed0248e1b2 100644 --- a/spec/models/namespace/aggregation_schedule_spec.rb +++ b/spec/models/namespace/aggregation_schedule_spec.rb @@ -2,6 +2,77 @@ require 'spec_helper' -RSpec.describe Namespace::AggregationSchedule, type: :model do +RSpec.describe Namespace::AggregationSchedule, :clean_gitlab_redis_shared_state, type: :model do + include ExclusiveLeaseHelpers + it { is_expected.to belong_to :namespace } + + describe '.delay_timeout' do + context 'when timeout is set on redis' do + it 'uses personalized timeout' do + Gitlab::Redis::SharedState.with do |redis| + redis.set(described_class::REDIS_SHARED_KEY, 1.hour) + end + + expect(described_class.delay_timeout).to eq(1.hour) + end + end + + context 'when timeout is not set on redis' do + it 'uses default timeout' do + expect(described_class.delay_timeout).to eq(3.hours) + end + end + end + + describe '#schedule_root_storage_statistics' do + let(:namespace) { create(:namespace) } + let(:aggregation_schedule) { namespace.build_aggregation_schedule } + let(:lease_key) { "namespace:namespaces_root_statistics:#{namespace.id}" } + + context "when we can't obtain the lease" do + it 'does not schedule the workers' do + stub_exclusive_lease_taken(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT) + + expect(Namespaces::RootStatisticsWorker) + .not_to receive(:perform_async) + + expect(Namespaces::RootStatisticsWorker) + .not_to receive(:perform_in) + + aggregation_schedule.save! + end + end + + context 'when we can obtain the lease' do + it 'schedules a root storage statistics after create' do + stub_exclusive_lease(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT) + + expect(Namespaces::RootStatisticsWorker) + .to receive(:perform_async).once + + expect(Namespaces::RootStatisticsWorker) + .to receive(:perform_in).once + .with(described_class::DEFAULT_LEASE_TIMEOUT, aggregation_schedule.namespace_id ) + + aggregation_schedule.save! + end + end + + context 'with a personalized lease timeout' do + before do + Gitlab::Redis::SharedState.with do |redis| + redis.set(described_class::REDIS_SHARED_KEY, 1.hour) + end + end + + it 'uses a personalized time' do + expect(Namespaces::RootStatisticsWorker) + .to receive(:perform_in) + .with(1.hour, aggregation_schedule.namespace_id) + + aggregation_schedule.save! + end + end + end end diff --git a/spec/models/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb index f6fb5af5aae..3229a32234e 100644 --- a/spec/models/namespace/root_storage_statistics_spec.rb +++ b/spec/models/namespace/root_storage_statistics_spec.rb @@ -7,4 +7,69 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do it { is_expected.to have_one(:route).through(:namespace) } it { is_expected.to delegate_method(:all_projects).to(:namespace) } + + describe '#recalculate!' do + let(:namespace) { create(:group) } + let(:root_storage_statistics) { create(:namespace_root_storage_statistics, namespace: namespace) } + + let(:project1) { create(:project, namespace: namespace) } + let(:project2) { create(:project, namespace: namespace) } + + let!(:stat1) { create(:project_statistics, project: project1, with_data: true, size_multiplier: 100) } + let!(:stat2) { create(:project_statistics, project: project2, with_data: true, size_multiplier: 200) } + + shared_examples 'data refresh' do + it 'aggregates project statistics' do + root_storage_statistics.recalculate! + + root_storage_statistics.reload + + total_repository_size = stat1.repository_size + stat2.repository_size + total_wiki_size = stat1.wiki_size + stat2.wiki_size + total_lfs_objects_size = stat1.lfs_objects_size + stat2.lfs_objects_size + total_build_artifacts_size = stat1.build_artifacts_size + stat2.build_artifacts_size + total_packages_size = stat1.packages_size + stat2.packages_size + total_storage_size = stat1.storage_size + stat2.storage_size + + expect(root_storage_statistics.repository_size).to eq(total_repository_size) + expect(root_storage_statistics.wiki_size).to eq(total_wiki_size) + expect(root_storage_statistics.lfs_objects_size).to eq(total_lfs_objects_size) + expect(root_storage_statistics.build_artifacts_size).to eq(total_build_artifacts_size) + expect(root_storage_statistics.packages_size).to eq(total_packages_size) + expect(root_storage_statistics.storage_size).to eq(total_storage_size) + end + + it 'works when there are no projects' do + Project.delete_all + + root_storage_statistics.recalculate! + + root_storage_statistics.reload + expect(root_storage_statistics.repository_size).to eq(0) + expect(root_storage_statistics.wiki_size).to eq(0) + expect(root_storage_statistics.lfs_objects_size).to eq(0) + expect(root_storage_statistics.build_artifacts_size).to eq(0) + expect(root_storage_statistics.packages_size).to eq(0) + expect(root_storage_statistics.storage_size).to eq(0) + end + end + + it_behaves_like 'data refresh' + + context 'with subgroups', :nested_groups do + let(:subgroup1) { create(:group, parent: namespace)} + let(:subgroup2) { create(:group, parent: subgroup1)} + + let(:project1) { create(:project, namespace: subgroup1) } + let(:project2) { create(:project, namespace: subgroup2) } + + it_behaves_like 'data refresh' + end + + context 'with a personal namespace' do + let(:namespace) { create(:user).namespace } + + it_behaves_like 'data refresh' + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 30e49cf204f..f908f3504e0 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -837,4 +837,20 @@ describe Namespace do it { is_expected.to be_falsy } end end + + describe '#aggregation_scheduled?' do + let(:namespace) { create(:namespace) } + + subject { namespace.aggregation_scheduled? } + + context 'with an aggregation scheduled association' do + let(:namespace) { create(:namespace, :with_aggregation_schedule) } + + it { is_expected.to be_truthy } + end + + context 'without an aggregation scheduled association' do + it { is_expected.to be_falsy } + end + end end diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb index 6818db48fee..d5b0f94f461 100644 --- a/spec/models/project_services/bugzilla_service_spec.rb +++ b/spec/models/project_services/bugzilla_service_spec.rb @@ -32,4 +32,49 @@ describe BugzillaService do it { is_expected.not_to validate_presence_of(:new_issue_url) } end end + + context 'overriding properties' do + let(:default_title) { 'JIRA' } + let(:default_description) { 'JiraService|Jira issue tracker' } + let(:url) { 'http://bugzilla.example.com' } + let(:access_params) do + { project_url: url, issues_url: url, new_issue_url: url } + end + + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 + context 'when data are stored in properties' do + let(:properties) { access_params.merge(title: title, description: description) } + let(:service) { create(:bugzilla_service, properties: properties) } + + include_examples 'issue tracker fields' + end + + context 'when data are stored in separated fields' do + let(:service) do + create(:bugzilla_service, title: title, description: description, properties: access_params) + end + + include_examples 'issue tracker fields' + end + + context 'when data are stored in both properties and separated fields' do + let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') } + let(:service) do + create(:bugzilla_service, title: title, description: description, properties: properties) + end + + include_examples 'issue tracker fields' + end + + context 'when no title & description are set' do + let(:service) do + create(:bugzilla_service, properties: access_params) + end + + it 'returns default values' do + expect(service.title).to eq('Bugzilla') + expect(service.description).to eq('Bugzilla issue tracker') + end + end + end end diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb index f0e7551693d..56b0bda6626 100644 --- a/spec/models/project_services/custom_issue_tracker_service_spec.rb +++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb @@ -48,4 +48,47 @@ describe CustomIssueTrackerService do end end end + + context 'overriding properties' do + let(:url) { 'http://custom.example.com' } + let(:access_params) do + { project_url: url, issues_url: url, new_issue_url: url } + end + + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 + context 'when data are stored in properties' do + let(:properties) { access_params.merge(title: title, description: description) } + let(:service) { create(:custom_issue_tracker_service, properties: properties) } + + include_examples 'issue tracker fields' + end + + context 'when data are stored in separated fields' do + let(:service) do + create(:custom_issue_tracker_service, title: title, description: description, properties: access_params) + end + + include_examples 'issue tracker fields' + end + + context 'when data are stored in both properties and separated fields' do + let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') } + let(:service) do + create(:custom_issue_tracker_service, title: title, description: description, properties: properties) + end + + include_examples 'issue tracker fields' + end + + context 'when no title & description are set' do + let(:service) do + create(:custom_issue_tracker_service, properties: access_params) + end + + it 'returns default values' do + expect(service.title).to eq('Custom Issue Tracker') + expect(service.description).to eq('Custom issue tracker') + end + end + end end diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 11f96c03d46..a3726f09dc5 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -51,4 +51,47 @@ describe GitlabIssueTrackerService do end end end + + context 'overriding properties' do + let(:url) { 'http://gitlab.example.com' } + let(:access_params) do + { project_url: url, issues_url: url, new_issue_url: url } + end + + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 + context 'when data are stored in properties' do + let(:properties) { access_params.merge(title: title, description: description) } + let(:service) { create(:gitlab_issue_tracker_service, properties: properties) } + + include_examples 'issue tracker fields' + end + + context 'when data are stored in separated fields' do + let(:service) do + create(:gitlab_issue_tracker_service, title: title, description: description, properties: access_params) + end + + include_examples 'issue tracker fields' + end + + context 'when data are stored in both properties and separated fields' do + let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') } + let(:service) do + create(:gitlab_issue_tracker_service, title: title, description: description, properties: properties) + end + + include_examples 'issue tracker fields' + end + + context 'when no title & description are set' do + let(:service) do + create(:gitlab_issue_tracker_service, properties: access_params) + end + + it 'returns default values' do + expect(service.title).to eq('GitLab') + expect(service.description).to eq('GitLab issue tracker') + end + end + end end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index fc08457a3c5..9b122d85293 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -115,6 +115,70 @@ describe JiraService do end end + describe '#create' do + let(:params) do + { + project: create(:project), title: 'custom title', description: 'custom description' + } + end + + subject { described_class.create(params) } + + it 'does not store title & description into properties' do + expect(subject.properties.keys).not_to include('title', 'description') + end + + it 'sets title & description correctly' do + service = subject + + expect(service.title).to eq('custom title') + expect(service.description).to eq('custom description') + end + end + + context 'overriding properties' do + let(:url) { 'http://issue_tracker.example.com' } + let(:access_params) do + { url: url, username: 'username', password: 'password' } + end + + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 + context 'when data are stored in properties' do + let(:properties) { access_params.merge(title: title, description: description) } + let(:service) { create(:jira_service, properties: properties) } + + include_examples 'issue tracker fields' + end + + context 'when data are stored in separated fields' do + let(:service) do + create(:jira_service, title: title, description: description, properties: access_params) + end + + include_examples 'issue tracker fields' + end + + context 'when data are stored in both properties and separated fields' do + let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') } + let(:service) do + create(:jira_service, title: title, description: description, properties: properties) + end + + include_examples 'issue tracker fields' + end + + context 'when no title & description are set' do + let(:service) do + create(:jira_service, properties: access_params) + end + + it 'returns default values' do + expect(service.title).to eq('Jira') + expect(service.description).to eq('Jira issue tracker') + end + end + end + describe '#close_issue' do let(:custom_base_url) { 'http://custom_url' } let(:user) { create(:user) } @@ -450,36 +514,54 @@ describe JiraService do end describe 'description and title' do - let(:project) { create(:project) } + let(:title) { 'Jira One' } + let(:description) { 'Jira One issue tracker' } + let(:properties) do + { + url: 'http://jira.example.com/web', + username: 'mic', + password: 'password', + title: title, + description: description + } + end context 'when it is not set' do - before do - @service = project.create_jira_service(active: true) - end + it 'default values are returned' do + service = create(:jira_service) - after do - @service.destroy! + expect(service.title).to eq('Jira') + expect(service.description).to eq('Jira issue tracker') end + end - it 'is initialized' do - expect(@service.title).to eq('Jira') - expect(@service.description).to eq('Jira issue tracker') + context 'when it is set in properties' do + it 'values from properties are returned' do + service = create(:jira_service, properties: properties) + + expect(service.title).to eq(title) + expect(service.description).to eq(description) end end - context 'when it is set' do - before do - properties = { 'title' => 'Jira One', 'description' => 'Jira One issue tracker' } - @service = project.create_jira_service(active: true, properties: properties) - end + context 'when it is in title & description fields' do + it 'values from title and description fields are returned' do + service = create(:jira_service, title: title, description: description) - after do - @service.destroy! + expect(service.title).to eq(title) + expect(service.description).to eq(description) end + end + + context 'when it is in both properites & title & description fields' do + it 'values from title and description fields are returned' do + title2 = 'Jira 2' + description2 = 'Jira description 2' - it 'is correct' do - expect(@service.title).to eq('Jira One') - expect(@service.description).to eq('Jira One issue tracker') + service = create(:jira_service, title: title2, description: description2, properties: properties) + + expect(service.title).to eq(title2) + expect(service.description).to eq(description2) end end end @@ -505,29 +587,21 @@ describe JiraService do end describe 'project and issue urls' do - let(:project) { create(:project) } - context 'when gitlab.yml was initialized' do - before do + it 'is prepopulated with the settings' do settings = { 'jira' => { - 'title' => 'Jira', 'url' => 'http://jira.sample/projects/project_a', 'api_url' => 'http://jira.sample/api' } } allow(Gitlab.config).to receive(:issues_tracker).and_return(settings) - @service = project.create_jira_service(active: true) - end - after do - @service.destroy! - end + project = create(:project) + service = project.create_jira_service(active: true) - it 'is prepopulated with the settings' do - expect(@service.properties['title']).to eq('Jira') - expect(@service.properties['url']).to eq('http://jira.sample/projects/project_a') - expect(@service.properties['api_url']).to eq('http://jira.sample/api') + expect(service.properties['url']).to eq('http://jira.sample/projects/project_a') + expect(service.properties['api_url']).to eq('http://jira.sample/api') end end end diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb index ac570ac27e1..806e3695962 100644 --- a/spec/models/project_services/redmine_service_spec.rb +++ b/spec/models/project_services/redmine_service_spec.rb @@ -40,4 +40,47 @@ describe RedmineService do expect(described_class.reference_pattern.match('#123')[:issue]).to eq('123') end end + + context 'overriding properties' do + let(:url) { 'http://redmine.example.com' } + let(:access_params) do + { project_url: url, issues_url: url, new_issue_url: url } + end + + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 + context 'when data are stored in properties' do + let(:properties) { access_params.merge(title: title, description: description) } + let(:service) { create(:redmine_service, properties: properties) } + + include_examples 'issue tracker fields' + end + + context 'when data are stored in separated fields' do + let(:service) do + create(:redmine_service, title: title, description: description, properties: access_params) + end + + include_examples 'issue tracker fields' + end + + context 'when data are stored in both properties and separated fields' do + let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') } + let(:service) do + create(:redmine_service, title: title, description: description, properties: properties) + end + + include_examples 'issue tracker fields' + end + + context 'when no title & description are set' do + let(:service) do + create(:redmine_service, properties: access_params) + end + + it 'returns default values' do + expect(service.title).to eq('Redmine') + expect(service.description).to eq('Redmine issue tracker') + end + end + end end diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb index bf9d892f66c..b47ef6702b4 100644 --- a/spec/models/project_services/youtrack_service_spec.rb +++ b/spec/models/project_services/youtrack_service_spec.rb @@ -37,4 +37,47 @@ describe YoutrackService do expect(described_class.reference_pattern.match('YT-123')[:issue]).to eq('YT-123') end end + + context 'overriding properties' do + let(:url) { 'http://youtrack.example.com' } + let(:access_params) do + { project_url: url, issues_url: url, new_issue_url: url } + end + + # this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084 + context 'when data are stored in properties' do + let(:properties) { access_params.merge(title: title, description: description) } + let(:service) { create(:youtrack_service, properties: properties) } + + include_examples 'issue tracker fields' + end + + context 'when data are stored in separated fields' do + let(:service) do + create(:youtrack_service, title: title, description: description, properties: access_params) + end + + include_examples 'issue tracker fields' + end + + context 'when data are stored in both properties and separated fields' do + let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') } + let(:service) do + create(:youtrack_service, title: title, description: description, properties: properties) + end + + include_examples 'issue tracker fields' + end + + context 'when no title & description are set' do + let(:service) do + create(:youtrack_service, properties: access_params) + end + + it 'returns default values' do + expect(service.title).to eq('YouTrack') + expect(service.description).to eq('YouTrack issue tracker') + end + end + end end diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 7c106ce6b85..e9d846e7291 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -64,4 +64,14 @@ RSpec.describe Release do is_expected.to all(be_a(Releases::Source)) end end + + describe '#upcoming_release?' do + context 'during the backfill migration when released_at could be nil' do + it 'handles a nil released_at value and returns false' do + allow(release).to receive(:released_at).and_return nil + + expect(release.upcoming_release?).to eq(false) + end + end + end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index d442c73c118..0797b9a9d83 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -244,7 +244,8 @@ describe Service do let(:service) do GitlabIssueTrackerService.create( project: create(:project), - title: 'random title' + title: 'random title', + project_url: 'http://gitlab.example.com' ) end @@ -252,8 +253,12 @@ describe Service do expect { service }.not_to raise_error end + it 'sets title correctly' do + expect(service.title).to eq('random title') + end + it 'creates the properties' do - expect(service.properties).to eq({ "title" => "random title" }) + expect(service.properties).to eq({ "project_url" => "http://gitlab.example.com" }) end end diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index 8603fa2a73d..206e898381d 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -24,7 +24,7 @@ describe API::Releases do project: project, tag: 'v0.1', author: maintainer, - created_at: 2.days.ago) + released_at: 2.days.ago) end let!(:release_2) do @@ -32,7 +32,7 @@ describe API::Releases do project: project, tag: 'v0.2', author: maintainer, - created_at: 1.day.ago) + released_at: 1.day.ago) end it 'returns 200 HTTP status' do @@ -41,7 +41,7 @@ describe API::Releases do expect(response).to have_gitlab_http_status(:ok) end - it 'returns releases ordered by created_at' do + it 'returns releases ordered by released_at' do get api("/projects/#{project.id}/releases", maintainer) expect(json_response.count).to eq(2) @@ -56,6 +56,26 @@ describe API::Releases do end end + it 'returns an upcoming_release status for a future release' do + tomorrow = Time.now.utc + 1.day + create(:release, project: project, tag: 'v0.1', author: maintainer, released_at: tomorrow) + + get api("/projects/#{project.id}/releases", maintainer) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.first['upcoming_release']).to eq(true) + end + + it 'returns an upcoming_release status for a past release' do + yesterday = Time.now.utc - 1.day + create(:release, project: project, tag: 'v0.1', author: maintainer, released_at: yesterday) + + get api("/projects/#{project.id}/releases", maintainer) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.first['upcoming_release']).to eq(false) + end + context 'when tag does not exist in git repository' do let!(:release) { create(:release, project: project, tag: 'v1.1.5') } @@ -316,6 +336,51 @@ describe API::Releases do expect(project.releases.last.description).to eq('Super nice release') end + it 'sets the released_at to the current time if the released_at parameter is not provided' do + now = Time.zone.parse('2015-08-25 06:00:00Z') + Timecop.freeze(now) do + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(project.releases.last.released_at).to eq(now) + end + end + + it 'sets the released_at to the value in the parameters if specified' do + params = { + name: 'New release', + tag_name: 'v0.1', + description: 'Super nice release', + released_at: '2019-03-20T10:00:00Z' + } + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(project.releases.last.released_at).to eq('2019-03-20T10:00:00Z') + end + + it 'assumes the utc timezone for released_at if the timezone is not provided' do + params = { + name: 'New release', + tag_name: 'v0.1', + description: 'Super nice release', + released_at: '2019-03-25 10:00:00' + } + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(project.releases.last.released_at).to eq('2019-03-25T10:00:00Z') + end + + it 'allows specifying a released_at with a local time zone' do + params = { + name: 'New release', + tag_name: 'v0.1', + description: 'Super nice release', + released_at: '2019-03-25T10:00:00+09:00' + } + post api("/projects/#{project.id}/releases", maintainer), params: params + + expect(project.releases.last.released_at).to eq('2019-03-25T01:00:00Z') + end + context 'when description is empty' do let(:params) do { @@ -540,6 +605,7 @@ describe API::Releases do project: project, tag: 'v0.1', name: 'New release', + released_at: '2018-03-01T22:00:00Z', description: 'Super nice release') end @@ -560,6 +626,7 @@ describe API::Releases do expect(project.releases.last.tag).to eq('v0.1') expect(project.releases.last.name).to eq('New release') + expect(project.releases.last.released_at).to eq('2018-03-01T22:00:00Z') end it 'matches response schema' do @@ -568,6 +635,14 @@ describe API::Releases do expect(response).to match_response_schema('public_api/v4/release') end + it 'updates released_at' do + params = { released_at: '2015-10-10T05:00:00Z' } + + put api("/projects/#{project.id}/releases/v0.1", maintainer), params: params + + expect(project.releases.last.released_at).to eq('2015-10-10T05:00:00Z') + end + context 'when user tries to update sha' do let(:params) { { sha: 'xxx' } } diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 89adbc77a7f..d832963292c 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -102,6 +102,27 @@ describe 'Rack Attack global throttles' do expect_rejection { get(*get_args) } end + + it 'logs RackAttack info into structured logs' do + requests_per_period.times do + get(*get_args) + expect(response).to have_http_status 200 + end + + arguments = { + message: 'Rack_Attack', + env: :throttle, + ip: '127.0.0.1', + request_method: 'GET', + fullpath: get_args.first, + user_id: user.id, + username: user.username + } + + expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once + + expect_rejection { get(*get_args) } + end end context 'when the throttle is disabled' do @@ -189,7 +210,15 @@ describe 'Rack Attack global throttles' do expect(response).to have_http_status 200 end - expect(Gitlab::AuthLogger).to receive(:error).once + arguments = { + message: 'Rack_Attack', + env: :throttle, + ip: '127.0.0.1', + request_method: 'GET', + fullpath: '/users/sign_in' + } + + expect(Gitlab::AuthLogger).to receive(:error).with(arguments) get url_that_does_not_require_authentication end @@ -345,7 +374,17 @@ describe 'Rack Attack global throttles' do expect(response).to have_http_status 200 end - expect(Gitlab::AuthLogger).to receive(:error).once + arguments = { + message: 'Rack_Attack', + env: :throttle, + ip: '127.0.0.1', + request_method: 'GET', + fullpath: '/dashboard/snippets', + user_id: user.id, + username: user.username + } + + expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once get url_that_requires_authentication end diff --git a/spec/services/deploy_tokens/create_service_spec.rb b/spec/services/deploy_tokens/create_service_spec.rb index 886ffd4593d..fbb66fe4cb7 100644 --- a/spec/services/deploy_tokens/create_service_spec.rb +++ b/spec/services/deploy_tokens/create_service_spec.rb @@ -32,6 +32,22 @@ describe DeployTokens::CreateService do end end + context 'when username is empty string' do + let(:deploy_token_params) { attributes_for(:deploy_token, username: '') } + + it 'converts it to nil' do + expect(subject.read_attribute(:username)).to be_nil + end + end + + context 'when username is provided' do + let(:deploy_token_params) { attributes_for(:deploy_token, username: 'deployer') } + + it 'keeps the provided username' do + expect(subject.read_attribute(:username)).to eq('deployer') + end + end + context 'when the deploy token is invalid' do let(:deploy_token_params) { attributes_for(:deploy_token, read_repository: false, read_registry: false) } diff --git a/spec/services/namespaces/statistics_refresher_service_spec.rb b/spec/services/namespaces/statistics_refresher_service_spec.rb new file mode 100644 index 00000000000..f4d9c96f7f4 --- /dev/null +++ b/spec/services/namespaces/statistics_refresher_service_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Namespaces::StatisticsRefresherService, '#execute' do + let(:group) { create(:group) } + let(:projects) { create_list(:project, 5, namespace: group) } + let(:service) { described_class.new } + + context 'without a root storage statistics relation' do + it 'creates one' do + expect do + service.execute(group) + end.to change(Namespace::RootStorageStatistics, :count).by(1) + + expect(group.reload.root_storage_statistics).to be_present + end + + it 'recalculate the namespace statistics' do + expect_any_instance_of(Namespace::RootStorageStatistics).to receive(:recalculate!).once + + service.execute(group) + end + end + + context 'with a root storage statistics relation' do + before do + Namespace::AggregationSchedule.safe_find_or_create_by!(namespace_id: group.id) + end + + it 'does not create one' do + expect do + service.execute(group) + end.not_to change(Namespace::RootStorageStatistics, :count) + end + + it 'recalculate the namespace statistics' do + expect(Namespace::RootStorageStatistics) + .to receive(:safe_find_or_create_by!).with({ namespace_id: group.id }) + .and_return(group.root_storage_statistics) + + service.execute(group) + end + end + + context 'when something goes wrong' do + before do + allow_any_instance_of(Namespace::RootStorageStatistics) + .to receive(:recalculate!).and_raise(ActiveRecord::ActiveRecordError) + end + + it 'raises RefreshError' do + expect do + service.execute(group) + end.to raise_error(Namespaces::StatisticsRefresherService::RefresherError) + end + end +end diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb index c4bf5da0cf0..7c8e57702ae 100644 --- a/spec/support/features/discussion_comments_shared_example.rb +++ b/spec/support/features/discussion_comments_shared_example.rb @@ -176,7 +176,7 @@ shared_examples 'thread comments' do |resource_name| if resource_name == 'merge request' let(:note_id) { find("#{comments_selector} .note:first-child", match: :first)['data-note-id'] } - let(:reply_id) { find("#{comments_selector} .note:last-child", match: :first)['data-note-id'] } + let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] } it 'can be replied to after resolving' do click_button "Resolve thread" diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb index ebfabcd8f24..933ed22b5d0 100644 --- a/spec/support/matchers/gitaly_matchers.rb +++ b/spec/support/matchers/gitaly_matchers.rb @@ -9,6 +9,6 @@ end RSpec::Matchers.define :gitaly_request_with_params do |params| match do |actual| - params.reduce(true) { |r, (key, val)| r && actual.send(key) == val } + params.reduce(true) { |r, (key, val)| r && actual[key.to_s] == val } end end diff --git a/spec/support/shared_examples/models/services_fields_shared_examples.rb b/spec/support/shared_examples/models/services_fields_shared_examples.rb new file mode 100644 index 00000000000..6fbd0da9383 --- /dev/null +++ b/spec/support/shared_examples/models/services_fields_shared_examples.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +shared_examples 'issue tracker fields' do + let(:title) { 'custom title' } + let(:description) { 'custom description' } + let(:url) { 'http://issue_tracker.example.com' } + + context 'when data are stored in the properties' do + describe '#update' do + before do + service.update(title: 'new_title', description: 'new description') + end + + it 'removes title and description from properties' do + expect(service.reload.properties).not_to include('title', 'description') + end + + it 'stores title & description in services table' do + expect(service.read_attribute(:title)).to eq('new_title') + expect(service.read_attribute(:description)).to eq('new description') + end + end + + describe 'reading fields' do + it 'returns correct values' do + expect(service.title).to eq(title) + expect(service.description).to eq(description) + end + end + end +end diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb index 1b09c3dd636..aad63982e7a 100644 --- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb +++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb @@ -25,16 +25,36 @@ shared_examples_for 'UpdateProjectStatistics' do .to change { reload_stat } .by(delta) end + + it 'schedules a namespace statistics worker' do + expect(Namespaces::ScheduleAggregationWorker) + .to receive(:perform_async).once + + subject.save! + end + + context 'when feature flag is disabled for the namespace' do + it 'does not schedules a namespace statistics worker' do + namespace = subject.project.root_ancestor + + stub_feature_flags(update_statistics_namespace: false, namespace: namespace) + + expect(Namespaces::ScheduleAggregationWorker) + .not_to receive(:perform_async) + + subject.save! + end + end end context 'when updating' do + let(:delta) { 42 } + before do subject.save! end it 'updates project statistics' do - delta = 42 - expect(ProjectStatistics) .to receive(:increment_statistic) .and_call_original @@ -45,6 +65,42 @@ shared_examples_for 'UpdateProjectStatistics' do .to change { reload_stat } .by(delta) end + + it 'schedules a namespace statistics worker' do + expect(Namespaces::ScheduleAggregationWorker) + .to receive(:perform_async).once + + subject.write_attribute(statistic_attribute, read_attribute + delta) + subject.save! + end + + it 'avoids N + 1 queries' do + subject.write_attribute(statistic_attribute, read_attribute + delta) + + control_count = ActiveRecord::QueryRecorder.new do + subject.save! + end + + subject.write_attribute(statistic_attribute, read_attribute + delta) + + expect do + subject.save! + end.not_to exceed_query_limit(control_count) + end + + context 'when the feature flag is disabled for the namespace' do + it 'does not schedule a namespace statistics worker' do + namespace = subject.project.root_ancestor + + stub_feature_flags(update_statistics_namespace: false, namespace: namespace) + + expect(Namespaces::ScheduleAggregationWorker) + .not_to receive(:perform_async) + + subject.write_attribute(statistic_attribute, read_attribute + delta) + subject.save! + end + end end context 'when destroying' do @@ -59,11 +115,18 @@ shared_examples_for 'UpdateProjectStatistics' do .to receive(:increment_statistic) .and_call_original - expect { subject.destroy } + expect { subject.destroy! } .to change { reload_stat } .by(delta) end + it 'schedules a namespace statistics worker' do + expect(Namespaces::ScheduleAggregationWorker) + .to receive(:perform_async).once + + subject.destroy! + end + context 'when it is destroyed from the project level' do it 'does not update the project statistics' do expect(ProjectStatistics) @@ -72,6 +135,27 @@ shared_examples_for 'UpdateProjectStatistics' do project.update(pending_delete: true) project.destroy! end + + it 'does not schedule a namespace statistics worker' do + expect(Namespaces::ScheduleAggregationWorker) + .not_to receive(:perform_async) + + project.update(pending_delete: true) + project.destroy! + end + end + + context 'when feature flag is disabled for the namespace' do + it 'does not schedule a namespace statistics worker' do + namespace = subject.project.root_ancestor + + stub_feature_flags(update_statistics_namespace: false, namespace: namespace) + + expect(Namespaces::ScheduleAggregationWorker) + .not_to receive(:perform_async) + + subject.destroy! + end end end end diff --git a/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb b/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb new file mode 100644 index 00000000000..b069b080531 --- /dev/null +++ b/spec/workers/namespaces/prune_aggregation_schedules_worker_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Namespaces::PruneAggregationSchedulesWorker, '#perform', :clean_gitlab_redis_shared_state do + include ExclusiveLeaseHelpers + + let(:namespaces) { create_list(:namespace, 5, :with_aggregation_schedule) } + let(:timeout) { Namespace::AggregationSchedule::DEFAULT_LEASE_TIMEOUT } + + subject(:worker) { described_class.new } + + before do + allow(Namespaces::RootStatisticsWorker) + .to receive(:perform_async).and_return(nil) + + allow(Namespaces::RootStatisticsWorker) + .to receive(:perform_in).and_return(nil) + + namespaces.each do |namespace| + lease_key = "namespace:namespaces_root_statistics:#{namespace.id}" + stub_exclusive_lease(lease_key, timeout: timeout) + end + end + + it 'schedules a worker per pending aggregation' do + expect(Namespaces::RootStatisticsWorker) + .to receive(:perform_async).exactly(5).times + + expect(Namespaces::RootStatisticsWorker) + .to receive(:perform_in).exactly(5).times + + worker.perform + end +end diff --git a/spec/workers/namespaces/root_statistics_worker_spec.rb b/spec/workers/namespaces/root_statistics_worker_spec.rb new file mode 100644 index 00000000000..8dd74b96d49 --- /dev/null +++ b/spec/workers/namespaces/root_statistics_worker_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Namespaces::RootStatisticsWorker, '#perform' do + let(:group) { create(:group, :with_aggregation_schedule) } + + subject(:worker) { described_class.new } + + context 'with a namespace' do + it 'executes refresher service' do + expect_any_instance_of(Namespaces::StatisticsRefresherService) + .to receive(:execute) + + worker.perform(group.id) + end + + it 'deletes namespace aggregated schedule row' do + worker.perform(group.id) + + expect(group.reload.aggregation_schedule).to be_nil + end + + context 'when something goes wrong when updating' do + before do + allow_any_instance_of(Namespaces::StatisticsRefresherService) + .to receive(:execute) + .and_raise(Namespaces::StatisticsRefresherService::RefresherError, 'error') + end + + it 'does not delete the aggregation schedule' do + worker.perform(group.id) + + expect(group.reload.aggregation_schedule).to be_present + end + + it 'logs the error' do + # A Namespace::RootStatisticsWorker is scheduled when + # a Namespace::AggregationSchedule is created, so having + # create(:group, :with_aggregation_schedule), will execute + # another worker + allow_any_instance_of(Namespace::AggregationSchedule) + .to receive(:schedule_root_storage_statistics).and_return(nil) + + expect(Gitlab::SidekiqLogger).to receive(:error).once + + worker.perform(group.id) + end + end + end + + context 'with no namespace' do + before do + group.destroy + end + + it 'does not execute the refresher service' do + expect_any_instance_of(Namespaces::StatisticsRefresherService) + .not_to receive(:execute) + + worker.perform(group.id) + end + end + + context 'with a namespace with no aggregation scheduled' do + before do + group.aggregation_schedule.destroy + end + + it 'does not execute the refresher service' do + expect_any_instance_of(Namespaces::StatisticsRefresherService) + .not_to receive(:execute) + + worker.perform(group.id) + end + end + + context 'when update_statistics_namespace is off' do + it 'does not create a new one' do + stub_feature_flags(update_statistics_namespace: false, namespace: group) + + expect_any_instance_of(Namespaces::StatisticsRefresherService) + .not_to receive(:execute) + + worker.perform(group.id) + end + end +end diff --git a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb new file mode 100644 index 00000000000..7432ca12f2a --- /dev/null +++ b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Namespaces::ScheduleAggregationWorker, '#perform' do + let(:group) { create(:group) } + + subject(:worker) { described_class.new } + + context 'when group is the root ancestor' do + context 'when aggregation schedule exists' do + it 'does not create a new one' do + Namespace::AggregationSchedule.safe_find_or_create_by!(namespace_id: group.id) + + expect do + worker.perform(group.id) + end.not_to change(Namespace::AggregationSchedule, :count) + end + end + + context 'when update_statistics_namespace is off' do + it 'does not create a new one' do + stub_feature_flags(update_statistics_namespace: false, namespace: group) + + expect do + worker.perform(group.id) + end.not_to change(Namespace::AggregationSchedule, :count) + end + end + + context 'when aggregation schedule does not exist' do + it 'creates one' do + allow_any_instance_of(Namespace::AggregationSchedule) + .to receive(:schedule_root_storage_statistics).and_return(nil) + + expect do + worker.perform(group.id) + end.to change(Namespace::AggregationSchedule, :count).by(1) + + expect(group.aggregation_schedule).to be_present + end + end + end + + context 'when group is not the root ancestor' do + let(:parent_group) { create(:group) } + let(:group) { create(:group, parent: parent_group) } + + it 'creates an aggregation schedule for the root' do + allow_any_instance_of(Namespace::AggregationSchedule) + .to receive(:schedule_root_storage_statistics).and_return(nil) + + worker.perform(group.id) + + expect(parent_group.aggregation_schedule).to be_present + end + end + + context 'when namespace does not exist' do + it 'logs the error' do + expect(Gitlab::SidekiqLogger).to receive(:error).once + + worker.perform(12345) + end + end +end |