summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/suggestions
diff options
context:
space:
mode:
Diffstat (limited to 'spec/lib/gitlab/suggestions')
-rw-r--r--spec/lib/gitlab/suggestions/commit_message_spec.rb87
-rw-r--r--spec/lib/gitlab/suggestions/file_suggestion_spec.rb241
-rw-r--r--spec/lib/gitlab/suggestions/suggestion_set_spec.rb110
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