Reviewed-on: #421 Co-authored-by: miteruzo <miteruzo@naver.com>
このコミットはプルリクエスト #421 でマージされました。
このコミットが含まれているのは:
@@ -11,6 +11,7 @@ RSpec.describe 'discarded tag cleanup migrations' do
|
||||
end
|
||||
|
||||
context 'with legacy records' do
|
||||
# Reproduce the historical schema required by these pre-multilingual migrations.
|
||||
self.use_transactional_tests = false
|
||||
|
||||
before do
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'TagName database invariants' do
|
||||
def insert_name!(**attributes)
|
||||
TagName.insert_all!([{ name: 'constraint_name', language_code: 'ja',
|
||||
script_code: 'Jpan', primary_flg: true,
|
||||
created_at: Time.current, updated_at: Time.current,
|
||||
**attributes }])
|
||||
end
|
||||
|
||||
it 'defaults persisted names to human-confirmed provenance' do
|
||||
insert_name!
|
||||
|
||||
expect(TagName.find_by!(name: 'constraint_name').auto_generated).to be(false)
|
||||
end
|
||||
|
||||
[[true, true], [true, false], [false, false]].each do |primary_flg, auto_generated|
|
||||
it "allows primary=#{ primary_flg }, auto_generated=#{ auto_generated }" do
|
||||
expect { insert_name!(primary_flg:, auto_generated:) }
|
||||
.to change(TagName, :count).by(1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects auto-generated aliases at the database boundary' do
|
||||
expect { insert_name!(primary_flg: false, auto_generated: true) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
|
||||
it 'rejects a second primary for the same tag and language' do
|
||||
tag = create(:tag)
|
||||
|
||||
expect { insert_name!(tag_id: tag.id, auto_generated: false) }
|
||||
.to raise_error(ActiveRecord::RecordNotUnique)
|
||||
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true).count).to eq(1)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,91 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'TagVersion names database constraint' do
|
||||
let(:tag) { create(:tag) }
|
||||
let(:names) do
|
||||
{ 'ja' => { 'name' => 'snapshot_name', 'script_code' => 'Jpan',
|
||||
'auto_generated' => false, 'aliases' => [] } }
|
||||
end
|
||||
let(:alias_name) { { 'name' => 'snapshot_alias', 'script_code' => 'Jpan' } }
|
||||
|
||||
def insert_snapshot!(snapshot)
|
||||
TagVersion.insert_all!([{ tag_id: tag.id, version_no: 1, event_type: 'create',
|
||||
name: 'snapshot_name', category: 'general',
|
||||
aliases: '', parent_tag_ids: '', names: snapshot,
|
||||
created_at: Time.current }])
|
||||
end
|
||||
|
||||
it 'accepts grouped Nipponese names with an empty alias list' do
|
||||
expect { insert_snapshot!(names) }.to change(TagVersion, :count).by(1)
|
||||
expect(TagVersion.find_by!(tag_id: tag.id).names).to eq(names)
|
||||
end
|
||||
|
||||
it 'accepts aliases containing only their name and script' do
|
||||
names['ja']['aliases'] = [alias_name]
|
||||
|
||||
expect { insert_snapshot!(names) }.to change(TagVersion, :count).by(1)
|
||||
end
|
||||
|
||||
it 'rejects a flat array root' do
|
||||
expect { insert_snapshot!([names.fetch('ja')]) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
|
||||
it 'rejects an empty object root' do
|
||||
expect { insert_snapshot!({ }) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
|
||||
it 'rejects a language entry that is not an object' do
|
||||
expect { insert_snapshot!({ 'ja' => 'not an object' }) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
|
||||
['name', 'script_code', 'auto_generated', 'aliases'].each do |field|
|
||||
it "rejects a primary entry missing #{ field }" do
|
||||
names['ja'].delete(field)
|
||||
|
||||
expect { insert_snapshot!(names) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects an empty primary name' do
|
||||
names['ja']['name'] = ''
|
||||
|
||||
expect { insert_snapshot!(names) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
|
||||
it 'rejects incorrect script casing' do
|
||||
names['ja']['script_code'] = 'JPAN'
|
||||
|
||||
expect { insert_snapshot!(names) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
|
||||
['unknown', 'primary_flg', 'language_code'].each do |field|
|
||||
it "rejects the extra primary field #{ field }" do
|
||||
names['ja'][field] = true
|
||||
|
||||
expect { insert_snapshot!(names) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
end
|
||||
|
||||
['unknown', 'primary_flg', 'auto_generated'].each do |field|
|
||||
it "rejects the extra alias field #{ field }" do
|
||||
names['ja']['aliases'] = [alias_name.merge(field => true)]
|
||||
|
||||
expect { insert_snapshot!(names) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects duplicate alias objects' do
|
||||
names['ja']['aliases'] = [alias_name, alias_name.deep_dup]
|
||||
|
||||
expect { insert_snapshot!(names) }
|
||||
.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,13 @@
|
||||
FactoryBot.define do
|
||||
factory :tag_name do
|
||||
name { "tag-#{SecureRandom.hex(4)}" }
|
||||
language_code { 'ja' }
|
||||
script_code { 'Jpan' }
|
||||
primary_flg { true }
|
||||
auto_generated { false }
|
||||
sequence(:name) { |number| "tag-#{ SecureRandom.hex(4) }-#{ number }" }
|
||||
|
||||
trait :alias do
|
||||
primary_flg { false }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,15 +1,41 @@
|
||||
FactoryBot.define do
|
||||
factory :tag do
|
||||
transient do
|
||||
name { nil }
|
||||
primary_name { nil }
|
||||
primary_tag_name do
|
||||
attributes = primary_name.nil? ? { } : { name: primary_name }
|
||||
build(:tag_name, **attributes)
|
||||
end
|
||||
end
|
||||
|
||||
category { :general }
|
||||
post_count { 0 }
|
||||
association :tag_name
|
||||
|
||||
after(:build) do |tag, evaluator|
|
||||
tag.name = evaluator.name if evaluator.name.present?
|
||||
evaluator.primary_tag_name.primary_flg = true
|
||||
tag.tag_names.target << evaluator.primary_tag_name
|
||||
end
|
||||
|
||||
before(:create) do |tag, evaluator|
|
||||
evaluator.primary_tag_name.save!
|
||||
# Compatibility write only; ownership is established through tag_id below.
|
||||
tag.tag_name_id = evaluator.primary_tag_name.id
|
||||
end
|
||||
|
||||
after(:create) do |tag, evaluator|
|
||||
evaluator.primary_tag_name.update!(tag:, primary_flg: true)
|
||||
|
||||
Locale.where(deprecated_at: nil).order(:language_code, :created_at, :script_code).to_a
|
||||
.uniq(&:language_code).each do |locale|
|
||||
next if tag.tag_names.exists?(language_code: locale.language_code,
|
||||
primary_flg: true)
|
||||
|
||||
create(:tag_name,
|
||||
tag:, language_code: locale.language_code,
|
||||
script_code: locale.script_code,
|
||||
name: TagName.generate_name(locale, tag, evaluator.primary_tag_name),
|
||||
primary_flg: true, auto_generated: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Language, type: :model do
|
||||
it 'accepts Nipponese as the current supported language' do
|
||||
expect(described_class.find('ja')).to be_valid
|
||||
expect(described_class.where(deprecated_at: nil).pluck(:code)).to eq(['ja'])
|
||||
end
|
||||
|
||||
it 'accepts English when multilingual support is enabled' do
|
||||
pending '日本語以外の Language 有効化は別課題で実装する'
|
||||
expect { described_class.create!(code: 'en', name: 'English') }
|
||||
.to change(described_class, :count).by(1)
|
||||
end
|
||||
|
||||
it 'rejects an unsupported language code' do
|
||||
expect {
|
||||
described_class.create!(code: 'xx', name: 'Unsupported')
|
||||
}.to raise_error { |error|
|
||||
expect(error).to be_a(ArgumentError).or be_a(ActiveRecord::RecordInvalid)
|
||||
}
|
||||
end
|
||||
|
||||
['en', 'fr'].each do |code|
|
||||
it "does not register #{ code } as a currently supported language" do
|
||||
expect {
|
||||
described_class.create!(code:, name: 'Unsupported')
|
||||
}.to raise_error { |error|
|
||||
expect(error).to be_a(ArgumentError).or be_a(ActiveRecord::RecordInvalid)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,129 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Locale, type: :model do
|
||||
include_context 'English locale'
|
||||
|
||||
def create_english_locale!
|
||||
prepare_english_reference!
|
||||
described_class.create!(code: 'en', language_code: 'en',
|
||||
script_code: 'Latn', region_code: 826,
|
||||
variants: '', name: 'English')
|
||||
end
|
||||
|
||||
it 'keeps the active Nipponese locale consistent with its identity tuple' do
|
||||
expect(described_class.where(deprecated_at: nil).pluck(:code)).to eq(['ja'])
|
||||
expect(described_class.nipponese).to have_attributes(
|
||||
code: 'ja', language_code: 'ja', script_code: 'Jpan',
|
||||
region_code: 392, variants: '')
|
||||
end
|
||||
|
||||
it 'rejects a duplicate locale identity tuple at the database boundary' do
|
||||
expect {
|
||||
described_class.insert_all!([{ code: 'ja-duplicate', language_code: 'ja',
|
||||
script_code: 'Jpan', region_code: 392,
|
||||
variants: '', name: '日本語',
|
||||
created_at: Time.current }])
|
||||
}.to raise_error(ActiveRecord::RecordNotUnique)
|
||||
end
|
||||
|
||||
it 'generates a primary name for every existing tag using the name generator' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
first = create(:tag, primary_name: 'first_existing_tag')
|
||||
second = create(:tag, primary_name: 'second_existing_tag')
|
||||
allow(TagName).to receive(:generate_name).and_call_original
|
||||
allow(TagName).to receive(:generate_name)
|
||||
.with(kind_of(described_class), first, primary_tag_name_for(first, 'ja'))
|
||||
.and_return('name_from_generator')
|
||||
|
||||
locale = create_english_locale!
|
||||
|
||||
expect(TagName).to have_received(:generate_name)
|
||||
.with(locale, first, primary_tag_name_for(first, 'ja'))
|
||||
expect(TagName).to have_received(:generate_name)
|
||||
.with(locale, second, primary_tag_name_for(second, 'ja'))
|
||||
expect(first.tag_names.find_by!(language_code: 'en', primary_flg: true))
|
||||
.to have_attributes(name: 'name_from_generator', tag_id: first.id,
|
||||
language_code: 'en', script_code: 'Latn',
|
||||
primary_flg: true, auto_generated: true)
|
||||
expect(second.tag_names.find_by!(language_code: 'en', primary_flg: true))
|
||||
.to have_attributes(tag_id: second.id, language_code: 'en',
|
||||
script_code: 'Latn', primary_flg: true,
|
||||
auto_generated: true)
|
||||
expect(TagName.where(language_code: 'en', primary_flg: true,
|
||||
tag_id: [first.id, second.id]).count).to eq(2)
|
||||
end
|
||||
|
||||
it 'does not duplicate an existing primary name in the new language' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
prepare_english_reference!
|
||||
tag = create(:tag, primary_name: 'already_named')
|
||||
existing = create(:tag_name, name: 'existing_english', tag:,
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
|
||||
create_english_locale!
|
||||
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'en', primary_flg: true))
|
||||
.to contain_exactly(existing)
|
||||
expect(existing.reload).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'en', script_code: 'Latn',
|
||||
primary_flg: true, auto_generated: false)
|
||||
end
|
||||
|
||||
it 'creates a primary name when the language has only an alias' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
prepare_english_reference!
|
||||
tag = create(:tag, primary_name: 'alias_only_tag')
|
||||
alias_name = create(:tag_name, :alias, name: 'alias_en',
|
||||
tag:,
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
|
||||
create_english_locale!
|
||||
|
||||
expect(alias_name.reload).to have_attributes(
|
||||
tag_id: tag.id,
|
||||
language_code: 'en', script_code: 'Latn', primary_flg: false)
|
||||
generated = TagName.find_by!(tag_id: tag.id, language_code: 'en', primary_flg: true)
|
||||
expect(generated).to have_attributes(tag_id: tag.id, language_code: 'en',
|
||||
script_code: 'Latn', primary_flg: true,
|
||||
auto_generated: true)
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'en', primary_flg: true).count).to eq(1)
|
||||
end
|
||||
|
||||
it 'rolls back the locale and all generated names when backfill fails' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
prepare_english_reference!
|
||||
first = create(:tag, primary_name: 'atomic_first')
|
||||
second = create(:tag, primary_name: 'atomic_second')
|
||||
allow(TagName).to receive(:generate_name).and_call_original
|
||||
allow(TagName).to receive(:generate_name)
|
||||
.with(kind_of(described_class), second, primary_tag_name_for(second, 'ja'))
|
||||
.and_raise('backfill failed')
|
||||
|
||||
expect { create_english_locale! }.to raise_error('backfill failed')
|
||||
|
||||
expect(described_class.exists?(code: 'en')).to be(false)
|
||||
expect(TagName.where(language_code: 'en', tag_id: [first.id, second.id])).to be_empty
|
||||
end
|
||||
|
||||
it 'accepts a stable locale code outside the historical enum values' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
prepare_english_reference!
|
||||
|
||||
locale = described_class.create!(
|
||||
code: 'en-x-rspec', language_code: 'en',
|
||||
script_code: 'Latn', region_code: 826,
|
||||
variants: 'x-rspec', name: 'English')
|
||||
|
||||
expect(locale.reload).to have_attributes(
|
||||
code: 'en-x-rspec', language_code: 'en', script_code: 'Latn',
|
||||
region_code: 826, variants: 'x-rspec')
|
||||
end
|
||||
|
||||
it 'registers the future British English locale tuple' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
|
||||
expect(locale).to have_attributes(code: 'en', language_code: 'en',
|
||||
script_code: 'Latn', region_code: 826,
|
||||
variants: '')
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe MaterialExportItem, type: :model do
|
||||
let(:user) { create(:user, :member) }
|
||||
let(:tag) { Tag.create!(tag_name: TagName.create!(name: 'export_item'), category: :material) }
|
||||
let(:tag) { create(:tag, primary_name: 'export_item', category: :material) }
|
||||
let(:material) do
|
||||
Material.create!(tag:, url: 'https://example.com/material',
|
||||
created_by_user: user, updated_by_user: user)
|
||||
|
||||
@@ -18,7 +18,7 @@ RSpec.describe Post, type: :model do
|
||||
expect(post.snapshot_tags_json).to eq([
|
||||
{ 'tag_id' => tag.id,
|
||||
'version_no' => tag.version_no,
|
||||
'name' => tag.name,
|
||||
'name' => primary_tag_name_for(tag, 'ja').name,
|
||||
'category' => tag.category,
|
||||
'sections' => [{ 'begin_ms' => 2000, 'end_ms' => nil }] },
|
||||
{ 'external_tag_id' => external.id }])
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe PostVersion, type: :model do
|
||||
let!(:tag_name) { TagName.create!(name: 'post_version_spec_tag') }
|
||||
let!(:tag) { Tag.create!(tag_name: tag_name, category: :general) }
|
||||
let!(:tag_name) { create(:tag_name, name: 'post_version_spec_tag') }
|
||||
let!(:tag) { create(:tag, primary_tag_name: tag_name, category: :general) }
|
||||
|
||||
let!(:post_record) do
|
||||
Post.create!(title: 'spec post', url: 'https://example.com/post-version-spec').tap do |post|
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Script, type: :model do
|
||||
include_context 'English locale'
|
||||
|
||||
it 'selects locales by script code, not language code' do
|
||||
expect(described_class.find('Jpan').locales).to contain_exactly(Locale.nipponese)
|
||||
end
|
||||
|
||||
it 'accepts supported script codes and rejects unsupported ones' do
|
||||
expect(described_class.find('Jpan')).to be_valid
|
||||
expect(described_class.where(deprecated_at: nil).pluck(:code)).to eq(['Jpan'])
|
||||
expect {
|
||||
described_class.create!(code: 'Zzzz', name: 'Unsupported')
|
||||
}.to raise_error { |error|
|
||||
expect(error).to be_a(ArgumentError).or be_a(ActiveRecord::RecordInvalid)
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not register Latin as a currently supported script' do
|
||||
expect {
|
||||
described_class.create!(code: 'Latn', name: 'Latin')
|
||||
}.to raise_error { |error|
|
||||
expect(error).to be_a(ArgumentError).or be_a(ActiveRecord::RecordInvalid)
|
||||
}
|
||||
end
|
||||
|
||||
it 'selects Latin locales when multilingual support is enabled' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
|
||||
expect(described_class.find('Latn').locales).to contain_exactly(locale)
|
||||
expect(described_class.find('Jpan').locales).not_to include(locale)
|
||||
end
|
||||
end
|
||||
@@ -2,8 +2,8 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe TagImplication, type: :model do
|
||||
it 'rejects a parent tag that would create a cycle' do
|
||||
child = create(:tag, name: 'tag_implication_cycle_child')
|
||||
parent = create(:tag, name: 'tag_implication_cycle_parent')
|
||||
child = create(:tag, primary_name: 'tag_implication_cycle_child')
|
||||
parent = create(:tag, primary_name: 'tag_implication_cycle_parent')
|
||||
|
||||
described_class.create!(tag: child, parent_tag: parent)
|
||||
|
||||
@@ -17,9 +17,9 @@ RSpec.describe TagImplication, type: :model do
|
||||
end
|
||||
|
||||
it 'terminates even when existing data already contains a cycle' do
|
||||
child = create(:tag, name: 'tag_implication_existing_cycle_child')
|
||||
parent = create(:tag, name: 'tag_implication_existing_cycle_parent')
|
||||
ancestor = create(:tag, name: 'tag_implication_existing_cycle_ancestor')
|
||||
child = create(:tag, primary_name: 'tag_implication_existing_cycle_child')
|
||||
parent = create(:tag, primary_name: 'tag_implication_existing_cycle_parent')
|
||||
ancestor = create(:tag, primary_name: 'tag_implication_existing_cycle_ancestor')
|
||||
|
||||
described_class.create!(tag: parent, parent_tag: ancestor)
|
||||
described_class.insert_all!(
|
||||
|
||||
@@ -31,12 +31,48 @@ RSpec.describe TagNameSanitisationRule, type: :model do
|
||||
|
||||
describe '.apply!' do
|
||||
before do
|
||||
described_class.create!(priority: 10, source_pattern: '_', replacement: '')
|
||||
described_class.create!(priority: 10, source_pattern: 'foo_', replacement: 'foo')
|
||||
end
|
||||
|
||||
context 'when only another language has the sanitised name' do
|
||||
include_context 'English locale'
|
||||
|
||||
it 'keeps both names when sanitisation produces a cross-language match' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
nipponese = create(:tag_name, name: 'foobar')
|
||||
english = create(:tag_name, name: 'temporary',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english.update_columns(name: 'foo_bar')
|
||||
|
||||
expect { described_class.apply! }.not_to change(TagName, :count)
|
||||
|
||||
expect(english.reload).to have_attributes(name: 'foobar', language_code: 'en')
|
||||
expect(nipponese.reload).to have_attributes(name: 'foobar', language_code: 'ja')
|
||||
end
|
||||
|
||||
it 'renames without merging tags or deleting either language identity' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
nipponese = create(:tag, primary_name: 'foobar')
|
||||
english_name = create(:tag_name, name: 'temporary',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english = create(:tag, primary_tag_name: english_name)
|
||||
english_name.update_columns(name: 'foo_bar')
|
||||
|
||||
described_class.apply!
|
||||
|
||||
expect(english_name.reload).to have_attributes(
|
||||
name: 'foobar', language_code: 'en', tag_id: english.id)
|
||||
expect(primary_tag_name_for(nipponese.reload, 'ja')).to have_attributes(
|
||||
name: 'foobar', language_code: 'ja', tag_id: nipponese.id)
|
||||
expect(primary_tag_name_for(english.reload, 'en')).to eq(english_name)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no conflicting tag_name exists' do
|
||||
let!(:tag_name) do
|
||||
TagName.create!(name: 'tmp').tap do |tn|
|
||||
create(:tag_name, name: 'tmp').tap do |tn|
|
||||
tn.update_columns(name: 'foo_bar', updated_at: Time.current)
|
||||
end
|
||||
end
|
||||
@@ -47,10 +83,10 @@ RSpec.describe TagNameSanitisationRule, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a conflicting canonical tag_name exists' do
|
||||
let!(:existing) { TagName.create!(name: 'foobar') }
|
||||
context 'when a conflicting primary tag name exists' do
|
||||
let!(:existing) { create(:tag_name, name: 'foobar') }
|
||||
let!(:source) do
|
||||
TagName.create!(name: 'tmp').tap do |tn|
|
||||
create(:tag_name, name: 'tmp').tap do |tn|
|
||||
tn.update_columns(name: 'foo_bar', updated_at: Time.current)
|
||||
end
|
||||
end
|
||||
@@ -63,48 +99,56 @@ RSpec.describe TagNameSanitisationRule, type: :model do
|
||||
end
|
||||
|
||||
context 'when the source tag_name has a tag and the existing one has no tag' do
|
||||
let!(:existing) { TagName.create!(name: 'foobar') }
|
||||
let!(:source_tag) { create(:tag, name: 'tmp', category: :general) }
|
||||
let!(:source_tag_name_id) { source_tag.tag_name_id }
|
||||
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
|
||||
source_tag.tag_name.update_columns(name: 'foo_bar', updated_at: Time.current)
|
||||
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!
|
||||
expected_tag_name_id = existing.canonical_id || existing.id
|
||||
expect(source_tag.reload.tag_name_id).to eq(expected_tag_name_id)
|
||||
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) }
|
||||
let!(:existing_primary) { primary_tag_name_for(existing_tag, 'ja') }
|
||||
let!(:alias_name) do
|
||||
TagName.create!(name: 'foobar', canonical: existing_tag.tag_name)
|
||||
create(:tag_name, :alias, name: 'foobar', tag: existing_tag)
|
||||
end
|
||||
let!(:source) do
|
||||
TagName.create!(name: 'tmp').tap do |tn|
|
||||
create(:tag_name, name: 'tmp').tap do |tn|
|
||||
tn.update_columns(name: 'foo_bar', updated_at: Time.current)
|
||||
end
|
||||
end
|
||||
|
||||
it 'deletes only the source and preserves the alias and its canonical tag' do
|
||||
it 'deletes only the source and preserves the alias and its owning tag' do
|
||||
described_class.apply!
|
||||
|
||||
expect(TagName.unscoped.exists?(source.id)).to be(false)
|
||||
expect(alias_name.reload.canonical).to eq(existing_tag.tag_name)
|
||||
expect(alias_name.reload).to have_attributes(
|
||||
tag_id: existing_tag.id, language_code: 'ja', primary_flg: false)
|
||||
expect(TagName.find_by!(tag_id: existing_tag.id,
|
||||
language_code: 'ja', primary_flg: true))
|
||||
.to eq(existing_primary)
|
||||
expect(Tag.find(existing_tag.id)).to eq(existing_tag)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when both source and existing tag_names have tags' do
|
||||
let!(:existing_tn) { TagName.create!(name: 'foobar') }
|
||||
let!(:existing_tag) { Tag.create!(tag_name: existing_tn, category: :general) }
|
||||
let!(:existing_tn) { create(:tag_name, name: 'foobar') }
|
||||
let!(:existing_tag) { create(:tag, primary_tag_name: existing_tn, category: :general) }
|
||||
|
||||
let!(:source_tn) { TagName.create!(name: 'tmp') }
|
||||
let!(:source_tag) { Tag.create!(tag_name: source_tn, category: :general) }
|
||||
let!(:source_tn) { create(:tag_name, name: 'tmp') }
|
||||
let!(:source_tag) { create(:tag, primary_tag_name: source_tn, category: :general) }
|
||||
let!(:source_tag_name_id) { source_tn.id }
|
||||
|
||||
before do
|
||||
@@ -120,7 +164,7 @@ RSpec.describe TagNameSanitisationRule, type: :model do
|
||||
expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
|
||||
expect(TagName.unscoped.exists?(source_tag_name_id)).to be(false)
|
||||
expect(post.reload.tags).to contain_exactly(existing_tag)
|
||||
expect(existing_tag.reload.name).to eq('foobar')
|
||||
expect(primary_tag_name_for(existing_tag.reload, 'ja').name).to eq('foobar')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe TagName, type: :model do
|
||||
include_context 'English locale'
|
||||
|
||||
it 'allows the same name in different languages' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
create(:tag_name, name: 'shared_name')
|
||||
english = build(:tag_name, name: 'shared_name',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
|
||||
expect(english).to be_valid
|
||||
expect { english.save! }.to change(described_class, :count).by(1)
|
||||
end
|
||||
|
||||
it 'rejects a duplicate name within the same language' do
|
||||
create(:tag_name, name: 'shared_name')
|
||||
duplicate = build(:tag_name, name: 'shared_name')
|
||||
|
||||
expect(duplicate).to be_invalid
|
||||
expect(duplicate.errors.of_kind?(:name, :taken)).to be(true)
|
||||
end
|
||||
|
||||
it 'creates Nipponese factory names without requiring a 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)
|
||||
end
|
||||
|
||||
it 'defaults ordinary persisted human names to confirmed provenance' do
|
||||
name = described_class.create!(name: 'ordinary_human_name', language_code: 'ja',
|
||||
script_code: 'Jpan', primary_flg: true)
|
||||
|
||||
expect(name.reload.auto_generated).to be(false)
|
||||
end
|
||||
|
||||
it 'persists both sides of the representative tag factory association' do
|
||||
name = create(:tag_name)
|
||||
tag = create(:tag, primary_tag_name: name)
|
||||
|
||||
expect(name.reload).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'ja', script_code: 'Jpan', primary_flg: true)
|
||||
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)
|
||||
end
|
||||
|
||||
it 'creates a complete ordinary tag factory under the active Nipponese language' do
|
||||
tag = create(:tag, primary_name: 'factory_multilingual')
|
||||
|
||||
Locale.where(deprecated_at: nil).distinct.pluck(:language_code).each do |language_code|
|
||||
expect(TagName.where(tag:, language_code:, primary_flg: true).count).to eq(1)
|
||||
end
|
||||
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true))
|
||||
.to contain_exactly(have_attributes(auto_generated: false))
|
||||
end
|
||||
|
||||
it 'completes an ordinary tag factory under future active languages' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
tag = create(:tag, primary_name: 'future_factory')
|
||||
|
||||
expect(TagName.where(tag:, language_code: 'en', primary_flg: true))
|
||||
.to contain_exactly(have_attributes(auto_generated: true))
|
||||
end
|
||||
|
||||
it 'builds a primary name without persisting the tag or its name' do
|
||||
tag = nil
|
||||
|
||||
expect {
|
||||
tag = build(:tag, primary_name: 'built_primary')
|
||||
}.to change(Tag, :count).by(0).and change(described_class, :count).by(0)
|
||||
|
||||
expect(tag).to be_new_record
|
||||
expect(tag.tag_names.target).to contain_exactly(have_attributes(
|
||||
name: 'built_primary', language_code: 'ja', script_code: 'Jpan',
|
||||
primary_flg: true))
|
||||
end
|
||||
|
||||
it 'persists an alias owned by the same tag as its primary name' do
|
||||
primary_name = create(:tag_name)
|
||||
tag = create(:tag, primary_tag_name: primary_name)
|
||||
alias_name = create(:tag_name, :alias, name: 'valid_alias', tag:)
|
||||
|
||||
expect(alias_name.reload).to have_attributes(
|
||||
tag_id: tag.id,
|
||||
primary_flg: false, language_code: 'ja')
|
||||
primary = described_class.find_by!(tag_id: alias_name.tag_id,
|
||||
language_code: alias_name.language_code,
|
||||
primary_flg: true)
|
||||
expect(primary).to eq(primary_name)
|
||||
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')
|
||||
|
||||
expect(described_class.canonicalise(Locale.nipponese, ['detached_alias']))
|
||||
.to eq(['detached_alias'])
|
||||
end
|
||||
|
||||
it 'resolves the primary name of the alias language on the same tag' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
tag = create(:tag, primary_name: '日本語正本')
|
||||
english_primary = primary_tag_name_for(tag, 'en')
|
||||
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
|
||||
|
||||
primary = described_class.find_by!(tag_id: english_alias.tag_id,
|
||||
language_code: english_alias.language_code,
|
||||
primary_flg: true)
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
it 'resolves only aliases in the requested language' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
nipponese = create(:tag, primary_name: 'nipponese_canonical')
|
||||
english_name = create(:tag_name, name: 'english_canonical',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english = create(:tag, primary_tag_name: english_name)
|
||||
nipponese_alias = create(:tag_name, :alias, name: 'shared_alias',
|
||||
tag: nipponese)
|
||||
english_alias = create(:tag_name, :alias, name: 'shared_alias',
|
||||
tag: english,
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
|
||||
expect(nipponese_alias.reload).to have_attributes(
|
||||
tag_id: nipponese.id,
|
||||
primary_flg: false, language_code: 'ja')
|
||||
expect(english_alias.reload).to have_attributes(
|
||||
tag_id: english.id,
|
||||
primary_flg: false, language_code: 'en')
|
||||
expect(described_class.canonicalise(locale, ['shared_alias']))
|
||||
.to eq(['english_canonical'])
|
||||
expect(described_class.canonicalise(Locale.nipponese, ['shared_alias']))
|
||||
.to eq(['nipponese_canonical'])
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects the generated-name prefix in human input' do
|
||||
human_name = build(:tag_name, name: 'Tag_#999', auto_generated: false)
|
||||
|
||||
expect(human_name).to be_invalid
|
||||
end
|
||||
|
||||
it 'allows the generated-name prefix for system-created names' do
|
||||
generated_name = build(:tag_name, name: 'Tag_#1000', auto_generated: true)
|
||||
|
||||
expect { generated_name.save! }.to change(described_class, :count).by(1)
|
||||
end
|
||||
end
|
||||
+352
-96
@@ -4,12 +4,12 @@ RSpec.describe Tag, type: :model do
|
||||
describe 'external tag separation' do
|
||||
['nico:reserved', 'NiCo:reserved'].each do |name|
|
||||
it "rejects the reserved prefix #{ name } for internal tags" do
|
||||
tag = build(:tag, name:)
|
||||
tag = build(:tag, primary_name: name)
|
||||
|
||||
expect(tag).to be_invalid
|
||||
expect(tag.errors[:name]).to be_present
|
||||
expect {
|
||||
described_class.normalise_tags!([name])
|
||||
described_class.normalise_tags!(Locale.nipponese, [name])
|
||||
}.to raise_error(Tag::NicoTagNormalisationError)
|
||||
end
|
||||
end
|
||||
@@ -25,128 +25,161 @@ RSpec.describe Tag, type: :model do
|
||||
end
|
||||
|
||||
describe '.normalise_tags!' do
|
||||
include_context 'English locale'
|
||||
|
||||
it 'canonicalises aliases in the supplied language only' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
prepare_english_reference!
|
||||
nipponese = create(:tag, primary_name: 'nipponese_canonical')
|
||||
english_name = create(:tag_name, name: 'english_canonical',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english = create(:tag, primary_tag_name: english_name)
|
||||
primary_tag_name_for(english, 'ja').update!(
|
||||
name: 'english_in_nipponese', auto_generated: false)
|
||||
english_locale = locale
|
||||
nipponese_alias = create(:tag_name, :alias, name: 'shared_alias',
|
||||
tag: nipponese)
|
||||
english_alias = create(:tag_name, :alias, name: 'shared_alias',
|
||||
tag: english,
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
|
||||
expect(nipponese_alias.reload).to have_attributes(
|
||||
tag_id: nipponese.id,
|
||||
primary_flg: false, language_code: 'ja')
|
||||
expect(english_alias.reload).to have_attributes(
|
||||
tag_id: english.id,
|
||||
primary_flg: false, language_code: 'en')
|
||||
expect(described_class.normalise_tags!(
|
||||
english_locale, ['shared_alias'],
|
||||
with_tagme: false, with_no_deerjikist: false)).to eq([english])
|
||||
expect(described_class.normalise_tags!(
|
||||
Locale.nipponese, ['shared_alias'],
|
||||
with_tagme: false, with_no_deerjikist: false)).to eq([nipponese])
|
||||
end
|
||||
|
||||
it 'rejects deprecated tags when deny_deprecated is enabled' do
|
||||
tag_name = TagName.create!(name: 'normalise deprecated tag')
|
||||
deprecated_tag = Tag.create!(
|
||||
tag_name:,
|
||||
category: :general,
|
||||
deprecated_at: 1.day.from_now
|
||||
)
|
||||
tag_name = create(:tag_name, name: 'normalise deprecated tag')
|
||||
deprecated_tag = create(:tag,
|
||||
primary_tag_name: tag_name,
|
||||
category: :general,
|
||||
deprecated_at: 1.day.from_now)
|
||||
|
||||
expect {
|
||||
described_class.normalise_tags!(
|
||||
[deprecated_tag.name],
|
||||
deny_deprecated: true
|
||||
)
|
||||
described_class.normalise_tags!(Locale.nipponese,
|
||||
[primary_tag_name_for(deprecated_tag, 'ja').name],
|
||||
deny_deprecated: true)
|
||||
}.to raise_error(Tag::DeprecatedTagNormalisationError) { |error|
|
||||
expect(error.tag_names).to eq([deprecated_tag.name])
|
||||
expect(error.tag_names).to eq([primary_tag_name_for(deprecated_tag, 'ja').name])
|
||||
}
|
||||
end
|
||||
|
||||
it 'rejects invalid section literals instead of treating them as zero' do
|
||||
expect {
|
||||
described_class.normalise_tags!(
|
||||
['normalise_invalid_section[1:aa-2:00]'],
|
||||
with_sections: true
|
||||
)
|
||||
described_class.normalise_tags!(Locale.nipponese,
|
||||
['normalise_invalid_section[1:aa-2:00]'],
|
||||
with_sections: true)
|
||||
}.to raise_error(Tag::SectionLiteralParseError)
|
||||
end
|
||||
|
||||
it 'parses open-ended section literals' do
|
||||
result = described_class.normalise_tags!(
|
||||
['伊地知ニジカ[1:00-]'],
|
||||
with_sections: true
|
||||
)
|
||||
result = described_class.normalise_tags!(Locale.nipponese,
|
||||
['伊地知ニジカ[1:00-]'],
|
||||
with_sections: true)
|
||||
|
||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||
tag = result.fetch(:tags).find do |candidate|
|
||||
primary_tag_name_for(candidate, 'ja').name == '伊地知ニジカ'
|
||||
end
|
||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, nil]])
|
||||
end
|
||||
|
||||
it 'parses omitted begin as zero' do
|
||||
result = described_class.normalise_tags!(
|
||||
['伊地知ニジカ[-1:00]'],
|
||||
with_sections: true
|
||||
)
|
||||
result = described_class.normalise_tags!(Locale.nipponese,
|
||||
['伊地知ニジカ[-1:00]'],
|
||||
with_sections: true)
|
||||
|
||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||
tag = result.fetch(:tags).find do |candidate|
|
||||
primary_tag_name_for(candidate, 'ja').name == '伊地知ニジカ'
|
||||
end
|
||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[0, 60_000]])
|
||||
end
|
||||
|
||||
it 'treats fully open section literals as plain tags' do
|
||||
result = described_class.normalise_tags!(
|
||||
['伊地知ニジカ[-]'],
|
||||
with_sections: true
|
||||
)
|
||||
result = described_class.normalise_tags!(Locale.nipponese,
|
||||
['伊地知ニジカ[-]'],
|
||||
with_sections: true)
|
||||
|
||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||
tag = result.fetch(:tags).find do |candidate|
|
||||
primary_tag_name_for(candidate, 'ja').name == '伊地知ニジカ'
|
||||
end
|
||||
expect(result.fetch(:sections)[tag.id]).to be_nil
|
||||
end
|
||||
|
||||
it 'treats [0:00-] as a plain tag' do
|
||||
result = described_class.normalise_tags!(
|
||||
['伊地知ニジカ[0:00-]'],
|
||||
with_sections: true
|
||||
)
|
||||
result = described_class.normalise_tags!(Locale.nipponese,
|
||||
['伊地知ニジカ[0:00-]'],
|
||||
with_sections: true)
|
||||
|
||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||
tag = result.fetch(:tags).find do |candidate|
|
||||
primary_tag_name_for(candidate, 'ja').name == '伊地知ニジカ'
|
||||
end
|
||||
expect(result.fetch(:sections)[tag.id]).to be_nil
|
||||
end
|
||||
|
||||
it 'expands zero-width sections to one millisecond' do
|
||||
result = described_class.normalise_tags!(
|
||||
['伊地知ニジカ[1:00-1:00]'],
|
||||
with_sections: true
|
||||
)
|
||||
result = described_class.normalise_tags!(Locale.nipponese,
|
||||
['伊地知ニジカ[1:00-1:00]'],
|
||||
with_sections: true)
|
||||
|
||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||
tag = result.fetch(:tags).find do |candidate|
|
||||
primary_tag_name_for(candidate, 'ja').name == '伊地知ニジカ'
|
||||
end
|
||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, 60_001]])
|
||||
end
|
||||
|
||||
it 'swaps reversed section boundaries' do
|
||||
result = described_class.normalise_tags!(
|
||||
['伊地知ニジカ[2:00-1:00]'],
|
||||
with_sections: true
|
||||
)
|
||||
result = described_class.normalise_tags!(Locale.nipponese,
|
||||
['伊地知ニジカ[2:00-1:00]'],
|
||||
with_sections: true)
|
||||
|
||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||
tag = result.fetch(:tags).find do |candidate|
|
||||
primary_tag_name_for(candidate, 'ja').name == '伊地知ニジカ'
|
||||
end
|
||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, 120_000]])
|
||||
end
|
||||
|
||||
it 'merges open-ended sections over later bounded sections' do
|
||||
result = described_class.normalise_tags!(
|
||||
['伊地知ニジカ[1:00-][2:00-3:00]'],
|
||||
with_sections: true
|
||||
)
|
||||
result = described_class.normalise_tags!(Locale.nipponese,
|
||||
['伊地知ニジカ[1:00-][2:00-3:00]'],
|
||||
with_sections: true)
|
||||
|
||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||
tag = result.fetch(:tags).find do |candidate|
|
||||
primary_tag_name_for(candidate, 'ja').name == '伊地知ニジカ'
|
||||
end
|
||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, nil]])
|
||||
end
|
||||
|
||||
it 'merges adjacent bounded and open-ended sections' do
|
||||
result = described_class.normalise_tags!(
|
||||
['伊地知ニジカ[1:00-3:00][3:00-]'],
|
||||
with_sections: true
|
||||
)
|
||||
result = described_class.normalise_tags!(Locale.nipponese,
|
||||
['伊地知ニジカ[1:00-3:00][3:00-]'],
|
||||
with_sections: true)
|
||||
|
||||
tag = result.fetch(:tags).find { _1.name == '伊地知ニジカ' }
|
||||
tag = result.fetch(:tags).find do |candidate|
|
||||
primary_tag_name_for(candidate, 'ja').name == '伊地知ニジカ'
|
||||
end
|
||||
expect(result.fetch(:sections).fetch(tag.id)).to eq([[60_000, nil]])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.expand_parent_tags' do
|
||||
it 'expands through multiple deprecated parents to an active ancestor' do
|
||||
child = create(:tag, name: 'expand_child')
|
||||
deprecated_parent = create(
|
||||
:tag,
|
||||
name: 'expand_deprecated_parent',
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
deprecated_grandparent = create(
|
||||
:tag,
|
||||
name: 'expand_deprecated_grandparent',
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
active_ancestor = create(:tag, name: 'expand_active_ancestor')
|
||||
child = create(:tag, primary_name: 'expand_child')
|
||||
deprecated_parent = create(:tag,
|
||||
primary_name: 'expand_deprecated_parent',
|
||||
deprecated_at: Time.current)
|
||||
deprecated_grandparent = create(:tag,
|
||||
primary_name: 'expand_deprecated_grandparent',
|
||||
deprecated_at: Time.current)
|
||||
active_ancestor = create(:tag, primary_name: 'expand_active_ancestor')
|
||||
TagImplication.create!(tag: child, parent_tag: deprecated_parent)
|
||||
TagImplication.create!(tag: deprecated_parent, parent_tag: deprecated_grandparent)
|
||||
TagImplication.create!(tag: deprecated_grandparent, parent_tag: active_ancestor)
|
||||
@@ -163,8 +196,8 @@ RSpec.describe Tag, type: :model do
|
||||
end
|
||||
|
||||
it 'terminates when implications contain a cycle' do
|
||||
first = create(:tag, name: 'expand_cycle_first')
|
||||
second = create(:tag, name: 'expand_cycle_second')
|
||||
first = create(:tag, primary_name: 'expand_cycle_first')
|
||||
second = create(:tag, primary_name: 'expand_cycle_second')
|
||||
TagImplication.create!(tag: first, parent_tag: second)
|
||||
now = Time.current
|
||||
TagImplication.insert_all!(
|
||||
@@ -183,55 +216,268 @@ RSpec.describe Tag, type: :model do
|
||||
end
|
||||
|
||||
describe '.find_or_create_by_tag_name!' do
|
||||
context 'with an explicit locale' do
|
||||
include_context 'English locale'
|
||||
|
||||
it 'creates the representative name with locale attributes and ownership' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
tag = described_class.find_or_create_by_tag_name!(
|
||||
locale, 'english_name', category: :character)
|
||||
|
||||
expect(primary_tag_name_for(tag, 'en')).to have_attributes(
|
||||
language_code: 'en', script_code: 'Latn',
|
||||
primary_flg: true, tag_id: tag.id)
|
||||
expect(tag.category).to eq('character')
|
||||
end
|
||||
|
||||
it 'uses the English primary as the source for a generated Nipponese name' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
allow(TagName).to receive(:generate_name).and_return('generated_nipponese')
|
||||
|
||||
tag = described_class.find_or_create_by_tag_name!(
|
||||
locale, 'english_origin', category: :general)
|
||||
|
||||
expect(TagName.where(tag:, language_code: 'en', primary_flg: true))
|
||||
.to contain_exactly(have_attributes(
|
||||
name: 'english_origin', auto_generated: false))
|
||||
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true))
|
||||
.to contain_exactly(have_attributes(
|
||||
name: 'generated_nipponese', auto_generated: true))
|
||||
source_tag_name = TagName.find_by!(tag:, language_code: 'en', primary_flg: true)
|
||||
expect(TagName).to have_received(:generate_name)
|
||||
.with(Locale.nipponese, tag, source_tag_name)
|
||||
end
|
||||
|
||||
it 'gives a new tag one primary name for every active language' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
tag = described_class.find_or_create_by_tag_name!(
|
||||
Locale.nipponese, 'new_multilingual_tag', category: :general)
|
||||
|
||||
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true).count).to eq(1)
|
||||
expect(TagName.where(tag:, language_code: 'en', primary_flg: true).count).to eq(1)
|
||||
end
|
||||
|
||||
it 'creates one primary per language when multiple locales use English' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
described_class.find_or_create_by_tag_name!(
|
||||
locale, 'before_second_english_locale', category: :general)
|
||||
Locale.create!(code: 'en-x-rspec', language_code: 'en',
|
||||
script_code: 'Latn', region_code: 826,
|
||||
variants: 'x-rspec', name: 'English')
|
||||
|
||||
tag = described_class.find_or_create_by_tag_name!(
|
||||
locale, 'one_english_primary', category: :general)
|
||||
|
||||
expect(TagName.where(tag:, language_code: 'en', primary_flg: true).count).to eq(1)
|
||||
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true).count).to eq(1)
|
||||
end
|
||||
|
||||
it 'rolls back a new tag and requested name if name generation fails' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
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, 'atomic_english_name', category: :general)
|
||||
}.to raise_error('generation failed')
|
||||
|
||||
expect(described_class.count).to eq(original_tag_count)
|
||||
expect(TagName.where(language_code: 'en', name: 'atomic_english_name')).to be_empty
|
||||
expect(described_class.joins(:tag_names)
|
||||
.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 有効化は別課題で実装する'
|
||||
locale
|
||||
nipponese = described_class.find_or_create_by_tag_name!(
|
||||
Locale.nipponese, 'same_name', category: :general)
|
||||
english = described_class.find_or_create_by_tag_name!(
|
||||
locale, 'same_name', category: :character)
|
||||
|
||||
expect(english).not_to eq(nipponese)
|
||||
expect(primary_tag_name_for(english, 'en')).to have_attributes(
|
||||
name: 'same_name', tag_id: english.id, language_code: 'en', primary_flg: true)
|
||||
expect(primary_tag_name_for(nipponese, 'ja')).to have_attributes(
|
||||
name: 'same_name', tag_id: nipponese.id, language_code: 'ja', primary_flg: true)
|
||||
expect(described_class.find_or_create_by_tag_name!(
|
||||
locale, 'same_name', category: :general)).to eq(english)
|
||||
expect(described_class.find_or_create_by_tag_name!(
|
||||
Locale.nipponese, 'same_name', category: :general)).to eq(nipponese)
|
||||
end
|
||||
|
||||
it 'preserves the V1 representative when looking up another primary language' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
tag = create(:tag, primary_name: '日本語代表名')
|
||||
representative_id = tag.tag_name_id
|
||||
english = primary_tag_name_for(tag, 'en')
|
||||
english.update!(name: 'english_primary', auto_generated: false)
|
||||
|
||||
found = described_class.find_or_create_by_tag_name!(
|
||||
locale, english.name, category: :general)
|
||||
|
||||
expect(found).to eq(tag)
|
||||
expect(tag.reload.tag_name_id).to eq(representative_id)
|
||||
expect(english.reload.tag_id).to eq(tag.id)
|
||||
end
|
||||
|
||||
it 'normalises names using the supplied locale' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
tags = described_class.normalise_tags!(
|
||||
locale, ['character:normalised_english'],
|
||||
with_tagme: false, with_no_deerjikist: false)
|
||||
|
||||
expect(tags.length).to eq(1)
|
||||
expect(primary_tag_name_for(tags.first, 'en')).to have_attributes(
|
||||
name: 'normalised_english', language_code: 'en', script_code: 'Latn',
|
||||
primary_flg: true, tag_id: tags.first.id)
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates a tag and name with the requested category after stripping whitespace' do
|
||||
tag = nil
|
||||
|
||||
expect {
|
||||
tag = described_class.find_or_create_by_tag_name!(
|
||||
tag = described_class.find_or_create_by_tag_name!(Locale.nipponese,
|
||||
' lookup_new ', category: :character)
|
||||
}.to change(Tag, :count).by(1).and change(TagName, :count).by(1)
|
||||
|
||||
expect(tag.name).to eq('lookup_new')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('lookup_new')
|
||||
expect(tag.category).to eq('character')
|
||||
expect(TagName.where(tag:, language_code: 'ja', primary_flg: true).count).to eq(1)
|
||||
expect(primary_tag_name_for(tag, 'ja').auto_generated).to be(false)
|
||||
end
|
||||
|
||||
it 'reuses the canonical tag for an alias without changing its category' do
|
||||
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)
|
||||
alias_name = TagName.create!(name: 'lookup_alias', canonical: tag.tag_name)
|
||||
representative_id = tag.tag_name_id
|
||||
alias_name = create(:tag_name, :alias, name: 'lookup_alias', tag:)
|
||||
expect(alias_name.reload).to have_attributes(
|
||||
tag_id: tag.id,
|
||||
primary_flg: false, language_code: 'ja')
|
||||
found = nil
|
||||
|
||||
expect {
|
||||
found = described_class.find_or_create_by_tag_name!(
|
||||
found = described_class.find_or_create_by_tag_name!(Locale.nipponese,
|
||||
alias_name.name, category: :general)
|
||||
}.to change(Tag, :count).by(0).and change(TagName, :count).by(0)
|
||||
|
||||
expect(found).to eq(tag)
|
||||
expect(found.category).to eq('character')
|
||||
expect(tag.reload.tag_name_id).to eq(representative_id)
|
||||
end
|
||||
|
||||
it 'creates a tag for an existing canonical name reached through an alias' do
|
||||
canonical = create(:tag_name)
|
||||
alias_name = TagName.create!(name: 'lookup_alias', canonical:)
|
||||
tag = nil
|
||||
it 'reuses the owning tag through another alias' do
|
||||
tag = create(:tag)
|
||||
primary = primary_tag_name_for(tag, 'ja')
|
||||
alias_name = create(:tag_name, :alias, name: 'lookup_alias', tag:)
|
||||
other_alias = create(:tag_name, :alias, name: 'lookup_other_alias', tag:)
|
||||
|
||||
expect([alias_name.reload, other_alias.reload]).to all(have_attributes(
|
||||
tag_id: tag.id,
|
||||
primary_flg: false, language_code: 'ja'))
|
||||
|
||||
expect {
|
||||
tag = described_class.find_or_create_by_tag_name!(
|
||||
found = described_class.find_or_create_by_tag_name!(Locale.nipponese,
|
||||
alias_name.name, category: :general)
|
||||
}.to change(Tag, :count).by(1).and change(TagName, :count).by(0)
|
||||
expect(found).to eq(tag)
|
||||
}.to change(Tag, :count).by(0).and change(TagName, :count).by(0)
|
||||
|
||||
expect(tag.tag_name).to eq(canonical)
|
||||
expect(primary_tag_name_for(tag.reload, 'ja')).to eq(primary)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.merge_tags!' do
|
||||
let!(:target_tag) { create(:tag, category: :general) }
|
||||
let!(:source_tag) { create(:tag, category: :general) }
|
||||
let!(:source_tag_name) { source_tag.tag_name }
|
||||
let!(:source_tag_name) { primary_tag_name_for(source_tag, 'ja') }
|
||||
|
||||
let!(:post_record) do
|
||||
Post.create!(url: 'https://example.com/posts/1', title: 'test post')
|
||||
end
|
||||
|
||||
context 'with names in multiple languages' do
|
||||
include_context 'English locale'
|
||||
|
||||
it 'keeps the target primary and moves source names as aliases' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
english_primary = primary_tag_name_for(target_tag, 'en')
|
||||
source_primary = primary_tag_name_for(source_tag, 'en')
|
||||
source_alias = create(:tag_name, :alias, tag: source_tag,
|
||||
name: 'source_english_alias', language_code: 'en', script_code: 'Latn')
|
||||
|
||||
expect([english_primary, source_primary]).to all(have_attributes(
|
||||
primary_flg: true, auto_generated: true))
|
||||
|
||||
described_class.merge_tags!(target_tag, [source_tag])
|
||||
|
||||
expect(primary_tag_name_for(target_tag.reload, 'en')).to eq(english_primary)
|
||||
expect(TagName.where(tag: target_tag, language_code: 'en', primary_flg: true))
|
||||
.to contain_exactly(english_primary)
|
||||
expect([source_primary.reload, source_alias.reload]).to all(have_attributes(
|
||||
tag_id: target_tag.id, language_code: 'en', primary_flg: false))
|
||||
expect(TagName.where(tag: target_tag, language_code: 'ja', primary_flg: true))
|
||||
.to contain_exactly(primary_tag_name_for(target_tag, 'ja'))
|
||||
expect(source_tag_name.reload).to have_attributes(
|
||||
tag_id: target_tag.id, language_code: 'ja', primary_flg: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merging a simple source tag' do
|
||||
let!(:source_post_tag) { PostTag.create!(post: post_record, tag: source_tag) }
|
||||
|
||||
@@ -243,7 +489,8 @@ RSpec.describe Tag, type: :model do
|
||||
expect(PostTag.exists?(post: post_record, tag: source_tag)).to be(false)
|
||||
expect(target_link).to be_present
|
||||
expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
|
||||
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||
expect(source_tag_name.reload).to have_attributes(
|
||||
tag_id: target_tag.id, language_code: 'ja', primary_flg: false)
|
||||
expect(target_tag.reload.post_count).to eq(1)
|
||||
end
|
||||
end
|
||||
@@ -269,15 +516,16 @@ RSpec.describe Tag, type: :model do
|
||||
expect(target_post_tag.reload.sections).to contain_exactly(target_section)
|
||||
|
||||
expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
|
||||
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||
expect(source_tag_name.reload).to have_attributes(
|
||||
tag_id: target_tag.id, language_code: 'ja', primary_flg: false)
|
||||
expect(target_tag.reload.post_count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'keeps source history and records the new target alias after deleting the source' do
|
||||
user = create_member_user!
|
||||
source_name = source_tag.name
|
||||
source_alias = TagName.create!(name: 'merge_alias', canonical: source_tag_name)
|
||||
source_name = primary_tag_name_for(source_tag, 'ja').name
|
||||
source_alias = create(:tag_name, :alias, name: 'merge_alias', tag: source_tag)
|
||||
TagVersioning.ensure_snapshot!(source_tag, created_by_user: user)
|
||||
original_version = source_tag.tag_versions.first
|
||||
|
||||
@@ -287,13 +535,17 @@ RSpec.describe Tag, type: :model do
|
||||
expect(versions.pluck(:version_no, :event_type))
|
||||
.to eq([[1, 'create'], [2, 'discard']])
|
||||
expect(versions.first).to eq(original_version)
|
||||
expect(versions.last).to have_attributes(
|
||||
name: source_name, aliases: source_alias.name, created_by_user: user)
|
||||
expect(versions.last.created_by_user).to eq(user)
|
||||
expect(versions.last.names.fetch('ja')).to eq(
|
||||
'name' => source_name, 'script_code' => 'Jpan',
|
||||
'auto_generated' => false,
|
||||
'aliases' => [{ 'name' => source_alias.name, 'script_code' => 'Jpan' }])
|
||||
expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
|
||||
|
||||
target_versions = target_tag.tag_versions.order(:version_no)
|
||||
expect(target_versions.pluck(:event_type)).to eq(['create', 'update'])
|
||||
expect(target_versions.last.aliases.split).to eq([source_name])
|
||||
target_aliases = target_versions.last.names.fetch('ja').fetch('aliases').pluck('name')
|
||||
expect(target_aliases).to contain_exactly('merge_alias', source_name)
|
||||
end
|
||||
|
||||
it 'deletes source relationships while preserving unrelated relationships' do
|
||||
@@ -347,7 +599,8 @@ RSpec.describe Tag, type: :model do
|
||||
expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
|
||||
expect(PostTag.exists?(post: post_record, tag: source_tag)).to be(false)
|
||||
expect(target_link).to be_present
|
||||
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||
expect(source_tag_name.reload).to have_attributes(
|
||||
tag_id: target_tag.id, language_code: 'ja', primary_flg: false)
|
||||
expect(target_tag.reload.post_count).to eq(1)
|
||||
end
|
||||
end
|
||||
@@ -378,7 +631,8 @@ RSpec.describe Tag, type: :model do
|
||||
expect(target_link).to be_present
|
||||
|
||||
expect(Tag.unscoped.exists?(source_tag.id)).to be(false)
|
||||
expect(source_tag_name.reload.canonical_id).to eq(target_tag.tag_name_id)
|
||||
expect(source_tag_name.reload).to have_attributes(
|
||||
tag_id: target_tag.id, language_code: 'ja', primary_flg: false)
|
||||
expect(target_tag.reload.post_count).to eq(1)
|
||||
end
|
||||
end
|
||||
@@ -397,7 +651,7 @@ RSpec.describe Tag, type: :model do
|
||||
|
||||
it 'rolls back earlier deletions, links, and history when a later source has a wiki' do
|
||||
earlier_source = create(:tag)
|
||||
earlier_name = earlier_source.tag_name
|
||||
earlier_name = primary_tag_name_for(earlier_source, 'ja')
|
||||
source_section = create(:post_tag_section, post: post_record,
|
||||
tag: source_tag,
|
||||
begin_ms: 1000, end_ms: 2000)
|
||||
@@ -407,7 +661,8 @@ RSpec.describe Tag, type: :model do
|
||||
}.to raise_error(ActiveRecord::RecordInvalid)
|
||||
|
||||
expect(Tag.unscoped.exists?(earlier_source.id)).to be(true)
|
||||
expect(earlier_name.reload.canonical_id).to be_nil
|
||||
expect(earlier_name.reload).to have_attributes(
|
||||
tag_id: earlier_source.id, language_code: 'ja', primary_flg: true)
|
||||
expect(TagVersion.where(tag_id: [earlier_source.id, source_tag.id, target_tag.id]))
|
||||
.to be_empty
|
||||
expect(Tag.unscoped.exists?(source_tag.id)).to be(true)
|
||||
@@ -416,7 +671,8 @@ RSpec.describe Tag, type: :model do
|
||||
expect(source_post_tag.sections).to contain_exactly(source_section)
|
||||
expect(PostTag.find_by(post: post_record, tag: target_tag)).to be_nil
|
||||
expect(source_tag.reload.post_count).to eq(1)
|
||||
expect(source_tag_name.reload.canonical_id).to be_nil
|
||||
expect(source_tag_name.reload).to have_attributes(
|
||||
tag_id: source_tag.id, language_code: 'ja', primary_flg: true)
|
||||
expect(target_tag.reload.post_count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe VersionRecord, type: :model do
|
||||
let!(:tag) { create(:tag, name: 'version_record_tag') }
|
||||
let!(:tag) { create(:tag, primary_name: 'version_record_tag') }
|
||||
let!(:nico_tag) { create(:external_tag, name: 'version_record_tag') }
|
||||
|
||||
it 'makes TagVersion read only after create' do
|
||||
@@ -9,13 +9,14 @@ RSpec.describe VersionRecord, type: :model do
|
||||
tag: tag,
|
||||
version_no: 1,
|
||||
event_type: 'create',
|
||||
name: tag.name,
|
||||
name: primary_tag_name_for(tag, 'ja').name,
|
||||
category: tag.category,
|
||||
aliases: '',
|
||||
names: { ja: { name: primary_tag_name_for(tag, 'ja').name,
|
||||
script_code: 'Jpan', auto_generated: false, aliases: [] } },
|
||||
parent_tag_ids: '',
|
||||
created_at: Time.current,
|
||||
created_by_user: nil
|
||||
)
|
||||
created_by_user: nil)
|
||||
|
||||
expect {
|
||||
version.update!(name: 'changed')
|
||||
@@ -27,13 +28,14 @@ RSpec.describe VersionRecord, type: :model do
|
||||
tag: tag,
|
||||
version_no: 1,
|
||||
event_type: 'create',
|
||||
name: tag.name,
|
||||
name: primary_tag_name_for(tag, 'ja').name,
|
||||
category: tag.category,
|
||||
aliases: '',
|
||||
names: { ja: { name: primary_tag_name_for(tag, 'ja').name,
|
||||
script_code: 'Jpan', auto_generated: false, aliases: [] } },
|
||||
parent_tag_ids: '',
|
||||
created_at: Time.current,
|
||||
created_by_user: nil
|
||||
)
|
||||
created_by_user: nil)
|
||||
|
||||
expect {
|
||||
version.destroy!
|
||||
|
||||
@@ -30,7 +30,7 @@ RSpec.describe 'Deerjikists API', type: :request do
|
||||
|
||||
expect(json['tag']).to be_a(Hash)
|
||||
expect(json['tag']['id']).to eq(tag1.id)
|
||||
expect(json['tag']['name']).to eq(tag1.name)
|
||||
expect(json['tag']['name']).to eq(primary_tag_name_for(tag1, 'ja').name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ RSpec.describe 'error responses', type: :request do
|
||||
|
||||
it 'returns a stable field-error payload for unprocessable requests' do
|
||||
member = create(:user, :member)
|
||||
tag = create(:tag, :general, name: 'error_response_tag')
|
||||
tag = create(:tag, category: :general, primary_name: 'error_response_tag')
|
||||
sign_in_as(member)
|
||||
|
||||
patch "/tags/#{ tag.id }", params: { category: 'nico' }
|
||||
|
||||
@@ -898,23 +898,22 @@ RSpec.describe 'Gekanator learning API', type: :request do
|
||||
|
||||
describe 'GET /gekanator/questions' do
|
||||
it 'omits questions for deprecated tags' do
|
||||
active_tag = Tag.create!(name: 'active_question_tag', category: :general)
|
||||
deprecated_tag = Tag.create!(
|
||||
name: 'deprecated_question_tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
active_tag = create(:tag, primary_name: 'active_question_tag', category: :general)
|
||||
deprecated_tag = create(:tag,
|
||||
primary_name: 'deprecated_question_tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current)
|
||||
|
||||
[active_tag, deprecated_tag].each do |question_tag|
|
||||
GekanatorQuestion.create!(
|
||||
text: "#{ question_tag.name }?",
|
||||
text: "#{ primary_tag_name_for(question_tag, 'ja').name }?",
|
||||
kind: 'tag',
|
||||
source: 'admin_curated',
|
||||
status: 'accepted',
|
||||
priority_weight: 1.0,
|
||||
condition: {
|
||||
type: 'tag',
|
||||
key: "#{ question_tag.category }:#{ question_tag.name }"
|
||||
key: "#{ question_tag.category }:#{ primary_tag_name_for(question_tag, 'ja').name }"
|
||||
},
|
||||
created_by: admin
|
||||
)
|
||||
|
||||
@@ -4,12 +4,11 @@ require 'rails_helper'
|
||||
RSpec.describe 'Gekanator posts API', type: :request do
|
||||
describe 'GET /gekanator/posts' do
|
||||
it 'omits deprecated tags and returns the stored similarity cosine' do
|
||||
active_tag = Tag.create!(name: 'active tag', category: :general)
|
||||
deprecated_tag = Tag.create!(
|
||||
name: 'deprecated tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
active_tag = create(:tag, primary_name: 'active tag', category: :general)
|
||||
deprecated_tag = create(:tag,
|
||||
primary_name: 'deprecated tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current)
|
||||
post_record = Post.create!(title: 'source', url: 'https://example.com/source')
|
||||
target_post = Post.create!(title: 'target', url: 'https://example.com/target')
|
||||
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
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
|
||||
@@ -26,10 +26,10 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
describe 'GET /materials' do
|
||||
let!(:tag_a) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_index_a'), category: :material)
|
||||
create(:tag, primary_name: 'material_index_a', category: :material)
|
||||
end
|
||||
let!(:tag_b) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_index_b'), category: :material)
|
||||
create(:tag, primary_name: 'material_index_b', category: :material)
|
||||
end
|
||||
|
||||
let!(:material_a) do
|
||||
@@ -116,20 +116,20 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
it 'filters by descendant tags and returns stable parent tag groups' do
|
||||
root =
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_scope_root'),
|
||||
create(:tag, primary_name: 'material_scope_root',
|
||||
category: :material)
|
||||
child_b =
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_scope_b'),
|
||||
create(:tag, primary_name: 'material_scope_b',
|
||||
category: :material)
|
||||
child_a =
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_scope_a'),
|
||||
create(:tag, primary_name: 'material_scope_a',
|
||||
category: :material)
|
||||
deprecated =
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_scope_old'),
|
||||
create(:tag, primary_name: 'material_scope_old',
|
||||
category: :material,
|
||||
deprecated_at: Time.current)
|
||||
grandchild =
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_scope_grandchild'),
|
||||
create(:tag, primary_name: 'material_scope_grandchild',
|
||||
category: :material)
|
||||
root_material =
|
||||
build_material(tag: root, user: member_user,
|
||||
@@ -179,7 +179,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
describe 'GET /materials/:id' do
|
||||
let!(:tag) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_show'), category: :material)
|
||||
create(:tag, primary_name: 'material_show', category: :material)
|
||||
end
|
||||
let!(:material) do
|
||||
build_material(tag:, user: member_user, file: dummy_upload(filename: 'show.png'))
|
||||
@@ -269,7 +269,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
expect(response).to have_http_status(:created)
|
||||
|
||||
material = Material.order(:id).last
|
||||
expect(material.tag.name).to eq('material_create_new')
|
||||
expect(primary_tag_name_for(material.tag, 'ja').name).to eq('material_create_new')
|
||||
expect(material.tag.category).to eq('material')
|
||||
expect(material.created_by_user).to eq(member_user)
|
||||
expect(material.updated_by_user).to eq(member_user)
|
||||
@@ -297,7 +297,8 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
|
||||
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: 'material_create_versioned_tag' })
|
||||
tag = TagName.find_by!(name: 'material_create_versioned_tag',
|
||||
language_code: 'ja', primary_flg: true).tag
|
||||
version = tag.tag_versions.order(:version_no).last
|
||||
|
||||
expect(version.event_type).to eq('create')
|
||||
@@ -323,8 +324,8 @@ RSpec.describe 'Materials API', type: :request do
|
||||
end
|
||||
|
||||
it 'returns 422 when the existing tag is not material/character' do
|
||||
general_tag_name = TagName.create!(name: 'material_create_general_tag')
|
||||
Tag.create!(tag_name: general_tag_name, category: :general)
|
||||
general_tag_name = create(:tag_name, name: 'material_create_general_tag')
|
||||
create(:tag, primary_tag_name: general_tag_name, category: :general)
|
||||
|
||||
post '/materials', params: {
|
||||
tag: 'material_create_general_tag',
|
||||
@@ -345,7 +346,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
expect(response).to have_http_status(:created)
|
||||
|
||||
material = Material.order(:id).last
|
||||
expect(material.tag.name).to eq('material_create_url_only')
|
||||
expect(primary_tag_name_for(material.tag, 'ja').name).to eq('material_create_url_only')
|
||||
expect(material.url).to eq('https://example.com/material-source')
|
||||
expect(material.file.attached?).to be(false)
|
||||
end
|
||||
@@ -384,7 +385,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
describe 'PUT /materials/:id' do
|
||||
let!(:tag) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_update_old'), category: :material)
|
||||
create(:tag, primary_name: 'material_update_old', category: :material)
|
||||
end
|
||||
let!(:material) do
|
||||
build_material(tag:, user: member_user, file: dummy_upload(filename: 'old.png'))
|
||||
@@ -463,7 +464,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
material.reload
|
||||
expect(material.tag.name).to eq('material_update_new')
|
||||
expect(primary_tag_name_for(material.tag, 'ja').name).to eq('material_update_new')
|
||||
expect(material.tag.category).to eq('material')
|
||||
expect(material.url).to eq('https://example.com/updated-source')
|
||||
expect(material.updated_by_user).to eq(member_user)
|
||||
@@ -497,7 +498,8 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: 'material_update_versioned_tag' })
|
||||
tag = TagName.find_by!(name: 'material_update_versioned_tag',
|
||||
language_code: 'ja', primary_flg: true).tag
|
||||
version = tag.tag_versions.order(:version_no).last
|
||||
|
||||
expect(version.event_type).to eq('create')
|
||||
@@ -508,7 +510,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
it 'backfills a create tag_version for an existing material tag without history' do
|
||||
existing_tag =
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_update_existing_no_history'),
|
||||
create(:tag, primary_name: 'material_update_existing_no_history',
|
||||
category: :material)
|
||||
|
||||
expect(existing_tag.tag_versions).to be_empty
|
||||
@@ -531,7 +533,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
it 'backfills a create tag_version for an existing character tag without history' do
|
||||
existing_tag =
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_update_character_no_history'),
|
||||
create(:tag, primary_name: 'material_update_character_no_history',
|
||||
category: :character)
|
||||
|
||||
expect(existing_tag.tag_versions).to be_empty
|
||||
@@ -563,7 +565,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
material.reload
|
||||
expect(material.tag.name).to eq('material_update_remove_file')
|
||||
expect(primary_tag_name_for(material.tag, 'ja').name).to eq('material_update_remove_file')
|
||||
expect(material.url).to eq('https://example.com/updated-source')
|
||||
expect(material.updated_by_user).to eq(member_user)
|
||||
expect(material.file.attached?).to be(false)
|
||||
@@ -651,8 +653,8 @@ RSpec.describe 'Materials API', type: :request do
|
||||
end
|
||||
|
||||
describe 'GET /materials/download.zip' do
|
||||
let!(:tag_a) { Tag.create!(tag_name: TagName.create!(name: 'zip_a'), category: :material) }
|
||||
let!(:tag_b) { Tag.create!(tag_name: TagName.create!(name: 'zip_b'), category: :material) }
|
||||
let!(:tag_a) { create(:tag, primary_name: 'zip_a', category: :material) }
|
||||
let!(:tag_b) { create(:tag, primary_name: 'zip_b', category: :material) }
|
||||
let!(:material_a) do
|
||||
build_material(tag: tag_a, user: member_user,
|
||||
file: dummy_upload(filename: 'a.png', body: 'zip-a'))
|
||||
@@ -701,7 +703,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
describe 'GET /materials/versions' do
|
||||
let!(:tag) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_history'), category: :material)
|
||||
create(:tag, primary_name: 'material_history', category: :material)
|
||||
end
|
||||
let!(:material) do
|
||||
build_material(tag:, user: member_user, file: dummy_upload(filename: 'history.png'))
|
||||
@@ -776,7 +778,7 @@ RSpec.describe 'Materials API', type: :request do
|
||||
|
||||
describe 'DELETE /materials/:id' do
|
||||
let!(:tag) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'material_destroy'), category: :material)
|
||||
create(:tag, primary_name: 'material_destroy', category: :material)
|
||||
end
|
||||
let!(:material) do
|
||||
build_material(tag:, user: member_user, file: dummy_upload(filename: 'destroy.png'))
|
||||
|
||||
@@ -44,10 +44,10 @@ RSpec.describe 'NicoTags', type: :request do
|
||||
unlinked.update!(name: 'search_unlinked')
|
||||
other = create(:external_tag)
|
||||
other.update!(name: 'other')
|
||||
destination = create(:tag, :general)
|
||||
destination.tag_name.update!(name: 'destination_search')
|
||||
destination = create(:tag, category: :general)
|
||||
primary_tag_name_for(destination, 'ja').update!(name: 'destination_search')
|
||||
NicoTagRelation.create!(nico_tag: linked, tag: destination)
|
||||
NicoTagRelation.create!(nico_tag: other, tag: create(:tag, :general))
|
||||
NicoTagRelation.create!(nico_tag: other, tag: create(:tag, category: :general))
|
||||
|
||||
get '/tags/nico', params: {
|
||||
name: 'search_',
|
||||
@@ -151,7 +151,7 @@ RSpec.describe 'NicoTags', type: :request do
|
||||
|
||||
it '404 when only an internal tag exists for the target id' do
|
||||
sign_in_as(member)
|
||||
non_nico = create(:tag, :general)
|
||||
non_nico = create(:tag, category: :general)
|
||||
expect(ExternalTag.exists?(non_nico.id)).to be(false)
|
||||
patch "/tags/nico/#{non_nico.id}", params: { tags: 'a b' }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
@@ -162,18 +162,18 @@ RSpec.describe 'NicoTags', type: :request do
|
||||
|
||||
nico_tag = create(:external_tag, name: 'nico_tags_spec_source')
|
||||
|
||||
linked_a_name = TagName.create!(name: 'nico_linked_a')
|
||||
linked_a = Tag.create!(tag_name: linked_a_name, category: :general)
|
||||
linked_a_name = create(:tag_name, name: 'nico_linked_a')
|
||||
linked_a = create(:tag, primary_tag_name: linked_a_name, category: :general)
|
||||
|
||||
linked_b_name = TagName.create!(name: 'nico_linked_b')
|
||||
linked_b = Tag.create!(tag_name: linked_b_name, category: :general)
|
||||
linked_b_name = create(:tag_name, name: 'nico_linked_b')
|
||||
linked_b = create(:tag, primary_tag_name: linked_b_name, category: :general)
|
||||
|
||||
NicoTagVersionRecorder.record!(external_tag: nico_tag,
|
||||
event_type: :create, created_by_user: admin)
|
||||
|
||||
expect {
|
||||
patch "/tags/nico/#{nico_tag.id}", params: {
|
||||
tags: " #{linked_a.name}\n#{linked_b.name} "
|
||||
tags: " #{ linked_a_name.name }\n#{ linked_b_name.name } "
|
||||
}
|
||||
}.to change(TagVersion, :count).by(2)
|
||||
.and change(NicoTagVersion, :count).by(1)
|
||||
|
||||
@@ -113,8 +113,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
'sections' => [])
|
||||
end
|
||||
|
||||
let!(:tag_name) { TagName.create!(name: 'spec_tag') }
|
||||
let!(:tag) { Tag.create!(tag_name: tag_name, category: :general) }
|
||||
let!(:tag_name) { create(:tag_name, name: 'spec_tag') }
|
||||
let!(:tag) { create(:tag, primary_tag_name: tag_name, category: :general) }
|
||||
|
||||
let!(:post_record) do
|
||||
Post.create!(title: 'spec post', url: 'https://example.com/spec').tap do |p|
|
||||
@@ -125,11 +125,11 @@ RSpec.describe 'Posts API', type: :request do
|
||||
describe 'GET /posts' do
|
||||
let!(:user) { create_member_user! }
|
||||
|
||||
let!(:tag_name) { TagName.create!(name: 'spec_tag') }
|
||||
let!(:tag) { Tag.create!(tag_name:, category: :general) }
|
||||
let!(:tag_name2) { TagName.create!(name: 'unko') }
|
||||
let!(:tag2) { Tag.create!(tag_name: tag_name2, category: :deerjikist) }
|
||||
let!(:alias_tag_name) { TagName.create!(name: 'manko', canonical: tag_name) }
|
||||
let!(:tag_name) { create(:tag_name, name: 'spec_tag') }
|
||||
let!(:tag) { create(:tag, primary_tag_name: tag_name, category: :general) }
|
||||
let!(:tag_name2) { create(:tag_name, name: 'unko') }
|
||||
let!(:tag2) { create(:tag, primary_tag_name: tag_name2, category: :deerjikist) }
|
||||
let!(:alias_tag_name) { create(:tag_name, :alias, name: 'manko', tag:) }
|
||||
|
||||
let!(:hit_post) do
|
||||
Post.create!(uploaded_user: user, title: 'hello spec world',
|
||||
@@ -267,7 +267,7 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
it 'keeps internal name searches independent of colliding external ids' do
|
||||
get '/posts', params: { tags: tag.name }
|
||||
get '/posts', params: { tags: primary_tag_name_for(tag, 'ja').name }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('count')).to eq(3)
|
||||
@@ -286,7 +286,7 @@ RSpec.describe 'Posts API', type: :request do
|
||||
|
||||
[nil, 'all'].each do |match|
|
||||
it "intersects internal and external matches with match=#{ match || 'omitted' }" do
|
||||
params = { tags: "#{ tag.name } nico:search_external" }
|
||||
params = { tags: "#{ primary_tag_name_for(tag, 'ja').name } nico:search_external" }
|
||||
params[:match] = match if match
|
||||
|
||||
get '/posts', params: params
|
||||
@@ -307,7 +307,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
it 'excludes external matches from an internal tag search' do
|
||||
get '/posts', params: { tags: "#{ tag.name } not:nico:search_external" }
|
||||
get '/posts', params: {
|
||||
tags: "#{ primary_tag_name_for(tag, 'ja').name } not:nico:search_external" }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('count')).to eq(2)
|
||||
@@ -316,7 +317,9 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
it 'unions an external match with a negated internal match' do
|
||||
get '/posts', params: { tags: "nico:search_external not:#{ tag.name }", match: 'any' }
|
||||
get '/posts', params: {
|
||||
tags: "nico:search_external not:#{ primary_tag_name_for(tag, 'ja').name }",
|
||||
match: 'any' }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('count')).to eq(2)
|
||||
@@ -333,7 +336,9 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
it 'applies the same mixed name search to the existing random endpoint' do
|
||||
get '/posts/random', params: { tags: "#{ tag.name } nico:search_external", match: 'all' }
|
||||
get '/posts/random', params: {
|
||||
tags: "#{ primary_tag_name_for(tag, 'ja').name } nico:search_external",
|
||||
match: 'all' }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('id')).to eq(both_post.id)
|
||||
@@ -341,17 +346,17 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
context 'when tags contain not:' do
|
||||
let!(:foo_tag_name) { TagName.create!(name: 'not_spec_foo') }
|
||||
let!(:foo_tag) { Tag.create!(tag_name: foo_tag_name, category: :general) }
|
||||
let!(:foo_tag_name) { create(:tag_name, name: 'not_spec_foo') }
|
||||
let!(:foo_tag) { create(:tag, primary_tag_name: foo_tag_name, category: :general) }
|
||||
|
||||
let!(:bar_tag_name) { TagName.create!(name: 'not_spec_bar') }
|
||||
let!(:bar_tag) { Tag.create!(tag_name: bar_tag_name, category: :general) }
|
||||
let!(:bar_tag_name) { create(:tag_name, name: 'not_spec_bar') }
|
||||
let!(:bar_tag) { create(:tag, primary_tag_name: bar_tag_name, category: :general) }
|
||||
|
||||
let!(:baz_tag_name) { TagName.create!(name: 'not_spec_baz') }
|
||||
let!(:baz_tag) { Tag.create!(tag_name: baz_tag_name, category: :general) }
|
||||
let!(:baz_tag_name) { create(:tag_name, name: 'not_spec_baz') }
|
||||
let!(:baz_tag) { create(:tag, primary_tag_name: baz_tag_name, category: :general) }
|
||||
|
||||
let!(:foo_alias_tag_name) do
|
||||
TagName.create!(name: 'not_spec_foo_alias', canonical: foo_tag_name)
|
||||
create(:tag_name, :alias, name: 'not_spec_foo_alias', tag: foo_tag)
|
||||
end
|
||||
|
||||
let!(:foo_only_post) do
|
||||
@@ -739,7 +744,9 @@ RSpec.describe 'Posts API', type: :request do
|
||||
.find { _1['name'] == 'nico:post_index_external' }
|
||||
|
||||
expect(post_json.fetch('tags')).to include(
|
||||
a_hash_including('id' => tag.id, 'name' => tag.name, 'category' => 'general'))
|
||||
a_hash_including('id' => tag.id,
|
||||
'name' => primary_tag_name_for(tag, 'ja').name,
|
||||
'category' => 'general'))
|
||||
expect(external_json).not_to be_nil
|
||||
expect_external_tag_json(external_json, external_tag)
|
||||
end
|
||||
@@ -772,11 +779,10 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
it 'omits deprecated tags' do
|
||||
deprecated_tag = Tag.create!(
|
||||
name: 'deprecated_post_tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
deprecated_tag = create(:tag,
|
||||
primary_name: 'deprecated_post_tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current)
|
||||
PostTag.create!(post: post_record, tag: deprecated_tag)
|
||||
|
||||
request
|
||||
@@ -853,9 +859,9 @@ RSpec.describe 'Posts API', type: :request do
|
||||
|
||||
tags =
|
||||
15.times.map do |i|
|
||||
tag_name = TagName.create!(name: "show_query_tag_#{ i }")
|
||||
tag = Tag.create!(tag_name:, category: :general)
|
||||
TagName.create!(name: "show_query_alias_#{ i }", canonical: tag_name)
|
||||
tag_name = create(:tag_name, name: "show_query_tag_#{ i }")
|
||||
tag = create(:tag, primary_tag_name: tag_name, category: :general)
|
||||
create(:tag_name, :alias, name: "show_query_alias_#{ i }", tag:)
|
||||
PostTag.create!(post: post_record, tag:)
|
||||
tag
|
||||
end
|
||||
@@ -910,7 +916,9 @@ RSpec.describe 'Posts API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
expect(json.fetch('tags')).to include(
|
||||
a_hash_including('id' => tag.id, 'name' => tag.name, 'category' => 'general'))
|
||||
a_hash_including('id' => tag.id,
|
||||
'name' => primary_tag_name_for(tag, 'ja').name,
|
||||
'category' => 'general'))
|
||||
external_json =
|
||||
json
|
||||
.fetch('tags')
|
||||
@@ -1000,7 +1008,7 @@ RSpec.describe 'Posts API', type: :request do
|
||||
|
||||
describe 'POST /posts' do
|
||||
let(:member) { create(:user, :member) }
|
||||
let!(:alias_tag_name) { TagName.create!(name: 'manko', canonical: tag_name) }
|
||||
let!(:alias_tag_name) { create(:tag_name, :alias, name: 'manko', tag:) }
|
||||
|
||||
it '401 when not logged in' do
|
||||
sign_out
|
||||
@@ -1157,11 +1165,10 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
it 'rejects a deprecated tag specified directly' do
|
||||
Tag.create!(
|
||||
name: 'deprecated_direct_tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
create(:tag,
|
||||
primary_name: 'deprecated_direct_tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current)
|
||||
sign_in_as(member)
|
||||
|
||||
post '/posts', params: post_write_params(
|
||||
@@ -1178,18 +1185,16 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
it 'expands through multiple deprecated parent tags and saves active ancestors' do
|
||||
child = Tag.create!(name: 'active_child', category: :general)
|
||||
deprecated_parent = Tag.create!(
|
||||
name: 'deprecated_parent',
|
||||
category: :general,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
deprecated_grandparent = Tag.create!(
|
||||
name: 'deprecated_grandparent',
|
||||
category: :general,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
active_grandparent = Tag.create!(name: 'active_grandparent', category: :general)
|
||||
child = create(:tag, primary_name: 'active_child', category: :general)
|
||||
deprecated_parent = create(:tag,
|
||||
primary_name: 'deprecated_parent',
|
||||
category: :general,
|
||||
deprecated_at: Time.current)
|
||||
deprecated_grandparent = create(:tag,
|
||||
primary_name: 'deprecated_grandparent',
|
||||
category: :general,
|
||||
deprecated_at: Time.current)
|
||||
active_grandparent = create(:tag, primary_name: 'active_grandparent', category: :general)
|
||||
TagImplication.create!(tag: child, parent_tag: deprecated_parent)
|
||||
TagImplication.create!(tag: deprecated_parent, parent_tag: deprecated_grandparent)
|
||||
TagImplication.create!(tag: deprecated_grandparent, parent_tag: active_grandparent)
|
||||
@@ -1203,7 +1208,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
)
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
saved_names = Post.find(json.fetch('id')).tags.map(&:name)
|
||||
saved_names = TagName.where(tag_id: Post.find(json.fetch('id')).tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name)
|
||||
expect(saved_names).to include('active_child', 'active_grandparent')
|
||||
expect(saved_names).not_to include('deprecated_parent', 'deprecated_grandparent')
|
||||
end
|
||||
@@ -1306,7 +1312,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
expect(response).to have_http_status(:created)
|
||||
|
||||
created_post = Post.find(json.fetch('id'))
|
||||
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: '伊地知ニジカ' })
|
||||
tag = TagName.find_by!(name: '伊地知ニジカ',
|
||||
language_code: 'ja', primary_flg: true).tag
|
||||
section = PostTagSection.find_by!(post: created_post, tag:)
|
||||
|
||||
expect(section.begin_ms).to eq(60_000)
|
||||
@@ -1326,7 +1333,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
expect(response).to have_http_status(:created)
|
||||
|
||||
created_post = Post.find(json.fetch('id'))
|
||||
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: '伊地知ニジカ' })
|
||||
tag = TagName.find_by!(name: '伊地知ニジカ',
|
||||
language_code: 'ja', primary_flg: true).tag
|
||||
expect(PostTagSection.find_by(post: created_post, tag:)).to be_nil
|
||||
end
|
||||
|
||||
@@ -1343,7 +1351,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
expect(response).to have_http_status(:created)
|
||||
|
||||
created_post = Post.find(json.fetch('id'))
|
||||
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: '伊地知ニジカ' })
|
||||
tag = TagName.find_by!(name: '伊地知ニジカ',
|
||||
language_code: 'ja', primary_flg: true).tag
|
||||
expect(PostTagSection.find_by(post: created_post, tag:)).to be_nil
|
||||
end
|
||||
|
||||
@@ -1622,6 +1631,7 @@ RSpec.describe 'Posts API', type: :request do
|
||||
expect(arguments[:thumbnails].keys).to eq([0])
|
||||
expect(arguments[:host]).to eq('http://www.example.com')
|
||||
end
|
||||
expect(creator).to have_received(:run).with(Locale.nipponese)
|
||||
end
|
||||
|
||||
it 'rejects malformed manifests as a request-level error' do
|
||||
@@ -1658,8 +1668,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
create(:post_tag_section, post: post_record, tag:,
|
||||
begin_ms: 1000, end_ms: 2000)
|
||||
|
||||
tn2 = TagName.create!(name: 'spec_tag_2')
|
||||
replacement_tag = Tag.create!(tag_name: tn2, category: :general)
|
||||
tn2 = create(:tag_name, name: 'spec_tag_2')
|
||||
replacement_tag = create(:tag, primary_tag_name: tn2, category: :general)
|
||||
|
||||
put "/posts/#{post_record.id}", params: post_update_params(
|
||||
post_record,
|
||||
@@ -1707,11 +1717,10 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
it 'rejects a deprecated tag specified directly' do
|
||||
Tag.create!(
|
||||
name: 'deprecated_update_tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
create(:tag,
|
||||
primary_name: 'deprecated_update_tag',
|
||||
category: :general,
|
||||
deprecated_at: Time.current)
|
||||
sign_in_as(member)
|
||||
|
||||
put "/posts/#{ post_record.id }", params: post_update_params(
|
||||
@@ -1966,7 +1975,7 @@ RSpec.describe 'Posts API', type: :request do
|
||||
put "/posts/#{post_record.id}", params: post_write_params(
|
||||
base_version_no: base_version.version_no,
|
||||
title: 'updated by me',
|
||||
tags: "spec_tag #{Tag.no_deerjikist.name}")
|
||||
tags: "spec_tag #{primary_tag_name_for(Tag.no_deerjikist, 'ja').name}")
|
||||
|
||||
expect(response).to have_http_status(:conflict)
|
||||
|
||||
@@ -1986,7 +1995,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
|
||||
base_version = create_post_version_for!(post_record.reload)
|
||||
|
||||
current_tag = Tag.find_or_create_by_tag_name!('current_added_tag', category: :general)
|
||||
current_tag = Tag.find_or_create_by_tag_name!(
|
||||
Locale.nipponese, 'current_added_tag', category: :general)
|
||||
PostTag.create!(post: post_record, tag: current_tag, created_user: member)
|
||||
|
||||
PostVersionRecorder.record!(
|
||||
@@ -1997,7 +2007,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
put "/posts/#{post_record.id}", params: post_write_params(
|
||||
base_version_no: base_version.version_no,
|
||||
title: post_record.title,
|
||||
tags: "spec_tag #{Tag.no_deerjikist.name} incoming_added_tag")
|
||||
tags: ['spec_tag', primary_tag_name_for(Tag.no_deerjikist, 'ja').name,
|
||||
'incoming_added_tag'].join(' '))
|
||||
|
||||
expect(response).to have_http_status(:conflict)
|
||||
|
||||
@@ -2015,7 +2026,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
|
||||
base_version = create_post_version_for!(post_record.reload)
|
||||
|
||||
current_tag = Tag.find_or_create_by_tag_name!('current_merge_tag', category: :general)
|
||||
current_tag = Tag.find_or_create_by_tag_name!(
|
||||
Locale.nipponese, 'current_merge_tag', category: :general)
|
||||
PostTag.create!(post: post_record, tag: current_tag, created_user: member)
|
||||
|
||||
PostVersionRecorder.record!(
|
||||
@@ -2026,15 +2038,17 @@ RSpec.describe 'Posts API', type: :request do
|
||||
put "/posts/#{post_record.id}", params: post_write_params(
|
||||
base_version_no: base_version.version_no,
|
||||
title: post_record.title,
|
||||
tags: "spec_tag #{Tag.no_deerjikist.name} incoming_merge_tag",
|
||||
tags: ['spec_tag', primary_tag_name_for(Tag.no_deerjikist, 'ja').name,
|
||||
'incoming_merge_tag'].join(' '),
|
||||
merge: '1')
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
names = post_record.reload.tags.map(&:name)
|
||||
names = TagName.where(tag_id: post_record.reload.tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name)
|
||||
|
||||
expect(names).to include('spec_tag')
|
||||
expect(names).to include(Tag.no_deerjikist.name)
|
||||
expect(names).to include(primary_tag_name_for(Tag.no_deerjikist, 'ja').name)
|
||||
expect(names).to include('current_merge_tag')
|
||||
expect(names).to include('incoming_merge_tag')
|
||||
end
|
||||
@@ -2057,14 +2071,15 @@ RSpec.describe 'Posts API', type: :request do
|
||||
put "/posts/#{post_record.id}", params: post_write_params(
|
||||
base_version_no: base_version.version_no,
|
||||
title: post_record.title,
|
||||
tags: "spec_tag #{ Tag.no_deerjikist.name }")
|
||||
tags: "spec_tag #{ primary_tag_name_for(Tag.no_deerjikist, 'ja').name }")
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
names = post_record.reload.tags.map(&:name)
|
||||
names = TagName.where(tag_id: post_record.reload.tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name)
|
||||
|
||||
expect(names).to include('spec_tag')
|
||||
expect(names).to include(Tag.no_deerjikist.name)
|
||||
expect(names).to include(primary_tag_name_for(Tag.no_deerjikist, 'ja').name)
|
||||
expect(post_record.external_tags).to contain_exactly(nico_tag)
|
||||
end
|
||||
|
||||
@@ -2079,14 +2094,15 @@ RSpec.describe 'Posts API', type: :request do
|
||||
put "/posts/#{post_record.id}", params: post_write_params(
|
||||
base_version_no: base_version.version_no,
|
||||
title: 'updated title',
|
||||
tags: "spec_tag #{ Tag.no_deerjikist.name }")
|
||||
tags: "spec_tag #{ primary_tag_name_for(Tag.no_deerjikist, 'ja').name }")
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
names = post_record.reload.tags.map(&:name)
|
||||
names = TagName.where(tag_id: post_record.reload.tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name)
|
||||
|
||||
expect(names).to include('spec_tag')
|
||||
expect(names).to include(Tag.no_deerjikist.name)
|
||||
expect(names).to include(primary_tag_name_for(Tag.no_deerjikist, 'ja').name)
|
||||
expect(post_record.external_tags).to contain_exactly(nico_tag)
|
||||
|
||||
external_json =
|
||||
@@ -2102,7 +2118,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
sign_in_as(member)
|
||||
|
||||
nico_tag = create_nico_tag!('nico:relation_source')
|
||||
linked_tag = Tag.find_or_create_by_tag_name!('relation_linked_tag', category: :general)
|
||||
linked_tag = Tag.find_or_create_by_tag_name!(
|
||||
Locale.nipponese, 'relation_linked_tag', category: :general)
|
||||
|
||||
NicoTagRelation.create!(nico_tag:, tag: linked_tag)
|
||||
PostExternalTag.create!(post: post_record, external_tag: nico_tag)
|
||||
@@ -2113,16 +2130,17 @@ RSpec.describe 'Posts API', type: :request do
|
||||
put "/posts/#{post_record.id}", params: post_write_params(
|
||||
base_version_no: base_version.version_no,
|
||||
title: post_record.title,
|
||||
tags: "spec_tag #{ Tag.no_deerjikist.name }")
|
||||
tags: "spec_tag #{ primary_tag_name_for(Tag.no_deerjikist, 'ja').name }")
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
names = post_record.reload.tags.map(&:name)
|
||||
names = TagName.where(tag_id: post_record.reload.tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name)
|
||||
|
||||
expect(post_record.external_tags).to contain_exactly(nico_tag)
|
||||
expect(names).to include('spec_tag')
|
||||
expect(names).to include(Tag.no_deerjikist.name)
|
||||
expect(names).not_to include(linked_tag.name)
|
||||
expect(names).to include(primary_tag_name_for(Tag.no_deerjikist, 'ja').name)
|
||||
expect(names).not_to include(primary_tag_name_for(linked_tag, 'ja').name)
|
||||
end
|
||||
|
||||
it 'force-updates stale posts without base_version_no' do
|
||||
@@ -2138,7 +2156,7 @@ RSpec.describe 'Posts API', type: :request do
|
||||
|
||||
put "/posts/#{post_record.id}", params: post_write_params(
|
||||
title: 'forced title',
|
||||
tags: "spec_tag #{Tag.no_deerjikist.name}",
|
||||
tags: "spec_tag #{primary_tag_name_for(Tag.no_deerjikist, 'ja').name}",
|
||||
force: '1')
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
@@ -2191,8 +2209,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
let(:oc_from) { Time.zone.local(2019, 12, 31, 0, 0, 0) }
|
||||
let(:oc_before) { Time.zone.local(2020, 1, 1, 0, 0, 0) }
|
||||
|
||||
let!(:tag_name2) { TagName.create!(name: 'spec_tag_2') }
|
||||
let!(:tag2) { Tag.create!(tag_name: tag_name2, category: :general) }
|
||||
let!(:tag_name2) { create(:tag_name, name: 'spec_tag_2') }
|
||||
let!(:tag2) { create(:tag, primary_tag_name: tag_name2, category: :general) }
|
||||
|
||||
def snapshot_tags(post)
|
||||
post.snapshot_tag_names.join(' ')
|
||||
@@ -2417,7 +2435,7 @@ RSpec.describe 'Posts API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('count')).to eq(3)
|
||||
expect(json.fetch('versions').first.fetch('tags')).to include(
|
||||
'name' => tag2.name, 'type' => 'context')
|
||||
'name' => primary_tag_name_for(tag2, 'ja').name, 'type' => 'context')
|
||||
end
|
||||
|
||||
it 'filters versions by tag when the current snapshot includes the tag' do
|
||||
@@ -2550,8 +2568,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
sign_in_as(member)
|
||||
base_version = create_post_version_for!(post_record)
|
||||
|
||||
tag_name2 = TagName.create!(name: 'spec_tag_2')
|
||||
Tag.create!(tag_name: tag_name2, category: :general)
|
||||
tag_name2 = create(:tag_name, name: 'spec_tag_2')
|
||||
create(:tag, primary_tag_name: tag_name2, category: :general)
|
||||
|
||||
expect do
|
||||
put "/posts/#{post_record.id}", params: post_write_params(
|
||||
@@ -2715,8 +2733,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
|
||||
base_version = create_post_version_for!(post_record.reload)
|
||||
|
||||
tag_name2 = TagName.create!(name: 'spec_tag_2')
|
||||
tag2 = Tag.create!(tag_name: tag_name2, category: :general)
|
||||
tag_name2 = create(:tag_name, name: 'spec_tag_2')
|
||||
tag2 = create(:tag, primary_tag_name: tag_name2, category: :general)
|
||||
|
||||
expect {
|
||||
put "/posts/#{post_record.id}", params: post_write_params(
|
||||
|
||||
@@ -3,13 +3,13 @@ require 'rails_helper'
|
||||
RSpec.describe 'TagVersions API', type: :request do
|
||||
let(:member) { create(:user, :member, name: 'version member') }
|
||||
|
||||
let!(:tag) { create(:tag, name: 'tag_versions_target', category: :general) }
|
||||
let!(:other_tag) { create(:tag, name: 'tag_versions_other', category: :general) }
|
||||
let!(:tag) { create(:tag, primary_name: 'tag_versions_target', category: :general) }
|
||||
let!(:other_tag) { create(:tag, primary_name: 'tag_versions_other', category: :general) }
|
||||
|
||||
let!(:parent_shared) { create(:tag, name: 'parent_shared', category: :general) }
|
||||
let!(:parent_old) { create(:tag, name: 'parent_old', category: :general) }
|
||||
let!(:parent_new) { create(:tag, name: 'parent_new', category: :general) }
|
||||
let!(:other_parent) { create(:tag, name: 'other_parent', category: :general) }
|
||||
let!(:parent_shared) { create(:tag, primary_name: 'parent_shared', category: :general) }
|
||||
let!(:parent_old) { create(:tag, primary_name: 'parent_old', category: :general) }
|
||||
let!(:parent_new) { create(:tag, primary_name: 'parent_new', category: :general) }
|
||||
let!(:other_parent) { create(:tag, primary_name: 'other_parent', category: :general) }
|
||||
|
||||
let(:t_v1) { Time.zone.local(2020, 1, 1, 12, 0, 0) }
|
||||
let(:t_v2) { Time.zone.local(2020, 1, 2, 12, 0, 0) }
|
||||
@@ -36,6 +36,10 @@ RSpec.describe 'TagVersions API', type: :request do
|
||||
category: category,
|
||||
deprecated_at: deprecated_at,
|
||||
aliases: Array(aliases).join(' '),
|
||||
names: { ja: { name:, script_code: 'Jpan', auto_generated: false,
|
||||
aliases: Array(aliases).sort.map { |alias_name|
|
||||
{ name: alias_name, script_code: 'Jpan' }
|
||||
} } },
|
||||
parent_tag_ids: Array(parent_tags).map(&:id).join(' '),
|
||||
created_by_user: created_by_user,
|
||||
created_at: created_at)
|
||||
@@ -211,7 +215,7 @@ RSpec.describe 'TagVersions API', type: :request do
|
||||
end
|
||||
|
||||
it 'returns empty when the specified tag has no versions' do
|
||||
fresh_tag = create(:tag, name: 'no_versions_tag', category: :general)
|
||||
fresh_tag = create(:tag, primary_name: 'no_versions_tag', category: :general)
|
||||
|
||||
get '/tags/versions', params: { id: fresh_tag.id }
|
||||
|
||||
@@ -232,8 +236,8 @@ RSpec.describe 'TagVersions API', type: :request do
|
||||
end
|
||||
|
||||
it 'does not create tag versions by wiki updates when tag has no versions yet' do
|
||||
wiki_tag_name = TagName.create!(name: 'tag_versions_from_wiki')
|
||||
wiki_tag = Tag.create!(tag_name: wiki_tag_name, category: :general)
|
||||
wiki_tag_name = create(:tag_name, name: 'tag_versions_from_wiki')
|
||||
wiki_tag = create(:tag, primary_tag_name: wiki_tag_name, category: :general)
|
||||
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
|
||||
@@ -8,13 +8,13 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
end
|
||||
|
||||
def create_tag! name:, category: :general
|
||||
tag_name = TagName.create!(name:)
|
||||
Tag.create!(tag_name:, category:)
|
||||
tag_name = create(:tag_name, name:)
|
||||
create(:tag, primary_tag_name: tag_name, category:)
|
||||
end
|
||||
|
||||
def create_wiki_for_tag! tag:, body: 'wiki body', user: member_user
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: tag.tag_name,
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body:,
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
@@ -43,7 +43,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
wiki_page.reload
|
||||
version = wiki_page.wiki_versions.order(:version_no).last
|
||||
|
||||
expect(tag.name).to eq('patch_tag_wiki_after')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('patch_tag_wiki_after')
|
||||
expect(wiki_page.title).to eq('patch_tag_wiki_after')
|
||||
|
||||
expect(version).to have_attributes(
|
||||
@@ -72,7 +72,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
tag.reload
|
||||
wiki_page.reload
|
||||
|
||||
expect(tag.name).to eq('patch_tag_category_only')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('patch_tag_category_only')
|
||||
expect(tag.category).to eq('meme')
|
||||
expect(wiki_page.wiki_versions.count).to eq(before_wiki_versions)
|
||||
end
|
||||
@@ -101,7 +101,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
wiki_page.reload
|
||||
version = wiki_page.wiki_versions.order(:version_no).last
|
||||
|
||||
expect(tag.name).to eq('put_tag_wiki_after')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('put_tag_wiki_after')
|
||||
expect(wiki_page.title).to eq('put_tag_wiki_after')
|
||||
|
||||
expect(version).to have_attributes(
|
||||
@@ -134,7 +134,7 @@ RSpec.describe 'Tag and wiki history integrity', type: :request do
|
||||
tag.reload
|
||||
wiki_page.reload
|
||||
|
||||
expect(tag.name).to eq('put_tag_category_only')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('put_tag_category_only')
|
||||
expect(tag.category).to eq('meme')
|
||||
expect(wiki_page.wiki_versions.count).to eq(before_wiki_versions)
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ RSpec.describe 'Tags deerjikists API', type: :request do
|
||||
let(:guest) { create(:user, role: :guest) }
|
||||
|
||||
before do
|
||||
tag.tag_name.update!(name: 'deerjika')
|
||||
primary_tag_name_for(tag, 'ja').update!(name: 'deerjika')
|
||||
end
|
||||
|
||||
describe 'GET /tags/:id/deerjikists' do
|
||||
@@ -344,7 +344,7 @@ RSpec.describe 'Tags deerjikists API', type: :request do
|
||||
end
|
||||
|
||||
before do
|
||||
other_tag.tag_name.update!(name: 'existing-deerjikist')
|
||||
primary_tag_name_for(other_tag, 'ja').update!(name: 'existing-deerjikist')
|
||||
end
|
||||
|
||||
it 'returns an indexed 422 error and rolls back the complete replacement' do
|
||||
|
||||
+245
-229
@@ -2,13 +2,13 @@ require 'cgi'
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Tags API', type: :request do
|
||||
let!(:tn) { TagName.create!(name: 'spec_tag') }
|
||||
let!(:tag) { Tag.create!(tag_name: tn, category: :general) }
|
||||
let!(:alias_tn) { TagName.create!(name: 'unko', canonical: tn) }
|
||||
let!(:tn) { create(:tag_name, name: 'spec_tag') }
|
||||
let!(:tag) { create(:tag, primary_tag_name: tn, category: :general) }
|
||||
let!(:alias_tn) { create(:tag_name, :alias, name: 'unko', tag:) }
|
||||
let!(:post) { Post.create!(url: 'https://example.com/unkounkounko') }
|
||||
let!(:post_tag) { PostTag.create!(post:, tag:) }
|
||||
let!(:tn2) { TagName.create!(name: 'unknown') }
|
||||
let!(:tag2) { Tag.create!(tag_name: tn2, category: :general) }
|
||||
let!(:tn2) { create(:tag_name, name: 'unknown') }
|
||||
let!(:tag2) { create(:tag, primary_tag_name: tn2, category: :general) }
|
||||
|
||||
def response_tags
|
||||
json.fetch('tags')
|
||||
@@ -18,6 +18,14 @@ RSpec.describe 'Tags API', type: :request do
|
||||
response_tags.map { |t| t.fetch('name') }
|
||||
end
|
||||
|
||||
def version_primary_name(version, language_code)
|
||||
version.names.fetch(language_code).fetch('name')
|
||||
end
|
||||
|
||||
def version_alias_names(version, language_code)
|
||||
version.names.fetch(language_code).fetch('aliases').pluck('name')
|
||||
end
|
||||
|
||||
def dummy_material_upload(filename: 'dummy.png', type: 'image/png', body: 'dummy')
|
||||
Rack::Test::UploadedFile.new(StringIO.new(body), type, original_filename: filename)
|
||||
end
|
||||
@@ -38,7 +46,9 @@ RSpec.describe 'Tags API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('count')).to eq(2)
|
||||
expect(response_tags).to contain_exactly(
|
||||
a_hash_including('id' => tag.id, 'name' => tag.name, 'category' => 'general'),
|
||||
a_hash_including('id' => tag.id,
|
||||
'name' => primary_tag_name_for(tag, 'ja').name,
|
||||
'category' => 'general'),
|
||||
a_hash_including(
|
||||
'id' => external.id,
|
||||
'name' => 'nico:spec_external',
|
||||
@@ -91,7 +101,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'filters tags by category' do
|
||||
meme = Tag.create!(tag_name: TagName.create!(name: 'meme_only'), category: :meme)
|
||||
meme = create(:tag, primary_name: 'meme_only', category: :meme)
|
||||
|
||||
get '/tags', params: { category: 'meme' }
|
||||
|
||||
@@ -101,30 +111,29 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'filters tags by deprecated state' do
|
||||
deprecated_tag = Tag.create!(
|
||||
name: 'deprecated_filter',
|
||||
category: :general,
|
||||
deprecated_at: 1.day.from_now
|
||||
)
|
||||
active_tag = Tag.create!(name: 'active_filter', category: :general)
|
||||
deprecated_tag = create(:tag,
|
||||
primary_name: 'deprecated_filter',
|
||||
category: :general,
|
||||
deprecated_at: 1.day.from_now)
|
||||
active_tag = create(:tag, primary_name: 'active_filter', category: :general)
|
||||
|
||||
get '/tags', params: { name: '_filter', deprecated: '1' }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response_names).to include(deprecated_tag.name)
|
||||
expect(response_names).not_to include(active_tag.name)
|
||||
expect(response_names).to include(primary_tag_name_for(deprecated_tag, 'ja').name)
|
||||
expect(response_names).not_to include(primary_tag_name_for(active_tag, 'ja').name)
|
||||
|
||||
get '/tags', params: { name: '_filter', deprecated: '0' }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response_names).to include(active_tag.name)
|
||||
expect(response_names).not_to include(deprecated_tag.name)
|
||||
expect(response_names).to include(primary_tag_name_for(active_tag, 'ja').name)
|
||||
expect(response_names).not_to include(primary_tag_name_for(deprecated_tag, 'ja').name)
|
||||
end
|
||||
|
||||
it 'filters tags by post_count range' do
|
||||
low = Tag.create!(tag_name: TagName.create!(name: 'pc_low'), category: :general)
|
||||
mid = Tag.create!(tag_name: TagName.create!(name: 'pc_mid'), category: :general)
|
||||
high = Tag.create!(tag_name: TagName.create!(name: 'pc_high'), category: :general)
|
||||
low = create(:tag, primary_name: 'pc_low', category: :general)
|
||||
mid = create(:tag, primary_name: 'pc_mid', category: :general)
|
||||
high = create(:tag, primary_name: 'pc_high', category: :general)
|
||||
|
||||
low.update_columns(post_count: 1)
|
||||
mid.update_columns(post_count: 3)
|
||||
@@ -142,8 +151,8 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'filters tags by created_at range' do
|
||||
old_tag = Tag.create!(tag_name: TagName.create!(name: 'created_old'), category: :general)
|
||||
new_tag = Tag.create!(tag_name: TagName.create!(name: 'created_new'), category: :general)
|
||||
old_tag = create(:tag, primary_name: 'created_old', category: :general)
|
||||
new_tag = create(:tag, primary_name: 'created_new', category: :general)
|
||||
|
||||
old_time = Time.zone.local(2024, 1, 1, 0, 0, 0)
|
||||
new_time = Time.zone.local(2024, 2, 1, 0, 0, 0)
|
||||
@@ -162,8 +171,8 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'filters tags by updated_at range' do
|
||||
old_tag = Tag.create!(tag_name: TagName.create!(name: 'updated_old'), category: :general)
|
||||
new_tag = Tag.create!(tag_name: TagName.create!(name: 'updated_new'), category: :general)
|
||||
old_tag = create(:tag, primary_name: 'updated_old', category: :general)
|
||||
new_tag = create(:tag, primary_name: 'updated_new', category: :general)
|
||||
|
||||
old_time = Time.zone.local(2024, 3, 1, 0, 0, 0)
|
||||
new_time = Time.zone.local(2024, 4, 1, 0, 0, 0)
|
||||
@@ -182,12 +191,12 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'orders tags by custom category order' do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'cat_deerjikist'), category: :deerjikist)
|
||||
Tag.create!(tag_name: TagName.create!(name: 'cat_meme'), category: :meme)
|
||||
Tag.create!(tag_name: TagName.create!(name: 'cat_character'), category: :character)
|
||||
Tag.create!(tag_name: TagName.create!(name: 'cat_general'), category: :general)
|
||||
Tag.create!(tag_name: TagName.create!(name: 'cat_material'), category: :material)
|
||||
Tag.create!(tag_name: TagName.create!(name: 'cat_meta'), category: :meta)
|
||||
create(:tag, primary_name: 'cat_deerjikist', category: :deerjikist)
|
||||
create(:tag, primary_name: 'cat_meme', category: :meme)
|
||||
create(:tag, primary_name: 'cat_character', category: :character)
|
||||
create(:tag, primary_name: 'cat_general', category: :general)
|
||||
create(:tag, primary_name: 'cat_material', category: :material)
|
||||
create(:tag, primary_name: 'cat_meta', category: :meta)
|
||||
create(:external_tag, name: 'cat_nico')
|
||||
|
||||
get '/tags', params: { name: 'cat_', order: 'category:asc', limit: 20 }
|
||||
@@ -202,20 +211,20 @@ RSpec.describe 'Tags API', type: :request do
|
||||
context 'with mixed legacy pagination' do
|
||||
let!(:first_tag) do
|
||||
create(:tag,
|
||||
tag_name: create(:tag_name, name: 'a_mixed_page'),
|
||||
category: :meme)
|
||||
primary_tag_name: create(:tag_name, name: 'a_mixed_page'),
|
||||
category: :meme)
|
||||
end
|
||||
|
||||
let!(:middle_tag) do
|
||||
create(:tag,
|
||||
tag_name: create(:tag_name, name: 'm_mixed_page'),
|
||||
category: :meta)
|
||||
primary_tag_name: create(:tag_name, name: 'm_mixed_page'),
|
||||
category: :meta)
|
||||
end
|
||||
|
||||
let!(:last_tag) do
|
||||
create(:tag,
|
||||
tag_name: create(:tag_name, name: 'z_mixed_page'),
|
||||
category: :general)
|
||||
primary_tag_name: create(:tag_name, name: 'z_mixed_page'),
|
||||
category: :general)
|
||||
end
|
||||
|
||||
let!(:first_external) do
|
||||
@@ -265,7 +274,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
it 'paginates and keeps total count' do
|
||||
%w[pag_a pag_b pag_c].each do |name|
|
||||
Tag.create!(tag_name: TagName.create!(name:), category: :general)
|
||||
create(:tag, primary_tag_name: create(:tag_name, name:), category: :general)
|
||||
end
|
||||
|
||||
get '/tags', params: { name: 'pag_', order: 'name:asc', page: 2, limit: 2 }
|
||||
@@ -276,8 +285,8 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'falls back to default ordering when order is invalid' do
|
||||
low = Tag.create!(tag_name: TagName.create!(name: 'fallback_low'), category: :general)
|
||||
high = Tag.create!(tag_name: TagName.create!(name: 'fallback_high'), category: :general)
|
||||
low = create(:tag, primary_name: 'fallback_low', category: :general)
|
||||
high = create(:tag, primary_name: 'fallback_high', category: :general)
|
||||
|
||||
low.update_columns(post_count: 1)
|
||||
high.update_columns(post_count: 9)
|
||||
@@ -290,7 +299,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
it 'normalises invalid page and limit' do
|
||||
%w[norm_a norm_b].each do |name|
|
||||
Tag.create!(tag_name: TagName.create!(name:), category: :general)
|
||||
create(:tag, primary_tag_name: create(:tag_name, name:), category: :general)
|
||||
end
|
||||
|
||||
get '/tags', params: { name: 'norm_', order: 'name:asc', page: 0, limit: 0 }
|
||||
@@ -302,10 +311,9 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'returns aliases and parent tags' do
|
||||
parent_tag = Tag.create!(
|
||||
tag_name: TagName.create!(name: 'index_parent_tag'),
|
||||
category: :meme
|
||||
)
|
||||
parent_tag = create(:tag,
|
||||
primary_tag_name: create(:tag_name, name: 'index_parent_tag'),
|
||||
category: :meme)
|
||||
TagImplication.create!(tag:, parent_tag:)
|
||||
|
||||
get '/tags', params: { name: 'spec_tag' }
|
||||
@@ -349,10 +357,9 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'returns aliases and parent tags' do
|
||||
parent_tag = Tag.create!(
|
||||
tag_name: TagName.create!(name: 'show_parent_tag'),
|
||||
category: :character
|
||||
)
|
||||
parent_tag = create(:tag,
|
||||
primary_tag_name: create(:tag_name, name: 'show_parent_tag'),
|
||||
category: :character)
|
||||
TagImplication.create!(tag:, parent_tag:)
|
||||
|
||||
request
|
||||
@@ -398,7 +405,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
it 'prefers Tag when Tag and ExternalTag have the same id' do
|
||||
internal = create(
|
||||
:tag,
|
||||
tag_name: create(:tag_name, name: 'internal_collision'))
|
||||
primary_tag_name: create(:tag_name, name: 'internal_collision'))
|
||||
create(
|
||||
:external_tag,
|
||||
id: internal.id,
|
||||
@@ -416,7 +423,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
describe 'GET /tags/autocomplete' do
|
||||
it 'combines internal and external matches without conflating equal ids' do
|
||||
internal = Tag.create!(category: :general, name: 'mixed_internal', post_count: 2)
|
||||
internal = create(:tag, category: :general, primary_name: 'mixed_internal', post_count: 2)
|
||||
external = create(:external_tag, id: internal.id,
|
||||
name: 'mixed_external', post_count: 3)
|
||||
|
||||
@@ -455,7 +462,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
it 'limits the combined results to 20 and sorts ties by displayed name' do
|
||||
11.times do |i|
|
||||
name = "combined_#{ i.to_s.rjust(2, '0') }"
|
||||
Tag.create!(category: :general, name:, post_count: 1)
|
||||
create(:tag, category: :general, primary_name: name, post_count: 1)
|
||||
create(:external_tag, name:, post_count: 1)
|
||||
end
|
||||
|
||||
@@ -467,34 +474,39 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'excludes external tags but preserves internal alias matches when nico is false' do
|
||||
internal = Tag.create!(category: :general, name: 'switch_internal', post_count: 1)
|
||||
alias_target = Tag.create!(category: :general, name: 'alias_target', post_count: 1)
|
||||
TagName.create!(name: 'switch_alias', canonical: alias_target.tag_name)
|
||||
internal = create(:tag, category: :general, primary_name: 'switch_internal', post_count: 1)
|
||||
alias_target = create(:tag, category: :general, primary_name: 'alias_target', post_count: 1)
|
||||
create(:tag_name, :alias, name: 'switch_alias', tag: alias_target)
|
||||
create(:external_tag, name: 'switch_external', post_count: 1)
|
||||
|
||||
get '/tags/autocomplete', params: { q: 'switch', nico: '0' }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to contain_exactly(
|
||||
a_hash_including('id' => internal.id, 'name' => internal.name),
|
||||
a_hash_including('id' => alias_target.id, 'name' => alias_target.name,
|
||||
a_hash_including('id' => internal.id,
|
||||
'name' => primary_tag_name_for(internal, 'ja').name),
|
||||
a_hash_including('id' => alias_target.id,
|
||||
'name' => primary_tag_name_for(alias_target, 'ja').name,
|
||||
'matched_alias' => 'switch_alias'))
|
||||
end
|
||||
|
||||
['%', '_'].each do |wildcard|
|
||||
it "treats #{ wildcard } literally for canonical, alias, and external names" do
|
||||
it "treats #{ wildcard } literally for primary, alias, and external names" do
|
||||
literal = "literal#{ wildcard }match"
|
||||
Tag.create!(category: :general, name: literal, post_count: 1)
|
||||
alias_target = Tag.create!(category: :general, name: 'literal_alias_target', post_count: 1)
|
||||
TagName.create!(name: "#{ literal }_alias", canonical: alias_target.tag_name)
|
||||
create(:tag, category: :general, primary_name: literal, post_count: 1)
|
||||
alias_target = create(
|
||||
:tag, category: :general, primary_name: 'literal_alias_target', post_count: 1)
|
||||
create(:tag_name, :alias, name: "#{ literal }_alias",
|
||||
tag: alias_target)
|
||||
create(:external_tag, name: literal, post_count: 1)
|
||||
Tag.create!(category: :general, name: 'literalXmatch', post_count: 2)
|
||||
create(:tag, category: :general, primary_name: 'literalXmatch', post_count: 2)
|
||||
create(:external_tag, name: 'literalXmatch', post_count: 2)
|
||||
|
||||
get '/tags/autocomplete', params: { q: "literal#{ wildcard }" }
|
||||
|
||||
expect(json.map { |row| row.fetch('name') })
|
||||
.to contain_exactly(literal, alias_target.name, "nico:#{ literal }")
|
||||
.to contain_exactly(
|
||||
literal, primary_tag_name_for(alias_target, 'ja').name, "nico:#{ literal }")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -510,7 +522,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
expect(t['matched_alias']).to be(nil)
|
||||
end
|
||||
|
||||
it 'returns matching canonical tags by q with aliases' do
|
||||
it 'returns matching primary tag names by q with aliases' do
|
||||
get '/tags/autocomplete', params: { q: 'unk' }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
@@ -523,11 +535,10 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'omits deprecated tags' do
|
||||
deprecated_tag = Tag.create!(
|
||||
name: 'spec_deprecated',
|
||||
category: :general,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
deprecated_tag = create(:tag,
|
||||
primary_name: 'spec_deprecated',
|
||||
category: :general,
|
||||
deprecated_at: Time.current)
|
||||
deprecated_tag.update_columns(post_count: 1)
|
||||
|
||||
get '/tags/autocomplete', params: { q: 'spec_', present: '0' }
|
||||
@@ -621,7 +632,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
tag.reload
|
||||
expect(tag.name).to eq('new')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('new')
|
||||
expect(tag.category).to eq('general')
|
||||
|
||||
body = JSON.parse(response.body)
|
||||
@@ -636,7 +647,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
tag.reload
|
||||
expect(tag.name).to eq('spec_tag')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('spec_tag')
|
||||
expect(tag.category).to eq('meme')
|
||||
end
|
||||
|
||||
@@ -646,7 +657,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
tag.reload
|
||||
expect(tag.name).to eq('spec_tag')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('spec_tag')
|
||||
expect(tag.category).to eq('general')
|
||||
end
|
||||
|
||||
@@ -656,7 +667,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
tag.reload
|
||||
expect(tag.name).to eq('n')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('n')
|
||||
expect(tag.category).to eq('meta')
|
||||
end
|
||||
|
||||
@@ -669,7 +680,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
patch "/tags/#{tag.id}", params: { name: 'new', category: 'nico' }
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(tag.reload.name).to eq('spec_tag')
|
||||
expect(primary_tag_name_for(tag.reload, 'ja').name).to eq('spec_tag')
|
||||
expect(tag.category).to eq('general')
|
||||
end
|
||||
|
||||
@@ -684,11 +695,11 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
expect(versions.map(&:event_type)).to eq(['create', 'update'])
|
||||
|
||||
expect(versions.first.name).to eq('spec_tag')
|
||||
expect(version_primary_name(versions.first, 'ja')).to eq('spec_tag')
|
||||
expect(versions.first.category).to eq('general')
|
||||
expect(versions.first.aliases.split).to include('unko')
|
||||
expect(version_alias_names(versions.first, 'ja')).to include('unko')
|
||||
|
||||
expect(versions.second.name).to eq('new_tag_name')
|
||||
expect(version_primary_name(versions.second, 'ja')).to eq('new_tag_name')
|
||||
expect(versions.second.category).to eq('meme')
|
||||
expect(versions.second.created_by_user_id).to eq(member_user.id)
|
||||
end
|
||||
@@ -719,7 +730,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
it 'PATCH で tag の name を変更すると対応する wiki version を作成する' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: tag.tag_name,
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
@@ -747,7 +758,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
it 'tag の category だけを変更しても wiki version は作成しない' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: tag.tag_name,
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
@@ -774,7 +785,8 @@ RSpec.describe 'Tags API', type: :request do
|
||||
}
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(TagName.find_by!(name: 'spec_tag').canonical).to eq(tag.reload.tag_name)
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'spec_tag'))
|
||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
||||
|
||||
patch "/tags/#{ tag.id }", params: { name: 'spec_tag' }
|
||||
|
||||
@@ -782,15 +794,17 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
tag.reload
|
||||
|
||||
expect(tag.name).to eq('spec_tag')
|
||||
expect(tag.tag_name.canonical_id).to be_nil
|
||||
expect(TagName.find_by!(name: 'patch_roundtrip_target').canonical).to eq(tag.tag_name)
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('spec_tag')
|
||||
expect(primary_tag_name_for(tag, 'ja')).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'ja', primary_flg: true)
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'patch_roundtrip_target'))
|
||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
||||
end
|
||||
|
||||
it '別 tag の正規名には変更できない' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: tag.tag_name,
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'patch collision wiki',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
@@ -802,9 +816,10 @@ RSpec.describe 'Tags API', type: :request do
|
||||
'name' => ['その名前は既に使はれてゐます.']
|
||||
)
|
||||
|
||||
expect(tag.reload.name).to eq('spec_tag')
|
||||
expect(tag.tag_name.aliases.map(&:name)).to contain_exactly('unko')
|
||||
expect(wiki_page.reload.tag_name).to eq(tag.tag_name)
|
||||
expect(primary_tag_name_for(tag.reload, 'ja').name).to eq('spec_tag')
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'ja', primary_flg: false).pluck(:name))
|
||||
.to contain_exactly('unko')
|
||||
expect(wiki_page.reload.tag_name).to eq(primary_tag_name_for(tag, 'ja'))
|
||||
end
|
||||
|
||||
it 'system tag の name は変更できない' do
|
||||
@@ -813,34 +828,34 @@ RSpec.describe 'Tags API', type: :request do
|
||||
patch "/tags/#{ system_tag.id }", params: { name: 'patch_system_tag_renamed' }
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(system_tag.reload.name).to eq('bot操作')
|
||||
expect(primary_tag_name_for(system_tag.reload, 'ja').name).to eq('bot操作')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /tags/with-depth' do
|
||||
let!(:root_meme) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'depth_a_root_meme'), category: :meme)
|
||||
create(:tag, primary_name: 'depth_a_root_meme', category: :meme)
|
||||
end
|
||||
|
||||
let!(:root_material) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'depth_b_root_material'), category: :material)
|
||||
create(:tag, primary_name: 'depth_b_root_material', category: :material)
|
||||
end
|
||||
|
||||
let!(:hidden_general_root) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'depth_hidden_general_root'), category: :general)
|
||||
create(:tag, primary_name: 'depth_hidden_general_root', category: :general)
|
||||
end
|
||||
|
||||
let!(:child_character) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'depth_child_character'), category: :character)
|
||||
create(:tag, primary_name: 'depth_child_character', category: :character)
|
||||
end
|
||||
|
||||
let!(:grandchild_material) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'depth_grandchild_material'), category: :material)
|
||||
create(:tag, primary_name: 'depth_grandchild_material', category: :material)
|
||||
end
|
||||
|
||||
let!(:child_general) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'depth_child_general'), category: :general)
|
||||
create(:tag, primary_name: 'depth_child_general', category: :general)
|
||||
end
|
||||
|
||||
before do
|
||||
@@ -883,15 +898,13 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'passes through deprecated tags when finding children' do
|
||||
deprecated_middle = Tag.create!(
|
||||
name: 'depth_deprecated_middle',
|
||||
category: :character,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
visible_descendant = Tag.create!(
|
||||
name: 'depth_visible_descendant',
|
||||
category: :material
|
||||
)
|
||||
deprecated_middle = create(:tag,
|
||||
primary_name: 'depth_deprecated_middle',
|
||||
category: :character,
|
||||
deprecated_at: Time.current)
|
||||
visible_descendant = create(:tag,
|
||||
primary_name: 'depth_visible_descendant',
|
||||
category: :material)
|
||||
TagImplication.create!(parent_tag: root_material, tag: deprecated_middle)
|
||||
TagImplication.create!(parent_tag: deprecated_middle, tag: visible_descendant)
|
||||
|
||||
@@ -903,24 +916,20 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'passes through multiple deprecated tags for roots and has_children' do
|
||||
active_child = Tag.create!(
|
||||
name: 'depth_active_child_below_deprecated',
|
||||
category: :character
|
||||
)
|
||||
deprecated_parent = Tag.create!(
|
||||
name: 'depth_deprecated_parent',
|
||||
category: :character,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
deprecated_grandparent = Tag.create!(
|
||||
name: 'depth_deprecated_grandparent',
|
||||
category: :material,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
active_ancestor = Tag.create!(
|
||||
name: 'depth_active_ancestor',
|
||||
category: :meme
|
||||
)
|
||||
active_child = create(:tag,
|
||||
primary_name: 'depth_active_child_below_deprecated',
|
||||
category: :character)
|
||||
deprecated_parent = create(:tag,
|
||||
primary_name: 'depth_deprecated_parent',
|
||||
category: :character,
|
||||
deprecated_at: Time.current)
|
||||
deprecated_grandparent = create(:tag,
|
||||
primary_name: 'depth_deprecated_grandparent',
|
||||
category: :material,
|
||||
deprecated_at: Time.current)
|
||||
active_ancestor = create(:tag,
|
||||
primary_name: 'depth_active_ancestor',
|
||||
category: :meme)
|
||||
TagImplication.create!(tag: active_child, parent_tag: deprecated_parent)
|
||||
TagImplication.create!(tag: deprecated_parent, parent_tag: deprecated_grandparent)
|
||||
TagImplication.create!(tag: deprecated_grandparent, parent_tag: active_ancestor)
|
||||
@@ -945,15 +954,13 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'treats an active tag with only deprecated ancestors as a root' do
|
||||
active_child = Tag.create!(
|
||||
name: 'depth_root_below_deprecated',
|
||||
category: :character
|
||||
)
|
||||
deprecated_parent = Tag.create!(
|
||||
name: 'depth_root_deprecated_parent',
|
||||
category: :material,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
active_child = create(:tag,
|
||||
primary_name: 'depth_root_below_deprecated',
|
||||
category: :character)
|
||||
deprecated_parent = create(:tag,
|
||||
primary_name: 'depth_root_deprecated_parent',
|
||||
category: :material,
|
||||
deprecated_at: Time.current)
|
||||
TagImplication.create!(tag: active_child, parent_tag: deprecated_parent)
|
||||
|
||||
get '/tags/with-depth'
|
||||
@@ -967,16 +974,14 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'terminates when deprecated implications contain a cycle' do
|
||||
first = Tag.create!(
|
||||
name: 'depth_cycle_first',
|
||||
category: :character,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
second = Tag.create!(
|
||||
name: 'depth_cycle_second',
|
||||
category: :material,
|
||||
deprecated_at: Time.current
|
||||
)
|
||||
first = create(:tag,
|
||||
primary_name: 'depth_cycle_first',
|
||||
category: :character,
|
||||
deprecated_at: Time.current)
|
||||
second = create(:tag,
|
||||
primary_name: 'depth_cycle_second',
|
||||
category: :material,
|
||||
deprecated_at: Time.current)
|
||||
TagImplication.create!(tag: first, parent_tag: root_material)
|
||||
TagImplication.create!(tag: second, parent_tag: first)
|
||||
now = Time.current
|
||||
@@ -1002,19 +1007,19 @@ RSpec.describe 'Tags API', type: :request do
|
||||
let!(:material_user) { create_member_user! }
|
||||
|
||||
let!(:root_tag) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'materials_root'), category: :material)
|
||||
create(:tag, primary_name: 'materials_root', category: :material)
|
||||
end
|
||||
|
||||
let!(:child_a_tag) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'materials_child_a'), category: :material)
|
||||
create(:tag, primary_name: 'materials_child_a', category: :material)
|
||||
end
|
||||
|
||||
let!(:child_b_tag) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'materials_child_b'), category: :character)
|
||||
create(:tag, primary_name: 'materials_child_b', category: :character)
|
||||
end
|
||||
|
||||
let!(:grandchild_tag) do
|
||||
Tag.create!(tag_name: TagName.create!(name: 'materials_grandchild'), category: :material)
|
||||
create(:tag, primary_name: 'materials_grandchild', category: :material)
|
||||
end
|
||||
|
||||
let!(:root_material) do
|
||||
@@ -1036,7 +1041,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'returns a tag tree with nested materials sorted by child name' do
|
||||
get "/tags/name/#{ CGI.escape(root_tag.name) }/materials"
|
||||
get "/tags/name/#{ CGI.escape(primary_tag_name_for(root_tag, 'ja').name) }/materials"
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
@@ -1127,7 +1132,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
}
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(tag.reload.name).to eq('spec_tag')
|
||||
expect(primary_tag_name_for(tag.reload, 'ja').name).to eq('spec_tag')
|
||||
end
|
||||
|
||||
it 'category が空なら 422 を返す' do
|
||||
@@ -1139,7 +1144,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
}
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(tag.reload.name).to eq('spec_tag')
|
||||
expect(primary_tag_name_for(tag.reload, 'ja').name).to eq('spec_tag')
|
||||
expect(tag.category).to eq('general')
|
||||
end
|
||||
|
||||
@@ -1158,14 +1163,12 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it 'name, category, aliases, parent tags をまとめて更新できる' do
|
||||
old_parent = Tag.create!(
|
||||
tag_name: TagName.create!(name: 'put_old_parent'),
|
||||
category: :general
|
||||
)
|
||||
kept_parent = Tag.create!(
|
||||
tag_name: TagName.create!(name: 'put_kept_parent'),
|
||||
category: :general
|
||||
)
|
||||
old_parent = create(:tag,
|
||||
primary_tag_name: create(:tag_name, name: 'put_old_parent'),
|
||||
category: :general)
|
||||
kept_parent = create(:tag,
|
||||
primary_tag_name: create(:tag_name, name: 'put_kept_parent'),
|
||||
category: :general)
|
||||
TagImplication.create!(tag:, parent_tag: old_parent)
|
||||
TagImplication.create!(tag:, parent_tag: kept_parent)
|
||||
|
||||
@@ -1181,22 +1184,24 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
tag.reload
|
||||
|
||||
expect(tag.name).to eq('put_renamed_tag')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('put_renamed_tag')
|
||||
expect(tag.category).to eq('meme')
|
||||
|
||||
expect(TagName.find_by(name: 'put_alias_a').canonical).to eq(tag.tag_name)
|
||||
expect(TagName.find_by(name: 'put_alias_b').canonical).to eq(tag.tag_name)
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'put_alias_a'))
|
||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'put_alias_b'))
|
||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
||||
|
||||
old_name_alias = TagName.find_by(name: 'spec_tag')
|
||||
old_name_alias = TagName.find_by(language_code: 'ja', name: 'spec_tag')
|
||||
expect(old_name_alias).to be_present
|
||||
expect(old_name_alias.canonical).to eq(tag.tag_name)
|
||||
expect(old_name_alias).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'ja', primary_flg: false)
|
||||
|
||||
expect(alias_tn.reload.canonical).to be_nil
|
||||
expect(alias_tn.reload.tag_id).to be_nil
|
||||
|
||||
expect(tag.parents.map(&:name)).to contain_exactly(
|
||||
'put_kept_parent',
|
||||
'put_new_parent'
|
||||
)
|
||||
expect(TagName.where(tag_id: tag.parents.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name))
|
||||
.to contain_exactly('put_kept_parent', 'put_new_parent')
|
||||
|
||||
expect(TagImplication.where(tag:, parent_tag: old_parent)).not_to exist
|
||||
|
||||
@@ -1226,7 +1231,8 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
tag.reload
|
||||
|
||||
expect(TagName.find_by(name: 'put_alias_self_test').canonical).to eq(tag.tag_name)
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'put_alias_self_test'))
|
||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
||||
expect(json['aliases']).to include('put_alias_self_test')
|
||||
expect(json['aliases']).not_to include('spec_tag')
|
||||
end
|
||||
@@ -1263,13 +1269,13 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
expect(versions.map(&:event_type)).to eq(['create', 'update'])
|
||||
|
||||
expect(versions.first.name).to eq('spec_tag')
|
||||
expect(version_primary_name(versions.first, 'ja')).to eq('spec_tag')
|
||||
expect(versions.first.category).to eq('general')
|
||||
expect(versions.first.aliases.split).to include('unko')
|
||||
expect(version_alias_names(versions.first, 'ja')).to include('unko')
|
||||
|
||||
expect(versions.second.name).to eq('put_versioned_tag')
|
||||
expect(version_primary_name(versions.second, 'ja')).to eq('put_versioned_tag')
|
||||
expect(versions.second.category).to eq('meta')
|
||||
expect(versions.second.aliases.split).to include('spec_tag')
|
||||
expect(version_alias_names(versions.second, 'ja')).to include('spec_tag')
|
||||
expect(versions.second.created_by_user_id).to eq(member_user.id)
|
||||
end
|
||||
|
||||
@@ -1296,35 +1302,37 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
tag.reload
|
||||
|
||||
expect(tag.name).to eq('spec_tag')
|
||||
expect(TagName.find_by!(name: 'put_roundtrip_b').canonical).to eq(tag.tag_name)
|
||||
expect(tag.tag_name.aliases.map(&:name)).to contain_exactly('put_roundtrip_b', 'unko')
|
||||
expect(tag.tag_name.aliases.map(&:name)).not_to include('spec_tag')
|
||||
expect(alias_tn.reload.canonical).to eq(tag.tag_name)
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('spec_tag')
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'put_roundtrip_b'))
|
||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'ja', primary_flg: false).pluck(:name))
|
||||
.to contain_exactly('put_roundtrip_b', 'unko')
|
||||
expect(TagName.where(tag_id: tag.id, language_code: 'ja', primary_flg: false).pluck(:name))
|
||||
.not_to include('spec_tag')
|
||||
expect(alias_tn.reload).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'ja', primary_flg: false)
|
||||
|
||||
version = tag.tag_versions.order(:version_no).last
|
||||
|
||||
expect(version.event_type).to eq('update')
|
||||
expect(version.name).to eq('spec_tag')
|
||||
expect(version.aliases.split).to contain_exactly('put_roundtrip_b', 'unko')
|
||||
expect(version_primary_name(version, 'ja')).to eq('spec_tag')
|
||||
expect(version_alias_names(version, 'ja')).to contain_exactly('put_roundtrip_b', 'unko')
|
||||
end
|
||||
|
||||
it 'parent tag の snapshot も作成する' do
|
||||
old_parent = Tag.create!(
|
||||
tag_name: TagName.create!(name: 'put_snapshot_old_parent'),
|
||||
category: :general
|
||||
)
|
||||
new_parent = Tag.create!(
|
||||
tag_name: TagName.create!(name: 'put_snapshot_new_parent'),
|
||||
category: :general
|
||||
)
|
||||
old_parent = create(:tag,
|
||||
primary_tag_name: create(:tag_name, name: 'put_snapshot_old_parent'),
|
||||
category: :general)
|
||||
new_parent = create(:tag,
|
||||
primary_tag_name: create(:tag_name, name: 'put_snapshot_new_parent'),
|
||||
category: :general)
|
||||
TagImplication.create!(tag:, parent_tag: old_parent)
|
||||
|
||||
put "/tags/#{ tag.id }", params: {
|
||||
name: 'spec_tag',
|
||||
category: 'general',
|
||||
aliases: 'unko',
|
||||
parent_tags: new_parent.name,
|
||||
parent_tags: primary_tag_name_for(new_parent, 'ja').name,
|
||||
deprecated: '0',
|
||||
}
|
||||
|
||||
@@ -1347,13 +1355,13 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
|
||||
expect(tag.reload.name).to eq('spec_tag')
|
||||
expect(primary_tag_name_for(tag.reload, 'ja').name).to eq('spec_tag')
|
||||
expect(tag.category).to eq('general')
|
||||
end
|
||||
|
||||
it 'system tag の name は変更できない' do
|
||||
system_tag = Tag.tagme
|
||||
old_name = system_tag.name
|
||||
old_name = primary_tag_name_for(system_tag, 'ja').name
|
||||
old_category = system_tag.category
|
||||
|
||||
expect {
|
||||
@@ -1368,14 +1376,14 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
|
||||
expect(system_tag.reload.name).to eq(old_name)
|
||||
expect(primary_tag_name_for(system_tag.reload, 'ja').name).to eq(old_name)
|
||||
expect(system_tag.category).to eq(old_category)
|
||||
end
|
||||
|
||||
it 'wiki を持つ tag を更新すると wiki version も作成する' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: tag.tag_name,
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
@@ -1411,10 +1419,10 @@ RSpec.describe 'Tags API', type: :request do
|
||||
)
|
||||
end
|
||||
|
||||
it 'wiki を持つ tag を旧 alias へ戻しても wiki を新 canonical へ移す' do
|
||||
it 'wiki を持つ tag を旧 alias へ戻しても wiki を新しい正本名へ移す' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: tag.tag_name,
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
@@ -1443,9 +1451,11 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
tag.reload
|
||||
|
||||
expect(wiki_page.reload.tag_name).to eq(tag.tag_name)
|
||||
expect(TagName.find_by!(name: 'put_wiki_roundtrip_b').wiki_page).to be_nil
|
||||
expect(TagName.find_by!(name: 'put_wiki_roundtrip_b').canonical).to eq(tag.tag_name)
|
||||
expect(wiki_page.reload.tag_name).to eq(primary_tag_name_for(tag, 'ja'))
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'put_wiki_roundtrip_b').wiki_page)
|
||||
.to be_nil
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'put_wiki_roundtrip_b'))
|
||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
||||
|
||||
versions = wiki_page.wiki_versions.order(:version_no).last(2)
|
||||
|
||||
@@ -1454,16 +1464,16 @@ RSpec.describe 'Tags API', type: :request do
|
||||
end
|
||||
|
||||
it '別名を他 tag から奪った場合、奪はれた側の tag version も作成する' do
|
||||
old_owner = Tag.create!(
|
||||
tag_name: TagName.create!(name: 'put_alias_old_owner'),
|
||||
category: :general
|
||||
)
|
||||
stolen_alias = TagName.create!(
|
||||
name: 'put_stolen_alias',
|
||||
canonical: old_owner.tag_name
|
||||
)
|
||||
old_owner = create(:tag,
|
||||
primary_tag_name: create(:tag_name, name: 'put_alias_old_owner'),
|
||||
category: :general)
|
||||
stolen_alias = create(:tag_name, :alias,
|
||||
name: 'put_stolen_alias',
|
||||
tag: old_owner)
|
||||
|
||||
expect(old_owner.tag_name.aliases.map(&:name)).to include('put_stolen_alias')
|
||||
expect(TagName.where(tag_id: old_owner.id, language_code: 'ja',
|
||||
primary_flg: false).pluck(:name))
|
||||
.to include('put_stolen_alias')
|
||||
|
||||
expect {
|
||||
put "/tags/#{ tag.id }", params: {
|
||||
@@ -1479,30 +1489,32 @@ RSpec.describe 'Tags API', type: :request do
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
expect(stolen_alias.reload.canonical).to eq(tag.tag_name)
|
||||
expect(old_owner.reload.tag_name.aliases.map(&:name)).not_to include('put_stolen_alias')
|
||||
expect(stolen_alias.reload).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'ja', primary_flg: false)
|
||||
expect(TagName.where(tag_id: old_owner.id, language_code: 'ja',
|
||||
primary_flg: false).pluck(:name))
|
||||
.not_to include('put_stolen_alias')
|
||||
|
||||
old_owner_versions = old_owner.tag_versions.order(:version_no)
|
||||
|
||||
expect(old_owner_versions.first.event_type).to eq('create')
|
||||
expect(old_owner_versions.first.aliases.split).to include('put_stolen_alias')
|
||||
expect(version_alias_names(old_owner_versions.first, 'ja')).to include('put_stolen_alias')
|
||||
|
||||
expect(old_owner_versions.second.event_type).to eq('update')
|
||||
expect(old_owner_versions.second.aliases.split).not_to include('put_stolen_alias')
|
||||
expect(version_alias_names(old_owner_versions.second, 'ja'))
|
||||
.not_to include('put_stolen_alias')
|
||||
end
|
||||
|
||||
it '別 tag の alias 名を rename で奪へる' do
|
||||
old_owner = Tag.create!(
|
||||
tag_name: TagName.create!(name: 'put_alias_collision_owner'),
|
||||
category: :general
|
||||
)
|
||||
stolen_alias = TagName.create!(
|
||||
name: 'put_alias_collision_name',
|
||||
canonical: old_owner.tag_name
|
||||
)
|
||||
old_owner = create(:tag,
|
||||
primary_tag_name: create(:tag_name, name: 'put_alias_collision_owner'),
|
||||
category: :general)
|
||||
stolen_alias = create(:tag_name, :alias,
|
||||
name: 'put_alias_collision_name',
|
||||
tag: old_owner)
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: tag.tag_name,
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'put collision wiki',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
@@ -1515,29 +1527,33 @@ RSpec.describe 'Tags API', type: :request do
|
||||
deprecated: '0',
|
||||
}
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response).to have_http_status(:ok), response.body
|
||||
|
||||
tag.reload
|
||||
old_owner.reload
|
||||
stolen_alias.reload
|
||||
|
||||
expect(tag.name).to eq('put_alias_collision_name')
|
||||
expect(stolen_alias.canonical_id).to be_nil
|
||||
expect(TagName.find_by!(name: 'spec_tag').canonical).to eq(tag.tag_name)
|
||||
expect(old_owner.tag_name.aliases.map(&:name)).not_to include('put_alias_collision_name')
|
||||
expect(primary_tag_name_for(tag, 'ja').name).to eq('put_alias_collision_name')
|
||||
expect(stolen_alias).to have_attributes(
|
||||
tag_id: tag.id, language_code: 'ja', primary_flg: true)
|
||||
expect(TagName.find_by!(language_code: 'ja', name: 'spec_tag'))
|
||||
.to have_attributes(tag_id: tag.id, primary_flg: false)
|
||||
expect(TagName.where(tag_id: old_owner.id, language_code: 'ja',
|
||||
primary_flg: false).pluck(:name))
|
||||
.not_to include('put_alias_collision_name')
|
||||
|
||||
old_owner_versions = old_owner.tag_versions.order(:version_no)
|
||||
|
||||
expect(old_owner_versions.last.event_type).to eq('update')
|
||||
expect(old_owner_versions.last.aliases.split).not_to include('put_alias_collision_name')
|
||||
expect(wiki_page.reload.tag_name).to eq(tag.tag_name)
|
||||
expect(version_alias_names(old_owner_versions.last, 'ja'))
|
||||
.not_to include('put_alias_collision_name')
|
||||
expect(wiki_page.reload.tag_name).to eq(primary_tag_name_for(tag, 'ja'))
|
||||
end
|
||||
|
||||
it 'parent_tags に指定すると循環する tag は 422 にする' do
|
||||
child = Tag.create!(
|
||||
tag_name: TagName.create!(name: 'put_cycle_child'),
|
||||
category: :general
|
||||
)
|
||||
child = create(:tag,
|
||||
primary_tag_name: create(:tag_name, name: 'put_cycle_child'),
|
||||
category: :general)
|
||||
|
||||
TagImplication.create!(tag: child, parent_tag: tag)
|
||||
|
||||
@@ -1545,7 +1561,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
name: 'spec_tag',
|
||||
category: 'general',
|
||||
aliases: 'unko',
|
||||
parent_tags: child.name,
|
||||
parent_tags: primary_tag_name_for(child, 'ja').name,
|
||||
deprecated: '0',
|
||||
}
|
||||
|
||||
@@ -1557,7 +1573,7 @@ RSpec.describe 'Tags API', type: :request do
|
||||
it 'tag の name を変更すると対応する wiki version を作成する' do
|
||||
wiki_page =
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: tag.tag_name,
|
||||
tag_name: primary_tag_name_for(tag, 'ja'),
|
||||
body: 'wiki body before',
|
||||
created_by_user: member_user,
|
||||
message: 'init')
|
||||
|
||||
@@ -396,7 +396,7 @@ RSpec.describe 'Theatres API', type: :request do
|
||||
end
|
||||
|
||||
it 'finalizes skip when votes reach majority and stores voters and tag snapshots' do
|
||||
tag = create(:tag, name: 'skip-target')
|
||||
tag = create(:tag, primary_name: 'skip-target')
|
||||
PostTag.create!(post: niconico_post, tag:)
|
||||
|
||||
TheatreSkipVote.create!(theatre:, post: niconico_post, user: member)
|
||||
@@ -503,7 +503,7 @@ RSpec.describe 'Theatres API', type: :request do
|
||||
end
|
||||
|
||||
it 'returns tag penalties and candidate weights for the current watchers' do
|
||||
tag = create(:tag, name: 'heavy-tag')
|
||||
tag = create(:tag, primary_name: 'heavy-tag')
|
||||
PostTag.create!(post: second_niconico_post, tag:)
|
||||
event = TheatreSkipEvent.create!(
|
||||
theatre:,
|
||||
|
||||
@@ -10,7 +10,7 @@ RSpec.describe 'Wiki conflict handling', type: :request do
|
||||
it 'returns 409 when base_revision_id is stale' do
|
||||
page =
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: TagName.create!(name: 'wiki_conflict_request'),
|
||||
tag_name: create(:tag_name, name: 'wiki_conflict_request'),
|
||||
body: 'first',
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
|
||||
@@ -10,7 +10,7 @@ RSpec.describe 'Wiki history integrity', type: :request do
|
||||
|
||||
def create_wiki_page title:, body: 'body', message: 'init', user: self.user
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: TagName.create!(name: title),
|
||||
tag_name: create(:tag_name, name: title),
|
||||
body:,
|
||||
created_by_user: user,
|
||||
message:)
|
||||
|
||||
@@ -10,7 +10,7 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
|
||||
let!(:user) { create_member_user! }
|
||||
|
||||
let!(:tn) { TagName.create!(name: 'spec_wiki_title') }
|
||||
let!(:tn) { create(:tag_name, name: 'spec_wiki_title') }
|
||||
let!(:page) do
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: tn,
|
||||
@@ -19,11 +19,10 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
message: 'init')
|
||||
end
|
||||
let!(:tag) do
|
||||
Tag.create!(
|
||||
tag_name: tn,
|
||||
category: :general,
|
||||
deprecated_at: Time.zone.local(2026, 6, 1)
|
||||
)
|
||||
create(:tag,
|
||||
primary_tag_name: tn,
|
||||
category: :general,
|
||||
deprecated_at: Time.zone.local(2026, 6, 1))
|
||||
end
|
||||
|
||||
describe 'GET /wiki' do
|
||||
@@ -208,7 +207,7 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
{ 'X-Transfer-Code' => user.inheritance_code }
|
||||
end
|
||||
|
||||
let!(:test_tag_name) { TagName.create!(name: 'TestPage') }
|
||||
let!(:test_tag_name) { create(:tag_name, name: 'TestPage') }
|
||||
|
||||
let!(:page) do
|
||||
Wiki::Commit.create_content!(
|
||||
@@ -275,8 +274,8 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
end
|
||||
|
||||
it 'wiki body だけを変更しても tag version は作成しない' do
|
||||
linked_tag_name = TagName.create!(name: 'wiki_body_only_tag')
|
||||
linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general)
|
||||
linked_tag_name = create(:tag_name, name: 'wiki_body_only_tag')
|
||||
linked_tag = create(:tag, primary_tag_name: linked_tag_name, category: :general)
|
||||
|
||||
TagVersionRecorder.record!(
|
||||
tag: linked_tag,
|
||||
@@ -359,13 +358,13 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
describe 'GET /wiki/search' do
|
||||
before do
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: TagName.create!(name: 'spec_wiki_title_2'),
|
||||
tag_name: create(:tag_name, name: 'spec_wiki_title_2'),
|
||||
body: 'search body 2',
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: TagName.create!(name: 'unrelated_title'),
|
||||
tag_name: create(:tag_name, name: 'unrelated_title'),
|
||||
body: 'unrelated body',
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
@@ -434,7 +433,7 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
|
||||
it 'returns empty array when page has no revisions and filtered by id' do
|
||||
# 別ページを作って revision 無し
|
||||
tn2 = TagName.create!(name: 'spec_no_rev')
|
||||
tn2 = create(:tag_name, name: 'spec_no_rev')
|
||||
# 異常データ: revision 無し WikiPage を直接作る
|
||||
p2 = WikiPage.create!(
|
||||
tag_name: tn2,
|
||||
@@ -514,7 +513,7 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
|
||||
describe 'Wiki::Commit.redirect!' do
|
||||
it 'raises because redirect revisions are deprecated' do
|
||||
target_tag_name = TagName.create!(name: 'redirect_deprecated_target')
|
||||
target_tag_name = create(:tag_name, name: 'redirect_deprecated_target')
|
||||
target =
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: target_tag_name,
|
||||
@@ -535,8 +534,8 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
end
|
||||
|
||||
it 'wiki title を変更すると対応する tag の version を作成する' do
|
||||
linked_tag_name = TagName.create!(name: 'wiki_linked_tag_for_version')
|
||||
linked_tag = Tag.create!(tag_name: linked_tag_name, category: :general)
|
||||
linked_tag_name = create(:tag_name, name: 'wiki_linked_tag_for_version')
|
||||
linked_tag = create(:tag, primary_tag_name: linked_tag_name, category: :general)
|
||||
|
||||
linked_page =
|
||||
Wiki::Commit.create_content!(
|
||||
@@ -564,7 +563,8 @@ RSpec.describe 'Wiki API', type: :request do
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
linked_tag.reload
|
||||
expect(linked_tag.name).to eq('wiki_linked_tag_for_version_renamed')
|
||||
expect(primary_tag_name_for(linked_tag, 'ja').name)
|
||||
.to eq('wiki_linked_tag_for_version_renamed')
|
||||
|
||||
versions = linked_tag.tag_versions.order(:version_no)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ RSpec.describe 'Wiki title collision', type: :request do
|
||||
|
||||
def create_wiki_page title:, body:
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: TagName.create!(name: title),
|
||||
tag_name: create(:tag_name, name: title),
|
||||
body:,
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
|
||||
@@ -2,7 +2,7 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe MaterialSyncImporter do
|
||||
let(:user) { create(:user, :member) }
|
||||
let(:tag) { Tag.create!(tag_name: TagName.create!(name: 'sync_tag'), category: :material) }
|
||||
let(:tag) { create(:tag, primary_name: 'sync_tag', category: :material) }
|
||||
|
||||
def tempfile_for body
|
||||
Tempfile.new(['material-sync-importer', '.png']).tap do |file|
|
||||
|
||||
@@ -65,7 +65,7 @@ RSpec.describe NicoTagVersionRecorder do
|
||||
end
|
||||
|
||||
it 'records sorted linked internal names and later link removal' do
|
||||
tags = ['z_link', 'a_link'].map { |name| Tag.create!(name:, category: :general) }
|
||||
tags = ['z_link', 'a_link'].map { |name| create(:tag, primary_name: name, category: :general) }
|
||||
tags.each { |tag| NicoTagRelation.create!(nico_tag: external_tag, tag:) }
|
||||
|
||||
expect(record(:create).linked_tags).to eq('a_link z_link')
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe PostBulkCreator do
|
||||
include_context 'English locale'
|
||||
|
||||
it 'limits workers to two and keeps failures in their request slots' do
|
||||
locale = Locale.nipponese
|
||||
actor = instance_double(User, id: 123)
|
||||
allow(User).to receive(:find).with(123) {
|
||||
instance_double(User, id: 123)
|
||||
@@ -9,6 +12,7 @@ RSpec.describe PostBulkCreator do
|
||||
mutex = Mutex.new
|
||||
active = 0
|
||||
maximum_active = 0
|
||||
creators = []
|
||||
|
||||
allow(PostCreatePreflight).to receive(:new) do |attributes:, **|
|
||||
preflight = instance_double(PostCreatePreflight)
|
||||
@@ -28,6 +32,7 @@ RSpec.describe PostBulkCreator do
|
||||
end
|
||||
allow(PostCreator).to receive(:new) do |attributes:, **|
|
||||
creator = instance_double(PostCreator)
|
||||
mutex.synchronize { creators << creator }
|
||||
if attributes[:title] == 'broken'
|
||||
allow(creator).to receive(:create!).and_raise(StandardError, 'broken')
|
||||
else
|
||||
@@ -45,7 +50,12 @@ RSpec.describe PostBulkCreator do
|
||||
results = described_class.new(
|
||||
actor:,
|
||||
posts:,
|
||||
thumbnails: { }).run.fetch(:results)
|
||||
thumbnails: { }).run(locale).fetch(:results)
|
||||
|
||||
expect(creators.length).to eq(posts.length)
|
||||
creators.each do |creator|
|
||||
expect(creator).to have_received(:create!).with(locale).once
|
||||
end
|
||||
|
||||
expect(maximum_active).to eq(2)
|
||||
expect(results.length).to eq(posts.length)
|
||||
@@ -56,4 +66,26 @@ RSpec.describe PostBulkCreator do
|
||||
expect(results[2].dig(:post, :id)).to eq(3)
|
||||
expect(results[3].dig(:post, :id)).to eq(4)
|
||||
end
|
||||
|
||||
it 'passes a future English locale to preflight and creation in every worker' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
actor = instance_double(User, id: 123)
|
||||
allow(User).to receive(:find).with(123).and_return(actor)
|
||||
preflight = instance_double(PostCreatePreflight)
|
||||
creator = instance_double(PostCreator)
|
||||
post = instance_double(Post, id: 1)
|
||||
allow(PostCreatePreflight).to receive(:new).and_return(preflight)
|
||||
allow(preflight).to receive(:run).with(locale).and_return(existing_post_id: nil)
|
||||
allow(PostCreator).to receive(:new).and_return(creator)
|
||||
allow(creator).to receive(:create!).with(locale).and_return(post)
|
||||
|
||||
results = described_class.new(
|
||||
actor:, posts: [{ title: 'future post' }], thumbnails: { }).run(locale)
|
||||
|
||||
expect(results.fetch(:results)).to contain_exactly(
|
||||
hash_including(status: 'created', post: { id: 1 }))
|
||||
expect(preflight).to have_received(:run).with(locale).once
|
||||
expect(creator).to have_received(:create!).with(locale).once
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ require 'rails_helper'
|
||||
|
||||
RSpec.describe PostCreatePlan do
|
||||
def create_tag! name, category
|
||||
Tag.create!(name:, category:)
|
||||
create(:tag, primary_name: name, category:)
|
||||
end
|
||||
|
||||
before do
|
||||
@@ -10,6 +10,63 @@ RSpec.describe PostCreatePlan do
|
||||
create_tag!('ニジラー情報不詳', :meta)
|
||||
end
|
||||
|
||||
context 'with an explicit locale' do
|
||||
include_context 'English locale'
|
||||
|
||||
it 'resolves the matching language identity and its parents' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
english_name = create(:tag_name, name: 'shared_name',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english = create(:tag, primary_tag_name: english_name, category: :character)
|
||||
nipponese = create(:tag, primary_name: 'temporary_nipponese', category: :general)
|
||||
# The DB permits this identity; validation has its own contract spec.
|
||||
primary_tag_name_for(nipponese, 'ja').update_columns(name: 'shared_name')
|
||||
english_parent_name = create(:tag_name, name: 'english_parent',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english_parent = create(:tag, primary_tag_name: english_parent_name,
|
||||
category: :material)
|
||||
nipponese_parent = create(:tag, primary_name: 'nipponese_parent', category: :general)
|
||||
TagImplication.create!(tag: english, parent_tag: english_parent)
|
||||
TagImplication.create!(tag: nipponese, parent_tag: nipponese_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: primary_tag_name_for(english_parent, 'en').name, category: :material })
|
||||
expect(plan[:post_tag_specs].pluck(:name))
|
||||
.not_to include(primary_tag_name_for(nipponese_parent, 'ja').name)
|
||||
end
|
||||
|
||||
it 'resolves aliases within the requested language' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
nipponese = create(:tag, primary_name: 'nipponese_canonical')
|
||||
english_name = create(:tag_name, name: 'english_canonical',
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
english = create(:tag, primary_tag_name: english_name, category: :character)
|
||||
nipponese_alias = create(:tag_name, :alias, name: 'shared_alias',
|
||||
tag: nipponese)
|
||||
english_alias = create(:tag_name, :alias, name: 'shared_alias',
|
||||
tag: english,
|
||||
language_code: 'en', script_code: 'Latn')
|
||||
|
||||
expect(nipponese_alias.reload).to have_attributes(
|
||||
tag_id: nipponese.id,
|
||||
primary_flg: false, language_code: 'ja')
|
||||
expect(english_alias.reload).to have_attributes(
|
||||
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.name, category: :character }])
|
||||
end
|
||||
end
|
||||
|
||||
it 'plans direct and existing default tags without persisting records' do
|
||||
counts = [TagName.count, Tag.count]
|
||||
|
||||
@@ -18,7 +75,7 @@ RSpec.describe PostCreatePlan do
|
||||
url: 'https://example.com/post',
|
||||
title: 'title',
|
||||
tags: 'character:new_character',
|
||||
parent_post_ids: '' }).build!
|
||||
parent_post_ids: '' }).build!(Locale.nipponese)
|
||||
|
||||
expect(plan[:tags]).to eq('new_character')
|
||||
expect(plan[:direct_tag_specs]).to eq(
|
||||
@@ -29,9 +86,9 @@ RSpec.describe PostCreatePlan do
|
||||
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)
|
||||
TagName.create!(name: 'にじか', canonical: canonical.tag_name)
|
||||
it 'resolves aliases and keeps tag sections separate from primary names' do
|
||||
tag = create_tag!('虹夏', :character)
|
||||
create(:tag_name, :alias, name: 'にじか', tag:)
|
||||
create_tag!('動画', :meta)
|
||||
|
||||
plan = described_class.new(
|
||||
@@ -40,7 +97,7 @@ RSpec.describe PostCreatePlan do
|
||||
title: 'video',
|
||||
tags: '動画 にじか[0:10-0:20]',
|
||||
duration: '1:00',
|
||||
parent_post_ids: '' }).build!
|
||||
parent_post_ids: '' }).build!(Locale.nipponese)
|
||||
|
||||
expect(plan[:tags].split).to include('動画', '虹夏[0:10-0:20]')
|
||||
expect(plan[:display_tags]).to include(
|
||||
@@ -55,7 +112,7 @@ RSpec.describe PostCreatePlan do
|
||||
counts = [Tag.count, TagName.count, ExternalTag.count]
|
||||
|
||||
expect {
|
||||
described_class.new(attributes: { tags: 'NiCo:reserved' }).build!
|
||||
described_class.new(attributes: { tags: 'NiCo:reserved' }).build!(Locale.nipponese)
|
||||
}.to raise_error(ActiveRecord::RecordInvalid) { |error|
|
||||
expect(error.record.errors[:tags]).to be_present
|
||||
}
|
||||
@@ -73,7 +130,7 @@ RSpec.describe PostCreatePlan do
|
||||
url: 'https://example.com/post',
|
||||
title: 'title',
|
||||
tags: long_name,
|
||||
parent_post_ids: '' }).build!
|
||||
parent_post_ids: '' }).build!(Locale.nipponese)
|
||||
}.to raise_error(ActiveRecord::RecordInvalid) { |error|
|
||||
expect(error.record.errors[:tags]).not_to be_empty
|
||||
}
|
||||
@@ -87,7 +144,7 @@ RSpec.describe PostCreatePlan do
|
||||
title: 'title',
|
||||
tags: 'ordinary_tag',
|
||||
duration: 'invalid',
|
||||
parent_post_ids: '' }).build!
|
||||
parent_post_ids: '' }).build!(Locale.nipponese)
|
||||
|
||||
expect(plan[:duration]).to eq('invalid')
|
||||
expect(plan[:video_ms]).to be_nil
|
||||
|
||||
@@ -14,7 +14,6 @@ RSpec.describe PostCreator do
|
||||
end
|
||||
|
||||
before do
|
||||
allow(Tag).to receive(:normalise_tags!).and_return({ tags: [], sections: {} })
|
||||
allow(TagVersioning).to receive(:record_tag_snapshots!)
|
||||
allow(Tag).to receive(:expand_parent_tags).and_return([])
|
||||
allow(PostVersionRecorder).to receive(:record!)
|
||||
@@ -34,7 +33,7 @@ RSpec.describe PostCreator do
|
||||
url: 'https://example.com/post',
|
||||
thumbnail: real_thumbnail_upload,
|
||||
thumbnail_base: 'https://example.com/thumb.jpg',
|
||||
tags: '' }).create!
|
||||
tags: '' }).create!(Locale.nipponese)
|
||||
|
||||
expect(post.thumbnail).to be_attached
|
||||
expect(post.thumbnail_base).to eq('https://example.com/thumb.jpg')
|
||||
@@ -54,7 +53,7 @@ RSpec.describe PostCreator do
|
||||
title: 'title',
|
||||
url: 'https://example.com/post',
|
||||
thumbnail_base: 'https://example.com/thumb.jpg',
|
||||
tags: '' }).create!
|
||||
tags: '' }).create!(Locale.nipponese)
|
||||
|
||||
expect(post.thumbnail_base).to eq('https://example.com/thumb.jpg')
|
||||
expect(post.thumbnail).to be_attached
|
||||
@@ -72,7 +71,36 @@ RSpec.describe PostCreator do
|
||||
tags: '' })
|
||||
|
||||
post_count = Post.count
|
||||
expect { creator.create! }.to raise_error(Post::RemoteThumbnailFetchFailed)
|
||||
expect { creator.create!(Locale.nipponese) }
|
||||
.to raise_error(Post::RemoteThumbnailFetchFailed)
|
||||
expect(Post.count).to eq(post_count)
|
||||
end
|
||||
|
||||
context 'with an explicit locale' do
|
||||
include_context 'English locale'
|
||||
|
||||
[false, true].each do |planned|
|
||||
it "passes locale to tag creation with planned attributes: #{ planned }" do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
locale
|
||||
attributes = {
|
||||
title: 'Locale propagation',
|
||||
url: 'https://example.com/locale-post',
|
||||
tags: 'character:locale_character' }
|
||||
if planned
|
||||
specs = [{ name: 'locale_character', category: :character }]
|
||||
attributes.merge!(snapshot_tag_specs: specs, post_tag_specs: specs)
|
||||
end
|
||||
allow(Tag).to receive(:find_or_create_by_tag_name!).and_call_original
|
||||
|
||||
post = described_class.new(actor:, attributes:).create!(locale)
|
||||
|
||||
expect(Tag).to have_received(:find_or_create_by_tag_name!)
|
||||
.with(locale, 'locale_character', category: :character).at_least(:once)
|
||||
tag_name = TagName.find_by!(language_code: 'en', name: 'locale_character')
|
||||
expect(tag_name).to have_attributes(script_code: 'Latn', primary_flg: true)
|
||||
expect(post.tags).to include(tag_name.tag)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -64,7 +64,7 @@ RSpec.describe PostImportPreviewer do
|
||||
end
|
||||
|
||||
it 'applies metadata to automatic fields and recognises metadata tags' do
|
||||
Tag.create!(name: 'known-tag', category: :general)
|
||||
create(:tag, primary_name: 'known-tag', category: :general)
|
||||
allow(PostMetadataFetcher).to receive(:fetch).and_return(
|
||||
title: 'metadata title',
|
||||
thumbnail_base: 'https://example.com/thumb.jpg',
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe TagVersionRecorder do
|
||||
include_context 'English locale'
|
||||
|
||||
let(:user) { create_member_user! }
|
||||
let!(:tag) { create(:tag, primary_name: '日本語名') }
|
||||
|
||||
before do
|
||||
create(:tag_name, :alias, tag:, name: 'alias_z')
|
||||
create(:tag_name, :alias, tag:, name: 'alias_a')
|
||||
end
|
||||
|
||||
def nipponese_snapshot
|
||||
{ 'name' => '日本語名', 'script_code' => 'Jpan', 'auto_generated' => false,
|
||||
'aliases' => [{ 'name' => 'alias_a', 'script_code' => 'Jpan' },
|
||||
{ 'name' => 'alias_z', 'script_code' => 'Jpan' }] }
|
||||
end
|
||||
|
||||
def prepare_english_names!
|
||||
locale
|
||||
primary_tag_name_for(tag, 'en').update!(name: 'English name', auto_generated: false)
|
||||
create(:tag_name, :alias, tag:, language_code: 'en', script_code: 'Latn',
|
||||
name: 'English alias')
|
||||
end
|
||||
|
||||
it 'snapshots the Nipponese primary and sorted aliases in grouped names' do
|
||||
version = described_class.record!(tag:, event_type: :create, created_by_user: user)
|
||||
|
||||
expect(version.reload.names).to eq('ja' => nipponese_snapshot)
|
||||
end
|
||||
|
||||
it 'does not create a version for an unchanged Nipponese snapshot' do
|
||||
original = described_class.record!(tag:, event_type: :create, created_by_user: user)
|
||||
|
||||
unchanged = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
||||
|
||||
expect(unchanged.version_no).to eq(original.version_no)
|
||||
expect(tag.reload.tag_versions.count).to eq(1)
|
||||
end
|
||||
|
||||
it 'snapshots an unconfirmed system primary with auto_generated true' do
|
||||
primary_tag_name_for(tag, 'ja').update!(auto_generated: true)
|
||||
|
||||
version = described_class.record!(tag:, event_type: :create, created_by_user: user)
|
||||
|
||||
expect(version.names.fetch('ja')).to eq(nipponese_snapshot.merge('auto_generated' => true))
|
||||
end
|
||||
|
||||
it 'keeps a past snapshot after current primary and alias changes' do
|
||||
original = described_class.record!(tag:, event_type: :create, created_by_user: user)
|
||||
primary_tag_name_for(tag, 'ja').update!(name: '改名後')
|
||||
tag.tag_names.find_by!(name: 'alias_z').destroy!
|
||||
|
||||
updated = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
||||
|
||||
expect(original.reload.names).to eq('ja' => nipponese_snapshot)
|
||||
expect(updated.names.fetch('ja')).to include('name' => '改名後')
|
||||
expect(updated.names.fetch('ja').fetch('aliases'))
|
||||
.to eq([{ 'name' => 'alias_a', 'script_code' => 'Jpan' }])
|
||||
expect(updated.version_no).to eq(2)
|
||||
end
|
||||
|
||||
it 'snapshots future languages together without a request locale argument' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
prepare_english_names!
|
||||
|
||||
version = described_class.record!(tag:, event_type: :create, created_by_user: user)
|
||||
|
||||
expect(version.names).to eq(
|
||||
'ja' => nipponese_snapshot,
|
||||
'en' => { 'name' => 'English name', 'script_code' => 'Latn',
|
||||
'auto_generated' => false,
|
||||
'aliases' => [{ 'name' => 'English alias', 'script_code' => 'Latn' }] })
|
||||
end
|
||||
|
||||
it 'creates a version for an English-only change and preserves prior names' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
prepare_english_names!
|
||||
original = described_class.record!(tag:, event_type: :create, created_by_user: user)
|
||||
primary_tag_name_for(tag, 'en').update!(name: 'Renamed')
|
||||
|
||||
updated = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
||||
|
||||
expect(updated.version_no).to eq(2)
|
||||
expect(original.reload.names.fetch('en')).to include('name' => 'English name')
|
||||
expect(updated.names.fetch('en')).to include('name' => 'Renamed')
|
||||
expect(updated.names.fetch('ja')).to eq(nipponese_snapshot)
|
||||
end
|
||||
|
||||
it 'does not create a version for an unchanged multilingual snapshot' do
|
||||
pending '日本語以外の Locale 有効化は別課題で実装する'
|
||||
prepare_english_names!
|
||||
described_class.record!(tag:, event_type: :create, created_by_user: user)
|
||||
|
||||
unchanged = described_class.record!(tag:, event_type: :update, created_by_user: user)
|
||||
|
||||
expect(unchanged.version_no).to eq(1)
|
||||
expect(tag.reload.tag_versions.count).to eq(1)
|
||||
end
|
||||
end
|
||||
@@ -6,7 +6,7 @@ RSpec.describe Wiki::Commit do
|
||||
|
||||
def create_page title:, body: 'initial body'
|
||||
described_class.create_content!(
|
||||
tag_name: TagName.create!(name: title),
|
||||
tag_name: create(:tag_name, name: title),
|
||||
body:,
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
@@ -16,7 +16,7 @@ RSpec.describe Wiki::Commit do
|
||||
it 'creates page, revision, and version with normalised body' do
|
||||
expect {
|
||||
described_class.create_content!(
|
||||
tag_name: TagName.create!(name: 'commit_integrity_create'),
|
||||
tag_name: create(:tag_name, name: 'commit_integrity_create'),
|
||||
body: "a\r\nb\r\n\r\n",
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
@@ -36,7 +36,7 @@ RSpec.describe Wiki::Commit do
|
||||
end
|
||||
|
||||
it 'rejects body that becomes blank after normalisation' do
|
||||
tag_name = TagName.create!(name: 'commit_integrity_blank')
|
||||
tag_name = create(:tag_name, name: 'commit_integrity_blank')
|
||||
|
||||
expect {
|
||||
described_class.create_content!(
|
||||
@@ -76,8 +76,8 @@ RSpec.describe Wiki::Commit do
|
||||
end
|
||||
|
||||
it 'does not record tag_version on body-only wiki update' do
|
||||
tag_name = TagName.create!(name: 'commit_integrity_linked_tag')
|
||||
tag = Tag.create!(tag_name:, category: :general)
|
||||
tag_name = create(:tag_name, name: 'commit_integrity_linked_tag')
|
||||
tag = create(:tag, primary_tag_name: tag_name, category: :general)
|
||||
|
||||
page =
|
||||
described_class.create_content!(
|
||||
|
||||
@@ -4,7 +4,7 @@ RSpec.describe Wiki::Commit do
|
||||
let(:user) { create_member_user! }
|
||||
|
||||
def create_page(title: 'commit_spec_page', body: 'initial body')
|
||||
tag_name = TagName.create!(name: title)
|
||||
tag_name = create(:tag_name, name: title)
|
||||
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name:,
|
||||
@@ -80,8 +80,8 @@ RSpec.describe Wiki::Commit do
|
||||
end
|
||||
|
||||
it 'does not record tag version when corresponding tag has no versions' do
|
||||
tag_name = TagName.create!(name: 'commit_linked_tag_without_versions')
|
||||
tag = Tag.create!(tag_name:, category: :general)
|
||||
tag_name = create(:tag_name, name: 'commit_linked_tag_without_versions')
|
||||
tag = create(:tag, primary_tag_name: tag_name, category: :general)
|
||||
|
||||
page =
|
||||
described_class.create_content!(
|
||||
@@ -107,8 +107,8 @@ RSpec.describe Wiki::Commit do
|
||||
end
|
||||
|
||||
it 'does not record tag version when corresponding tag has no versions' do
|
||||
tag_name = TagName.create!(name: 'commit_linked_tag_without_versions')
|
||||
tag = Tag.create!(tag_name:, category: :general)
|
||||
tag_name = create(:tag_name, name: 'commit_linked_tag_without_versions')
|
||||
tag = create(:tag, primary_tag_name: tag_name, category: :general)
|
||||
|
||||
page =
|
||||
described_class.create_content!(
|
||||
|
||||
@@ -5,7 +5,7 @@ RSpec.describe WikiVersionRecorder do
|
||||
|
||||
def create_page title:, body: 'body'
|
||||
Wiki::Commit.create_content!(
|
||||
tag_name: TagName.create!(name: title),
|
||||
tag_name: create(:tag_name, name: title),
|
||||
body:,
|
||||
created_by_user: user,
|
||||
message: 'init')
|
||||
|
||||
@@ -172,7 +172,8 @@ RSpec.describe Youtube::Sync do
|
||||
Tag.video
|
||||
Tag.no_deerjikist
|
||||
|
||||
deerjikist_tag = Tag.find_or_create_by_tag_name!('テスト投稿者', category: :deerjikist)
|
||||
deerjikist_tag = Tag.find_or_create_by_tag_name!(
|
||||
Locale.nipponese, 'テスト投稿者', category: :deerjikist)
|
||||
Deerjikist.create!(
|
||||
platform: 'youtube',
|
||||
code: 'UC_MAPPED',
|
||||
@@ -226,7 +227,8 @@ RSpec.describe Youtube::Sync do
|
||||
)
|
||||
PostTag.create!(post:, tag: Tag.no_deerjikist)
|
||||
|
||||
deerjikist_tag = Tag.find_or_create_by_tag_name!('後から判明した投稿者', category: :deerjikist)
|
||||
deerjikist_tag = Tag.find_or_create_by_tag_name!(
|
||||
Locale.nipponese, '後から判明した投稿者', category: :deerjikist)
|
||||
Deerjikist.create!(
|
||||
platform: 'youtube',
|
||||
code: 'UC_MAPPED_LATER',
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
RSpec.shared_context 'English locale' do
|
||||
def prepare_english_reference!
|
||||
Language.find_or_create_by!(code: 'en') { _1.name = 'English' }
|
||||
Script.find_or_create_by!(code: 'Latn') { _1.name = 'Latin' }
|
||||
region_class = Class.new(ApplicationRecord) { self.table_name = 'regions' }
|
||||
region_class.find_or_create_by!(code: 826) do |record|
|
||||
record.alpha_code = 'GB'
|
||||
record.name = 'United Kingdom'
|
||||
end
|
||||
end
|
||||
|
||||
let(:locale) do
|
||||
prepare_english_reference!
|
||||
Locale.find_or_create_by!(code: 'en') do |record|
|
||||
record.language_code = 'en'
|
||||
record.script_code = 'Latn'
|
||||
record.region_code = 826
|
||||
record.variants = ''
|
||||
record.name = 'English'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,8 @@
|
||||
module TestRecords
|
||||
def primary_tag_name_for(tag, language_code)
|
||||
TagName.find_by!(tag_id: tag.id, language_code:, primary_flg: true)
|
||||
end
|
||||
|
||||
def create_member_user!
|
||||
User.create!(name: 'spec user',
|
||||
inheritance_code: SecureRandom.hex(16),
|
||||
|
||||
@@ -8,7 +8,7 @@ RSpec.describe 'nico:sync' do
|
||||
end
|
||||
|
||||
def create_tag!(name, category:)
|
||||
Tag.find_or_create_by_tag_name!(name, category:)
|
||||
Tag.find_or_create_by_tag_name!(Locale.nipponese, name, category:)
|
||||
end
|
||||
|
||||
def link_nico_to_tag!(nico_tag, tag)
|
||||
@@ -16,17 +16,22 @@ RSpec.describe 'nico:sync' do
|
||||
end
|
||||
|
||||
def create_nico_sanitisation_rules!
|
||||
TagNameSanitisationRule.create!(priority: 20,
|
||||
source_pattern: '\\?',
|
||||
replacement: '_')
|
||||
rules = [
|
||||
[20, '\\?', '_'],
|
||||
[40, '_$', ''],
|
||||
[45, '^([^:]+\\:)?_', '\\1']]
|
||||
|
||||
TagNameSanitisationRule.create!(priority: 40,
|
||||
source_pattern: '_$',
|
||||
replacement: '')
|
||||
priorities = rules.map(&:first)
|
||||
source_patterns = rules.map { _1[1] }
|
||||
|
||||
TagNameSanitisationRule.create!(priority: 45,
|
||||
source_pattern: '^([^:]+\\:)?_',
|
||||
replacement: '\\1')
|
||||
scope = TagNameSanitisationRule.unscoped
|
||||
scope.where(priority: priorities)
|
||||
.or(scope.where(source_pattern: source_patterns))
|
||||
.delete_all
|
||||
|
||||
rules.each do |priority, source_pattern, replacement|
|
||||
TagNameSanitisationRule.create!(priority:, source_pattern:, replacement:)
|
||||
end
|
||||
end
|
||||
|
||||
it '既存 post を見つけて、nico tag と linked tag を追加し、差分が出たら bot を付ける' do
|
||||
@@ -64,7 +69,8 @@ RSpec.describe 'nico:sync' do
|
||||
run_rake_task('nico:sync')
|
||||
|
||||
post.reload
|
||||
active_tag_names = post.tags.joins(:tag_name).pluck('tag_names.name')
|
||||
active_tag_names = TagName.where(tag_id: post.tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name)
|
||||
|
||||
expect(active_tag_names).to include('spec_kept')
|
||||
expect(post.external_tags).to contain_exactly(nico)
|
||||
@@ -434,7 +440,9 @@ RSpec.describe 'nico:sync' do
|
||||
|
||||
external = post.external_tags.sole
|
||||
expect(external.name).to eq('raw tag[]')
|
||||
expect(post.tags.map(&:name)).not_to include('bot操作')
|
||||
expect(TagName.where(tag_id: post.tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name))
|
||||
.not_to include('bot操作')
|
||||
expect(post.post_versions.order(:version_no).last.tags_json)
|
||||
.to include('external_tag_id' => external.id)
|
||||
|
||||
@@ -446,7 +454,9 @@ RSpec.describe 'nico:sync' do
|
||||
run_nico_sync_with_tags!([])
|
||||
}.to change(PostVersion, :count).by(1)
|
||||
expect(post.reload.external_tags).to be_empty
|
||||
expect(post.tags.map(&:name)).not_to include('bot操作')
|
||||
expect(TagName.where(tag_id: post.tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name))
|
||||
.not_to include('bot操作')
|
||||
end
|
||||
|
||||
it 'サニタイズ後の既存外部タグを再利用し、その連携タグを記載する' do
|
||||
@@ -563,7 +573,9 @@ RSpec.describe 'nico:sync' do
|
||||
name: 'AAA')
|
||||
expect(ExternalTag.exists?(platform: :nico, name: 'AAA?')).to be(false)
|
||||
|
||||
expect(post.tags.map(&:name)).not_to include('bot操作')
|
||||
expect(TagName.where(tag_id: post.tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name))
|
||||
.not_to include('bot操作')
|
||||
expect(post.post_versions.order(:version_no).last.tags_json)
|
||||
.to include('external_tag_id' => external.id)
|
||||
|
||||
@@ -577,7 +589,9 @@ RSpec.describe 'nico:sync' do
|
||||
}.to change(PostVersion, :count).by(1)
|
||||
|
||||
expect(post.reload.external_tags).to be_empty
|
||||
expect(post.tags.map(&:name)).not_to include('bot操作')
|
||||
expect(TagName.where(tag_id: post.tags.select(:id),
|
||||
language_code: 'ja', primary_flg: true).pluck(:name))
|
||||
.not_to include('bot操作')
|
||||
end
|
||||
|
||||
it 'nico: prefix を含む従来の規則で外部タグ名をサニタイズする' do
|
||||
|
||||
@@ -6,10 +6,10 @@ RSpec.describe 'post_similarity:calc' do
|
||||
|
||||
it 'calculates similarities from active tags only' do
|
||||
# 必要最低限のデータ
|
||||
t1 = Tag.create!(name: "t1")
|
||||
t2 = Tag.create!(name: "t2")
|
||||
t3 = Tag.create!(name: "t3")
|
||||
deprecated_tag = Tag.create!(name: 'deprecated', deprecated_at: Time.current)
|
||||
t1 = create(:tag, primary_name: 't1')
|
||||
t2 = create(:tag, primary_name: 't2')
|
||||
t3 = create(:tag, primary_name: 't3')
|
||||
deprecated_tag = create(:tag, primary_name: 'deprecated', deprecated_at: Time.current)
|
||||
|
||||
p1 = Post.create!(url: "https://example.com/1")
|
||||
p2 = Post.create!(url: "https://example.com/2")
|
||||
|
||||
@@ -6,10 +6,10 @@ RSpec.describe 'tag_similarity:calc' do
|
||||
|
||||
it 'calculates similarities for active tags only' do
|
||||
# 必要最低限のデータ
|
||||
t1 = Tag.create!(name: "t1")
|
||||
t2 = Tag.create!(name: "t2")
|
||||
t3 = Tag.create!(name: "t3")
|
||||
deprecated_tag = Tag.create!(name: 'deprecated', deprecated_at: Time.current)
|
||||
t1 = create(:tag, primary_name: 't1')
|
||||
t2 = create(:tag, primary_name: 't2')
|
||||
t3 = create(:tag, primary_name: 't3')
|
||||
deprecated_tag = create(:tag, primary_name: 'deprecated', deprecated_at: Time.current)
|
||||
|
||||
p1 = Post.create!(url: "https://example.com/1")
|
||||
p2 = Post.create!(url: "https://example.com/2")
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする