ぼざクリタグ広場 https://hub.nizika.monster
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

321 lines
9.8 KiB

  1. require "rails_helper"
  2. RSpec.describe "nico:sync" do
  3. def stub_python(json_array)
  4. status = instance_double(Process::Status, success?: true)
  5. allow(Open3).to receive(:capture3).and_return([json_array.to_json, "", status])
  6. end
  7. def create_tag!(name, category:)
  8. tn = TagName.find_undiscard_or_create_by!(name: name.to_s.strip)
  9. Tag.find_undiscard_or_create_by!(tag_name_id: tn.id) { |t| t.category = category }
  10. end
  11. def link_nico_to_tag!(nico_tag, tag)
  12. NicoTagRelation.create!(nico_tag_id: nico_tag.id, tag_id: tag.id)
  13. end
  14. it "既存 post を見つけて、nico tag と linked tag を追加し、差分が出たら bot を付ける" do
  15. # 既存 post(正規表現で拾われるURL)
  16. post = Post.create!(title: "old", url: "https://www.nicovideo.jp/watch/sm9", uploaded_user: nil)
  17. # 既存の非nicoタグ(kept_non_nico_ids)
  18. kept_general = create_tag!("spec_kept", category: "general")
  19. PostTag.create!(post: post, tag: kept_general)
  20. # 追加される linked tag を準備(nico tag に紐付く一般タグ)
  21. linked = create_tag!("spec_linked", category: "general")
  22. nico = create_tag!("nico:AAA", category: "nico")
  23. link_nico_to_tag!(nico, linked)
  24. # bot / tagme は task 内で使うので作っておく(Tag.bot/tagme がある前提)
  25. Tag.bot
  26. Tag.tagme
  27. # pythonの出力(AAA が付く)
  28. stub_python([{
  29. 'code' => 'sm9',
  30. 'title' => 't',
  31. 'tags' => ['AAA'],
  32. 'uploaded_at' => '2026-01-01 12:34:56',
  33. 'deleted_at' => '2026-01-31 00:00:00' }])
  34. # 外部HTTPは今回「既存 post なので呼ばれない」はずだが、念のため塞ぐ
  35. allow(URI).to receive(:open).and_return(StringIO.new("<html></html>"))
  36. run_rake_task("nico:sync")
  37. post.reload
  38. active_tag_names = post.tags.joins(:tag_name).pluck("tag_names.name")
  39. expect(active_tag_names).to include("spec_kept")
  40. expect(active_tag_names).to include("nico:AAA")
  41. expect(active_tag_names).to include("spec_linked")
  42. expect(post.original_created_from).to eq(Time.iso8601('2026-01-01T03:34:00Z'))
  43. expect(post.original_created_before).to eq(Time.iso8601('2026-01-01T03:35:00Z'))
  44. # 差分が出るので bot が付く(kept_non_nico_ids != desired_non_nico_ids)
  45. expect(active_tag_names).to include("bot操作")
  46. end
  47. it "既存 post にあった古い nico tag は active から外され、履歴として discard される" do
  48. post = Post.create!(title: "old", url: "https://www.nicovideo.jp/watch/sm9", uploaded_user: nil)
  49. # 旧nicoタグ(今回の同期結果に含まれない)
  50. old_nico = create_tag!("nico:OLD", category: "nico")
  51. old_pt = PostTag.create!(post: post, tag: old_nico)
  52. expect(old_pt.discarded_at).to be_nil
  53. # 今回は NEW のみ欲しい
  54. new_nico = create_tag!("nico:NEW", category: "nico")
  55. # bot/tagme 念のため
  56. Tag.bot
  57. Tag.tagme
  58. stub_python([{ "code" => "sm9", "title" => "t", "tags" => ["NEW"] }])
  59. allow(URI).to receive(:open).and_return(StringIO.new("<html></html>"))
  60. run_rake_task("nico:sync")
  61. # OLD は active から外れる(discarded_at が入る)
  62. old_pts = PostTag.where(post_id: post.id, tag_id: old_nico.id).order(:id).to_a
  63. expect(old_pts.last.discarded_at).to be_present
  64. # NEW は active にいる
  65. post.reload
  66. active_names = post.tags.joins(:tag_name).pluck("tag_names.name")
  67. expect(active_names).to include("nico:NEW")
  68. expect(active_names).not_to include("nico:OLD")
  69. end
  70. def snapshot_tags(post)
  71. post.snapshot_tag_names.join(' ')
  72. end
  73. def create_post_version_for!(post, version_no: 1, event_type: 'create', created_by_user: nil)
  74. PostVersion.create!(
  75. post: post,
  76. version_no: version_no,
  77. event_type: event_type,
  78. title: post.title,
  79. url: post.url,
  80. thumbnail_base: post.thumbnail_base,
  81. tags: snapshot_tags(post),
  82. original_created_from: post.original_created_from,
  83. original_created_before: post.original_created_before,
  84. created_at: Time.current,
  85. created_by_user: created_by_user
  86. )
  87. end
  88. it '新規 post 作成時に version 1 を作る' do
  89. Tag.bot
  90. Tag.tagme
  91. Tag.niconico
  92. Tag.video
  93. Tag.no_deerjikist
  94. stub_python([{
  95. 'code' => 'sm9',
  96. 'title' => 't',
  97. 'tags' => ['AAA'],
  98. 'uploaded_at' => '2026-01-01 12:34:56'
  99. }])
  100. allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))
  101. expect {
  102. run_rake_task('nico:sync')
  103. }.to change(PostVersion, :count).by(1)
  104. post = Post.find_by!(url: 'https://www.nicovideo.jp/watch/sm9')
  105. version = post.post_versions.order(:version_no).last
  106. expect(version.version_no).to eq(1)
  107. expect(version.event_type).to eq('create')
  108. expect(version.created_by_user).to be_nil
  109. expect(version.tags).to eq(snapshot_tags(post.reload))
  110. end
  111. it '既存 post の内容または tags が変わったとき update version を作る' do
  112. post = Post.create!(
  113. title: 'old',
  114. url: 'https://www.nicovideo.jp/watch/sm9',
  115. uploaded_user: nil
  116. )
  117. kept_general = create_tag!('spec_kept', category: 'general')
  118. PostTag.create!(post: post, tag: kept_general)
  119. create_post_version_for!(post)
  120. linked = create_tag!('spec_linked', category: 'general')
  121. nico = create_tag!('nico:AAA', category: 'nico')
  122. link_nico_to_tag!(nico, linked)
  123. Tag.bot
  124. Tag.tagme
  125. Tag.no_deerjikist
  126. stub_python([{
  127. 'code' => 'sm9',
  128. 'title' => 't',
  129. 'tags' => ['AAA'],
  130. 'uploaded_at' => '2026-01-01 12:34:56'
  131. }])
  132. allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))
  133. expect {
  134. run_rake_task('nico:sync')
  135. }.to change(PostVersion, :count).by(1)
  136. version = post.reload.post_versions.order(:version_no).last
  137. expect(version.version_no).to eq(2)
  138. expect(version.event_type).to eq('update')
  139. expect(version.created_by_user).to be_nil
  140. expect(version.tags).to eq(snapshot_tags(post.reload))
  141. end
  142. it '既存 post に差分が無いときは新しい version を作らない' do
  143. nico = create_tag!('nico:AAA', category: 'nico')
  144. no_deerjikist = create_tag!('ニジラー情報不詳', category: 'meta')
  145. post = Post.create!(
  146. title: 't',
  147. url: 'https://www.nicovideo.jp/watch/sm9',
  148. uploaded_user: nil,
  149. original_created_from: Time.iso8601('2026-01-01T03:34:00Z'),
  150. original_created_before: Time.iso8601('2026-01-01T03:35:00Z')
  151. )
  152. PostTag.create!(post: post, tag: nico)
  153. PostTag.create!(post: post, tag: no_deerjikist)
  154. create_post_version_for!(post)
  155. stub_python([{
  156. 'code' => 'sm9',
  157. 'title' => 't',
  158. 'tags' => ['AAA'],
  159. 'uploaded_at' => '2026-01-01 12:34:56'
  160. }])
  161. allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))
  162. expect {
  163. run_rake_task('nico:sync')
  164. }.not_to change(PostVersion, :count)
  165. version = post.reload.post_versions.order(:version_no).last
  166. expect(version.version_no).to eq(1)
  167. expect(version.event_type).to eq('create')
  168. expect(version.tags).to eq(snapshot_tags(post.reload))
  169. end
  170. it '新規 nico tag に nico tag version を作る' do
  171. Tag.bot
  172. Tag.tagme
  173. Tag.niconico
  174. Tag.video
  175. Tag.no_deerjikist
  176. stub_python([{
  177. 'code' => 'sm9',
  178. 'title' => 't',
  179. 'tags' => ['AAA'],
  180. 'uploaded_at' => '2026-01-01 12:34:56'
  181. }])
  182. allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))
  183. expect {
  184. run_rake_task('nico:sync')
  185. }.to change(NicoTagVersion, :count).by(1)
  186. nico_tag = Tag.joins(:tag_name).find_by!(tag_names: { name: 'nico:AAA' })
  187. version = nico_tag.nico_tag_versions.order(:version_no).last
  188. expect(version.version_no).to eq(1)
  189. expect(version.event_type).to eq('create')
  190. expect(version.name).to eq('nico:AAA')
  191. expect(version.created_by_user).to be_nil
  192. end
  193. it '既存 post に version が無い場合は create snapshot を補う' do
  194. post = Post.create!(
  195. title: 'old',
  196. url: 'https://www.nicovideo.jp/watch/sm9',
  197. uploaded_user: nil
  198. )
  199. kept_general = create_tag!('spec_kept_without_version', category: 'general')
  200. PostTag.create!(post: post, tag: kept_general)
  201. Tag.bot
  202. Tag.tagme
  203. Tag.no_deerjikist
  204. stub_python([{
  205. 'code' => 'sm9',
  206. 'title' => 'changed title',
  207. 'tags' => ['AAA'],
  208. 'uploaded_at' => '2026-01-01 12:34:56'
  209. }])
  210. allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))
  211. expect {
  212. run_rake_task('nico:sync')
  213. }.to change { post.reload.post_versions.count }.by(1)
  214. versions = post.reload.post_versions.order(:version_no)
  215. expect(versions.map(&:event_type)).to eq(['create'])
  216. expect(versions.first.title).to eq('changed title')
  217. expect(versions.first.tags).to eq(snapshot_tags(post.reload))
  218. end
  219. it '既存 version がある post には update version を作る' do
  220. post = Post.create!(
  221. title: 'old',
  222. url: 'https://www.nicovideo.jp/watch/sm9',
  223. uploaded_user: nil
  224. )
  225. kept_general = create_tag!('spec_kept_with_version', category: 'general')
  226. PostTag.create!(post: post, tag: kept_general)
  227. PostVersionRecorder.record!(
  228. post: post,
  229. event_type: :create,
  230. created_by_user: nil
  231. )
  232. Tag.bot
  233. Tag.tagme
  234. Tag.no_deerjikist
  235. stub_python([{
  236. 'code' => 'sm9',
  237. 'title' => 'changed title',
  238. 'tags' => ['AAA'],
  239. 'uploaded_at' => '2026-01-01 12:34:56'
  240. }])
  241. allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))
  242. expect {
  243. run_rake_task('nico:sync')
  244. }.to change { post.reload.post_versions.count }.by(1)
  245. versions = post.reload.post_versions.order(:version_no)
  246. expect(versions.map(&:event_type)).to eq(['create', 'update'])
  247. expect(versions.first.title).to eq('old')
  248. expect(versions.second.title).to eq('changed title')
  249. expect(versions.second.tags).to eq(snapshot_tags(post.reload))
  250. end
  251. end