このコミットが含まれているのは:
@@ -9,11 +9,11 @@ RSpec.describe 'Posts API', type: :request do
|
||||
# resized_thumbnail! が MiniMagick 依存でコケやすいので request spec ではスタブしとくのが無難。
|
||||
before do
|
||||
allow_any_instance_of(Post).to receive(:resized_thumbnail!).and_return(true)
|
||||
allow(Post).to receive(:resized_thumbnail_attachment).and_return(
|
||||
io: StringIO.new('dummy'),
|
||||
filename: 'resized_thumbnail.jpg',
|
||||
content_type: 'image/jpeg'
|
||||
)
|
||||
allow(Post).to receive(:resized_thumbnail_attachment) do
|
||||
{ io: StringIO.new('dummy'),
|
||||
filename: 'resized_thumbnail.jpg',
|
||||
content_type: 'image/jpeg' }
|
||||
end
|
||||
end
|
||||
|
||||
def create_nico_tag!(name)
|
||||
@@ -21,8 +21,7 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
|
||||
def dummy_upload
|
||||
# 中身は何でもいい(加工処理はスタブしてる)
|
||||
Rack::Test::UploadedFile.new(StringIO.new('dummy'), 'image/jpeg', original_filename: 'dummy.jpg')
|
||||
real_thumbnail_upload
|
||||
end
|
||||
|
||||
def real_thumbnail_upload
|
||||
@@ -693,6 +692,73 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /posts/metadata' do
|
||||
let(:member) { create(:user, :member) }
|
||||
|
||||
it 'returns compact existing post data without fetching external metadata' do
|
||||
sign_in_as(member)
|
||||
existing = create(
|
||||
:post,
|
||||
title: 'existing post',
|
||||
url: 'https://example.com/existing')
|
||||
existing.thumbnail.attach(
|
||||
io: StringIO.new('thumbnail'),
|
||||
filename: 'thumbnail.jpg',
|
||||
content_type: 'image/jpeg')
|
||||
expect(PostMetadataFetcher).not_to receive(:fetch)
|
||||
|
||||
get '/posts/metadata', params: { url: 'https://example.com/existing' }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to include(
|
||||
'url' => existing.url,
|
||||
'existing_post_id' => existing.id,
|
||||
'field_warnings' => { })
|
||||
expect(json.fetch('existing_post')).to include(
|
||||
'id' => existing.id,
|
||||
'title' => existing.title,
|
||||
'url' => existing.url,
|
||||
'thumbnail_base' => existing.thumbnail_base)
|
||||
expect(json.dig('existing_post', 'thumbnail'))
|
||||
.to include('/rails/active_storage/blobs/proxy/')
|
||||
end
|
||||
|
||||
it 'returns fetched metadata and structured display tags' do
|
||||
sign_in_as(member)
|
||||
allow(Preview::UrlSafety).to receive(:validate)
|
||||
allow(PostMetadataFetcher).to receive(:fetch).and_return(
|
||||
title: 'fetched title',
|
||||
thumbnail_base: 'https://example.com/thumbnail.jpg',
|
||||
tags: 'character:虹夏',
|
||||
display_tags: [{ name: '虹夏', category: 'character' }],
|
||||
original_created_from: nil,
|
||||
original_created_before: nil,
|
||||
duration: '1:00',
|
||||
video_ms: 60_000)
|
||||
|
||||
get '/posts/metadata', params: { url: 'https://example.com/new' }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to include(
|
||||
'url' => 'https://example.com/new',
|
||||
'title' => 'fetched title',
|
||||
'duration' => '1:00',
|
||||
'video_ms' => 60_000,
|
||||
'field_warnings' => { })
|
||||
expect(json.fetch('display_tags')).to eq(
|
||||
[{ 'name' => '虹夏', 'category' => 'character' }])
|
||||
end
|
||||
|
||||
it 'returns URL validation errors as 422' do
|
||||
sign_in_as(member)
|
||||
|
||||
get '/posts/metadata', params: { url: 'file:///etc/passwd' }
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(json.fetch('errors')).to have_key('url')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /posts' do
|
||||
let(:member) { create(:user, :member) }
|
||||
let!(:alias_tag_name) { TagName.create!(name: 'manko', canonical: tag_name) }
|
||||
@@ -712,6 +778,46 @@ RSpec.describe 'Posts API', type: :request do
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
|
||||
it 'dry-runs without persisting posts or new tags' do
|
||||
sign_in_as(member)
|
||||
counts = [Post.count, Tag.count, TagName.count]
|
||||
|
||||
post '/posts?dry=1', params: post_write_params(
|
||||
title: 'dry-run post',
|
||||
url: 'https://example.com/dry-run',
|
||||
tags: 'character:new_dry_run_tag')
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to include(
|
||||
'url' => 'https://example.com/dry-run',
|
||||
'tags' => 'new_dry_run_tag',
|
||||
'existing_post_id' => nil)
|
||||
expect(json.fetch('display_tags')).to eq(
|
||||
[{ 'name' => 'new_dry_run_tag',
|
||||
'category' => 'character',
|
||||
'section_literals' => [] }])
|
||||
expect([Post.count, Tag.count, TagName.count]).to eq(counts)
|
||||
end
|
||||
|
||||
it 'dry-runs an existing URL without validating its upload' do
|
||||
sign_in_as(member)
|
||||
existing = create(:post, url: 'https://example.com/dry-existing')
|
||||
invalid_upload = Rack::Test::UploadedFile.new(
|
||||
StringIO.new('<svg></svg>'),
|
||||
'image/png',
|
||||
original_filename: 'thumbnail.png')
|
||||
|
||||
post '/posts?dry=1', params: post_write_params(
|
||||
title: '',
|
||||
url: existing.url,
|
||||
tags: '',
|
||||
thumbnail: invalid_upload)
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json).to include('existing_post_id' => existing.id)
|
||||
expect(json.fetch('field_warnings')).not_to have_key('thumbnail_base')
|
||||
end
|
||||
|
||||
it '201 and creates post + tags when member' do
|
||||
sign_in_as(member)
|
||||
|
||||
@@ -769,7 +875,8 @@ RSpec.describe 'Posts API', type: :request do
|
||||
)
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
expect(open_transactions).to eq([baseline_open_transactions])
|
||||
expect(open_transactions).to eq(
|
||||
[baseline_open_transactions, baseline_open_transactions])
|
||||
end
|
||||
|
||||
it 'returns 422 and does not create a post when thumbnail resize fails' do
|
||||
@@ -827,7 +934,7 @@ RSpec.describe 'Posts API', type: :request do
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(json.fetch('errors')).to include(
|
||||
'tags' => ['廃止済みタグは付与できません.']
|
||||
'tags' => ['廃止済みタグがあります: deprecated_direct_tag']
|
||||
)
|
||||
end
|
||||
|
||||
@@ -1251,6 +1358,47 @@ RSpec.describe 'Posts API', type: :request do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /posts/bulk' do
|
||||
let(:member) { create(:user, :member) }
|
||||
|
||||
it 'parses the manifest and indexed thumbnail parts' do
|
||||
sign_in_as(member)
|
||||
manifest = [{ 'url' => 'https://example.com/bulk',
|
||||
'title' => 'bulk post',
|
||||
'tags' => 'spec_tag',
|
||||
'parent_post_ids' => '' }]
|
||||
creator = instance_double(
|
||||
PostBulkCreator,
|
||||
run: { results: [{ status: 'created', post: { id: 123 } }] })
|
||||
allow(PostBulkCreator).to receive(:new).and_return(creator)
|
||||
|
||||
post '/posts/bulk', params: {
|
||||
posts: JSON.generate(manifest),
|
||||
thumbnails: { '0' => real_thumbnail_upload } }
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json.fetch('results')).to eq(
|
||||
[{ 'status' => 'created', 'post' => { 'id' => 123 } }])
|
||||
expect(PostBulkCreator).to have_received(:new) do |arguments|
|
||||
expect(arguments[:actor]).to eq(member)
|
||||
expect(arguments[:posts]).to eq(manifest)
|
||||
expect(arguments[:thumbnails].keys).to eq([0])
|
||||
expect(arguments[:host]).to eq('http://www.example.com')
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects malformed manifests as a request-level error' do
|
||||
sign_in_as(member)
|
||||
|
||||
post '/posts/bulk', params: {
|
||||
posts: '{',
|
||||
thumbnails: { '0' => real_thumbnail_upload } }
|
||||
|
||||
expect(response).to have_http_status(:bad_request)
|
||||
expect(json.fetch('message')).to eq('posts manifest の JSON が不正です.')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /posts/:id' do
|
||||
let(:member) { create(:user, :member) }
|
||||
|
||||
@@ -2271,9 +2419,9 @@ RSpec.describe 'Posts API', type: :request do
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
expect(Time.iso8601(json.fetch('original_created_from')))
|
||||
.to eq(Time.iso8601('2020-01-01T00:00Z'))
|
||||
.to eq(Time.utc(2020, 1, 1, 0, 0))
|
||||
expect(Time.iso8601(json.fetch('original_created_before')))
|
||||
.to eq(Time.iso8601('2020-01-01T00:01Z'))
|
||||
.to eq(Time.utc(2020, 1, 1, 0, 1))
|
||||
end
|
||||
|
||||
it 'rejects unparseable original created timestamps on PUT /posts/:id' do
|
||||
|
||||
新しい課題から参照
ユーザをブロックする