タグ履歴 (#309) (#319)

#309

#309

#309

#309

#309

Merge remote-tracking branch 'origin/main' into feature/309

#309

Co-authored-by: miteruzo <miteruzo@naver.com>
Reviewed-on: #319
This commit was merged in pull request #319.
This commit is contained in:
2026-04-19 20:21:51 +09:00
parent 5c7580d571
commit bde7d33949
27 changed files with 923 additions and 123 deletions
+55
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
+55 -6
View File
@@ -1,8 +1,8 @@
include ActiveSupport::Testing::TimeHelpers
require 'rails_helper'
require 'set'
include ActiveSupport::Testing::TimeHelpers
RSpec.describe 'Posts API', type: :request do
# create / update で thumbnail.attach は走るが、
# resized_thumbnail! が MiniMagick 依存でコケやすいので request spec ではスタブしとくのが無難。
@@ -1082,15 +1082,16 @@ RSpec.describe 'Posts API', type: :request do
it 'does not create a new version on PUT /posts/:id when snapshot is unchanged' do
sign_in_as(member)
create_post_version_for!(post_record)
expect do
PostTag.create!(post: post_record, tag: Tag.no_deerjikist)
create_post_version_for!(post_record.reload)
expect {
put "/posts/#{post_record.id}", params: {
title: post_record.title,
tags: 'spec_tag'
}
end.not_to change(PostVersion, :count)
}.not_to change(PostVersion, :count)
expect(response).to have_http_status(:ok)
version = post_record.reload.post_versions.order(:version_no).last
@@ -1130,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
+78 -6
View File
@@ -58,15 +58,47 @@ 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(:no_content)
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
@@ -116,17 +148,57 @@ 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(:no_content)
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
+64 -3
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