このコミットが含まれているのは:
2026-10-15 20:20:00 +09:00
コミット 1e464107a8
36個のファイルの変更、566行の追加、2082行の削除
+23 -70
ファイルの表示
@@ -1,84 +1,37 @@
require 'rails_helper'
RSpec.describe 'Shared Tag and ExternalTag IDs', type: :model do
include TagGroupRecords
let(:user) { create_member_user! }
it 'allocates distinct increasing IDs across the two tables' do
tag = create(:tag)
external = create(:external_tag)
next_tag = create(:tag)
def create_resource resource
if resource == :tag
Tag.find_or_create_by_tag_name!(Locale.nipponese, 'shared_id_new', category: :general)
else
ExternalTag.create!(platform: :nico, name: 'shared_id_new')
end
expect(external.id).to be > tag.id
expect(next_tag.id).to be > external.id
end
[:tag, :external_tag].each do |resource|
it "starts #{ resource } at 1 when both tables are empty" do
expect(Tag.count).to eq(0)
expect(ExternalTag.count).to eq(0)
expect(create_resource(resource).id).to eq(1)
end
it 'does not reuse physically deleted IDs in either table' do
tag = create(:tag)
external = create(:external_tag)
TagName.where(tag_id: tag.id).delete_all
tag.destroy!
external.destroy!
it "allocates #{ resource } above the other table's maximum" do
create(:tag, id: resource == :tag ? 1 : 20)
create(:external_tag, id: resource == :external_tag ? 1 : 20)
expect(create_resource(resource).id).to eq(21)
end
it "uses the current maximum after deleting earlier #{ resource } records" do
tag = create(:tag, id: 100)
TagName.where(tag_id: tag.id).update_all(tag_id: nil)
tag.destroy!
create(:external_tag, id: 200).destroy!
expect(Tag.unscoped.count).to eq(0)
expect(ExternalTag.unscoped.count).to eq(0)
expect(create_resource(resource).id).to eq(1)
end
it "preserves legacy overlaps and gaps when creating #{ resource }" do
[1, 2, 5].each { create(:tag, id: _1) }
[1, 3, 8].each { create(:external_tag, id: _1) }
tags = Tag.order(:id).map(&:attributes)
external_tags = ExternalTag.order(:id).map(&:attributes)
record = create_resource(resource)
aggregate_failures do
expect(record.id).to eq(9)
expect(Tag.where(id: [1, 2, 5]).order(:id).map(&:attributes)).to eq(tags)
expect(ExternalTag.where(id: [1, 3, 8]).order(:id).map(&:attributes)).to eq(external_tags)
expect(Tag.exists?(id: 3)).to be(false)
expect(ExternalTag.exists?(id: 2)).to be(false)
end
end
it "ignores the description and group ID space when creating #{ resource }" do
tag = create(:tag, id: 1)
create(:external_tag, id: 2)
TagDescription.create!(id: 100, tag:, language_code: 'ja', body: '', version_no: 1)
seed_group(name: 'separate_space', id: 200)
expect(create_resource(resource).id).to eq(3)
end
expect(create(:tag).id).to be > external.id
expect(create(:external_tag).id).to be > external.id
end
it 'preserves overlapping IDs during updates without rewriting the other resource' do
tag = create(:tag, id: 5)
external = create(:external_tag, id: 5)
it 'retains existing overlapping IDs without renumbering on update or insert' do
tag = create(:tag)
external = create(:external_tag)
external.update_columns(id: tag.id)
tag.update!(category: :meta)
external.update!(name: 'updated_external')
before_tag = tag.reload.attributes
before_external = external.reload.attributes
expect(tag.reload.id).to eq(5)
expect(external.reload.id).to eq(5)
expect(tag.category).to eq('meta')
expect(external.name).to eq('updated_external')
expect(Tag.count).to eq(1)
expect(ExternalTag.count).to eq(1)
end
it 'allocates distinct IDs for successive creations across both tables' do
records = [:tag, :external_tag].map { create_resource(_1) }
expect(records.map(&:id)).to eq([1, 2])
expect(create(:tag).id).to be > tag.id
expect(tag.reload.attributes).to eq(before_tag)
expect(external.reload.attributes).to eq(before_external)
end
end
+23
ファイルの表示
@@ -0,0 +1,23 @@
require 'rails_helper'
RSpec.describe TagGroup, type: :model do
let(:actor) { create_member_user! }
def create_group name:, members: [], placements: []
TagGroupOperation.create!(name:, description: nil, members:, placements:, actor:)
end
it 'retains version IDs and snapshots after destroying an empty group' do
group = create_group(name: 'history owner')
versions = group.versions.order(:version_no).map(&:attributes)
expect { group.destroy! }.to change(described_class, :count).by(-1)
.and change(TagGroupVersion, :count).by(0)
expect(TagGroupVersion.where(tag_group_id: group.id).order(:version_no).map(&:attributes))
.to eq(versions)
expect(TagGroupRepr.historical(TagGroupVersion.find(versions.first.fetch('id'))))
.to include('id' => group.id, 'name' => 'history owner', 'members' => [])
end
end
-19
ファイルの表示
@@ -98,25 +98,6 @@ RSpec.describe TagNameSanitisationRule, type: :model do
end
end
context 'when the source tag_name has a tag and the existing one has no tag' do
let!(:existing) { create(:tag_name, name: 'foobar') }
let!(:source_tag) { create(:tag, primary_name: 'tmp', category: :general) }
let!(:source_tag_name_id) { primary_tag_name_for(source_tag, 'ja').id }
before do
primary_tag_name_for(source_tag, 'ja').update_columns(
name: 'foo_bar', updated_at: Time.current)
end
it 'moves the tag to the existing tag_name' do
described_class.apply!
expect(existing.reload).to have_attributes(
tag_id: source_tag.id, language_code: 'ja', primary_flg: true)
expect(primary_tag_name_for(source_tag, 'ja')).to eq(existing)
expect(primary_tag_name_for(source_tag.reload, 'ja')).to eq(existing)
expect(TagName.unscoped.exists?(source_tag_name_id)).to be(false)
end
end
context 'when the sanitised name is an alias of an existing tag' do
let!(:existing_tag) { create(:tag) }
+18 -16
ファイルの表示
@@ -22,16 +22,23 @@ RSpec.describe TagName, type: :model do
expect(duplicate.errors.of_kind?(:name, :taken)).to be(true)
end
it 'creates Nipponese factory names without requiring a tag' do
it 'creates Nipponese factory names with an owning tag' do
name = create(:tag_name)
expect(name.reload).to have_attributes(
language_code: 'ja', script_code: 'Jpan', primary_flg: true,
auto_generated: false, tag_id: nil)
auto_generated: false, tag_id: name.tag.id)
end
it 'rejects an ownerless name at model and database boundaries' do
name = build(:tag_name, tag: nil)
expect(name).to be_invalid
expect { name.save!(validate: false) }.to raise_error(ActiveRecord::NotNullViolation)
end
it 'defaults ordinary persisted human names to confirmed provenance' do
name = described_class.create!(name: 'ordinary_human_name', language_code: 'ja',
name = described_class.create!(tag: create(:tag, primary_tag_name: nil),
name: 'ordinary_human_name', language_code: 'ja',
script_code: 'Jpan', primary_flg: true)
expect(name.reload.auto_generated).to be(false)
@@ -46,7 +53,7 @@ RSpec.describe TagName, type: :model do
expect(name.tag).to eq(tag)
expect(described_class.find_by!(tag_id: tag.id, language_code: 'ja', primary_flg: true))
.to eq(name)
expect(tag.reload.tag_name_id).to eq(name.id)
expect(tag.reload.tag_name('ja')&.id).to eq(name.id)
end
it 'creates a complete ordinary tag factory under the active Nipponese language' do
@@ -96,19 +103,14 @@ RSpec.describe TagName, type: :model do
end
describe '.canonicalise' do
it 'leaves detached aliases unchanged' do
create(:tag_name, :alias, name: 'detached_alias')
expect(described_class.canonicalise(Locale.nipponese, ['detached_alias']))
.to eq(['detached_alias'])
end
it 'does not resolve a detached alias through another detached primary' do
create(:tag_name, :alias, name: 'detached_alias')
create(:tag_name, name: 'unrelated_detached_primary')
it 'resolves an alias to its owning primary name' do
tag = create(:tag, primary_name: 'canonical')
create(:tag_name, :alias, tag:, name: 'alias')
expect(described_class.canonicalise(Locale.nipponese, ['detached_alias']))
.to eq(['detached_alias'])
expect(described_class.canonicalise(Locale.nipponese, ['alias', 'unknown']))
.to eq(['canonical', 'unknown'])
end
it 'resolves the primary name of the alias language on the same tag' do
@@ -119,7 +121,7 @@ RSpec.describe TagName, type: :model do
english_primary.update!(name: 'english_primary', auto_generated: false)
english_alias = create(:tag_name, :alias, tag:, name: 'english_alias',
language_code: 'en', script_code: 'Latn')
representative_id = tag.tag_name_id
representative_id = tag.tag_name('ja')&.id
primary = described_class.find_by!(tag_id: english_alias.tag_id,
language_code: english_alias.language_code,
@@ -128,7 +130,7 @@ RSpec.describe TagName, type: :model do
expect(primary).to eq(english_primary)
expect(described_class.canonicalise(locale, [english_alias.name]))
.to eq([english_primary.name])
expect(tag.reload.tag_name_id).to eq(representative_id)
expect(tag.reload.tag_name('ja')&.id).to eq(representative_id)
end
it 'resolves only aliases in the requested language' do
+6 -54
ファイルの表示
@@ -1,6 +1,7 @@
require 'rails_helper'
RSpec.describe Tag, type: :model do
include WikiRecords
describe '#has_description?' do
let(:tag) { create(:tag) }
@@ -14,12 +15,6 @@ RSpec.describe Tag, type: :model do
expect(tag.has_description?(language_code: 'ja')).to be(false)
end
it 'does not use a Wiki page as evidence of a description' do
user = create_member_user!
Wiki::Commit.create_legacy_content!(tag_name: tag.tag_name('ja'), body: 'legacy',
created_by_user: user)
expect(tag.has_description?(language_code: 'ja')).to be(false)
end
it 'reuses preloaded descriptions without reading Wiki or querying again' do
TagDescription.create!(tag:, language_code: 'ja', body: '', version_no: 1)
@@ -334,38 +329,7 @@ RSpec.describe Tag, type: :model do
.where(tag_names: { name: 'atomic_english_name' })).to be_empty
end
it 'keeps a detached requested name detached if generation fails' do
pending '日本語以外の Locale 有効化は別課題で実装する'
locale
detached = create(:tag_name, :alias, name: 'atomic_detached',
language_code: 'en', script_code: 'Latn')
original_tag_count = described_class.count
allow(TagName).to receive(:generate_name).and_raise('generation failed')
expect {
described_class.find_or_create_by_tag_name!(
locale, detached.name, category: :general)
}.to raise_error('generation failed')
expect(described_class.count).to eq(original_tag_count)
expect(detached.reload).to have_attributes(tag_id: nil, primary_flg: false)
end
it 'promotes a detached alias into the requested primary name' do
pending '日本語以外の Locale 有効化は別課題で実装する'
locale
detached = create(:tag_name, :alias, name: 'detached_english',
language_code: 'en', script_code: 'Latn')
tag = described_class.find_or_create_by_tag_name!(
locale, detached.name, category: :general)
expect(detached.reload).to have_attributes(
tag_id: tag.id, primary_flg: true, canonical_id: nil,
auto_generated: false)
expect(TagName.where(tag:, language_code: 'en', primary_flg: true))
.to contain_exactly(detached)
end
it 'creates separate tag identities for the same name in different languages' do
pending '日本語以外の Locale 有効化は別課題で実装する'
@@ -390,7 +354,7 @@ RSpec.describe Tag, type: :model do
pending '日本語以外の Locale 有効化は別課題で実装する'
locale
tag = create(:tag, primary_name: '日本語代表名')
representative_id = tag.tag_name_id
representative_id = tag.tag_name('ja')&.id
english = primary_tag_name_for(tag, 'en')
english.update!(name: 'english_primary', auto_generated: false)
@@ -398,7 +362,7 @@ RSpec.describe Tag, type: :model do
locale, english.name, category: :general)
expect(found).to eq(tag)
expect(tag.reload.tag_name_id).to eq(representative_id)
expect(tag.reload.tag_name('ja')&.id).to eq(representative_id)
expect(english.reload.tag_id).to eq(tag.id)
end
@@ -430,22 +394,10 @@ RSpec.describe Tag, type: :model do
expect(primary_tag_name_for(tag, 'ja').auto_generated).to be(false)
end
it 'promotes a detached Nipponese alias into a human-confirmed primary' do
detached = create(:tag_name, :alias, name: 'detached_nipponese')
tag = described_class.find_or_create_by_tag_name!(
Locale.nipponese, detached.name, category: :general)
expect(detached.reload).to have_attributes(
tag_id: tag.id, primary_flg: true, canonical_id: nil,
auto_generated: false)
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true))
.to contain_exactly(detached)
end
it 'reuses the owning tag for an alias without changing its category' do
tag = create(:tag, category: :character)
representative_id = tag.tag_name_id
representative_id = tag.tag_name('ja')&.id
alias_name = create(:tag_name, :alias, name: 'lookup_alias', tag:)
expect(alias_name.reload).to have_attributes(
tag_id: tag.id,
@@ -459,7 +411,7 @@ RSpec.describe Tag, type: :model do
expect(found).to eq(tag)
expect(found.category).to eq('character')
expect(tag.reload.tag_name_id).to eq(representative_id)
expect(tag.reload.tag_name('ja')&.id).to eq(representative_id)
end
it 'reuses the owning tag through another alias' do
@@ -683,7 +635,7 @@ RSpec.describe Tag, type: :model do
let!(:wiki_page) do
admin = create_admin_user!
Wiki::Commit.create_content!(
seed_description(
tag_name: source_tag_name,
body: 'source wiki body',
created_by_user: admin,