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 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 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