Reviewed-on: #432 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #432 でマージされました。
このコミットが含まれているのは:
@@ -222,28 +222,26 @@ RSpec.describe 'Materials API', type: :request do
|
||||
expect(queries).to be_empty
|
||||
end
|
||||
|
||||
it 'reads the description even when the legacy revision has different content' do
|
||||
page = seed_wiki_page(tag_name: tag.tag_name('ja'), body: 'legacy body',
|
||||
it 'reads the description even when the stored version has different content' do
|
||||
page = seed_description(tag_name: tag.tag_name('ja'), body: 'legacy body',
|
||||
created_by_user: member_user)
|
||||
TagDescription.find_by!(tag_id: tag.id, language_code: 'ja')
|
||||
.update!(body: 'description body')
|
||||
get_material_without_legacy_reads
|
||||
expect(json.fetch('wiki_page_body')).to eq('description body')
|
||||
expect(page.current_revision.body).to eq('legacy body')
|
||||
expect(current_description_version(page).body).to eq('legacy body')
|
||||
end
|
||||
|
||||
it 'uses tag_id rather than description ID without a Wiki page or TagName' do
|
||||
description = TagDescription.create!(id: tag.id + 1000, tag:, language_code: 'ja',
|
||||
it 'uses tag_id rather than description Id. without a Wiki page' do
|
||||
description = TagDescription.create!(tag:, language_code: 'ja',
|
||||
body: 'independent body', version_no: 1)
|
||||
tag.tag_names.update_all(tag_id: nil)
|
||||
get_material_without_legacy_reads
|
||||
expect(description.id).not_to eq(tag.id)
|
||||
expect(json.fetch('wiki_page_body')).to eq('independent body')
|
||||
expect(json.fetch('tag').fetch('has_wiki')).to be(true)
|
||||
end
|
||||
|
||||
it 'returns nil when only legacy Wiki content exists' do
|
||||
seed_wiki_page(tag_name: tag.tag_name('ja'), body: 'legacy only',
|
||||
it 'returns nil when only description history remains' do
|
||||
seed_description(tag_name: tag.tag_name('ja'), body: 'legacy only',
|
||||
created_by_user: member_user)
|
||||
TagDescription.where(tag_id: tag.id).delete_all
|
||||
get_material_without_legacy_reads
|
||||
|
||||
@@ -4,34 +4,21 @@ RSpec.describe 'Tag description Wiki compatibility', type: :request do
|
||||
include WikiRecords
|
||||
let!(:user) { create_member_user! }
|
||||
let!(:tag_name) { create(:tag_name, name: 'description_contract') }
|
||||
let!(:page) do
|
||||
seed_wiki_page(
|
||||
tag_name:, body: 'old-revision', created_by_user: user)
|
||||
end
|
||||
let!(:revisions) do
|
||||
first = page.current_revision
|
||||
second = Wiki::Commit.legacy_content!(page:, body: 'old-second', created_user: user)
|
||||
third = Wiki::Commit.legacy_content!(page:, body: 'old-third', created_user: user)
|
||||
[first, second, third]
|
||||
end
|
||||
let!(:tag) { create(:tag, primary_tag_name: tag_name) }
|
||||
let!(:tag) { tag_name.tag }
|
||||
let!(:versions) do
|
||||
revisions.each_with_index.map do |revision, index|
|
||||
['A', 'A', 'new-version'].each_with_index.map do |body, index|
|
||||
TagDescriptionVersion.create!(
|
||||
id: revision.id, tag_id: tag.id, language_code: 'ja',
|
||||
version_no: index + 1, event_type: index.zero? ? 'create' : 'update',
|
||||
body: index == 2 ? 'new-version' : 'A',
|
||||
created_by_user_id: user.id, created_at: revision.created_at)
|
||||
tag_id: tag.id, language_code: 'ja', version_no: index + 1,
|
||||
event_type: index.zero? ? 'create' : 'update', body:,
|
||||
created_by_user_id: user.id, created_at: Time.current)
|
||||
end
|
||||
end
|
||||
let!(:description) do
|
||||
TagDescription.create!(
|
||||
id: page.id, tag:, language_code: 'ja', version_no: 3, body: 'new',
|
||||
tag:, language_code: 'ja', version_no: 3, body: 'new',
|
||||
created_by_user_id: user.id, updated_by_user_id: user.id)
|
||||
end
|
||||
|
||||
before { page.update_columns(body: 'old') }
|
||||
|
||||
# Observe real SELECTs, including cached queries, only during the request.
|
||||
def without_legacy_reads
|
||||
queries = []
|
||||
@@ -70,7 +57,7 @@ RSpec.describe 'Tag description Wiki compatibility', type: :request do
|
||||
it 'does not fall back to a revision absent from new history' do
|
||||
versions.first.destroy!
|
||||
without_legacy_reads do
|
||||
get "/wiki/#{ description.id }", params: { version: revisions.first.id }
|
||||
get "/wiki/#{ description.id }", params: { version: versions.first.id }
|
||||
end
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
@@ -132,16 +119,6 @@ RSpec.describe 'Tag description Wiki compatibility', type: :request do
|
||||
'pred' => versions.first.id, 'succ' => versions.last.id)
|
||||
end
|
||||
|
||||
it 'ignores changes to same-title legacy content and revision metadata' do
|
||||
get "/wiki/title/#{ tag_name.name }"
|
||||
original = json
|
||||
page.update_columns(body: 'changed legacy body')
|
||||
revisions.last.update_columns(message: 'legacy message', created_at: 1.day.ago)
|
||||
revisions.last.wiki_lines.update_all(body: 'changed legacy revision')
|
||||
get "/wiki/title/#{ tag_name.name }"
|
||||
expect(json).to eq(original)
|
||||
expect(json.fetch('body')).to eq('new')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'exists' do
|
||||
@@ -155,28 +132,6 @@ RSpec.describe 'Tag description Wiki compatibility', type: :request do
|
||||
expect(response).to have_http_status(:no_content)
|
||||
end
|
||||
|
||||
context 'without a legacy page' do
|
||||
before do
|
||||
WikiVersion.where(wiki_page_id: page.id).delete_all
|
||||
page.destroy!
|
||||
end
|
||||
|
||||
it 'recognises the description ID alone' do
|
||||
get "/wiki/#{ description.id }/exists"
|
||||
expect(response).to have_http_status(:no_content)
|
||||
end
|
||||
|
||||
it 'recognises the TagName alone' do
|
||||
get "/wiki/title/#{ tag_name.name }/exists"
|
||||
expect(response).to have_http_status(:no_content)
|
||||
end
|
||||
|
||||
it 'shows the description by title' do
|
||||
get "/wiki/title/#{ tag_name.name }"
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to include('body' => 'new', 'revision_id' => versions.last.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'diff' do
|
||||
@@ -229,39 +184,8 @@ RSpec.describe 'Tag description Wiki compatibility', type: :request do
|
||||
'title' => tag_name.name)
|
||||
end
|
||||
|
||||
it 'ignores legacy-only revisions and metadata changes' do
|
||||
get '/wiki/changes', params: { id: description.id }
|
||||
original = json
|
||||
revisions.first.update_columns(message: 'legacy changed', created_at: 1.day.ago)
|
||||
WikiRevision.create!(
|
||||
wiki_page: page, created_user: user, kind: :content,
|
||||
tree_sha256: Digest::SHA256.hexdigest('legacy only'), lines_count: 0)
|
||||
get '/wiki/changes', params: { id: description.id }
|
||||
expect(json).to eq(original)
|
||||
expect(json.map { |row| row.fetch('revision_id') }).to eq(versions.reverse.map(&:id))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'mixed changes' do
|
||||
it 'excludes unmigrated history and migrated legacy-only revisions' do
|
||||
legacy_page = seed_wiki_page(
|
||||
tag_name: create(:tag_name, name: 'Help_mixed'),
|
||||
body: 'help', created_by_user: user)
|
||||
legacy_revision = legacy_page.current_revision
|
||||
extra = WikiRevision.create!(
|
||||
wiki_page: page, created_user: user, kind: :content,
|
||||
tree_sha256: Digest::SHA256.hexdigest('legacy only'), lines_count: 0)
|
||||
|
||||
get '/wiki/changes'
|
||||
expect(response).to have_http_status(:ok)
|
||||
migrated = json.select { |row| row.fetch('wiki_page').fetch('id') == description.id }
|
||||
expect(migrated.map { |row| row.fetch('revision_id') }).to eq(versions.reverse.map(&:id))
|
||||
expect(json.map { |row| row.fetch('revision_id') }).not_to include(extra.id)
|
||||
expect(json).not_to include(include(
|
||||
'revision_id' => legacy_revision.id,
|
||||
'wiki_page' => include('id' => legacy_page.id, 'title' => 'Help_mixed')))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'index' do
|
||||
[nil, 'description_contract'].each do |title|
|
||||
|
||||
@@ -1,280 +1,93 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Tag description Wiki dual writes', type: :request do
|
||||
RSpec.describe 'Tag description Wiki facade writes', type: :request do
|
||||
include WikiRecords
|
||||
let!(:user) { create_member_user! }
|
||||
let!(:tag_name) { create(:tag_name, name: 'description_write') }
|
||||
let(:tag) { create(:tag, primary_tag_name: tag_name) }
|
||||
let!(:tag) { create(:tag, primary_name: 'description_write') }
|
||||
let(:headers) { { 'X-Transfer-Code' => user.inheritance_code } }
|
||||
|
||||
def stored_state
|
||||
[WikiPage, WikiRevision, WikiVersion, TagDescription, TagDescriptionVersion,
|
||||
TagName, TagVersion].map do |model|
|
||||
[Tag, TagName, TagVersion, TagDescription, TagDescriptionVersion].map do |model|
|
||||
model.order(:id).map(&:attributes)
|
||||
end
|
||||
end
|
||||
|
||||
# Fail after a real INSERT/UPDATE, exercising rollback rather than a mock write.
|
||||
def fail_save model
|
||||
failure = Class.new(StandardError)
|
||||
callback = ->(_record) { raise failure, 'injected persistence failure' }
|
||||
model.set_callback(:save, :after, callback)
|
||||
expect { yield }.to raise_error(failure)
|
||||
ensure
|
||||
model.skip_callback(:save, :after, callback)
|
||||
end
|
||||
|
||||
describe 'POST /wiki' do
|
||||
before { tag }
|
||||
|
||||
def create_description
|
||||
post '/wiki', params: { title: tag_name.name, body: 'initial', message: 'create' },
|
||||
headers: headers
|
||||
end
|
||||
|
||||
it 'creates a description with matching Wiki content and histories' do
|
||||
expect { create_description }
|
||||
.to change(WikiPage, :count).by(1)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
.and change(TagDescription, :count).by(1)
|
||||
.and change(TagDescriptionVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:created)
|
||||
description = TagDescription.find_by!(tag_id: tag.id, language_code: 'ja')
|
||||
expect(description).to have_attributes(body: 'initial', version_no: 1)
|
||||
page = WikiPage.find_by!(tag_name:)
|
||||
expect(page.body).to eq(description.body)
|
||||
expect(page.current_revision.body).to eq(description.body)
|
||||
expect(page.wiki_versions.sole.body).to eq(description.body)
|
||||
expect(json.fetch('id')).to eq(description.id)
|
||||
expect(TagDescriptionVersion.find_by!(tag_id: tag.id, language_code: 'ja'))
|
||||
.to have_attributes(body: 'initial', version_no: 1, created_by_user_id: user.id)
|
||||
end
|
||||
|
||||
it 'reuses an existing compatibility page and synchronises its histories' do
|
||||
page = Wiki::Commit.create_legacy_content!(
|
||||
tag_name:, body: 'legacy', created_by_user: user)
|
||||
expect { create_description }
|
||||
.to change(WikiPage, :count).by(0)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:created)
|
||||
description = TagDescription.find_by!(tag_id: tag.id)
|
||||
expect(page.reload.body).to eq(description.body)
|
||||
expect(TagDescriptionVersion.find(page.current_revision.id).body).to eq(page.body)
|
||||
end
|
||||
|
||||
['missing_description', 'orphan_description'].each do |title|
|
||||
it "rejects the bare title #{ title } without writing any resource" do
|
||||
create(:tag_name, name: title) if title == 'orphan_description'
|
||||
before_state = stored_state
|
||||
|
||||
post '/wiki', params: { title:, body: 'initial' }, headers: headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(json).to include('type' => 'validation_error')
|
||||
expect(stored_state).to eq(before_state)
|
||||
end
|
||||
end
|
||||
|
||||
[false, true].each do |linked|
|
||||
it "rejects namespaced creation with Tag present: #{ linked }" do
|
||||
name = create(:tag_name, name: 'Help:frozen')
|
||||
create(:tag, primary_tag_name: name) if linked
|
||||
before_state = stored_state
|
||||
post '/wiki', params: { title: name.name, body: 'legacy' }, headers: headers
|
||||
expect(response.status).to be_between(400, 499)
|
||||
expect(stored_state).to eq(before_state)
|
||||
end
|
||||
end
|
||||
|
||||
[WikiPage, WikiRevision, WikiVersion, TagDescription, TagDescriptionVersion].each do |model|
|
||||
it "rolls back new description creation when #{ model.name } persistence fails" do
|
||||
before_state = stored_state
|
||||
aggregate_failures do
|
||||
fail_save(model) { create_description }
|
||||
expect(stored_state).to eq(before_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates missing compatibility data without writing an ID-colliding Help page' do
|
||||
description = TagDescription.create!(
|
||||
id: 500, tag:, language_code: 'ja', body: 'before', version_no: 1)
|
||||
help = WikiPage.create!(id: description.id,
|
||||
tag_name: create(:tag_name, name: 'Help:unrelated'),
|
||||
body: 'unrelated', created_user: user, updated_user: user)
|
||||
Wiki::Commit.legacy_content!(page: help, body: help.body, created_user: user)
|
||||
before_help = help.attributes
|
||||
it 'creates a native description and exposes its version ID as revision ID' do
|
||||
expect do
|
||||
put "/wiki/#{ description.id }", params: { title: tag_name.name, body: 'after' },
|
||||
headers: headers
|
||||
end.to change(WikiPage, :count).by(1)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(description.reload).to have_attributes(id: 500, body: 'after')
|
||||
expect(help.reload.attributes).to eq(before_help)
|
||||
compatibility = WikiPage.find_by!(tag_name:)
|
||||
expect(compatibility.body).to eq(description.body)
|
||||
expect(compatibility.current_revision.body).to eq(description.body)
|
||||
post '/wiki', params: { title: tag.name('ja'), body: 'initial' }, headers: headers
|
||||
end.to change(TagDescription, :count).by(1)
|
||||
.and change(TagDescriptionVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:created)
|
||||
description = TagDescription.find(json.fetch('id'))
|
||||
expect(description).to have_attributes(tag_id: tag.id, body: 'initial', version_no: 1)
|
||||
get "/wiki/#{ description.id }"
|
||||
expect(json.fetch('revision_id')).to eq(current_description_version(description).id)
|
||||
end
|
||||
|
||||
describe 'PUT /wiki/:id' do
|
||||
let!(:page) do
|
||||
tag
|
||||
seed_wiki_page(
|
||||
tag_name:, body: 'before', created_by_user: user)
|
||||
end
|
||||
let!(:first_revision) { page.current_revision }
|
||||
|
||||
context 'with an existing description' do
|
||||
let!(:description) do
|
||||
TagDescription.find_by!(tag_id: tag.id, language_code: 'ja')
|
||||
TagDescriptionOperation.create!(
|
||||
Locale.nipponese, tag:, body: 'before', created_by_user: user)
|
||||
end
|
||||
let!(:first_version) { current_description_version(description) }
|
||||
|
||||
def update_description body: 'after', title: tag_name.name, base: first_revision.id
|
||||
put "/wiki/#{ page.id }",
|
||||
params: { title:, body:, message: 'edit', base_revision_id: base },
|
||||
def update_description body: 'after', title: tag.name('ja'), base: first_version.id
|
||||
put "/wiki/#{ description.id }", params: { title:, body:, base_revision_id: base },
|
||||
headers: headers
|
||||
end
|
||||
|
||||
it 'increments the independent description sequence and shares revision identity' do
|
||||
expect(page.reload.version_no).to eq(1)
|
||||
expect(description.version_no).to eq(1)
|
||||
expect { update_description }
|
||||
.to change(WikiPage, :count).by(0)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
.and change(TagDescriptionVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
revision = page.reload.current_revision
|
||||
version = TagDescriptionVersion.find(revision.id)
|
||||
expect(description.reload).to have_attributes(body: 'after', version_no: 2)
|
||||
expect(version).to have_attributes(
|
||||
body: 'after', version_no: 2, event_type: 'update', created_by_user_id: user.id)
|
||||
expect(page).to have_attributes(body: 'after', version_no: 2)
|
||||
expect(revision.body).to eq(version.body)
|
||||
expect(page.wiki_versions.order(:version_no).last.body).to eq(version.body)
|
||||
end
|
||||
|
||||
it 'uses description identity even when its Wiki TagName no longer owns the Tag' do
|
||||
tag_name.update_columns(tag_id: nil)
|
||||
|
||||
expect { update_description }
|
||||
.to change(WikiPage, :count).by(0)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
.and change(TagDescriptionVersion, :count).by(1)
|
||||
|
||||
it 'records a body change without changing resource identity or old history' do
|
||||
snapshot = first_version.attributes
|
||||
expect { update_description }.to change(TagDescriptionVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(description.reload).to have_attributes(body: 'after', version_no: 2)
|
||||
expect(TagDescriptionVersion.find(page.reload.current_revision.id))
|
||||
.to have_attributes(tag_id: tag.id, body: 'after', version_no: 2)
|
||||
expect(first_version.reload.attributes).to eq(snapshot)
|
||||
expect(current_description_version(description)).to have_attributes(
|
||||
body: 'after', event_type: 'update', created_by_user_id: user.id)
|
||||
end
|
||||
|
||||
it 'does not add body history for a title-only update' do
|
||||
revision_count = WikiRevision.count
|
||||
wiki_version_count = WikiVersion.count
|
||||
description_version_count = TagDescriptionVersion.count
|
||||
wiki_version_no = page.reload.version_no
|
||||
description_version_no = description.version_no
|
||||
current_revision_id = page.current_revision.id
|
||||
page_state = page.attributes
|
||||
description_state = description.attributes
|
||||
|
||||
update_description(body: 'before', title: 'description_renamed')
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(tag_name.reload.name).to eq('description_renamed')
|
||||
expect(WikiRevision.count).to eq(revision_count)
|
||||
expect(WikiVersion.count).to eq(wiki_version_count)
|
||||
expect(TagDescriptionVersion.count).to eq(description_version_count)
|
||||
expect(page.reload).to have_attributes(body: 'before', version_no: wiki_version_no)
|
||||
expect(page.current_revision.id).to eq(current_revision_id)
|
||||
expect(page.reload.attributes).to eq(page_state)
|
||||
expect(description.reload).to have_attributes(
|
||||
body: 'before', version_no: description_version_no)
|
||||
expect(description.reload.attributes).to eq(description_state)
|
||||
end
|
||||
|
||||
it 'does not add body history for a TagName rename through tags' do
|
||||
revision_count = WikiRevision.count
|
||||
wiki_version_count = WikiVersion.count
|
||||
description_version_count = TagDescriptionVersion.count
|
||||
page_version_no = page.reload.version_no
|
||||
current_revision_id = page.current_revision.id
|
||||
|
||||
patch "/tags/#{ tag.id }",
|
||||
params: { name: 'description_tag_renamed' },
|
||||
headers: headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(tag_name.reload.name).to eq('description_tag_renamed')
|
||||
expect(WikiRevision.count).to eq(revision_count)
|
||||
expect(WikiVersion.count).to eq(wiki_version_count)
|
||||
expect(TagDescriptionVersion.count).to eq(description_version_count)
|
||||
expect(page.reload.version_no).to eq(page_version_no)
|
||||
expect(page.current_revision.id).to eq(current_revision_id)
|
||||
expect(description.reload).to have_attributes(
|
||||
body: 'before', version_no: 1)
|
||||
end
|
||||
|
||||
it 'keeps both systems and the title unchanged on a stale revision conflict' do
|
||||
update_description(body: 'current body', base: first_revision.id)
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
stale_revision_id = first_revision.id
|
||||
before_state = stored_state
|
||||
|
||||
update_description(
|
||||
body: 'conflicting body',
|
||||
title: 'conflicting_rename',
|
||||
base: stale_revision_id)
|
||||
|
||||
expect(response).to have_http_status(:conflict)
|
||||
expect(stored_state).to eq(before_state)
|
||||
expect(tag_name.reload.name).not_to eq('conflicting_rename')
|
||||
end
|
||||
|
||||
[WikiPage, WikiRevision, WikiVersion, TagDescription, TagDescriptionVersion].each do |model|
|
||||
it "rolls back both systems when #{ model.name } persistence fails" do
|
||||
['before', "before\r\n\r\n"].each do |body|
|
||||
it "does not write for normalised-equivalent body #{ body.inspect }" do
|
||||
before_state = stored_state
|
||||
aggregate_failures do
|
||||
fail_save(model) do
|
||||
update_description(body: 'rollback body', title: 'rollback_rename')
|
||||
end
|
||||
expect(stored_state).to eq(before_state)
|
||||
end
|
||||
update_description(body:)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(stored_state).to eq(before_state)
|
||||
end
|
||||
end
|
||||
|
||||
[['identical body', 'before'],
|
||||
['normalised-equivalent body', "before\n\n"]].each do |label, body|
|
||||
it "does not write histories for #{ label }" do
|
||||
before_state = stored_state
|
||||
page_state = page.reload.attributes
|
||||
description_state = description.reload.attributes
|
||||
current_revision_id = page.current_revision.id
|
||||
wiki_lines = WikiLine.order(:id).map(&:attributes)
|
||||
revision_lines = WikiRevisionLine.order(:id).map(&:attributes)
|
||||
page_version_no = page.version_no
|
||||
description_version_no = description.version_no
|
||||
|
||||
update_description(body:)
|
||||
it 'records a title-only edit in Tag history without adding body history' do
|
||||
snapshot = description.attributes
|
||||
expect { update_description(body: 'before', title: 'renamed') }
|
||||
.to change(TagVersion, :count).by(2)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(tag.reload.name('ja')).to eq('renamed')
|
||||
expect(description.reload.attributes).to eq(snapshot)
|
||||
end
|
||||
|
||||
['conflicting body', 'after'].each do |body|
|
||||
it "rejects a stale revision even for #{ body } without a partial rename" do
|
||||
update_description
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(WikiRevision.count).to eq(before_state[1].length)
|
||||
expect(WikiVersion.count).to eq(before_state[2].length)
|
||||
expect(TagDescriptionVersion.count).to eq(before_state[4].length)
|
||||
expect(page.reload.version_no).to eq(page_version_no)
|
||||
expect(description.reload.version_no).to eq(description_version_no)
|
||||
expect(page.current_revision.id).to eq(current_revision_id)
|
||||
expect(page.reload.attributes).to eq(page_state)
|
||||
expect(description.reload.attributes).to eq(description_state)
|
||||
expect(WikiLine.order(:id).map(&:attributes)).to eq(wiki_lines)
|
||||
expect(WikiRevisionLine.order(:id).map(&:attributes)).to eq(revision_lines)
|
||||
before_state = stored_state
|
||||
update_description(body:, title: 'conflicting rename')
|
||||
expect(response).to have_http_status(:conflict)
|
||||
expect(stored_state).to eq(before_state)
|
||||
end
|
||||
end
|
||||
|
||||
[TagDescription, TagDescriptionVersion].each do |model|
|
||||
it "rolls back the rename and body after #{ model.name } persistence fails" do
|
||||
before_state = stored_state
|
||||
failure = Class.new(StandardError)
|
||||
callback = ->(_record) { raise failure }
|
||||
model.set_callback(:save, :after, callback)
|
||||
begin
|
||||
expect { update_description(title: 'rollback rename') }.to raise_error(failure)
|
||||
expect(stored_state).to eq(before_state)
|
||||
ensure
|
||||
model.skip_callback(:save, :after, callback)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Tag description native API', type: :request do
|
||||
include WikiRecords
|
||||
include TagGroupRecords
|
||||
let!(:author) { create_member_user! }
|
||||
let(:editor) { create(:user, :member, name: 'description editor') }
|
||||
let(:tag_name) { create(:tag_name, name: 'native_description') }
|
||||
@@ -9,8 +11,7 @@ RSpec.describe 'Tag description native API', type: :request do
|
||||
let(:endpoint) { "/tags/#{ tag.id }/description" }
|
||||
|
||||
def stored_state
|
||||
[WikiPage, WikiRevision, WikiVersion, TagDescription, TagDescriptionVersion,
|
||||
WikiLine, WikiRevisionLine, TagName, TagVersion].map do |model|
|
||||
[TagDescription, TagDescriptionVersion, TagName, TagVersion].map do |model|
|
||||
model.order(:id).map(&:attributes)
|
||||
end
|
||||
end
|
||||
@@ -84,7 +85,7 @@ RSpec.describe 'Tag description native API', type: :request do
|
||||
describe 'native reads' do
|
||||
let!(:description) do
|
||||
TagDescription.create!(
|
||||
id: tag.id + 1_000_000, tag:, language_code: 'ja', body: 'native current',
|
||||
tag:, language_code: 'ja', body: 'native current',
|
||||
version_no: 1, created_by_user_id: author.id, updated_by_user_id: editor.id)
|
||||
end
|
||||
let!(:version) do
|
||||
@@ -95,15 +96,14 @@ RSpec.describe 'Tag description native API', type: :request do
|
||||
let(:name_endpoint) { "/tags/name/#{ tag_name.name }/description" }
|
||||
|
||||
it 'reads the current resource by Tag ID without a WikiPage' do
|
||||
expect(description.id).not_to eq(tag.id)
|
||||
expect(WikiPage.exists?(id: description.id)).to be(false)
|
||||
without_legacy_reads { native_get(endpoint) }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect_current(description)
|
||||
end
|
||||
|
||||
it 'does not interpret a compatibility handle as a Tag ID' do
|
||||
other_tag = create(:tag, id: description.id, primary_name: 'handle_collision')
|
||||
other_tag = create(:tag, primary_name: 'handle_collision')
|
||||
description.update_columns(id: other_tag.id)
|
||||
other = TagDescription.create!(
|
||||
tag: other_tag, language_code: 'ja', body: 'other current', version_no: 1,
|
||||
created_by_user_id: author.id, updated_by_user_id: author.id)
|
||||
@@ -153,32 +153,6 @@ RSpec.describe 'Tag description native API', type: :request do
|
||||
expect_current(description)
|
||||
end
|
||||
|
||||
[:id, :name].each do |lookup|
|
||||
it "ignores divergent legacy body and history for #{ lookup } lookup" do
|
||||
page = WikiPage.create!(
|
||||
id: description.id, tag_name:, body: 'old current',
|
||||
created_user: author, updated_user: author)
|
||||
revision = WikiRevision.create!(
|
||||
wiki_page: page, created_user: author, kind: :content,
|
||||
lines_count: 1, tree_sha256: Digest::SHA256.hexdigest('old revision'))
|
||||
line = WikiLine.create!(body: 'old revision', sha256: revision.tree_sha256)
|
||||
WikiRevisionLine.create!(wiki_revision: revision, wiki_line: line, position: 0)
|
||||
WikiVersionRecorder.record!(page:, event_type: :create, created_by_user: author)
|
||||
path = lookup == :id ? endpoint : name_endpoint
|
||||
action = lookup == :id ? 'show' : 'show_by_title'
|
||||
|
||||
without_legacy_reads { native_get(path, action:) }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect_current(description)
|
||||
original = json
|
||||
page.update_columns(body: 'changed old current')
|
||||
line.update_columns(body: 'changed old revision')
|
||||
revision.update_columns(message: 'changed old history', created_at: 1.day.ago)
|
||||
without_legacy_reads { native_get(path, action:) }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to eq(original)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'missing native resources' do
|
||||
@@ -206,44 +180,24 @@ RSpec.describe 'Tag description native API', type: :request do
|
||||
context 'without an existing description' do
|
||||
before { tag }
|
||||
|
||||
it 'creates a description with synchronised Wiki content and histories' do
|
||||
unrelated = Wiki::Commit.create_legacy_content!(
|
||||
tag_name: create(:tag_name, name: 'Help:unrelated_native_description'),
|
||||
body: 'unrelated body', created_by_user: author)
|
||||
unrelated_state = [unrelated.attributes,
|
||||
unrelated.wiki_revisions.map(&:attributes),
|
||||
unrelated.wiki_versions.map(&:attributes)]
|
||||
|
||||
it 'creates native history that is immediately available through the facade' do
|
||||
expect { native_write(body: 'created natively') }
|
||||
.to change(TagDescription, :count).by(1)
|
||||
.and change(TagDescriptionVersion, :count).by(1)
|
||||
.and change(WikiPage, :count).by(1)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:created)
|
||||
description = TagDescription.find_by!(tag_id: tag.id, language_code: 'ja')
|
||||
page = WikiPage.find_by!(tag_name: tag.tag_name('ja'))
|
||||
revision = page.current_revision
|
||||
version = TagDescriptionVersion.find(revision.id)
|
||||
expect(description).to have_attributes(
|
||||
body: 'created natively', version_no: 1,
|
||||
created_by_user_id: editor.id, updated_by_user_id: editor.id)
|
||||
expect(version).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'ja', body: description.body,
|
||||
version_no: 1, event_type: 'create', created_by_user_id: editor.id)
|
||||
expect(page).to have_attributes(tag_name_id: tag_name.id, body: description.body)
|
||||
expect(revision.body).to eq(version.body)
|
||||
expect(page.wiki_versions.last.body).to eq(version.body)
|
||||
unrelated.reload
|
||||
expect([unrelated.attributes, unrelated.wiki_revisions.map(&:attributes),
|
||||
unrelated.wiki_versions.map(&:attributes)]).to eq(unrelated_state)
|
||||
expect_current(description)
|
||||
|
||||
version = current_description_version(description)
|
||||
expect(version).to have_attributes(
|
||||
body: 'created natively', version_no: 1, event_type: 'create',
|
||||
created_by_user_id: editor.id)
|
||||
get "/wiki/#{ description.id }"
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to include('body' => description.body, 'revision_id' => version.id)
|
||||
end
|
||||
|
||||
[WikiPage, WikiRevision, WikiVersion, TagDescription, TagDescriptionVersion].each do |model|
|
||||
[TagDescription, TagDescriptionVersion].each do |model|
|
||||
it "rolls back native create when #{ model.name } persistence fails" do
|
||||
headers
|
||||
before_state = stored_state
|
||||
@@ -258,7 +212,7 @@ RSpec.describe 'Tag description native API', type: :request do
|
||||
context 'with an existing description' do
|
||||
let!(:page) do
|
||||
tag
|
||||
Wiki::Commit.create_content!(tag_name:, body: 'before', created_by_user: author)
|
||||
seed_description(tag_name:, body: 'before', created_by_user: author)
|
||||
end
|
||||
let!(:description) do
|
||||
TagDescription.find_by!(tag_id: tag.id, language_code: 'ja')
|
||||
@@ -268,22 +222,20 @@ RSpec.describe 'Tag description native API', type: :request do
|
||||
expect(page.reload.version_no).to eq(1)
|
||||
expect(description.version_no).to eq(1)
|
||||
expect { native_write(body: 'after') }
|
||||
.to change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
.and change(TagDescriptionVersion, :count).by(1)
|
||||
.to change(TagDescriptionVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(description.reload).to have_attributes(
|
||||
body: 'after', version_no: 2,
|
||||
created_by_user_id: author.id, updated_by_user_id: editor.id)
|
||||
expect(page.reload).to have_attributes(body: 'after', version_no: 2)
|
||||
|
||||
revision = page.current_revision
|
||||
revision = current_description_version(page)
|
||||
version = TagDescriptionVersion.find(revision.id)
|
||||
expect(version).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'ja', version_no: 2, event_type: 'update',
|
||||
body: 'after', created_by_user_id: editor.id)
|
||||
expect(revision.body).to eq(version.body)
|
||||
expect(page.wiki_versions.order(:version_no).last.body).to eq(version.body)
|
||||
expect(description_versions(page).order(:version_no).last.body).to eq(version.body)
|
||||
get "/wiki/#{ description.id }"
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to include('body' => 'after', 'revision_id' => version.id)
|
||||
@@ -294,7 +246,7 @@ RSpec.describe 'Tag description native API', type: :request do
|
||||
before_state = stored_state
|
||||
page_state = page.reload.attributes
|
||||
description_state = description.reload.attributes
|
||||
current_revision_id = page.current_revision.id
|
||||
current_revision_id = current_description_version(page).id
|
||||
|
||||
native_write(body:)
|
||||
|
||||
@@ -302,11 +254,11 @@ RSpec.describe 'Tag description native API', type: :request do
|
||||
expect(stored_state).to eq(before_state)
|
||||
expect(page.reload.attributes).to eq(page_state)
|
||||
expect(description.reload.attributes).to eq(description_state)
|
||||
expect(page.current_revision.id).to eq(current_revision_id)
|
||||
expect(current_description_version(page).id).to eq(current_revision_id)
|
||||
end
|
||||
end
|
||||
|
||||
[WikiPage, WikiRevision, WikiVersion, TagDescription, TagDescriptionVersion].each do |model|
|
||||
[TagDescription, TagDescriptionVersion].each do |model|
|
||||
it "rolls back native update when #{ model.name } persistence fails" do
|
||||
headers
|
||||
before_state = stored_state
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Tag group locale contracts', type: :request do
|
||||
include TagGroupRecords
|
||||
include_context 'Ainu locale contract'
|
||||
|
||||
let!(:user) { create_member_user! }
|
||||
let!(:tag) { create(:tag, primary_name: 'japanese_member') }
|
||||
let!(:ainu_name) { add_ainu_name(tag, 'ainu_member') }
|
||||
let(:headers) { { 'X-Transfer-Code' => user.inheritance_code } }
|
||||
let(:members) { [{ 'tag_id' => tag.id }] }
|
||||
let(:placements) do
|
||||
[{ 'section_name' => 'section', 'description' => nil, 'children' => [
|
||||
{ 'tag_id' => tag.id, 'children' => [
|
||||
{ 'tag_id' => tag.id, 'children' => [] }] }] }]
|
||||
end
|
||||
let!(:group) do
|
||||
TagVersioning.ensure_snapshot!(tag, created_by_user: user)
|
||||
TagGroupOperation.create!(
|
||||
ainu_locale, name: 'locale group', description: nil, members:, placements:, actor: user)
|
||||
end
|
||||
|
||||
it 'uses selected-language names in native collection and nested placements' do
|
||||
["/tag_groups/#{ group.id }", '/tag_groups'].each do |path|
|
||||
get path, params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:ok)
|
||||
representation = json.is_a?(Array) ? json.sole : json
|
||||
expect(representation.fetch('members').sole.fetch('tag').fetch('name'))
|
||||
.to eq('ainu_member')
|
||||
node = representation.fetch('placements').sole.fetch('children').sole
|
||||
expect(node.fetch('tag').fetch('name')).to eq('ainu_member')
|
||||
expect(node.fetch('children').sole.fetch('tag').fetch('name')).to eq('ainu_member')
|
||||
expect(response.body).not_to include('japanese_member')
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses snapshot names recursively for history and current names for the latest view' do
|
||||
version = group.versions.sole
|
||||
ainu_name.update!(name: 'renamed_ainu')
|
||||
get "/wiki/#{ group.id }", params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('body')).to include('renamed_ainu')
|
||||
get "/wiki/#{ group.id }", params: { locale: 'ain', version: version.id }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('body').scan('ainu_member').length).to eq(2)
|
||||
expect(json.fetch('body')).not_to include('renamed_ainu', 'japanese_member')
|
||||
get "/wiki/#{ group.id }/diff", params: { locale: 'ain', from: version.id }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('diff').map { _1.fetch('type') }.uniq).to eq(['context'])
|
||||
end
|
||||
|
||||
['タググループ:', 'tag_group:', 'TAG_GROUP:'].each do |prefix|
|
||||
it "round trips #{ prefix } through create, lookup, search and update" do
|
||||
post '/wiki', params: {
|
||||
locale: 'ain', title: "#{ prefix }parent", body: '- tag_group:locale group' },
|
||||
headers: headers
|
||||
expect(response).to have_http_status(:created)
|
||||
id = json.fetch('id')
|
||||
expect(json.fetch('body')).to include('タググループ:locale group')
|
||||
path = "/wiki/title/#{ CGI.escapeURIComponent("#{ prefix }parent") }"
|
||||
get path, params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('id')).to eq(id)
|
||||
get "#{ path }/exists", params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:no_content)
|
||||
get '/wiki/search', params: { locale: 'ain', title: "#{ prefix }parent" }
|
||||
expect(json.map { _1.fetch('id') }).to eq([id])
|
||||
put "/wiki/#{ id }", params: {
|
||||
locale: 'ain', title: "#{ prefix }renamed", body: '- ainu_member' }, headers: headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
get "/tag_groups/#{ id }", params: { locale: 'ain' }
|
||||
expect(json.fetch('members').sole.fetch('tag').fetch('name')).to eq('ainu_member')
|
||||
end
|
||||
end
|
||||
|
||||
it 'resolves Markdown names in the requested language and rolls back invalid input' do
|
||||
parsed = TagGroupMarkdownParser.parse(ainu_locale, body: '- ainu_member')
|
||||
expect(parsed.fetch(:members)).to eq(members)
|
||||
expect do
|
||||
TagGroupMarkdownParser.parse(ainu_locale, body: '- japanese_member')
|
||||
end.to raise_error(TagGroupMarkdownParser::InvalidDocument)
|
||||
before_state = group_state
|
||||
put "/wiki/#{ group.id }", params: {
|
||||
locale: 'ain', title: 'タググループ:partial rename', body: '- japanese_member' },
|
||||
headers: headers
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(group_state).to eq(before_state)
|
||||
end
|
||||
|
||||
it 'passes locale into native validation and facade compatibility checks' do
|
||||
post '/tag_groups', params: {
|
||||
locale: 'ain', name: 'native', description: nil, members:, placements: },
|
||||
headers:, as: :json
|
||||
expect(response).to have_http_status(:created)
|
||||
id = json.fetch('id')
|
||||
expect(json.fetch('members').sole.fetch('tag').fetch('name')).to eq('ainu_member')
|
||||
get "/wiki/#{ id }", params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:ok)
|
||||
body = json.fetch('body')
|
||||
put "/wiki/#{ id }", params: { locale: 'ain', title: 'tag_group:native', body: },
|
||||
headers: headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(TagGroup.find(id).version_no).to eq(1)
|
||||
end
|
||||
|
||||
it 'does not substitute current or Japanese names for a missing historical language' do
|
||||
other = create(:tag, primary_name: 'historical_japanese')
|
||||
TagVersioning.ensure_snapshot!(other, created_by_user: user)
|
||||
old_group = TagGroupOperation.create!(
|
||||
Locale.nipponese, name: 'old group', description: nil,
|
||||
members: [{ 'tag_id' => other.id }],
|
||||
placements: [{ 'tag_id' => other.id, 'children' => [] }], actor: user)
|
||||
add_ainu_name(other, 'current_ainu')
|
||||
version = old_group.versions.sole
|
||||
representation = TagGroupRepr.historical(ainu_locale, version)
|
||||
expect(representation.fetch('members').sole.fetch('tag').fetch('name')).to be_nil
|
||||
expect(representation.fetch('placements').sole.fetch('tag').fetch('name')).to be_nil
|
||||
get "/wiki/#{ old_group.id }", params: { locale: 'ain', version: version.id }
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response.body).not_to include('historical_japanese', 'current_ainu')
|
||||
end
|
||||
end
|
||||
@@ -115,6 +115,15 @@ RSpec.describe 'Tag group Markdown round trips through the Wiki facade', type: :
|
||||
MARKDOWN
|
||||
end
|
||||
|
||||
it 'round trips a top-level list followed by heading Sections' do
|
||||
check_round_trip(
|
||||
members: [tag_member(series), tag_member(deer)],
|
||||
placements: [tag_node(series),
|
||||
{ 'section_name' => '区分', 'description' => nil,
|
||||
'children' => [tag_node(deer)] }],
|
||||
markdown: "- ぼざろクリーチャーシリーズ\n\n## 区分\n\n- 洗澡鹿")
|
||||
end
|
||||
|
||||
it 'rejects mixed top-level heading Sections and Tags without losing structure' do
|
||||
id = seed_group(
|
||||
name: '混在構造', members: [tag_member(series)],
|
||||
|
||||
@@ -7,21 +7,18 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
|
||||
let!(:user) { create_member_user! }
|
||||
let!(:old_tag) { group_tag('旧タグ') }
|
||||
let!(:new_tag) { group_tag('新タグ') }
|
||||
let!(:page) { legacy_group('読取り契約', '- 旧タグ') }
|
||||
let!(:first_revision) { page.current_revision }
|
||||
let!(:page) { create_markdown_group('読取り契約', '- 旧タグ') }
|
||||
let!(:first_revision) { page.versions.sole }
|
||||
let!(:last_revision) do
|
||||
Wiki::Commit.legacy_content!(page:, body: '- 新タグ', created_user: user)
|
||||
parsed = TagGroupMarkdownParser.parse(Locale.nipponese, body: '- 新タグ')
|
||||
TagGroupOperation.update!(
|
||||
Locale.nipponese,
|
||||
group: page, name: page.name, actor: user,
|
||||
**parsed.slice(:description, :members, :placements))
|
||||
page.versions.order(:version_no).last
|
||||
end
|
||||
|
||||
before do
|
||||
I18n.locale = :ja
|
||||
migrate_groups
|
||||
page.update_columns(body: 'legacy stale current')
|
||||
page.wiki_revisions.each do |revision|
|
||||
revision.wiki_lines.update_all(body: 'legacy stale revision')
|
||||
end
|
||||
page.wiki_versions.update_all(body: 'legacy stale version')
|
||||
end
|
||||
before { I18n.locale = :ja }
|
||||
|
||||
[:id, :title].each do |lookup|
|
||||
context "with #{ lookup } lookup" do
|
||||
@@ -71,36 +68,14 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
|
||||
end
|
||||
|
||||
it 'combines Tag Groups and Tag Descriptions without Help in the unfiltered list' do
|
||||
help = seed_wiki_page(
|
||||
tag_name: create(:tag_name, name: 'Help:混在'), body: 'help', created_by_user: user)
|
||||
TagDescriptionOperation.create!(tag: new_tag, body: 'tag description', created_by_user: user)
|
||||
TagDescriptionOperation.create!(
|
||||
Locale.nipponese, tag: new_tag, body: 'tag description', created_by_user: user)
|
||||
get '/wiki'
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.map { _1.fetch('title') }).to include(
|
||||
'タググループ:読取り契約', '新タグ')
|
||||
expect(json.map { _1.fetch('title') }).not_to include(help.title)
|
||||
end
|
||||
|
||||
it 'excludes Help when its ID collides with a new group' do
|
||||
help = seed_wiki_page(
|
||||
tag_name: create(:tag_name, name: 'Help:ID重複'), body: 'help body',
|
||||
created_by_user: user)
|
||||
seed_group(name: '別 ID重複', id: help.id, description: 'group body')
|
||||
|
||||
get '/wiki'
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.map { _1.fetch('title') }).to include('タググループ:別 ID重複')
|
||||
expect(json.map { _1.fetch('title') }).not_to include(help.title)
|
||||
|
||||
get "/wiki/title/#{ CGI.escapeURIComponent(help.title) }"
|
||||
expect(response).to have_http_status(:not_found)
|
||||
|
||||
get '/wiki/changes'
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.map { _1.dig('wiki_page', 'title') }).to include(
|
||||
'タググループ:別 ID重複')
|
||||
expect(json.map { _1.dig('wiki_page', 'title') }).not_to include(help.title)
|
||||
end
|
||||
|
||||
it 'finds groups by a partial prefix of their displayed title' do
|
||||
get '/wiki', params: { title: 'タググル' }
|
||||
@@ -112,8 +87,10 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
|
||||
end
|
||||
|
||||
it 'does not restore a stale namespaced Wiki page after a group rename' do
|
||||
old_page = legacy_group('旧名称', '- 旧本文')
|
||||
seed_group(name: '新名称', id: old_page.id, description: '正本')
|
||||
group = create_markdown_group('旧名称', '正本')
|
||||
TagGroupOperation.update!(
|
||||
Locale.nipponese,
|
||||
group:, name: '新名称', description: '正本', members: [], placements: [], actor: user)
|
||||
|
||||
get group_title_path('旧名称')
|
||||
expect(response).to have_http_status(:not_found)
|
||||
@@ -125,12 +102,9 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
|
||||
end
|
||||
|
||||
it 'orders mixed resource history by timestamp rather than independent IDs' do
|
||||
help = seed_wiki_page(
|
||||
tag_name: create(:tag_name, name: 'Help:時系列'), body: 'help',
|
||||
created_by_user: user)
|
||||
TagDescriptionOperation.create!(tag: new_tag, body: 'description', created_by_user: user)
|
||||
TagDescriptionOperation.create!(
|
||||
Locale.nipponese, tag: new_tag, body: 'description', created_by_user: user)
|
||||
TagGroupVersion.update_all(created_at: 3.days.ago)
|
||||
WikiRevision.where(wiki_page_id: help.id).update_all(created_at: 2.days.ago)
|
||||
TagDescriptionVersion.update_all(created_at: 1.day.ago)
|
||||
|
||||
get '/wiki/changes'
|
||||
@@ -139,7 +113,6 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
|
||||
timestamps = json.map { Time.iso8601(_1.fetch('timestamp')) }
|
||||
expect(timestamps).to eq(timestamps.sort.reverse)
|
||||
titles = json.map { _1.dig('wiki_page', 'title') }
|
||||
expect(titles).not_to include(help.title)
|
||||
expect(titles.index('新タグ')).to be < titles.index('タググループ:読取り契約')
|
||||
end
|
||||
|
||||
@@ -169,19 +142,6 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
|
||||
'wiki_page' => include('id' => page.id, 'title' => 'タググループ:読取り契約'))
|
||||
end
|
||||
|
||||
it 'includes group history but excludes all legacy-only revisions' do
|
||||
extra = Wiki::Commit.legacy_content!(page:, body: 'legacy only', created_user: user)
|
||||
help = seed_wiki_page(
|
||||
tag_name: create(:tag_name, name: 'Help:変更一覧'), body: 'help', created_by_user: user)
|
||||
get '/wiki/changes'
|
||||
expect(response).to have_http_status(:ok)
|
||||
rows = json.select { _1.fetch('wiki_page').fetch('title') == 'タググループ:読取り契約' }
|
||||
expect(rows.map { _1.fetch('revision_id') }).to eq([last_revision.id, first_revision.id])
|
||||
expect(rows.map { _1.fetch('revision_id') }).not_to include(extra.id)
|
||||
expect(json).not_to include(include(
|
||||
'revision_id' => help.current_revision.id,
|
||||
'wiki_page' => include('title' => help.title)))
|
||||
end
|
||||
|
||||
[false, true].each do |explicit|
|
||||
it "diffs structured snapshots with #{ explicit ? 'explicit' : 'current' } target" do
|
||||
@@ -215,95 +175,5 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
|
||||
expect(json.fetch('body').strip).to eq('独立した説明')
|
||||
end
|
||||
|
||||
context 'when Help, description and group share the same numeric ID' do
|
||||
let(:colliding_help) do
|
||||
seed_wiki_page(
|
||||
tag_name: create(:tag_name, name: 'Help:三者ID衝突'),
|
||||
body: 'help', created_by_user: user)
|
||||
end
|
||||
let(:colliding_group_id) do
|
||||
seed_group(name: '三者ID衝突', id: colliding_help.id, description: 'group')
|
||||
end
|
||||
let(:colliding_description) do
|
||||
TagDescription.create!(
|
||||
id: colliding_help.id, tag: new_tag, language_code: 'ja',
|
||||
body: 'description', version_no: 1)
|
||||
end
|
||||
let(:colliding_description_version) do
|
||||
TagDescriptionVersion.create!(
|
||||
tag_id: new_tag.id, language_code: 'ja', version_no: 1,
|
||||
event_type: 'create', body: 'description', created_by_user_id: user.id,
|
||||
created_at: Time.current)
|
||||
end
|
||||
|
||||
before do
|
||||
colliding_group_id
|
||||
colliding_description
|
||||
colliding_description_version
|
||||
end
|
||||
|
||||
it 'lists both new resources sharing a numeric ID without legacy Help' do
|
||||
get '/wiki'
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
rows = json.select { _1.fetch('id') == colliding_help.id }
|
||||
expect(rows.map { [_1.fetch('id'), _1.fetch('title')] }).to contain_exactly(
|
||||
[colliding_description.id, '新タグ'],
|
||||
[colliding_group_id, 'タググループ:三者ID衝突'])
|
||||
end
|
||||
|
||||
it 'lists each resource history without deduplicating the shared resource ID' do
|
||||
get '/wiki/changes'
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
rows = json.select { _1.fetch('wiki_page').fetch('id') == colliding_help.id }
|
||||
histories = rows.map do |row|
|
||||
[row.fetch('revision_id'), row.fetch('wiki_page').fetch('title')]
|
||||
end
|
||||
expect(histories).to contain_exactly(
|
||||
[colliding_description_version.id, '新タグ'],
|
||||
[group_version(colliding_group_id, 1).fetch('id'), 'タググループ:三者ID衝突'])
|
||||
end
|
||||
|
||||
it 'still excludes legacy copies of an actually migrated group and its history' do
|
||||
extra = Wiki::Commit.legacy_content!(page:, body: 'legacy only', created_user: user)
|
||||
|
||||
get '/wiki'
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
rows = json.select { _1.fetch('id') == page.id }
|
||||
expect(rows.map { _1.fetch('title') }).to eq(['タググループ:読取り契約'])
|
||||
|
||||
get '/wiki/changes'
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
rows = json.select { _1.fetch('wiki_page').fetch('id') == page.id }
|
||||
expect(rows.map { _1.fetch('revision_id') })
|
||||
.to contain_exactly(first_revision.id, last_revision.id)
|
||||
expect(rows.map { _1.fetch('revision_id') }).not_to include(extra.id)
|
||||
expect(rows.map { _1.fetch('wiki_page').fetch('title') })
|
||||
.to all(eq('タググループ:読取り契約'))
|
||||
end
|
||||
end
|
||||
|
||||
it 'resolves new resources by title while excluding colliding legacy Help' do
|
||||
help = seed_wiki_page(
|
||||
tag_name: create(:tag_name, name: 'Help:ID衝突'), body: 'help', created_by_user: user)
|
||||
seed_group(name: 'ID衝突', id: help.id, description: 'group')
|
||||
TagDescription.create!(
|
||||
id: help.id, tag: new_tag, language_code: 'ja', body: 'description', version_no: 1)
|
||||
TagDescriptionVersion.create!(
|
||||
tag_id: new_tag.id, language_code: 'ja', version_no: 1,
|
||||
event_type: 'create', body: 'description', created_at: Time.current)
|
||||
{ 'タググループ:ID衝突' => 'group', '新タグ' => 'description' }
|
||||
.each do |title, body|
|
||||
get "/wiki/title/#{ CGI.escapeURIComponent(title) }"
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('body').strip).to eq(body)
|
||||
end
|
||||
without_group_legacy_reads do
|
||||
get "/wiki/title/#{ CGI.escapeURIComponent(help.title) }"
|
||||
end
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -33,11 +33,10 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates the group, snapshot and matching compatibility histories' do
|
||||
it 'creates the group, snapshot and native version history' do
|
||||
expect { create_group }
|
||||
.to change(WikiPage, :count).by(1)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
.to change(TagGroup, :count).by(1)
|
||||
.and change(TagGroupVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:created)
|
||||
group = group_rows('tag_groups', name: '新規').sole
|
||||
expect(json).to include('id' => group.fetch('id'), 'title' => 'タググループ:新規')
|
||||
@@ -45,10 +44,9 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
version = group_version(group.fetch('id'), 1)
|
||||
expect(version).to include('event_type' => 'create', 'created_by_user_id' => user.id)
|
||||
expect(group).to include('created_by_user_id' => user.id, 'updated_by_user_id' => user.id)
|
||||
page = WikiPage.joins(:tag_name).find_by!(tag_names: { name: 'タググループ:新規' })
|
||||
expect(page.body).to eq('- 洗澡鹿')
|
||||
expect(page.current_revision.body).to eq(page.body)
|
||||
expect(page.wiki_versions.sole.body).to eq(page.body)
|
||||
get group_title_path('新規')
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('body')).to eq('- 洗澡鹿')
|
||||
end
|
||||
|
||||
it 'keeps repeated placements but only one membership and one snapshot member' do
|
||||
@@ -108,10 +106,9 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
end
|
||||
|
||||
context 'updating a migrated group' do
|
||||
let!(:page) { legacy_group('更新', '- 洗澡鹿') }
|
||||
let!(:old_revision) { page.current_revision }
|
||||
let!(:page) { create_markdown_group('更新', '- 洗澡鹿') }
|
||||
let!(:old_revision) { page.versions.order(:version_no).last }
|
||||
|
||||
before { migrate_groups }
|
||||
|
||||
def update_group body: '- 伊地知ニジカ', title: 'タググループ:更新', request_headers: headers
|
||||
put "/wiki/#{ page.id }", params: { title:, body: }, headers: request_headers
|
||||
@@ -119,9 +116,9 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
|
||||
[1, 3].each do |repetitions|
|
||||
it "keeps identical Markdown unchanged after #{ repetitions } Wiki PUTs" do
|
||||
body = page.body
|
||||
body = group_markdown(page)
|
||||
travel_to(1.hour.from_now) do
|
||||
expect_noop_group_update(group_id: page.id, page:) do
|
||||
expect_noop_group_update(group_id: page.id) do
|
||||
repetitions.times do
|
||||
update_group(body:)
|
||||
expect(response).to have_http_status(:ok)
|
||||
@@ -134,13 +131,13 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
it 'keeps identical Markdown unchanged with a stale base_revision_id' do
|
||||
update_group
|
||||
expect(response).to have_http_status(:ok)
|
||||
body = page.reload.body
|
||||
expect(page.current_revision.id).not_to eq(old_revision.id)
|
||||
body = group_markdown(page)
|
||||
expect(page.versions.order(:version_no).last.id).not_to eq(old_revision.id)
|
||||
|
||||
travel_to(1.hour.from_now) do
|
||||
expect_noop_group_update(group_id: page.id, page:) do
|
||||
expect_noop_group_update(group_id: page.id) do
|
||||
put "/wiki/#{ page.id }",
|
||||
params: { title: page.title, body:, base_revision_id: old_revision.id },
|
||||
params: { title: "タググループ:#{ page.name }", body:, base_revision_id: old_revision.id },
|
||||
headers: headers
|
||||
end
|
||||
end
|
||||
@@ -148,9 +145,9 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
|
||||
it 'keeps the original editor on identical Markdown from another member' do
|
||||
editor = create(:user, :member)
|
||||
body = page.body
|
||||
body = group_markdown(page)
|
||||
travel_to(1.hour.from_now) do
|
||||
expect_noop_group_update(group_id: page.id, page:) do
|
||||
expect_noop_group_update(group_id: page.id) do
|
||||
update_group(body:, request_headers: { 'X-Transfer-Code' => editor.inheritance_code })
|
||||
end
|
||||
end
|
||||
@@ -168,11 +165,11 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
page.id, number: 2,
|
||||
members: [{ 'tag_id' => other_tag.id, 'version_no' => other_tag.version_no }],
|
||||
placements: changed_placements)
|
||||
body = page.reload.body
|
||||
body = group_markdown(page)
|
||||
expect(body).to eq('- 伊地知ニジカ')
|
||||
|
||||
travel_to(1.hour.from_now) do
|
||||
expect_noop_group_update(group_id: page.id, page:) { update_group(body:) }
|
||||
expect_noop_group_update(group_id: page.id) { update_group(body:) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -187,16 +184,13 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
expect(group_version(page.id, 1)).to eq(old)
|
||||
expect(group_version(page.id, 2)).to include(
|
||||
'event_type' => 'update', 'created_by_user_id' => user.id)
|
||||
expect(page.reload.body.strip).to eq('- 伊地知ニジカ')
|
||||
expect(page.wiki_revisions.count).to eq(2)
|
||||
expect(page.wiki_versions.count).to eq(2)
|
||||
expect(page.current_revision.body).to eq(page.body)
|
||||
expect(page.wiki_versions.order(:version_no).last.body).to eq(page.body)
|
||||
expect(group_markdown(page).strip).to eq('- 伊地知ニジカ')
|
||||
expect(page.versions.count).to eq(2)
|
||||
end
|
||||
|
||||
it 'accepts the current base_revision_id used by the existing frontend' do
|
||||
put "/wiki/#{ page.id }",
|
||||
params: { title: page.title, body: '- 伊地知ニジカ',
|
||||
params: { title: "タググループ:#{ page.name }", body: '- 伊地知ニジカ',
|
||||
base_revision_id: old_revision.id }, headers: headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(group_rows('tag_groups', id: page.id).sole.fetch('version_no')).to eq(2)
|
||||
@@ -207,12 +201,12 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
put "/wiki/#{ page.id }",
|
||||
params: { title: page.title, body: '- 洗澡鹿',
|
||||
params: { title: "タググループ:#{ page.name }", body: '- 洗澡鹿',
|
||||
base_revision_id: old_revision.id }, headers: headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(group_rows('tag_groups', id: page.id).sole.fetch('version_no')).to eq(3)
|
||||
expect(page.reload.body.strip).to eq('- 洗澡鹿')
|
||||
expect(group_markdown(page).strip).to eq('- 洗澡鹿')
|
||||
end
|
||||
|
||||
it 'records the editor and update time' do
|
||||
@@ -257,9 +251,7 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
let!(:page) do
|
||||
next unless operation == :update
|
||||
|
||||
legacy = legacy_group('更新', '- 洗澡鹿')
|
||||
seed_group(name: '更新', id: legacy.id, members:, placements:)
|
||||
legacy
|
||||
create_markdown_group('更新', '- 洗澡鹿')
|
||||
end
|
||||
let!(:child_id) { seed_group(name: '子グループ', description: '子') }
|
||||
|
||||
@@ -296,7 +288,6 @@ RSpec.describe 'Tag group Wiki facade writes', type: :request do
|
||||
end
|
||||
|
||||
tables = TagGroupRecords::GROUP_TABLES
|
||||
tables += ['wiki_pages', 'wiki_revisions', 'wiki_versions']
|
||||
tables.each do |table|
|
||||
it "rolls back all writes after a real #{ table } write fails" do
|
||||
expect_atomic_group_write(table) { write_group(operation, headers) }
|
||||
|
||||
@@ -24,11 +24,6 @@ RSpec.describe 'Tag group write integrity', type: :request do
|
||||
put "/wiki/#{ id }", params: { title:, body:, base_revision_id: base }, headers: headers
|
||||
end
|
||||
|
||||
def create_help body
|
||||
seed_wiki_page(
|
||||
tag_name: create(:tag_name, name: 'Help:既存'), body:, created_by_user: user)
|
||||
end
|
||||
|
||||
def expect_no_writes
|
||||
before_state = group_state
|
||||
yield
|
||||
@@ -37,66 +32,28 @@ RSpec.describe 'Tag group write integrity', type: :request do
|
||||
end
|
||||
|
||||
context 'resource identity' do
|
||||
it 'rejects removing the prefix from a migrated group atomically' do
|
||||
page = legacy_group('更新', '- 洗澡鹿')
|
||||
seed_group(name: '更新', id: page.id, members:, placements:)
|
||||
|
||||
expect_no_writes do
|
||||
put_wiki(page.id, title: '更新', base: page.current_revision.id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects an unprefixed update to an independent group' do
|
||||
id = seed_group(name: '独立', members:, placements:)
|
||||
expect_no_writes { put_wiki(id, title: '独立') }
|
||||
end
|
||||
|
||||
it 'does not revive legacy writes when the compatibility title is stale' do
|
||||
page = legacy_group('更新', '- 洗澡鹿')
|
||||
seed_group(name: '更新', id: page.id, members:, placements:)
|
||||
page.tag_name.update!(name: '古い別名')
|
||||
|
||||
expect_no_writes { put_wiki(page.id, title: '古い別名') }
|
||||
end
|
||||
|
||||
it 'renames the group and its compatibility page through the operation' do
|
||||
page = legacy_group('更新', '- 洗澡鹿')
|
||||
seed_group(name: '更新', id: page.id, members:, placements:)
|
||||
it 'renames the group and its facade title through the operation' do
|
||||
page = create_markdown_group('更新', '- 洗澡鹿')
|
||||
|
||||
put_wiki(page.id, title: 'タググループ:改名')
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(TagGroup.find(page.id)).to have_attributes(name: '改名', version_no: 2)
|
||||
expect(page.reload.title).to eq('タググループ:改名')
|
||||
expect(page.body).to eq('- 伊地知ニジカ')
|
||||
expect("タググループ:#{ page.reload.name }").to eq('タググループ:改名')
|
||||
expect(group_markdown(page)).to eq('- 伊地知ニジカ')
|
||||
end
|
||||
|
||||
it 'rejects a standalone Help update despite a colliding group ID' do
|
||||
page = create_help('Help body')
|
||||
id = seed_group(name: '別グループ', id: page.id, members:, placements:)
|
||||
group_before = TagGroup.find(id).attributes
|
||||
version_before = group_version(id, 1)
|
||||
|
||||
expect_no_writes { put_wiki(id, title: 'Help:改名', body: 'Help after') }
|
||||
|
||||
expect(page.reload.body).to eq('Help body')
|
||||
expect(page.title).to eq('Help:既存')
|
||||
expect(TagGroup.find(id).attributes).to eq(group_before)
|
||||
expect(group_version(id, 1)).to eq(version_before)
|
||||
end
|
||||
|
||||
it 'rejects an ambiguous unprefixed rename when Help and group IDs collide' do
|
||||
page = create_help('Help body')
|
||||
seed_group(name: '更新', id: page.id, members:, placements:)
|
||||
|
||||
expect_no_writes { put_wiki(page.id, title: '更新') }
|
||||
expect_no_writes do
|
||||
put_wiki(page.id, title: '更新', base: page.current_revision.id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'keeps a description update separate from a colliding group' do
|
||||
page = Wiki::Commit.create_content!(
|
||||
page = seed_description(
|
||||
tag_name: tag.tag_name('ja'), body: 'description before', created_by_user: user)
|
||||
description = TagDescription.find_by!(tag_id: tag.id)
|
||||
id = seed_group(name: '更新', id: description.id, members:, placements:)
|
||||
@@ -110,106 +67,22 @@ RSpec.describe 'Tag group write integrity', type: :request do
|
||||
expect(TagGroup.find(id).attributes).to eq(group_before)
|
||||
end
|
||||
|
||||
it 'does not route a colliding Help write into an unrelated description' do
|
||||
page = create_help('Help before')
|
||||
id = seed_group(name: '更新', id: page.id, members:, placements:)
|
||||
description = TagDescription.create!(
|
||||
id:, tag:, language_code: 'ja', version_no: 1, body: 'description')
|
||||
end
|
||||
|
||||
expect_no_writes { put_wiki(id, title: 'Help:既存', body: 'Help after') }
|
||||
|
||||
expect(page.reload.body).to eq('Help before')
|
||||
expect(description.reload.body).to eq('description')
|
||||
expect(TagGroup.find(id).version_no).to eq(1)
|
||||
put_wiki(id, title: '洗澡鹿', body: 'description after')
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(description.reload.body).to eq('description after')
|
||||
put_wiki(id, title: 'タググループ:更新')
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(TagGroup.find(id).version_no).to eq(2)
|
||||
expect(page.reload.body).to eq('Help before')
|
||||
expect(description.reload.body).to eq('description after')
|
||||
it 'rolls back a facade create when its saved structure cannot be rendered' do
|
||||
allow(TagGroupMarkdownRenderer).to receive(:render)
|
||||
.and_raise(TagGroupMarkdownRenderer::UnsupportedStructure, 'unsupported')
|
||||
expect_no_writes do
|
||||
post '/wiki', params: { title: 'タググループ:新規', body: '- 洗澡鹿' }, headers: headers
|
||||
end
|
||||
end
|
||||
|
||||
context 'compatibility synchronisation' do
|
||||
it 'synchronises a native rename without modifying an ID-colliding Help page' do
|
||||
help = create_help('Help before')
|
||||
id = seed_group(name: '更新', id: help.id, members:, placements:)
|
||||
help_before = help.attributes
|
||||
|
||||
expect do
|
||||
put "/tag_groups/#{ id }", params: native_payload(name: '改名'), headers:, as: :json
|
||||
end.to change(WikiPage, :count).by(1)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(TagGroup.find(id).name).to eq('改名')
|
||||
expect(help.reload.attributes).to eq(help_before)
|
||||
end
|
||||
|
||||
['wiki_lines', 'wiki_revision_lines'].each do |table|
|
||||
it "rolls back native and compatibility state on #{ table } failure" do
|
||||
expect_atomic_group_write(table) do
|
||||
post '/tag_groups', params: native_payload, headers:, as: :json
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'rolls back a renderer failure instead of treating it as unsupported structure' do
|
||||
allow(TagGroupMarkdownRenderer).to receive(:render).and_raise('renderer failure')
|
||||
before_state = group_state
|
||||
|
||||
expect do
|
||||
post '/tag_groups', params: native_payload, headers:, as: :json
|
||||
end.to raise_error(RuntimeError, 'renderer failure')
|
||||
|
||||
expect(group_state).to eq(before_state)
|
||||
end
|
||||
|
||||
it 'preserves unplaced membership and reports the skipped sync with its version' do
|
||||
page = legacy_group('更新', '- 洗澡鹿')
|
||||
id = seed_group(name: '更新', id: page.id, members:, placements:)
|
||||
wiki_before = group_state.slice(*TagGroupRecords::WRITE_TABLES.drop(5))
|
||||
warnings = []
|
||||
allow(Rails.logger).to receive(:warn) { warnings << _1 }
|
||||
|
||||
put "/tag_groups/#{ id }",
|
||||
params: native_payload(member_rows: members + [{ 'tag_id' => other_tag.id }]),
|
||||
headers:, as: :json
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('members').map { _1.fetch('tag').fetch('id') })
|
||||
.to contain_exactly(tag.id, other_tag.id)
|
||||
expect(group_version(id, 2).fetch('members')).to include(
|
||||
{ 'tag_id' => other_tag.id, 'version_no' => other_tag.version_no })
|
||||
expect(group_state.slice(*TagGroupRecords::WRITE_TABLES.drop(5))).to eq(wiki_before)
|
||||
payload = JSON.parse(warnings.grep(/tag_group_wiki_sync_skipped/).sole.split(' ', 2).last)
|
||||
expect(payload).to include(
|
||||
'status' => 'unsupported_structure', 'tag_group_id' => id,
|
||||
'version_no' => 2, 'user_id' => user.id,
|
||||
'reason' => '掲載位置のない membership は Markdown にできません.')
|
||||
end
|
||||
|
||||
it 'preserves a list Section description and records why no Wiki was created' do
|
||||
nodes = [{ 'tag_id' => tag.id, 'children' => [
|
||||
{ 'section_name' => '区分', 'description' => '構造化の説明', 'children' => [] }] }]
|
||||
warnings = []
|
||||
allow(Rails.logger).to receive(:warn) { warnings << _1 }
|
||||
before_count = WikiPage.count
|
||||
|
||||
post '/tag_groups', params: native_payload(nodes:), headers:, as: :json
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
id = json.fetch('id')
|
||||
expect(group_version(id, 1).fetch('placements')).to eq(nodes)
|
||||
expect(json.fetch('placements').sole.fetch('children').sole)
|
||||
.to include('description' => '構造化の説明')
|
||||
expect(WikiPage.count).to eq(before_count)
|
||||
expect(warnings.grep(/tag_group_wiki_sync_skipped/).sole)
|
||||
.to include('リスト内 Section の説明文は Markdown にできません.')
|
||||
end
|
||||
it 'rolls back a facade update when its saved structure cannot be rendered' do
|
||||
id = seed_group(name: '更新', members:, placements:)
|
||||
allow(TagGroupMarkdownRenderer).to receive(:render)
|
||||
.and_raise(TagGroupMarkdownRenderer::UnsupportedStructure, 'unsupported')
|
||||
expect_no_writes { put_wiki(id, title: 'タググループ:改名') }
|
||||
end
|
||||
|
||||
context 'identifier Markdown' do
|
||||
@@ -293,10 +166,10 @@ RSpec.describe 'Tag group write integrity', type: :request do
|
||||
expect(group_version(id, 2)).to include('description' => '説明を追加', 'members' => members)
|
||||
end
|
||||
|
||||
it 'rolls back membership removal and placement edits on a late sync failure' do
|
||||
it 'rolls back membership removal and placement edits on a version persistence failure' do
|
||||
new_members = [{ 'tag_id' => tag.id }]
|
||||
new_nodes = [{ 'tag_id' => tag.id, 'children' => [] }]
|
||||
expect_atomic_group_write('wiki_versions') do
|
||||
expect_atomic_group_write('tag_group_versions') do
|
||||
put "/tag_groups/#{ id }",
|
||||
params: native_payload(nodes: new_nodes, member_rows: new_members), headers:, as: :json
|
||||
end
|
||||
|
||||
@@ -234,8 +234,7 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
|
||||
context 'compatibility writes for representable structures' do
|
||||
let(:group_id) do
|
||||
page = legacy_group('専用API', '説明')
|
||||
seed_group(name: '専用API', id: page.id, description: '説明')
|
||||
seed_group(name: '専用API', description: '説明')
|
||||
end
|
||||
let(:members) { [{ 'tag_id' => tag.id }, { 'tag_group_id' => child_id }] }
|
||||
let(:snapshot_members) do
|
||||
@@ -252,10 +251,9 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
group_id, number: 2, description: '全体の説明', placements:,
|
||||
members: snapshot_members + [
|
||||
{ 'tag_id' => unplaced.id, 'version_no' => unplaced.version_no }])
|
||||
page = WikiPage.joins(:tag_name).find_by!(tag_names: { name: 'タググループ:専用API' })
|
||||
|
||||
travel_to(1.hour.from_now) do
|
||||
expect_noop_group_update(group_id:, page:) do
|
||||
expect_noop_group_update(group_id:) do
|
||||
native_write(:update, unplaced_payload)
|
||||
end
|
||||
end
|
||||
@@ -267,20 +265,18 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
members: [{ 'tag_id' => tag.id }, { 'tag_group_id' => child_id }], placements: }
|
||||
end
|
||||
let(:additional_tag) { group_tag('追加対象タグ') }
|
||||
let(:compatibility_page) do
|
||||
WikiPage.joins(:tag_name).find_by!(tag_names: { name: 'タググループ:構造化' })
|
||||
end
|
||||
|
||||
before do
|
||||
native_write(:update, synchronised_payload)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(compatibility_page.body).to include('- 掲載タグ', '- タググループ:子')
|
||||
expect(group_markdown(TagGroup.find(group_id)))
|
||||
.to include('- 掲載タグ', '- タググループ:子')
|
||||
end
|
||||
|
||||
[1, 3].each do |repetitions|
|
||||
it "keeps identical native content unchanged after #{ repetitions } PUTs" do
|
||||
travel_to(1.hour.from_now) do
|
||||
expect_noop_group_update(group_id:, page: compatibility_page) do
|
||||
expect_noop_group_update(group_id:) do
|
||||
repetitions.times do
|
||||
native_write(:update, synchronised_payload)
|
||||
expect(response).to have_http_status(:ok)
|
||||
@@ -292,7 +288,7 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
|
||||
it 'treats membership order alone as a no-op' do
|
||||
travel_to(1.hour.from_now) do
|
||||
expect_noop_group_update(group_id:, page: compatibility_page) do
|
||||
expect_noop_group_update(group_id:) do
|
||||
native_write(:update, synchronised_payload.merge(
|
||||
members: synchronised_payload.fetch(:members).reverse))
|
||||
end
|
||||
@@ -302,7 +298,7 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
it 'keeps the original editor when another member submits identical content' do
|
||||
editor = create(:user, :member)
|
||||
travel_to(1.hour.from_now) do
|
||||
expect_noop_group_update(group_id:, page: compatibility_page) do
|
||||
expect_noop_group_update(group_id:) do
|
||||
put "/tag_groups/#{ group_id }", params: synchronised_payload,
|
||||
headers: { 'X-Transfer-Code' => editor.inheritance_code }, as: :json
|
||||
end
|
||||
@@ -313,7 +309,7 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
put "/wiki/#{ group_id }",
|
||||
params: { title: 'タググループ:構造化', body: '- 未掲載タグ' }, headers: headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(compatibility_page.reload.body).to eq('- 未掲載タグ')
|
||||
expect(group_markdown(TagGroup.find(group_id))).to eq('- 未掲載タグ')
|
||||
same_content = {
|
||||
name: '構造化', description: nil, members: [{ 'tag_id' => unplaced.id }],
|
||||
placements: [{ 'tag_id' => unplaced.id, 'children' => [] }] }
|
||||
@@ -323,7 +319,7 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
placements: same_content.fetch(:placements))
|
||||
|
||||
travel_to(1.hour.from_now) do
|
||||
expect_noop_group_update(group_id:, page: compatibility_page) do
|
||||
expect_noop_group_update(group_id:) do
|
||||
native_write(:update, same_content)
|
||||
end
|
||||
end
|
||||
@@ -332,7 +328,7 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
['name', 'description', 'membership addition', 'membership removal',
|
||||
'Section name', 'Section description', 'placement order', 'placement hierarchy']
|
||||
.each do |change|
|
||||
it "records a real #{ change } change in both histories" do
|
||||
it "records a real #{ change } change in native history" do
|
||||
changed = synchronised_payload.deep_dup
|
||||
section = changed.fetch(:placements).first
|
||||
children = section.fetch('children')
|
||||
@@ -356,11 +352,6 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
old_versions = group_rows('tag_group_versions', tag_group_id: group_id)
|
||||
group = TagGroup.find(group_id)
|
||||
number = group.version_no
|
||||
page = compatibility_page
|
||||
old_revision_id = page.current_revision.id
|
||||
old_wiki_version_id = page.wiki_versions.order(version_no: :desc).first.id
|
||||
revision_count = page.wiki_revisions.count
|
||||
version_count = page.wiki_versions.count
|
||||
travel_to(1.hour.from_now) { native_write(:update, changed) }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
@@ -379,23 +370,17 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
versions = group_rows('tag_group_versions', tag_group_id: group_id)
|
||||
expect(versions.size).to eq(old_versions.size + 1)
|
||||
expect(versions.select { _1.fetch('version_no') <= number }).to eq(old_versions)
|
||||
expect(page.wiki_revisions.count).to eq(revision_count + 1)
|
||||
expect(page.wiki_versions.count).to eq(version_count + 1)
|
||||
expect(page.reload.current_revision.id).not_to eq(old_revision_id)
|
||||
expect(page.wiki_versions.order(version_no: :desc).first.id)
|
||||
.not_to eq(old_wiki_version_id)
|
||||
get group_title_path(changed.fetch(:name))
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('body')).to eq(page.body)
|
||||
expect(json.fetch('body')).to eq(group_markdown(group))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates native state and its compatibility histories' do
|
||||
it 'creates native state and its native history' do
|
||||
expect { native_write(:create) }
|
||||
.to change(WikiPage, :count).by(1)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
.to change(TagGroup, :count).by(1)
|
||||
.and change(TagGroupVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:created)
|
||||
expect_group_snapshot(
|
||||
json.fetch('id'), number: 1, members: snapshot_members,
|
||||
@@ -410,18 +395,13 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
number = operation == :create ? 1 : 2
|
||||
expect_group_snapshot(
|
||||
id, number:, members: snapshot_members, placements:, description: '全体の説明')
|
||||
page = WikiPage.joins(:tag_name).find_by!(tag_names: { name: 'タググループ:構造化' })
|
||||
expect(page.body).to include('全体の説明', '# 区分', '- 掲載タグ', '- タググループ:子')
|
||||
expect(page.current_revision.body).to eq(page.body)
|
||||
expect(page.wiki_versions.order(:version_no).last.body).to eq(page.body)
|
||||
expect(page.wiki_revisions.count).to eq(number)
|
||||
expect(page.wiki_versions.count).to eq(number)
|
||||
expect(TagGroup.find(id).versions.count).to eq(number)
|
||||
get group_title_path('構造化')
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('body')).to eq(page.body)
|
||||
expect(json.fetch('body')).to include('全体の説明', '# 区分', '- 掲載タグ')
|
||||
end
|
||||
|
||||
(TagGroupRecords::GROUP_TABLES + ['wiki_pages', 'wiki_revisions', 'wiki_versions'])
|
||||
TagGroupRecords::GROUP_TABLES
|
||||
.each do |table|
|
||||
it "rolls back native #{ operation } after #{ table } writes" do
|
||||
expect_atomic_group_write(table) { native_write(operation) }
|
||||
@@ -429,12 +409,12 @@ RSpec.describe 'Tag group native API', type: :request do
|
||||
end
|
||||
end
|
||||
|
||||
it 'assigns the next group ID from the existing maximum' do
|
||||
it 'assigns the next group ID above the existing maximum' do
|
||||
child_id
|
||||
expected_id = [TagGroup.maximum(:id).to_i, TagDescription.maximum(:id).to_i].max + 1
|
||||
native_write(:create)
|
||||
expect(response).to have_http_status(:created)
|
||||
expect(json.fetch('id')).to eq(expected_id)
|
||||
expect(json.fetch('id')).to be >= expected_id
|
||||
end
|
||||
|
||||
TagGroupRecords::GROUP_TABLES.each do |table|
|
||||
|
||||
@@ -1,101 +1,61 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Tag resource ID allocation', type: :request do
|
||||
include TagGroupRecords
|
||||
include WikiRecords
|
||||
|
||||
let!(:user) { create_member_user! }
|
||||
let(:headers) { { 'X-Transfer-Code' => user.inheritance_code } }
|
||||
|
||||
def seed_description id
|
||||
tag = create(:tag, primary_name: "description_id_#{ id }")
|
||||
TagDescription.create!(id:, tag:, language_code: 'ja', body: 'existing', version_no: 1)
|
||||
end
|
||||
|
||||
def create_resource resource
|
||||
if resource == :description
|
||||
tag = create(:tag, primary_name: 'new_description')
|
||||
post '/wiki', params: { title: tag.name('ja'), body: 'new description' }, headers: headers
|
||||
else
|
||||
post '/tag_groups', params: {
|
||||
name: 'new_group', description: 'new group', members: [], placements: [] },
|
||||
headers:, as: :json
|
||||
end
|
||||
def create_description name
|
||||
tag = create(:tag, primary_name: name)
|
||||
post '/wiki', params: { title: tag.name('ja'), body: 'description' }, headers: headers
|
||||
expect(response).to have_http_status(:created)
|
||||
TagDescription.find(json.fetch('id'))
|
||||
end
|
||||
|
||||
[:description, :group].each do |resource|
|
||||
it "keeps #{ resource } independent of the Tag and ExternalTag ID space" do
|
||||
create(:tag, id: 100)
|
||||
create(:external_tag, id: 200)
|
||||
seed_description(1)
|
||||
seed_group(name: 'shared_space', id: 2)
|
||||
def create_group name
|
||||
post '/tag_groups', params: { name:, members: [], placements: [] }, headers:, as: :json
|
||||
expect(response).to have_http_status(:created)
|
||||
TagGroup.find(json.fetch('id'))
|
||||
end
|
||||
|
||||
create_resource(resource)
|
||||
|
||||
expect(json.fetch('id')).to eq(3)
|
||||
end
|
||||
|
||||
it "uses the other resource's larger maximum when allocating #{ resource }" do
|
||||
seed_description(resource == :description ? 1 : 20)
|
||||
seed_group(name: 'other_space', id: resource == :group ? 1 : 20)
|
||||
create_resource(resource)
|
||||
expect(json.fetch('id')).to eq(21)
|
||||
end
|
||||
|
||||
it "starts #{ resource } at 1 even after previous allocated rows were removed" do
|
||||
expect(TagDescription.count).to eq(0)
|
||||
expect(TagGroup.count).to eq(0)
|
||||
if resource == :description
|
||||
seed_description(100).destroy!
|
||||
else
|
||||
id = seed_group(name: 'removed', id: 100)
|
||||
TagGroup.find(id).destroy!
|
||||
end
|
||||
create_resource(resource)
|
||||
expect(json.fetch('id')).to eq(1)
|
||||
end
|
||||
|
||||
it "allocates #{ resource } above both maxima, preserving legacy overlaps and gaps" do
|
||||
[1, 2, 5].each { seed_description(_1) }
|
||||
[1, 3, 8].each { seed_group(name: "group_#{ _1 }", id: _1) }
|
||||
descriptions = TagDescription.order(:id).map(&:attributes)
|
||||
groups = TagGroup.order(:id).map(&:attributes)
|
||||
|
||||
create_resource(resource)
|
||||
|
||||
expect(json.fetch('id')).to eq(9)
|
||||
expect(TagDescription.where(id: [1, 2, 5]).order(:id).map(&:attributes)).to eq(descriptions)
|
||||
expect(TagGroup.where(id: [1, 3, 8]).order(:id).map(&:attributes)).to eq(groups)
|
||||
expect(TagDescription.exists?(id: 3)).to be(false)
|
||||
expect(TagGroup.exists?(id: 2)).to be(false)
|
||||
it 'shares resource and revision IDs while keeping independent version numbers' do
|
||||
description = create_description('first')
|
||||
group = create_group('second')
|
||||
other = create_description('third')
|
||||
expect([description.id, group.id, other.id]).to eq(
|
||||
[description.id, group.id, other.id].sort.uniq)
|
||||
versions = [current_description_version(description), group.versions.sole,
|
||||
current_description_version(other)]
|
||||
expect(versions.map(&:id)).to eq(versions.map(&:id).sort.uniq)
|
||||
expect(versions.map(&:version_no)).to eq([1, 1, 1])
|
||||
[description, group, other].zip(versions).each do |resource, version|
|
||||
get "/wiki/#{ resource.id }"
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('revision_id')).to eq(version.id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'preserves group ID and unrelated rows on update' do
|
||||
id = seed_group(name: 'existing', id: 5, description: 'before')
|
||||
other_id = seed_group(name: 'other', id: 8, description: 'other')
|
||||
other = TagGroup.find(other_id).attributes
|
||||
put "/tag_groups/#{ id }", params: {
|
||||
name: 'renamed', description: 'after', members: [], placements: [] }, headers:, as: :json
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('id')).to eq(id)
|
||||
expect(TagGroup.find(id).description).to eq('after')
|
||||
expect(TagGroup.find(other_id).attributes).to eq(other)
|
||||
expect(TagGroup.count).to eq(2)
|
||||
it 'does not reuse a deleted description ID for a group or vice versa' do
|
||||
description = create_description('deleted')
|
||||
description.destroy!
|
||||
group = create_group('deleted group')
|
||||
expect(group.id).to be > description.id
|
||||
group.destroy!
|
||||
expect(create_description('replacement').id).to be > group.id
|
||||
end
|
||||
|
||||
it 'preserves description identity on a compatibility update' do
|
||||
tag = create(:tag, primary_name: 'existing_description')
|
||||
page = seed_wiki_page(tag_name: tag.tag_name('ja'), body: 'before', created_by_user: user)
|
||||
description = TagDescription.find_by!(tag_id: tag.id, language_code: 'ja')
|
||||
id = description.id
|
||||
put "/wiki/#{ id }", params: {
|
||||
title: tag.name('ja'), body: 'after', base_revision_id: page.current_revision.id },
|
||||
it 'preserves resource and old revision identity on an update' do
|
||||
description = create_description('stable')
|
||||
original_resource_id = description.id
|
||||
original = current_description_version(description)
|
||||
snapshot = original.attributes
|
||||
put "/wiki/#{ description.id }",
|
||||
params: { title: 'stable', body: 'after', base_revision_id: original.id },
|
||||
headers: headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(description.reload.id).to eq(id)
|
||||
expect(description.body).to eq('after')
|
||||
expect(TagDescription.count).to eq(1)
|
||||
expect(description.reload.id).to eq(original_resource_id)
|
||||
expect(description.reload).to have_attributes(body: 'after', version_no: 2)
|
||||
expect(original.reload.attributes).to eq(snapshot)
|
||||
expect(current_description_version(description).id).to be > original.id
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'TagVersions API', type: :request do
|
||||
include WikiRecords
|
||||
let(:member) { create(:user, :member, name: 'version member') }
|
||||
|
||||
let!(:tag) { create(:tag, primary_name: 'tag_versions_target', category: :general) }
|
||||
@@ -240,18 +241,19 @@ RSpec.describe 'TagVersions API', type: :request do
|
||||
wiki_tag = create(:tag, primary_tag_name: wiki_tag_name, category: :general)
|
||||
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
seed_description(
|
||||
tag_name: wiki_tag_name,
|
||||
body: 'before',
|
||||
created_by_user: member,
|
||||
message: 'init')
|
||||
|
||||
Wiki::Commit.content!(
|
||||
page: wiki_page,
|
||||
TagDescriptionOperation.update!(
|
||||
Locale.nipponese,
|
||||
tag: wiki_page.tag,
|
||||
body: 'after',
|
||||
created_user: member,
|
||||
updated_by_user: member,
|
||||
message: 'edit',
|
||||
base_revision_id: wiki_page.current_revision.id)
|
||||
base_revision_id: current_description_version(wiki_page).id)
|
||||
|
||||
get '/tags/versions', params: { id: wiki_tag.id }
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
end
|
||||
|
||||
def create_wiki_for_tag! tag:, body: 'wiki body', user: member_user
|
||||
seed_wiki_page(
|
||||
seed_description(
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body:,
|
||||
created_by_user: user,
|
||||
@@ -29,7 +29,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
it 'renames the current Wiki resource without adding body history' do
|
||||
tag = create_tag!(name: 'patch_tag_wiki_before')
|
||||
wiki_page = create_wiki_for_tag!(tag:, body: 'wiki body before')
|
||||
wiki_history = wiki_page.wiki_versions.map(&:attributes)
|
||||
wiki_history = description_versions(wiki_page).map(&:attributes)
|
||||
|
||||
expect {
|
||||
patch "/tags/#{ tag.id }", params: {
|
||||
@@ -37,7 +37,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
}
|
||||
}
|
||||
.to change(TagVersion, :count).by(2)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
@@ -45,16 +45,16 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
wiki_page.reload
|
||||
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('patch_tag_wiki_after')
|
||||
expect(wiki_page.title).to eq('patch_tag_wiki_after')
|
||||
expect(wiki_page.tag.name('ja')).to eq('patch_tag_wiki_after')
|
||||
|
||||
expect(wiki_page.wiki_versions.reload.map(&:attributes)).to eq(wiki_history)
|
||||
expect(description_versions(wiki_page).reload.map(&:attributes)).to eq(wiki_history)
|
||||
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
|
||||
before_wiki_versions = description_versions(wiki_page).count
|
||||
|
||||
expect {
|
||||
patch "/tags/#{ tag.id }", params: {
|
||||
@@ -70,7 +70,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('patch_tag_category_only')
|
||||
expect(tag.category).to eq('meme')
|
||||
expect(wiki_page.wiki_versions.count).to eq(before_wiki_versions)
|
||||
expect(description_versions(wiki_page).count).to eq(before_wiki_versions)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -78,7 +78,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
it 'renames the current Wiki resource without adding body history' do
|
||||
tag = create_tag!(name: 'put_tag_wiki_before')
|
||||
wiki_page = create_wiki_for_tag!(tag:, body: 'wiki body before')
|
||||
wiki_history = wiki_page.wiki_versions.map(&:attributes)
|
||||
wiki_history = description_versions(wiki_page).map(&:attributes)
|
||||
|
||||
expect {
|
||||
put "/tags/#{ tag.id }", params: {
|
||||
@@ -90,7 +90,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
}
|
||||
}
|
||||
.to change(TagVersion, :count).by(2)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
@@ -98,16 +98,16 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
wiki_page.reload
|
||||
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('put_tag_wiki_after')
|
||||
expect(wiki_page.title).to eq('put_tag_wiki_after')
|
||||
expect(wiki_page.tag.name('ja')).to eq('put_tag_wiki_after')
|
||||
|
||||
expect(wiki_page.wiki_versions.reload.map(&:attributes)).to eq(wiki_history)
|
||||
expect(description_versions(wiki_page).reload.map(&:attributes)).to eq(wiki_history)
|
||||
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
|
||||
before_wiki_versions = description_versions(wiki_page).count
|
||||
|
||||
expect {
|
||||
put "/tags/#{ tag.id }", params: {
|
||||
@@ -127,14 +127,14 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('put_tag_category_only')
|
||||
expect(tag.category).to eq('meme')
|
||||
expect(wiki_page.wiki_versions.count).to eq(before_wiki_versions)
|
||||
expect(description_versions(wiki_page).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
|
||||
before_wiki_versions = description_versions(wiki_page).count
|
||||
|
||||
expect {
|
||||
put "/tags/#{ tag.id }", params: {
|
||||
@@ -148,7 +148,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
.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)
|
||||
expect(description_versions(wiki_page).count).to eq(before_wiki_versions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@ require 'cgi'
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Tags API', type: :request do
|
||||
include WikiRecords
|
||||
let!(:tn) { create(:tag_name, name: 'spec_tag') }
|
||||
let!(:tag) { create(:tag, primary_tag_name: tn, category: :general) }
|
||||
let!(:alias_tn) { create(:tag_name, :alias, name: 'unko', tag:) }
|
||||
@@ -745,13 +746,13 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
it 'PATCH の名称変更で Wiki の本文履歴を追加しない' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
seed_description(
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
|
||||
wiki_history = wiki_page.wiki_versions.map(&:attributes)
|
||||
wiki_history = description_versions(wiki_page).map(&:attributes)
|
||||
|
||||
expect {
|
||||
patch "/tags/#{ tag.id }", params: {
|
||||
@@ -759,23 +760,23 @@ RSpec.describe 'Tags API', type: :request do
|
||||
}
|
||||
}
|
||||
.to change(TagVersion, :count).by(2)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
expect(wiki_page.reload.title).to eq('patch_wiki_renamed_tag')
|
||||
expect(wiki_page.wiki_versions.reload.map(&:attributes)).to eq(wiki_history)
|
||||
expect(wiki_page.reload.tag.name('ja')).to eq('patch_wiki_renamed_tag')
|
||||
expect(description_versions(wiki_page).reload.map(&:attributes)).to eq(wiki_history)
|
||||
end
|
||||
|
||||
it 'tag の category だけを変更しても wiki version は作成しない' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
seed_description(
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
|
||||
before_wiki_version_count = wiki_page.reload.wiki_versions.count
|
||||
before_wiki_version_count = description_versions(wiki_page).count
|
||||
|
||||
expect {
|
||||
patch "/tags/#{ tag.id }", params: {
|
||||
@@ -784,7 +785,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
}.to change(TagVersion, :count).by(2)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(wiki_page.reload.wiki_versions.count).to eq(before_wiki_version_count)
|
||||
expect(description_versions(wiki_page).count).to eq(before_wiki_version_count)
|
||||
end
|
||||
|
||||
it 'full update で旧名 alias が残った tag を PATCH で旧名へ戻せる' do
|
||||
@@ -815,7 +816,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
it '別 tag の正規名には変更できない' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
seed_description(
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'patch collision wiki',
|
||||
created_by_user: member_user,
|
||||
@@ -831,7 +832,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
expect(primary_tag_name_for(tag.reload, 'ja').name).to eq('spec_tag')
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'ja', primary_flg: false).pluck(:name))
|
||||
.to contain_exactly('unko')
|
||||
expect(wiki_page.reload.tag_name).to eq(primary_tag_name_for(tag, 'ja'))
|
||||
expect(wiki_page.reload.tag.tag_name('ja')).to eq(primary_tag_name_for(tag, 'ja'))
|
||||
end
|
||||
|
||||
it 'system tag の name は変更できない' do
|
||||
@@ -1209,7 +1210,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
expect(old_name_alias).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'ja', primary_flg: false)
|
||||
|
||||
expect(alias_tn.reload.tag_id).to be_nil
|
||||
expect(TagName.exists?(alias_tn.id)).to be(false)
|
||||
|
||||
expect(TagName.where(tag_id: tag.parents.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name))
|
||||
@@ -1394,20 +1395,17 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
it 'Wiki を持つ Tag の名称変更で本文履歴を追加しない' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
seed_description(
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
|
||||
Wiki::Commit.content!(
|
||||
page: wiki_page,
|
||||
body: 'wiki body before',
|
||||
created_user: member_user,
|
||||
message: 'init'
|
||||
)
|
||||
TagDescriptionOperation.update!(
|
||||
Locale.nipponese, tag: wiki_page.tag, body: 'wiki body before',
|
||||
updated_by_user: member_user, message: 'init')
|
||||
|
||||
wiki_history = wiki_page.wiki_versions.map(&:attributes)
|
||||
wiki_history = description_versions(wiki_page).map(&:attributes)
|
||||
|
||||
expect {
|
||||
put "/tags/#{ tag.id }", params: {
|
||||
@@ -1419,22 +1417,22 @@ RSpec.describe 'Tags API', type: :request do
|
||||
}
|
||||
}
|
||||
.to change(TagVersion, :count).by(2)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
expect(wiki_page.reload.title).to eq('put_wiki_version_tag')
|
||||
expect(wiki_page.wiki_versions.reload.map(&:attributes)).to eq(wiki_history)
|
||||
expect(wiki_page.reload.tag.name('ja')).to eq('put_wiki_version_tag')
|
||||
expect(description_versions(wiki_page).reload.map(&:attributes)).to eq(wiki_history)
|
||||
end
|
||||
|
||||
it 'wiki を持つ tag を旧 alias へ戻しても wiki を新しい正本名へ移す' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
seed_description(
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
wiki_history = wiki_page.wiki_versions.map(&:attributes)
|
||||
wiki_history = description_versions(wiki_page).map(&:attributes)
|
||||
|
||||
expect {
|
||||
put "/tags/#{ tag.id }", params: {
|
||||
@@ -1454,20 +1452,19 @@ RSpec.describe 'Tags API', type: :request do
|
||||
}
|
||||
}
|
||||
.to change(TagVersion, :count).by(3)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
tag.reload
|
||||
|
||||
expect(wiki_page.reload.tag_name).to eq(primary_tag_name_for(tag, 'ja'))
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'put_wiki_roundtrip_b').wiki_page)
|
||||
.to be_nil
|
||||
expect(wiki_page.reload.tag.tag_name('ja')).to eq(primary_tag_name_for(tag, 'ja'))
|
||||
expect(wiki_page.reload.tag_id).to eq(tag.id)
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'put_wiki_roundtrip_b'))
|
||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
||||
|
||||
expect(wiki_page.title).to eq('spec_tag')
|
||||
expect(wiki_page.wiki_versions.reload.map(&:attributes)).to eq(wiki_history)
|
||||
expect(wiki_page.tag.name('ja')).to eq('spec_tag')
|
||||
expect(description_versions(wiki_page).reload.map(&:attributes)).to eq(wiki_history)
|
||||
end
|
||||
|
||||
it '別名を他 tag から奪った場合、奪はれた側の tag version も作成する' do
|
||||
@@ -1520,7 +1517,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
name: 'put_alias_collision_name',
|
||||
tag: old_owner)
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
seed_description(
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'put collision wiki',
|
||||
created_by_user: member_user,
|
||||
@@ -1554,7 +1551,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
expect(old_owner_versions.last.event_type).to eq('update')
|
||||
expect(version_alias_names(old_owner_versions.last, 'ja'))
|
||||
.not_to include('put_alias_collision_name')
|
||||
expect(wiki_page.reload.tag_name).to eq(primary_tag_name_for(tag, 'ja'))
|
||||
expect(wiki_page.reload.tag.tag_name('ja')).to eq(primary_tag_name_for(tag, 'ja'))
|
||||
end
|
||||
|
||||
it 'parent_tags に指定すると循環する tag は 422 にする' do
|
||||
@@ -1579,13 +1576,13 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
it 'Tag の名称変更で Wiki の本文履歴を追加しない' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
seed_description(
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
|
||||
wiki_history = wiki_page.wiki_versions.map(&:attributes)
|
||||
wiki_history = description_versions(wiki_page).map(&:attributes)
|
||||
|
||||
expect {
|
||||
put "/tags/#{ tag.id }", params: {
|
||||
@@ -1597,12 +1594,12 @@ RSpec.describe 'Tags API', type: :request do
|
||||
}
|
||||
}
|
||||
.to change(TagVersion, :count).by(2)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
expect(wiki_page.reload.title).to eq('put_wiki_renamed_tag')
|
||||
expect(wiki_page.wiki_versions.reload.map(&:attributes)).to eq(wiki_history)
|
||||
expect(wiki_page.reload.tag.name('ja')).to eq('put_wiki_renamed_tag')
|
||||
expect(description_versions(wiki_page).reload.map(&:attributes)).to eq(wiki_history)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,18 +10,19 @@ RSpec.describe 'Wiki conflict handling', type: :request do
|
||||
|
||||
it 'returns 409 when base_revision_id is stale' do
|
||||
page =
|
||||
seed_wiki_page(
|
||||
seed_description(
|
||||
tag_name: create(:tag, primary_name: 'wiki_conflict_request').tag_name('ja'),
|
||||
body: 'first',
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
|
||||
stale_id = page.current_revision.id
|
||||
stale_id = current_description_version(page).id
|
||||
|
||||
Wiki::Commit.content!(
|
||||
page:,
|
||||
TagDescriptionOperation.update!(
|
||||
Locale.nipponese,
|
||||
tag: page.tag,
|
||||
body: 'second',
|
||||
created_user: user,
|
||||
updated_by_user: user,
|
||||
message: 'other edit',
|
||||
base_revision_id: stale_id)
|
||||
|
||||
@@ -36,6 +37,6 @@ RSpec.describe 'Wiki conflict handling', type: :request do
|
||||
|
||||
page.reload
|
||||
expect(page.body).to eq('second')
|
||||
expect(page.current_revision.message).to eq('other edit')
|
||||
expect(current_description_version(page)).to have_attributes(body: 'second', version_no: 2)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,27 +7,11 @@ RSpec.describe 'Wiki creation freeze', type: :request do
|
||||
let!(:user) { create_member_user! }
|
||||
let(:headers) { { 'X-Transfer-Code' => user.inheritance_code } }
|
||||
|
||||
it 'creates missing compatibility history when updating an independent group' do
|
||||
id = seed_group(name: 'independent', description: 'before')
|
||||
expect do
|
||||
put "/tag_groups/#{ id }", params: {
|
||||
name: 'independent', description: 'after', members: [], placements: [] },
|
||||
headers:, as: :json
|
||||
end.to change(WikiPage, :count).by(1)
|
||||
.and change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(TagGroup.find(id).description).to eq('after')
|
||||
page = WikiPage.joins(:tag_name).find_by!(tag_names: { name: 'タググループ:independent' })
|
||||
expect(page.body).to include('after')
|
||||
expect(page.current_revision.body).to eq(page.body)
|
||||
expect(page.wiki_versions.sole.body).to eq(page.body)
|
||||
end
|
||||
|
||||
context 'existing description write protection' do
|
||||
let!(:tag) { create(:tag, primary_name: 'protected_description') }
|
||||
let!(:page) do
|
||||
seed_wiki_page(tag_name: tag.tag_name('ja'), body: 'before', created_by_user: user)
|
||||
seed_description(tag_name: tag.tag_name('ja'), body: 'before', created_by_user: user)
|
||||
end
|
||||
|
||||
[nil, :guest, :banned].each do |role|
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
require 'cgi'
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Wiki history integrity', type: :request do
|
||||
include WikiRecords
|
||||
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
|
||||
seed_wiki_page(
|
||||
tag_name: create(:tag, primary_name: title).tag_name('ja'),
|
||||
body:,
|
||||
created_by_user: user,
|
||||
message:)
|
||||
end
|
||||
|
||||
describe 'POST /wiki' do
|
||||
it 'rejects frozen creation without partial page or history' do
|
||||
expect do
|
||||
post '/wiki', params: { title: 'Help:wiki_history_create_atomic', body: 'initial' },
|
||||
headers: auth_headers(user)
|
||||
end.to change(WikiPage, :count).by(0)
|
||||
.and change(WikiRevision, :count).by(0)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
expect(response.status).to be_between(400, 499)
|
||||
end
|
||||
|
||||
it 'returns 422 and creates nothing when normalised body is blank' do
|
||||
expect {
|
||||
post '/wiki',
|
||||
params: {
|
||||
title: 'Help: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: 'Help:wiki_history_blank_body' })).not_to exist
|
||||
end
|
||||
|
||||
it 'rejects frozen creation without modifying an existing page or history' do
|
||||
create_wiki_page(title: 'Help:wiki_history_duplicate_title', body: 'first')
|
||||
|
||||
expect do
|
||||
post '/wiki',
|
||||
params: { title: 'Help:wiki_history_duplicate_title',
|
||||
body: 'second', message: 'duplicate' },
|
||||
headers: auth_headers(user)
|
||||
end.to change(WikiPage, :count).by(0)
|
||||
.and change(WikiRevision, :count).by(0)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
|
||||
expect(response.status).to be_between(400, 499)
|
||||
expect(WikiPage.joins(:tag_name)
|
||||
.where(tag_names: { name: 'Help: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
|
||||
@@ -28,15 +28,15 @@ RSpec.describe 'Wiki resource ordering', type: :request do
|
||||
it 'breaks timestamp ties by resource kind and ID while retaining legacy overlaps' do
|
||||
time = Time.utc(2026, 1, 1)
|
||||
tag = create(:tag, primary_name: 'tied')
|
||||
TagDescription.create!(id: 51, tag:, language_code: 'ja', body: 'current',
|
||||
description = TagDescription.create!(tag:, language_code: 'ja', body: 'current',
|
||||
version_no: 1, updated_at: time)
|
||||
group = TagGroup.find(seed_group(name: 'tied', id: 51))
|
||||
group = TagGroup.find(seed_group(name: 'tied', id: description.id))
|
||||
group.update_columns(updated_at: time)
|
||||
|
||||
2.times do
|
||||
get '/wiki', params: { title: 'tied' }
|
||||
expect(json.map { [_1.fetch('resource_type'), _1.fetch('id')] })
|
||||
.to eq([['tag_description', 51], ['tag_group', 51]])
|
||||
.to eq([['tag_description', description.id], ['tag_group', description.id]])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Wiki and native API locale contracts', type: :request do
|
||||
include WikiRecords
|
||||
include TagGroupRecords
|
||||
include_context 'Ainu locale contract'
|
||||
|
||||
let!(:user) { create_member_user! }
|
||||
let!(:tag) { create(:tag, primary_name: 'japanese_title') }
|
||||
let!(:ainu_name) { add_ainu_name(tag, 'ainu_title') }
|
||||
let(:headers) { { 'X-Transfer-Code' => user.inheritance_code } }
|
||||
let!(:japanese) do
|
||||
TagDescriptionOperation.create!(
|
||||
Locale.nipponese, tag:, body: 'Japanese body', created_by_user: user)
|
||||
end
|
||||
let!(:ainu) do
|
||||
TagDescriptionOperation.create!(
|
||||
ainu_locale, tag:, body: 'Ainu body', created_by_user: user)
|
||||
end
|
||||
|
||||
['/wiki', '/wiki/search'].each do |path|
|
||||
it "filters #{ path } by the selected language" do
|
||||
get path, params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.map { _1.fetch('id') }).to eq([ainu.id])
|
||||
expect(json.sole).to include('title' => 'ainu_title', 'body' => 'Ainu body')
|
||||
|
||||
get path, params: { locale: 'ain', title: 'japanese' }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
it 'uses the existing default locale when no locale is specified' do
|
||||
get '/wiki'
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.map { _1.fetch('id') }).to eq([japanese.id])
|
||||
end
|
||||
|
||||
it 'resolves titles and aliases only in the selected language in both APIs' do
|
||||
['/wiki/title/japanese_title', '/tags/name/japanese_title/description'].each do |path|
|
||||
get path, params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
['/wiki/title/ainu_title', '/tags/name/ainu_title/description'].each do |path|
|
||||
get path, params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('body')).to eq('Ainu body')
|
||||
end
|
||||
get '/wiki/title/ainu_title/exists', params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:no_content)
|
||||
get '/wiki/title/japanese_title/exists', params: { locale: 'ain' }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it 'does not disclose another language through ID, existence, diff or history' do
|
||||
revision = current_description_version(japanese)
|
||||
["/wiki/#{ japanese.id }", "/wiki/#{ japanese.id }/exists",
|
||||
"/wiki/#{ japanese.id }/diff"].each do |path|
|
||||
get path, params: { locale: 'ain', from: revision.id }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
expect(response.body).not_to include('Japanese body')
|
||||
end
|
||||
get '/wiki/changes', params: { locale: 'ain', id: japanese.id }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to eq([])
|
||||
get '/wiki/changes', params: { locale: 'ain' }
|
||||
expect(json.map { _1.fetch('revision_id') })
|
||||
.to eq([current_description_version(ainu).id])
|
||||
end
|
||||
|
||||
it 'does not update a description addressed through another language ID' do
|
||||
snapshot = [japanese.attributes, ainu.attributes, ainu_name.attributes]
|
||||
expect do
|
||||
put "/wiki/#{ japanese.id }", params: {
|
||||
locale: 'ain', title: 'renamed', body: 'wrong target' }, headers: headers
|
||||
end.not_to change(TagDescriptionVersion, :count)
|
||||
expect(response).to have_http_status(:not_found)
|
||||
expect([japanese.reload.attributes, ainu.reload.attributes, ainu_name.reload.attributes])
|
||||
.to eq(snapshot)
|
||||
end
|
||||
|
||||
[:wiki, :native].each do |api|
|
||||
it "creates the selected language through #{ api } without changing Japanese history" do
|
||||
other = create(:tag, primary_name: 'other_japanese')
|
||||
add_ainu_name(other, 'other_ainu')
|
||||
original = TagDescriptionOperation.create!(
|
||||
Locale.nipponese, tag: other, body: 'original', created_by_user: user)
|
||||
snapshot = original.attributes
|
||||
expect do
|
||||
if api == :wiki
|
||||
post '/wiki', params: { locale: 'ain', title: 'other_ainu', body: 'created' },
|
||||
headers: headers
|
||||
else
|
||||
put "/tags/#{ other.id }/description", params: { locale: 'ain', body: 'created' },
|
||||
headers: headers
|
||||
end
|
||||
end.to change(TagDescription, :count).by(1)
|
||||
.and change(TagDescriptionVersion, :count).by(1)
|
||||
expect(response).to have_http_status(:created)
|
||||
created = TagDescription.find_by!(tag: other, language_code: 'ain')
|
||||
expect(created).to have_attributes(body: 'created', version_no: 1)
|
||||
expect(current_description_version(created)).to have_attributes(language_code: 'ain')
|
||||
expect(original.reload.attributes).to eq(snapshot)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shares selected-language edits and revision identity across both APIs' do
|
||||
snapshot = japanese.attributes
|
||||
first = current_description_version(ainu)
|
||||
put "/tags/#{ tag.id }/description", params: { locale: 'ain', body: 'native edit' },
|
||||
headers: headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
second = current_description_version(ainu)
|
||||
expect(second).to have_attributes(language_code: 'ain', version_no: 2)
|
||||
expect(second.id).not_to eq(first.id)
|
||||
get "/wiki/#{ ainu.id }", params: { locale: 'ain' }
|
||||
expect(json).to include('body' => 'native edit', 'revision_id' => second.id)
|
||||
|
||||
put "/wiki/#{ ainu.id }", params: {
|
||||
locale: 'ain', title: 'ainu_title', body: 'facade edit',
|
||||
base_revision_id: second.id }, headers: headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
get "/tags/#{ tag.id }/description", params: { locale: 'ain' }
|
||||
expect(json).to include('body' => 'facade edit', 'language_code' => 'ain', 'version_no' => 3)
|
||||
expect(japanese.reload.attributes).to eq(snapshot)
|
||||
end
|
||||
|
||||
it 'rejects a stale selected-language revision without a partial title or body edit' do
|
||||
stale = current_description_version(ainu)
|
||||
TagDescriptionOperation.update!(
|
||||
ainu_locale, tag:, body: 'current', updated_by_user: user)
|
||||
snapshot = [ainu.reload.attributes, ainu_name.attributes, japanese.attributes]
|
||||
expect do
|
||||
put "/wiki/#{ ainu.id }", params: {
|
||||
locale: 'ain', title: 'conflicting_name', body: 'current',
|
||||
base_revision_id: stale.id }, headers: headers
|
||||
end.to change(TagDescriptionVersion, :count).by(0).and change(TagVersion, :count).by(0)
|
||||
expect(response).to have_http_status(:conflict)
|
||||
expect([ainu.reload.attributes, ainu_name.reload.attributes, japanese.reload.attributes])
|
||||
.to eq(snapshot)
|
||||
end
|
||||
|
||||
it 'scopes historical show and diff to the selected description' do
|
||||
first = current_description_version(ainu)
|
||||
TagDescriptionOperation.update!(
|
||||
ainu_locale, tag:, body: 'later', updated_by_user: user)
|
||||
get "/wiki/#{ ainu.id }", params: { locale: 'ain', version: first.id }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to include('body' => 'Ainu body', 'revision_id' => first.id)
|
||||
get "/wiki/#{ ainu.id }/diff", params: { locale: 'ain', from: first.id }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('newer_revision_id')).to eq(current_description_version(ainu).id)
|
||||
get "/wiki/#{ ainu.id }/diff", params: {
|
||||
locale: 'ain', from: current_description_version(japanese).id }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
@@ -19,7 +19,7 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
deprecated_at: Time.zone.local(2026, 6, 1))
|
||||
end
|
||||
let!(:page) do
|
||||
seed_wiki_page(
|
||||
seed_description(
|
||||
tag_name: tn,
|
||||
body: 'init',
|
||||
created_by_user: user,
|
||||
@@ -113,9 +113,9 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
it "rejects frozen creation of #{ title } without history writes" do
|
||||
expect do
|
||||
post endpoint, params: { title:, body: 'initial' }, headers: auth_headers(member)
|
||||
end.to change(WikiPage, :count).by(0)
|
||||
.and change(WikiRevision, :count).by(0)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
end.to change(TagDescription, :count).by(0)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
|
||||
expect(response.status).to be_between(400, 499)
|
||||
end
|
||||
end
|
||||
@@ -132,7 +132,7 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
let!(:test_tag_name) { create(:tag, primary_name: 'TestPage').tag_name('ja') }
|
||||
|
||||
let!(:page) do
|
||||
seed_wiki_page(
|
||||
seed_description(
|
||||
tag_name: test_tag_name,
|
||||
body: "a\nb",
|
||||
created_by_user: member,
|
||||
@@ -166,7 +166,7 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
|
||||
context 'when success' do
|
||||
it 'creates new revision and returns 200' do
|
||||
current_id = page.wiki_revisions.maximum(:id)
|
||||
current_id = description_versions(page).maximum(:id)
|
||||
|
||||
expect do
|
||||
put "/wiki/#{page.id}",
|
||||
@@ -174,26 +174,22 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
base_revision_id: current_id },
|
||||
headers: auth_headers(member)
|
||||
end
|
||||
.to change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
.to change(TagDescriptionVersion, :count).by(1)
|
||||
|
||||
version = page.wiki_versions.order(:version_no).last
|
||||
|
||||
version = description_versions(page).order(:version_no).last
|
||||
|
||||
expect(version).to have_attributes(
|
||||
event_type: 'update',
|
||||
title: 'TestPage',
|
||||
body: "x\ny",
|
||||
created_by_user_id: member.id
|
||||
)
|
||||
created_by_user_id: member.id)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
page.reload
|
||||
rev = page.current_revision
|
||||
expect(rev).to be_content
|
||||
expect(rev.message).to eq('edit')
|
||||
rev = current_description_version(page)
|
||||
expect(page.body).to eq("x\ny")
|
||||
expect(rev.base_revision_id).to eq(current_id)
|
||||
expect(rev.version_no).to eq(2)
|
||||
end
|
||||
|
||||
it 'wiki body だけを変更しても tag version は作成しない' do
|
||||
@@ -206,13 +202,13 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
created_by_user: member)
|
||||
|
||||
linked_page =
|
||||
seed_wiki_page(
|
||||
seed_description(
|
||||
tag_name: linked_tag_name,
|
||||
body: 'before',
|
||||
created_by_user: member,
|
||||
message: 'init')
|
||||
|
||||
current_id = linked_page.current_revision.id
|
||||
current_id = current_description_version(linked_page).id
|
||||
before_count = linked_tag.reload.tag_versions.count
|
||||
|
||||
expect {
|
||||
@@ -225,8 +221,8 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
},
|
||||
headers: auth_headers(member)
|
||||
}
|
||||
.to change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
.to change(TagDescriptionVersion, :count).by(1)
|
||||
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(linked_tag.reload.tag_versions.count).to eq(before_count)
|
||||
@@ -236,10 +232,12 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
context 'when conflict' do
|
||||
it 'returns 409 when base_revision_id mismatches' do
|
||||
# 先に別ユーザ(同じ member でもOK)が 1 回更新して先頭を進める
|
||||
Wiki::Commit.content!(page: page, body: "zzz", created_user: member, message: 'other edit')
|
||||
TagDescriptionOperation.update!(
|
||||
Locale.nipponese, tag: page.tag, body: 'zzz',
|
||||
updated_by_user: member, message: 'other edit')
|
||||
page.reload
|
||||
|
||||
stale_id = page.wiki_revisions.order(:id).first.id # わざと古い id
|
||||
stale_id = description_versions(page).order(:id).first.id # わざと古い id
|
||||
put "/wiki/#{page.id}",
|
||||
params: { title: 'TestPage', body: 'x', base_revision_id: stale_id },
|
||||
headers: auth_headers(member)
|
||||
@@ -281,13 +279,13 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
describe 'GET /wiki/search' do
|
||||
before do
|
||||
search_tag = create(:tag, primary_name: 'spec_wiki_title_2')
|
||||
seed_wiki_page(
|
||||
seed_description(
|
||||
tag_name: search_tag.tag_name('ja'),
|
||||
body: 'search body 2',
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
|
||||
seed_wiki_page(
|
||||
seed_description(
|
||||
tag_name: create(:tag_name, name: 'unrelated_title'),
|
||||
body: 'unrelated body',
|
||||
created_by_user: user,
|
||||
@@ -318,13 +316,15 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
|
||||
describe 'GET /wiki/changes' do
|
||||
let!(:rev1) do
|
||||
Wiki::Commit.content!(page: page, body: "a\nb", created_user: user, message: 'r1')
|
||||
page.current_revision
|
||||
TagDescriptionOperation.update!(
|
||||
Locale.nipponese, tag: page.tag, body: "a\nb", updated_by_user: user, message: 'r1')
|
||||
current_description_version(page)
|
||||
end
|
||||
|
||||
let!(:rev2) do
|
||||
Wiki::Commit.content!(page: page, body: "a\nc", created_user: user, message: 'r2')
|
||||
page.current_revision
|
||||
TagDescriptionOperation.update!(
|
||||
Locale.nipponese, tag: page.tag, body: "a\nc", updated_by_user: user, message: 'r2')
|
||||
current_description_version(page)
|
||||
end
|
||||
|
||||
it 'returns latest revisions (optionally filtered by page id)' do
|
||||
@@ -358,12 +358,9 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
it 'returns empty array when page has no revisions and filtered by id' do
|
||||
# 別ページを作って revision 無し
|
||||
tn2 = create(:tag_name, name: 'spec_no_rev')
|
||||
# 異常データ: revision 無し WikiPage を直接作る
|
||||
p2 = WikiPage.create!(
|
||||
tag_name: tn2,
|
||||
body: 'init',
|
||||
created_user: user,
|
||||
updated_user: user)
|
||||
# 異常データ: Version のない TagDescription を直接作る
|
||||
p2 = TagDescription.create!(
|
||||
tag: tn2.tag, language_code: 'ja', body: 'init', version_no: 1)
|
||||
|
||||
get "/wiki/changes?id=#{p2.id}"
|
||||
expect(response).to have_http_status(:ok)
|
||||
@@ -399,13 +396,15 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
|
||||
describe 'GET /wiki/:id/diff' do
|
||||
let!(:rev_a) do
|
||||
Wiki::Commit.content!(page: page, body: "a\nb\nc", created_user: user, message: 'A')
|
||||
page.current_revision
|
||||
TagDescriptionOperation.update!(
|
||||
Locale.nipponese, tag: page.tag, body: "a\nb\nc", updated_by_user: user, message: 'A')
|
||||
current_description_version(page)
|
||||
end
|
||||
|
||||
let!(:rev_b) do
|
||||
Wiki::Commit.content!(page: page, body: "a\nx\nc", created_user: user, message: 'B')
|
||||
page.current_revision
|
||||
TagDescriptionOperation.update!(
|
||||
Locale.nipponese, tag: page.tag, body: "a\nx\nc", updated_by_user: user, message: 'B')
|
||||
current_description_version(page)
|
||||
end
|
||||
|
||||
it 'returns diff json between revisions' do
|
||||
@@ -431,44 +430,23 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json['older_revision_id']).to eq(rev_a.id)
|
||||
expect(json['newer_revision_id']).to eq(page.current_revision.id)
|
||||
expect(json['newer_revision_id']).to eq(current_description_version(page).id)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Wiki::Commit.redirect!' do
|
||||
it 'raises because redirect revisions are deprecated' do
|
||||
target_tag_name = create(:tag_name, name: 'redirect_deprecated_target')
|
||||
target =
|
||||
seed_wiki_page(
|
||||
tag_name: target_tag_name,
|
||||
body: 'target',
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
|
||||
expect {
|
||||
Wiki::Commit.redirect!(
|
||||
page: page,
|
||||
redirect_page: target,
|
||||
created_user: user,
|
||||
message: 'redirect',
|
||||
base_revision_id: page.current_revision.id
|
||||
)
|
||||
}.to raise_error(RuntimeError, '廃止しました.')
|
||||
end
|
||||
end
|
||||
|
||||
it 'wiki title を変更すると対応する tag の version を作成する' do
|
||||
linked_tag_name = create(:tag_name, name: 'wiki_linked_tag_for_version')
|
||||
linked_tag = create(:tag, primary_tag_name: linked_tag_name, category: :general)
|
||||
|
||||
linked_page =
|
||||
seed_wiki_page(
|
||||
seed_description(
|
||||
tag_name: linked_tag_name,
|
||||
body: 'before',
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
|
||||
current_id = linked_page.current_revision.id
|
||||
current_id = current_description_version(linked_page).id
|
||||
|
||||
expect {
|
||||
put "/wiki/#{ linked_page.id }",
|
||||
@@ -480,8 +458,7 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
},
|
||||
headers: auth_headers(user)
|
||||
}
|
||||
.to change(WikiRevision, :count).by(1)
|
||||
.and change(WikiVersion, :count).by(1)
|
||||
.to change(TagDescriptionVersion, :count).by(1)
|
||||
.and change { linked_tag.reload.tag_versions.count }.by(2)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
@@ -9,7 +9,7 @@ RSpec.describe 'Wiki title collision', type: :request do
|
||||
end
|
||||
|
||||
def create_wiki_page title:, body:
|
||||
seed_wiki_page(
|
||||
seed_description(
|
||||
tag_name: create(:tag_name, name: title),
|
||||
body:,
|
||||
created_by_user: user,
|
||||
@@ -20,9 +20,9 @@ RSpec.describe 'Wiki title collision', type: :request 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
|
||||
source_revision_count = description_versions(source).count
|
||||
source_version_count = description_versions(source).count
|
||||
old_title = source.tag.reload.name('ja')
|
||||
old_body = source.body
|
||||
|
||||
put "/wiki/#{ source.id }",
|
||||
@@ -30,7 +30,7 @@ RSpec.describe 'Wiki title collision', type: :request do
|
||||
title: 'wiki_collision_target',
|
||||
body: 'new body',
|
||||
message: 'rename collision',
|
||||
base_revision_id: source.current_revision.id,
|
||||
base_revision_id: current_description_version(source).id,
|
||||
},
|
||||
headers: auth_headers(user)
|
||||
|
||||
@@ -38,10 +38,10 @@ RSpec.describe 'Wiki title collision', type: :request do
|
||||
|
||||
source.reload
|
||||
|
||||
expect(source.title).to eq(old_title)
|
||||
expect(source.tag.reload.name('ja')).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)
|
||||
expect(description_versions(source).count).to eq(source_revision_count)
|
||||
expect(description_versions(source).count).to eq(source_version_count)
|
||||
end
|
||||
|
||||
it 'rejects frozen creation with an existing title without adding history' do
|
||||
@@ -52,9 +52,8 @@ RSpec.describe 'Wiki title collision', type: :request do
|
||||
params: { title: 'Help:wiki_collision_create', body: 'new body',
|
||||
message: 'duplicate create' },
|
||||
headers: auth_headers(user)
|
||||
end.to change(WikiPage, :count).by(0)
|
||||
.and change(WikiRevision, :count).by(0)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
end.to change(TagDescription, :count).by(0)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
|
||||
expect(response.status).to be_between(400, 499)
|
||||
end
|
||||
|
||||
@@ -1,78 +1,25 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Unmigrated Wiki compatibility', type: :request do
|
||||
include WikiRecords
|
||||
let!(:user) { create_member_user! }
|
||||
let!(:tag_name) { create(:tag_name, name: 'Help_contract') }
|
||||
let!(:page) do
|
||||
seed_wiki_page(tag_name:, body: 'before', created_by_user: user)
|
||||
end
|
||||
let!(:first_revision) { page.current_revision }
|
||||
let!(:last_revision) do
|
||||
Wiki::Commit.legacy_content!(page:, body: 'after', created_user: user,
|
||||
message: 'legacy edit', base_revision_id: first_revision.id)
|
||||
end
|
||||
|
||||
before { expect(TagDescription.exists?(id: page.id)).to be(false) }
|
||||
|
||||
it 'rejects standalone updates even after a Tag is linked to the legacy page' do
|
||||
create(:tag, primary_tag_name: tag_name)
|
||||
original = page.reload.attributes
|
||||
|
||||
expect do
|
||||
put "/wiki/#{ page.id }",
|
||||
params: { title: 'legacy_tag_renamed', body: 'changed legacy body',
|
||||
message: 'legacy rename', base_revision_id: last_revision.id },
|
||||
headers: { 'X-Transfer-Code' => user.inheritance_code }
|
||||
end.to change(WikiRevision, :count).by(0)
|
||||
.and change(WikiVersion, :count).by(0)
|
||||
.and change(TagDescription, :count).by(0)
|
||||
.and change(TagDescriptionVersion, :count).by(0)
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(page.reload.attributes).to eq(original)
|
||||
expect(page.current_revision).to have_attributes(message: 'legacy edit')
|
||||
end
|
||||
|
||||
RSpec.describe 'Removed standalone Wiki resources', type: :request do
|
||||
include TagGroupRecords
|
||||
let(:user) { create_member_user! }
|
||||
|
||||
[:id, :title].each do |lookup|
|
||||
[nil, :historical].each do |edition|
|
||||
it "rejects legacy #{ lookup } #{ edition || 'current' } without legacy SELECTs" do
|
||||
path = lookup == :id ? "/wiki/#{ page.id }" : "/wiki/title/#{ tag_name.name }"
|
||||
params = edition ? { version: first_revision.id } : { }
|
||||
without_group_legacy_reads { get path, params: }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
it "does not recognise legacy #{ lookup } existence" do
|
||||
path = lookup == :id ? "/wiki/#{ page.id }" : "/wiki/title/#{ tag_name.name }"
|
||||
without_group_legacy_reads { get "#{ path }/exists" }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
['/wiki', '/wiki/search', '/wiki/changes'].each do |path|
|
||||
it "excludes unmigrated content from #{ path } without legacy SELECTs" do
|
||||
['Help:old', 'ヘルプ:旧ページ'].each do |title|
|
||||
it "does not resolve #{ title } as a Wiki resource" do
|
||||
path = "/wiki/title/#{ CGI.escapeURIComponent(title) }"
|
||||
without_group_legacy_reads { get path }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns empty scoped history without legacy SELECTs' do
|
||||
without_group_legacy_reads { get '/wiki/changes', params: { id: page.id } }
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to eq([])
|
||||
end
|
||||
|
||||
[false, true].each do |explicit|
|
||||
it "rejects legacy diff with #{ explicit ? 'explicit' : 'current' } target" do
|
||||
params = { from: first_revision.id }
|
||||
params[:to] = last_revision.id if explicit
|
||||
without_group_legacy_reads { get "/wiki/#{ page.id }/diff", params: }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
get "#{ path }/exists"
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it "rejects standalone creation of #{ title } without writes" do
|
||||
user
|
||||
before_state = group_state
|
||||
post '/wiki', params: { title:, body: 'body' },
|
||||
headers: { 'X-Transfer-Code' => user.inheritance_code }
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(group_state).to eq(before_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする