This commit is contained in:
2026-04-26 20:17:05 +09:00
parent 6ac044278f
commit 5f0c1953ce
6 changed files with 319 additions and 99 deletions
@@ -91,7 +91,7 @@ class WikiPagesController < ApplicationController
return head :unprocessable_entity if name.blank? || body.blank? return head :unprocessable_entity if name.blank? || body.blank?
tag_name = TagName.find_undiscard_or_create_by!(name:) 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 if page.save
message = params[:message].presence message = params[:message].presence
Wiki::Commit.content!(page:, body:, created_user: current_user, message:) Wiki::Commit.content!(page:, body:, created_user: current_user, message:)
@@ -115,10 +115,16 @@ class WikiPagesController < ApplicationController
page = WikiPage.find(params[:id]) page = WikiPage.find(params[:id])
base_revision_id = page.current_revision.id base_revision_id = page.current_revision.id
if params[:title].present? && params[:title].strip != page.title old_title = page.title
return head :unprocessable_entity
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 end
page.tag_name.update!(name: title) if title != old_title
message = params[:message].presence message = params[:message].presence
Wiki::Commit.content!(page:, Wiki::Commit.content!(page:,
body:, body:,
@@ -126,6 +132,11 @@ class WikiPagesController < ApplicationController
message:, message:,
base_revision_id:) base_revision_id:)
if tag && title != old_title
tag.reload
TagVersionRecorder.record!(tag:, event_type: :update, created_by_user: current_user)
end
head :ok head :ok
end end
-4
View File
@@ -79,10 +79,6 @@ module Wiki
event_type: @page.wiki_versions.exists? ? :update : :create, event_type: @page.wiki_versions.exists? ? :update : :create,
reason: message, reason: message,
created_by_user: @created_user) 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!( rev = WikiRevision.create!(
wiki_page: @page, wiki_page: @page,
+3 -7
View File
@@ -215,7 +215,7 @@ RSpec.describe 'TagVersions API', type: :request do
expect(versions.first['version_no']).to eq(2) expect(versions.first['version_no']).to eq(2)
end 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_name = TagName.create!(name: 'tag_versions_from_wiki')
wiki_tag = Tag.create!(tag_name: wiki_tag_name, category: :general) 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 } get '/tags/versions', params: { id: wiki_tag.id }
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json.fetch('versions')).to eq([])
versions = json.fetch('versions') expect(json.fetch('count')).to eq(0)
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'])
end end
end end
end end
+79
View File
@@ -472,6 +472,54 @@ RSpec.describe 'Tags API', type: :request do
expect(nico_tag.reload.category).to eq('nico') expect(nico_tag.reload.category).to eq('nico')
end end
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 end
describe 'GET /tags/with-depth' do 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 expect(TagImplication.where(tag:, parent_tag: child)).not_to exist
end end
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
end end
+177 -69
View File
@@ -4,18 +4,19 @@ require 'securerandom'
RSpec.describe 'Wiki API', type: :request do RSpec.describe 'Wiki API', type: :request do
def auth_headers(user)
{ 'X-Transfer-Code' => user.inheritance_code }
end
let!(:user) { create_member_user! } let!(:user) { create_member_user! }
let!(:tn) { TagName.create!(name: 'spec_wiki_title') } let!(:tn) { TagName.create!(name: 'spec_wiki_title') }
let!(:page) do let!(:page) do
WikiPage.create!( Wiki::Commit.create_content!(
tag_name: tn, tag_name: tn,
body: 'init', body: 'init',
created_user: user, created_by_user: user,
updated_user: user message: 'init')
).tap do |p|
Wiki::Commit.content!(page: p, body: 'init', created_user: user, message: 'init')
end
end end
describe 'GET /wiki' do describe 'GET /wiki' do
@@ -42,11 +43,12 @@ RSpec.describe 'Wiki API', type: :request do
context 'when wiki page exists' do context 'when wiki page exists' do
it 'returns wiki page with title' do it 'returns wiki page with title' do
request request
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json).to include( expect(json).to include(
'id' => page.id, 'id' => page.id,
'title' => 'spec_wiki_title') 'title' => 'spec_wiki_title')
end end
end end
@@ -55,6 +57,7 @@ RSpec.describe 'Wiki API', type: :request do
it 'returns 404' do it 'returns 404' do
request request
expect(response).to have_http_status(:not_found) expect(response).to have_http_status(:not_found)
end end
end end
@@ -106,7 +109,13 @@ RSpec.describe 'Wiki API', type: :request do
.and change(WikiRevision, :count).by(1) .and change(WikiRevision, :count).by(1)
.and change(WikiVersion, :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( expect(version).to have_attributes(
version_no: 1, version_no: 1,
@@ -116,24 +125,16 @@ RSpec.describe 'Wiki API', type: :request do
created_by_user_id: member.id created_by_user_id: member.id
) )
expect(response).to have_http_status(:created) rev = created_page.current_revision
page_id = json.fetch('id')
expect(json.fetch('title')).to eq('TestPage')
page = WikiPage.find(page_id)
rev = page.current_revision
expect(rev).to be_present expect(rev).to be_present
expect(rev).to be_content expect(rev).to be_content
expect(rev.message).to eq('init') expect(rev.message).to eq('init')
# body が復元できること expect(created_page.body).to eq("a\nb\nc")
expect(page.body).to eq("a\nb\nc")
# 行数とリレーションの整合
expect(rev.lines_count).to eq(3) expect(rev.lines_count).to eq(3)
expect(rev.wiki_revision_lines.order(:position).pluck(:position)).to eq([0, 1, 2]) 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 end
it 'reuses existing WikiLine rows by sha256' do 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(rev.lines_count).to eq(2)
expect(WikiLine.where(body: duplicated).count).to eq(1) 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.count).to eq(2)
expect(rev.wiki_revision_lines.pluck(:wiki_line_id).uniq.size).to eq(1)
end end
it 'normalises CRLF and strips trailing newlines' do it 'normalises CRLF and strips trailing newlines' do
@@ -230,14 +231,6 @@ RSpec.describe 'Wiki API', type: :request do
headers: auth_headers(member) headers: auth_headers(member)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_entity)
end 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 end
context 'when success' do context 'when success' do
@@ -271,29 +264,29 @@ RSpec.describe 'Wiki API', type: :request do
expect(rev.base_revision_id).to eq(current_id) expect(rev.base_revision_id).to eq(current_id)
end end
it 'wiki page に対応する tag があれば tag version 作成する' do it 'wiki body だけを変更しても tag version 作成しない' do
linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version') linked_tag_name = TagName.create!(name: 'wiki_body_only_tag')
linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general) 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!( TagVersionRecorder.record!(
page: linked_page, tag: linked_tag,
body: 'before', event_type: :create,
created_user: member, created_by_user: member)
message: 'init'
)
current_id = linked_page.wiki_revisions.maximum(:id) linked_page =
Wiki::Commit.create_content!(
tag_name: linked_tag_name,
body: 'before',
created_by_user: member,
message: 'init')
current_id = linked_page.current_revision.id
before_count = linked_tag.reload.tag_versions.count
expect { expect {
put "/wiki/#{ linked_page.id }", put "/wiki/#{ linked_page.id }",
params: { params: {
title: 'wiki_linked_tag_for_version', title: 'wiki_body_only_tag',
body: 'after', body: 'after',
message: 'edit', message: 'edit',
base_revision_id: current_id, base_revision_id: current_id,
@@ -302,15 +295,9 @@ RSpec.describe 'Wiki API', type: :request do
} }
.to change(WikiRevision, :count).by(1) .to change(WikiRevision, :count).by(1)
.and change(WikiVersion, :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) expect(response).to have_http_status(:ok)
expect(linked_tag.reload.tag_versions.count).to eq(before_count)
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)
end end
end end
@@ -362,14 +349,17 @@ RSpec.describe 'Wiki API', type: :request do
describe 'GET /wiki/search' do describe 'GET /wiki/search' do
before do before do
# 追加で検索ヒット用 Wiki::Commit.create_content!(
TagName.create!(name: 'spec_wiki_title_2') tag_name: TagName.create!(name: 'spec_wiki_title_2'),
WikiPage.create!(tag_name: TagName.find_by!(name: 'spec_wiki_title_2'), body: 'init', body: 'search body 2',
created_user: user, updated_user: user) created_by_user: user,
message: 'init')
TagName.create!(name: 'unrelated_title') Wiki::Commit.create_content!(
WikiPage.create!(tag_name: TagName.find_by!(name: 'unrelated_title'), body: 'init', tag_name: TagName.create!(name: 'unrelated_title'),
created_user: user, updated_user: user) body: 'unrelated body',
created_by_user: user,
message: 'init')
end end
it 'returns up to 20 pages filtered by title like' do 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) expect(json).to be_an(Array)
titles = json.map { |p| p['title'] } 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') expect(titles).not_to include('unrelated_title')
end 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 it 'returns empty array when page has no revisions and filtered by id' do
# 別ページを作って revision 無し # 別ページを作って revision 無し
tn2 = TagName.create!(name: 'spec_no_rev') 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}" get "/wiki/changes?id=#{p2.id}"
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@@ -504,12 +501,12 @@ RSpec.describe 'Wiki API', type: :request do
describe 'Wiki::Commit.redirect!' do describe 'Wiki::Commit.redirect!' do
it 'raises because redirect revisions are deprecated' do it 'raises because redirect revisions are deprecated' do
target_tag_name = TagName.create!(name: 'redirect_deprecated_target') target_tag_name = TagName.create!(name: 'redirect_deprecated_target')
target = WikiPage.create!( target =
tag_name: target_tag_name, Wiki::Commit.create_content!(
body: 'target', tag_name: target_tag_name,
created_user: user, body: 'target',
updated_user: user created_by_user: user,
) message: 'init')
expect { expect {
Wiki::Commit.redirect!( Wiki::Commit.redirect!(
@@ -522,4 +519,115 @@ RSpec.describe 'Wiki API', type: :request do
}.to raise_error(RuntimeError, '廃止しました.') }.to raise_error(RuntimeError, '廃止しました.')
end end
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 end
+46 -16
View File
@@ -79,26 +79,56 @@ RSpec.describe Wiki::Commit do
}.to raise_error(Wiki::Commit::Conflict) }.to raise_error(Wiki::Commit::Conflict)
end end
it 'records tag version when page has corresponding tag' do it 'does not record tag version when corresponding tag has no versions' do
tag_name = TagName.create!(name: 'commit_linked_tag') tag_name = TagName.create!(name: 'commit_linked_tag_without_versions')
tag = Tag.create!(tag_name:, category: :general) tag = Tag.create!(tag_name:, category: :general)
page = WikiPage.create!(
tag_name:, page =
body: '', described_class.create_content!(
created_user: user, tag_name:,
updated_user: user 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 { expect {
described_class.content!( described_class.content!(
page:, page:,
body: 'body', body: 'after',
created_user: user, created_user: user,
message: 'init' message: 'edit',
) base_revision_id: current_revision_id)
} }.to change(WikiVersion, :count).by(1)
.to change(WikiVersion, :count).by(1)
.and change { tag.reload.tag_versions.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
end end