wiki_page_spec.rb 19.8 KB
Newer Older
1 2
# frozen_string_literal: true

3 4
require "spec_helper"

5
describe WikiPage do
Z
Zeger-Jan van de Weg 已提交
6
  let(:project) { create(:project, :wiki_repo) }
7
  let(:user) { project.owner }
8
  let(:wiki) { ProjectWiki.new(project, user) }
9

10 11 12 13 14 15 16 17 18 19 20 21
  let(:new_page) do
    described_class.new(wiki).tap do |page|
      page.attributes = { title: 'test page', content: 'test content' }
    end
  end

  let(:existing_page) do
    create_page('test page', 'test content')
    wiki.find_page('test page')
  end

  subject { new_page }
22

23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
  describe '.group_by_directory' do
    context 'when there are no pages' do
      it 'returns an empty array' do
        expect(described_class.group_by_directory(nil)).to eq([])
        expect(described_class.group_by_directory([])).to eq([])
      end
    end

    context 'when there are pages' do
      before do
        create_page('dir_1/dir_1_1/page_3', 'content')
        create_page('page_1', 'content')
        create_page('dir_1/page_2', 'content')
        create_page('dir_2', 'page with dir name')
        create_page('dir_2/page_5', 'content')
        create_page('page_6', 'content')
        create_page('dir_2/page_4', 'content')
      end

      let(:page_1) { wiki.find_page('page_1') }
      let(:page_6) { wiki.find_page('page_6') }
      let(:page_dir_2) { wiki.find_page('dir_2') }

      let(:dir_1) do
        WikiDirectory.new('dir_1', [wiki.find_page('dir_1/page_2')])
      end
      let(:dir_1_1) do
        WikiDirectory.new('dir_1/dir_1_1', [wiki.find_page('dir_1/dir_1_1/page_3')])
      end
      let(:dir_2) do
        pages = [wiki.find_page('dir_2/page_5'),
                 wiki.find_page('dir_2/page_4')]
        WikiDirectory.new('dir_2', pages)
      end

58
      describe "#list_pages" do
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
        context 'sort by title' do
          let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages) }
          let(:expected_grouped_entries) { [dir_1_1, dir_1, page_dir_2, dir_2, page_1, page_6] }

          it 'returns an array with pages and directories' do
            grouped_entries.each_with_index do |page_or_dir, i|
              expected_page_or_dir = expected_grouped_entries[i]
              expected_slugs = get_slugs(expected_page_or_dir)
              slugs = get_slugs(page_or_dir)

              expect(slugs).to match_array(expected_slugs)
            end
          end
        end

        context 'sort by created_at' do
          let(:grouped_entries) { described_class.group_by_directory(wiki.list_pages(sort: 'created_at')) }
          let(:expected_grouped_entries) { [dir_1_1, page_1, dir_1, page_dir_2, dir_2, page_6] }

          it 'returns an array with pages and directories' do
            grouped_entries.each_with_index do |page_or_dir, i|
              expected_page_or_dir = expected_grouped_entries[i]
              expected_slugs = get_slugs(expected_page_or_dir)
              slugs = get_slugs(page_or_dir)

              expect(slugs).to match_array(expected_slugs)
            end
          end
        end

        it 'returns an array with retained order with directories at the top' do
          expected_order = ['dir_1/dir_1_1/page_3', 'dir_1/page_2', 'dir_2', 'dir_2/page_4', 'dir_2/page_5', 'page_1', 'page_6']

          grouped_entries = described_class.group_by_directory(wiki.list_pages)

          actual_order =
            grouped_entries.flat_map do |page_or_dir|
              get_slugs(page_or_dir)
            end
          expect(actual_order).to eq(expected_order)
        end
      end
    end
  end

A
Alex Braha Stoll 已提交
104 105 106 107
  describe '.unhyphenize' do
    it 'removes hyphens from a name' do
      name = 'a-name--with-hyphens'

108
      expect(described_class.unhyphenize(name)).to eq('a name with hyphens')
A
Alex Braha Stoll 已提交
109 110 111
    end
  end

112
  describe "#initialize" do
113
    context "when initialized with an existing page" do
114
      subject { existing_page }
115 116

      it "sets the slug attribute" do
117
        expect(subject.slug).to eq("test-page")
118 119 120
      end

      it "sets the title attribute" do
121
        expect(subject.title).to eq("test page")
122 123 124
      end

      it "sets the formatted content attribute" do
125
        expect(subject.content).to eq("test content")
126 127 128
      end

      it "sets the format attribute" do
129
        expect(subject.format).to eq(:markdown)
130 131 132
      end

      it "sets the message attribute" do
133
        expect(subject.message).to eq("test commit")
134 135 136
      end

      it "sets the version attribute" do
137
        expect(subject.version).to be_a Gitlab::Git::WikiPageVersion
138 139 140 141 142 143 144
      end
    end
  end

  describe "validations" do
    it "validates presence of title" do
      subject.attributes.delete(:title)
145 146 147

      expect(subject).not_to be_valid
      expect(subject.errors.keys).to contain_exactly(:title)
148 149 150 151
    end

    it "validates presence of content" do
      subject.attributes.delete(:content)
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194

      expect(subject).not_to be_valid
      expect(subject.errors.keys).to contain_exactly(:content)
    end

    describe '#validate_path_limits' do
      let(:max_title) { described_class::MAX_TITLE_BYTES }
      let(:max_directory) { described_class::MAX_DIRECTORY_BYTES }

      where(:character) do
        ['a', 'ä', '🙈']
      end

      with_them do
        let(:size) { character.bytesize.to_f }
        let(:valid_title) { character * (max_title / size).floor }
        let(:valid_directory) { character * (max_directory / size).floor }
        let(:invalid_title) { character * ((max_title + 1) / size).ceil }
        let(:invalid_directory) { character * ((max_directory + 1) / size).ceil }

        it 'accepts page titles below the limit' do
          subject.title = valid_title

          expect(subject).to be_valid
        end

        it 'accepts directories below the limit' do
          subject.title = valid_directory + '/foo'

          expect(subject).to be_valid
        end

        it 'accepts a path with page title and directory below the limit' do
          subject.title = "#{valid_directory}/#{valid_title}"

          expect(subject).to be_valid
        end

        it 'rejects page titles exceeding the limit' do
          subject.title = invalid_title

          expect(subject).not_to be_valid
          expect(subject.errors[:title]).to contain_exactly(
195
            "exceeds the limit of #{max_title} bytes"
196 197 198 199
          )
        end

        it 'rejects directories exceeding the limit' do
200
          subject.title = "#{invalid_directory}/#{invalid_directory}2/foo"
201 202 203

          expect(subject).not_to be_valid
          expect(subject.errors[:title]).to contain_exactly(
204 205
            "exceeds the limit of #{max_directory} bytes for directory name \"#{invalid_directory}\"",
            "exceeds the limit of #{max_directory} bytes for directory name \"#{invalid_directory}2\""
206 207 208 209 210 211 212 213
          )
        end

        it 'rejects a page with both title and directory exceeding the limit' do
          subject.title = "#{invalid_directory}/#{invalid_title}"

          expect(subject).not_to be_valid
          expect(subject.errors[:title]).to contain_exactly(
214 215
            "exceeds the limit of #{max_title} bytes",
            "exceeds the limit of #{max_directory} bytes for directory name \"#{invalid_directory}\""
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
          )
        end
      end

      context 'with an existing page title exceeding the limit' do
        subject do
          title = 'a' * (max_title + 1)
          create_page(title, 'content')
          wiki.find_page(title)
        end

        it 'accepts the exceeding title length when unchanged' do
          expect(subject).to be_valid
        end

        it 'rejects the exceeding title length when changed' do
          subject.title = 'b' * (max_title + 1)

          expect(subject).not_to be_valid
          expect(subject.errors).to include(:title)
        end
      end
238 239 240 241
    end
  end

  describe "#create" do
242
    let(:attributes) do
243 244 245 246 247 248 249 250
      {
        title: "Index",
        content: "Home Page",
        format: "markdown",
        message: 'Custom Commit Message'
      }
    end

251 252
    context "with valid attributes" do
      it "saves the wiki page" do
253 254
        subject.create(attributes)

255
        expect(wiki.find_page("Index")).not_to be_nil
256 257 258
      end

      it "returns true" do
259
        expect(subject.create(attributes)).to eq(true)
260 261 262
      end

      it 'saves the wiki page with message' do
263
        subject.create(attributes)
264 265

        expect(wiki.find_page("Index").message).to eq 'Custom Commit Message'
266 267 268 269
      end
    end
  end

S
Stan Hu 已提交
270 271 272 273
  describe "dot in the title" do
    let(:title) { 'Index v1.2.3' }

    describe "#create" do
274
      let(:attributes) { { title: title, content: "Home Page", format: "markdown" } }
S
Stan Hu 已提交
275 276 277

      context "with valid attributes" do
        it "saves the wiki page" do
278 279
          subject.create(attributes)

S
Stan Hu 已提交
280 281 282 283
          expect(wiki.find_page(title)).not_to be_nil
        end

        it "returns true" do
284
          expect(subject.create(attributes)).to eq(true)
S
Stan Hu 已提交
285 286 287 288 289
        end
      end
    end

    describe "#update" do
290
      subject do
S
Stan Hu 已提交
291
        create_page(title, "content")
292
        wiki.find_page(title)
S
Stan Hu 已提交
293 294 295
      end

      it "updates the content of the page" do
296 297 298 299
        subject.update(content: "new content")
        page = wiki.find_page(title)

        expect(page.content).to eq('new content')
S
Stan Hu 已提交
300 301 302
      end

      it "returns true" do
303
        expect(subject.update(content: "more content")).to be_truthy
S
Stan Hu 已提交
304 305 306 307
      end
    end
  end

F
Francisco Javier López 已提交
308
  describe '#create' do
309 310 311 312
    context 'with valid attributes' do
      it 'raises an error if a page with the same path already exists' do
        create_page('New Page', 'content')
        create_page('foo/bar', 'content')
313

314 315 316
        expect { create_page('New Page', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError
        expect { create_page('foo/bar', 'other content') }.to raise_error Gitlab::Git::Wiki::DuplicatePageError
      end
317

318 319
      it 'if the title is preceded by a / it is removed' do
        create_page('/New Page', 'content')
320

321
        expect(wiki.find_page('New Page')).not_to be_nil
322 323
      end
    end
324
  end
325

326
  describe "#update" do
327
    subject { existing_page }
328

329 330 331 332
    context "with valid attributes" do
      it "updates the content of the page" do
        new_content = "new content"

333 334
        subject.update(content: new_content)
        page = wiki.find_page('test page')
335

336
        expect(page.content).to eq("new content")
337 338
      end

339 340
      it "updates the title of the page" do
        new_title = "Index v.1.2.4"
F
Francisco Javier López 已提交
341

342 343
        subject.update(title: new_title)
        page = wiki.find_page(new_title)
F
Francisco Javier López 已提交
344

345
        expect(page.title).to eq(new_title)
346
      end
347

348
      it "returns true" do
349
        expect(subject.update(content: "more content")).to be_truthy
350
      end
351
    end
H
Hiroyuki Sato 已提交
352

353 354
    context 'with same last commit sha' do
      it 'returns true' do
355
        expect(subject.update(content: 'more content', last_commit_sha: subject.last_commit_sha)).to be_truthy
H
Hiroyuki Sato 已提交
356
      end
357
    end
H
Hiroyuki Sato 已提交
358

359 360
    context 'with different last commit sha' do
      it 'raises exception' do
361
        expect { subject.update(content: 'more content', last_commit_sha: 'xxx') }.to raise_error(WikiPage::PageChangedError)
H
Hiroyuki Sato 已提交
362
      end
363
    end
364

365 366 367
    context 'when renaming a page' do
      it 'raises an error if the page already exists' do
        create_page('Existing Page', 'content')
368

369 370 371
        expect { subject.update(title: 'Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError)
        expect(subject.title).to eq 'test page'
        expect(subject.content).to eq 'new_content'
372
      end
373

374 375 376
      it 'updates the content and rename the file' do
        new_title = 'Renamed Page'
        new_content = 'updated content'
377

378
        expect(subject.update(title: new_title, content: new_content)).to be_truthy
379

380
        page = wiki.find_page(new_title)
381

382 383
        expect(page).not_to be_nil
        expect(page.content).to eq new_content
384
      end
385
    end
386

387 388 389
    context 'when moving a page' do
      it 'raises an error if the page already exists' do
        create_page('foo/Existing Page', 'content')
390

391 392 393
        expect { subject.update(title: 'foo/Existing Page', content: 'new_content') }.to raise_error(WikiPage::PageRenameError)
        expect(subject.title).to eq 'test page'
        expect(subject.content).to eq 'new_content'
394
      end
F
Francisco Javier López 已提交
395

396 397 398
      it 'updates the content and moves the file' do
        new_title = 'foo/Other Page'
        new_content = 'new_content'
F
Francisco Javier López 已提交
399

400
        expect(subject.update(title: new_title, content: new_content)).to be_truthy
F
Francisco Javier López 已提交
401

402
        page = wiki.find_page(new_title)
403

404 405 406
        expect(page).not_to be_nil
        expect(page.content).to eq new_content
      end
407

408
      context 'in subdir' do
409
        subject do
410
          create_page('foo/Existing Page', 'content')
411
          wiki.find_page('foo/Existing Page')
412
        end
413

414
        it 'moves the page to the root folder if the title is preceded by /' do
415 416 417
          expect(subject.slug).to eq 'foo/Existing-Page'
          expect(subject.update(title: '/Existing Page', content: 'new_content')).to be_truthy
          expect(subject.slug).to eq 'Existing-Page'
418 419
        end

420
        it 'does nothing if it has the same title' do
421
          original_path = subject.slug
422

423 424
          expect(subject.update(title: 'Existing Page', content: 'new_content')).to be_truthy
          expect(subject.slug).to eq original_path
425 426 427
        end
      end

428 429
      context 'in root dir' do
        it 'does nothing if the title is preceded by /' do
430
          original_path = subject.slug
F
Francisco Javier López 已提交
431

432 433
          expect(subject.update(title: '/test page', content: 'new_content')).to be_truthy
          expect(subject.slug).to eq original_path
434 435 436 437
        end
      end
    end

438 439
    context "with invalid attributes" do
      it 'aborts update if title blank' do
440 441
        expect(subject.update(title: '', content: 'new_content')).to be_falsey
        expect(subject.content).to eq 'new_content'
442

443
        page = wiki.find_page('test page')
444

445
        expect(page.content).to eq 'test content'
446
      end
447
    end
448 449 450
  end

  describe "#destroy" do
451
    subject { existing_page }
452

453
    it "deletes the page" do
454 455
      subject.delete

456
      expect(wiki.list_pages).to be_empty
457 458
    end

459
    it "returns true" do
460
      expect(subject.delete).to eq(true)
461 462 463 464
    end
  end

  describe "#versions" do
465
    subject { existing_page }
466

467
    it "returns an array of all commits for the page" do
468
      3.times { |i| subject.update(content: "content #{i}") }
469

470
      expect(subject.versions.count).to eq(4)
471 472
    end

473
    it 'returns instances of WikiPageVersion' do
474
      expect(subject.versions).to all( be_a(Gitlab::Git::WikiPageVersion) )
475 476 477
    end
  end

478 479
  describe '#title_changed?' do
    using RSpec::Parameterized::TableSyntax
480

481 482
    let(:untitled_page) { described_class.new(wiki) }
    let(:directory_page) do
483 484
      create_page('parent directory/child page', 'test content')
      wiki.find_page('parent directory/child page')
485
    end
J
Jacopo 已提交
486

487
    where(:page, :title, :changed) do
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
      :untitled_page  | nil                             | false
      :untitled_page  | 'new title'                     | true

      :new_page       | nil                             | true
      :new_page       | 'test page'                     | true
      :new_page       | 'new title'                     | true

      :existing_page  | nil                             | false
      :existing_page  | 'test page'                     | false
      :existing_page  | 'test-page'                     | false
      :existing_page  | '/test page'                    | false
      :existing_page  | '/test-page'                    | false
      :existing_page  | ' test page '                   | true
      :existing_page  | 'new title'                     | true
      :existing_page  | 'new-title'                     | true

      :directory_page | nil                             | false
      :directory_page | 'parent directory/child page'   | false
      :directory_page | 'parent-directory/child page'   | false
      :directory_page | 'parent-directory/child-page'   | false
      :directory_page | 'child page'                    | false
      :directory_page | 'child-page'                    | false
      :directory_page | '/child page'                   | true
      :directory_page | 'parent directory/other'        | true
      :directory_page | 'parent-directory/other'        | true
      :directory_page | 'parent-directory / child-page' | true
      :directory_page | 'other directory/child page'    | true
      :directory_page | 'other-directory/child page'    | true
516 517 518 519 520 521
    end

    with_them do
      it 'returns the expected value' do
        subject = public_send(page)
        subject.title = title if title
J
Jacopo 已提交
522

523 524
        expect(subject.title_changed?).to be(changed)
      end
J
Jacopo 已提交
525
    end
526 527
  end

528 529
  describe '#path' do
    it 'returns the path when persisted' do
530
      expect(existing_page.path).to eq('test-page.md')
531 532 533
    end

    it 'returns nil when not persisted' do
534
      expect(new_page.path).to be_nil
535 536 537
    end
  end

538 539
  describe '#directory' do
    context 'when the page is at the root directory' do
540
      subject { existing_page }
541

542 543
      it 'returns an empty string' do
        expect(subject.directory).to eq('')
544 545 546 547
      end
    end

    context 'when the page is inside an actual directory' do
548
      subject do
549
        create_page('dir_1/dir_1_1/file', 'content')
550 551
        wiki.find_page('dir_1/dir_1_1/file')
      end
552

553 554
      it 'returns the full directory hierarchy' do
        expect(subject.directory).to eq('dir_1/dir_1_1')
555 556 557 558
      end
    end
  end

559
  describe '#historical?' do
560
    subject { existing_page }
561

562 563 564 565
    let(:old_version) { subject.versions.last.id }
    let(:old_page) { wiki.find_page(subject.title, old_version) }
    let(:latest_version) { subject.versions.first.id }
    let(:latest_page) { wiki.find_page(subject.title, latest_version) }
566

567 568
    before do
      3.times { |i| subject.update(content: "content #{i}") }
569 570 571
    end

    it 'returns true when requesting an old version' do
572
      expect(old_page.historical?).to be_truthy
573 574 575
    end

    it 'returns false when requesting latest version' do
576
      expect(latest_page.historical?).to be_falsy
577 578 579
    end

    it 'returns false when version is nil' do
580 581 582 583 584 585 586 587 588 589 590
      expect(latest_page.historical?).to be_falsy
    end

    it 'returns false when the last version is nil' do
      expect(old_page).to receive(:last_version) { nil }

      expect(old_page.historical?).to be_falsy
    end

    it 'returns false when the version is nil' do
      expect(old_page).to receive(:version) { nil }
591

592
      expect(old_page.historical?).to be_falsy
593 594 595
    end
  end

A
Alex Braha Stoll 已提交
596 597
  describe '#to_partial_path' do
    it 'returns the relative path to the partial to be used' do
598
      expect(subject.to_partial_path).to eq('projects/wikis/wiki_page')
A
Alex Braha Stoll 已提交
599 600 601
    end
  end

602
  describe '#==' do
603
    subject { existing_page }
604 605

    it 'returns true for identical wiki page' do
606
      expect(subject).to eq(subject)
607 608 609
    end

    it 'returns false for updated wiki page' do
610 611 612 613 614
      subject.update(content: "Updated content")
      updated_page = wiki.find_page('test page')

      expect(updated_page).not_to be_nil
      expect(updated_page).not_to eq(subject)
615 616 617
    end
  end

H
Hiroyuki Sato 已提交
618
  describe '#last_commit_sha' do
619
    subject { existing_page }
H
Hiroyuki Sato 已提交
620 621

    it 'returns commit sha' do
622
      expect(subject.last_commit_sha).to eq subject.last_version.sha
H
Hiroyuki Sato 已提交
623 624 625
    end

    it 'is changed after page updated' do
626
      last_commit_sha_before_update = subject.last_commit_sha
H
Hiroyuki Sato 已提交
627

628 629
      subject.update(content: "new content")
      page = wiki.find_page('test page')
H
Hiroyuki Sato 已提交
630

631
      expect(page.last_commit_sha).not_to eq last_commit_sha_before_update
H
Hiroyuki Sato 已提交
632 633 634
    end
  end

635
  describe '#hook_attrs' do
636
    it 'adds absolute urls for images in the content' do
637
      subject.attributes[:content] = 'test![WikiPage_Image](/uploads/abc/WikiPage_Image.png)'
638

639
      expect(subject.hook_attrs['content']).to eq("test![WikiPage_Image](#{Settings.gitlab.url}/uploads/abc/WikiPage_Image.png)")
640 641 642
    end
  end

643 644 645 646 647 648 649
  private

  def remove_temp_repo(path)
    FileUtils.rm_rf path
  end

  def commit_details
650
    Gitlab::Git::Wiki::CommitDetails.new(user.id, user.username, user.name, user.email, "test commit")
651 652 653 654 655 656
  end

  def create_page(name, content)
    wiki.wiki.write_page(name, :markdown, content, commit_details)
  end

657 658 659 660 661 662 663
  def get_slugs(page_or_dir)
    if page_or_dir.is_a? WikiPage
      [page_or_dir.slug]
    else
      page_or_dir.pages.present? ? page_or_dir.pages.map(&:slug) : []
    end
  end
664
end