diff options
Diffstat (limited to 'spec/lib/gitlab/suggestions')
-rw-r--r-- | spec/lib/gitlab/suggestions/commit_message_spec.rb | 87 | ||||
-rw-r--r-- | spec/lib/gitlab/suggestions/file_suggestion_spec.rb | 241 | ||||
-rw-r--r-- | spec/lib/gitlab/suggestions/suggestion_set_spec.rb | 110 |
3 files changed, 438 insertions, 0 deletions
diff --git a/spec/lib/gitlab/suggestions/commit_message_spec.rb b/spec/lib/gitlab/suggestions/commit_message_spec.rb new file mode 100644 index 00000000000..0774fc80528 --- /dev/null +++ b/spec/lib/gitlab/suggestions/commit_message_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Suggestions::CommitMessage do + def create_suggestion(file_path, new_line, to_content) + position = Gitlab::Diff::Position.new(old_path: file_path, + new_path: file_path, + old_line: nil, + new_line: new_line, + diff_refs: merge_request.diff_refs) + + diff_note = create(:diff_note_on_merge_request, + noteable: merge_request, + position: position, + project: project) + + create(:suggestion, + :content_from_repo, + note: diff_note, + to_content: to_content) + end + + let_it_be(:user) do + create(:user, :commit_email, name: 'Test User', username: 'test.user') + end + + let_it_be(:project) do + create(:project, :repository, path: 'project-1', name: 'Project_1') + end + + let_it_be(:merge_request) do + create(:merge_request, source_project: project, target_project: project) + end + + let_it_be(:suggestion_set) do + suggestion1 = create_suggestion('files/ruby/popen.rb', 9, '*** SUGGESTION 1 ***') + suggestion2 = create_suggestion('files/ruby/popen.rb', 13, '*** SUGGESTION 2 ***') + suggestion3 = create_suggestion('files/ruby/regex.rb', 22, '*** SUGGESTION 3 ***') + + Gitlab::Suggestions::SuggestionSet.new([suggestion1, suggestion2, suggestion3]) + end + + describe '#message' do + before do + # Updating the suggestion_commit_message on a project shared across specs + # avoids recreating the repository for each spec. + project.update!(suggestion_commit_message: message) + end + + context 'when a custom commit message is not specified' do + let(:expected_message) { 'Apply 3 suggestion(s) to 2 file(s)' } + + context 'and is nil' do + let(:message) { nil } + + it 'uses the default commit message' do + expect(described_class + .new(user, suggestion_set) + .message).to eq(expected_message) + end + end + + context 'and is an empty string' do + let(:message) { '' } + + it 'uses the default commit message' do + expect(described_class + .new(user, suggestion_set) + .message).to eq(expected_message) + end + end + end + + context 'is specified and includes all placeholders' do + let(:message) do + '*** %{branch_name} %{files_count} %{file_paths} %{project_name} %{project_path} %{user_full_name} %{username} %{suggestions_count} ***' + end + + it 'generates a custom commit message' do + expect(Gitlab::Suggestions::CommitMessage + .new(user, suggestion_set) + .message).to eq('*** master 2 files/ruby/popen.rb, files/ruby/regex.rb Project_1 project-1 Test User test.user 3 ***') + end + end + end +end diff --git a/spec/lib/gitlab/suggestions/file_suggestion_spec.rb b/spec/lib/gitlab/suggestions/file_suggestion_spec.rb new file mode 100644 index 00000000000..6fbbad017c5 --- /dev/null +++ b/spec/lib/gitlab/suggestions/file_suggestion_spec.rb @@ -0,0 +1,241 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Suggestions::FileSuggestion do + def create_suggestion(new_line, to_content) + position = Gitlab::Diff::Position.new(old_path: file_path, + new_path: file_path, + old_line: nil, + new_line: new_line, + diff_refs: merge_request.diff_refs) + + diff_note = create(:diff_note_on_merge_request, + noteable: merge_request, + position: position, + project: project) + + create(:suggestion, + :content_from_repo, + note: diff_note, + to_content: to_content) + end + + let_it_be(:user) { create(:user) } + + let_it_be(:file_path) { 'files/ruby/popen.rb'} + + let_it_be(:project) { create(:project, :repository) } + + let_it_be(:merge_request) do + create(:merge_request, source_project: project, target_project: project) + end + + let_it_be(:suggestion1) do + create_suggestion(9, " *** SUGGESTION 1 ***\n") + end + + let_it_be(:suggestion2) do + create_suggestion(15, " *** SUGGESTION 2 ***\n") + end + + let(:file_suggestion) { described_class.new } + + describe '#add_suggestion' do + it 'succeeds when adding a suggestion for the same file as the original' do + file_suggestion.add_suggestion(suggestion1) + + expect { file_suggestion.add_suggestion(suggestion2) }.not_to raise_error + end + + it 'raises an error when adding a suggestion for a different file' do + allow(suggestion2) + .to(receive_message_chain(:diff_file, :file_path) + .and_return('path/to/different/file')) + + file_suggestion.add_suggestion(suggestion1) + + expect { file_suggestion.add_suggestion(suggestion2) }.to( + raise_error(described_class::SuggestionForDifferentFileError) + ) + end + end + + describe '#line_conflict' do + def stub_suggestions(line_index_spans) + fake_suggestions = line_index_spans.map do |span| + double("Suggestion", + from_line_index: span[:from_line_index], + to_line_index: span[:to_line_index]) + end + + allow(file_suggestion).to(receive(:suggestions).and_return(fake_suggestions)) + end + + context 'when line ranges do not overlap' do + it 'return false' do + stub_suggestions( + [ + { + from_line_index: 0, + to_line_index: 10 + }, + { + from_line_index: 11, + to_line_index: 20 + } + ] + ) + + expect(file_suggestion.line_conflict?).to be(false) + end + end + + context 'when line ranges are identical' do + it 'returns true' do + stub_suggestions( + [ + { + from_line_index: 0, + to_line_index: 10 + }, + { + from_line_index: 0, + to_line_index: 10 + } + ] + ) + + expect(file_suggestion.line_conflict?).to be(true) + end + end + + context 'when one range starts, and the other ends, on the same line' do + it 'returns true' do + stub_suggestions( + [ + { + from_line_index: 0, + to_line_index: 10 + }, + { + from_line_index: 10, + to_line_index: 20 + } + ] + ) + + expect(file_suggestion.line_conflict?).to be(true) + end + end + + context 'when one line range contains the other' do + it 'returns true' do + stub_suggestions( + [ + { + from_line_index: 0, + to_line_index: 10 + }, + { + from_line_index: 5, + to_line_index: 7 + } + ] + ) + + expect(file_suggestion.line_conflict?).to be(true) + end + end + + context 'when line ranges overlap' do + it 'returns true' do + stub_suggestions( + [ + { + from_line_index: 0, + to_line_index: 10 + }, + { + from_line_index: 8, + to_line_index: 15 + } + ] + ) + + expect(file_suggestion.line_conflict?).to be(true) + end + end + + context 'when no suggestions have been added' do + it 'returns false' do + expect(file_suggestion.line_conflict?).to be(false) + end + end + end + + describe '#new_content' do + it 'returns a blob with the suggestions applied to it' do + file_suggestion.add_suggestion(suggestion1) + file_suggestion.add_suggestion(suggestion2) + + expected_content = <<-CONTENT.strip_heredoc + require 'fileutils' + require 'open3' + + module Popen + extend self + + def popen(cmd, path=nil) + unless cmd.is_a?(Array) + *** SUGGESTION 1 *** + end + + path ||= Dir.pwd + + vars = { + *** SUGGESTION 2 *** + } + + options = { + chdir: path + } + + unless File.directory?(path) + FileUtils.mkdir_p(path) + end + + @cmd_output = "" + @cmd_status = 0 + + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + @cmd_output << stderr.read + @cmd_status = wait_thr.value.exitstatus + end + + return @cmd_output, @cmd_status + end + end + CONTENT + + expect(file_suggestion.new_content).to eq(expected_content) + end + + it 'returns an empty string when no suggestions have been added' do + expect(file_suggestion.new_content).to eq('') + end + end + + describe '#file_path' do + it 'returns the path of the file associated with the suggestions' do + file_suggestion.add_suggestion(suggestion1) + + expect(file_suggestion.file_path).to eq(file_path) + end + + it 'returns nil if no suggestions have been added' do + expect(file_suggestion.file_path).to be(nil) + end + end +end diff --git a/spec/lib/gitlab/suggestions/suggestion_set_spec.rb b/spec/lib/gitlab/suggestions/suggestion_set_spec.rb new file mode 100644 index 00000000000..8c61e6c42a6 --- /dev/null +++ b/spec/lib/gitlab/suggestions/suggestion_set_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Suggestions::SuggestionSet do + def create_suggestion(file_path, new_line, to_content) + position = Gitlab::Diff::Position.new(old_path: file_path, + new_path: file_path, + old_line: nil, + new_line: new_line, + diff_refs: merge_request.diff_refs) + + diff_note = create(:diff_note_on_merge_request, + noteable: merge_request, + position: position, + project: project) + + create(:suggestion, + :content_from_repo, + note: diff_note, + to_content: to_content) + end + + let_it_be(:user) { create(:user) } + + let_it_be(:project) { create(:project, :repository) } + + let_it_be(:merge_request) do + create(:merge_request, source_project: project, target_project: project) + end + + let_it_be(:suggestion) { create(:suggestion)} + + let_it_be(:suggestion2) do + create_suggestion('files/ruby/popen.rb', 13, "*** SUGGESTION 2 ***") + end + + let_it_be(:suggestion3) do + create_suggestion('files/ruby/regex.rb', 22, "*** SUGGESTION 3 ***") + end + + let_it_be(:unappliable_suggestion) { create(:suggestion, :unappliable) } + + let(:suggestion_set) { described_class.new([suggestion]) } + + describe '#project' do + it 'returns the project associated with the suggestions' do + expected_project = suggestion.project + + expect(suggestion_set.project).to be(expected_project) + end + end + + describe '#branch' do + it 'returns the branch associated with the suggestions' do + expected_branch = suggestion.branch + + expect(suggestion_set.branch).to be(expected_branch) + end + end + + describe '#valid?' do + it 'returns true if no errors are found' do + expect(suggestion_set.valid?).to be(true) + end + + it 'returns false if an error is found' do + suggestion_set = described_class.new([unappliable_suggestion]) + + expect(suggestion_set.valid?).to be(false) + end + end + + describe '#error_message' do + it 'returns an error message if an error is found' do + suggestion_set = described_class.new([unappliable_suggestion]) + + expect(suggestion_set.error_message).to be_a(String) + end + + it 'returns nil if no errors are found' do + expect(suggestion_set.error_message).to be(nil) + end + end + + describe '#actions' do + it 'returns an array of hashes with proper key/value pairs' do + first_action = suggestion_set.actions.first + + file_path, file_suggestion = suggestion_set + .send(:suggestions_per_file).first + + expect(first_action[:action]).to be('update') + expect(first_action[:file_path]).to eq(file_path) + expect(first_action[:content]).to eq(file_suggestion.new_content) + end + end + + describe '#file_paths' do + it 'returns an array of unique file paths associated with the suggestions' do + suggestion_set = described_class.new([suggestion, suggestion2, suggestion3]) + + expected_paths = %w(files/ruby/popen.rb files/ruby/regex.rb) + + actual_paths = suggestion_set.file_paths + + expect(actual_paths.sort).to eq(expected_paths) + end + end +end |