このコミットが含まれているのは:
2026-10-16 00:15:21 +09:00
コミット f362b915a5
16個のファイルの変更、458行の追加、32行の削除
+23
ファイルの表示
@@ -0,0 +1,23 @@
require 'rails_helper'
RSpec.describe 'Schema after Wiki removal' do
it 'has no legacy Wiki tables or name indirection columns' do
connection = ApplicationRecord.connection
['wiki_pages', 'wiki_revisions', 'wiki_lines', 'wiki_revision_lines', 'wiki_versions']
.each do |table|
expect(connection.table_exists?(table)).to be(false)
end
expect(connection.column_exists?(:tags, :tag_name_id)).to be(false)
expect(connection.column_exists?(:tag_names, :canonical_id)).to be(false)
end
it 'requires TagName ownership while keeping the native resources and history' do
connection = ApplicationRecord.connection
expect(TagName.columns_hash.fetch('tag_id').null).to be(false)
expect(connection.foreign_key_exists?(:tag_names, :tags)).to be(true)
['tag_descriptions', 'tag_description_versions', 'tag_groups', 'tag_group_versions']
.each do |table|
expect(connection.table_exists?(table)).to be(true)
end
end
end
+6 -10
ファイルの表示
@@ -4,7 +4,8 @@ RSpec.describe TagGroup, type: :model do
let(:actor) { create_member_user! } let(:actor) { create_member_user! }
def create_group name:, members: [], placements: [] def create_group name:, members: [], placements: []
TagGroupOperation.create!(name:, description: nil, members:, placements:, actor:) TagGroupOperation.create!(
Locale.nipponese, name:, description: nil, members:, placements:, actor:)
end end
it 'retains version IDs and snapshots after destroying an empty group' do it 'retains version IDs and snapshots after destroying an empty group' do
@@ -16,7 +17,8 @@ RSpec.describe TagGroup, type: :model do
expect(TagGroupVersion.where(tag_group_id: group.id).order(:version_no).map(&:attributes)) expect(TagGroupVersion.where(tag_group_id: group.id).order(:version_no).map(&:attributes))
.to eq(versions) .to eq(versions)
expect(TagGroupRepr.historical(TagGroupVersion.find(versions.first.fetch('id')))) expect(TagGroupRepr.historical(
Locale.nipponese, TagGroupVersion.find(versions.first.fetch('id'))))
.to include('id' => group.id, 'name' => 'history owner', 'members' => []) .to include('id' => group.id, 'name' => 'history owner', 'members' => [])
end end
@@ -26,14 +28,8 @@ RSpec.describe TagGroup, type: :model do
parent = create_group( parent = create_group(
name: 'parent group', name: 'parent group',
members: [{ 'tag_group_id' => child.id }], members: [{ 'tag_group_id' => child.id }],
placements: [{ placements: [{ 'section_name' => 'section', 'description' => nil,
'section_name' => 'section', 'children' => [{ 'tag_group_id' => child.id, 'children' => [] }] }])
'description' => nil,
'children' => [{
'tag_group_id' => child.id,
'children' => []
}]
}])
versions = parent.versions.order(:version_no).map(&:attributes) versions = parent.versions.order(:version_no).map(&:attributes)
+60
ファイルの表示
@@ -34,6 +34,66 @@ RSpec.describe TagNameSanitisationRule, type: :model do
described_class.create!(priority: 10, source_pattern: 'foo_', replacement: 'foo') described_class.create!(priority: 10, source_pattern: 'foo_', replacement: 'foo')
end end
[:primary, :alias].each do |source_kind|
it "keeps the primary identity when a #{ source_kind } collides within its Tag" do
tag = create(:tag, primary_name: source_kind == :primary ? 'temporary' : 'foobar')
primary = tag.tag_name('ja')
alias_name = create(:tag_name, :alias, tag:,
name: source_kind == :primary ? 'foobar' : 'temporary_alias')
source = source_kind == :primary ? primary : alias_name
source.update_columns(name: 'foo_bar')
expect { described_class.apply! }.to change(TagName, :count).by(-1)
.and change(Tag, :count).by(0)
expect(primary.reload).to have_attributes(
name: 'foobar', primary_flg: true, tag_id: tag.id)
expect(TagName.exists?(alias_name.id)).to be(false)
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true))
.to contain_exactly(primary)
expect(tag.tag_versions.order(:version_no).last.names.fetch('ja'))
.to include('name' => 'foobar', 'aliases' => [])
end
end
it 'deduplicates colliding aliases without changing the primary name' do
tag = create(:tag, primary_name: 'primary')
primary = tag.tag_name('ja')
kept = create(:tag_name, :alias, tag:, name: 'foobar')
removed = create(:tag_name, :alias, tag:, name: 'temporary_alias')
removed.update_columns(name: 'foo_bar')
described_class.apply!
expect(primary.reload).to have_attributes(name: 'primary', primary_flg: true)
expect(kept.reload.tag_id).to eq(tag.id)
expect(TagName.exists?(removed.id)).to be(false)
expect(tag.tag_versions.order(:version_no).last.names.fetch('ja').fetch('aliases'))
.to eq([{ 'name' => 'foobar', 'script_code' => 'Jpan' }])
end
it 'rolls back a collision merge when the source owns a description' do
user = create_member_user!
target = create(:tag, primary_name: 'foobar')
source = create(:tag, primary_name: 'temporary')
TagDescriptionOperation.create!(
Locale.nipponese, tag: source, body: 'retained', created_by_user: user)
source.tag_name('ja').update_columns(name: 'foo_bar')
post = create(:post)
PostTag.create!(post:, tag: source)
models = [Tag, TagName, TagVersion, TagDescription, TagDescriptionVersion, PostTag]
snapshots = models.map { |model| model.order(*Array(model.primary_key)).map(&:attributes) }
expect { described_class.apply! }.to raise_error(ActiveRecord::RecordInvalid)
after_state = models.map do |model|
model.order(*Array(model.primary_key)).map(&:attributes)
end
expect(after_state).to eq(snapshots)
expect(post.reload.tags).to contain_exactly(source)
expect(target.reload.name('ja')).to eq('foobar')
end
context 'when only another language has the sanitised name' do context 'when only another language has the sanitised name' do
include_context 'English locale' include_context 'English locale'
+25
ファイルの表示
@@ -0,0 +1,25 @@
require 'rails_helper'
RSpec.describe User, type: :model do
it 'nullifies description authorship without deleting native content or history' do
author = create_member_user!
editor = create_member_user!
tag = create(:tag)
description = TagDescriptionOperation.create!(
Locale.nipponese, tag:, body: 'initial', created_by_user: author)
TagDescriptionOperation.update!(
Locale.nipponese, tag:, body: 'edited', updated_by_user: editor)
versions = TagDescriptionVersion.where(tag_id: tag.id).order(:version_no)
snapshots = versions.map(&:attributes)
expect(author.created_tag_descriptions).to contain_exactly(description)
expect(editor.updated_tag_descriptions).to contain_exactly(description)
expect { author.destroy! }.to change(TagDescription, :count).by(0)
.and change(TagDescriptionVersion, :count).by(0)
expect(description.reload).to have_attributes(
body: 'edited', created_by_user_id: nil, updated_by_user_id: editor.id)
expect { editor.destroy! }.not_to change(TagDescription, :count)
expect(description.reload.updated_by_user_id).to be_nil
expect(versions.reload.map(&:attributes)).to eq(snapshots)
end
end
+2 -1
ファイルの表示
@@ -26,7 +26,8 @@ RSpec.describe 'Tag description Wiki facade writes', type: :request do
context 'with an existing description' do context 'with an existing description' do
let!(:description) do let!(:description) do
TagDescriptionOperation.create!(tag:, body: 'before', created_by_user: user) TagDescriptionOperation.create!(
Locale.nipponese, tag:, body: 'before', created_by_user: user)
end end
let!(:first_version) { current_description_version(description) } let!(:first_version) { current_description_version(description) }
+122
ファイルの表示
@@ -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
+9
ファイルの表示
@@ -115,6 +115,15 @@ RSpec.describe 'Tag group Markdown round trips through the Wiki facade', type: :
MARKDOWN MARKDOWN
end 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 it 'rejects mixed top-level heading Sections and Tags without losing structure' do
id = seed_group( id = seed_group(
name: '混在構造', members: [tag_member(series)], name: '混在構造', members: [tag_member(series)],
+7 -3
ファイルの表示
@@ -10,8 +10,9 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
let!(:page) { create_markdown_group('読取り契約', '- 旧タグ') } let!(:page) { create_markdown_group('読取り契約', '- 旧タグ') }
let!(:first_revision) { page.versions.sole } let!(:first_revision) { page.versions.sole }
let!(:last_revision) do let!(:last_revision) do
parsed = TagGroupMarkdownParser.parse(body: '- 新タグ') parsed = TagGroupMarkdownParser.parse(Locale.nipponese, body: '- 新タグ')
TagGroupOperation.update!( TagGroupOperation.update!(
Locale.nipponese,
group: page, name: page.name, actor: user, group: page, name: page.name, actor: user,
**parsed.slice(:description, :members, :placements)) **parsed.slice(:description, :members, :placements))
page.versions.order(:version_no).last page.versions.order(:version_no).last
@@ -67,7 +68,8 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
end end
it 'combines Tag Groups and Tag Descriptions without Help in the unfiltered list' do it 'combines Tag Groups and Tag Descriptions without Help in the unfiltered list' do
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' get '/wiki'
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json.map { _1.fetch('title') }).to include( expect(json.map { _1.fetch('title') }).to include(
@@ -87,6 +89,7 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
it 'does not restore a stale namespaced Wiki page after a group rename' do it 'does not restore a stale namespaced Wiki page after a group rename' do
group = create_markdown_group('旧名称', '正本') group = create_markdown_group('旧名称', '正本')
TagGroupOperation.update!( TagGroupOperation.update!(
Locale.nipponese,
group:, name: '新名称', description: '正本', members: [], placements: [], actor: user) group:, name: '新名称', description: '正本', members: [], placements: [], actor: user)
get group_title_path('旧名称') get group_title_path('旧名称')
@@ -99,7 +102,8 @@ RSpec.describe 'Tag group Wiki facade reads', type: :request do
end end
it 'orders mixed resource history by timestamp rather than independent IDs' do it 'orders mixed resource history by timestamp rather than independent IDs' do
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) TagGroupVersion.update_all(created_at: 3.days.ago)
TagDescriptionVersion.update_all(created_at: 1.day.ago) TagDescriptionVersion.update_all(created_at: 1.day.ago)
+1
ファイルの表示
@@ -248,6 +248,7 @@ RSpec.describe 'TagVersions API', type: :request do
message: 'init') message: 'init')
TagDescriptionOperation.update!( TagDescriptionOperation.update!(
Locale.nipponese,
tag: wiki_page.tag, tag: wiki_page.tag,
body: 'after', body: 'after',
updated_by_user: member, updated_by_user: member,
+2 -5
ファイルの表示
@@ -1402,11 +1402,8 @@ RSpec.describe 'Tags API', type: :request do
message: 'init') message: 'init')
TagDescriptionOperation.update!( TagDescriptionOperation.update!(
tag: wiki_page.tag, Locale.nipponese, tag: wiki_page.tag, body: 'wiki body before',
body: 'wiki body before', updated_by_user: member_user, message: 'init')
updated_by_user: member_user,
message: 'init'
)
wiki_history = description_versions(wiki_page).map(&:attributes) wiki_history = description_versions(wiki_page).map(&:attributes)
+1
ファイルの表示
@@ -19,6 +19,7 @@ RSpec.describe 'Wiki conflict handling', type: :request do
stale_id = current_description_version(page).id stale_id = current_description_version(page).id
TagDescriptionOperation.update!( TagDescriptionOperation.update!(
Locale.nipponese,
tag: page.tag, tag: page.tag,
body: 'second', body: 'second',
updated_by_user: user, updated_by_user: user,
+158
ファイルの表示
@@ -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
+12 -6
ファイルの表示
@@ -232,7 +232,9 @@ RSpec.describe 'Wiki API', type: :request do
context 'when conflict' do context 'when conflict' do
it 'returns 409 when base_revision_id mismatches' do it 'returns 409 when base_revision_id mismatches' do
# 先に別ユーザ(同じ member でもOK)が 1 回更新して先頭を進める # 先に別ユーザ(同じ member でもOK)が 1 回更新して先頭を進める
TagDescriptionOperation.update!(tag: page.tag, body: "zzz", updated_by_user: member, message: 'other edit') TagDescriptionOperation.update!(
Locale.nipponese, tag: page.tag, body: 'zzz',
updated_by_user: member, message: 'other edit')
page.reload page.reload
stale_id = description_versions(page).order(:id).first.id # わざと古い id stale_id = description_versions(page).order(:id).first.id # わざと古い id
@@ -314,12 +316,14 @@ RSpec.describe 'Wiki API', type: :request do
describe 'GET /wiki/changes' do describe 'GET /wiki/changes' do
let!(:rev1) do let!(:rev1) do
TagDescriptionOperation.update!(tag: page.tag, body: "a\nb", updated_by_user: user, message: 'r1') TagDescriptionOperation.update!(
Locale.nipponese, tag: page.tag, body: "a\nb", updated_by_user: user, message: 'r1')
current_description_version(page) current_description_version(page)
end end
let!(:rev2) do let!(:rev2) do
TagDescriptionOperation.update!(tag: page.tag, body: "a\nc", updated_by_user: user, message: 'r2') TagDescriptionOperation.update!(
Locale.nipponese, tag: page.tag, body: "a\nc", updated_by_user: user, message: 'r2')
current_description_version(page) current_description_version(page)
end end
@@ -354,7 +358,7 @@ RSpec.describe 'Wiki API', type: :request do
it 'returns empty array when page has no revisions and filtered by id' do it 'returns empty array when page has no revisions and filtered by id' do
# 別ページを作って revision 無し # 別ページを作って revision 無し
tn2 = create(:tag_name, name: 'spec_no_rev') tn2 = create(:tag_name, name: 'spec_no_rev')
# 異常データ: revision 無し WikiPage を直接作る # 異常データ: Version のない TagDescription を直接作る
p2 = TagDescription.create!( p2 = TagDescription.create!(
tag: tn2.tag, language_code: 'ja', body: 'init', version_no: 1) tag: tn2.tag, language_code: 'ja', body: 'init', version_no: 1)
@@ -392,12 +396,14 @@ RSpec.describe 'Wiki API', type: :request do
describe 'GET /wiki/:id/diff' do describe 'GET /wiki/:id/diff' do
let!(:rev_a) do let!(:rev_a) do
TagDescriptionOperation.update!(tag: page.tag, body: "a\nb\nc", updated_by_user: user, message: 'A') TagDescriptionOperation.update!(
Locale.nipponese, tag: page.tag, body: "a\nb\nc", updated_by_user: user, message: 'A')
current_description_version(page) current_description_version(page)
end end
let!(:rev_b) do let!(:rev_b) do
TagDescriptionOperation.update!(tag: page.tag, body: "a\nx\nc", updated_by_user: user, message: 'B') TagDescriptionOperation.update!(
Locale.nipponese, tag: page.tag, body: "a\nx\nc", updated_by_user: user, message: 'B')
current_description_version(page) current_description_version(page)
end end
+22
ファイルの表示
@@ -0,0 +1,22 @@
RSpec.shared_context 'Ainu locale contract' do
let(:ainu_locale) { instance_double(Locale, language_code: 'ain', script_code: 'Jpan') }
before do
# Only the foreign-key reference is seeded; no Locale or enum is enabled.
Language.connection.execute(<<~SQL)
INSERT IGNORE INTO languages (code, name, created_at)
VALUES ('ain', 'Ainu', CURRENT_TIMESTAMP)
SQL
allow(Locale).to receive(:find_by).and_call_original
allow(Locale).to receive(:find_by).with(code: 'ain').and_return(ainu_locale)
end
def add_ainu_name tag, name
TagName.insert_all!([{
tag_id: tag.id, language_code: 'ain', script_code: 'Jpan', name:,
primary_flg: true, auto_generated: false,
created_at: Time.current, updated_at: Time.current }])
tag.tag_names.reset
TagName.find_by!(tag_id: tag.id, language_code: 'ain', name:)
end
end
+6 -5
ファイルの表示
@@ -94,13 +94,14 @@ module TagGroupRecords
tag.reload tag.reload
end end
def create_markdown_group name, body def create_markdown_group name, body, locale: Locale.nipponese
parsed = TagGroupMarkdownParser.parse(body:) parsed = TagGroupMarkdownParser.parse(locale, body:)
TagGroupOperation.create!(name:, actor: user, **parsed.slice(:description, :members, :placements)) TagGroupOperation.create!(
locale, name:, actor: user, **parsed.slice(:description, :members, :placements))
end end
def group_markdown group def group_markdown group, locale: Locale.nipponese
TagGroupMarkdownRenderer.render(TagGroupRepr.one(group.reload)) TagGroupMarkdownRenderer.render(TagGroupRepr.one(locale, group.reload))
end end
def seed_group name:, placements: [], members: [], description: nil, id: nil def seed_group name:, placements: [], members: [], description: nil, id: nil
+2 -2
ファイルの表示
@@ -1,6 +1,6 @@
module WikiRecords module WikiRecords
def seed_description(tag_name:, body:, created_by_user:, message: nil) def seed_description(tag_name:, body:, created_by_user:, message: nil, locale: Locale.nipponese)
TagDescriptionOperation.create!(tag: tag_name.tag, body:, created_by_user:, message:) TagDescriptionOperation.create!(locale, tag: tag_name.tag, body:, created_by_user:, message:)
end end
def description_versions(description) def description_versions(description)