このコミットが含まれているのは:
@@ -0,0 +1,191 @@
|
||||
require 'rails_helper'
|
||||
require_relative '../../db/migrate/20260921040000_create_external_tags'
|
||||
require_relative '../../db/migrate/20260921050000_create_post_external_tags'
|
||||
require_relative '../../db/migrate/20260921060000_change_foreign_key_on_nico_tag_relations'
|
||||
require_relative '../../db/migrate/20260921230000_migrate_external_tags'
|
||||
|
||||
RSpec.describe 'external tag migrations' do
|
||||
self.use_transactional_tests = false
|
||||
|
||||
before do
|
||||
record_class = Class.new(ActiveRecord::Base) do
|
||||
self.abstract_class = true
|
||||
end
|
||||
stub_const('ExternalTagMigrationRecord', record_class)
|
||||
config = ActiveRecord::Base.connection_db_config.configuration_hash
|
||||
@database = "btrc_hub_test_external_tags_#{ Process.pid }_#{ SecureRandom.hex(4) }"
|
||||
record_class.establish_connection(config.merge(database: nil))
|
||||
@connection = record_class.lease_connection
|
||||
@connection.create_database(@database)
|
||||
@database_created = true
|
||||
@connection.execute("USE #{ @connection.quote_table_name(@database) }")
|
||||
version_class = Class.new(record_class) do
|
||||
self.table_name = 'post_versions'
|
||||
end
|
||||
stub_const('MigrateExternalTags::MigrationPostVersion', version_class)
|
||||
create_legacy_tables
|
||||
seed_legacy_records
|
||||
end
|
||||
|
||||
after do
|
||||
@connection.drop_database(@database) if @database_created
|
||||
ensure
|
||||
ExternalTagMigrationRecord.remove_connection
|
||||
end
|
||||
|
||||
def migrate klass
|
||||
migration = klass.new
|
||||
allow(migration).to receive(:connection).and_return(@connection)
|
||||
migration.suppress_messages { migration.migrate(:up) }
|
||||
end
|
||||
|
||||
def create_legacy_tables
|
||||
@connection.create_table(:tag_names) { |t| t.string :name }
|
||||
@connection.create_table(:tags) do |t|
|
||||
t.references :tag_name, foreign_key: true
|
||||
t.string :category
|
||||
t.integer :post_count
|
||||
t.datetime :created_at
|
||||
end
|
||||
@connection.create_table(:posts)
|
||||
@connection.create_table(:post_tags) do |t|
|
||||
t.references :post, foreign_key: true
|
||||
t.references :tag, foreign_key: true
|
||||
t.datetime :created_at
|
||||
end
|
||||
@connection.create_table(:nico_tag_versions) do |t|
|
||||
t.bigint :tag_id
|
||||
t.integer :version_no
|
||||
t.string :name
|
||||
t.datetime :created_at
|
||||
end
|
||||
@connection.create_table(:nico_tag_relations) do |t|
|
||||
t.references :tag, foreign_key: true
|
||||
t.references :nico_tag, foreign_key: { to_table: :tags }
|
||||
end
|
||||
[:post_tag_sections, :materials, :theatre_skip_event_tags].each do |table|
|
||||
@connection.create_table(table) { |t| t.references :tag, foreign_key: true }
|
||||
end
|
||||
@connection.create_table(:tag_implications) do |t|
|
||||
t.references :tag, foreign_key: true
|
||||
t.references :parent_tag, foreign_key: { to_table: :tags }
|
||||
end
|
||||
@connection.create_table(:tag_similarities) do |t|
|
||||
t.references :tag, foreign_key: true
|
||||
t.references :target_tag, foreign_key: { to_table: :tags }
|
||||
end
|
||||
@connection.create_table(:post_versions) { |t| t.json :tags_json, null: false }
|
||||
@connection.add_check_constraint(:post_versions, 'JSON_VALID(tags_json)',
|
||||
name: 'chk_post_versions_tags_json_schema')
|
||||
end
|
||||
|
||||
def seed_legacy_records
|
||||
@connection.execute(<<~SQL)
|
||||
INSERT INTO tag_names (id, name) VALUES (1, 'internal'), (2, 'nico:raw tag[]');
|
||||
SQL
|
||||
@connection.execute(<<~SQL)
|
||||
INSERT INTO tags (id, tag_name_id, category, post_count, created_at) VALUES
|
||||
(1, 1, 'general', 1, '2026-09-01'), (2, 2, 'nico', 1, '2026-09-02')
|
||||
SQL
|
||||
@connection.execute('INSERT INTO posts (id) VALUES (1)')
|
||||
@connection.execute(<<~SQL)
|
||||
INSERT INTO post_tags (post_id, tag_id, created_at) VALUES
|
||||
(1, 1, '2026-09-03'), (1, 2, '2026-09-04')
|
||||
SQL
|
||||
@connection.execute(<<~SQL)
|
||||
INSERT INTO nico_tag_versions (tag_id, version_no, name, created_at) VALUES
|
||||
(2, 1, 'nico:raw tag[]', '2026-09-02'),
|
||||
(3, 1, 'nico:deleted', '2026-09-01'),
|
||||
(3, 2, 'nico:deleted_later', '2026-09-02')
|
||||
SQL
|
||||
@connection.execute('INSERT INTO nico_tag_relations (tag_id, nico_tag_id) VALUES (1, 2)')
|
||||
@connection.execute(<<~SQL)
|
||||
INSERT INTO tag_similarities (tag_id, target_tag_id) VALUES (1, 1), (1, 2), (2, 1)
|
||||
SQL
|
||||
@connection.execute('INSERT INTO theatre_skip_event_tags (tag_id) VALUES (1), (2)')
|
||||
@legacy_internal = { 'id' => 1, 'version_no' => 2,
|
||||
'name' => 'internal', 'category' => 'general',
|
||||
'sections' => [{ 'begin_ms' => 1000, 'end_ms' => nil }] }
|
||||
snapshots = [@legacy_internal,
|
||||
{ 'id' => 2, 'category' => 'nico' },
|
||||
{ 'id' => 3, 'category' => 'nico' }]
|
||||
@connection.execute(<<~SQL)
|
||||
INSERT INTO post_versions (tags_json) VALUES
|
||||
(#{ @connection.quote(snapshots.to_json) }), ('[]')
|
||||
SQL
|
||||
end
|
||||
|
||||
def prepare_external_tables
|
||||
migrate(CreateExternalTags)
|
||||
migrate(CreatePostExternalTags)
|
||||
migrate(ChangeForeignKeyOnNicoTagRelations)
|
||||
end
|
||||
|
||||
it 'preserves ids, raw names, counts, timestamps, post links and historical-only tags' do
|
||||
prepare_external_tables
|
||||
|
||||
rows = @connection.select_rows(<<~SQL)
|
||||
SELECT id, platform, name, post_count, DATE_FORMAT(created_at, '%Y-%m-%d')
|
||||
FROM external_tags ORDER BY id
|
||||
SQL
|
||||
expect(rows).to eq([
|
||||
[2, 'nico', 'raw tag[]', 1, '2026-09-02'],
|
||||
[3, 'nico', 'deleted', 0, '2026-09-01']])
|
||||
expect(@connection.select_rows(<<~SQL)).to eq([[1, 2, '2026-09-04']])
|
||||
SELECT post_id, external_tag_id, DATE_FORMAT(created_at, '%Y-%m-%d')
|
||||
FROM post_external_tags
|
||||
SQL
|
||||
expect(@connection.foreign_key_exists?(:nico_tag_relations, :external_tags,
|
||||
column: :nico_tag_id)).to be(true)
|
||||
expect(@connection.foreign_key_exists?(:nico_tag_relations, :tags,
|
||||
column: :tag_id)).to be(true)
|
||||
end
|
||||
|
||||
it 'converts snapshots and removes only obsolete internal rows and derived links' do
|
||||
prepare_external_tables
|
||||
migrate(MigrateExternalTags)
|
||||
|
||||
expect(@connection.select_values('SELECT id FROM tags')).to eq([1])
|
||||
expect(@connection.select_values('SELECT id FROM tag_names')).to eq([1])
|
||||
expect(@connection.select_values('SELECT tag_id FROM post_tags')).to eq([1])
|
||||
expect(@connection.select_rows('SELECT tag_id, nico_tag_id FROM nico_tag_relations'))
|
||||
.to eq([[1, 2]])
|
||||
expect(@connection.select_rows('SELECT tag_id, target_tag_id FROM tag_similarities'))
|
||||
.to eq([[1, 1]])
|
||||
expect(@connection.select_values('SELECT tag_id FROM theatre_skip_event_tags')).to eq([1])
|
||||
expect(@connection.select_value('SELECT COUNT(*) FROM nico_tag_versions')).to eq(3)
|
||||
versions = MigrateExternalTags::MigrationPostVersion.order(:id)
|
||||
expect(versions.first.tags_json).to eq([
|
||||
@legacy_internal.except('id').merge('tag_id' => 1),
|
||||
{ 'external_tag_id' => 2 }, { 'external_tag_id' => 3 }])
|
||||
expect(versions.last.tags_json).to eq([])
|
||||
|
||||
invalid_snapshots = [@legacy_internal,
|
||||
{ 'external_tag_id' => 0 },
|
||||
{ 'external_tag_id' => 2, 'tag_id' => 1 },
|
||||
{ 'external_tag_id' => 2, 'name' => 'extra' },
|
||||
{ 'tag_id' => 1, 'version_no' => 1 }]
|
||||
invalid_snapshots.each do |snapshot|
|
||||
expect {
|
||||
MigrateExternalTags::MigrationPostVersion.create!(tags_json: [snapshot])
|
||||
}.to raise_error(ActiveRecord::StatementInvalid, /check constraint/i)
|
||||
end
|
||||
expect { MigrateExternalTags.new.down }
|
||||
.to raise_error(ActiveRecord::IrreversibleMigration)
|
||||
end
|
||||
|
||||
{ post_tag_sections: '(tag_id) VALUES (2)',
|
||||
materials: '(tag_id) VALUES (2)',
|
||||
tag_implications: '(tag_id, parent_tag_id) VALUES (1, 2)' }.each do |table, values|
|
||||
it "aborts before deleting data when #{ table } references a legacy nico tag" do
|
||||
prepare_external_tables
|
||||
@connection.execute("INSERT INTO #{ table } #{ values }")
|
||||
|
||||
expect { migrate(MigrateExternalTags) }.to raise_error(RuntimeError, /#{ table }/)
|
||||
|
||||
expect(@connection.select_values('SELECT id FROM tags ORDER BY id')).to eq([1, 2])
|
||||
expect(MigrateExternalTags::MigrationPostVersion.first.tags_json.first)
|
||||
.to eq(@legacy_internal)
|
||||
end
|
||||
end
|
||||
end
|
||||
新しいイシューから参照
ユーザーをブロックする