diff --git a/backend/spec/models/tag_spec.rb b/backend/spec/models/tag_spec.rb index a9fc35e..1dee732 100644 --- a/backend/spec/models/tag_spec.rb +++ b/backend/spec/models/tag_spec.rb @@ -145,5 +145,70 @@ RSpec.describe Tag, type: :model do expect(target_tag.reload.post_count).to eq(0) end end + + def snapshot_tags(post) + post.snapshot_tag_names.join(' ') + end + + def create_post_version_for!(post, version_no: 1, event_type: 'create', created_by_user: nil) + PostVersion.create!( + post: post, + version_no: version_no, + event_type: event_type, + title: post.title, + url: post.url, + thumbnail_base: post.thumbnail_base, + tags: snapshot_tags(post), + parent: post.parent, + original_created_from: post.original_created_from, + original_created_before: post.original_created_before, + created_at: Time.current, + created_by_user: created_by_user + ) + end + + context 'when post versions are enabled' do + let!(:source_post_tag) { PostTag.create!(post: post_record, tag: source_tag) } + let!(:unaffected_post) do + Post.create!(url: 'https://example.com/posts/2', title: 'unaffected post') + end + + before do + create_post_version_for!(post_record) + create_post_version_for!(unaffected_post) + end + + it 'creates an update post_version only for affected posts' do + expect { + described_class.merge_tags!(target_tag, [source_tag]) + }.to change(PostVersion, :count).by(1) + + affected_versions = post_record.reload.post_versions.order(:version_no) + expect(affected_versions.pluck(:version_no)).to eq([1, 2]) + + latest = affected_versions.last + expect(latest.event_type).to eq('update') + expect(latest.created_by_user).to be_nil + expect(latest.tags).to eq(snapshot_tags(post_record.reload)) + + expect(unaffected_post.reload.post_versions.count).to eq(1) + end + end + + context 'when the source tag has no active post_tags' do + let!(:another_post) do + Post.create!(url: 'https://example.com/posts/3', title: 'another post') + end + + before do + create_post_version_for!(another_post) + end + + it 'does not create any post_version' do + expect { + described_class.merge_tags!(target_tag, [source_tag]) + }.not_to change(PostVersion, :count) + end + end end end diff --git a/backend/spec/tasks/nico_sync_spec.rb b/backend/spec/tasks/nico_sync_spec.rb index d4f0e09..ff64490 100644 --- a/backend/spec/tasks/nico_sync_spec.rb +++ b/backend/spec/tasks/nico_sync_spec.rb @@ -90,4 +90,128 @@ RSpec.describe "nico:sync" do expect(active_names).to include("nico:NEW") expect(active_names).not_to include("nico:OLD") end + + def snapshot_tags(post) + post.snapshot_tag_names.join(' ') + end + + def create_post_version_for!(post, version_no: 1, event_type: 'create', created_by_user: nil) + PostVersion.create!( + post: post, + version_no: version_no, + event_type: event_type, + title: post.title, + url: post.url, + thumbnail_base: post.thumbnail_base, + tags: snapshot_tags(post), + parent: post.parent, + original_created_from: post.original_created_from, + original_created_before: post.original_created_before, + created_at: Time.current, + created_by_user: created_by_user + ) + end + + it '新規 post 作成時に version 1 を作る' do + Tag.bot + Tag.tagme + Tag.niconico + Tag.video + Tag.no_deerjikist + + stub_python([{ + 'code' => 'sm9', + 'title' => 't', + 'tags' => ['AAA'], + 'uploaded_at' => '2026-01-01 12:34:56' + }]) + + allow(URI).to receive(:open).and_return(StringIO.new('')) + + expect { + run_rake_task('nico:sync') + }.to change(PostVersion, :count).by(1) + + post = Post.find_by!(url: 'https://www.nicovideo.jp/watch/sm9') + version = post.post_versions.order(:version_no).last + + expect(version.version_no).to eq(1) + expect(version.event_type).to eq('create') + expect(version.created_by_user).to be_nil + expect(version.tags).to eq(snapshot_tags(post.reload)) + end + + it '既存 post の内容または tags が変わったとき update version を作る' do + post = Post.create!( + title: 'old', + url: 'https://www.nicovideo.jp/watch/sm9', + uploaded_user: nil + ) + + kept_general = create_tag!('spec_kept', category: 'general') + PostTag.create!(post: post, tag: kept_general) + create_post_version_for!(post) + + linked = create_tag!('spec_linked', category: 'general') + nico = create_tag!('nico:AAA', category: 'nico') + link_nico_to_tag!(nico, linked) + + Tag.bot + Tag.tagme + Tag.no_deerjikist + + stub_python([{ + 'code' => 'sm9', + 'title' => 't', + 'tags' => ['AAA'], + 'uploaded_at' => '2026-01-01 12:34:56' + }]) + + allow(URI).to receive(:open).and_return(StringIO.new('')) + + expect { + run_rake_task('nico:sync') + }.to change(PostVersion, :count).by(1) + + version = post.reload.post_versions.order(:version_no).last + expect(version.version_no).to eq(2) + expect(version.event_type).to eq('update') + expect(version.created_by_user).to be_nil + expect(version.tags).to eq(snapshot_tags(post.reload)) + end + + it '既存 post に差分が無いときは新しい version を作らない' do + nico = create_tag!('nico:AAA', category: 'nico') + no_deerjikist = create_tag!('ニジラー情報不詳', category: 'meta') + + post = Post.create!( + title: 't', + url: 'https://www.nicovideo.jp/watch/sm9', + uploaded_user: nil, + original_created_from: Time.iso8601('2026-01-01T03:34:00Z'), + original_created_before: Time.iso8601('2026-01-01T03:35:00Z') + ) + + PostTag.create!(post: post, tag: nico) + PostTag.create!(post: post, tag: no_deerjikist) + create_post_version_for!(post) + + stub_python([{ + 'code' => 'sm9', + 'title' => 't', + 'tags' => ['AAA'], + 'uploaded_at' => '2026-01-01 12:34:56' + }]) + + allow(URI).to receive(:open).and_return(StringIO.new('')) + + expect { + run_rake_task('nico:sync') + }.not_to change(PostVersion, :count) + + version = post.reload.post_versions.order(:version_no).last + expect(version.version_no).to eq(1) + expect(version.event_type).to eq('create') + expect(version.tags).to eq(snapshot_tags(post.reload)) + end end