From 90c1842224830ad8368e0575299cfba6abefafc7 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 26 Apr 2026 21:24:50 +0900 Subject: [PATCH] #317 --- backend/app/controllers/tags_controller.rb | 28 ++- .../tag_wiki_history_integrity_spec.rb | 160 ++++++++++++++ .../requests/wiki_body_search_pending_spec.rb | 27 +++ backend/spec/requests/wiki_conflict_spec.rb | 42 ++++ .../requests/wiki_history_integrity_spec.rb | 196 ++++++++++++++++++ .../requests/wiki_restore_pending_spec.rb | 37 ++++ .../requests/wiki_title_collision_spec.rb | 62 ++++++ .../services/wiki/commit_integrity_spec.rb | 173 ++++++++++++++++ .../services/wiki_version_recorder_spec.rb | 99 +++++++++ 9 files changed, 815 insertions(+), 9 deletions(-) create mode 100644 backend/spec/requests/tag_wiki_history_integrity_spec.rb create mode 100644 backend/spec/requests/wiki_body_search_pending_spec.rb create mode 100644 backend/spec/requests/wiki_conflict_spec.rb create mode 100644 backend/spec/requests/wiki_history_integrity_spec.rb create mode 100644 backend/spec/requests/wiki_restore_pending_spec.rb create mode 100644 backend/spec/requests/wiki_title_collision_spec.rb create mode 100644 backend/spec/services/wiki/commit_integrity_spec.rb create mode 100644 backend/spec/services/wiki_version_recorder_spec.rb diff --git a/backend/app/controllers/tags_controller.rb b/backend/app/controllers/tags_controller.rb index c0d54d1..6a81dd4 100644 --- a/backend/app/controllers/tags_controller.rb +++ b/backend/app/controllers/tags_controller.rb @@ -238,21 +238,26 @@ class TagsController < ApplicationController TagVersioning.ensure_snapshot!(tag, created_by_user: current_user) old_name = tag.name + name_changed = name != old_name + wiki_page = tag.tag_name.wiki_page if name_changed tag.update!(category:) tag.tag_name.update!(name:) - alias_names << old_name if name != old_name + alias_names << old_name if name_changed alias_names.delete(name) update_aliases!(tag, alias_names) update_parent_tags!(tag, parent_names) tag.reload - record_tag_version!(tag, - event_type: :update, - created_by_user: current_user, - name_changed: name.present? && name != old_name) + + record_tag_version!( + tag, + event_type: :update, + created_by_user: current_user, + name_changed:, + wiki_page:) end render json: TagRepr.base(tag.reload) @@ -277,6 +282,7 @@ class TagsController < ApplicationController old_name = tag.name name_changed = name.present? && name != old_name + wiki_page = tag.tag_name.wiki_page if name_changed tag.tag_name.update!(name:) if name.present? tag.update!(category:) if category.present? @@ -287,7 +293,8 @@ class TagsController < ApplicationController tag, event_type: :update, created_by_user: current_user, - name_changed:) + name_changed:, + wiki_page:) end render json: TagRepr.base(tag.reload) @@ -309,7 +316,7 @@ class TagsController < ApplicationController material: material.as_json&.merge(file:, content_type:)) end - def record_tag_version! tag, event_type:, created_by_user:, name_changed: false + def record_tag_version! tag, event_type:, created_by_user:, name_changed: false, wiki_page: nil if tag.nico? NicoTagVersionRecorder.record!(tag:, event_type:, created_by_user:) return @@ -319,10 +326,13 @@ class TagsController < ApplicationController return unless name_changed - wiki_page = tag.tag_name.wiki_page + wiki_page ||= tag.tag_name.wiki_page return unless wiki_page&.wiki_versions&.exists? - WikiVersionRecorder.record!(page: wiki_page, event_type: :update, created_by_user:) + WikiVersionRecorder.record!( + page: wiki_page, + event_type: :update, + created_by_user:) end def update_aliases! tag, alias_names diff --git a/backend/spec/requests/tag_wiki_history_integrity_spec.rb b/backend/spec/requests/tag_wiki_history_integrity_spec.rb new file mode 100644 index 0000000..909ebe3 --- /dev/null +++ b/backend/spec/requests/tag_wiki_history_integrity_spec.rb @@ -0,0 +1,160 @@ +require 'rails_helper' + +RSpec.describe 'Tag and wiki history integrity', type: :request do + let(:member_user) { create(:user, role: 'member') } + + def stub_current_user user + allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(user) + end + + def create_tag! name:, category: :general + tag_name = TagName.create!(name:) + Tag.create!(tag_name:, category:) + end + + def create_wiki_for_tag! tag:, body: 'wiki body', user: member_user + Wiki::Commit.create_content!( + tag_name: tag.tag_name, + body:, + created_by_user: user, + message: 'init') + end + + before do + stub_current_user(member_user) + end + + describe 'PATCH /tags/:id' do + it 'records wiki_version when tag name changes and tag has wiki' do + tag = create_tag!(name: 'patch_tag_wiki_before') + wiki_page = create_wiki_for_tag!(tag:, body: 'wiki body before') + + expect { + patch "/tags/#{ tag.id }", params: { + name: 'patch_tag_wiki_after', + } + } + .to change(TagVersion, :count).by(2) + .and change(WikiVersion, :count).by(1) + + expect(response).to have_http_status(:ok) + + tag.reload + wiki_page.reload + version = wiki_page.wiki_versions.order(:version_no).last + + expect(tag.name).to eq('patch_tag_wiki_after') + expect(wiki_page.title).to eq('patch_tag_wiki_after') + + expect(version).to have_attributes( + event_type: 'update', + title: 'patch_tag_wiki_after', + body: 'wiki body before', + created_by_user_id: member_user.id + ) + end + + it 'does not record wiki_version when only category changes' do + tag = create_tag!(name: 'patch_tag_category_only') + wiki_page = create_wiki_for_tag!(tag:, body: 'wiki body before') + + before_wiki_versions = wiki_page.wiki_versions.count + + expect { + patch "/tags/#{ tag.id }", params: { + category: 'meme', + } + } + .to change(TagVersion, :count).by(2) + + expect(response).to have_http_status(:ok) + + tag.reload + wiki_page.reload + + expect(tag.name).to eq('patch_tag_category_only') + expect(tag.category).to eq('meme') + expect(wiki_page.wiki_versions.count).to eq(before_wiki_versions) + end + end + + describe 'PUT /tags/:id' do + it 'records wiki_version when tag name changes and tag has wiki' do + tag = create_tag!(name: 'put_tag_wiki_before') + wiki_page = create_wiki_for_tag!(tag:, body: 'wiki body before') + + expect { + put "/tags/#{ tag.id }", params: { + name: 'put_tag_wiki_after', + category: 'general', + aliases: '', + parent_tags: '', + } + } + .to change(TagVersion, :count).by(2) + .and change(WikiVersion, :count).by(1) + + expect(response).to have_http_status(:ok) + + tag.reload + wiki_page.reload + version = wiki_page.wiki_versions.order(:version_no).last + + expect(tag.name).to eq('put_tag_wiki_after') + expect(wiki_page.title).to eq('put_tag_wiki_after') + + expect(version).to have_attributes( + event_type: 'update', + title: 'put_tag_wiki_after', + body: 'wiki body before', + created_by_user_id: member_user.id + ) + end + + it 'does not record wiki_version when only category changes' do + tag = create_tag!(name: 'put_tag_category_only') + wiki_page = create_wiki_for_tag!(tag:, body: 'wiki body before') + + before_wiki_versions = wiki_page.wiki_versions.count + + expect { + put "/tags/#{ tag.id }", params: { + name: 'put_tag_category_only', + category: 'meme', + aliases: '', + parent_tags: '', + } + } + .to change(TagVersion, :count).by(2) + + expect(response).to have_http_status(:ok) + + tag.reload + wiki_page.reload + + expect(tag.name).to eq('put_tag_category_only') + expect(tag.category).to eq('meme') + expect(wiki_page.wiki_versions.count).to eq(before_wiki_versions) + end + + it 'does not record wiki_version when only aliases change' do + tag = create_tag!(name: 'put_tag_alias_only') + wiki_page = create_wiki_for_tag!(tag:, body: 'wiki body before') + + before_wiki_versions = wiki_page.wiki_versions.count + + expect { + put "/tags/#{ tag.id }", params: { + name: 'put_tag_alias_only', + category: 'general', + aliases: 'put_tag_alias_only_alias', + parent_tags: '', + } + } + .to change(TagVersion, :count).by(2) + + expect(response).to have_http_status(:ok) + expect(wiki_page.reload.wiki_versions.count).to eq(before_wiki_versions) + end + end +end diff --git a/backend/spec/requests/wiki_body_search_pending_spec.rb b/backend/spec/requests/wiki_body_search_pending_spec.rb new file mode 100644 index 0000000..ebfc874 --- /dev/null +++ b/backend/spec/requests/wiki_body_search_pending_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +RSpec.describe 'Wiki body search', type: :request do + let!(:user) { create_member_user! } + + it 'searches wiki pages by body text' do + pending 'Wiki 本文検索実装時に有効化する' + + Wiki::Commit.create_content!( + tag_name: TagName.create!(name: 'wiki_body_search_hit'), + body: 'unique body keyword for wiki search', + created_by_user: user, + message: 'init') + + Wiki::Commit.create_content!( + tag_name: TagName.create!(name: 'wiki_body_search_miss'), + body: 'ordinary body', + created_by_user: user, + message: 'init') + + get '/wiki/search', params: { body: 'unique body keyword' } + + expect(response).to have_http_status(:ok) + expect(json.map { |page| page['title'] }).to include('wiki_body_search_hit') + expect(json.map { |page| page['title'] }).not_to include('wiki_body_search_miss') + end +end diff --git a/backend/spec/requests/wiki_conflict_spec.rb b/backend/spec/requests/wiki_conflict_spec.rb new file mode 100644 index 0000000..9ae9052 --- /dev/null +++ b/backend/spec/requests/wiki_conflict_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +RSpec.describe 'Wiki conflict handling', type: :request do + let!(:user) { create_member_user! } + + def auth_headers user + { 'X-Transfer-Code' => user.inheritance_code } + end + + it 'returns 409 when base_revision_id is stale' do + page = + Wiki::Commit.create_content!( + tag_name: TagName.create!(name: 'wiki_conflict_request'), + body: 'first', + created_by_user: user, + message: 'init') + + stale_id = page.current_revision.id + + Wiki::Commit.content!( + page:, + body: 'second', + created_user: user, + message: 'other edit', + base_revision_id: stale_id) + + put "/wiki/#{ page.id }", + params: { + title: 'wiki_conflict_request', + body: 'third', + message: 'stale', + base_revision_id: stale_id, + }, + headers: auth_headers(user) + + expect(response).to have_http_status(:conflict) + + page.reload + expect(page.body).to eq('second') + expect(page.current_revision.message).to eq('other edit') + end +end diff --git a/backend/spec/requests/wiki_history_integrity_spec.rb b/backend/spec/requests/wiki_history_integrity_spec.rb new file mode 100644 index 0000000..7f2ccce --- /dev/null +++ b/backend/spec/requests/wiki_history_integrity_spec.rb @@ -0,0 +1,196 @@ +require 'cgi' +require 'rails_helper' + +RSpec.describe 'Wiki history integrity', type: :request do + let!(:user) { create_member_user! } + + def auth_headers user + { 'X-Transfer-Code' => user.inheritance_code } + end + + def create_wiki_page title:, body: 'body', message: 'init', user: self.user + Wiki::Commit.create_content!( + tag_name: TagName.create!(name: title), + body:, + created_by_user: user, + message:) + end + + describe 'POST /wiki' do + it 'creates wiki_page, wiki_revision, and wiki_version atomically' do + expect { + post '/wiki', + params: { + title: 'wiki_history_create_atomic', + body: "a\nb\nc", + message: 'initial commit', + }, + headers: auth_headers(user) + } + .to change(WikiPage, :count).by(1) + .and change(WikiRevision, :count).by(1) + .and change(WikiVersion, :count).by(1) + + expect(response).to have_http_status(:created) + + page = WikiPage.find(json.fetch('id')) + revision = page.current_revision + version = page.wiki_versions.order(:version_no).last + + expect(page.title).to eq('wiki_history_create_atomic') + expect(page.body).to eq("a\nb\nc") + + expect(revision).to be_content + expect(revision.message).to eq('initial commit') + expect(revision.lines_count).to eq(3) + + expect(version).to have_attributes( + version_no: 1, + event_type: 'create', + title: 'wiki_history_create_atomic', + body: "a\nb\nc", + reason: 'initial commit', + created_by_user_id: user.id + ) + end + + it 'returns 422 and creates nothing when normalised body is blank' do + expect { + post '/wiki', + params: { + title: 'wiki_history_blank_body', + body: "\r\n\r\n", + message: 'blank', + }, + headers: auth_headers(user) + } + .not_to change(WikiPage, :count) + + expect(response).to have_http_status(:unprocessable_entity) + expect(WikiPage.joins(:tag_name).where(tag_names: { name: 'wiki_history_blank_body' })).not_to exist + end + + it 'returns 422 and creates no partial page when title already exists' do + create_wiki_page(title: 'wiki_history_duplicate_title', body: 'first') + + expect { + post '/wiki', + params: { + title: 'wiki_history_duplicate_title', + body: 'second', + message: 'duplicate', + }, + headers: auth_headers(user) + } + .not_to change(WikiPage, :count) + + expect(response).to have_http_status(:unprocessable_entity) + expect(WikiPage.joins(:tag_name).where(tag_names: { name: 'wiki_history_duplicate_title' }).count).to eq(1) + end + end + + describe 'PUT /wiki/:id' do + it 'updates body and records wiki_revision and wiki_version' do + page = create_wiki_page(title: 'wiki_history_update_body', body: 'before') + current_id = page.current_revision.id + + expect { + put "/wiki/#{ page.id }", + params: { + title: 'wiki_history_update_body', + body: 'after', + message: 'edit body', + base_revision_id: current_id, + }, + headers: auth_headers(user) + } + .to change(WikiRevision, :count).by(1) + .and change(WikiVersion, :count).by(1) + + expect(response).to have_http_status(:ok) + + page.reload + version = page.wiki_versions.order(:version_no).last + + expect(page.title).to eq('wiki_history_update_body') + expect(page.body).to eq('after') + + expect(version).to have_attributes( + event_type: 'update', + title: 'wiki_history_update_body', + body: 'after', + reason: 'edit body', + created_by_user_id: user.id + ) + end + + it 'renames title and records wiki_version with new title' do + page = create_wiki_page(title: 'wiki_history_rename_before', body: 'before') + current_id = page.current_revision.id + + expect { + put "/wiki/#{ page.id }", + params: { + title: 'wiki_history_rename_after', + body: 'after', + message: 'rename', + base_revision_id: current_id, + }, + headers: auth_headers(user) + } + .to change(WikiRevision, :count).by(1) + .and change(WikiVersion, :count).by(1) + + expect(response).to have_http_status(:ok) + + page.reload + version = page.wiki_versions.order(:version_no).last + + expect(page.title).to eq('wiki_history_rename_after') + expect(page.body).to eq('after') + + expect(version).to have_attributes( + event_type: 'update', + title: 'wiki_history_rename_after', + body: 'after', + reason: 'rename', + created_by_user_id: user.id + ) + end + + it 'does not change title, body, revision, or version on stale base_revision_id' do + page = create_wiki_page(title: 'wiki_history_conflict_page', body: 'first') + stale_id = page.current_revision.id + + Wiki::Commit.content!( + page:, + body: 'second', + created_user: user, + message: 'other edit', + base_revision_id: stale_id) + + page.reload + current_title = page.title + current_body = page.body + revision_count = page.wiki_revisions.count + version_count = page.wiki_versions.count + + put "/wiki/#{ page.id }", + params: { + title: 'wiki_history_conflict_renamed', + body: 'third', + message: 'stale edit', + base_revision_id: stale_id, + }, + headers: auth_headers(user) + + expect(response).to have_http_status(:conflict) + + page.reload + expect(page.title).to eq(current_title) + expect(page.body).to eq(current_body) + expect(page.wiki_revisions.count).to eq(revision_count) + expect(page.wiki_versions.count).to eq(version_count) + end + end +end diff --git a/backend/spec/requests/wiki_restore_pending_spec.rb b/backend/spec/requests/wiki_restore_pending_spec.rb new file mode 100644 index 0000000..0ceeca1 --- /dev/null +++ b/backend/spec/requests/wiki_restore_pending_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +RSpec.describe 'Wiki restore', type: :request do + let!(:user) { create_member_user! } + + def auth_headers user + { 'X-Transfer-Code' => user.inheritance_code } + end + + it 'restores wiki page to previous version' do + pending 'Wiki 版巻き戻し API 実装時に有効化する' + + page = + Wiki::Commit.create_content!( + tag_name: TagName.create!(name: 'wiki_restore_page'), + body: 'v1', + created_by_user: user, + message: 'init') + + v1 = page.wiki_versions.order(:version_no).last + + Wiki::Commit.content!( + page:, + body: 'v2', + created_user: user, + message: 'edit', + base_revision_id: page.current_revision.id) + + post "/wiki/#{ page.id }/restore", + params: { version_no: v1.version_no }, + headers: auth_headers(user) + + expect(response).to have_http_status(:ok) + expect(page.reload.body).to eq('v1') + expect(page.wiki_versions.order(:version_no).last.event_type).to eq('restore') + end +end diff --git a/backend/spec/requests/wiki_title_collision_spec.rb b/backend/spec/requests/wiki_title_collision_spec.rb new file mode 100644 index 0000000..ad02dfa --- /dev/null +++ b/backend/spec/requests/wiki_title_collision_spec.rb @@ -0,0 +1,62 @@ +require 'rails_helper' + +RSpec.describe 'Wiki title collision', type: :request do + let!(:user) { create_member_user! } + + def auth_headers user + { 'X-Transfer-Code' => user.inheritance_code } + end + + def create_wiki_page title:, body: + Wiki::Commit.create_content!( + tag_name: TagName.create!(name: title), + body:, + created_by_user: user, + message: 'init') + end + + it 'returns 422 when renaming wiki title to existing title' do + source = create_wiki_page(title: 'wiki_collision_source', body: 'source body') + create_wiki_page(title: 'wiki_collision_target', body: 'target body') + + source_revision_count = source.wiki_revisions.count + source_version_count = source.wiki_versions.count + old_title = source.title + old_body = source.body + + put "/wiki/#{ source.id }", + params: { + title: 'wiki_collision_target', + body: 'new body', + message: 'rename collision', + base_revision_id: source.current_revision.id, + }, + headers: auth_headers(user) + + expect(response).to have_http_status(:unprocessable_entity) + + source.reload + + expect(source.title).to eq(old_title) + expect(source.body).to eq(old_body) + expect(source.wiki_revisions.count).to eq(source_revision_count) + expect(source.wiki_versions.count).to eq(source_version_count) + end + + it 'returns 422 when creating wiki with existing title' do + create_wiki_page(title: 'wiki_collision_create', body: 'already exists') + + expect { + post '/wiki', + params: { + title: 'wiki_collision_create', + body: 'new body', + message: 'duplicate create', + }, + headers: auth_headers(user) + } + .not_to change(WikiPage, :count) + + expect(response).to have_http_status(:unprocessable_entity) + end +end diff --git a/backend/spec/services/wiki/commit_integrity_spec.rb b/backend/spec/services/wiki/commit_integrity_spec.rb new file mode 100644 index 0000000..4a08de5 --- /dev/null +++ b/backend/spec/services/wiki/commit_integrity_spec.rb @@ -0,0 +1,173 @@ +require 'digest' +require 'rails_helper' + +RSpec.describe Wiki::Commit do + let(:user) { create_member_user! } + + def create_page title:, body: 'initial body' + described_class.create_content!( + tag_name: TagName.create!(name: title), + body:, + created_by_user: user, + message: 'init') + end + + describe '.create_content!' do + it 'creates page, revision, and version with normalised body' do + expect { + described_class.create_content!( + tag_name: TagName.create!(name: 'commit_integrity_create'), + body: "a\r\nb\r\n\r\n", + created_by_user: user, + message: 'init') + } + .to change(WikiPage, :count).by(1) + .and change(WikiRevision, :count).by(1) + .and change(WikiVersion, :count).by(1) + + page = WikiPage.joins(:tag_name).find_by!(tag_names: { name: 'commit_integrity_create' }) + revision = page.current_revision + version = page.wiki_versions.order(:version_no).last + + expect(page.body).to eq("a\nb") + expect(revision.lines_count).to eq(2) + expect(version.body).to eq("a\nb") + expect(version.reason).to eq('init') + end + + it 'rejects body that becomes blank after normalisation' do + tag_name = TagName.create!(name: 'commit_integrity_blank') + + expect { + described_class.create_content!( + tag_name:, + body: "\r\n\r\n", + created_by_user: user, + message: 'blank') + } + .to raise_error(ActiveRecord::RecordInvalid) + + expect(WikiPage.where(tag_name:)).not_to exist + end + end + + describe '.content!' do + it 'updates page body, revision, and version' do + page = create_page(title: 'commit_integrity_update', body: 'before') + current_id = page.current_revision.id + + expect { + described_class.content!( + page:, + body: 'after', + created_user: user, + message: 'edit', + base_revision_id: current_id) + } + .to change(WikiRevision, :count).by(1) + .and change(WikiVersion, :count).by(1) + + page.reload + version = page.wiki_versions.order(:version_no).last + + expect(page.body).to eq('after') + expect(version.body).to eq('after') + expect(version.reason).to eq('edit') + end + + it 'does not record tag_version on body-only wiki update' do + tag_name = TagName.create!(name: 'commit_integrity_linked_tag') + tag = Tag.create!(tag_name:, category: :general) + + page = + described_class.create_content!( + tag_name:, + body: 'before', + created_by_user: user, + message: 'init') + + TagVersionRecorder.record!( + tag:, + event_type: :create, + created_by_user: user) + + before_count = tag.reload.tag_versions.count + + described_class.content!( + page:, + body: 'after', + created_user: user, + message: 'edit', + base_revision_id: page.current_revision.id) + + expect(tag.reload.tag_versions.count).to eq(before_count) + end + + it 'raises conflict and leaves page, revision, and version unchanged' do + page = create_page(title: 'commit_integrity_conflict', body: 'first') + stale_id = page.current_revision.id + + described_class.content!( + page:, + body: 'second', + created_user: user, + message: 'second', + base_revision_id: stale_id) + + page.reload + before_body = page.body + before_revision_count = page.wiki_revisions.count + before_version_count = page.wiki_versions.count + + expect { + described_class.content!( + page:, + body: 'third', + created_user: user, + message: 'stale', + base_revision_id: stale_id) + } + .to raise_error(Wiki::Commit::Conflict) + + page.reload + expect(page.body).to eq(before_body) + expect(page.wiki_revisions.count).to eq(before_revision_count) + expect(page.wiki_versions.count).to eq(before_version_count) + end + + it 'deduplicates duplicated missing wiki lines' do + page = create_page(title: 'commit_integrity_dedup', body: 'before') + duplicated = 'commit_integrity_duplicate_line' + + described_class.content!( + page:, + body: "#{ duplicated }\n#{ duplicated }", + created_user: user, + message: 'dedup', + base_revision_id: page.current_revision.id) + + revision = page.reload.current_revision + + expect(WikiLine.where(body: duplicated).count).to eq(1) + expect(revision.wiki_revision_lines.count).to eq(2) + expect(revision.wiki_revision_lines.pluck(:wiki_line_id).uniq.size).to eq(1) + end + end + + describe '.redirect!' do + it 'raises because redirect revisions are deprecated' do + page = create_page(title: 'commit_integrity_redirect_source', body: 'source') + target = create_page(title: 'commit_integrity_redirect_target', body: 'target') + + expect { + described_class.redirect!( + page:, + redirect_page: target, + created_user: user, + message: 'redirect', + base_revision_id: page.current_revision.id) + } + .to raise_error(RuntimeError, '廃止しました.') + end + end +end diff --git a/backend/spec/services/wiki_version_recorder_spec.rb b/backend/spec/services/wiki_version_recorder_spec.rb new file mode 100644 index 0000000..9f1c6dd --- /dev/null +++ b/backend/spec/services/wiki_version_recorder_spec.rb @@ -0,0 +1,99 @@ +require 'rails_helper' + +RSpec.describe WikiVersionRecorder do + let(:user) { create_member_user! } + + def create_page title:, body: 'body' + Wiki::Commit.create_content!( + tag_name: TagName.create!(name: title), + body:, + created_by_user: user, + message: 'init') + end + + describe '.record!' do + it 'records title, body, reason, user, and version number' do + page = create_page(title: 'wiki_version_recorder_basic', body: 'body') + + expect { + described_class.record!( + page:, + event_type: :update, + reason: 'manual reason', + created_by_user: user) + } + .to change { page.reload.wiki_versions.count }.by(1) + + version = page.wiki_versions.order(:version_no).last + + expect(version).to have_attributes( + version_no: 2, + event_type: 'update', + title: 'wiki_version_recorder_basic', + body: 'body', + reason: 'manual reason', + created_by_user_id: user.id + ) + end + + it 'does not create duplicated update version for identical snapshot' do + page = create_page(title: 'wiki_version_recorder_duplicate', body: 'body') + + described_class.record!( + page:, + event_type: :update, + reason: nil, + created_by_user: user) + + before_count = page.reload.wiki_versions.count + + described_class.record!( + page:, + event_type: :update, + reason: nil, + created_by_user: user) + + expect(page.reload.wiki_versions.count).to eq(before_count) + end + + it 'creates update version when title changes' do + page = create_page(title: 'wiki_version_recorder_title_before', body: 'body') + page.tag_name.update!(name: 'wiki_version_recorder_title_after') + + expect { + described_class.record!( + page:, + event_type: :update, + reason: 'rename', + created_by_user: user) + } + .to change { page.reload.wiki_versions.count }.by(1) + + version = page.wiki_versions.order(:version_no).last + + expect(version.title).to eq('wiki_version_recorder_title_after') + expect(version.body).to eq('body') + expect(version.reason).to eq('rename') + end + + it 'creates update version when body changes' do + page = create_page(title: 'wiki_version_recorder_body', body: 'before') + page.update!(body: 'after') + + expect { + described_class.record!( + page:, + event_type: :update, + reason: 'body', + created_by_user: user) + } + .to change { page.reload.wiki_versions.count }.by(1) + + version = page.wiki_versions.order(:version_no).last + + expect(version.title).to eq('wiki_version_recorder_body') + expect(version.body).to eq('after') + expect(version.reason).to eq('body') + end + end +end