summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/internal_ids.rb7
-rw-r--r--spec/features/profiles/user_visits_notifications_tab_spec.rb2
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb25
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb1
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin343092 -> 341299 bytes
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb12
-rw-r--r--spec/features/projects/show_project_spec.rb22
-rw-r--r--spec/helpers/import_helper_spec.rb6
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js8
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb99
-rw-r--r--spec/lib/gitlab/database_spec.rb23
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb12
-rw-r--r--spec/lib/gitlab/kubernetes/namespace_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/command_spec.rb5
-rw-r--r--spec/lib/gitlab/slash_commands/issue_move_spec.rb117
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb26
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb62
-rw-r--r--spec/models/ci/pipeline_spec.rb4
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb2
-rw-r--r--spec/models/concerns/issuable_spec.rb2
-rw-r--r--spec/models/internal_id_spec.rb106
-rw-r--r--spec/models/issue_spec.rb8
-rw-r--r--spec/models/merge_request_spec.rb4
-rw-r--r--spec/models/project_auto_devops_spec.rb8
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb2
-rw-r--r--spec/requests/api/project_export_spec.rb11
-rw-r--r--spec/services/clusters/applications/install_service_spec.rb2
-rw-r--r--spec/services/files/create_service_spec.rb4
-rw-r--r--spec/services/files/multi_service_spec.rb72
-rw-r--r--spec/services/lfs/file_transformer_spec.rb97
-rw-r--r--spec/services/merge_requests/merge_request_diff_cache_service_spec.rb36
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_spec.rb40
-rw-r--r--spec/views/projects/diffs/_stats.html.haml_spec.rb56
40 files changed, 915 insertions, 60 deletions
diff --git a/spec/factories/internal_ids.rb b/spec/factories/internal_ids.rb
new file mode 100644
index 00000000000..fbde07a391a
--- /dev/null
+++ b/spec/factories/internal_ids.rb
@@ -0,0 +1,7 @@
+FactoryBot.define do
+ factory :internal_id do
+ project
+ usage :issues
+ last_value { project.issues.maximum(:iid) || 0 }
+ end
+end
diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb
index 1952fdae798..95953fbcfac 100644
--- a/spec/features/profiles/user_visits_notifications_tab_spec.rb
+++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb
@@ -16,6 +16,6 @@ feature 'User visits the notifications tab', :js do
first('#notifications-button').click
click_link('On mention')
- expect(page).to have_content('On mention')
+ expect(page).to have_selector('#notifications-button', text: 'On mention')
end
end
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index 88813d9b5ff..ac82f869f0f 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -509,4 +509,29 @@ feature 'File blob', :js do
end
end
end
+
+ context 'realtime pipelines' do
+ before do
+ Files::CreateService.new(
+ project,
+ project.creator,
+ start_branch: 'feature',
+ branch_name: 'feature',
+ commit_message: "Add ruby file",
+ file_path: 'files/ruby/test.rb',
+ file_content: "# Awesome content"
+ ).execute
+
+ create(:ci_pipeline, status: 'running', project: project, ref: 'feature', sha: project.commit('feature').sha)
+ visit_blob('files/ruby/test.rb', ref: 'feature')
+ end
+
+ it 'should show the realtime pipeline status' do
+ page.within('.commit-actions') do
+ expect(page).to have_css('.ci-status-icon')
+ expect(page).to have_css('.ci-status-icon-running')
+ expect(page).to have_css('.js-ci-status-icon-running')
+ end
+ end
+ end
end
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index e8bb9c6a86c..b25f5161748 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -41,6 +41,7 @@ feature 'Import/Export - project import integration test', :js do
project = Project.last
expect(project).not_to be_nil
+ expect(project.description).to eq("Foo Bar")
expect(project.issues).not_to be_empty
expect(project.merge_requests).not_to be_empty
expect(project_hook_exists?(project)).to be true
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 0cc68aff494..ecb7651acad 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 33ad59abfdf..0e81c6c629a 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -349,6 +349,18 @@ describe 'Pipelines', :js do
it { expect(page).not_to have_selector('.build-artifacts') }
end
+
+ context 'with trace artifact' do
+ before do
+ create(:ci_build, :success, :trace_artifact, pipeline: pipeline)
+
+ visit_project_pipelines
+ end
+
+ it 'does not show trace artifact as artifacts' do
+ expect(page).not_to have_selector('.build-artifacts')
+ end
+ end
end
context 'mini pipeline graph' do
diff --git a/spec/features/projects/show_project_spec.rb b/spec/features/projects/show_project_spec.rb
index 0a014e9f080..e4f13e6cab7 100644
--- a/spec/features/projects/show_project_spec.rb
+++ b/spec/features/projects/show_project_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Project show page', :feature do
+ include DropzoneHelper
+
context 'when project pending delete' do
let(:project) { create(:project, :empty_repo, pending_delete: true) }
@@ -334,4 +336,24 @@ describe 'Project show page', :feature do
end
end
end
+
+ describe 'dropzone', :js do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ before do
+ project.add_master(user)
+ sign_in(user)
+
+ visit project_path(project)
+ end
+
+ it 'can upload files' do
+ find('.add-to-tree').click
+ click_link 'Upload file'
+ drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
+
+ expect(find('.dz-filename')).to have_content('doc_sample.txt')
+ end
+ end
end
diff --git a/spec/helpers/import_helper_spec.rb b/spec/helpers/import_helper_spec.rb
index 57d843c1be2..033155617c6 100644
--- a/spec/helpers/import_helper_spec.rb
+++ b/spec/helpers/import_helper_spec.rb
@@ -28,12 +28,10 @@ describe ImportHelper do
describe '#provider_project_link' do
context 'when provider is "github"' do
let(:github_server_url) { nil }
+ let(:provider) { OpenStruct.new(name: 'github', url: github_server_url) }
before do
- setting = Settingslogic.new('name' => 'github')
- setting['url'] = github_server_url if github_server_url
-
- allow(Gitlab.config.omniauth).to receive(:providers).and_return([setting])
+ stub_omniauth_setting(providers: [provider])
end
context 'when provider does not specify a custom URL' do
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
index fe87f110354..046968fbc1f 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_unresolved_discussions_spec.js
@@ -1,10 +1,10 @@
import Vue from 'vue';
-import unresolvedDiscussionsComponent from '~/vue_merge_request_widget/components/states/mr_widget_unresolved_discussions';
+import UnresolvedDiscussions from '~/vue_merge_request_widget/components/states/unresolved_discussions.vue';
-describe('MRWidgetUnresolvedDiscussions', () => {
+describe('UnresolvedDiscussions', () => {
describe('props', () => {
it('should have props', () => {
- const { mr } = unresolvedDiscussionsComponent.props;
+ const { mr } = UnresolvedDiscussions.props;
expect(mr.type instanceof Object).toBeTruthy();
expect(mr.required).toBeTruthy();
@@ -17,7 +17,7 @@ describe('MRWidgetUnresolvedDiscussions', () => {
const path = 'foo/bar';
beforeEach(() => {
- const Component = Vue.extend(unresolvedDiscussionsComponent);
+ const Component = Vue.extend(UnresolvedDiscussions);
const mr = {
createIssueToResolveDiscussionsPath: path,
};
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 3ca4652f7cc..ba8dc68ceda 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -217,6 +217,23 @@ describe Banzai::Filter::RelativeLinkFilter do
end
end
+ context 'when ref name contains special chars' do
+ let(:ref) {'mark#\'@],+;-._/#@!$&()+down'}
+
+ it 'correctly escapes the ref' do
+ # Adressable won't escape the '#', so we do this manually
+ ref_escaped = 'mark%23\'@%5D,+;-._/%23@!$&()+down'
+
+ # Stub this method so the branch doesn't actually need to be in the repo
+ allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw)
+
+ doc = filter(link('files/images/logo-black.png'))
+
+ expect(doc.at_css('a')['href'])
+ .to eq "/#{project_path}/raw/#{ref_escaped}/files/images/logo-black.png"
+ end
+ end
+
context 'when requested path is a directory with space in the repo' do
let(:ref) { 'master' }
let(:commit) { project.commit('38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e') }
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
new file mode 100644
index 00000000000..cc1257484d2
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Variables::Collection::Item do
+ let(:variable) do
+ { key: 'VAR', value: 'something', public: true }
+ end
+
+ describe '.fabricate' do
+ it 'supports using a hash' do
+ resource = described_class.fabricate(variable)
+
+ expect(resource).to be_a(described_class)
+ expect(resource).to eq variable
+ end
+
+ it 'supports using an active record resource' do
+ variable = create(:ci_variable, key: 'CI_VAR', value: '123')
+ resource = described_class.fabricate(variable)
+
+ expect(resource).to be_a(described_class)
+ expect(resource).to eq(key: 'CI_VAR', value: '123', public: false)
+ end
+
+ it 'supports using another collection item' do
+ item = described_class.new(**variable)
+
+ resource = described_class.fabricate(item)
+
+ expect(resource).to be_a(described_class)
+ expect(resource).to eq variable
+ expect(resource.object_id).not_to eq item.object_id
+ end
+ end
+
+ describe '#==' do
+ it 'compares a hash representation of a variable' do
+ expect(described_class.new(**variable) == variable).to be true
+ end
+ end
+
+ describe '#[]' do
+ it 'behaves like a hash accessor' do
+ item = described_class.new(**variable)
+
+ expect(item[:key]).to eq 'VAR'
+ end
+ end
+
+ describe '#to_hash' do
+ it 'returns a hash representation of a collection item' do
+ expect(described_class.new(**variable).to_hash).to eq variable
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
new file mode 100644
index 00000000000..90b6e178242
--- /dev/null
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -0,0 +1,99 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Variables::Collection do
+ describe '.new' do
+ it 'can be initialized with an array' do
+ variable = { key: 'VAR', value: 'value', public: true }
+
+ collection = described_class.new([variable])
+
+ expect(collection.first.to_hash).to eq variable
+ end
+
+ it 'can be initialized without an argument' do
+ expect(subject).to be_none
+ end
+ end
+
+ describe '#append' do
+ it 'appends a hash' do
+ subject.append(key: 'VARIABLE', value: 'something')
+
+ expect(subject).to be_one
+ end
+
+ it 'appends a Ci::Variable' do
+ subject.append(build(:ci_variable))
+
+ expect(subject).to be_one
+ end
+
+ it 'appends an internal resource' do
+ collection = described_class.new([{ key: 'TEST', value: 1 }])
+
+ subject.append(collection.first)
+
+ expect(subject).to be_one
+ end
+
+ it 'returns self' do
+ expect(subject.append(key: 'VAR', value: 'test'))
+ .to eq subject
+ end
+ end
+
+ describe '#concat' do
+ it 'appends all elements from an array' do
+ collection = described_class.new([{ key: 'VAR_1', value: '1' }])
+ variables = [{ key: 'VAR_2', value: '2' }, { key: 'VAR_3', value: '3' }]
+
+ collection.concat(variables)
+
+ expect(collection).to include(key: 'VAR_1', value: '1', public: true)
+ expect(collection).to include(key: 'VAR_2', value: '2', public: true)
+ expect(collection).to include(key: 'VAR_3', value: '3', public: true)
+ end
+
+ it 'appends all elements from other collection' do
+ collection = described_class.new([{ key: 'VAR_1', value: '1' }])
+ additional = described_class.new([{ key: 'VAR_2', value: '2' },
+ { key: 'VAR_3', value: '3' }])
+
+ collection.concat(additional)
+
+ expect(collection).to include(key: 'VAR_1', value: '1', public: true)
+ expect(collection).to include(key: 'VAR_2', value: '2', public: true)
+ expect(collection).to include(key: 'VAR_3', value: '3', public: true)
+ end
+
+ it 'returns self' do
+ expect(subject.concat([key: 'VAR', value: 'test']))
+ .to eq subject
+ end
+ end
+
+ describe '#+' do
+ it 'makes it possible to combine with an array' do
+ collection = described_class.new([{ key: 'TEST', value: 1 }])
+ variables = [{ key: 'TEST', value: 'something' }]
+
+ expect((collection + variables).count).to eq 2
+ end
+
+ it 'makes it possible to combine with another collection' do
+ collection = described_class.new([{ key: 'TEST', value: 1 }])
+ other = described_class.new([{ key: 'TEST', value: 2 }])
+
+ expect((collection + other).count).to eq 2
+ end
+ end
+
+ describe '#to_runner_variables' do
+ it 'creates an array of hashes in a runner-compatible format' do
+ collection = described_class.new([{ key: 'TEST', value: 1 }])
+
+ expect(collection.to_runner_variables)
+ .to eq [{ key: 'TEST', value: 1, public: true }]
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index b2f13fae73f..1fe1d3926ad 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -287,6 +287,29 @@ describe Gitlab::Database do
end
end
+ describe '.cached_column_exists?' do
+ it 'only retrieves data once' do
+ expect(ActiveRecord::Base.connection).to receive(:columns).once.and_call_original
+
+ 2.times do
+ expect(described_class.cached_column_exists?(:projects, :id)).to be_truthy
+ expect(described_class.cached_column_exists?(:projects, :bogus_column)).to be_falsey
+ end
+ end
+ end
+
+ describe '.cached_table_exists?' do
+ it 'only retrieves data once per table' do
+ expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:projects).once.and_call_original
+ expect(ActiveRecord::Base.connection).to receive(:table_exists?).with(:bogus_table_name).once.and_call_original
+
+ 2.times do
+ expect(described_class.cached_table_exists?(:projects)).to be_truthy
+ expect(described_class.cached_table_exists?(:bogus_table_name)).to be_falsey
+ end
+ end
+ end
+
describe '#true_value' do
it 'returns correct value for PostgreSQL' do
expect(described_class).to receive(:postgresql?).and_return(true)
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index a067c42b75b..f48ee8924e8 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Diff::FileCollection::MergeRequestDiff do
diff_files
end
- it 'does not files marked as undiffable in .gitattributes' do
+ it 'does not highlight files marked as undiffable in .gitattributes' do
allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(false)
expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines)
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 9204ea37963..0c2e18c268a 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -455,5 +455,17 @@ describe Gitlab::Diff::File do
expect(diff_file.size).to be_zero
end
end
+
+ describe '#different_type?' do
+ it 'returns false' do
+ expect(diff_file).not_to be_different_type
+ end
+ end
+
+ describe '#content_changed?' do
+ it 'returns false' do
+ expect(diff_file).not_to be_content_changed
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index bece82e531a..a204a8f1ffe 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -279,6 +279,7 @@ project:
- lfs_file_locks
- project_badges
- source_of_merge_requests
+- internal_ids
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 13e930bafe3..8e25cd26c2f 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -42,6 +42,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED)
end
+ it 'has the project description' do
+ expect(Project.find_by_path('project').description).to eq('Nisi et repellendus ut enim quo accusamus vel magnam.')
+ end
+
it 'has the project html description' do
expect(Project.find_by_path('project').description_html).to eq('description')
end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 3049491f0ae..0d20a551e2a 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -29,8 +29,17 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
project_json(project_tree_saver.full_path)
end
+ context 'with description override' do
+ let(:params) { { description: 'Foo Bar' } }
+ let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared, params: params) }
+
+ it 'overrides the project description' do
+ expect(saved_project_json).to include({ 'description' => params[:description] })
+ end
+ end
+
it 'saves the correct json' do
- expect(saved_project_json).to include({ "visibility_level" => 20 })
+ expect(saved_project_json).to include({ 'description' => 'description', 'visibility_level' => 20 })
end
it 'has milestones' do
@@ -259,6 +268,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
:issues_disabled,
:wiki_enabled,
:builds_private,
+ description: 'description',
issues: [issue],
snippets: [snippet],
releases: [release],
diff --git a/spec/lib/gitlab/kubernetes/namespace_spec.rb b/spec/lib/gitlab/kubernetes/namespace_spec.rb
index b3c987f9344..e098612f6fb 100644
--- a/spec/lib/gitlab/kubernetes/namespace_spec.rb
+++ b/spec/lib/gitlab/kubernetes/namespace_spec.rb
@@ -9,7 +9,7 @@ describe Gitlab::Kubernetes::Namespace do
describe '#exists?' do
context 'when namespace do not exits' do
- let(:exception) { ::KubeException.new(404, "namespace #{name} not found", nil) }
+ let(:exception) { ::Kubeclient::HttpError.new(404, "namespace #{name} not found", nil) }
it 'returns false' do
expect(client).to receive(:get_namespace).with(name).once.and_raise(exception)
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index e3447d974aa..194cae8c645 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -108,5 +108,10 @@ describe Gitlab::SlashCommands::Command do
it { is_expected.to eq(Gitlab::SlashCommands::IssueSearch) }
end
+
+ context 'IssueMove is triggered' do
+ let(:params) { { text: 'issue move #78291 to gitlab/gitlab-ci' } }
+ it { is_expected.to eq(Gitlab::SlashCommands::IssueMove) }
+ end
end
end
diff --git a/spec/lib/gitlab/slash_commands/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
new file mode 100644
index 00000000000..d41441c9472
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::IssueMove, service: true do
+ describe '#match' do
+ shared_examples_for 'move command' do |text_command|
+ it 'can be parsed to extract the needed fields' do
+ match_data = described_class.match(text_command)
+
+ expect(match_data['iid']).to eq('123456')
+ expect(match_data['project_path']).to eq('gitlab/gitlab-ci')
+ end
+ end
+
+ it_behaves_like 'move command', 'issue move #123456 to gitlab/gitlab-ci'
+ it_behaves_like 'move command', 'issue move #123456 gitlab/gitlab-ci'
+ it_behaves_like 'move command', 'issue move #123456 gitlab/gitlab-ci '
+ it_behaves_like 'move command', 'issue move 123456 to gitlab/gitlab-ci'
+ it_behaves_like 'move command', 'issue move 123456 gitlab/gitlab-ci'
+ it_behaves_like 'move command', 'issue move 123456 gitlab/gitlab-ci '
+ end
+
+ describe '#execute' do
+ set(:user) { create(:user) }
+ set(:issue) { create(:issue) }
+ set(:chat_name) { create(:chat_name, user: user) }
+ set(:project) { issue.project }
+ set(:other_project) { create(:project, namespace: project.namespace) }
+
+ before do
+ [project, other_project].each { |prj| prj.add_master(user) }
+ end
+
+ subject { described_class.new(project, chat_name) }
+
+ def process_message(message)
+ subject.execute(described_class.match(message))
+ end
+
+ context 'when the user can move the issue' do
+ context 'when the move fails' do
+ it 'returns the error message' do
+ message = "issue move #{issue.iid} #{project.full_path}"
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('Cannot move issue'))
+ end
+ end
+
+ context 'when the move succeeds' do
+ let(:message) { "issue move #{issue.iid} #{other_project.full_path}" }
+
+ it 'moves the issue to the new destination' do
+ expect { process_message(message) }.to change { Issue.count }.by(1)
+
+ new_issue = issue.reload.moved_to
+
+ expect(new_issue.state).to eq('opened')
+ expect(new_issue.project_id).to eq(other_project.id)
+ expect(new_issue.author_id).to eq(issue.author_id)
+
+ expect(issue.state).to eq('closed')
+ expect(issue.project_id).to eq(project.id)
+ end
+
+ it 'returns the new issue' do
+ expect(process_message(message))
+ .to include(response_type: :in_channel,
+ attachments: [a_hash_including(title_link: a_string_including(other_project.full_path))])
+ end
+
+ it 'mentions the old issue' do
+ expect(process_message(message))
+ .to include(attachments: [a_hash_including(pretext: a_string_including(project.full_path))])
+ end
+ end
+ end
+
+ context 'when the issue does not exist' do
+ it 'returns not found' do
+ message = "issue move #{issue.iid.succ} #{other_project.full_path}"
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('not found'))
+ end
+ end
+
+ context 'when the target project does not exist' do
+ it 'returns not found' do
+ message = "issue move #{issue.iid} #{other_project.full_path}/foo"
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('not found'))
+ end
+ end
+
+ context 'when the user cannot see the target project' do
+ it 'returns not found' do
+ message = "issue move #{issue.iid} #{other_project.full_path}"
+ other_project.team.truncate
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('not found'))
+ end
+ end
+
+ context 'when the user does not have the required permissions on the target project' do
+ it 'returns the error message' do
+ message = "issue move #{issue.iid} #{other_project.full_path}"
+ other_project.team.truncate
+ other_project.team.add_guest(user)
+
+ expect(process_message(message)).to include(response_type: :ephemeral,
+ text: a_string_matching('Cannot move issue'))
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
new file mode 100644
index 00000000000..58c341a284e
--- /dev/null
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::SlashCommands::Presenters::IssueMove do
+ set(:admin) { create(:admin) }
+ set(:project) { create(:project) }
+ set(:other_project) { create(:project) }
+ set(:old_issue) { create(:issue, project: project) }
+ set(:new_issue) { Issues::MoveService.new(project, admin).execute(old_issue, other_project) }
+ let(:attachment) { subject[:attachments].first }
+
+ subject { described_class.new(new_issue).present(old_issue) }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'shows the new issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject).to have_key(:attachments)
+ expect(attachment[:title]).to start_with(new_issue.title)
+ expect(attachment[:title_link]).to include(other_project.full_path)
+ end
+
+ it 'mentions the old issue and the new issue in the pretext' do
+ expect(attachment[:pretext]).to include(project.full_path)
+ expect(attachment[:pretext]).to include(other_project.full_path)
+ end
+end
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index 92eb1d9ce86..638b2853374 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -66,7 +66,7 @@ describe MigrateOldArtifacts do
end
it 'all files do have artifacts' do
- Ci::Build.with_artifacts do |build|
+ Ci::Build.with_artifacts_archive do |build|
expect(build).to have_artifacts
end
end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 6e202de0db9..9da3de7a828 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -80,6 +80,42 @@ describe Ci::Build do
end
end
+ describe '.with_artifacts_archive' do
+ subject { described_class.with_artifacts_archive }
+
+ context 'when job does not have an archive' do
+ let!(:job) { create(:ci_build) }
+
+ it 'does not return the job' do
+ is_expected.not_to include(job)
+ end
+ end
+
+ context 'when job has a legacy archive' do
+ let!(:job) { create(:ci_build, :legacy_artifacts) }
+
+ it 'returns the job' do
+ is_expected.to include(job)
+ end
+ end
+
+ context 'when job has a job artifact archive' do
+ let!(:job) { create(:ci_build, :artifacts) }
+
+ it 'returns the job' do
+ is_expected.to include(job)
+ end
+ end
+
+ context 'when job has a job artifact trace' do
+ let!(:job) { create(:ci_build, :trace_artifact) }
+
+ it 'does not return the job' do
+ is_expected.not_to include(job)
+ end
+ end
+ end
+
describe '#actionize' do
context 'when build is a created' do
before do
@@ -679,21 +715,21 @@ describe Ci::Build do
describe '#erase' do
before do
- build.erase(erased_by: user)
+ build.erase(erased_by: erased_by)
end
context 'erased by user' do
- let!(:user) { create(:user, username: 'eraser') }
+ let!(:erased_by) { create(:user, username: 'eraser') }
include_examples 'erasable'
it 'records user who erased a build' do
- expect(build.erased_by).to eq user
+ expect(build.erased_by).to eq erased_by
end
end
context 'erased by system' do
- let(:user) { nil }
+ let(:erased_by) { nil }
include_examples 'erasable'
@@ -748,21 +784,21 @@ describe Ci::Build do
describe '#erase' do
before do
- build.erase(erased_by: user)
+ build.erase(erased_by: erased_by)
end
context 'erased by user' do
- let!(:user) { create(:user, username: 'eraser') }
+ let!(:erased_by) { create(:user, username: 'eraser') }
include_examples 'erasable'
it 'records user who erased a build' do
- expect(build.erased_by).to eq user
+ expect(build.erased_by).to eq erased_by
end
end
context 'erased by system' do
- let(:user) { nil }
+ let(:erased_by) { nil }
include_examples 'erasable'
@@ -1885,10 +1921,10 @@ describe Ci::Build do
describe 'variables ordering' do
context 'when variables hierarchy is stubbed' do
- let(:build_pre_var) { { key: 'build', value: 'value' } }
- let(:project_pre_var) { { key: 'project', value: 'value' } }
- let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } }
- let(:build_yaml_var) { { key: 'yaml', value: 'value' } }
+ let(:build_pre_var) { { key: 'build', value: 'value', public: true } }
+ let(:project_pre_var) { { key: 'project', value: 'value', public: true } }
+ let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true } }
+ let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true } }
before do
allow(build).to receive(:predefined_variables) { [build_pre_var] }
@@ -1958,7 +1994,7 @@ describe Ci::Build do
context 'when depended job has not been completed yet' do
let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) }
- it { expect { job.run! }.not_to raise_error(Ci::Build::MissingDependenciesError) }
+ it { expect { job.run! }.not_to raise_error }
end
context 'when artifacts of depended job has been expired' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 86bb2fefae1..4635f8cfe9d 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -170,10 +170,8 @@ describe Ci::Pipeline, :mailer do
describe '#predefined_variables' do
subject { pipeline.predefined_variables }
- it { is_expected.to be_an(Array) }
-
it 'includes all predefined variables in a valid order' do
- keys = subject.map { |variable| variable.fetch(:key) }
+ keys = subject.map { |variable| variable[:key] }
expect(keys).to eq %w[CI_PIPELINE_ID CI_CONFIG_PATH CI_PIPELINE_SOURCE]
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 53a4e545ff6..add481b8096 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -252,7 +252,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
stub_kubeclient_pods(status: 500)
end
- it { expect { subject }.to raise_error(KubeException) }
+ it { expect { subject }.to raise_error(Kubeclient::HttpError) }
end
context 'when kubernetes responds with 404s' do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 4b217df2e8f..f8874d14e3f 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -34,7 +34,7 @@ describe Issuable do
subject { build(:issue) }
before do
- allow(subject).to receive(:set_iid).and_return(false)
+ allow(InternalId).to receive(:generate_next).and_return(nil)
end
it { is_expected.to validate_presence_of(:project) }
diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb
new file mode 100644
index 00000000000..581fd0293cc
--- /dev/null
+++ b/spec/models/internal_id_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper'
+
+describe InternalId do
+ let(:project) { create(:project) }
+ let(:usage) { :issues }
+ let(:issue) { build(:issue, project: project) }
+ let(:scope) { { project: project } }
+ let(:init) { ->(s) { s.project.issues.size } }
+
+ context 'validations' do
+ it { is_expected.to validate_presence_of(:usage) }
+ end
+
+ describe '.generate_next' do
+ subject { described_class.generate_next(issue, scope, usage, init) }
+
+ context 'in the absence of a record' do
+ it 'creates a record if not yet present' do
+ expect { subject }.to change { described_class.count }.from(0).to(1)
+ end
+
+ it 'stores record attributes' do
+ subject
+
+ described_class.first.tap do |record|
+ expect(record.project).to eq(project)
+ expect(record.usage).to eq(usage.to_s)
+ end
+ end
+
+ context 'with existing issues' do
+ before do
+ rand(1..10).times { create(:issue, project: project) }
+ described_class.delete_all
+ end
+
+ it 'calculates last_value values automatically' do
+ expect(subject).to eq(project.issues.size + 1)
+ end
+ end
+
+ context 'with concurrent inserts on table' do
+ it 'looks up the record if it was created concurrently' do
+ args = { **scope, usage: described_class.usages[usage.to_s] }
+ record = double
+ expect(described_class).to receive(:find_by).with(args).and_return(nil) # first call, record not present
+ expect(described_class).to receive(:find_by).with(args).and_return(record) # second call, record was created by another process
+ expect(described_class).to receive(:create!).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
+ expect(record).to receive(:increment_and_save!)
+
+ subject
+ end
+ end
+ end
+
+ it 'generates a strictly monotone, gapless sequence' do
+ seq = (0..rand(100)).map do
+ described_class.generate_next(issue, scope, usage, init)
+ end
+ normalized = seq.map { |i| i - seq.min }
+
+ expect(normalized).to eq((0..seq.size - 1).to_a)
+ end
+
+ context 'with an insufficient schema version' do
+ before do
+ described_class.reset_column_information
+ expect(ActiveRecord::Migrator).to receive(:current_version).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1)
+ end
+
+ let(:init) { double('block') }
+
+ it 'calculates next internal ids on the fly' do
+ val = rand(1..100)
+
+ expect(init).to receive(:call).with(issue).and_return(val)
+ expect(subject).to eq(val + 1)
+ end
+ end
+ end
+
+ describe '#increment_and_save!' do
+ let(:id) { create(:internal_id) }
+ subject { id.increment_and_save! }
+
+ it 'returns incremented iid' do
+ value = id.last_value
+
+ expect(subject).to eq(value + 1)
+ end
+
+ it 'saves the record' do
+ subject
+
+ expect(id.changed?).to be_falsey
+ end
+
+ context 'with last_value=nil' do
+ let(:id) { build(:internal_id, last_value: nil) }
+
+ it 'returns 1' do
+ expect(subject).to eq(1)
+ end
+ end
+ end
+end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index feed7968f09..11154291368 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -9,11 +9,17 @@ describe Issue do
describe 'modules' do
subject { described_class }
- it { is_expected.to include_module(InternalId) }
it { is_expected.to include_module(Issuable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
it { is_expected.to include_module(Taskable) }
+
+ it_behaves_like 'AtomicInternalId' do
+ let(:internal_id_attribute) { :iid }
+ let(:instance) { build(:issue) }
+ let(:scope_attrs) { { project: instance.project } }
+ let(:usage) { :issues }
+ end
end
subject { create(:issue) }
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 7986aa31e16..ff5a6f63010 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -17,7 +17,7 @@ describe MergeRequest do
describe 'modules' do
subject { described_class }
- it { is_expected.to include_module(InternalId) }
+ it { is_expected.to include_module(NonatomicInternalId) }
it { is_expected.to include_module(Issuable) }
it { is_expected.to include_module(Referable) }
it { is_expected.to include_module(Sortable) }
@@ -1544,7 +1544,7 @@ describe MergeRequest do
end
it "executes diff cache service" do
- expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject)
+ expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject, an_instance_of(MergeRequestDiff))
subject.reload_diff
end
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 296b91a771c..7545c0797e9 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -36,14 +36,14 @@ describe ProjectAutoDevops do
end
end
- describe '#variables' do
+ describe '#predefined_variables' do
let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) }
context 'when domain is defined' do
let(:domain) { 'example.com' }
it 'returns AUTO_DEVOPS_DOMAIN' do
- expect(auto_devops.variables).to include(domain_variable)
+ expect(auto_devops.predefined_variables).to include(domain_variable)
end
end
@@ -55,7 +55,7 @@ describe ProjectAutoDevops do
allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return('example.com')
end
- it { expect(auto_devops.variables).to include(domain_variable) }
+ it { expect(auto_devops.predefined_variables).to include(domain_variable) }
end
context 'when there is no instance domain specified' do
@@ -63,7 +63,7 @@ describe ProjectAutoDevops do
allow(Gitlab::CurrentSettings).to receive(:auto_devops_domain).and_return(nil)
end
- it { expect(auto_devops.variables).not_to include(domain_variable) }
+ it { expect(auto_devops.predefined_variables).not_to include(domain_variable) }
end
end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 622d8844a72..3be023a48c1 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -370,7 +370,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
stub_kubeclient_pods(status: 500)
end
- it { expect { subject }.to raise_error(KubeException) }
+ it { expect { subject }.to raise_error(Kubeclient::HttpError) }
end
context 'when kubernetes responds with 404s' do
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index fbed527963f..12583109b59 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -285,6 +285,17 @@ describe API::ProjectExport do
context 'when user is not a member' do
it_behaves_like 'post project export start not found'
end
+
+ context 'when overriding description' do
+ it 'starts' do
+ params = { description: "Foo" }
+
+ expect_any_instance_of(Projects::ImportExport::ExportService).to receive(:execute)
+ post api(path, project.owner), params
+
+ expect(response).to have_gitlab_http_status(202)
+ end
+ end
end
end
end
diff --git a/spec/services/clusters/applications/install_service_spec.rb b/spec/services/clusters/applications/install_service_spec.rb
index ad175226e92..93199964a0e 100644
--- a/spec/services/clusters/applications/install_service_spec.rb
+++ b/spec/services/clusters/applications/install_service_spec.rb
@@ -34,7 +34,7 @@ describe Clusters::Applications::InstallService do
context 'when k8s cluster communication fails' do
before do
- error = KubeException.new(500, 'system failure', nil)
+ error = Kubeclient::HttpError.new(500, 'system failure', nil)
expect(helm_client).to receive(:install).with(install_command).and_raise(error)
end
diff --git a/spec/services/files/create_service_spec.rb b/spec/services/files/create_service_spec.rb
index 030263b1502..abe99b9e794 100644
--- a/spec/services/files/create_service_spec.rb
+++ b/spec/services/files/create_service_spec.rb
@@ -43,7 +43,7 @@ describe Files::CreateService do
blob = repository.blob_at('lfs', file_path)
- expect(blob.data).not_to start_with('version https://git-lfs.github.com/spec/v1')
+ expect(blob.data).not_to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
expect(blob.data).to eq(file_content)
end
end
@@ -58,7 +58,7 @@ describe Files::CreateService do
blob = repository.blob_at('lfs', file_path)
- expect(blob.data).to start_with('version https://git-lfs.github.com/spec/v1')
+ expect(blob.data).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
end
it "creates an LfsObject with the file's content" do
diff --git a/spec/services/files/multi_service_spec.rb b/spec/services/files/multi_service_spec.rb
index b9971776b33..59984c10990 100644
--- a/spec/services/files/multi_service_spec.rb
+++ b/spec/services/files/multi_service_spec.rb
@@ -4,28 +4,30 @@ describe Files::MultiService do
subject { described_class.new(project, user, commit_params) }
let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
let(:user) { create(:user) }
let(:branch_name) { project.default_branch }
let(:original_file_path) { 'files/ruby/popen.rb' }
let(:new_file_path) { 'files/ruby/popen.rb' }
+ let(:file_content) { 'New content' }
let(:action) { 'update' }
let!(:original_commit_id) do
Gitlab::Git::Commit.last_for_path(project.repository, branch_name, original_file_path).sha
end
- let(:actions) do
- [
- {
- action: action,
- file_path: new_file_path,
- previous_path: original_file_path,
- content: 'New content',
- last_commit_id: original_commit_id
- }
- ]
+ let(:default_action) do
+ {
+ action: action,
+ file_path: new_file_path,
+ previous_path: original_file_path,
+ content: file_content,
+ last_commit_id: original_commit_id
+ }
end
+ let(:actions) { [default_action] }
+
let(:commit_params) do
{
commit_message: "Update File",
@@ -110,6 +112,56 @@ describe Files::MultiService do
end
end
+ context 'when creating a file matching an LFS filter' do
+ let(:action) { 'create' }
+ let(:branch_name) { 'lfs' }
+ let(:new_file_path) { 'test_file.lfs' }
+
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ end
+
+ it 'creates an LFS pointer' do
+ subject.execute
+
+ blob = repository.blob_at('lfs', new_file_path)
+
+ expect(blob.data).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+ end
+
+ it "creates an LfsObject with the file's content" do
+ subject.execute
+
+ expect(LfsObject.last.file.read).to eq file_content
+ end
+
+ context 'with base64 encoded content' do
+ let(:raw_file_content) { 'Raw content' }
+ let(:file_content) { Base64.encode64(raw_file_content) }
+ let(:actions) { [default_action.merge(encoding: 'base64')] }
+
+ it 'creates an LFS pointer' do
+ subject.execute
+
+ blob = repository.blob_at('lfs', new_file_path)
+
+ expect(blob.data).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+ end
+
+ it "creates an LfsObject with the file's content" do
+ subject.execute
+
+ expect(LfsObject.last.file.read).to eq raw_file_content
+ end
+ end
+
+ it 'links the LfsObject to the project' do
+ expect do
+ subject.execute
+ end.to change { project.lfs_objects.count }.by(1)
+ end
+ end
+
context 'when file status validation is skipped' do
let(:action) { 'create' }
let(:new_file_path) { 'files/ruby/new_file.rb' }
diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb
new file mode 100644
index 00000000000..e8938338cb7
--- /dev/null
+++ b/spec/services/lfs/file_transformer_spec.rb
@@ -0,0 +1,97 @@
+require "spec_helper"
+
+describe Lfs::FileTransformer do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+ let(:file_content) { 'Test file content' }
+ let(:branch_name) { 'lfs' }
+ let(:file_path) { 'test_file.lfs' }
+
+ subject { described_class.new(project, branch_name) }
+
+ describe '#new_file' do
+ context 'with lfs disabled' do
+ it 'skips gitattributes check' do
+ expect(repository.raw).not_to receive(:blob_at)
+
+ subject.new_file(file_path, file_content)
+ end
+
+ it 'returns untransformed content' do
+ result = subject.new_file(file_path, file_content)
+
+ expect(result.content).to eq(file_content)
+ end
+
+ it 'returns untransformed encoding' do
+ result = subject.new_file(file_path, file_content, encoding: 'base64')
+
+ expect(result.encoding).to eq('base64')
+ end
+ end
+
+ context 'with lfs enabled' do
+ before do
+ allow(project).to receive(:lfs_enabled?).and_return(true)
+ end
+
+ it 'reuses cached gitattributes' do
+ second_file = 'another_file.lfs'
+
+ expect(repository.raw).to receive(:blob_at).with(branch_name, '.gitattributes').once
+
+ subject.new_file(file_path, file_content)
+ subject.new_file(second_file, file_content)
+ end
+
+ it "creates an LfsObject with the file's content" do
+ subject.new_file(file_path, file_content)
+
+ expect(LfsObject.last.file.read).to eq file_content
+ end
+
+ it 'returns an LFS pointer' do
+ result = subject.new_file(file_path, file_content)
+
+ expect(result.content).to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+ end
+
+ it 'returns LFS pointer encoding as text' do
+ result = subject.new_file(file_path, file_content, encoding: 'base64')
+
+ expect(result.encoding).to eq('text')
+ end
+
+ context "when doesn't use LFS" do
+ let(:file_path) { 'other.filetype' }
+
+ it "doesn't create LFS pointers" do
+ new_content = subject.new_file(file_path, file_content).content
+
+ expect(new_content).not_to start_with(Gitlab::Git::LfsPointerFile::VERSION_LINE)
+ expect(new_content).to eq(file_content)
+ end
+ end
+
+ it 'links LfsObjects to project' do
+ expect do
+ subject.new_file(file_path, file_content)
+ end.to change { project.lfs_objects.count }.by(1)
+ end
+
+ context 'when LfsObject already exists' do
+ let(:lfs_pointer) { Gitlab::Git::LfsPointerFile.new(file_content) }
+
+ before do
+ create(:lfs_object, oid: lfs_pointer.sha256, size: lfs_pointer.size)
+ end
+
+ it 'links LfsObjects to project' do
+ expect do
+ subject.new_file(file_path, file_content)
+ end.to change { project.lfs_objects.count }.by(1)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
index bb46e1dd9ab..57b6165cfb0 100644
--- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
+++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb
@@ -1,19 +1,39 @@
require 'spec_helper'
-describe MergeRequests::MergeRequestDiffCacheService do
+describe MergeRequests::MergeRequestDiffCacheService, :use_clean_rails_memory_store_caching do
let(:subject) { described_class.new }
+ let(:merge_request) { create(:merge_request) }
describe '#execute' do
- it 'retrieves the diff files to cache the highlighted result' do
- merge_request = create(:merge_request)
- cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequestDiff.default_options]
-
- expect(Rails.cache).to receive(:read).with(cache_key).and_return({})
- expect(Rails.cache).to receive(:write).with(cache_key, anything)
+ before do
allow_any_instance_of(Gitlab::Diff::File).to receive(:text?).and_return(true)
allow_any_instance_of(Gitlab::Diff::File).to receive(:diffable?).and_return(true)
+ end
+
+ it 'retrieves the diff files to cache the highlighted result' do
+ new_diff = merge_request.merge_request_diff
+ cache_key = new_diff.diffs.cache_key
+
+ expect(Rails.cache).to receive(:read).with(cache_key).and_call_original
+ expect(Rails.cache).to receive(:write).with(cache_key, anything, anything).and_call_original
+
+ subject.execute(merge_request, new_diff)
+ end
+
+ it 'clears the cache for older diffs on the merge request' do
+ old_diff = merge_request.merge_request_diff
+ old_cache_key = old_diff.diffs.cache_key
+
+ subject.execute(merge_request, old_diff)
+
+ new_diff = merge_request.create_merge_request_diff
+ new_cache_key = new_diff.diffs.cache_key
+
+ expect(Rails.cache).to receive(:delete).with(old_cache_key).and_call_original
+ expect(Rails.cache).to receive(:read).with(new_cache_key).and_call_original
+ expect(Rails.cache).to receive(:write).with(new_cache_key, anything, anything).and_call_original
- subject.execute(merge_request)
+ subject.execute(merge_request, new_diff)
end
end
end
diff --git a/spec/support/shared_examples/models/atomic_internal_id_spec.rb b/spec/support/shared_examples/models/atomic_internal_id_spec.rb
new file mode 100644
index 00000000000..144af4fc475
--- /dev/null
+++ b/spec/support/shared_examples/models/atomic_internal_id_spec.rb
@@ -0,0 +1,40 @@
+require 'spec_helper'
+
+shared_examples_for 'AtomicInternalId' do
+ describe '.has_internal_id' do
+ describe 'Module inclusion' do
+ subject { described_class }
+
+ it { is_expected.to include_module(AtomicInternalId) }
+ end
+
+ describe 'Validation' do
+ subject { instance }
+
+ before do
+ allow(InternalId).to receive(:generate_next).and_return(nil)
+ end
+
+ it { is_expected.to validate_presence_of(internal_id_attribute) }
+ it { is_expected.to validate_numericality_of(internal_id_attribute) }
+ end
+
+ describe 'internal id generation' do
+ subject { instance.save! }
+
+ it 'calls InternalId.generate_next and sets internal id attribute' do
+ iid = rand(1..1000)
+
+ expect(InternalId).to receive(:generate_next).with(instance, scope_attrs, usage, any_args).and_return(iid)
+ subject
+ expect(instance.public_send(internal_id_attribute)).to eq(iid)
+ end
+
+ it 'does not overwrite an existing internal id' do
+ instance.public_send("#{internal_id_attribute}=", 4711)
+
+ expect { subject }.not_to change { instance.public_send(internal_id_attribute) }
+ end
+ end
+ end
+end
diff --git a/spec/views/projects/diffs/_stats.html.haml_spec.rb b/spec/views/projects/diffs/_stats.html.haml_spec.rb
new file mode 100644
index 00000000000..c7d2f85747c
--- /dev/null
+++ b/spec/views/projects/diffs/_stats.html.haml_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe 'projects/diffs/_stats.html.haml' do
+ let(:project) { create(:project, :repository) }
+ let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') }
+
+ def render_view
+ render partial: "projects/diffs/stats", locals: { diff_files: commit.diffs.diff_files }
+ end
+
+ context 'when the commit contains several changes' do
+ it 'uses plural for additions' do
+ render_view
+
+ expect(rendered).to have_text('additions')
+ end
+
+ it 'uses plural for deletions' do
+ render_view
+ end
+ end
+
+ context 'when the commit contains no addition and no deletions' do
+ let(:commit) { project.commit('4cd80ccab63c82b4bad16faa5193fbd2aa06df40') }
+
+ it 'uses plural for additions' do
+ render_view
+
+ expect(rendered).to have_text('additions')
+ end
+
+ it 'uses plural for deletions' do
+ render_view
+
+ expect(rendered).to have_text('deletions')
+ end
+ end
+
+ context 'when the commit contains exactly one addition and one deletion' do
+ let(:commit) { project.commit('08f22f255f082689c0d7d39d19205085311542bc') }
+
+ it 'uses singular for additions' do
+ render_view
+
+ expect(rendered).to have_text('addition')
+ expect(rendered).not_to have_text('additions')
+ end
+
+ it 'uses singular for deletions' do
+ render_view
+
+ expect(rendered).to have_text('deletion')
+ expect(rendered).not_to have_text('deletions')
+ end
+ end
+end