require 'rails_helper' RSpec.describe PostCreatePlan do def create_tag! name, category create(:tag, name:, category:) end before do create_tag!('タグ希望', :meta) create_tag!('ニジラー情報不詳', :meta) end context 'with an explicit locale' do include_context 'English locale' it 'resolves the matching language identity and its parents' do english_name = create(:tag_name, name: 'shared_name', language_code: 'en', script_code: 'Latn') english = create(:tag, tag_name: english_name, category: :character) japanese = create(:tag, name: 'temporary_japanese', category: :general) # The DB permits this identity; validation has its own contract spec. japanese.tag_name.update_columns(name: 'shared_name') english_parent = create(:tag, name: 'english_parent', category: :material) japanese_parent = create(:tag, name: 'japanese_parent', category: :general) TagImplication.create!(tag: english, parent_tag: english_parent) TagImplication.create!(tag: japanese, parent_tag: japanese_parent) plan = described_class.new(attributes: { tags: 'shared_name' }).build!(locale) expect(plan[:direct_tag_specs]).to eq( [{ name: 'shared_name', category: :character }]) expect(plan[:post_tag_specs]).to include( { name: english_parent.name, category: :material }) expect(plan[:post_tag_specs].pluck(:name)).not_to include(japanese_parent.name) end it 'resolves aliases within the requested language' do japanese = create(:tag, name: 'japanese_canonical') english_name = create(:tag_name, name: 'english_canonical', language_code: 'en', script_code: 'Latn') english = create(:tag, tag_name: english_name, category: :character) japanese_alias = create(:tag_name, :alias, name: 'shared_alias', canonical: japanese.tag_name) english_alias = create(:tag_name, :alias, name: 'temporary_alias', canonical: english_name, language_code: 'en', script_code: 'Latn') english_alias.update_columns(name: 'shared_alias') expect(japanese_alias.reload).to have_attributes( canonical_id: japanese.tag_name_id, tag_id: japanese.id, primary_flg: false, language_code: 'ja') expect(english_alias.reload).to have_attributes( canonical_id: english_name.id, tag_id: english.id, primary_flg: false, language_code: 'en') plan = described_class.new(attributes: { tags: 'shared_alias' }).build!(locale) expect(plan[:direct_tag_specs]).to eq( [{ name: english.name, category: :character }]) end end it 'plans direct and existing default tags without persisting records' do counts = [TagName.count, Tag.count] plan = described_class.new( attributes: { url: 'https://example.com/post', title: 'title', tags: 'character:new_character', parent_post_ids: '' }).build!(Locale.nipponese) expect(plan[:tags]).to eq('new_character') expect(plan[:direct_tag_specs]).to eq( [{ name: 'new_character', category: :character }]) expect(plan[:default_tag_specs]).to include( { name: 'タグ希望', category: :meta }, { name: 'ニジラー情報不詳', category: :meta }) expect([TagName.count, Tag.count]).to eq(counts) end it 'resolves aliases and keeps tag sections separate from canonical names' do canonical = create_tag!('虹夏', :character) create(:tag_name, :alias, name: 'にじか', canonical: canonical.tag_name) create_tag!('動画', :meta) plan = described_class.new( attributes: { url: 'https://example.com/video', title: 'video', tags: '動画 にじか[0:10-0:20]', duration: '1:00', parent_post_ids: '' }).build!(Locale.nipponese) expect(plan[:tags].split).to include('動画', '虹夏[0:10-0:20]') expect(plan[:display_tags]).to include( { name: '虹夏', category: 'character', section_literals: ['[0:10-0:20]'] }) expect(plan[:video_ms]).to eq(60_000) end it 'rejects direct external tag input without creating internal records' do create(:external_tag, name: 'reserved') counts = [Tag.count, TagName.count, ExternalTag.count] expect { described_class.new(attributes: { tags: 'NiCo:reserved' }).build!(Locale.nipponese) }.to raise_error(ActiveRecord::RecordInvalid) { |error| expect(error.record.errors[:tags]).to be_present } expect([Tag.count, TagName.count, ExternalTag.count]).to eq(counts) end it 'validates a new tag name without persisting it' do long_name = 'a' * 256 counts = [TagName.count, Tag.count] expect { described_class.new( attributes: { url: 'https://example.com/post', title: 'title', tags: long_name, parent_post_ids: '' }).build!(Locale.nipponese) }.to raise_error(ActiveRecord::RecordInvalid) { |error| expect(error.record.errors[:tags]).not_to be_empty } expect([TagName.count, Tag.count]).to eq(counts) end it 'ignores duration when the planned tags do not include video' do plan = described_class.new( attributes: { url: 'https://example.com/post', title: 'title', tags: 'ordinary_tag', duration: 'invalid', parent_post_ids: '' }).build!(Locale.nipponese) expect(plan[:duration]).to eq('invalid') expect(plan[:video_ms]).to be_nil end end