【再掲】タグの局所記載 (#351) (#380)

#353 を再開できなかったので.

Reviewed-on: #380
Co-authored-by: miteruzo <miteruzo@naver.com>
Co-committed-by: miteruzo <miteruzo@naver.com>
このコミットはPull リクエスト #380 でマージされました.
このコミットが含まれているのは:
2026-07-03 03:23:36 +09:00
committed by みてるぞ
コミット fdf652951a
32個のファイルの変更920行の追加63行の削除
+209
ファイルの表示
@@ -54,6 +54,7 @@ RSpec.describe 'Posts API', type: :request do
title: post.title,
url: post.url,
thumbnail_base: post.thumbnail_base,
video_ms: post.video_ms,
tags: post.snapshot_tag_names.join(' '),
parent_post_ids: post.snapshot_parent_post_ids.join(' '),
original_created_from: post.original_created_from,
@@ -145,6 +146,22 @@ RSpec.describe 'Posts API', type: :request do
expect(all_tag_names).to include("spec_tag")
end
it 'keeps children and sections keys in non-detail tag responses' do
PostTagSection.create!(post: hit_post, tag:, begin_ms: 1_000, end_ms: nil)
get '/posts'
expect(response).to have_http_status(:ok)
hit_json = json.fetch('posts').find { |post| post['id'] == hit_post.id }
tag_json = hit_json.fetch('tags').find { |item| item['name'] == 'spec_tag' }
expect(tag_json.fetch('children')).to eq([])
expect(tag_json.fetch('sections')).to eq([
{ 'begin_ms' => 1_000, 'end_ms' => nil }
])
end
context "when q is provided" do
it "filters posts by q (hit case)" do
get "/posts", params: { tags: "spec_tag" }
@@ -845,6 +862,198 @@ RSpec.describe 'Posts API', type: :request do
expect(saved_names).not_to include('deprecated_parent', 'deprecated_grandparent')
end
it 'returns validation error for an invalid section literal' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'invalid section literal',
url: 'https://example.com/invalid-section-literal',
tags: 'spec_tag[1:aa-2:00]',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:unprocessable_entity)
expect(json).to include(
'type' => 'validation_error',
'message' => '入力内容を確認してください.'
)
expect(json.fetch('errors')).to include(
'tags' => ['タグ区間の記法が不正です.']
)
end
it 'creates a video post with duration' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'video post',
url: 'https://example.com/video-post',
tags: '動画 spec_tag',
duration: '3:00.500',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:created)
expect(Post.find(json.fetch('id')).video_ms).to eq(180_500)
expect(json.fetch('video_ms')).to eq(180_500)
end
it 'creates a video post with number input duration seconds' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'video post seconds',
url: 'https://example.com/video-post-seconds',
tags: '動画 spec_tag',
duration: '180.5',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:created)
expect(Post.find(json.fetch('id')).video_ms).to eq(180_500)
expect(json.fetch('video_ms')).to eq(180_500)
end
it 'clears video_ms when the saved tags do not include 動画' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'non video post',
url: 'https://example.com/non-video-post',
tags: 'spec_tag',
duration: '3:00',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:created)
expect(Post.find(json.fetch('id')).video_ms).to be_nil
end
it 'returns validation error when a bounded section exceeds duration' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'too long section',
url: 'https://example.com/too-long-section',
tags: '動画 伊地知ニジカ[2:50-3:10]',
duration: '3:00',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:unprocessable_entity)
expect(json.fetch('errors')).to include(
'video_ms' => ['タグ区間の終端が動画時間を超えてゐます.']
)
end
it 'saves open-ended sections with end_ms NULL' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'open ended section literal',
url: 'https://example.com/open-ended-section-literal',
tags: '動画 伊地知ニジカ[1:00-]',
duration: '3:00',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:created)
created_post = Post.find(json.fetch('id'))
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: '伊地知ニジカ' })
section = PostTagSection.find_by!(post: created_post, tag:)
expect(section.begin_ms).to eq(60_000)
expect(section.end_ms).to be_nil
end
it 'does not save sections for [-]' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'fully open section literal',
url: 'https://example.com/fully-open-section-literal',
tags: '伊地知ニジカ[-]',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:created)
created_post = Post.find(json.fetch('id'))
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: '伊地知ニジカ' })
expect(PostTagSection.find_by(post: created_post, tag:)).to be_nil
end
it 'does not save sections for [0:00-]' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'zero open ended section literal',
url: 'https://example.com/zero-open-ended-section-literal',
tags: '伊地知ニジカ[0:00-]',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:created)
created_post = Post.find(json.fetch('id'))
tag = Tag.joins(:tag_name).find_by!(tag_names: { name: '伊地知ニジカ' })
expect(PostTagSection.find_by(post: created_post, tag:)).to be_nil
end
it 'returns end_ms null for open-ended sections in show response' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'show open ended section literal',
url: 'https://example.com/show-open-ended-section-literal',
tags: '動画 伊地知ニジカ[1:00-]',
duration: '3:00',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:created)
get "/posts/#{ json.fetch('id') }"
expect(response).to have_http_status(:ok)
tag_json = json.fetch('tags').find { |item| item['name'] == '伊地知ニジカ' }
expect(tag_json.fetch('sections')).to eq([
{ 'begin_ms' => 60_000, 'end_ms' => nil }
])
end
it 'allows open-ended sections when begin is within duration' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'valid open ended section literal',
url: 'https://example.com/valid-open-ended-section-literal',
tags: '動画 伊地知ニジカ[1:00-]',
duration: '3:00',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:created)
end
it 'rejects open-ended sections when begin equals duration' do
sign_in_as(member)
post '/posts', params: post_write_params(
title: 'invalid open ended section literal',
url: 'https://example.com/invalid-open-ended-section-literal',
tags: '動画 伊地知ニジカ[3:00-]',
duration: '3:00',
thumbnail: dummy_upload
)
expect(response).to have_http_status(:unprocessable_entity)
expect(json.fetch('errors')).to include(
'video_ms' => ['タグ区間の開始が動画時間以上です.']
)
end
context "when nico tag already exists in tags" do
before do
Tag.find_undiscard_or_create_by!(