広場投稿追加画面の刷新 (#399) (#413)

Reviewed-on: #413
Co-authored-by: miteruzo <miteruzo@naver.com>
Co-committed-by: miteruzo <miteruzo@naver.com>
このコミットはPull リクエスト #413 でマージされました.
このコミットが含まれているのは:
2026-07-19 00:03:10 +09:00
committed by みてるぞ
コミット f1181e8510
99個のファイルの変更10242行の追加1126行の削除
+59
ファイルの表示
@@ -0,0 +1,59 @@
require 'rails_helper'
RSpec.describe PostBulkCreator do
it 'limits workers to two and keeps failures in their request slots' do
actor = instance_double(User, id: 123)
allow(User).to receive(:find).with(123) {
instance_double(User, id: 123)
}
mutex = Mutex.new
active = 0
maximum_active = 0
allow(PostCreatePreflight).to receive(:new) do |attributes:, **|
preflight = instance_double(PostCreatePreflight)
allow(preflight).to receive(:run) do
mutex.synchronize do
active += 1
maximum_active = [maximum_active, active].max
end
sleep 0.02
mutex.synchronize { active -= 1 }
attributes.symbolize_keys.merge(
existing_post_id: nil,
field_warnings: { },
base_warnings: [])
end
preflight
end
allow(PostCreator).to receive(:new) do |attributes:, **|
creator = instance_double(PostCreator)
if attributes[:title] == 'broken'
allow(creator).to receive(:create!).and_raise(StandardError, 'broken')
else
post = instance_double(Post, id: attributes[:title].delete_prefix('post ').to_i)
allow(creator).to receive(:create!).and_return(post)
end
creator
end
posts = [
{ 'title' => 'post 1', 'url' => 'https://example.com/1' },
{ 'title' => 'broken', 'url' => 'https://example.com/2' },
{ 'title' => 'post 3', 'url' => 'https://example.com/3' },
{ 'title' => 'post 4', 'url' => 'https://example.com/4' }]
results = described_class.new(
actor:,
posts:,
thumbnails: { }).run.fetch(:results)
expect(maximum_active).to eq(2)
expect(results.length).to eq(posts.length)
expect(results.map { _1[:status] }).to eq(
['created', 'failed', 'created', 'created'])
expect(results[0].dig(:post, :id)).to eq(1)
expect(results[1]).to include(status: 'failed', recoverable: false)
expect(results[2].dig(:post, :id)).to eq(3)
expect(results[3].dig(:post, :id)).to eq(4)
end
end
+82
ファイルの表示
@@ -0,0 +1,82 @@
require 'rails_helper'
RSpec.describe PostCreatePlan do
def create_tag! name, category
Tag.create!(name:, category:)
end
before do
create_tag!('タグ希望', :meta)
create_tag!('ニジラー情報不詳', :meta)
end
it 'plans direct and existing default tags without persisting records' do
counts = [TagName.count, Tag.count]
plan = described_class.new(
attributes: {
url: 'https://example.com/post',
title: 'title',
tags: 'character:new_character',
parent_post_ids: '' }).build!
expect(plan[:tags]).to eq('new_character')
expect(plan[:direct_tag_specs]).to eq(
[{ name: 'new_character', category: :character }])
expect(plan[:default_tag_specs]).to include(
{ name: 'タグ希望', category: :meta },
{ name: 'ニジラー情報不詳', category: :meta })
expect([TagName.count, Tag.count]).to eq(counts)
end
it 'resolves aliases and keeps tag sections separate from canonical names' do
canonical = create_tag!('虹夏', :character)
TagName.create!(name: 'にじか', canonical: canonical.tag_name)
create_tag!('動画', :meta)
plan = described_class.new(
attributes: {
url: 'https://example.com/video',
title: 'video',
tags: '動画 にじか[0:10-0:20]',
duration: '1:00',
parent_post_ids: '' }).build!
expect(plan[:tags].split).to include('動画', '虹夏[0:10-0:20]')
expect(plan[:display_tags]).to include(
{ name: '虹夏',
category: 'character',
section_literals: ['[0:10-0:20]'] })
expect(plan[:video_ms]).to eq(60_000)
end
it 'validates a new tag name without persisting it' do
long_name = 'a' * 256
counts = [TagName.count, Tag.count]
expect {
described_class.new(
attributes: {
url: 'https://example.com/post',
title: 'title',
tags: long_name,
parent_post_ids: '' }).build!
}.to raise_error(ActiveRecord::RecordInvalid) { |error|
expect(error.record.errors[:tags]).not_to be_empty
}
expect([TagName.count, Tag.count]).to eq(counts)
end
it 'ignores duration when the planned tags do not include video' do
plan = described_class.new(
attributes: {
url: 'https://example.com/post',
title: 'title',
tags: 'ordinary_tag',
duration: 'invalid',
parent_post_ids: '' }).build!
expect(plan[:duration]).to eq('invalid')
expect(plan[:video_ms]).to be_nil
end
end
+78
ファイルの表示
@@ -0,0 +1,78 @@
require 'rails_helper'
require 'base64'
RSpec.describe PostCreator do
let(:actor) { create(:user, :member) }
def real_thumbnail_upload
gif = Base64.decode64('R0lGODdhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=')
Rack::Test::UploadedFile.new(
StringIO.new(gif),
'image/gif',
original_filename: 'thumbnail.gif')
end
before do
allow(Tag).to receive(:normalise_tags!).and_return({ tags: [], sections: {} })
allow(TagVersioning).to receive(:record_tag_snapshots!)
allow(Tag).to receive(:expand_parent_tags).and_return([])
allow(PostVersionRecorder).to receive(:record!)
end
it 'prefers thumbnail_base over an explicit upload' do
expect(Post).not_to receive(:resized_thumbnail_attachment)
allow(Post).to receive(:remote_thumbnail_attachment).and_return(
io: StringIO.new('remote'),
filename: 'resized_thumbnail.jpg',
content_type: 'image/jpeg')
post = described_class.new(
actor:,
attributes: {
title: 'title',
url: 'https://example.com/post',
thumbnail: real_thumbnail_upload,
thumbnail_base: 'https://example.com/thumb.jpg',
tags: '' }).create!
expect(post.thumbnail).to be_attached
expect(post.thumbnail_base).to eq('https://example.com/thumb.jpg')
end
it 'uses the common remote thumbnail attach path when thumbnail_base is given' do
expect(Post).to receive(:remote_thumbnail_attachment)
.with('https://example.com/thumb.jpg')
.and_return(
io: StringIO.new('thumbnail'),
filename: 'thumbnail.jpg',
content_type: 'image/jpeg')
post = described_class.new(
actor:,
attributes: {
title: 'title',
url: 'https://example.com/post',
thumbnail_base: 'https://example.com/thumb.jpg',
tags: '' }).create!
expect(post.thumbnail_base).to eq('https://example.com/thumb.jpg')
expect(post.thumbnail).to be_attached
end
it 'does not create a post when remote thumbnail fetch fails' do
allow(Post).to receive(:remote_thumbnail_attachment)
.and_raise(Post::RemoteThumbnailFetchFailed, 'サムネール画像を取得できませんでした.')
creator = described_class.new(
actor:,
attributes: {
title: 'title',
url: 'https://example.com/post',
thumbnail_base: 'https://example.com/thumb.jpg',
tags: '' })
post_count = Post.count
expect { creator.create! }.to raise_error(Post::RemoteThumbnailFetchFailed)
expect(Post.count).to eq(post_count)
end
end
+187
ファイルの表示
@@ -0,0 +1,187 @@
require 'rails_helper'
RSpec.describe PostImportPreviewer do
before do
allow(Preview::UrlSafety).to receive(:validate) do |url|
[URI.parse(url), ['8.8.8.8']]
end
end
def row(source_row:, url:, attributes: { }, provenance: { }, **values)
{
source_row:,
url:,
attributes:,
provenance:
}.merge(values)
end
describe '#preview_rows' do
it 'marks an existing post for skipping without fetching metadata' do
existing = create(:post, url: 'https://example.com/existing')
expect(PostMetadataFetcher).not_to receive(:fetch)
result = described_class.new.preview_rows(rows: [
row(source_row: 1, url: 'https://EXAMPLE.com/existing/')
]).first
expect(result).to include(
url: 'https://example.com/existing',
skip_reason: 'existing',
existing_post_id: existing.id,
validation_errors: {}
)
expect(result.fetch(:field_warnings)).to eq({})
end
it 'reports list duplicates as URL errors instead of existing skips' do
create(:post, url: 'https://example.com/duplicate')
expect(PostMetadataFetcher).not_to receive(:fetch)
results = described_class.new.preview_rows(rows: [
row(source_row: 1, url: 'https://example.com/duplicate'),
row(source_row: 2, url: 'https://EXAMPLE.com/duplicate/')
])
expect(results.map { _1[:validation_errors] }).to all(
include(url: ['URL が重複しています.'])
)
expect(results).to all(include(skip_reason: nil, existing_post_id: nil))
end
it 'does not fetch metadata for an unsafe URL' do
allow(Preview::UrlSafety).to receive(:validate)
.and_raise(Preview::UrlSafety::UnsafeUrl, '安全でない接続先は使用できません.')
expect(PostMetadataFetcher).not_to receive(:fetch)
result = described_class.new.preview_rows(rows: [
row(source_row: 1, url: 'https://unsafe.example/post')
]).first
expect(result.fetch(:validation_errors)).to include(
url: ['安全でない接続先は使用できません.']
)
end
it 'applies metadata to automatic fields and recognises metadata tags' do
Tag.create!(name: 'known-tag', category: :general)
allow(PostMetadataFetcher).to receive(:fetch).and_return(
title: 'metadata title',
thumbnail_base: 'https://example.com/thumb.jpg',
duration: '2',
tags: 'known-tag'
)
result = described_class.new.preview_rows(rows: [
row(source_row: 1, url: 'https://example.com/new')
]).first
expect(result.fetch(:attributes)).to include(
'title' => 'metadata title',
'thumbnail_base' => 'https://example.com/thumb.jpg',
'duration' => '2',
'tags' => 'known-tag'
)
expect(result.fetch(:field_warnings)).not_to have_key('tags')
expect(PostMetadataFetcher).to have_received(:fetch).once
end
it 'preserves manual empty values when metadata is available' do
allow(PostMetadataFetcher).to receive(:fetch).and_return(
title: 'metadata title',
tags: 'metadata-tag'
)
result = described_class.new.preview_rows(rows: [
row(
source_row: 1,
url: 'https://example.com/manual',
attributes: { title: '', tags: '' },
provenance: { title: 'manual', tags: 'manual' },
tag_sources: { automatic: 'old-tag', manual: '' }
)
]).first
expect(result.fetch(:attributes)).to include('title' => '', 'tags' => '')
expect(result.fetch(:tag_sources)).to include('manual' => '')
end
it 'clears warnings derived from an old metadata URL' do
allow(PostMetadataFetcher).to receive(:fetch).and_return(
title: 'new title',
thumbnail_base: 'https://example.com/new-thumb.jpg',
tags: ''
)
result = described_class.new.preview_rows(rows: [
row(
source_row: 1,
url: 'https://example.com/new-url',
attributes: { title: 'old title', tags: 'old-tag' },
provenance: { title: 'automatic', tags: 'automatic' },
tag_sources: { automatic: 'old-tag', manual: '' },
metadata_url: 'https://example.com/old-url',
field_warnings: { tags: ['old tag warning'], url: ['old fetch warning'] },
base_warnings: ['old base warning']
)
]).first
expect(result.fetch(:attributes)).to include('title' => 'new title', 'tags' => '')
expect(result.fetch(:base_warnings)).to eq([])
expect(result.fetch(:field_warnings).values.flatten)
.not_to include('old tag warning', 'old fetch warning')
end
it 'isolates an unexpected metadata failure to the affected URL' do
allow(PostMetadataFetcher).to receive(:fetch) do |url|
raise 'fetch failure' if url.include?('failure')
{ title: 'successful title', tags: '' }
end
allow(Rails.logger).to receive(:error)
results = described_class.new.preview_rows(rows: [
row(source_row: 1, url: 'https://example.com/failure'),
row(source_row: 2, url: 'https://example.com/success')
])
expect(results[0].fetch(:field_warnings)).to include(
'url' => [described_class::METADATA_FETCH_WARNING]
)
expect(results[1].fetch(:attributes)).to include('title' => 'successful title')
end
it 'turns an unsafe URL detected during metadata fetching into a URL error' do
allow(PostMetadataFetcher).to receive(:fetch)
.and_raise(Preview::UrlSafety::UnsafeUrl, '安全でない接続先です.')
allow(Rails.logger).to receive(:info)
result = described_class.new.preview_rows(rows: [
row(source_row: 1, url: 'https://example.com/redirects-to-private')
]).first
expect(result.fetch(:validation_errors)).to include(
url: ['安全でない接続先です.']
)
end
it 'validates parent IDs from the preloaded set' do
parent = create(:post)
results = described_class.new.preview_rows(
rows: [
row(source_row: 1, url: 'https://example.com/valid-parent',
attributes: { parent_post_ids: parent.id.to_s }),
row(source_row: 2, url: 'https://example.com/missing-parent',
attributes: { parent_post_ids: '999999' })
],
fetch_metadata: false
)
expect(results[0].fetch(:validation_errors)).not_to have_key(:parent_post_ids)
expect(results[1].fetch(:validation_errors)).to include(
parent_post_ids: ['存在しない親投稿 Id. があります.']
)
end
end
end
+123
ファイルの表示
@@ -0,0 +1,123 @@
require 'rails_helper'
RSpec.describe PostMetadataFetcher do
Response = Struct.new(:body)
def fetch_with_published_time(value, url: 'https://example.com/video')
html = <<~HTML
<html><head>
<meta property="article:published_time" content="#{ value }">
</head></html>
HTML
uri = URI.parse(url)
allow(Preview::UrlSafety).to receive(:validate).with(url).and_return([uri, ['8.8.8.8']])
allow(Preview::HttpFetcher).to receive(:fetch).and_return(Response.new(html))
allow(Preview::HtmlMetadataExtractor).to receive(:extract)
.and_return(title: 'title', image_url: nil)
described_class.fetch(url)
end
it 'builds ranges explicitly for year, month, and day precision' do
year = fetch_with_published_time('2024')
month = fetch_with_published_time('2024-02')
day = fetch_with_published_time('2024-02-03')
expect(year).to include(
original_created_from: Time.zone.local(2024, 1, 1).iso8601,
original_created_before: Time.zone.local(2025, 1, 1).iso8601
)
expect(month).to include(
original_created_from: Time.zone.local(2024, 2, 1).iso8601,
original_created_before: Time.zone.local(2024, 3, 1).iso8601
)
expect(day).to include(
original_created_from: Time.zone.local(2024, 2, 3).iso8601,
original_created_before: Time.zone.local(2024, 2, 4).iso8601
)
end
it 'rounds second and fractional-second timestamps down to one-minute ranges' do
result = fetch_with_published_time('2024-02-03T12:34:56.123+02:30')
expect(Time.iso8601(result.fetch(:original_created_from)))
.to eq(Time.iso8601('2024-02-03T12:34:00+02:30'))
expect(
Time.iso8601(result.fetch(:original_created_before)) -
Time.iso8601(result.fetch(:original_created_from))
).to eq(60)
expect(result.fetch(:original_created_from)).not_to include('.')
end
it 'treats hour-precision timestamps as one-hour ranges with and without offsets' do
offset_result = fetch_with_published_time('2024-02-03T12+02:30')
local_result = fetch_with_published_time('2024-02-03T12')
expect(Time.iso8601(offset_result.fetch(:original_created_from)))
.to eq(Time.iso8601('2024-02-03T12:00:00+02:30'))
expect(
Time.iso8601(offset_result.fetch(:original_created_before)) -
Time.iso8601(offset_result.fetch(:original_created_from))
).to eq(3600)
expect(local_result.fetch(:original_created_from)).to eq('2024-02-03T12:00:00Z')
expect(local_result.fetch(:original_created_before)).to eq('2024-02-03T13:00:00Z')
end
it 'adds platform tags for known video URLs' do
result = fetch_with_published_time('2024', url: 'https://youtu.be/abc123')
expect(result.fetch(:tags)).to eq('動画 YouTube')
end
it 'returns nil dates for an invalid timestamp' do
result = fetch_with_published_time('not-a-time')
expect(result.fetch(:original_created_from)).to be_nil
expect(result.fetch(:original_created_before)).to be_nil
end
it 'returns nil dates for an invalid timestamp second value' do
result = fetch_with_published_time('2024-02-03T12:34:99Z')
expect(result.fetch(:original_created_from)).to be_nil
expect(result.fetch(:original_created_before)).to be_nil
end
it 'returns nil dates for invalid calendar dates and invalid hour values' do
invalid_day = fetch_with_published_time('2024-02-31T12:00Z')
invalid_leap = fetch_with_published_time('2023-02-29T12:00Z')
invalid_hour = fetch_with_published_time('2024-02-29T24:00Z')
[invalid_day, invalid_leap, invalid_hour].each do |result|
expect(result.fetch(:original_created_from)).to be_nil
expect(result.fetch(:original_created_before)).to be_nil
end
end
it 'accepts a valid leap-day timestamp at minute precision' do
result = fetch_with_published_time('2024-02-29T12:34Z')
expect(result.fetch(:original_created_from)).to eq('2024-02-29T12:34:00Z')
expect(result.fetch(:original_created_before)).to eq('2024-02-29T12:35:00Z')
end
it 'serialises metadata duration as the same seconds string contract used by forms' do
html = <<~HTML
<html><head>
<meta property="article:published_time" content="2024-02-03T12:34:56.123+02:30">
<meta property="og:video:duration" content="2.5">
</head></html>
HTML
uri = URI.parse('https://example.com/video')
allow(Preview::UrlSafety).to receive(:validate)
.with('https://example.com/video')
.and_return([uri, ['8.8.8.8']])
allow(Preview::HttpFetcher).to receive(:fetch).and_return(Response.new(html))
allow(Preview::HtmlMetadataExtractor).to receive(:extract)
.and_return(title: 'title', image_url: nil)
result = described_class.fetch('https://example.com/video')
expect(result.fetch(:duration)).to eq('2.5')
end
end
+52
ファイルの表示
@@ -0,0 +1,52 @@
require 'rails_helper'
require 'base64'
require 'tempfile'
RSpec.describe PostThumbnailUploadValidator do
def with_upload bytes, content_type:, filename:
tempfile = Tempfile.new(['thumbnail-upload', File.extname(filename)])
tempfile.binmode
tempfile.write(bytes)
tempfile.rewind
upload = ActionDispatch::Http::UploadedFile.new(
tempfile:,
filename:,
type: content_type)
yield upload
ensure
tempfile&.close!
end
it 'accepts a raster upload after decoding and rewinds it' do
gif = Base64.decode64('R0lGODdhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=')
with_upload(gif, content_type: 'image/gif', filename: 'thumbnail.gif') do |upload|
expect { described_class.validate!(upload) }.not_to raise_error
expect(upload.read(6)).to eq('GIF87a')
end
end
it 'rejects SVG content disguised as a raster MIME type' do
with_upload(
'<svg width="10" height="10"></svg>',
content_type: 'image/png',
filename: 'thumbnail.png') do |upload|
expect { described_class.validate!(upload) }
.to raise_error(
described_class::InvalidUpload,
'サムネイル画像の形式が不正です.')
end
end
it 'rejects non-raster content disguised as an image' do
with_upload(
'%PDF-1.7',
content_type: 'image/png',
filename: 'thumbnail.png') do |upload|
expect { described_class.validate!(upload) }
.to raise_error(
described_class::InvalidUpload,
'サムネイル画像の形式が不正です.')
end
end
end
+27
ファイルの表示
@@ -0,0 +1,27 @@
require 'rails_helper'
RSpec.describe PostUrlNormaliser do
before do
PostUrlSanitisationRule.unscoped.delete_all
end
describe '.normalise' do
it 'normalises the host and trailing slash before applying sanitisation rules' do
PostUrlSanitisationRule.create!(
priority: 10,
source_pattern: '\\Ahttps://example\\.com/source\\z',
replacement: 'https://example.com/canonical'
)
result = described_class.normalise(' https://EXAMPLE.com/source/ ')
expect(result).to eq('https://example.com/canonical')
end
it 'returns nil for unsupported or malformed URLs' do
expect(described_class.normalise('ftp://example.com/file')).to be_nil
expect(described_class.normalise('https://[')).to be_nil
expect(described_class.normalise('https:/path')).to be_nil
end
end
end
+63 -10
ファイルの表示
@@ -2,7 +2,7 @@ require 'rails_helper'
RSpec.describe Preview::ThumbnailFetcher do
describe '.fetch' do
it 'rejects svg thumbnails' do
it 'accepts svg thumbnails for the common safe rasterisation path' do
page = Preview::HttpFetcher::Response.new(
'<meta property="og:image" content="https://example.com/thumb.svg">',
'text/html',
@@ -12,8 +12,9 @@ RSpec.describe Preview::ThumbnailFetcher do
'image/svg+xml',
'https://example.com/thumb.svg')
allow(Preview::UrlSafety).to receive(:validate)
.and_return([URI.parse('https://example.com/page'), ['203.0.113.10']])
allow(Preview::UrlSafety).to receive(:validate) do |url|
[URI.parse(url), ['203.0.113.10']]
end
allow(Preview::HttpFetcher).to receive(:fetch)
.with('https://example.com/page', max_bytes: described_class::HTML_MAX_BYTES)
.and_return(page)
@@ -21,9 +22,7 @@ RSpec.describe Preview::ThumbnailFetcher do
.with('https://example.com/thumb.svg')
.and_return(svg)
expect {
described_class.fetch('https://example.com/page')
}.to raise_error(Preview::ThumbnailFetcher::GenerationFailed)
expect(described_class.fetch('https://example.com/page')).to eq('<svg></svg>')
end
it 'accepts allowed image content type with parameters' do
@@ -32,12 +31,13 @@ RSpec.describe Preview::ThumbnailFetcher do
'text/html',
'https://example.com/page')
image = Preview::HttpFetcher::Response.new(
'jpeg-bytes',
"\xFF\xD8\xFFjpeg-bytes".b,
'image/jpeg; charset=binary',
'https://example.com/thumb.jpg')
allow(Preview::UrlSafety).to receive(:validate)
.and_return([URI.parse('https://example.com/page'), ['203.0.113.10']])
allow(Preview::UrlSafety).to receive(:validate) do |url|
[URI.parse(url), ['203.0.113.10']]
end
allow(Preview::HttpFetcher).to receive(:fetch)
.with('https://example.com/page', max_bytes: described_class::HTML_MAX_BYTES)
.and_return(page)
@@ -45,7 +45,60 @@ RSpec.describe Preview::ThumbnailFetcher do
.with('https://example.com/thumb.jpg')
.and_return(image)
expect(described_class.fetch('https://example.com/page')).to eq('jpeg-bytes')
expect(described_class.fetch('https://example.com/page'))
.to eq("\xFF\xD8\xFFjpeg-bytes".b)
end
end
describe '.fetch_image_response' do
it 'rejects an unsafe input URL before HTTP fetch' do
allow(Preview::UrlSafety).to receive(:validate)
.and_raise(Preview::UrlSafety::UnsafeUrl, '安全でない接続先は使用できません.')
expect(Preview::HttpFetcher).not_to receive(:fetch)
expect {
described_class.fetch_image_response('https://unsafe.example.com/thumb.jpg')
}.to raise_error(Preview::UrlSafety::UnsafeUrl, '安全でない接続先は使用できません.')
end
it 'rejects an unsafe redirect target' do
allow(Preview::UrlSafety).to receive(:validate)
.and_return([URI.parse('https://example.com/thumb.jpg'), ['8.8.8.8']])
allow(Preview::HttpFetcher).to receive(:fetch)
.with('https://example.com/thumb.jpg')
.and_raise(Preview::UrlSafety::UnsafeUrl, '安全でない接続先は使用できません.')
expect {
described_class.fetch_image_response('https://example.com/thumb.jpg')
}.to raise_error(Preview::UrlSafety::UnsafeUrl, '安全でない接続先は使用できません.')
end
it 'rejects an oversized image response' do
allow(Preview::UrlSafety).to receive(:validate)
.and_return([URI.parse('https://example.com/thumb.jpg'), ['8.8.8.8']])
allow(Preview::HttpFetcher).to receive(:fetch)
.with('https://example.com/thumb.jpg')
.and_raise(Preview::HttpFetcher::ResponseTooLarge, 'too large')
expect {
described_class.fetch_image_response('https://example.com/thumb.jpg')
}.to raise_error(Preview::HttpFetcher::ResponseTooLarge)
end
it 'rejects a non-image content type' do
allow(Preview::UrlSafety).to receive(:validate)
.and_return([URI.parse('https://example.com/thumb.jpg'), ['8.8.8.8']])
allow(Preview::HttpFetcher).to receive(:fetch)
.with('https://example.com/thumb.jpg')
.and_return(
Preview::HttpFetcher::Response.new(
'<html></html>',
'text/html',
'https://example.com/thumb.jpg'))
expect {
described_class.fetch_image_response('https://example.com/thumb.jpg')
}.to raise_error(Preview::ThumbnailFetcher::GenerationFailed)
end
end
end
+32 -1
ファイルの表示
@@ -10,6 +10,37 @@ RSpec.describe Youtube::Sync do
allow(sync).to receive(:attach_thumbnail_if_needed!)
end
describe '#attach_thumbnail_if_needed!' do
it 'uses the common remote thumbnail attach path' do
post = create(:post, thumbnail_base: nil)
allow(sync).to receive(:attach_thumbnail_if_needed!).and_call_original
expect(post).to receive(:attach_thumbnail_from_url!)
.with('https://example.com/thumb.jpg')
sync.send(
:attach_thumbnail_if_needed!,
post,
'https://example.com/thumb.jpg')
end
it 'retries on a later sync when the previous remote attach failed' do
post = create(:post, thumbnail_base: nil)
allow(sync).to receive(:attach_thumbnail_if_needed!).and_call_original
expect(post).to receive(:attach_thumbnail_from_url!)
.with('https://example.com/thumb.jpg')
.twice
.and_raise(Post::RemoteThumbnailFetchFailed, 'failed')
2.times do
sync.send(
:attach_thumbnail_if_needed!,
post,
'https://example.com/thumb.jpg')
end
end
end
describe '#sync!' do
it 'returns without fetching video details when no video ids are discovered' do
allow(sync).to receive(:query_terms).and_return([])
@@ -76,7 +107,7 @@ RSpec.describe Youtube::Sync do
sync.sync!
end
it 'creates a YouTube post with default tags and no_deerjikist when no deerjikist mapping exists' do
it 'creates a YouTube post with default tags when no deerjikist mapping exists' do
Tag.tagme
Tag.bot
Tag.youtube