From 9c78a96ac9e178cff9d314a7d21a1a2c1850eca4 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sat, 11 Jul 2026 04:26:25 +0900 Subject: [PATCH] #406 --- .../spec/requests/tags_deerjikists_spec.rb | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/backend/spec/requests/tags_deerjikists_spec.rb b/backend/spec/requests/tags_deerjikists_spec.rb index 7bbf224..e0d3a3e 100644 --- a/backend/spec/requests/tags_deerjikists_spec.rb +++ b/backend/spec/requests/tags_deerjikists_spec.rb @@ -226,6 +226,14 @@ RSpec.describe 'Tags deerjikists API', type: :request do [platform2, code2], ) end + + it 'locks the tag before replacing the complete list' do + expect_any_instance_of(Tag).to receive(:lock!).and_call_original + + do_request + + expect(response).to have_http_status(:ok) + end end context 'when tag already has deerjikists' do @@ -299,6 +307,81 @@ RSpec.describe 'Tags deerjikists API', type: :request do end end + context 'when platform is outside the enum' do + let(:payload) do + [ + { platform: 'invalid', code: code1 }, + ] + end + + it 'returns 422 with an indexed platform error without changing the list' do + Deerjikist.create!(platform: platform1, code: code1, tag: tag) + + expect { + do_request + }.not_to change { Deerjikist.where(tag: tag).map { |d| [d.platform, d.code] } } + + expect(response).to have_http_status(:unprocessable_entity) + expect(json.fetch('errors')).to include( + 'deerjikists.0.platform' => [be_present], + ) + end + end + + context 'when a requested deerjikist belongs to another tag' do + let!(:other_tag) { create(:tag, category: :deerjikist) } + let!(:owned_deerjikist) do + Deerjikist.create!(platform: platform1, code: code1, tag: tag) + end + let!(:conflicting_deerjikist) do + Deerjikist.create!(platform: platform2, code: code2, tag: other_tag) + end + let(:payload) do + [ + { platform: 'nico', code: 'new-code' }, + { platform: platform2, code: code2 }, + ] + end + + before do + other_tag.tag_name.update!(name: 'existing-deerjikist') + end + + it 'returns an indexed 422 error and rolls back the complete replacement' do + expect { + do_request + }.not_to change { Deerjikist.order(:platform, :code).pluck(:platform, :code, :tag_id) } + + expect(response).to have_http_status(:unprocessable_entity) + expect(json.fetch('errors')).to include( + 'deerjikists.1.code' => [include('existing-deerjikist')], + ) + expect(owned_deerjikist.reload.tag_id).to eq(tag.id) + expect(conflicting_deerjikist.reload.tag_id).to eq(other_tag.id) + end + end + + context 'when a requested deerjikist already belongs to the same tag' do + let!(:existing_deerjikist) do + Deerjikist.create!(platform: platform1, code: code1, tag: tag) + end + let(:payload) do + [ + { platform: platform1, code: code1 }, + ] + end + + it 'keeps the existing row' do + expect { + do_request + }.not_to change { existing_deerjikist.reload.created_at } + + expect(response).to have_http_status(:ok) + expect(Deerjikist.where(tag: tag).pluck(:platform, :code)) + .to eq([[platform1, code1]]) + end + end + context 'when youtube code is handle' do let(:channel_id) { 'UCabcdefghijklmnopqrstuv' } let(:payload) do