このコミットが含まれているのは:
2026-11-03 13:37:09 +09:00
コミット ad04c03e14
34個のファイルの変更、554行の追加、114行の削除
+1
ファイルの表示
@@ -102,6 +102,7 @@ RSpec.describe 'Tag group persistence constraints' do
6.times { nodes = [{ 'tag_id' => tag.id, 'children' => nodes }] } 6.times { nodes = [{ 'tag_id' => tag.id, 'children' => nodes }] }
reject_statement do reject_statement do
insert_group_row('tag_group_versions', { insert_group_row('tag_group_versions', {
event_id: create_event!(event_type: 'unknown').id,
tag_group_id: group_id, version_no: 2, event_type: 'update', name: '制約確認', tag_group_id: group_id, version_no: 2, event_type: 'update', name: '制約確認',
members: [{ 'tag_id' => tag.id, 'version_no' => tag.version_no }].to_json, members: [{ 'tag_id' => tag.id, 'version_no' => tag.version_no }].to_json,
placements: nodes.to_json, created_at: Time.current }) placements: nodes.to_json, created_at: Time.current })
+1
ファイルの表示
@@ -10,6 +10,7 @@ RSpec.describe 'TagVersion names database constraint' do
def insert_snapshot!(snapshot) def insert_snapshot!(snapshot)
TagVersion.insert_all!([{ tag_id: tag.id, version_no: 1, event_type: 'create', TagVersion.insert_all!([{ tag_id: tag.id, version_no: 1, event_type: 'create',
event_id: create_event!(event_type: 'unknown').id,
name: 'snapshot_name', category: 'general', name: 'snapshot_name', category: 'general',
aliases: '', parent_tag_ids: '', names: snapshot, aliases: '', parent_tag_ids: '', names: snapshot,
created_at: Time.current }]) created_at: Time.current }])
+21 -7
ファイルの表示
@@ -9,7 +9,9 @@ RSpec.describe HistoryAccess do
locale locale
rows = ['ja', 'ain'].flat_map do |language| rows = ['ja', 'ain'].flat_map do |language|
['create', 'discard', 'restore'].each_with_index.map do |event, index| ['create', 'discard', 'restore'].each_with_index.map do |event, index|
TagDescriptionVersion.create!(tag_id: tag.id, language_code: language, TagDescriptionVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag_id: tag.id, language_code: language,
version_no: index + 1, event_type: language == 'ain' ? 'update' : event, version_no: index + 1, event_type: language == 'ain' ? 'update' : event,
body: "#{ language } #{ index }", created_at: Time.utc(2024)) body: "#{ language } #{ index }", created_at: Time.utc(2024))
end end
@@ -28,7 +30,9 @@ RSpec.describe HistoryAccess do
public_metadata = described_class.set(user: nil) { described_class.authority } public_metadata = described_class.set(user: nil) { described_class.authority }
admin = create(:user, :admin) admin = create(:user, :admin)
admin_metadata = described_class.set(user: admin) { described_class.authority } admin_metadata = described_class.set(user: admin) { described_class.authority }
TagVersion.create!(tag:, version_no: 1, event_type: 'discard', name: 'private', TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag:, version_no: 1, event_type: 'discard', name: 'private',
category: 'general', category: 'general',
names: { ja: { name: 'private', script_code: 'Jpan', names: { ja: { name: 'private', script_code: 'Jpan',
auto_generated: false, aliases: [] } }, auto_generated: false, aliases: [] } },
@@ -52,7 +56,9 @@ RSpec.describe HistoryAccess do
blob = ActiveStorage::Blob.create_before_direct_upload!(filename: 'private.png', blob = ActiveStorage::Blob.create_before_direct_upload!(filename: 'private.png',
byte_size: 1, checksum: Base64.strict_encode64('checksum'), content_type: 'image/png') byte_size: 1, checksum: Base64.strict_encode64('checksum'), content_type: 'image/png')
ActiveStorage::Attachment.create!(name: 'thumbnail', record: post, blob:) ActiveStorage::Attachment.create!(name: 'thumbnail', record: post, blob:)
PostVersion.create!(post:, version_no: 1, event_type: 'discard', url: post.url, PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: 1, event_type: 'discard', url: post.url,
title: 'private', tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024)) title: 'private', tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024))
variant = ActiveStorage::VariantRecord.create!(blob:, variation_digest: 'spec-variation') variant = ActiveStorage::VariantRecord.create!(blob:, variation_digest: 'spec-variation')
derived = ActiveStorage::Blob.create_before_direct_upload!(filename: 'derived.png', derived = ActiveStorage::Blob.create_before_direct_upload!(filename: 'derived.png',
@@ -78,7 +84,9 @@ RSpec.describe 'Counts artifact policy and external references' do
it 'does not expose a private linked tag name through an external history snapshot' do it 'does not expose a private linked tag name through an external history snapshot' do
tag = create(:tag, primary_name: 'private-linked-name') tag = create(:tag, primary_name: 'private-linked-name')
TagVersion.create!(tag:, version_no: 1, event_type: 'discard', name: 'private-linked-name', TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag:, version_no: 1, event_type: 'discard', name: 'private-linked-name',
category: 'general', aliases: '', parent_tag_ids: '', created_at: Time.utc(2024), category: 'general', aliases: '', parent_tag_ids: '', created_at: Time.utc(2024),
names: { ja: { name: 'private-linked-name', script_code: 'Jpan', names: { ja: { name: 'private-linked-name', script_code: 'Jpan',
auto_generated: false, aliases: [] } }) auto_generated: false, aliases: [] } })
@@ -87,7 +95,9 @@ RSpec.describe 'Counts artifact policy and external references' do
name: 'nico:public-external', linked_tags: 'private-linked-name', name: 'nico:public-external', linked_tags: 'private-linked-name',
created_at: Time.utc(2024)) created_at: Time.utc(2024))
post = create(:post) post = create(:post)
snapshot = PostVersion.create!(post:, version_no: 1, event_type: 'create', snapshot = PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: 1, event_type: 'create',
title: 'external reference', url: post.url, tags: '', title: 'external reference', url: post.url, tags: '',
tags_json: [{ external_tag_id: external.id }], parent_post_ids: '', tags_json: [{ external_tag_id: external.id }], parent_post_ids: '',
created_at: Time.utc(2024, 2)) created_at: Time.utc(2024, 2))
@@ -112,13 +122,17 @@ RSpec.describe 'Counts artifact policy and external references' do
child = create(:tag, primary_name: 'referencing child') child = create(:tag, primary_name: 'referencing child')
[[parent, 'discard', '', 'private parent'], [[parent, 'discard', '', 'private parent'],
[child, 'create', parent.id.to_s, 'referencing child']].each do |tag, event, parents, name| [child, 'create', parent.id.to_s, 'referencing child']].each do |tag, event, parents, name|
TagVersion.create!(tag:, version_no: 1, event_type: event, name:, TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag:, version_no: 1, event_type: event, name:,
category: 'general', aliases: '', parent_tag_ids: parents, created_at: Time.utc(2023), category: 'general', aliases: '', parent_tag_ids: parents, created_at: Time.utc(2023),
names: { ja: { name:, script_code: 'Jpan', names: { ja: { name:, script_code: 'Jpan',
auto_generated: false, aliases: [] } }) auto_generated: false, aliases: [] } })
end end
post = create(:post) post = create(:post)
row = PostVersion.create!(post:, version_no: 1, event_type: 'create', title: 'snapshot', row = PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: 1, event_type: 'create', title: 'snapshot',
url: post.url, tags: '', parent_post_ids: '', url: post.url, tags: '', parent_post_ids: '',
tags_json: [{ tag_id: child.id, version_no: 1, name: 'referencing child', sections: [] }], tags_json: [{ tag_id: child.id, version_no: 1, name: 'referencing child', sections: [] }],
created_at: Time.utc(2024)) created_at: Time.utc(2024))
+24 -1
ファイルの表示
@@ -23,9 +23,22 @@ RSpec.describe 'Locale activation', type: :model do
.and_raise('backfill failed') .and_raise('backfill failed')
end end
it 'groups all tag histories from locale creation into one event' do
previous_ids = TagVersion.pluck(:id)
expect { build_locale.save! }.to change { event_rows.length }.by(1)
event = event_rows.last
expect(event).to include('event_type' => 'locales.create', 'created_by_user_id' => nil)
versions = TagVersion.where.not(id: previous_ids)
expect(versions.pluck(:tag_id)).to include(first.id, second.id)
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end
it 'rolls back a newly created locale and the preceding generated names' do it 'rolls back a newly created locale and the preceding generated names' do
locale = build_locale locale = build_locale
fail_second_generation! fail_second_generation!
events = event_rows
expect { locale.save! }.to raise_error('backfill failed') expect { locale.save! }.to raise_error('backfill failed')
@@ -33,6 +46,7 @@ RSpec.describe 'Locale activation', type: :model do
expect(generated_names).to be_empty expect(generated_names).to be_empty
expect(first.reload.name('ja')).to eq('activation_first') expect(first.reload.name('ja')).to eq('activation_first')
expect(second.reload.name('ja')).to eq('activation_second') expect(second.reload.name('ja')).to eq('activation_second')
expect(event_rows).to eq(events)
end end
it 'does not backfill while the locale is inactive' do it 'does not backfill while the locale is inactive' do
@@ -45,7 +59,14 @@ RSpec.describe 'Locale activation', type: :model do
locale = build_locale(deprecated_at: Time.current) locale = build_locale(deprecated_at: Time.current)
locale.save! locale.save!
locale.update!(deprecated_at: nil) previous_ids = TagVersion.pluck(:id)
expect { locale.update!(deprecated_at: nil) }.to change { event_rows.length }.by(1)
event = event_rows.last
expect(event).to include('event_type' => 'locales.restore', 'created_by_user_id' => nil)
versions = TagVersion.where.not(id: previous_ids)
expect(versions.pluck(:tag_id)).to include(first.id, second.id)
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
expect(generated_names.pluck(:tag_id)).to contain_exactly(first.id, second.id) expect(generated_names.pluck(:tag_id)).to contain_exactly(first.id, second.id)
expect(generated_names).to all(have_attributes( expect(generated_names).to all(have_attributes(
@@ -57,11 +78,13 @@ RSpec.describe 'Locale activation', type: :model do
locale.save! locale.save!
previous_deprecated_at = locale.reload.deprecated_at previous_deprecated_at = locale.reload.deprecated_at
fail_second_generation! fail_second_generation!
events = event_rows
expect { locale.update!(deprecated_at: nil) }.to raise_error('backfill failed') expect { locale.update!(deprecated_at: nil) }.to raise_error('backfill failed')
expect(locale.reload.deprecated_at).to eq(previous_deprecated_at) expect(locale.reload.deprecated_at).to eq(previous_deprecated_at)
expect(generated_names).to be_empty expect(generated_names).to be_empty
expect(event_rows).to eq(events)
end end
it 'preserves a human primary through deactivation and repeated activation' do it 'preserves a human primary through deactivation and repeated activation' do
+2 -2
ファイルの表示
@@ -12,6 +12,7 @@ RSpec.describe PostVersion, type: :model do
let!(:post_version) do let!(:post_version) do
PostVersion.create!( PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: post_record, post: post_record,
version_no: 1, version_no: 1,
event_type: 'create', event_type: 'create',
@@ -24,8 +25,7 @@ RSpec.describe PostVersion, type: :model do
original_created_from: post_record.original_created_from, original_created_from: post_record.original_created_from,
original_created_before: post_record.original_created_before, original_created_before: post_record.original_created_before,
created_at: Time.current, created_at: Time.current,
created_by_user: nil created_by_user: nil)
)
end end
it 'is read only after create' do it 'is read only after create' do
+27
ファイルの表示
@@ -34,6 +34,31 @@ RSpec.describe TagNameSanitisationRule, type: :model do
described_class.create!(priority: 10, source_pattern: 'foo_', replacement: 'foo') described_class.create!(priority: 10, source_pattern: 'foo_', replacement: 'foo')
end end
it 'groups renames, merges and affected post histories into one event' do
target = create(:tag, primary_name: 'foobar')
source = create(:tag, primary_name: 'temporary_source')
renamed = create(:tag, primary_name: 'temporary_rename')
source.tag_name('ja').update_columns(name: 'foo_bar')
renamed.tag_name('ja').update_columns(name: 'foo_baz')
post = create(:post)
PostTag.create!(post:, tag: source)
previous_tag_ids = TagVersion.pluck(:id)
previous_post_ids = PostVersion.pluck(:id)
expect { described_class.apply! }.to change { event_rows.length }.by(1)
event = event_rows.last
expect(event).to include(
'event_type' => 'tag_name_sanitisation_rules.apply', 'created_by_user_id' => nil)
tag_versions = TagVersion.where.not(id: previous_tag_ids)
expect(tag_versions.pluck(:tag_id).uniq).to contain_exactly(target.id, source.id, renamed.id)
expect(tag_versions.pluck(:event_type)).to include('discard', 'update')
expect(tag_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
post_versions = PostVersion.where.not(id: previous_post_ids)
expect(post_versions.pluck(:post_id).uniq).to eq([post.id])
expect(post_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end
[:primary, :alias].each do |source_kind| [:primary, :alias].each do |source_kind|
it "keeps the primary identity when a #{ source_kind } collides within its Tag" do it "keeps the primary identity when a #{ source_kind } collides within its Tag" do
tag = create(:tag, primary_name: source_kind == :primary ? 'temporary' : 'foobar') tag = create(:tag, primary_name: source_kind == :primary ? 'temporary' : 'foobar')
@@ -83,6 +108,7 @@ RSpec.describe TagNameSanitisationRule, type: :model do
PostTag.create!(post:, tag: source) PostTag.create!(post:, tag: source)
models = [Tag, TagName, TagVersion, TagDescription, TagDescriptionVersion, PostTag] models = [Tag, TagName, TagVersion, TagDescription, TagDescriptionVersion, PostTag]
snapshots = models.map { |model| model.order(*Array(model.primary_key)).map(&:attributes) } snapshots = models.map { |model| model.order(*Array(model.primary_key)).map(&:attributes) }
events = event_rows
expect { described_class.apply! }.to raise_error(ActiveRecord::RecordInvalid) expect { described_class.apply! }.to raise_error(ActiveRecord::RecordInvalid)
@@ -90,6 +116,7 @@ RSpec.describe TagNameSanitisationRule, type: :model do
model.order(*Array(model.primary_key)).map(&:attributes) model.order(*Array(model.primary_key)).map(&:attributes)
end end
expect(after_state).to eq(snapshots) expect(after_state).to eq(snapshots)
expect(event_rows).to eq(events)
expect(post.reload.tags).to contain_exactly(source) expect(post.reload.tags).to contain_exactly(source)
expect(target.reload.name('ja')).to eq('foobar') expect(target.reload.name('ja')).to eq('foobar')
end end
+2 -2
ファイルの表示
@@ -665,6 +665,7 @@ RSpec.describe Tag, type: :model do
def create_post_version_for!(post, version_no: 1, event_type: 'create', created_by_user: nil) def create_post_version_for!(post, version_no: 1, event_type: 'create', created_by_user: nil)
PostVersion.create!( PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: post, post: post,
version_no: version_no, version_no: version_no,
event_type: event_type, event_type: event_type,
@@ -677,8 +678,7 @@ RSpec.describe Tag, type: :model do
original_created_from: post.original_created_from, original_created_from: post.original_created_from,
original_created_before: post.original_created_before, original_created_before: post.original_created_before,
created_at: Time.current, created_at: Time.current,
created_by_user: created_by_user created_by_user: created_by_user)
)
end end
context 'when post versions are enabled' do context 'when post versions are enabled' do
+2
ファイルの表示
@@ -6,6 +6,7 @@ RSpec.describe VersionRecord, type: :model do
it 'makes TagVersion read only after create' do it 'makes TagVersion read only after create' do
version = TagVersion.create!( version = TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag: tag, tag: tag,
version_no: 1, version_no: 1,
event_type: 'create', event_type: 'create',
@@ -25,6 +26,7 @@ RSpec.describe VersionRecord, type: :model do
it 'prevents TagVersion destroy' do it 'prevents TagVersion destroy' do
version = TagVersion.create!( version = TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag: tag, tag: tag,
version_no: 1, version_no: 1,
event_type: 'create', event_type: 'create',
+1
ファイルの表示
@@ -44,6 +44,7 @@ RSpec.configure do |config|
end end
config.include TestRecords config.include TestRecords
config.include EventRecords
config.include RakeTaskHelper config.include RakeTaskHelper
# FactoryBot の create / create_list を使へるやぅにする # FactoryBot の create / create_list を使へるやぅにする
+3 -1
ファイルの表示
@@ -84,7 +84,9 @@ RSpec.describe 'Counts native GET acknowledgement', type: :request do
expect(response).to have_http_status(:not_found) expect(response).to have_http_status(:not_found)
job, file, attempt = ready_job('json') job, file, attempt = ready_job('json')
record = create(:post) record = create(:post)
PostVersion.create!(post: record, version_no: 1, event_type: 'discard', title: 'private', PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: record, version_no: 1, event_type: 'discard', title: 'private',
url: record.url, tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024)) url: record.url, tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024))
get "/counts/export/files/#{ file }", params: { job:, attempt: } get "/counts/export/files/#{ file }", params: { job:, attempt: }
expect(response).to have_http_status(:not_found) expect(response).to have_http_status(:not_found)
+3 -1
ファイルの表示
@@ -8,7 +8,9 @@ RSpec.describe 'History boundaries shared with Counts', type: :request do
before do before do
['create', 'discard'].each_with_index do |event, index| ['create', 'discard'].each_with_index do |event, index|
TagDescriptionVersion.create!(tag_id: tag.id, language_code: 'ja', TagDescriptionVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag_id: tag.id, language_code: 'ja',
version_no: index + 1, event_type: event, body: 'private body', version_no: index + 1, event_type: event, body: 'private body',
created_at: Time.utc(2024)) created_at: Time.utc(2024))
end end
+6 -2
ファイルの表示
@@ -10,12 +10,16 @@ RSpec.describe 'Legacy Counts API', type: :request do
category: 'general', sections: [] } category: 'general', sections: [] }
[1, 2, 3].each do |number| [1, 2, 3].each do |number|
time = Time.utc(2024, number == 3 ? 2 : 1, number, 0, 0) time = Time.utc(2024, number == 3 ? 2 : 1, number, 0, 0)
PostVersion.create!(post:, version_no: number, PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: number,
event_type: number == 1 ? 'create' : 'update', event_type: number == 1 ? 'create' : 'update',
tags: '', url: post.url, title: "Snapshot #{ number }", parent_post_ids: '', tags: '', url: post.url, title: "Snapshot #{ number }", parent_post_ids: '',
tags_json: number == 1 ? [snapshot] : [], tags_json: number == 1 ? [snapshot] : [],
created_by_user: number == 3 ? nil : actor, created_at: time) created_by_user: number == 3 ? nil : actor, created_at: time)
TagVersion.create!(tag:, version_no: number, TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag:, version_no: number,
event_type: number == 1 ? 'create' : 'update', event_type: number == 1 ? 'create' : 'update',
name: 'counts tag', category: 'general', aliases: '', parent_tag_ids: '', name: 'counts tag', category: 'general', aliases: '', parent_tag_ids: '',
names: { ja: { name: 'counts tag', script_code: 'Jpan', names: { ja: { name: 'counts tag', script_code: 'Jpan',
+27 -9
ファイルの表示
@@ -14,7 +14,9 @@ RSpec.describe 'Counts table API', type: :request do
end end
before do before do
TagVersion.create!(tag:, version_no: 1, event_type: 'create', name: 'alpha', TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag:, version_no: 1, event_type: 'create', name: 'alpha',
category: 'general', aliases: '', parent_tag_ids: '', category: 'general', aliases: '', parent_tag_ids: '',
names: { ja: { name: 'alpha', script_code: 'Jpan', auto_generated: false, aliases: [] } }, names: { ja: { name: 'alpha', script_code: 'Jpan', auto_generated: false, aliases: [] } },
created_at: Time.utc(2023)) created_at: Time.utc(2023))
@@ -24,7 +26,9 @@ RSpec.describe 'Counts table API', type: :request do
{ tag_id: tag.id, version_no: 1, name: 'alpha', category: 'general', sections: [] }, { tag_id: tag.id, version_no: 1, name: 'alpha', category: 'general', sections: [] },
{ external_tag_id: external.id }] { external_tag_id: external.id }]
[1, 2, 3].each do |number| [1, 2, 3].each do |number|
PostVersion.create!(post: record, version_no: number, PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: record, version_no: number,
event_type: number == 1 ? 'create' : 'update', url: record.url, event_type: number == 1 ? 'create' : 'update', url: record.url,
title: "snapshot #{ number }", tags: '', parent_post_ids: '', title: "snapshot #{ number }", tags: '', parent_post_ids: '',
tags_json: snapshots.first(number == 1 ? 2 : (number == 2 ? 1 : 0)), tags_json: snapshots.first(number == 1 ? 2 : (number == 2 ? 1 : 0)),
@@ -134,7 +138,9 @@ RSpec.describe 'Counts table API', type: :request do
end end
it 'checks the current latest flag beyond the execution timestamp' do it 'checks the current latest flag beyond the execution timestamp' do
PostVersion.create!(post: record, version_no: 4, event_type: 'update', PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: record, version_no: 4, event_type: 'update',
url: record.url, title: 'after execution', tags: '', tags_json: [], url: record.url, title: 'after execution', tags: '', tags_json: [],
parent_post_ids: '', created_at: Time.utc(2025, 2, 1)) parent_post_ids: '', created_at: Time.utc(2025, 2, 1))
result = query result = query
@@ -188,12 +194,16 @@ RSpec.describe 'Counts table API', type: :request do
tag.tag_names.find_by!(language_code: 'ja', primary_flg: true) tag.tag_names.find_by!(language_code: 'ja', primary_flg: true)
.update!(name: 'renamed-alpha') .update!(name: 'renamed-alpha')
create(:tag_name, :alias, tag:, name: 'alpha-alias') create(:tag_name, :alias, tag:, name: 'alpha-alias')
TagVersion.create!(tag:, version_no: 2, event_type: 'update', name: 'renamed-alpha', TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag:, version_no: 2, event_type: 'update', name: 'renamed-alpha',
category: 'general', aliases: 'alpha-alias', parent_tag_ids: '', category: 'general', aliases: 'alpha-alias', parent_tag_ids: '',
names: { ja: { name: 'renamed-alpha', script_code: 'Jpan', auto_generated: false, names: { ja: { name: 'renamed-alpha', script_code: 'Jpan', auto_generated: false,
aliases: [{ name: 'alpha-alias', script_code: 'Jpan' }] } }, aliases: [{ name: 'alpha-alias', script_code: 'Jpan' }] } },
created_at: Time.utc(2024, 2, 1)) created_at: Time.utc(2024, 2, 1))
PostVersion.create!(post: record, version_no: 4, event_type: 'update', PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: record, version_no: 4, event_type: 'update',
url: record.url, title: 'after rename', tags: '', parent_post_ids: '', url: record.url, title: 'after rename', tags: '', parent_post_ids: '',
tags_json: [{ tag_id: tag.id, version_no: 2, name: 'renamed-alpha', tags_json: [{ tag_id: tag.id, version_no: 2, name: 'renamed-alpha',
category: 'general', sections: [] }], category: 'general', sections: [] }],
@@ -257,7 +267,9 @@ RSpec.describe 'Counts table API', type: :request do
it 'reports column overflow separately from malformed input' do it 'reports column overflow separately from malformed input' do
101.times do |index| 101.times do |index|
PostVersion.create!(post: record, version_no: index + 4, PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: record, version_no: index + 4,
event_type: 'update', url: record.url, title: "column #{ index }", event_type: 'update', url: record.url, title: "column #{ index }",
tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024, 3)) tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024, 3))
end end
@@ -301,7 +313,9 @@ RSpec.describe 'Counts table API', type: :request do
sign_in_as(create(:user, role)) if role sign_in_as(create(:user, role)) if role
# Equal timestamps deliberately require version ordering, not a date comparison. # Equal timestamps deliberately require version ordering, not a date comparison.
['discard', 'restore', 'discard', 'restore'].each_with_index do |event, index| ['discard', 'restore', 'discard', 'restore'].each_with_index do |event, index|
PostVersion.create!(post: record, version_no: index + 4, event_type: event, PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: record, version_no: index + 4, event_type: event,
url: record.url, title: event == 'restore' ? 'restored' : 'private snapshot', url: record.url, title: event == 'restore' ? 'restored' : 'private snapshot',
tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024, 3)) tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024, 3))
end end
@@ -329,7 +343,9 @@ RSpec.describe 'Counts table API', type: :request do
end end
it 'does not resolve private condition names even when the result set is empty' do it 'does not resolve private condition names even when the result set is empty' do
TagVersion.create!(tag:, version_no: 2, event_type: 'discard', name: 'secret tag', TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag:, version_no: 2, event_type: 'discard', name: 'secret tag',
category: 'general', aliases: '', parent_tag_ids: '', category: 'general', aliases: '', parent_tag_ids: '',
names: { ja: { name: 'secret tag', script_code: 'Jpan', auto_generated: false, aliases: [] } }, names: { ja: { name: 'secret tag', script_code: 'Jpan', auto_generated: false, aliases: [] } },
created_at: Time.utc(2024, 2)) created_at: Time.utc(2024, 2))
@@ -345,7 +361,9 @@ RSpec.describe 'Counts table API', type: :request do
end end
end end
it 'includes private history in the public timeline without an exclusion count' do it 'includes private history in the public timeline without an exclusion count' do
PostVersion.create!(post: record, version_no: 4, event_type: 'discard', url: record.url, PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: record, version_no: 4, event_type: 'discard', url: record.url,
title: 'hidden title', tags: '', tags_json: [], parent_post_ids: '', title: 'hidden title', tags: '', tags_json: [], parent_post_ids: '',
created_at: Time.utc(2024, 3)) created_at: Time.utc(2024, 3))
result = query(mode: 'summary', rows: 'created_at:month', cols: 'event_type') result = query(mode: 'summary', rows: 'created_at:month', cols: 'event_type')
+40 -1
ファイルの表示
@@ -314,6 +314,7 @@ RSpec.describe 'Materials API', type: :request do
.and change(Tag, :count).by(1) .and change(Tag, :count).by(1)
.and change(TagName, :count).by(1) .and change(TagName, :count).by(1)
.and change(MaterialVersion, :count).by(1) .and change(MaterialVersion, :count).by(1)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:created) expect(response).to have_http_status(:created)
@@ -325,6 +326,12 @@ RSpec.describe 'Materials API', type: :request do
expect(material.file.attached?).to be(true) expect(material.file.attached?).to be(true)
expect(material.version_no).to eq(1) expect(material.version_no).to eq(1)
expect(material.material_versions.first.event_type).to eq('create') expect(material.material_versions.first.event_type).to eq('create')
event = event_rows.last
expect(event).to include(
'event_type' => 'materials.create',
'created_by_user_id' => member_user.id)
expect(material.material_versions.first.event_id).to eq(event.fetch('id'))
expect(material.tag.tag_versions.pluck(:event_id)).to eq([event.fetch('id')])
expect(material.material_export_items.first.export_path).to eq('伊地知ニジカ/created.png') expect(material.material_export_items.first.export_path).to eq('伊地知ニジカ/created.png')
expect(material.material_versions.first.export_paths_json).to eq( expect(material.material_versions.first.export_paths_json).to eq(
'legacy_drive' => '伊地知ニジカ/created.png' 'legacy_drive' => '伊地知ニジカ/created.png'
@@ -509,6 +516,7 @@ RSpec.describe 'Materials API', type: :request do
export_paths: { legacy_drive: '伊地知ニジカ/updated.jpg' } export_paths: { legacy_drive: '伊地知ニジカ/updated.jpg' }
} }
end.to change(MaterialVersion, :count).by(2) end.to change(MaterialVersion, :count).by(2)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@@ -517,6 +525,12 @@ RSpec.describe 'Materials API', type: :request do
expect(material.tag.category).to eq('material') expect(material.tag.category).to eq('material')
expect(material.url).to eq('https://example.com/updated-source') expect(material.url).to eq('https://example.com/updated-source')
expect(material.updated_by_user).to eq(member_user) expect(material.updated_by_user).to eq(member_user)
event = event_rows.last
expect(event).to include(
'event_type' => 'materials.update',
'created_by_user_id' => member_user.id)
expect(material.material_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
expect(material.tag.tag_versions.pluck(:event_id)).to eq([event.fetch('id')])
expect(material.file.attached?).to be(true) expect(material.file.attached?).to be(true)
expect(material.file.blob.id).not_to eq(old_blob_id) expect(material.file.blob.id).not_to eq(old_blob_id)
expect(ActiveStorage::Blob.where(id: old_blob_id).exists?).to be(true) expect(ActiveStorage::Blob.where(id: old_blob_id).exists?).to be(true)
@@ -632,6 +646,7 @@ RSpec.describe 'Materials API', type: :request do
MaterialVersionRecorder.record!(material:, event_type: :create, MaterialVersionRecorder.record!(material:, event_type: :create,
created_by_user: member_user) created_by_user: member_user)
TagVersioning.ensure_snapshot!(tag, created_by_user: member_user) TagVersioning.ensure_snapshot!(tag, created_by_user: member_user)
events = event_rows
expect do expect do
put "/materials/#{ material.id }", params: { put "/materials/#{ material.id }", params: {
@@ -643,6 +658,7 @@ RSpec.describe 'Materials API', type: :request do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(material.reload.version_no).to eq(1) expect(material.reload.version_no).to eq(1)
expect(event_rows).to eq(events)
end end
it 'records update version when only export_path changes' do it 'records update version when only export_path changes' do
@@ -807,6 +823,12 @@ RSpec.describe 'Materials API', type: :request do
it 'creates source suppression as member' do it 'creates source suppression as member' do
sign_in_as(member_user) sign_in_as(member_user)
materials = ['a', 'b'].map do |name|
Material.create!(
source_kind: 'google_drive_file', source_file_id: "blocked-#{ name }",
source_path: "伊地知ニジカ/危険素材/#{ name }.png",
url: "https://example.com/blocked-#{ name }", created_by_user: member_user)
end
expect do expect do
post '/materials/suppressions', params: { post '/materials/suppressions', params: {
@@ -815,6 +837,7 @@ RSpec.describe 'Materials API', type: :request do
reason: 'copyright_high_risk' reason: 'copyright_high_risk'
} }
end.to change(MaterialSyncSuppression, :count).by(1) end.to change(MaterialSyncSuppression, :count).by(1)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:created) expect(response).to have_http_status(:created)
expect(json).to include( expect(json).to include(
@@ -822,6 +845,14 @@ RSpec.describe 'Materials API', type: :request do
'drive_path' => '伊地知ニジカ/危険素材', 'drive_path' => '伊地知ニジカ/危険素材',
'normalized_source_key' => 'google_drive_path_prefix:伊地知ニジカ/危険素材' 'normalized_source_key' => 'google_drive_path_prefix:伊地知ニジカ/危険素材'
) )
event = event_rows.last
expect(event).to include(
'event_type' => 'materials.block', 'created_by_user_id' => member_user.id)
expect(materials.map { _1.reload.discarded? }).to eq([true, true])
versions = MaterialVersion.where(material_id: materials.map(&:id))
expect(versions.where(event_type: 'discard').pluck(:material_id))
.to match_array(materials.map(&:id))
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end end
end end
@@ -860,11 +891,19 @@ RSpec.describe 'Materials API', type: :request do
end end
it 'discards the material and returns 204' do it 'discards the material and returns 204' do
delete "/materials/#{ material.id }" expect { delete "/materials/#{ material.id }" }
.to change { event_rows.length }.by(1)
expect(response).to have_http_status(:no_content) expect(response).to have_http_status(:no_content)
expect(Material.find_by(id: material.id)).to be_nil expect(Material.find_by(id: material.id)).to be_nil
expect(Material.with_discarded.find(material.id)).to be_discarded expect(Material.with_discarded.find(material.id)).to be_discarded
event = event_rows.last
expect(event).to include(
'event_type' => 'materials.destroy',
'created_by_user_id' => member_user.id)
versions = MaterialVersion.where(material_id: material.id)
expect(versions.order(:version_no).last.event_type).to eq('discard')
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end end
end end
end end
+5
ファイルの表示
@@ -177,6 +177,7 @@ RSpec.describe 'NicoTags', type: :request do
} }
}.to change(TagVersion, :count).by(2) }.to change(TagVersion, :count).by(2)
.and change(NicoTagVersion, :count).by(1) .and change(NicoTagVersion, :count).by(1)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@@ -186,6 +187,10 @@ RSpec.describe 'NicoTags', type: :request do
linked_versions = TagVersion.where(tag: [linked_a, linked_b]).order(:tag_id) linked_versions = TagVersion.where(tag: [linked_a, linked_b]).order(:tag_id)
expect(linked_versions.map(&:event_type)).to eq(['create', 'create']) expect(linked_versions.map(&:event_type)).to eq(['create', 'create'])
expect(linked_versions.map(&:created_by_user_id)).to all(eq(admin.id)) expect(linked_versions.map(&:created_by_user_id)).to all(eq(admin.id))
event = event_rows.last
expect(event).to include(
'event_type' => 'tags.update', 'created_by_user_id' => admin.id)
expect(linked_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
versions = nico_tag.reload.nico_tag_versions.order(:version_no) versions = nico_tag.reload.nico_tag_versions.order(:version_no)
expect(versions.map(&:event_type)).to eq(['create', 'update']) expect(versions.map(&:event_type)).to eq(['create', 'update'])
+27 -4
ファイルの表示
@@ -50,6 +50,8 @@ RSpec.describe 'Posts API', type: :request do
post:, post:,
version_no: 1, version_no: 1,
event_type: 'create', event_type: 'create',
event_id: create_event!(
event_type: 'posts.create', created_by_user: post.uploaded_user).id,
title: post.title, title: post.title,
url: post.url, url: post.url,
thumbnail_base: post.thumbnail_base, thumbnail_base: post.thumbnail_base,
@@ -1027,7 +1029,7 @@ RSpec.describe 'Posts API', type: :request do
it 'dry-runs without persisting posts or new tags' do it 'dry-runs without persisting posts or new tags' do
sign_in_as(member) sign_in_as(member)
counts = [Post.count, Tag.count, TagName.count] counts = [Post.count, Tag.count, TagName.count, event_rows.length]
post '/posts?dry=1', params: post_write_params( post '/posts?dry=1', params: post_write_params(
title: 'dry-run post', title: 'dry-run post',
@@ -1043,7 +1045,7 @@ RSpec.describe 'Posts API', type: :request do
[{ 'name' => 'new_dry_run_tag', [{ 'name' => 'new_dry_run_tag',
'category' => 'character', 'category' => 'character',
'section_literals' => [] }]) 'section_literals' => [] }])
expect([Post.count, Tag.count, TagName.count]).to eq(counts) expect([Post.count, Tag.count, TagName.count, event_rows.length]).to eq(counts)
end end
it 'dry-runs an existing URL without validating its upload' do it 'dry-runs an existing URL without validating its upload' do
@@ -2218,6 +2220,7 @@ RSpec.describe 'Posts API', type: :request do
def create_post_version! post, version_no:, event_type:, created_by_user:, created_at: def create_post_version! post, version_no:, event_type:, created_by_user:, created_at:
PostVersion.create!( PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, post:,
version_no:, version_no:,
event_type:, event_type:,
@@ -2230,8 +2233,7 @@ RSpec.describe 'Posts API', type: :request do
original_created_from: post.original_created_from, original_created_from: post.original_created_from,
original_created_before: post.original_created_before, original_created_before: post.original_created_before,
created_at:, created_at:,
created_by_user: created_by_user:)
)
end end
let!(:v1) do let!(:v1) do
@@ -2559,6 +2561,7 @@ RSpec.describe 'Posts API', type: :request do
tags: 'spec_tag', tags: 'spec_tag',
thumbnail: dummy_upload) thumbnail: dummy_upload)
end.to change(PostVersion, :count).by(1) end.to change(PostVersion, :count).by(1)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:created) expect(response).to have_http_status(:created)
@@ -2570,6 +2573,13 @@ RSpec.describe 'Posts API', type: :request do
expect(version.url).to eq('https://example.com/versioned-post') expect(version.url).to eq('https://example.com/versioned-post')
expect(version.created_by_user_id).to eq(member.id) expect(version.created_by_user_id).to eq(member.id)
expect(version.tags).to eq(snapshot_tags(created_post)) expect(version.tags).to eq(snapshot_tags(created_post))
event = event_rows.last
expect(event).to include(
'event_type' => 'posts.create', 'created_by_user_id' => member.id)
expect(version.event_id).to eq(event.fetch('id'))
tag_versions = TagVersion.where(tag_id: created_post.tags.select(:id))
expect(tag_versions).not_to be_empty
expect(tag_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end end
it 'creates next version on PUT /posts/:id when snapshot changes' do it 'creates next version on PUT /posts/:id when snapshot changes' do
@@ -2585,6 +2595,7 @@ RSpec.describe 'Posts API', type: :request do
title: 'updated title', title: 'updated title',
tags: 'spec_tag_2') tags: 'spec_tag_2')
end.to change(PostVersion, :count).by(1) end.to change(PostVersion, :count).by(1)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@@ -2594,6 +2605,12 @@ RSpec.describe 'Posts API', type: :request do
expect(version.title).to eq('updated title') expect(version.title).to eq('updated title')
expect(version.created_by_user_id).to eq(member.id) expect(version.created_by_user_id).to eq(member.id)
expect(version.tags).to eq(snapshot_tags(post_record.reload)) expect(version.tags).to eq(snapshot_tags(post_record.reload))
event = event_rows.last
expect(event).to include(
'event_type' => 'posts.update', 'created_by_user_id' => member.id)
expect(version.event_id).to eq(event.fetch('id'))
tag_version = TagVersion.find_by!(tag_id: tag_name2.reload.tag_id, version_no: 1)
expect(tag_version.event_id).to eq(event.fetch('id'))
end end
it 'does not create a new version on PUT /posts/:id when snapshot is unchanged' do it 'does not create a new version on PUT /posts/:id when snapshot is unchanged' do
@@ -2602,6 +2619,7 @@ RSpec.describe 'Posts API', type: :request do
PostTag.create!(post: post_record, tag: Tag.no_deerjikist) PostTag.create!(post: post_record, tag: Tag.no_deerjikist)
base_version = create_post_version_for!(post_record.reload) base_version = create_post_version_for!(post_record.reload)
events = event_rows
expect { expect {
put "/posts/#{post_record.id}", params: post_write_params( put "/posts/#{post_record.id}", params: post_write_params(
base_version_no: base_version.version_no, base_version_no: base_version.version_no,
@@ -2615,10 +2633,12 @@ RSpec.describe 'Posts API', type: :request do
expect(version.version_no).to eq(1) expect(version.version_no).to eq(1)
expect(version.event_type).to eq('create') expect(version.event_type).to eq('create')
expect(version.tags).to eq(snapshot_tags(post_record)) expect(version.tags).to eq(snapshot_tags(post_record))
expect(event_rows).to eq(events)
end end
it 'does not create a version when POST /posts is invalid' do it 'does not create a version when POST /posts is invalid' do
sign_in_as(member) sign_in_as(member)
events = event_rows
expect do expect do
post '/posts', params: post_write_params( post '/posts', params: post_write_params(
@@ -2629,11 +2649,13 @@ RSpec.describe 'Posts API', type: :request do
end.not_to change(PostVersion, :count) end.not_to change(PostVersion, :count)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_entity)
expect(event_rows).to eq(events)
end end
it 'does not create a version when PUT /posts/:id is invalid' do it 'does not create a version when PUT /posts/:id is invalid' do
sign_in_as(member) sign_in_as(member)
base_version = create_post_version_for!(post_record) base_version = create_post_version_for!(post_record)
events = event_rows
expect do expect do
put "/posts/#{post_record.id}", params: post_write_params( put "/posts/#{post_record.id}", params: post_write_params(
@@ -2645,6 +2667,7 @@ RSpec.describe 'Posts API', type: :request do
end.not_to change(PostVersion, :count) end.not_to change(PostVersion, :count)
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_entity)
expect(event_rows).to eq(events)
end end
it 'rejects second-bearing original created timestamps on POST /posts' do it 'rejects second-bearing original created timestamps on POST /posts' do
+3 -1
ファイルの表示
@@ -119,7 +119,9 @@ RSpec.describe 'Sitemap entries API', type: :request do
# Legacy empty bodies can exist even though the write service rejects them. # Legacy empty bodies can exist even though the write service rejects them.
empty = TagDescription.create!(tag: empty_tag, language_code: 'ain', empty = TagDescription.create!(tag: empty_tag, language_code: 'ain',
body: '', version_no: 1) body: '', version_no: 1)
TagDescriptionVersion.create!(tag_id: empty_tag.id, language_code: 'ain', TagDescriptionVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag_id: empty_tag.id, language_code: 'ain',
body: '', version_no: 1, event_type: 'create', body: '', version_no: 1, event_type: 'create',
created_by_user_id: user.id, created_at: Time.current) created_by_user_id: user.id, created_at: Time.current)
+10
ファイルの表示
@@ -53,8 +53,13 @@ RSpec.describe "TagChildren", type: :request do
expect { do_request } expect { do_request }
.to change { parent.reload.children.ids.include?(child.id) } .to change { parent.reload.children.ids.include?(child.id) }
.from(false).to(true) .from(false).to(true)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:no_content) expect(response).to have_http_status(:no_content)
event = event_rows.last
expect(event).to include(
'event_type' => 'tags.update', 'created_by_user_id' => admin.id)
expect(child.tag_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end end
it 'returns 422 and does not create relation when the new link makes a cycle' do it 'returns 422 and does not create relation when the new link makes a cycle' do
@@ -132,6 +137,7 @@ RSpec.describe "TagChildren", type: :request do
expect { expect {
do_request do_request
}.to change(TagVersion, :count).by(2) }.to change(TagVersion, :count).by(2)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:no_content) expect(response).to have_http_status(:no_content)
@@ -140,6 +146,10 @@ RSpec.describe "TagChildren", type: :request do
expect(versions.first.parent_tag_ids.split).to include(parent.id.to_s) expect(versions.first.parent_tag_ids.split).to include(parent.id.to_s)
expect(versions.second.parent_tag_ids).to eq('') expect(versions.second.parent_tag_ids).to eq('')
expect(versions.second.created_by_user_id).to eq(admin.id) expect(versions.second.created_by_user_id).to eq(admin.id)
event = event_rows.last
expect(event).to include(
'event_type' => 'tags.update', 'created_by_user_id' => admin.id)
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end end
end end
+2
ファイルの表示
@@ -8,6 +8,7 @@ RSpec.describe 'Tag description Wiki compatibility', type: :request do
let!(:versions) do let!(:versions) do
['A', 'A', 'new-version'].each_with_index.map do |body, index| ['A', 'A', 'new-version'].each_with_index.map do |body, index|
TagDescriptionVersion.create!( TagDescriptionVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag_id: tag.id, language_code: 'ja', version_no: index + 1, tag_id: tag.id, language_code: 'ja', version_no: index + 1,
event_type: index.zero? ? 'create' : 'update', body:, event_type: index.zero? ? 'create' : 'update', body:,
created_by_user_id: user.id, created_at: Time.current) created_by_user_id: user.id, created_at: Time.current)
@@ -73,6 +74,7 @@ RSpec.describe 'Tag description Wiki compatibility', type: :request do
let!(:other_tag) { create(:tag, primary_name: 'other_description_contract') } let!(:other_tag) { create(:tag, primary_name: 'other_description_contract') }
let!(:other_version) do let!(:other_version) do
TagDescriptionVersion.create!( TagDescriptionVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag_id: other_tag.id, language_code: 'ja', version_no: 1, tag_id: other_tag.id, language_code: 'ja', version_no: 1,
event_type: 'create', body: 'other tag history', event_type: 'create', body: 'other tag history',
created_by_user_id: user.id, created_at: Time.current) created_by_user_id: user.id, created_at: Time.current)
+13 -1
ファイルの表示
@@ -13,7 +13,7 @@ RSpec.describe 'Tag description native API', type: :request do
def stored_state def stored_state
[TagDescription, TagDescriptionVersion, TagName, TagVersion].map do |model| [TagDescription, TagDescriptionVersion, TagName, TagVersion].map do |model|
model.order(:id).map(&:attributes) model.order(:id).map(&:attributes)
end end + [event_rows]
end end
def fail_save model def fail_save model
@@ -91,6 +91,8 @@ RSpec.describe 'Tag description native API', type: :request do
let!(:version) do let!(:version) do
TagDescriptionVersion.create!( TagDescriptionVersion.create!(
tag_id: tag.id, language_code: 'ja', body: 'native version', version_no: 1, tag_id: tag.id, language_code: 'ja', body: 'native version', version_no: 1,
event_id: create_event!(
event_type: 'tag_descriptions.create', created_by_user: author).id,
event_type: 'create', created_by_user_id: author.id, created_at: Time.current) event_type: 'create', created_by_user_id: author.id, created_at: Time.current)
end end
let(:name_endpoint) { "/tags/name/#{ tag_name.name }/description" } let(:name_endpoint) { "/tags/name/#{ tag_name.name }/description" }
@@ -185,6 +187,7 @@ RSpec.describe 'Tag description native API', type: :request do
expect { native_write(body: 'created natively') } expect { native_write(body: 'created natively') }
.to change(TagDescription, :count).by(1) .to change(TagDescription, :count).by(1)
.and change(TagDescriptionVersion, :count).by(1) .and change(TagDescriptionVersion, :count).by(1)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:created) expect(response).to have_http_status(:created)
description = TagDescription.find_by!(tag_id: tag.id, language_code: 'ja') description = TagDescription.find_by!(tag_id: tag.id, language_code: 'ja')
expect_current(description) expect_current(description)
@@ -192,6 +195,10 @@ RSpec.describe 'Tag description native API', type: :request do
expect(version).to have_attributes( expect(version).to have_attributes(
body: 'created natively', version_no: 1, event_type: 'create', body: 'created natively', version_no: 1, event_type: 'create',
created_by_user_id: editor.id) created_by_user_id: editor.id)
event = event_rows.last
expect(event).to include(
'event_type' => 'tag_descriptions.create', 'created_by_user_id' => editor.id)
expect(version.event_id).to eq(event.fetch('id'))
get "/wiki/#{ description.id }" get "/wiki/#{ description.id }"
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json).to include('body' => description.body, 'revision_id' => version.id) expect(json).to include('body' => description.body, 'revision_id' => version.id)
@@ -223,6 +230,7 @@ RSpec.describe 'Tag description native API', type: :request do
expect(description.version_no).to eq(1) expect(description.version_no).to eq(1)
expect { native_write(body: 'after') } expect { native_write(body: 'after') }
.to change(TagDescriptionVersion, :count).by(1) .to change(TagDescriptionVersion, :count).by(1)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(description.reload).to have_attributes( expect(description.reload).to have_attributes(
body: 'after', version_no: 2, body: 'after', version_no: 2,
@@ -234,6 +242,10 @@ RSpec.describe 'Tag description native API', type: :request do
expect(version).to have_attributes( expect(version).to have_attributes(
tag_id: tag.id, language_code: 'ja', version_no: 2, event_type: 'update', tag_id: tag.id, language_code: 'ja', version_no: 2, event_type: 'update',
body: 'after', created_by_user_id: editor.id) body: 'after', created_by_user_id: editor.id)
event = event_rows.last
expect(event).to include(
'event_type' => 'tag_descriptions.update', 'created_by_user_id' => editor.id)
expect(version.event_id).to eq(event.fetch('id'))
expect(revision.body).to eq(version.body) expect(revision.body).to eq(version.body)
expect(description_versions(page).order(:version_no).last.body).to eq(version.body) expect(description_versions(page).order(:version_no).last.body).to eq(version.body)
get "/wiki/#{ description.id }" get "/wiki/#{ description.id }"
+5 -1
ファイルの表示
@@ -121,10 +121,14 @@ RSpec.describe 'Tag group native API', type: :request do
[:create, :update].each do |operation| [:create, :update].each do |operation|
it "#{ operation }s structured data and keeps unplaced membership" do it "#{ operation }s structured data and keeps unplaced membership" do
previous = group_version(group_id, 1) previous = group_version(group_id, 1)
native_write(operation) expect { native_write(operation) }.to change { event_rows.length }.by(1)
expect(response).to have_http_status(operation == :create ? :created : :ok) expect(response).to have_http_status(operation == :create ? :created : :ok)
id = operation == :create ? json.fetch('id') : group_id id = operation == :create ? json.fetch('id') : group_id
number = operation == :create ? 1 : 2 number = operation == :create ? 1 : 2
event = event_rows.last
event_type = operation == :create ? 'tag_groups.create' : 'tag_groups.update'
expect(event).to include('event_type' => event_type, 'created_by_user_id' => user.id)
expect(group_version(id, number).fetch('event_id')).to eq(event.fetch('id'))
expect_group_snapshot( expect_group_snapshot(
id, number:, members: snapshot_members, placements:, description: '全体の説明') id, number:, members: snapshot_members, placements:, description: '全体の説明')
expect(group_version(group_id, 1)).to eq(previous) expect(group_version(group_id, 1)).to eq(previous)
+1
ファイルの表示
@@ -30,6 +30,7 @@ RSpec.describe 'TagVersions API', type: :request do
) )
version = version =
TagVersion.create!( TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag: tag, tag: tag,
version_no: version_no, version_no: version_no,
event_type: event_type, event_type: event_type,
+10
ファイルの表示
@@ -705,6 +705,7 @@ RSpec.describe 'Tags API', type: :request do
expect { expect {
patch "/tags/#{tag.id}", params: { name: 'new_tag_name', category: 'meme' } patch "/tags/#{tag.id}", params: { name: 'new_tag_name', category: 'meme' }
}.to change(TagVersion, :count).by(2) }.to change(TagVersion, :count).by(2)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@@ -719,6 +720,10 @@ RSpec.describe 'Tags API', type: :request do
expect(version_primary_name(versions.second, 'ja')).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.category).to eq('meme')
expect(versions.second.created_by_user_id).to eq(member_user.id) expect(versions.second.created_by_user_id).to eq(member_user.id)
event = event_rows.last
expect(event).to include(
'event_type' => 'tags.update', 'created_by_user_id' => member_user.id)
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end end
it 'updates deprecated state and records it in tag versions' do it 'updates deprecated state and records it in tag versions' do
@@ -1275,6 +1280,7 @@ RSpec.describe 'Tags API', type: :request do
deprecated: '0', deprecated: '0',
} }
}.to change(TagVersion, :count).by(2) }.to change(TagVersion, :count).by(2)
.and change { event_rows.length }.by(1)
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
@@ -1290,6 +1296,10 @@ RSpec.describe 'Tags API', type: :request do
expect(versions.second.category).to eq('meta') expect(versions.second.category).to eq('meta')
expect(version_alias_names(versions.second, 'ja')).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) expect(versions.second.created_by_user_id).to eq(member_user.id)
event = event_rows.last
expect(event).to include(
'event_type' => 'tags.update', 'created_by_user_id' => member_user.id)
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end end
it '同じ tag の旧 alias へ戻しても rename できる' do it '同じ tag の旧 alias へ戻しても rename できる' do
+3 -1
ファイルの表示
@@ -143,7 +143,9 @@ RSpec.describe 'Counts export contracts' do
expect(CountsExportDownload.find(token, 'owner')).not_to be_nil expect(CountsExportDownload.find(token, 'owner')).not_to be_nil
expect(CountsExportDownload.find(token, 'other')).to be_nil expect(CountsExportDownload.find(token, 'other')).to be_nil
post = create(:post) post = create(:post)
PostVersion.create!(post:, version_no: 1, event_type: 'discard', url: post.url, PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: 1, event_type: 'discard', url: post.url,
title: 'private', tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024)) title: 'private', tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024))
expect(CountsExportDownload.find(token, 'owner')).to be_nil expect(CountsExportDownload.find(token, 'owner')).to be_nil
end end
+21 -7
ファイルの表示
@@ -13,7 +13,9 @@ RSpec.describe 'Counts masked pivot contracts' do
let!(:tags) do let!(:tags) do
['A', 'B', 'C'].map do |name| ['A', 'B', 'C'].map do |name|
tag = create(:tag, primary_name: name) tag = create(:tag, primary_name: name)
TagVersion.create!(tag:, version_no: 1, event_type: 'create', name:, TagVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag:, version_no: 1, event_type: 'create', name:,
category: 'general', aliases: '', parent_tag_ids: '', category: 'general', aliases: '', parent_tag_ids: '',
names: { ja: { name:, script_code: 'Jpan', auto_generated: false, aliases: [] } }, names: { ja: { name:, script_code: 'Jpan', auto_generated: false, aliases: [] } },
created_at: Time.utc(2023)) created_at: Time.utc(2023))
@@ -25,12 +27,16 @@ RSpec.describe 'Counts masked pivot contracts' do
[[0, 1], [0], [1, 2]].each_with_index do |indexes, index| [[0, 1], [0], [1, 2]].each_with_index do |indexes, index|
post = create(:post) post = create(:post)
snapshot = indexes.map { { tag_id: tags[_1].id, version_no: 1, sections: [] } } snapshot = indexes.map { { tag_id: tags[_1].id, version_no: 1, sections: [] } }
PostVersion.create!(post:, version_no: 1, event_type: 'create', PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: 1, event_type: 'create',
title: "hidden-title-#{ index }", url: post.url, tags: '', tags_json: snapshot, title: "hidden-title-#{ index }", url: post.url, tags: '', tags_json: snapshot,
parent_post_ids: '', created_by_user: member, created_at: Time.utc(2024, 1)) parent_post_ids: '', created_by_user: member, created_at: Time.utc(2024, 1))
next if index == 2 next if index == 2
PostVersion.create!(post:, version_no: 2, event_type: 'discard', PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: 2, event_type: 'discard',
title: "private-discard-#{ index }", url: post.url, tags: '', tags_json: [], title: "private-discard-#{ index }", url: post.url, tags: '', tags_json: [],
parent_post_ids: '', created_by_user: member, created_at: Time.utc(2024, 2)) parent_post_ids: '', created_by_user: member, created_at: Time.utc(2024, 2))
end end
@@ -98,12 +104,16 @@ RSpec.describe 'Counts masked pivot contracts' do
it 'uses changed-record membership for totals and hides changes to private predecessors' do it 'uses changed-record membership for totals and hides changes to private predecessors' do
post = PostVersion.order(:id).last.post post = PostVersion.order(:id).last.post
PostVersion.create!(post:, version_no: 2, event_type: 'update', title: 'changed-public-title', PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: 2, event_type: 'update', title: 'changed-public-title',
url: post.url, tags: '', url: post.url, tags: '',
tags_json: [{ tag_id: tags.last.id, version_no: 1, sections: [] }], tags_json: [{ tag_id: tags.last.id, version_no: 1, sections: [] }],
parent_post_ids: '', created_at: Time.utc(2024, 3)) parent_post_ids: '', created_at: Time.utc(2024, 3))
restored = PostVersion.order(:id).first.post restored = PostVersion.order(:id).first.post
PostVersion.create!(post: restored, version_no: 3, event_type: 'restore', PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: restored, version_no: 3, event_type: 'restore',
title: 'restored-public-title', url: restored.url, tags: '', title: 'restored-public-title', url: restored.url, tags: '',
tags_json: [{ tag_id: tags.first.id, version_no: 1, sections: [] }], parent_post_ids: '', tags_json: [{ tag_id: tags.first.id, version_no: 1, sections: [] }], parent_post_ids: '',
created_at: Time.utc(2024, 4)) created_at: Time.utc(2024, 4))
@@ -134,7 +144,9 @@ RSpec.describe 'Counts masked pivot contracts' do
it 'keeps NULL as a public data value rather than treating it as a rollup' do it 'keeps NULL as a public data value rather than treating it as a rollup' do
post = create(:post) post = create(:post)
PostVersion.create!(post:, version_no: 1, event_type: 'create', title: 'public bot', PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: 1, event_type: 'create', title: 'public bot',
url: post.url, tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024, 5)) url: post.url, tags: '', tags_json: [], parent_post_ids: '', created_at: Time.utc(2024, 5))
page = result(nil, 'rows' => 'user', 'cols' => 'event_type') page = result(nil, 'rows' => 'user', 'cols' => 'event_type')
expect(page[:cells].select { _1[:values].first.nil? }.sum { _1[:count] }).to eq(1) expect(page[:cells].select { _1[:values].first.nil? }.sum { _1[:count] }).to eq(1)
@@ -145,7 +157,9 @@ RSpec.describe 'Counts masked pivot contracts' do
it 'sorts public numeric values numerically alongside a masked group' do it 'sorts public numeric values numerically alongside a masked group' do
post = PostVersion.order(:id).last.post post = PostVersion.order(:id).last.post
[2, 10].each do |number| [2, 10].each do |number|
PostVersion.create!(post:, version_no: number, event_type: 'update', PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post:, version_no: number, event_type: 'update',
title: 'public numeric sort', url: post.url, tags: '', tags_json: [], title: 'public numeric sort', url: post.url, tags: '', tags_json: [],
parent_post_ids: '', created_at: Time.utc(2024, 5)) parent_post_ids: '', created_at: Time.utc(2024, 5))
end end
+2
ファイルの表示
@@ -56,6 +56,7 @@ RSpec.describe MaterialSyncImporter do
profile: 'legacy_drive', profile: 'legacy_drive',
export_path: '素材/a.png', export_path: '素材/a.png',
created_by_user: user) created_by_user: user)
events = event_rows
result = result =
described_class.import!( described_class.import!(
@@ -73,6 +74,7 @@ RSpec.describe MaterialSyncImporter do
expect(result.action).to eq(:unchanged) expect(result.action).to eq(:unchanged)
expect(material.reload.tag).to eq(tag) expect(material.reload.tag).to eq(tag)
expect(material.url).to eq('https://example.com/human') expect(material.url).to eq('https://example.com/human')
expect(event_rows).to eq(events)
end end
it 'suppresses a Google Drive candidate by relative path prefix' do it 'suppresses a Google Drive candidate by relative path prefix' do
+41
ファイルの表示
@@ -14,6 +14,47 @@ RSpec.describe MaterialSyncRunner do
let(:drive_client) { instance_double(GoogleDrive::ApiClient) } let(:drive_client) { instance_double(GoogleDrive::ApiClient) }
describe '#sync!' do describe '#sync!' do
it 'groups created and updated materials across sync sources into one event' do
existing = Material.create!(
source_kind: 'google_drive_file', source_file_id: 'existing-file',
source_path: 'old.png', url: 'https://example.com/existing',
created_by_user: user, updated_by_user: user)
source
MaterialSyncSource.create!(
name: 'Second Drive source', source_kind: 'google_drive_path',
source_file_id: 'folder-456', profile: 'legacy_drive',
created_by_user: user, updated_by_user: user)
allow(GoogleDrive::ApiClient).to receive(:new).and_return(drive_client)
allow(drive_client).to receive(:each_material_file_under_folder) do |folder_id, &block|
id = folder_id == 'folder-123' ? 'existing-file' : 'new-file'
block.call(id:, name: "#{ id }.png", mime_type: 'image/png',
relative_path: "素材/#{ id }.png", sha256_checksum: nil,
web_view_link: "https://drive.google.com/file/d/#{ id }/view")
end
allow(drive_client).to receive(:download_to_tempfile) do |id, **|
Tempfile.new(['sync-event', '.png']).tap do |file|
file.binmode
file.write(id)
file.rewind
end
end
allow(MaterialThumbnailGenerator).to receive(:generate!)
previous_ids = MaterialVersion.pluck(:id)
result = nil
expect { result = described_class.sync_enabled! }
.to change { event_rows.length }.by(1)
expect(result).to have_attributes(imported: 1, updated: 1, failed: 0)
event = event_rows.last
expect(event.fetch('event_type')).to eq('materials.sync')
created = Material.find_by!(source_file_id: 'new-file')
versions = MaterialVersion.where.not(id: previous_ids)
expect(versions.pluck(:material_id).uniq).to contain_exactly(existing.id, created.id)
expect(versions.pluck(:event_type)).to include('create', 'update')
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end
it 'imports google drive path candidates as they are yielded' do it 'imports google drive path candidates as they are yielded' do
first_entry = { id: 'file-1', first_entry = { id: 'file-1',
name: 'a.png', name: 'a.png',
+83
ファイルの表示
@@ -88,3 +88,86 @@ RSpec.describe PostBulkCreator do
expect(creator).to have_received(:create!).with(locale).once expect(creator).to have_received(:create!).with(locale).once
end end
end end
RSpec.describe 'PostBulkCreator event grouping', type: :model do
self.use_transactional_tests = false
before do
expect(Rails.env.test?).to be(true)
expect(ApplicationRecord.connection.open_transactions).to eq(0)
@prefix = "bulk_event_#{ SecureRandom.hex(8) }"
@actor = create(:user, :member)
allow(PostCreatePreflight).to receive(:new) do |attributes:, **|
specs = [{ name: attributes.fetch(:title), category: :general }]
instance_double(PostCreatePreflight, run: attributes.merge(
existing_post_id: nil, snapshot_tag_specs: specs, post_tag_specs: specs,
normalised_parent_post_ids: [], tag_sections: { }, video_ms: nil))
end
end
after do
if @actor
posts = Post.where(uploaded_user: @actor)
PostTagSection.where(post_id: posts.select(:id)).delete_all
PostTag.where(post_id: posts.select(:id)).delete_all
PostVersion.where(post_id: posts.select(:id)).delete_all
posts.delete_all
tag_ids = TagName.where(name: ["#{ @prefix }_1", "#{ @prefix }_2"]).pluck(:tag_id)
TagVersion.where(tag_id: tag_ids).delete_all
TagName.where(tag_id: tag_ids).delete_all
Tag.where(id: tag_ids).delete_all
EventRecords::EventRow.where(created_by_user_id: @actor.id).delete_all
@actor.delete
end
end
it 'shares one creation event between real workers and their tag histories' do
posts = [1, 2].map do |number|
{ title: "#{ @prefix }_#{ number }",
url: "https://example.com/#{ @prefix }/#{ number }" }
end
result = nil
expect {
result = PostBulkCreator.new(actor: @actor, posts:, thumbnails: { })
.run(Locale.nipponese)
}.to change { event_rows.length }.by(1)
expect(result.fetch(:results).map { _1.fetch(:status) }).to eq(['created', 'created'])
event = event_rows.last
expect(event).to include(
'event_type' => 'posts.create', 'created_by_user_id' => @actor.id)
versions = PostVersion.where(created_by_user_id: @actor.id)
expect(versions.count).to eq(2)
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
tag_versions = TagVersion.where(created_by_user_id: @actor.id)
expect(tag_versions.count).to eq(2)
expect(tag_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end
it 'does not leave an event when every row fails validation' do
preflight = instance_double(PostCreatePreflight)
allow(PostCreatePreflight).to receive(:new).and_return(preflight)
allow(preflight).to receive(:run).and_raise(
PostCreatePreflight::ValidationFailed.new(fields: { url: ['invalid'] }))
events = event_rows
result = PostBulkCreator.new(
actor: @actor, posts: [{ title: 'invalid' }], thumbnails: { }).run(Locale.nipponese)
expect(result.fetch(:results).map { _1.fetch(:status) }).to eq(['failed'])
expect(event_rows).to eq(events)
end
it 'does not leave an event when every row is skipped' do
preflight = instance_double(PostCreatePreflight, run: { existing_post_id: 123 })
allow(PostCreatePreflight).to receive(:new).and_return(preflight)
events = event_rows
result = PostBulkCreator.new(
actor: @actor, posts: [{ title: 'existing' }], thumbnails: { }).run(Locale.nipponese)
expect(result.fetch(:results).map { _1.fetch(:status) }).to eq(['skipped'])
expect(event_rows).to eq(events)
end
end
+10
ファイルの表示
@@ -16,6 +16,7 @@ RSpec.describe 'Shared resource IDs across MySQL connections', type: :model do
@tag = create(:tag, primary_name: "#{ @prefix }_owner") @tag = create(:tag, primary_name: "#{ @prefix }_owner")
@group = TagGroup.create!(name: "#{ @prefix }_owner", version_no: 1) @group = TagGroup.create!(name: "#{ @prefix }_owner", version_no: 1)
@tag_ids = Queue.new @tag_ids = Queue.new
@event_ids = Queue.new
@threads = [] @threads = []
@release = Queue.new @release = Queue.new
end end
@@ -42,6 +43,9 @@ RSpec.describe 'Shared resource IDs across MySQL connections', type: :model do
ids << @tag.id if @tag ids << @tag.id if @tag
TagName.where(tag_id: ids).delete_all TagName.where(tag_id: ids).delete_all
Tag.where(id: ids).delete_all Tag.where(id: ids).delete_all
event_ids = []
event_ids << @event_ids.pop until @event_ids.empty? if @event_ids
EventRecords::EventRow.where(id: event_ids).delete_all
end end
end end
@@ -72,11 +76,17 @@ RSpec.describe 'Shared resource IDs across MySQL connections', type: :model do
when :description when :description
TagDescription.create!(tag: @tag, language_code: 'ja', body: '', version_no: 1) TagDescription.create!(tag: @tag, language_code: 'ja', body: '', version_no: 1)
when :description_version when :description_version
event = create_event!(event_type: 'unknown')
@event_ids << event.id
TagDescriptionVersion.create!( TagDescriptionVersion.create!(
event_id: event.id,
tag_id: @tag.id, language_code: 'ja', body: 'body', version_no: 1, tag_id: @tag.id, language_code: 'ja', body: 'body', version_no: 1,
event_type: 'create', created_at: Time.current) event_type: 'create', created_at: Time.current)
when :group_version when :group_version
event = create_event!(event_type: 'unknown')
@event_ids << event.id
TagGroupVersion.create!( TagGroupVersion.create!(
event_id: event.id,
tag_group: @group, name: @group.name, version_no: 1, event_type: 'create', tag_group: @group, name: @group.name, version_no: 1, event_type: 'create',
members: [], placements: [], created_at: Time.current) members: [], placements: [], created_at: Time.current)
when :group when :group
+3
ファイルの表示
@@ -15,10 +15,12 @@ RSpec.describe SharedResourceIdAllocator do
TagGroup.new(id:, name: "group #{ number }", version_no: number) TagGroup.new(id:, name: "group #{ number }", version_no: number)
when 'tag_description_versions' when 'tag_description_versions'
TagDescriptionVersion.new( TagDescriptionVersion.new(
event_id: create_event!(event_type: 'unknown').id,
id:, tag_id: tag.id, language_code: 'ja', body: 'body', version_no: number, id:, tag_id: tag.id, language_code: 'ja', body: 'body', version_no: number,
event_type: 'create', created_at: Time.current) event_type: 'create', created_at: Time.current)
when 'tag_group_versions' when 'tag_group_versions'
TagGroupVersion.new( TagGroupVersion.new(
event_id: create_event!(event_type: 'unknown').id,
id:, tag_group_id: group.id, name: group.name, version_no: number, id:, tag_group_id: group.id, name: group.name, version_no: number,
event_type: 'create', members: [], placements: [], created_at: Time.current) event_type: 'create', members: [], placements: [], created_at: Time.current)
end end
@@ -82,6 +84,7 @@ RSpec.describe SharedResourceIdAllocator do
it 'reserves a Tag ID referenced only by remaining description history' do it 'reserves a Tag ID referenced only by remaining description history' do
high = [Tag.maximum(:id).to_i, ExternalTag.maximum(:id).to_i].max + 1000 high = [Tag.maximum(:id).to_i, ExternalTag.maximum(:id).to_i].max + 1000
TagDescriptionVersion.create!( TagDescriptionVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
tag_id: high, language_code: 'ja', version_no: 1, body: 'history', tag_id: high, language_code: 'ja', version_no: 1, body: 'history',
event_type: 'create', created_at: Time.current) event_type: 'create', created_at: Time.current)
expect(create(:tag).id).to be > high expect(create(:tag).id).to be > high
+35
ファイルの表示
@@ -42,6 +42,41 @@ RSpec.describe Youtube::Sync do
end end
describe '#sync!' do describe '#sync!' do
it 'groups created and updated post histories into one sync event' do
allow(PostVersionRecorder).to receive(:record!).and_call_original
allow(PostVersionRecorder).to receive(:ensure_snapshot!).and_call_original
existing = create(:post, url: 'https://youtu.be/video-1', title: 'old title')
allow(sync).to receive(:query_terms).and_return([])
allow(sync).to receive(:playlist_ids).and_return(['PL123'])
allow(client).to receive(:playlist_items).with(playlist_id: 'PL123', page_token: nil)
.and_return('items' => [
{ 'contentDetails' => { 'videoId' => 'video-1' } },
{ 'contentDetails' => { 'videoId' => 'video-2' } }])
allow(client).to receive(:videos).with(['video-1', 'video-2'])
.and_return('items' => [
youtube_video_item(id: 'video-1', title: 'updated', channel_id: 'UC_NONE'),
youtube_video_item(id: 'video-2', title: 'created', channel_id: 'UC_NONE')])
previous_post_ids = PostVersion.pluck(:id)
previous_tag_ids = TagVersion.pluck(:id)
expect { sync.sync! }.to change { event_rows.length }.by(1)
event = event_rows.last
expect(event).to include('event_type' => 'posts.sync', 'created_by_user_id' => nil)
versions = PostVersion.where.not(id: previous_post_ids)
created = Post.find_by!(url: 'https://www.youtube.com/watch?v=video-2')
expect(versions.pluck(:post_id).uniq).to contain_exactly(existing.id, created.id)
expect(versions.pluck(:event_type)).to include('create', 'update')
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
tag_versions = TagVersion.where.not(id: previous_tag_ids)
expect(tag_versions).not_to be_empty
expect(tag_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
events = event_rows
expect { sync.sync! }.not_to change(PostVersion, :count)
expect(event_rows).to eq(events)
end
it 'returns without fetching video details when no video ids are discovered' do it 'returns without fetching video details when no video ids are discovered' do
allow(sync).to receive(:query_terms).and_return([]) allow(sync).to receive(:query_terms).and_return([])
allow(sync).to receive(:playlist_ids).and_return([]) allow(sync).to receive(:playlist_ids).and_return([])
+15
ファイルの表示
@@ -0,0 +1,15 @@
module EventRecords
# Read the database contract without requiring an application Event model.
class EventRow < ActiveRecord::Base
self.table_name = 'events'
end
def event_rows
EventRow.order(:id).map(&:attributes)
end
def create_event! event_type:, created_by_user: nil
EventRow.create!(event_type:, created_by_user_id: created_by_user&.id,
created_at: Time.current)
end
end
+4 -1
ファイルの表示
@@ -3,11 +3,13 @@ require 'cgi'
# SQL fixtures use the migration contract without inventing application models. # SQL fixtures use the migration contract without inventing application models.
# Include explicitly; these helpers must not affect unrelated examples. # Include explicitly; these helpers must not affect unrelated examples.
module TagGroupRecords module TagGroupRecords
include EventRecords
GROUP_TABLES = [ GROUP_TABLES = [
'tag_groups', 'tag_group_tags', 'tag_group_groups', 'tag_groups', 'tag_group_tags', 'tag_group_groups',
'tag_group_placements', 'tag_group_versions'].freeze 'tag_group_placements', 'tag_group_versions'].freeze
WRITE_TABLES = GROUP_TABLES + [ WRITE_TABLES = GROUP_TABLES + [
'tag_names', 'tag_description_versions', 'events', 'tag_names', 'tag_description_versions',
'tag_descriptions'] 'tag_descriptions']
def group_connection = ApplicationRecord.connection def group_connection = ApplicationRecord.connection
@@ -114,6 +116,7 @@ module TagGroupRecords
group_id = insert_group_row('tag_groups', attributes) group_id = insert_group_row('tag_groups', attributes)
insert_group_row('tag_group_versions', { insert_group_row('tag_group_versions', {
tag_group_id: group_id, version_no: 1, event_type: 'create', tag_group_id: group_id, version_no: 1, event_type: 'create',
event_id: create_event!(event_type: 'tag_groups.create', created_by_user: user).id,
name:, description:, members: members.to_json, placements: placements.to_json, name:, description:, members: members.to_json, placements: placements.to_json,
created_by_user_id: user.id, created_at: now }) created_by_user_id: user.id, created_at: now })
members.each do |member| members.each do |member|
+31 -2
ファイルの表示
@@ -174,6 +174,7 @@ RSpec.describe 'nico:sync' do
def create_post_version_for!(post, version_no: 1, event_type: 'create', created_by_user: nil) def create_post_version_for!(post, version_no: 1, event_type: 'create', created_by_user: nil)
PostVersion.create!( PostVersion.create!(
event_id: create_event!(event_type: 'unknown').id,
post: post, post: post,
version_no: version_no, version_no: version_no,
event_type: event_type, event_type: event_type,
@@ -186,8 +187,7 @@ RSpec.describe 'nico:sync' do
original_created_from: post.original_created_from, original_created_from: post.original_created_from,
original_created_before: post.original_created_before, original_created_before: post.original_created_before,
created_at: Time.current, created_at: Time.current,
created_by_user: created_by_user created_by_user: created_by_user)
)
end end
it '新規 post 作成時に version 1 を作る' do it '新規 post 作成時に version 1 を作る' do
@@ -209,6 +209,7 @@ RSpec.describe 'nico:sync' do
expect { expect {
run_rake_task('nico:sync') run_rake_task('nico:sync')
}.to change(PostVersion, :count).by(1) }.to change(PostVersion, :count).by(1)
.and change { event_rows.length }.by(1)
post = Post.find_by!(url: 'https://www.nicovideo.jp/watch/sm9') post = Post.find_by!(url: 'https://www.nicovideo.jp/watch/sm9')
version = post.post_versions.order(:version_no).last version = post.post_versions.order(:version_no).last
@@ -217,6 +218,32 @@ RSpec.describe 'nico:sync' do
expect(version.event_type).to eq('create') expect(version.event_type).to eq('create')
expect(version.created_by_user).to be_nil expect(version.created_by_user).to be_nil
expect(version.tags).to eq(snapshot_tags(post.reload)) expect(version.tags).to eq(snapshot_tags(post.reload))
event = event_rows.last
expect(event).to include('event_type' => 'posts.sync', 'created_by_user_id' => nil)
expect(version.event_id).to eq(event.fetch('id'))
end
it 'groups created and updated post histories into one sync event' do
existing = create(:post, title: 'old', url: 'https://www.nicovideo.jp/watch/sm9')
stub_python([
{ 'code' => 'sm9', 'title' => 'updated', 'tags' => [] },
{ 'code' => 'sm10', 'title' => 'created', 'tags' => [] }])
allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))
previous_post_ids = PostVersion.pluck(:id)
previous_tag_ids = TagVersion.pluck(:id)
expect { run_rake_task('nico:sync') }.to change { event_rows.length }.by(1)
event = event_rows.last
expect(event).to include('event_type' => 'posts.sync', 'created_by_user_id' => nil)
created = Post.find_by!(url: 'https://www.nicovideo.jp/watch/sm10')
versions = PostVersion.where.not(id: previous_post_ids)
expect(versions.pluck(:post_id).uniq).to contain_exactly(existing.id, created.id)
expect(versions.pluck(:event_type)).to include('create', 'update')
expect(versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
tag_versions = TagVersion.where.not(id: previous_tag_ids)
expect(tag_versions).not_to be_empty
expect(tag_versions.pluck(:event_id).uniq).to eq([event.fetch('id')])
end end
it '既存 post の内容または tags が変わったとき update version を作る' do it '既存 post の内容または tags が変わったとき update version を作る' do
@@ -282,6 +309,7 @@ RSpec.describe 'nico:sync' do
}]) }])
allow(URI).to receive(:open).and_return(StringIO.new('<html></html>')) allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))
events = event_rows
expect { expect {
run_rake_task('nico:sync') run_rake_task('nico:sync')
@@ -291,6 +319,7 @@ RSpec.describe 'nico:sync' do
expect(version.version_no).to eq(1) expect(version.version_no).to eq(1)
expect(version.event_type).to eq('create') expect(version.event_type).to eq('create')
expect(version.tags).to eq(snapshot_tags(post.reload)) expect(version.tags).to eq(snapshot_tags(post.reload))
expect(event_rows).to eq(events)
end end
it '新規 nico tag に nico tag version を作る' do it '新規 nico tag に nico tag version を作る' do