require 'rails_helper' RSpec.describe 'Locale propagation on write paths', type: :request do include_context 'English locale' let(:member) { create(:user, :member) } before do sign_in_as(member) end def expect_localised_tag(name, expected_locale) tag_name = TagName.find_by!(name:, language_code: expected_locale.language_code) expect(tag_name).to have_attributes( script_code: expected_locale.script_code, primary_flg: true) expect(tag_name.tag_id).to be_present tag_name.tag end it 'confirms a generated Nipponese primary when a human renames it' do tag = create(:tag, primary_name: 'generated_source') primary = primary_tag_name_for(tag, 'ja') primary.update!(name: "Tag_##{ tag.id }", auto_generated: true) patch "/tags/#{ tag.id }", params: { locale: 'ja', name: '人間が確定した名称' } expect(response).to have_http_status(:ok), response.body expect(primary.reload).to have_attributes( name: '人間が確定した名称', auto_generated: false) end it 'rolls back Nipponese edits when an alias conflicts with another primary' do tag = create(:tag, primary_name: 'target_before_collision') create(:tag_name, :alias, tag:, name: 'existing_alias') other_tag = create(:tag, primary_name: 'other_primary') other_primary = primary_tag_name_for(other_tag, 'ja') primary_name_before = primary_tag_name_for(tag, 'ja').name aliases_before = tag.tag_names.where(primary_flg: false).order(:name).pluck(:name) category_before = tag.category put "/tags/#{ tag.id }", params: { locale: 'ja', name: 'renamed_before_collision', category: 'character', deprecated: false, aliases: 'other_primary', parent_tags: '' } aggregate_failures do expect(response).to have_http_status(:unprocessable_entity) expect(other_primary.reload).to have_attributes(tag_id: other_tag.id, primary_flg: true) expect(TagName.where(tag: other_tag, language_code: 'ja', primary_flg: true)) .to contain_exactly(other_primary) expect(primary_tag_name_for(tag.reload, 'ja').name).to eq(primary_name_before) expect(tag.category).to eq(category_before) expect(tag.tag_names.where(primary_flg: false).order(:name).pluck(:name)) .to eq(aliases_before) end end ['en', nil].each do |requested_locale| context "with locale #{ requested_locale.inspect }" do let(:locale_params) { requested_locale ? { locale: requested_locale } : { } } let(:expected_locale) { requested_locale ? locale : Locale.nipponese } it 'creates post tags in the requested language or Nipponese fallback' do pending '日本語以外の Locale 有効化は別課題で実装する' if requested_locale locale if requested_locale post '/posts', params: locale_params.merge( title: 'Locale post', url: 'https://example.com/locale-post', tags: 'locale_post_tag', parent_post_ids: '') expect(response).to have_http_status(:created), response.body tag = expect_localised_tag('locale_post_tag', expected_locale) expect(Post.find(json.fetch('id')).tags).to include(tag) end it 'updates post tags in the requested language or Nipponese fallback' do pending '日本語以外の Locale 有効化は別課題で実装する' if requested_locale locale if requested_locale record = create(:post) PostVersionRecorder.record!(post: record, event_type: :create, created_by_user: member) put "/posts/#{ record.id }", params: locale_params.merge( title: record.title, tags: 'locale_updated_tag', parent_post_ids: '', base_version_no: record.reload.version_no) expect(response).to have_http_status(:ok), response.body tag = expect_localised_tag('locale_updated_tag', expected_locale) expect(record.reload.tags).to include(tag) end it 'passes the resolved locale to bulk creation' do pending '日本語以外の Locale 有効化は別課題で実装する' if requested_locale locale if requested_locale # Worker propagation is exercised in post_bulk_creator_spec. expect_any_instance_of(PostBulkCreator).to receive(:run) .with(expected_locale).and_return(results: []) manifest = [{ title: 'Bulk locale', url: 'https://example.com/bulk-locale', tags: 'bulk_locale_tag', parent_post_ids: '' }] post '/posts/bulk', params: locale_params.merge(posts: JSON.generate(manifest)), headers: { 'CONTENT_TYPE' => 'multipart/form-data' } expect(response).to have_http_status(:ok), response.body end it 'creates materials with tags in the resolved locale' do pending '日本語以外の Locale 有効化は別課題で実装する' if requested_locale locale if requested_locale post '/materials', params: locale_params.merge( tag: 'locale_material', url: 'https://example.com/material') expect(response).to have_http_status(:created), response.body tag = expect_localised_tag('locale_material', expected_locale) expect(Material.find(json.fetch('id')).tag).to eq(tag) end it 'updates materials with tags in the resolved locale' do pending '日本語以外の Locale 有効化は別課題で実装する' if requested_locale locale if requested_locale tag = create(:tag, category: :material) material = Material.create!(tag:, url: 'https://example.com/material') put "/materials/#{ material.id }", params: locale_params.merge( tag: 'locale_material_updated', url: material.url) expect(response).to have_http_status(:ok), response.body resolved_tag = expect_localised_tag('locale_material_updated', expected_locale) expect(material.reload.tag).to eq(resolved_tag) end it 'links Nico external tags to internal tags in the resolved locale' do pending '日本語以外の Locale 有効化は別課題で実装する' if requested_locale locale if requested_locale external = create(:external_tag) NicoTagVersionRecorder.record!( external_tag: external, event_type: :create, created_by_user: member) put "/tags/nico/#{ external.id }", params: locale_params.merge(tags: 'locale_link') expect(response).to have_http_status(:ok), response.body tag = expect_localised_tag('locale_link', expected_locale) expect(external.reload.linked_tags).to contain_exactly(tag) end it 'creates parent tags in the resolved locale' do pending '日本語以外の Locale 有効化は別課題で実装する' if requested_locale locale if requested_locale tag = create(:tag) put "/tags/#{ tag.id }", params: locale_params.merge( name: primary_tag_name_for(tag, 'ja').name, category: tag.category, deprecated: false, aliases: '', parent_tags: 'locale_parent') expect(response).to have_http_status(:ok), response.body parent = expect_localised_tag('locale_parent', expected_locale) expect(TagImplication.where(tag:).pluck(:parent_tag_id)).to eq([parent.id]) end end end context 'with existing names in both languages' do let(:english_name) do create(:tag_name, name: 'shared_name', language_code: 'en', script_code: 'Latn') end let(:english_tag) { create(:tag, primary_tag_name: english_name, category: :material) } let(:nipponese_tag) do create(:tag, primary_name: 'temporary_nipponese', category: :general).tap do |tag| # Isolate lookup from the separately tested uniqueness validation. primary_tag_name_for(tag, 'ja').update_columns(name: 'shared_name') end end it 'plans and creates the post using the English identity and category' do pending '日本語以外の Locale 有効化は別課題で実装する' locale english_tag nipponese_tag post '/posts', params: { locale: 'en', title: 'English identity', url: 'https://example.com/english-identity', tags: 'shared_name', parent_post_ids: '' } expect(response).to have_http_status(:created), response.body record = Post.find(json.fetch('id')) expect(record.tags).to include(english_tag) expect(record.tags).not_to include(nipponese_tag) expect(english_tag.reload.category).to eq('material') expect(nipponese_tag.reload.category).to eq('general') end it 'updates the post using the English identity' do pending '日本語以外の Locale 有効化は別課題で実装する' locale english_tag nipponese_tag record = create(:post) PostVersionRecorder.record!(post: record, event_type: :create, created_by_user: member) put "/posts/#{ record.id }", params: { locale: 'en', title: record.title, tags: 'shared_name', parent_post_ids: '', base_version_no: record.reload.version_no } expect(response).to have_http_status(:ok), response.body expect(record.reload.tags).to include(english_tag) expect(record.tags).not_to include(nipponese_tag) end it 'resolves the English material tag' do pending '日本語以外の Locale 有効化は別課題で実装する' locale english_tag nipponese_tag post '/materials', params: { locale: 'en', tag: 'shared_name', url: 'https://example.com/english-material' } expect(response).to have_http_status(:created), response.body expect(Material.find(json.fetch('id')).tag).to eq(english_tag) expect(Material.where(tag: nipponese_tag)).to be_empty end it 'resolves the English parent tag' do pending '日本語以外の Locale 有効化は別課題で実装する' locale english_tag nipponese_tag child = create(:tag) put "/tags/#{ child.id }", params: { locale: 'en', name: primary_tag_name_for(child, 'ja').name, category: child.category, deprecated: false, aliases: '', parent_tags: 'shared_name' } expect(response).to have_http_status(:ok), response.body expect(TagImplication.where(tag: child).pluck(:parent_tag_id)).to eq([english_tag.id]) end it 'resolves the English internal tag for a Nico link' do pending '日本語以外の Locale 有効化は別課題で実装する' locale english_tag nipponese_tag external = create(:external_tag) NicoTagVersionRecorder.record!( external_tag: external, event_type: :create, created_by_user: member) put "/tags/nico/#{ external.id }", params: { locale: 'en', tags: 'shared_name' } expect(response).to have_http_status(:ok), response.body expect(external.reload.linked_tags).to contain_exactly(english_tag) end end context 'when renaming or updating aliases in English' do let(:english_name) do create(:tag_name, name: 'english_original', language_code: 'en', script_code: 'Latn') end let(:tag) { create(:tag, primary_tag_name: english_name) } it 'marks a human-renamed generated primary as no longer auto-generated' do pending '日本語以外の Locale 有効化は別課題で実装する' locale generated_tag = create(:tag, primary_name: 'rename_generated_source') generated_name = primary_tag_name_for(generated_tag, 'en') expect(generated_name.auto_generated).to be(true) patch "/tags/#{ generated_tag.id }", params: { locale: 'en', name: 'human_english_name' } expect(response).to have_http_status(:ok), response.body expect(generated_name.reload).to have_attributes( name: 'human_english_name', auto_generated: false) end it 'does not steal another tag primary for an alias' do pending '日本語以外の Locale 有効化は別課題で実装する' locale other_tag = create(:tag, primary_name: 'other_nipponese') other_primary = primary_tag_name_for(other_tag, 'en') other_primary.update!(name: 'other_english_primary', auto_generated: false) create(:tag_name, :alias, tag:, name: 'existing_english_alias', language_code: 'en', script_code: 'Latn') primary_name_before = primary_tag_name_for(tag, 'en').name aliases_before = tag.tag_names.where(language_code: 'en', primary_flg: false) .order(:name).pluck(:name) category_before = tag.category put "/tags/#{ tag.id }", params: { locale: 'en', name: 'renamed_before_alias_collision', category: 'character', deprecated: false, aliases: 'other_english_primary', parent_tags: '' } aggregate_failures do expect(response).to have_http_status(:unprocessable_entity) expect(other_primary.reload).to have_attributes( tag_id: other_tag.id, language_code: 'en', primary_flg: true) expect(TagName.where(tag: other_tag, language_code: 'en', primary_flg: true)) .to contain_exactly(other_primary) expect(primary_tag_name_for(tag.reload, 'en').name).to eq(primary_name_before) expect(tag.category).to eq(category_before) aliases_after = tag.tag_names.where(language_code: 'en', primary_flg: false) .order(:name).pluck(:name) expect(aliases_after).to eq(aliases_before) end end it 'allows a rename to a name already used in Nipponese' do pending '日本語以外の Locale 有効化は別課題で実装する' locale nipponese = create(:tag, primary_name: 'rename_target') patch "/tags/#{ tag.id }", params: { locale: 'en', name: 'rename_target' } expect(response).to have_http_status(:ok), response.body expect(primary_tag_name_for(tag.reload, 'en')).to have_attributes( name: 'rename_target', language_code: 'en', tag_id: tag.id) expect(primary_tag_name_for(nipponese.reload, 'ja')).to have_attributes( name: 'rename_target', language_code: 'ja', tag_id: nipponese.id) end it 'creates aliases in the requested language' do pending '日本語以外の Locale 有効化は別課題で実装する' locale put "/tags/#{ tag.id }", params: { locale: 'en', name: english_name.name, category: tag.category, deprecated: false, aliases: 'english_alias', parent_tags: '' } expect(response).to have_http_status(:ok), response.body expect(TagName.find_by!(language_code: 'en', name: 'english_alias')) .to have_attributes( script_code: 'Latn', tag_id: tag.id, primary_flg: false, language_code: 'en') primary = TagName.find_by!(tag_id: tag.id, language_code: 'en', primary_flg: true) expect(primary).to have_attributes(name: 'english_original', script_code: 'Latn') end it 'does not take an alias from another language' do pending '日本語以外の Locale 有効化は別課題で実装する' locale nipponese = create(:tag, primary_name: 'nipponese_owner') nipponese_alias = create(:tag_name, :alias, name: 'shared_alias', tag: nipponese) expect(nipponese_alias.reload).to have_attributes( tag_id: nipponese.id, primary_flg: false, language_code: 'ja') put "/tags/#{ tag.id }", params: { locale: 'en', name: english_name.name, category: tag.category, deprecated: false, aliases: 'shared_alias', parent_tags: '' } expect(response).to have_http_status(:ok), response.body expect(nipponese_alias.reload).to have_attributes( tag_id: nipponese.id, primary_flg: false, language_code: 'ja') expect(TagName.find_by!(language_code: 'en', name: 'shared_alias')) .to have_attributes( tag_id: tag.id, script_code: 'Latn', primary_flg: false, language_code: 'en') expect(TagName.where(name: 'shared_alias').pluck(:language_code, :tag_id)) .to contain_exactly(['ja', nipponese.id], ['en', tag.id]) end end end