このコミットが含まれているのは:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする