From 5f0c1953ceb44164ab40afb0dd64edcccc09864a Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 26 Apr 2026 20:17:05 +0900 Subject: [PATCH] #317 --- .../app/controllers/wiki_pages_controller.rb | 17 +- backend/app/services/wiki/commit.rb | 4 - backend/spec/requests/tag_versions_spec.rb | 10 +- backend/spec/requests/tags_spec.rb | 79 ++++++ backend/spec/requests/wiki_spec.rb | 246 +++++++++++++----- backend/spec/services/wiki/commit_spec.rb | 62 +++-- 6 files changed, 319 insertions(+), 99 deletions(-) diff --git a/backend/app/controllers/wiki_pages_controller.rb b/backend/app/controllers/wiki_pages_controller.rb index dc6c47f..60941b8 100644 --- a/backend/app/controllers/wiki_pages_controller.rb +++ b/backend/app/controllers/wiki_pages_controller.rb @@ -91,7 +91,7 @@ class WikiPagesController < ApplicationController return head :unprocessable_entity if name.blank? || body.blank? tag_name = TagName.find_undiscard_or_create_by!(name:) - page = WikiPage.new(tag_name:, created_user: current_user, updated_user: current_user) + page = WikiPage.new(tag_name:, body:, created_user: current_user, updated_user: current_user) if page.save message = params[:message].presence Wiki::Commit.content!(page:, body:, created_user: current_user, message:) @@ -115,10 +115,16 @@ class WikiPagesController < ApplicationController page = WikiPage.find(params[:id]) base_revision_id = page.current_revision.id - if params[:title].present? && params[:title].strip != page.title - return head :unprocessable_entity + old_title = page.title + + tag = Tag.find_by(tag_name_id: page.tag_name_id) + + if tag && title != old_title + TagVersioning.ensure_snapshot!(tag, created_by_user: current_user) end + page.tag_name.update!(name: title) if title != old_title + message = params[:message].presence Wiki::Commit.content!(page:, body:, @@ -126,6 +132,11 @@ class WikiPagesController < ApplicationController message:, base_revision_id:) + if tag && title != old_title + tag.reload + TagVersionRecorder.record!(tag:, event_type: :update, created_by_user: current_user) + end + head :ok end diff --git a/backend/app/services/wiki/commit.rb b/backend/app/services/wiki/commit.rb index 4f22fc3..dde51fd 100644 --- a/backend/app/services/wiki/commit.rb +++ b/backend/app/services/wiki/commit.rb @@ -79,10 +79,6 @@ module Wiki event_type: @page.wiki_versions.exists? ? :update : :create, reason: message, created_by_user: @created_user) - tag = @page.tag_name.tag - if tag&.tag_versions&.exists? - TagVersionRecorder.record!(tag:, event_type: :update, created_by_user: @created_user) - end rev = WikiRevision.create!( wiki_page: @page, diff --git a/backend/spec/requests/tag_versions_spec.rb b/backend/spec/requests/tag_versions_spec.rb index 0175704..aae790c 100644 --- a/backend/spec/requests/tag_versions_spec.rb +++ b/backend/spec/requests/tag_versions_spec.rb @@ -215,7 +215,7 @@ RSpec.describe 'TagVersions API', type: :request do expect(versions.first['version_no']).to eq(2) end - it 'includes tag versions created by wiki updates' do + it 'does not create tag versions by wiki updates when tag has no versions yet' do wiki_tag_name = TagName.create!(name: 'tag_versions_from_wiki') wiki_tag = Tag.create!(tag_name: wiki_tag_name, category: :general) @@ -236,12 +236,8 @@ RSpec.describe 'TagVersions API', type: :request do get '/tags/versions', params: { id: wiki_tag.id } expect(response).to have_http_status(:ok) - - versions = json.fetch('versions') - - expect(json.fetch('count')).to eq(2) - expect(versions.map { |v| v['event_type'] }).to eq(['update', 'create']) - expect(versions.map { |v| v['name']['current'] }.uniq).to eq(['tag_versions_from_wiki']) + expect(json.fetch('versions')).to eq([]) + expect(json.fetch('count')).to eq(0) end end end diff --git a/backend/spec/requests/tags_spec.rb b/backend/spec/requests/tags_spec.rb index e186582..fa442fe 100644 --- a/backend/spec/requests/tags_spec.rb +++ b/backend/spec/requests/tags_spec.rb @@ -472,6 +472,54 @@ RSpec.describe 'Tags API', type: :request do expect(nico_tag.reload.category).to eq('nico') end end + + it 'PATCH で tag の name を変更すると対応する wiki version を作成する' do + wiki_page = + Wiki::Commit.create_content!( + tag_name: tag.tag_name, + body: 'wiki body before', + created_by_user: member_user, + message: 'init') + + expect { + patch "/tags/#{ tag.id }", params: { + name: 'patch_wiki_renamed_tag', + } + } + .to change(TagVersion, :count).by(2) + .and change(WikiVersion, :count).by(1) + + expect(response).to have_http_status(:ok) + + version = wiki_page.reload.wiki_versions.order(:version_no).last + + expect(version).to have_attributes( + event_type: 'update', + title: 'patch_wiki_renamed_tag', + body: 'wiki body before', + created_by_user_id: member_user.id + ) + end + + it 'tag の category だけを変更しても wiki version は作成しない' do + wiki_page = + Wiki::Commit.create_content!( + tag_name: tag.tag_name, + body: 'wiki body before', + created_by_user: member_user, + message: 'init') + + before_count = wiki_page.reload.wiki_versions.count + + expect { + patch "/tags/#{ tag.id }", params: { + category: 'meme', + } + }.to change(TagVersion, :count).by(2) + + expect(response).to have_http_status(:ok) + expect(wiki_page.reload.wiki_versions.count).to eq(before_count) + end end describe 'GET /tags/with-depth' do @@ -976,5 +1024,36 @@ RSpec.describe 'Tags API', type: :request do expect(TagImplication.where(tag:, parent_tag: child)).not_to exist end end + + it 'tag の name を変更すると対応する wiki version を作成する' do + wiki_page = + Wiki::Commit.create_content!( + tag_name: tag.tag_name, + body: 'wiki body before', + created_by_user: member_user, + message: 'init') + + expect { + put "/tags/#{ tag.id }", params: { + name: 'put_wiki_renamed_tag', + category: 'general', + aliases: 'unko', + parent_tags: '', + } + } + .to change(TagVersion, :count).by(2) + .and change(WikiVersion, :count).by(1) + + expect(response).to have_http_status(:ok) + + version = wiki_page.reload.wiki_versions.order(:version_no).last + + expect(version).to have_attributes( + event_type: 'update', + title: 'put_wiki_renamed_tag', + body: 'wiki body before', + created_by_user_id: member_user.id + ) + end end end diff --git a/backend/spec/requests/wiki_spec.rb b/backend/spec/requests/wiki_spec.rb index 020e412..d6cf88f 100644 --- a/backend/spec/requests/wiki_spec.rb +++ b/backend/spec/requests/wiki_spec.rb @@ -4,18 +4,19 @@ require 'securerandom' RSpec.describe 'Wiki API', type: :request do + def auth_headers(user) + { 'X-Transfer-Code' => user.inheritance_code } + end + let!(:user) { create_member_user! } let!(:tn) { TagName.create!(name: 'spec_wiki_title') } let!(:page) do - WikiPage.create!( - tag_name: tn, - body: 'init', - created_user: user, - updated_user: user - ).tap do |p| - Wiki::Commit.content!(page: p, body: 'init', created_user: user, message: 'init') - end + Wiki::Commit.create_content!( + tag_name: tn, + body: 'init', + created_by_user: user, + message: 'init') end describe 'GET /wiki' do @@ -42,11 +43,12 @@ RSpec.describe 'Wiki API', type: :request do context 'when wiki page exists' do it 'returns wiki page with title' do request + expect(response).to have_http_status(:ok) expect(json).to include( - 'id' => page.id, - 'title' => 'spec_wiki_title') + 'id' => page.id, + 'title' => 'spec_wiki_title') end end @@ -55,6 +57,7 @@ RSpec.describe 'Wiki API', type: :request do it 'returns 404' do request + expect(response).to have_http_status(:not_found) end end @@ -106,7 +109,13 @@ RSpec.describe 'Wiki API', type: :request do .and change(WikiRevision, :count).by(1) .and change(WikiVersion, :count).by(1) - version = page.wiki_versions.order(:version_no).last + expect(response).to have_http_status(:created) + + page_id = json.fetch('id') + expect(json.fetch('title')).to eq('TestPage') + + created_page = WikiPage.find(page_id) + version = created_page.wiki_versions.order(:version_no).last expect(version).to have_attributes( version_no: 1, @@ -116,24 +125,16 @@ RSpec.describe 'Wiki API', type: :request do created_by_user_id: member.id ) - expect(response).to have_http_status(:created) - - page_id = json.fetch('id') - expect(json.fetch('title')).to eq('TestPage') - - page = WikiPage.find(page_id) - rev = page.current_revision + rev = created_page.current_revision expect(rev).to be_present expect(rev).to be_content expect(rev.message).to eq('init') - # body が復元できること - expect(page.body).to eq("a\nb\nc") + expect(created_page.body).to eq("a\nb\nc") - # 行数とリレーションの整合 expect(rev.lines_count).to eq(3) expect(rev.wiki_revision_lines.order(:position).pluck(:position)).to eq([0, 1, 2]) - expect(rev.wiki_lines.pluck(:body)).to match_array(%w[a b c]) + expect(rev.wiki_lines.pluck(:body)).to match_array(['a', 'b', 'c']) end it 'reuses existing WikiLine rows by sha256' do @@ -166,8 +167,8 @@ RSpec.describe 'Wiki API', type: :request do expect(rev.lines_count).to eq(2) expect(WikiLine.where(body: duplicated).count).to eq(1) - expect(rev.wiki_lines.where(body: duplicated).count).to eq(1) expect(rev.wiki_revision_lines.count).to eq(2) + expect(rev.wiki_revision_lines.pluck(:wiki_line_id).uniq.size).to eq(1) end it 'normalises CRLF and strips trailing newlines' do @@ -230,14 +231,6 @@ RSpec.describe 'Wiki API', type: :request do headers: auth_headers(member) expect(response).to have_http_status(:unprocessable_entity) end - - it 'returns 422 when title mismatched (if you forbid rename here)' do - put "/wiki/#{page.id}", - params: { title: 'OtherTitle', body: 'x' }, - headers: auth_headers(member) - # 君の controller 例だと title 変更は 422 にしてた - expect(response).to have_http_status(:unprocessable_entity) - end end context 'when success' do @@ -271,29 +264,29 @@ RSpec.describe 'Wiki API', type: :request do expect(rev.base_revision_id).to eq(current_id) end - it 'wiki page に対応する tag があれば tag version も作成する' do - linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version') + it 'wiki body だけを変更しても tag version は作成しない' do + linked_tag_name = TagName.create!(name: 'wiki_body_only_tag') linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general) - linked_page = WikiPage.create!( - tag_name: linked_tag_name, - body: 'before', - created_user: member, - updated_user: member - ) - Wiki::Commit.content!( - page: linked_page, - body: 'before', - created_user: member, - message: 'init' - ) + TagVersionRecorder.record!( + tag: linked_tag, + event_type: :create, + created_by_user: member) + + linked_page = + Wiki::Commit.create_content!( + tag_name: linked_tag_name, + body: 'before', + created_by_user: member, + message: 'init') - current_id = linked_page.wiki_revisions.maximum(:id) + current_id = linked_page.current_revision.id + before_count = linked_tag.reload.tag_versions.count expect { put "/wiki/#{ linked_page.id }", params: { - title: 'wiki_linked_tag_for_version', + title: 'wiki_body_only_tag', body: 'after', message: 'edit', base_revision_id: current_id, @@ -302,15 +295,9 @@ RSpec.describe 'Wiki API', type: :request do } .to change(WikiRevision, :count).by(1) .and change(WikiVersion, :count).by(1) - .and change { linked_tag.reload.tag_versions.count }.by(1) expect(response).to have_http_status(:ok) - - version = linked_tag.reload.tag_versions.order(:version_no).last - - expect(version.event_type).to eq('update') - expect(version.name).to eq('wiki_linked_tag_for_version') - expect(version.created_by_user_id).to eq(member.id) + expect(linked_tag.reload.tag_versions.count).to eq(before_count) end end @@ -362,14 +349,17 @@ RSpec.describe 'Wiki API', type: :request do describe 'GET /wiki/search' do before do - # 追加で検索ヒット用 - TagName.create!(name: 'spec_wiki_title_2') - WikiPage.create!(tag_name: TagName.find_by!(name: 'spec_wiki_title_2'), body: 'init', - created_user: user, updated_user: user) + Wiki::Commit.create_content!( + tag_name: TagName.create!(name: 'spec_wiki_title_2'), + body: 'search body 2', + created_by_user: user, + message: 'init') - TagName.create!(name: 'unrelated_title') - WikiPage.create!(tag_name: TagName.find_by!(name: 'unrelated_title'), body: 'init', - created_user: user, updated_user: user) + Wiki::Commit.create_content!( + tag_name: TagName.create!(name: 'unrelated_title'), + body: 'unrelated body', + created_by_user: user, + message: 'init') end it 'returns up to 20 pages filtered by title like' do @@ -379,7 +369,9 @@ RSpec.describe 'Wiki API', type: :request do expect(json).to be_an(Array) titles = json.map { |p| p['title'] } - expect(titles).to include('spec_wiki_title', 'spec_wiki_title_2') + + expect(titles).to include('spec_wiki_title') + expect(titles).to include('spec_wiki_title_2') expect(titles).not_to include('unrelated_title') end @@ -430,7 +422,12 @@ RSpec.describe 'Wiki API', type: :request do it 'returns empty array when page has no revisions and filtered by id' do # 別ページを作って revision 無し tn2 = TagName.create!(name: 'spec_no_rev') - p2 = WikiPage.create!(tag_name: tn2, body: 'init', created_user: user, updated_user: user) + # 異常データ: revision 無し WikiPage を直接作る + p2 = WikiPage.create!( + tag_name: tn2, + body: 'init', + created_user: user, + updated_user: user) get "/wiki/changes?id=#{p2.id}" expect(response).to have_http_status(:ok) @@ -504,12 +501,12 @@ RSpec.describe 'Wiki API', type: :request do describe 'Wiki::Commit.redirect!' do it 'raises because redirect revisions are deprecated' do target_tag_name = TagName.create!(name: 'redirect_deprecated_target') - target = WikiPage.create!( - tag_name: target_tag_name, - body: 'target', - created_user: user, - updated_user: user - ) + target = + Wiki::Commit.create_content!( + tag_name: target_tag_name, + body: 'target', + created_by_user: user, + message: 'init') expect { Wiki::Commit.redirect!( @@ -522,4 +519,115 @@ RSpec.describe 'Wiki API', type: :request do }.to raise_error(RuntimeError, '廃止しました.') end end + + it 'wiki title を変更すると対応する tag の version を作成する' do + linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version') + linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general) + + linked_page = + Wiki::Commit.create_content!( + tag_name: linked_tag_name, + body: 'before', + created_by_user: user, + message: 'init') + + current_id = linked_page.current_revision.id + + expect { + put "/wiki/#{ linked_page.id }", + params: { + title: 'wiki_linked_tag_for_version_renamed', + body: 'after', + message: 'edit', + base_revision_id: current_id, + }, + headers: auth_headers(member) + } + .to change(WikiRevision, :count).by(1) + .and change(WikiVersion, :count).by(1) + .and change { linked_tag.reload.tag_versions.count }.by(2) + + expect(response).to have_http_status(:ok) + + linked_tag.reload + expect(linked_tag.name).to eq('wiki_linked_tag_for_version_renamed') + + versions = linked_tag.tag_versions.order(:version_no) + + expect(versions.first.event_type).to eq('create') + expect(versions.first.name).to eq('wiki_linked_tag_for_version') + + expect(versions.second.event_type).to eq('update') + expect(versions.second.name).to eq('wiki_linked_tag_for_version_renamed') + end + + it 'wiki body だけを変更しても tag version は作成しない' do + linked_tag_name = TagName.create!(name: 'wiki_body_only_tag') + linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general) + + linked_page = + Wiki::Commit.create_content!( + tag_name: linked_tag_name, + body: 'before', + created_by_user: user, + message: 'init') + + current_id = linked_page.current_revision.id + + expect { + put "/wiki/#{ linked_page.id }", + params: { + title: 'wiki_body_only_tag', + body: 'after', + message: 'edit', + base_revision_id: current_id, + }, + headers: auth_headers(member) + } + .to change(WikiRevision, :count).by(1) + .and change(WikiVersion, :count).by(1) + + expect(linked_tag.reload.tag_versions.count).to eq(0) + end + + it 'wiki title を変更すると対応する tag の version を作成する' do + linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version') + linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general) + + linked_page = + Wiki::Commit.create_content!( + tag_name: linked_tag_name, + body: 'before', + created_by_user: user, + message: 'init') + + current_id = linked_page.current_revision.id + + expect { + put "/wiki/#{ linked_page.id }", + params: { + title: 'wiki_linked_tag_for_version_renamed', + 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) + .and change { linked_tag.reload.tag_versions.count }.by(2) + + expect(response).to have_http_status(:ok) + + linked_tag.reload + expect(linked_tag.name).to eq('wiki_linked_tag_for_version_renamed') + + versions = linked_tag.tag_versions.order(:version_no) + + expect(versions.first.event_type).to eq('create') + expect(versions.first.name).to eq('wiki_linked_tag_for_version') + + expect(versions.second.event_type).to eq('update') + expect(versions.second.name).to eq('wiki_linked_tag_for_version_renamed') + end end diff --git a/backend/spec/services/wiki/commit_spec.rb b/backend/spec/services/wiki/commit_spec.rb index d8513c5..2e564cf 100644 --- a/backend/spec/services/wiki/commit_spec.rb +++ b/backend/spec/services/wiki/commit_spec.rb @@ -79,26 +79,56 @@ RSpec.describe Wiki::Commit do }.to raise_error(Wiki::Commit::Conflict) end - it 'records tag version when page has corresponding tag' do - tag_name = TagName.create!(name: 'commit_linked_tag') + it 'does not record tag version when corresponding tag has no versions' do + tag_name = TagName.create!(name: 'commit_linked_tag_without_versions') tag = Tag.create!(tag_name:, category: :general) - page = WikiPage.create!( - tag_name:, - body: '', - created_user: user, - updated_user: user - ) + + page = + described_class.create_content!( + tag_name:, + body: 'before', + created_by_user: user, + message: 'init') + + expect(tag.reload.tag_versions.count).to eq(0) + + current_revision_id = page.current_revision.id expect { described_class.content!( - page:, - body: 'body', - created_user: user, - message: 'init' - ) - } - .to change(WikiVersion, :count).by(1) - .and change { tag.reload.tag_versions.count }.by(1) + page:, + body: 'after', + created_user: user, + message: 'edit', + base_revision_id: current_revision_id) + }.to change(WikiVersion, :count).by(1) + + expect(tag.reload.tag_versions.count).to eq(0) + end + + it 'does not record tag version when corresponding tag has no versions' do + tag_name = TagName.create!(name: 'commit_linked_tag_without_versions') + tag = Tag.create!(tag_name:, category: :general) + + page = + described_class.create_content!( + tag_name:, + body: 'before', + created_by_user: user, + message: 'init') + + current_revision_id = page.current_revision.id + + expect { + described_class.content!( + page:, + body: 'after', + created_user: user, + message: 'edit', + base_revision_id: current_revision_id) + }.to change(WikiVersion, :count).by(1) + + expect(tag.reload.tag_versions.count).to eq(0) end end