From b1e9418cd80f2f896b17d7a5c9b38b7aeb437d8a Mon Sep 17 00:00:00 2001 From: miteruzo Date: Thu, 15 Jan 2026 08:06:58 +0900 Subject: [PATCH] =?UTF-8?q?#215=20=E3=83=8B=E3=82=B3=E3=83=8B=E3=82=B3?= =?UTF-8?q?=E5=90=8C=E6=9C=9F=E3=83=86=E3=82=B9=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/spec/rails_helper.rb | 1 + backend/spec/support/rake.rb | 21 +++++++ backend/spec/tasks/nico_sync_spec.rb | 86 ++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) create mode 100644 backend/spec/support/rake.rb create mode 100644 backend/spec/tasks/nico_sync_spec.rb diff --git a/backend/spec/rails_helper.rb b/backend/spec/rails_helper.rb index 480f4cb..15bee9b 100644 --- a/backend/spec/rails_helper.rb +++ b/backend/spec/rails_helper.rb @@ -44,6 +44,7 @@ RSpec.configure do |config| end config.include TestRecords + config.include RakeTaskHelper # FactoryBot の create / create_list を使へるやぅにする config.include FactoryBot::Syntax::Methods diff --git a/backend/spec/support/rake.rb b/backend/spec/support/rake.rb new file mode 100644 index 0000000..97020ef --- /dev/null +++ b/backend/spec/support/rake.rb @@ -0,0 +1,21 @@ +require "rake" + + +module RakeTaskHelper + # Railsの rake task を一度だけロードする + def load_rails_tasks! + return if defined?(@rails_tasks_loaded) && @rails_tasks_loaded + @rails_tasks_loaded = true + + Rake.application = Rake::Application.new + Rails.application.load_tasks + end + + def run_rake_task(full_name) + load_rails_tasks! + + task = Rake::Task[full_name] # ここは rake[...] じゃなくて良い + task.reenable + task.invoke + end +end diff --git a/backend/spec/tasks/nico_sync_spec.rb b/backend/spec/tasks/nico_sync_spec.rb new file mode 100644 index 0000000..e4cef17 --- /dev/null +++ b/backend/spec/tasks/nico_sync_spec.rb @@ -0,0 +1,86 @@ +require "rails_helper" + + +RSpec.describe "nico:sync" do + def stub_python(json_array) + status = instance_double(Process::Status, success?: true) + allow(Open3).to receive(:capture3).and_return([json_array.to_json, "", status]) + end + + def create_tag!(name, category:) + tn = TagName.find_or_create_by!(name: name.to_s.strip) + Tag.find_or_create_by!(tag_name_id: tn.id) { |t| t.category = category } + end + + def link_nico_to_tag!(nico_tag, tag) + # NicoTagRelation がある前提(君の model からそう見える) + NicoTagRelation.create!(nico_tag_id: nico_tag.id, tag_id: tag.id) + end + + it "既存 post を見つけて、nico tag と linked tag を追加し、差分が出たら bot を付ける" do + # 既存 post(正規表現で拾われるURL) + post = Post.create!(title: "old", url: "https://www.nicovideo.jp/watch/sm9", uploaded_user: nil) + + # 既存の非nicoタグ(kept_non_nico_ids) + kept_general = create_tag!("spec_kept", category: "general") + PostTag.create!(post: post, tag: kept_general) + + # 追加される linked tag を準備(nico tag に紐付く一般タグ) + linked = create_tag!("spec_linked", category: "general") + nico = create_tag!("nico:AAA", category: "nico") + link_nico_to_tag!(nico, linked) + + # bot / tagme は task 内で使うので作っておく(Tag.bot/tagme がある前提) + Tag.bot + Tag.tagme + + # pythonの出力(AAA が付く) + stub_python([{ "code" => "sm9", "title" => "t", "tags" => ["AAA"] }]) + + # 外部HTTPは今回「既存 post なので呼ばれない」はずだが、念のため塞ぐ + allow(URI).to receive(:open).and_return(StringIO.new("")) + + run_rake_task("nico:sync") + + post.reload + active_tag_names = post.tags.joins(:tag_name).pluck("tag_names.name") + + expect(active_tag_names).to include("spec_kept") + expect(active_tag_names).to include("nico:AAA") + expect(active_tag_names).to include("spec_linked") + + # 差分が出るので bot が付く(kept_non_nico_ids != desired_non_nico_ids) + expect(active_tag_names).to include("bot操作") + end + + it "既存 post にあった古い nico tag は active から外され、履歴として discard される" do + post = Post.create!(title: "old", url: "https://www.nicovideo.jp/watch/sm9", uploaded_user: nil) + + # 旧nicoタグ(今回の同期結果に含まれない) + old_nico = create_tag!("nico:OLD", category: "nico") + old_pt = PostTag.create!(post: post, tag: old_nico) + expect(old_pt.discarded_at).to be_nil + + # 今回は NEW のみ欲しい + new_nico = create_tag!("nico:NEW", category: "nico") + + # bot/tagme 念のため + Tag.bot + Tag.tagme + + stub_python([{ "code" => "sm9", "title" => "t", "tags" => ["NEW"] }]) + allow(URI).to receive(:open).and_return(StringIO.new("")) + + run_rake_task("nico:sync") + + # OLD は active から外れる(discarded_at が入る) + old_pts = PostTag.where(post_id: post.id, tag_id: old_nico.id).order(:id).to_a + expect(old_pts.last.discarded_at).to be_present + + # NEW は active にいる + post.reload + active_names = post.tags.joins(:tag_name).pluck("tag_names.name") + expect(active_names).to include("nico:NEW") + expect(active_names).not_to include("nico:OLD") + end +end