require 'rails_helper' RSpec.describe 'Post imports API', type: :request do let(:member) { create(:user, :member) } before do allow(Preview::UrlSafety).to receive(:validate) do |url| [URI.parse(url), ['8.8.8.8']] end allow(PostMetadataFetcher).to receive(:fetch).and_return( title: 'fetched title', thumbnail_base: nil, tags: '' ) end describe 'POST /posts/import/preview' do it 'requires a member' do sign_out post '/posts/import/preview', params: { source: 'https://example.com/post' } expect(response).to have_http_status(:unauthorized) sign_in_as(create(:user, :guest)) post '/posts/import/preview', params: { source: 'https://example.com/post' } expect(response).to have_http_status(:forbidden) end it 'parses a URL list and returns preview rows' do sign_in_as(member) post '/posts/import/preview', params: { source: " https://example.com/one \r\n\r\nhttps://example.com/two" } expect(response).to have_http_status(:ok) expect(json.fetch('rows').map { _1.fetch('source_row') }).to eq([1, 3]) expect(json.fetch('rows').map { _1.fetch('url') }).to eq([ 'https://example.com/one', 'https://example.com/two' ]) end it 'returns a safe 400 response for an invalid source' do sign_in_as(member) post '/posts/import/preview', params: { source: '' } expect(response).to have_http_status(:bad_request) expect(json.fetch('message')).to eq('URL を入力してください.') end end describe 'POST /posts/import/validate' do it 'accepts camel-case row properties and returns their warnings' do sign_in_as(member) post '/posts/import/validate', params: { rows: [{ sourceRow: '1', url: 'https://example.com/post', metadataUrl: 'https://example.com/post', attributes: { title: 'manual title' }, provenance: { url: 'manual', title: 'manual' }, tagSources: { automatic: '', manual: '' }, fieldWarnings: { title: ['old warning'] }, baseWarnings: ['base warning'] }], changed_row: -1 } expect(response).to have_http_status(:ok) result = json.fetch('rows').first expect(result.fetch('source_row')).to eq(1) expect(result.fetch('tag_sources')).to eq('automatic' => '', 'manual' => '') expect(result.fetch('field_warnings')).to eq('title' => ['old warning']) expect(result.fetch('base_warnings')).to eq(['base warning']) end it 'rejects a non-array rows value with 400' do sign_in_as(member) post '/posts/import/validate', params: { rows: { sourceRow: 1 }, changed_row: -1 } expect(response).to have_http_status(:bad_request) expect(json.fetch('message')).to eq('取込行の形式が不正です.') end it 'returns original created datetime validation errors for minute precision' do sign_in_as(member) post '/posts/import/validate', params: { rows: [{ sourceRow: 1, url: 'https://example.com/post', metadataUrl: 'https://example.com/post', attributes: { originalCreatedFrom: '2020-01-01T00:00:30Z', originalCreatedBefore: '2020-01-01T00:01Z' }, provenance: { url: 'manual', originalCreatedFrom: 'manual', originalCreatedBefore: 'manual' }, tagSources: { automatic: '', manual: '' } }], changed_row: -1 } expect(response).to have_http_status(:ok) expect(json.fetch('rows').first.fetch('validation_errors')).to include( 'original_created_from' => ['オリジナルの作成日時は分単位で入力してください.'] ) expect(json.fetch('rows').first.fetch('validation_errors')) .not_to have_key('original_created_at') end end describe 'POST /posts/import' do it 'returns a formal skipped result for an existing post' do existing = create(:post, url: 'https://example.com/existing') sign_in_as(member) post '/posts/import', params: { rows: [{ sourceRow: 1, url: existing.url, attributes: { title: 'ignored' }, provenance: { url: 'manual', title: 'manual' }, tagSources: { automatic: '', manual: '' } }] } expect(response).to have_http_status(:ok) expect(json).to include('created' => 0, 'skipped' => 1, 'failed' => 0) expect(json.fetch('rows').first).to include( 'status' => 'skipped', 'existing_post_id' => existing.id ) end end end