1# frozen_string_literal: true 2 3require 'spec_helper' 4 5RSpec.describe Projects::MergeRequests::DiffsController do 6 include ProjectForksHelper 7 include TrackingHelpers 8 9 shared_examples '404 for unexistent diffable' do 10 context 'when diffable does not exists' do 11 it 'returns 404' do 12 go(diff_id: non_existing_record_id) 13 14 expect(MergeRequestDiff.find_by(id: non_existing_record_id)).to be_nil 15 expect(response).to have_gitlab_http_status(:not_found) 16 end 17 end 18 19 context 'when the merge_request_diff.id is blank' do 20 it 'returns 404' do 21 allow_next_instance_of(MergeRequest) do |instance| 22 allow(instance).to receive(:merge_request_diff).and_return(MergeRequestDiff.new(merge_request_id: instance.id)) 23 24 go 25 26 expect(response).to have_gitlab_http_status(:not_found) 27 end 28 end 29 end 30 end 31 32 shared_examples 'forked project with submodules' do 33 render_views 34 35 let(:project) { create(:project, :repository) } 36 let(:forked_project) { fork_project_with_submodules(project) } 37 let(:merge_request) { create(:merge_request_with_diffs, source_project: forked_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) } 38 39 before do 40 project.add_developer(user) 41 42 merge_request.reload 43 go 44 end 45 46 it 'renders' do 47 expect(response).to be_successful 48 expect(response.body).to have_content('Subproject commit') 49 end 50 end 51 52 shared_examples 'cached diff collection' do 53 it 'ensures diff highlighting cache writing' do 54 expect_next_instance_of(Gitlab::Diff::HighlightCache) do |cache| 55 expect(cache).to receive(:write_if_empty).once 56 end 57 58 go 59 end 60 end 61 62 shared_examples "diff note on-demand position creation" do 63 it "updates diff discussion positions" do 64 service = double("service") 65 66 expect(Discussions::CaptureDiffNotePositionsService).to receive(:new).with(merge_request).and_return(service) 67 expect(service).to receive(:execute) 68 69 go 70 end 71 end 72 73 shared_examples 'show the right diff files with previous diff_id' do 74 context 'with previous diff_id' do 75 let!(:merge_request_diff_1) { merge_request.merge_request_diffs.create!(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } 76 let!(:merge_request_diff_2) { merge_request.merge_request_diffs.create!(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e', diff_type: :merge_head) } 77 78 subject { go(diff_id: merge_request_diff_1.id, diff_head: true) } 79 80 it 'shows the right diff files' do 81 subject 82 expect(json_response["diff_files"].size).to eq(merge_request_diff_1.files_count) 83 end 84 end 85 end 86 87 let(:project) { create(:project, :repository) } 88 let(:user) { create(:user) } 89 let(:maintainer) { true } 90 let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } 91 92 before do 93 project.add_maintainer(user) if maintainer 94 sign_in(user) 95 end 96 97 describe 'GET show' do 98 def go(extra_params = {}) 99 params = { 100 namespace_id: project.namespace.to_param, 101 project_id: project, 102 id: merge_request.iid, 103 format: 'json' 104 } 105 106 get :show, params: params.merge(extra_params) 107 end 108 109 context 'with default params' do 110 context 'for the same project' do 111 before do 112 allow(controller).to receive(:rendered_for_merge_request?).and_return(true) 113 end 114 115 it 'serializes merge request diff collection' do 116 expect_next_instance_of(DiffsSerializer) do |instance| 117 expect(instance).to receive(:represent).with(an_instance_of(Gitlab::Diff::FileCollection::MergeRequestDiff), an_instance_of(Hash)) 118 end 119 120 go 121 end 122 end 123 124 context 'when note is a legacy diff note' do 125 before do 126 create(:legacy_diff_note_on_merge_request, project: project, noteable: merge_request) 127 end 128 129 it 'serializes merge request diff collection' do 130 expect_next_instance_of(DiffsSerializer) do |instance| 131 expect(instance).to receive(:represent).with(an_instance_of(Gitlab::Diff::FileCollection::MergeRequestDiff), an_instance_of(Hash)) 132 end 133 134 go 135 end 136 end 137 138 it_behaves_like 'forked project with submodules' 139 end 140 141 it_behaves_like 'cached diff collection' 142 it_behaves_like 'diff note on-demand position creation' 143 end 144 145 describe 'GET diffs_metadata' do 146 shared_examples_for 'serializes diffs metadata with expected arguments' do 147 it 'returns success' do 148 subject 149 150 expect(response).to have_gitlab_http_status(:ok) 151 end 152 153 it 'serializes paginated merge request diff collection' do 154 expect_next_instance_of(DiffsMetadataSerializer) do |instance| 155 expect(instance).to receive(:represent) 156 .with(an_instance_of(collection), expected_options) 157 .and_call_original 158 end 159 160 subject 161 end 162 end 163 164 def go(extra_params = {}) 165 params = { 166 namespace_id: project.namespace.to_param, 167 project_id: project, 168 id: merge_request.iid, 169 format: 'json' 170 } 171 172 get :diffs_metadata, params: params.merge(extra_params) 173 end 174 175 it_behaves_like '404 for unexistent diffable' 176 177 it_behaves_like 'show the right diff files with previous diff_id' 178 179 context 'when not authorized' do 180 let(:another_user) { create(:user) } 181 182 before do 183 sign_in(another_user) 184 end 185 186 it 'returns 404 when not a member' do 187 go 188 189 expect(response).to have_gitlab_http_status(:not_found) 190 end 191 192 it 'returns 404 when visibility level is not enough' do 193 project.add_guest(another_user) 194 195 go 196 197 expect(response).to have_gitlab_http_status(:not_found) 198 end 199 end 200 201 context 'with valid diff_id' do 202 subject { go(diff_id: merge_request.merge_request_diff.id) } 203 204 it_behaves_like 'serializes diffs metadata with expected arguments' do 205 let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiff } 206 let(:expected_options) do 207 { 208 environment: nil, 209 merge_request: merge_request, 210 merge_request_diff: merge_request.merge_request_diff, 211 merge_request_diffs: merge_request.merge_request_diffs, 212 start_version: nil, 213 start_sha: nil, 214 commit: nil, 215 latest_diff: true, 216 only_context_commits: false, 217 allow_tree_conflicts: true, 218 merge_ref_head_diff: false 219 } 220 end 221 end 222 end 223 224 context "with the :default_merge_ref_for_diffs flag on" do 225 let(:diffable_merge_ref) { true } 226 227 subject do 228 go(diff_head: true, 229 diff_id: merge_request.merge_request_diff.id, 230 start_sha: merge_request.merge_request_diff.start_commit_sha) 231 end 232 233 it "correctly generates the right diff between versions" do 234 MergeRequests::MergeToRefService.new(project: project, current_user: merge_request.author).execute(merge_request) 235 236 expect_next_instance_of(CompareService) do |service| 237 expect(service).to receive(:execute).with( 238 project, 239 merge_request.merge_request_diff.head_commit_sha, 240 straight: true) 241 end 242 243 subject 244 end 245 end 246 247 context 'with diff_head param passed' do 248 before do 249 allow(merge_request).to receive(:diffable_merge_ref?) 250 .and_return(diffable_merge_ref) 251 end 252 253 context 'the merge request can be compared with head' do 254 let(:diffable_merge_ref) { true } 255 256 it 'compares diffs with the head' do 257 create(:merge_request_diff, :merge_head, merge_request: merge_request) 258 259 go(diff_head: true) 260 261 expect(response).to have_gitlab_http_status(:ok) 262 end 263 end 264 265 context 'the merge request cannot be compared with head' do 266 let(:diffable_merge_ref) { false } 267 268 it 'compares diffs with the base' do 269 go(diff_head: true) 270 271 expect(response).to have_gitlab_http_status(:ok) 272 end 273 end 274 end 275 276 context 'with MR regular diff params' do 277 subject { go } 278 279 it_behaves_like 'serializes diffs metadata with expected arguments' do 280 let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiff } 281 let(:expected_options) do 282 { 283 environment: nil, 284 merge_request: merge_request, 285 merge_request_diff: merge_request.merge_request_diff, 286 merge_request_diffs: merge_request.merge_request_diffs, 287 start_version: nil, 288 start_sha: nil, 289 commit: nil, 290 latest_diff: true, 291 only_context_commits: false, 292 allow_tree_conflicts: true, 293 merge_ref_head_diff: nil 294 } 295 end 296 end 297 end 298 299 context 'with commit param' do 300 subject { go(commit_id: merge_request.diff_head_sha) } 301 302 it_behaves_like 'serializes diffs metadata with expected arguments' do 303 let(:collection) { Gitlab::Diff::FileCollection::Commit } 304 let(:expected_options) do 305 { 306 environment: nil, 307 merge_request: merge_request, 308 merge_request_diff: nil, 309 merge_request_diffs: merge_request.merge_request_diffs, 310 start_version: nil, 311 start_sha: nil, 312 commit: merge_request.diff_head_commit, 313 latest_diff: nil, 314 only_context_commits: false, 315 allow_tree_conflicts: true, 316 merge_ref_head_diff: nil 317 } 318 end 319 end 320 end 321 322 context 'when display_merge_conflicts_in_diff is disabled' do 323 subject { go } 324 325 before do 326 stub_feature_flags(display_merge_conflicts_in_diff: false) 327 end 328 329 it_behaves_like 'serializes diffs metadata with expected arguments' do 330 let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiff } 331 let(:expected_options) do 332 { 333 environment: nil, 334 merge_request: merge_request, 335 merge_request_diff: merge_request.merge_request_diff, 336 merge_request_diffs: merge_request.merge_request_diffs, 337 start_version: nil, 338 start_sha: nil, 339 commit: nil, 340 latest_diff: true, 341 only_context_commits: false, 342 allow_tree_conflicts: false, 343 merge_ref_head_diff: nil 344 } 345 end 346 end 347 end 348 end 349 350 describe 'GET diff_for_path' do 351 def diff_for_path(extra_params = {}) 352 params = { 353 namespace_id: project.namespace.to_param, 354 project_id: project, 355 id: merge_request.iid, 356 format: 'json' 357 } 358 359 get :diff_for_path, params: params.merge(extra_params) 360 end 361 362 let(:existing_path) { 'files/ruby/popen.rb' } 363 364 context 'when the merge request exists' do 365 context 'when the user can view the merge request' do 366 context 'when the path exists in the diff' do 367 it 'enables diff notes' do 368 diff_for_path(old_path: existing_path, new_path: existing_path) 369 370 expect(assigns(:diff_notes_disabled)).to be_falsey 371 expect(assigns(:new_diff_note_attrs)).to eq(noteable_type: 'MergeRequest', 372 noteable_id: merge_request.id, 373 commit_id: nil) 374 end 375 376 it 'only renders the diffs for the path given' do 377 diff_for_path(old_path: existing_path, new_path: existing_path) 378 379 paths = json_response['diff_files'].map { |file| file['new_path'] } 380 381 expect(paths).to include(existing_path) 382 end 383 end 384 end 385 386 context 'when the user cannot view the merge request' do 387 let(:maintainer) { false } 388 389 before do 390 diff_for_path(old_path: existing_path, new_path: existing_path) 391 end 392 393 it 'returns a 404' do 394 expect(response).to have_gitlab_http_status(:not_found) 395 end 396 end 397 end 398 399 context 'when the merge request does not exist' do 400 before do 401 diff_for_path(id: merge_request.iid.succ, old_path: existing_path, new_path: existing_path) 402 end 403 404 it 'returns a 404' do 405 expect(response).to have_gitlab_http_status(:not_found) 406 end 407 end 408 409 context 'when the merge request belongs to a different project' do 410 let(:other_project) { create(:project) } 411 412 before do 413 other_project.add_maintainer(user) 414 diff_for_path(old_path: existing_path, new_path: existing_path, project_id: other_project) 415 end 416 417 it 'returns a 404' do 418 expect(response).to have_gitlab_http_status(:not_found) 419 end 420 end 421 end 422 423 describe 'GET diffs_batch' do 424 shared_examples_for 'serializes diffs with expected arguments' do 425 it 'serializes paginated merge request diff collection' do 426 expect_next_instance_of(PaginatedDiffSerializer) do |instance| 427 expect(instance).to receive(:represent) 428 .with(an_instance_of(collection), expected_options) 429 .and_call_original 430 end 431 432 subject 433 end 434 end 435 436 shared_examples_for 'successful request' do 437 it 'returns success' do 438 subject 439 440 expect(response).to have_gitlab_http_status(:ok) 441 end 442 443 it 'tracks mr_diffs event' do 444 expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) 445 .to receive(:track_mr_diffs_action) 446 .with(merge_request: merge_request) 447 448 subject 449 end 450 451 context 'when DNT is enabled' do 452 before do 453 stub_do_not_track('1') 454 end 455 456 it 'does not track any mr_diffs event' do 457 expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) 458 .not_to receive(:track_mr_diffs_action) 459 460 expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) 461 .not_to receive(:track_mr_diffs_single_file_action) 462 463 subject 464 end 465 end 466 467 context 'when user has view_diffs_file_by_file set to false' do 468 before do 469 user.update!(view_diffs_file_by_file: false) 470 end 471 472 it 'does not track single_file_diffs events' do 473 expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) 474 .not_to receive(:track_mr_diffs_single_file_action) 475 476 subject 477 end 478 end 479 480 context 'when user has view_diffs_file_by_file set to true' do 481 before do 482 user.update!(view_diffs_file_by_file: true) 483 end 484 485 it 'tracks single_file_diffs events' do 486 expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter) 487 .to receive(:track_mr_diffs_single_file_action) 488 .with(merge_request: merge_request, user: user) 489 490 subject 491 end 492 end 493 end 494 495 def collection_arguments(pagination_data = {}) 496 { 497 environment: nil, 498 merge_request: merge_request, 499 commit: nil, 500 diff_view: :inline, 501 merge_ref_head_diff: nil, 502 allow_tree_conflicts: true, 503 pagination_data: { 504 total_pages: nil 505 }.merge(pagination_data) 506 } 507 end 508 509 def go(extra_params = {}) 510 params = { 511 namespace_id: project.namespace.to_param, 512 project_id: project, 513 id: merge_request.iid, 514 page: 0, 515 per_page: 20, 516 format: 'json' 517 } 518 519 get :diffs_batch, params: params.merge(extra_params) 520 end 521 522 it_behaves_like '404 for unexistent diffable' 523 524 it_behaves_like 'show the right diff files with previous diff_id' 525 526 context 'when not authorized' do 527 let(:other_user) { create(:user) } 528 529 before do 530 sign_in(other_user) 531 end 532 533 it 'returns 404' do 534 go 535 536 expect(response).to have_gitlab_http_status(:not_found) 537 end 538 end 539 540 context 'with valid diff_id' do 541 subject { go(diff_id: merge_request.merge_request_diff.id) } 542 543 it_behaves_like 'serializes diffs with expected arguments' do 544 let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch } 545 let(:expected_options) { collection_arguments(total_pages: 20).merge(merge_ref_head_diff: false) } 546 end 547 548 it_behaves_like 'successful request' 549 end 550 551 context 'with commit_id param' do 552 subject { go(commit_id: merge_request.diff_head_sha) } 553 554 it_behaves_like 'serializes diffs with expected arguments' do 555 let(:collection) { Gitlab::Diff::FileCollection::Commit } 556 let(:expected_options) { collection_arguments.merge(commit: merge_request.commits(limit: 1).first) } 557 end 558 end 559 560 context 'with diff_id and start_sha params' do 561 subject do 562 go(diff_id: merge_request.merge_request_diff.id, 563 start_sha: merge_request.merge_request_diff.start_commit_sha) 564 end 565 566 it_behaves_like 'serializes diffs with expected arguments' do 567 let(:collection) { Gitlab::Diff::FileCollection::Compare } 568 let(:expected_options) { collection_arguments.merge(merge_ref_head_diff: false) } 569 end 570 571 it_behaves_like 'successful request' 572 end 573 574 context 'with paths param' do 575 let(:example_file_path) { "README" } 576 let(:file_path_option) { { paths: [example_file_path] } } 577 578 subject do 579 go(file_path_option) 580 end 581 582 it_behaves_like 'serializes diffs with expected arguments' do 583 let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch } 584 let(:expected_options) do 585 collection_arguments(total_pages: 20) 586 end 587 end 588 589 it_behaves_like 'successful request' 590 591 it 'filters down the response to the expected file path' do 592 subject 593 594 expect(json_response["diff_files"].size).to eq(1) 595 expect(json_response["diff_files"].first["file_path"]).to eq(example_file_path) 596 end 597 end 598 599 context 'with default params' do 600 subject { go } 601 602 it_behaves_like 'serializes diffs with expected arguments' do 603 let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch } 604 let(:expected_options) { collection_arguments(total_pages: 20) } 605 end 606 607 it_behaves_like 'successful request' 608 end 609 610 context 'with smaller diff batch params' do 611 subject { go(page: 5, per_page: 5) } 612 613 it_behaves_like 'serializes diffs with expected arguments' do 614 let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch } 615 let(:expected_options) { collection_arguments(total_pages: 20) } 616 end 617 618 it_behaves_like 'successful request' 619 end 620 621 context 'when display_merge_conflicts_in_diff is disabled' do 622 before do 623 stub_feature_flags(display_merge_conflicts_in_diff: false) 624 end 625 626 subject { go } 627 628 it_behaves_like 'serializes diffs with expected arguments' do 629 let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiffBatch } 630 let(:expected_options) { collection_arguments(total_pages: 20).merge(allow_tree_conflicts: false) } 631 end 632 633 it_behaves_like 'successful request' 634 end 635 636 it_behaves_like 'forked project with submodules' 637 it_behaves_like 'cached diff collection' 638 639 context 'diff unfolding' do 640 let!(:unfoldable_diff_note) do 641 create(:diff_note_on_merge_request, :folded_position, project: project, noteable: merge_request) 642 end 643 644 let!(:diff_note) do 645 create(:diff_note_on_merge_request, project: project, noteable: merge_request) 646 end 647 648 it 'unfolds correct diff file positions' do 649 expect_next_instance_of(Gitlab::Diff::FileCollection::MergeRequestDiffBatch) do |instance| 650 expect(instance) 651 .to receive(:unfold_diff_files) 652 .with([unfoldable_diff_note.position]) 653 .and_call_original 654 end 655 656 go 657 end 658 end 659 end 660end 661