Browse Source

#309

feature/309
みてるぞ 2 weeks ago
parent
commit
351b8348c9
6 changed files with 421 additions and 7 deletions
  1. +74
    -0
      backend/spec/models/version_record_spec.rb
  2. +55
    -0
      backend/spec/requests/nico_tags_spec.rb
  3. +48
    -0
      backend/spec/requests/posts_spec.rb
  4. +76
    -4
      backend/spec/requests/tag_children_spec.rb
  5. +64
    -3
      backend/spec/requests/tags_spec.rb
  6. +104
    -0
      backend/spec/tasks/nico_sync_spec.rb

+ 74
- 0
backend/spec/models/version_record_spec.rb View File

@@ -0,0 +1,74 @@
require 'rails_helper'

RSpec.describe VersionRecord, type: :model do
let!(:tag) { create(:tag, name: 'version_record_tag') }
let!(:nico_tag) { create(:tag, :nico, name: 'nico:version_record_tag') }

it 'makes TagVersion read only after create' do
version = TagVersion.create!(
tag: tag,
version_no: 1,
event_type: 'create',
name: tag.name,
category: tag.category,
aliases: '',
parent_tag_ids: '',
created_at: Time.current,
created_by_user: nil
)

expect {
version.update!(name: 'changed')
}.to raise_error(ActiveRecord::ReadOnlyRecord)
end

it 'prevents TagVersion destroy' do
version = TagVersion.create!(
tag: tag,
version_no: 1,
event_type: 'create',
name: tag.name,
category: tag.category,
aliases: '',
parent_tag_ids: '',
created_at: Time.current,
created_by_user: nil
)

expect {
version.destroy!
}.to raise_error(ActiveRecord::ReadOnlyRecord)
end

it 'makes NicoTagVersion read only after create' do
version = NicoTagVersion.create!(
tag: nico_tag,
version_no: 1,
event_type: 'create',
name: nico_tag.name,
linked_tags: '',
created_at: Time.current,
created_by_user: nil
)

expect {
version.update!(name: 'nico:changed')
}.to raise_error(ActiveRecord::ReadOnlyRecord)
end

it 'prevents NicoTagVersion destroy' do
version = NicoTagVersion.create!(
tag: nico_tag,
version_no: 1,
event_type: 'create',
name: nico_tag.name,
linked_tags: '',
created_at: Time.current,
created_by_user: nil
)

expect {
version.destroy!
}.to raise_error(ActiveRecord::ReadOnlyRecord)
end
end

+ 55
- 0
backend/spec/requests/nico_tags_spec.rb View File

@@ -14,6 +14,7 @@ RSpec.describe 'NicoTags', type: :request do

describe 'PATCH /tags/nico/:id' do
let(:member) { create(:user, :member) }
let(:admin) { create(:user, :admin) }
let(:nico_tag) { create(:tag, :nico) }

it '401 when not logged in' do
@@ -34,5 +35,59 @@ RSpec.describe 'NicoTags', type: :request do
patch "/tags/nico/#{non_nico.id}", params: { tags: 'a b' }
expect(response).to have_http_status(:bad_request)
end

it '200 and updates linked tags while recording tag versions' do
sign_in_as(admin)

nico_tag_name = TagName.create!(name: 'nico:nico_tags_spec_source')
nico_tag = Tag.create!(tag_name: nico_tag_name, category: :nico)

linked_a_name = TagName.create!(name: 'nico_linked_a')
linked_a = Tag.create!(tag_name: linked_a_name, category: :general)

linked_b_name = TagName.create!(name: 'nico_linked_b')
linked_b = Tag.create!(tag_name: linked_b_name, category: :general)

TagVersioning.ensure_snapshot!(nico_tag, created_by_user: admin)

expect {
patch "/tags/nico/#{nico_tag.id}", params: {
tags: " #{linked_a.name}\n#{linked_b.name} "
}
}.to change(TagVersion, :count).by(2)
.and change(NicoTagVersion, :count).by(1)

expect(response).to have_http_status(:ok)

names = json.map { |t| t['name'] }
expect(names).to match_array(['nico_linked_a', 'nico_linked_b'])

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(&:created_by_user_id)).to all(eq(admin.id))

versions = nico_tag.reload.nico_tag_versions.order(:version_no)
expect(versions.map(&:event_type)).to eq(['create', 'update'])
expect(versions.last.linked_tags.split).to match_array([
'nico_linked_a',
'nico_linked_b'
])
expect(versions.last.created_by_user_id).to eq(admin.id)
end

it '400 when linked tag normalises to nico tag' do
sign_in_as(member)

other_nico = create(:tag, :nico, name: 'nico:linked_ng')
TagName.create!(name: 'linked_ng_alias', canonical: other_nico.tag_name)

TagVersioning.ensure_snapshot!(nico_tag, created_by_user: member)

expect {
patch "/tags/nico/#{nico_tag.id}", params: { tags: 'linked_ng_alias' }
}.not_to change(NicoTagVersion, :count)

expect(response).to have_http_status(:bad_request)
end
end
end

+ 48
- 0
backend/spec/requests/posts_spec.rb View File

@@ -1131,4 +1131,52 @@ RSpec.describe 'Posts API', type: :request do
expect(response).to have_http_status(:unprocessable_entity)
end
end

describe 'tag versioning from post write actions' do
let(:member) { create(:user, :member) }

it 'creates tag snapshot for normalised tags on POST /posts' do
sign_in_as(member)

expect {
post '/posts', params: {
title: 'tag versioned post',
url: 'https://example.com/tag-versioned-post',
tags: 'spec_tag',
thumbnail: dummy_upload
}
}.to change { tag.reload.tag_versions.count }.by(1)

expect(response).to have_http_status(:created)

version = tag.reload.tag_versions.order(:version_no).last
expect(version.version_no).to eq(1)
expect(version.event_type).to eq('create')
expect(version.name).to eq('spec_tag')
expect(version.category).to eq('general')
expect(version.created_by_user_id).to eq(member.id)
end

it 'creates tag snapshot for normalised tags on PUT /posts/:id' do
sign_in_as(member)

tag_name2 = TagName.create!(name: 'spec_tag_2')
tag2 = Tag.create!(tag_name: tag_name2, category: :general)

expect {
put "/posts/#{post_record.id}", params: {
title: 'updated title',
tags: 'spec_tag_2'
}
}.to change { tag2.reload.tag_versions.count }.by(1)

expect(response).to have_http_status(:ok)

version = tag2.reload.tag_versions.order(:version_no).last
expect(version.version_no).to eq(1)
expect(version.event_type).to eq('create')
expect(version.name).to eq('spec_tag_2')
expect(version.created_by_user_id).to eq(member.id)
end
end
end

+ 76
- 4
backend/spec/requests/tag_children_spec.rb View File

@@ -58,17 +58,49 @@ RSpec.describe "TagChildren", type: :request do
end
end

context "when Tag.find raises (invalid ids) it still returns 204" do
context "when Tag.find raises (invalid ids)" do
before { stub_current_user(admin) }

let(:parent_id) { -1 }
let(:child_id) { -1 }

it "returns 204 (rescue nil)" do
it "returns 404" do
do_request
expect(response).to have_http_status(:not_found)
end
end

context 'when parent is nico' do
before { stub_current_user(admin) }

let!(:parent) { create(:tag, :nico, name: 'nico:parent_ng') }
let(:parent_id) { parent.id }
let(:child_id) { child.id }

it 'returns 400 and does not create relation' do
expect {
do_request
}.not_to change(TagImplication, :count)

expect(response).to have_http_status(:bad_request)
end
end

context 'when child is nico' do
before { stub_current_user(admin) }

let!(:child) { create(:tag, :nico, name: 'nico:child_ng') }
let(:parent_id) { parent.id }
let(:child_id) { child.id }

it 'returns 400 and does not create relation' do
expect {
do_request
}.not_to change(TagImplication, :count)

expect(response).to have_http_status(:bad_request)
end
end
end

describe "DELETE /tag_children" do
@@ -116,18 +148,58 @@ RSpec.describe "TagChildren", type: :request do

expect(response).to have_http_status(:no_content)
end

it 'records create and update versions for child tag' do
expect {
do_request
}.to change(TagVersion, :count).by(2)

expect(response).to have_http_status(:no_content)

versions = child.reload.tag_versions.order(:version_no)
expect(versions.map(&:event_type)).to eq(['create', 'update'])
expect(versions.first.parent_tag_ids.split).to include(parent.id.to_s)
expect(versions.second.parent_tag_ids).to eq('')
expect(versions.second.created_by_user_id).to eq(admin.id)
end
end

context "when Tag.find raises (invalid ids) it still returns 204" do
context "when Tag.find raises (invalid ids)" do
before { stub_current_user(admin) }

let(:parent_id) { -1 }
let(:child_id) { -1 }

it "returns 204 (rescue nil)" do
it "returns 404" do
do_request
expect(response).to have_http_status(:not_found)
end
end

context 'when parent is nico' do
before { stub_current_user(admin) }

let!(:parent) { create(:tag, :nico, name: 'nico:parent_ng_delete') }
let(:parent_id) { parent.id }
let(:child_id) { child.id }

it 'returns 400' do
do_request
expect(response).to have_http_status(:bad_request)
end
end

context 'when child is nico' do
before { stub_current_user(admin) }

let!(:child) { create(:tag, :nico, name: 'nico:child_ng_delete') }
let(:parent_id) { parent.id }
let(:child_id) { child.id }

it 'returns 400' do
do_request
expect(response).to have_http_status(:bad_request)
end
end
end
end

+ 64
- 3
backend/spec/requests/tags_spec.rb View File

@@ -364,9 +364,70 @@ RSpec.describe 'Tags API', type: :request do
expect(response.status).to be_in([404, 500])
end

it 'バリデーションで update! が失敗したら(通常は 422 か 500)' do
patch "/tags/#{ tag.id }", params: { name: 'new', category: 'nico' }
expect(response.status).to be_in([422, 500])
it 'nico category への変更は 422 を返す' do
patch "/tags/#{tag.id}", params: { name: 'new', category: 'nico' }

expect(response).to have_http_status(:unprocessable_entity)
expect(tag.reload.name).to eq('spec_tag')
expect(tag.category).to eq('general')
end

it 'creates initial and update tag versions when name and category change' do
expect {
patch "/tags/#{tag.id}", params: { name: 'new_tag_name', category: 'meme' }
}.to change(TagVersion, :count).by(2)

expect(response).to have_http_status(:ok)

versions = tag.reload.tag_versions.order(:version_no)

expect(versions.map(&:event_type)).to eq(['create', 'update'])

expect(versions.first.name).to eq('spec_tag')
expect(versions.first.category).to eq('general')
expect(versions.first.aliases.split).to include('unko')

expect(versions.second.name).to eq('new_tag_name')
expect(versions.second.category).to eq('meme')
expect(versions.second.created_by_user_id).to eq(member_user.id)
end

it 'returns 422 when changing normal tag category to nico' do
expect {
patch "/tags/#{tag.id}", params: { category: 'nico' }
}.not_to change(TagVersion, :count)

expect(response).to have_http_status(:unprocessable_entity)
expect(tag.reload.category).to eq('general')
end

it 'creates nico tag versions when updating nico tag name' do
nico_tag_name = TagName.create!(name: 'nico:tags_spec_source')
nico_tag = Tag.create!(tag_name: nico_tag_name, category: :nico)

expect {
patch "/tags/#{nico_tag.id}", params: { name: 'nico:tags_spec_renamed' }
}.to change(NicoTagVersion, :count).by(2)

expect(response).to have_http_status(:ok)

versions = nico_tag.reload.nico_tag_versions.order(:version_no)
expect(versions.map(&:event_type)).to eq(['create', 'update'])
expect(versions.first.name).to eq('nico:tags_spec_source')
expect(versions.second.name).to eq('nico:tags_spec_renamed')
expect(versions.second.created_by_user_id).to eq(member_user.id)
end

it 'returns 422 when changing nico tag category to normal category' do
nico_tag_name = TagName.create!(name: 'nico:category_change_ng')
nico_tag = Tag.create!(tag_name: nico_tag_name, category: :nico)

expect {
patch "/tags/#{nico_tag.id}", params: { category: 'general' }
}.not_to change(NicoTagVersion, :count)

expect(response).to have_http_status(:unprocessable_entity)
expect(nico_tag.reload.category).to eq('nico')
end
end
end


+ 104
- 0
backend/spec/tasks/nico_sync_spec.rb View File

@@ -214,4 +214,108 @@ RSpec.describe "nico:sync" do
expect(version.event_type).to eq('create')
expect(version.tags).to eq(snapshot_tags(post.reload))
end

it '新規 nico tag に nico tag version を作る' 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('<html></html>'))

expect {
run_rake_task('nico:sync')
}.to change(NicoTagVersion, :count).by(1)

nico_tag = Tag.joins(:tag_name).find_by!(tag_names: { name: 'nico:AAA' })
version = nico_tag.nico_tag_versions.order(:version_no).last

expect(version.version_no).to eq(1)
expect(version.event_type).to eq('create')
expect(version.name).to eq('nico:AAA')
expect(version.created_by_user).to be_nil
end

it '既存 post に version が無い場合は create snapshot を補う' do
post = Post.create!(
title: 'old',
url: 'https://www.nicovideo.jp/watch/sm9',
uploaded_user: nil
)

kept_general = create_tag!('spec_kept_without_version', category: 'general')
PostTag.create!(post: post, tag: kept_general)

Tag.bot
Tag.tagme
Tag.no_deerjikist

stub_python([{
'code' => 'sm9',
'title' => 'changed title',
'tags' => ['AAA'],
'uploaded_at' => '2026-01-01 12:34:56'
}])

allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

expect {
run_rake_task('nico:sync')
}.to change { post.reload.post_versions.count }.by(1)

versions = post.reload.post_versions.order(:version_no)

expect(versions.map(&:event_type)).to eq(['create'])
expect(versions.first.title).to eq('changed title')
expect(versions.first.tags).to eq(snapshot_tags(post.reload))
end

it '既存 version がある post には update version を作る' do
post = Post.create!(
title: 'old',
url: 'https://www.nicovideo.jp/watch/sm9',
uploaded_user: nil
)

kept_general = create_tag!('spec_kept_with_version', category: 'general')
PostTag.create!(post: post, tag: kept_general)

PostVersionRecorder.record!(
post: post,
event_type: :create,
created_by_user: nil
)

Tag.bot
Tag.tagme
Tag.no_deerjikist

stub_python([{
'code' => 'sm9',
'title' => 'changed title',
'tags' => ['AAA'],
'uploaded_at' => '2026-01-01 12:34:56'
}])

allow(URI).to receive(:open).and_return(StringIO.new('<html></html>'))

expect {
run_rake_task('nico:sync')
}.to change { post.reload.post_versions.count }.by(1)

versions = post.reload.post_versions.order(:version_no)

expect(versions.map(&:event_type)).to eq(['create', 'update'])
expect(versions.first.title).to eq('old')
expect(versions.second.title).to eq('changed title')
expect(versions.second.tags).to eq(snapshot_tags(post.reload))
end
end

Loading…
Cancel
Save