├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── image_info.gemspec ├── lib ├── image_info.rb └── image_info │ ├── configurable.rb │ ├── configuration.rb │ ├── image.rb │ ├── null_parser.rb │ ├── parser.rb │ ├── processor.rb │ ├── request_handler.rb │ └── version.rb └── spec ├── fixtures ├── ad.gif ├── basecamp.gif ├── close.png ├── short.tif └── upload_bird.jpg ├── image_info ├── configurable_spec.rb ├── image_spec.rb ├── parser_spec.rb └── processor_spec.rb ├── image_info_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Metrics/BlockLength: 2 | Exclude: 3 | - 'spec/**/*' 4 | 5 | Gemspec/RequiredRubyVersion: 6 | Enabled: false 7 | 8 | Lint/AmbiguousBlockAssociation: 9 | Exclude: 10 | - 'spec/**/*' 11 | 12 | Style/Documentation: 13 | Enabled: false 14 | 15 | Style/FrozenStringLiteralComment: 16 | Enabled: false 17 | 18 | AllCops: 19 | NewCops: disable 20 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.6 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.5 4 | - 2.6 5 | script: 6 | - bundle exec rake 7 | - bundle exec rubocop 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [1.2.2] - 2020-10-06 10 | ### Fixed 11 | 12 | - Fix an issue when we tried to explicitely set the URI port number 13 | 14 | ## [1.2.1] - 2020-09-28 15 | ### Added 16 | 17 | - Adds Rubocop as a dev dependency. 18 | 19 | ### Fixed 20 | 21 | - Fixes `URI.encode` obsolete warning by using `Addressable` instead. 22 | 23 | ## [1.2.0] - 2020-07-24 24 | ### Added 25 | 26 | - Allows to set a maximum image size in bytes to fetch. The HTTP connection 27 | will be aborted as soon as the max size is reached. Setting a value to 28 | `zero` or a negative number will deactivate this feature. Default to `-1` not 29 | to break retro compatibility. 30 | 31 | ### Fixed 32 | 33 | - Partial image loading was sometime yielding empty results. Image size and 34 | type was not set properly in this case. 35 | - `max_concurrency` option was not properly set by default. 36 | - Locks dependencies to their major version. 37 | - `typhoeus` is now locked to `~> 1.0` 38 | - `image_size` is now locked to `~> 2.0` 39 | 40 | ## [1.1.2] - 2015-08-12 41 | ### Fixed 42 | 43 | - Fix issues with schemaless uri (ex: `//foo.com`) 44 | 45 | ## [1.1.1] - 2015-08-11 46 | ### Adds 47 | 48 | - Adds image partial loading and abort the connection as soon as image 49 | size and type has been found. 50 | 51 | ### Fixed 52 | 53 | - Fixes URL encoding issues. 54 | 55 | ## [1.1.0] - 2015-07-19 56 | ### Adds 57 | 58 | - Introduces `width` and `height` method. 59 | 60 | ## [1.0.0] - 2015-07-19 61 | ### Adds 62 | 63 | - First release 🎆 64 | 65 | [Unreleased]: https://github.com/gottfrois/image_info/compare/v1.2.2...HEAD 66 | [1.2.2]: https://github.com/gottfrois/image_info/compare/v1.2.1...v1.2.2 67 | [1.2.1]: https://github.com/gottfrois/image_info/compare/v1.2.0...v1.2.1 68 | [1.2.0]: https://github.com/gottfrois/image_info/compare/v1.1.2...v1.2.0 69 | [1.1.2]: https://github.com/gottfrois/image_info/compare/v1.1.1...v1.1.2 70 | [1.1.1]: https://github.com/gottfrois/image_info/compare/v1.1.0...v1.1.1 71 | [1.1.0]: https://github.com/gottfrois/image_info/compare/v1.0.0...v1.1.0 72 | [1.0.0]: https://github.com/gottfrois/image_info/releases/tag/v1.0.0 73 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in image_info.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pierre-Louis Gottfrois 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageInfo 2 | 3 | [![Code Climate](https://codeclimate.com/github/gottfrois/image_info/badges/gpa.svg)](https://codeclimate.com/github/gottfrois/image_info) 4 | [![Build Status](https://travis-ci.org/gottfrois/image_info.svg)](https://travis-ci.org/gottfrois/image_info) 5 | [![Dependency Status](https://gemnasium.com/gottfrois/image_info.svg)](https://gemnasium.com/gottfrois/image_info) 6 | [![Gem Version](https://badge.fury.io/rb/image_info.svg)](http://badge.fury.io/rb/image_info) 7 | 8 | ImageInfo finds the size and type of a single or multiple images from the web by fetching as little data as needed (partial image) in batches. 9 | 10 | ## Why 11 | 12 | In [LinkThumbnailer](https://github.com/gottfrois/link_thumbnailer) I needed to find images sizes not only for one image. 13 | A well known gem like FastImage was not enough so I decided to build my own. 14 | The gem use [typhoeus](https://github.com/typhoeus/typhoeus)'s parallel requests and stream capability under the hood to 15 | get images. 16 | 17 | ## Installation 18 | 19 | Add this line to your application's Gemfile: 20 | 21 | ```ruby 22 | gem 'image_info' 23 | ``` 24 | 25 | And then execute: 26 | 27 | $ bundle 28 | 29 | Or install it yourself as: 30 | 31 | $ gem install image_info 32 | 33 | ## Usage 34 | 35 | For a single image: 36 | 37 | ```ruby 38 | image = ImageInfo.from('http://foo.com/foo.png').first 39 | image.width 40 | => 256 41 | image.height 42 | => 256 43 | image.size 44 | => [256, 256] 45 | image.type 46 | => :png 47 | image.uri 48 | => # 49 | ``` 50 | 51 | For multiple images: 52 | 53 | ```ruby 54 | images = ImageInfo.from(['http://foo.com/foo.png', 'http://foo.com/bar.jpg']) 55 | images.map &:size 56 | => [[256, 256], [128, 128]] 57 | images.map &:type 58 | => [:png, :jpeg] 59 | ``` 60 | 61 | ## Configuration 62 | 63 | ### Concurrency 64 | 65 | You can configure the `max_concurrency` value (20 by default) used to fetch images in parallel: 66 | 67 | ```ruby 68 | ImageInfo.configure do |config| 69 | config.max_concurrency = 10 70 | end 71 | ``` 72 | 73 | or at runtime: 74 | 75 | ```ruby 76 | ImageInfo.from('http://foo.com/foo.png', max_concurrency: 10) 77 | ``` 78 | 79 | ### Image Size Limit 80 | 81 | You can set a `max_image_size` for which the connection will be aborted 82 | if reached. 83 | 84 | ```ruby 85 | ImageInfo.configure do |config| 86 | config.max_image_size = 5 * 1024 * 1024 # 5Mb 87 | end 88 | ``` 89 | 90 | or at runtime: 91 | 92 | ```ruby 93 | ImageInfo.from('http://foo.com/foo.png', max_image_size: -1) 94 | ``` 95 | 96 | Setting the value to zero or a negative number simply disable the limit. 97 | By default it is set to `-1`. 98 | 99 | ## Development 100 | 101 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 102 | 103 | To install this gem onto your local machine, run `bundle exec rake install`. 104 | 105 | ## Contributing 106 | 107 | Bug reports and pull requests are welcome on GitHub at https://github.com/gottfrois/image_info. 108 | 109 | 110 | ## License 111 | 112 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 113 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task default: :spec 7 | task test: :spec 8 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'image_info' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /image_info.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'image_info/version' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'image_info' 7 | spec.version = ImageInfo::VERSION 8 | spec.authors = ['Pierre-Louis Gottfrois'] 9 | spec.email = ['pierrelouis.gottfrois@gmail.com'] 10 | 11 | spec.summary = 'ImageInfo finds the size and type of a single or ' \ 12 | 'multiple images from the web by fetching as little as ' \ 13 | 'needed.' 14 | spec.description = 'ImageInfo finds the size and type of a single or ' \ 15 | 'multiple images from the web by fetching as little as ' \ 16 | 'needed.' 17 | spec.homepage = 'https://github.com/gottfrois/image_info' 18 | spec.license = 'MIT' 19 | 20 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 21 | spec.bindir = 'exe' 22 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 23 | spec.require_paths = ['lib'] 24 | 25 | spec.add_dependency 'addressable', '~> 2.0' 26 | spec.add_dependency 'image_size', '~> 2.0' 27 | spec.add_dependency 'typhoeus', '~> 1.0' 28 | 29 | spec.add_development_dependency 'bundler', '~> 1.10' 30 | spec.add_development_dependency 'rake', '~> 10.0' 31 | spec.add_development_dependency 'rspec' 32 | spec.add_development_dependency 'rubocop' 33 | spec.add_development_dependency 'webmock' 34 | end 35 | -------------------------------------------------------------------------------- /lib/image_info.rb: -------------------------------------------------------------------------------- 1 | require 'image_info/version' 2 | require 'image_info/processor' 3 | require 'image_info/configurable' 4 | 5 | module ImageInfo 6 | extend Configurable 7 | 8 | # Returns type and size given an url or a list of urls. 9 | # 10 | # @example 11 | # ImageInfo.from('http://foo.com/foo.png').first.size 12 | # => [250, 86] 13 | # 14 | # @example 15 | # ImageInfo.from(['http://foo.com/foo.png', 'http://foo.com/bar.jpeg']).map(&:type) 16 | # => [:png, :jpeg] 17 | # 18 | # @return [Array] 19 | def self.from(urls, options = { max_concurrency: config.max_concurrency }) 20 | ::ImageInfo::Processor.new(urls, options).process 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/image_info/configurable.rb: -------------------------------------------------------------------------------- 1 | require 'image_info/configuration' 2 | 3 | module ImageInfo 4 | module Configurable 5 | def config 6 | @config ||= ::ImageInfo::Configuration.new 7 | end 8 | 9 | def configure 10 | yield config if block_given? 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/image_info/configuration.rb: -------------------------------------------------------------------------------- 1 | module ImageInfo 2 | class Configuration 3 | attr_accessor :max_concurrency, :max_image_size 4 | 5 | def initialize 6 | @max_concurrency = 20 7 | @max_image_size = -1 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/image_info/image.rb: -------------------------------------------------------------------------------- 1 | require 'addressable/uri' 2 | 3 | module ImageInfo 4 | class Image 5 | attr_reader :uri 6 | attr_accessor :width, :height, :type 7 | 8 | def initialize(uri) 9 | @uri = ::Addressable::URI.parse(uri.to_s) 10 | @uri.scheme = 'http' unless @uri.scheme 11 | @uri.normalize! 12 | rescue ::Addressable::URI::InvalidURIError 13 | @uri = NullUri.new 14 | end 15 | 16 | def size 17 | [width, height].compact 18 | end 19 | 20 | def valid? 21 | !!uri.host 22 | end 23 | 24 | class NullUri 25 | def host; end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/image_info/null_parser.rb: -------------------------------------------------------------------------------- 1 | module ImageInfo 2 | class NullParser 3 | def width; end 4 | 5 | def height; end 6 | 7 | def format; end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/image_info/parser.rb: -------------------------------------------------------------------------------- 1 | require 'image_size' 2 | require 'image_info/null_parser' 3 | 4 | module ImageInfo 5 | class Parser 6 | attr_reader :image, :data 7 | 8 | def initialize(image, data) 9 | @image = image 10 | @data = data 11 | end 12 | 13 | def call 14 | set_image_size 15 | set_image_type 16 | end 17 | 18 | private 19 | 20 | def set_image_size 21 | image.width = parser.width 22 | image.height = parser.height 23 | end 24 | 25 | def set_image_type 26 | image.type = parser.format 27 | end 28 | 29 | def parser 30 | @parser ||= ::ImageSize.new(data) 31 | rescue ::ImageSize::FormatError, NoMethodError 32 | @parser ||= ::ImageInfo::NullParser.new 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/image_info/processor.rb: -------------------------------------------------------------------------------- 1 | require 'typhoeus' 2 | 3 | require 'image_info/image' 4 | require 'image_info/parser' 5 | require 'image_info/request_handler' 6 | 7 | module ImageInfo 8 | class Processor 9 | attr_reader :images, :options 10 | 11 | def initialize(urls, options = { max_concurrency: ::ImageInfo.config.max_concurrency }) 12 | @images = Array(urls).map { |uri| ::ImageInfo::Image.new(uri) }.keep_if(&:valid?) 13 | @options = options 14 | end 15 | 16 | def process 17 | images.each { |image| hydra.queue(::ImageInfo::RequestHandler.new(image).build) } 18 | hydra.run 19 | 20 | images 21 | end 22 | 23 | private 24 | 25 | def hydra 26 | @hydra ||= ::Typhoeus::Hydra.new(max_concurrency: options[:max_concurrency].to_i) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/image_info/request_handler.rb: -------------------------------------------------------------------------------- 1 | require 'stringio' 2 | 3 | module ImageInfo 4 | class RequestHandler 5 | attr_reader :image, :buffer 6 | 7 | def initialize(image) 8 | @image = image 9 | @buffer = StringIO.new 10 | end 11 | 12 | def build 13 | ::Typhoeus::Request.new(image.uri.to_s, followlocation: true, accept_encoding: :gzip).tap do |request| 14 | request.on_body do |chunk| 15 | buffer.write(chunk) 16 | buffer.rewind 17 | :abort if max_image_size_reached? || found_image_info? 18 | end 19 | end 20 | end 21 | 22 | private 23 | 24 | def found_image_info? 25 | ::ImageInfo::Parser.new(image, buffer).call 26 | end 27 | 28 | def max_image_size_reached? 29 | return false if max_image_size <= 0 30 | 31 | buffer.size > max_image_size 32 | end 33 | 34 | def max_image_size 35 | ::ImageInfo.config.max_image_size 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/image_info/version.rb: -------------------------------------------------------------------------------- 1 | module ImageInfo 2 | VERSION = '1.2.2'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /spec/fixtures/ad.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottfrois/image_info/36b8d2617e77219b212f9ee90fb2d2fafae9138d/spec/fixtures/ad.gif -------------------------------------------------------------------------------- /spec/fixtures/basecamp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottfrois/image_info/36b8d2617e77219b212f9ee90fb2d2fafae9138d/spec/fixtures/basecamp.gif -------------------------------------------------------------------------------- /spec/fixtures/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottfrois/image_info/36b8d2617e77219b212f9ee90fb2d2fafae9138d/spec/fixtures/close.png -------------------------------------------------------------------------------- /spec/fixtures/short.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottfrois/image_info/36b8d2617e77219b212f9ee90fb2d2fafae9138d/spec/fixtures/short.tif -------------------------------------------------------------------------------- /spec/fixtures/upload_bird.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gottfrois/image_info/36b8d2617e77219b212f9ee90fb2d2fafae9138d/spec/fixtures/upload_bird.jpg -------------------------------------------------------------------------------- /spec/image_info/configurable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ImageInfo::Configurable do 4 | let(:klass) do 5 | Class.new do 6 | include ImageInfo::Configurable 7 | end 8 | end 9 | let(:instance) { klass.new } 10 | 11 | describe '#configure' do 12 | let(:configuration) { ImageInfo::Configuration.new } 13 | 14 | it 'yields' do 15 | expect(instance).to receive(:configure).and_yield(configuration) 16 | instance.configure { |c| } 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/image_info/image_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ImageInfo::Image do 4 | let(:uri) { 'http://foo.com/foo.png' } 5 | let(:instance) { described_class.new(uri) } 6 | 7 | it { expect(instance.width).to eq(nil) } 8 | it { expect(instance.height).to eq(nil) } 9 | it { expect(instance.valid?).to eq(true) } 10 | 11 | describe '#size' do 12 | it { expect(instance.size).to eq([]) } 13 | 14 | context 'when set' do 15 | before do 16 | instance.width = 1 17 | instance.height = 2 18 | end 19 | 20 | it { expect(instance.size).to eq([1, 2]) } 21 | end 22 | end 23 | 24 | describe 'parsing invalid uri does not crash' do 25 | context 'trys to fix invalid characters' do 26 | let(:url) do 27 | 'http://karrierebibel+fr.de/wp-content/uploads/2015/03/7_Todsünden_der_Jobsuche_sauer_wütend.jpg' 28 | end 29 | let(:uri) do 30 | Addressable::URI.parse( 31 | 'http://karrierebibel+fr.de/wp-content/uploads/2015/03/7_Tods%C3%BCnden_der_Jobsuche_sauer_w%C3%BCtend.jpg' 32 | ) 33 | end 34 | 35 | it { expect(instance.valid?).to eq(true) } 36 | it { expect(instance.uri).to eq(uri) } 37 | end 38 | end 39 | 40 | describe '#valid?' do 41 | subject(:valid?) { instance.valid? } 42 | 43 | context 'when uri is not valid' do 44 | let(:uri) { 'foo' } 45 | 46 | it { expect(valid?).to eq(false) } 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/image_info/parser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ImageInfo::Parser do 4 | let(:uri) { 'http://foo.com' } 5 | let(:image) { ImageInfo::Image.new(uri) } 6 | let(:instance) { described_class.new(image, data) } 7 | 8 | describe '#call' do 9 | subject(:call) { instance.call } 10 | 11 | context 'animated_gif' do 12 | let(:data) { File.read(File.expand_path('../fixtures/ad.gif', __dir__)) } 13 | 14 | it { expect { call }.to change { image.type }.to eq(:gif) } 15 | it { expect { call }.to change { image.size }.to eq([300, 250]) } 16 | end 17 | 18 | context 'static gif' do 19 | let(:data) { File.read(File.expand_path('../fixtures/basecamp.gif', __dir__)) } 20 | 21 | it { expect { call }.to change { image.type }.to eq(:gif) } 22 | it { expect { call }.to change { image.size }.to eq([153, 36]) } 23 | end 24 | 25 | context 'png' do 26 | let(:data) { File.read(File.expand_path('../fixtures/close.png', __dir__)) } 27 | 28 | it { expect { call }.to change { image.type }.to eq(:png) } 29 | it { expect { call }.to change { image.size }.to eq([25, 25]) } 30 | end 31 | 32 | context 'jpeg' do 33 | let(:data) { File.read(File.expand_path('../fixtures/upload_bird.jpg', __dir__)) } 34 | 35 | it { expect { call }.to change { image.type }.to eq(:jpeg) } 36 | it { expect { call }.to change { image.size }.to eq([775, 525]) } 37 | end 38 | 39 | context 'tiff' do 40 | let(:data) { File.read(File.expand_path('../fixtures/short.tif', __dir__)) } 41 | 42 | it { expect { call }.to change { image.type }.to eq(:tiff) } 43 | it { expect { call }.to change { image.size }.to eq([50, 20]) } 44 | end 45 | 46 | context 'partial image with not enough data' do 47 | let(:data) { File.open(File.expand_path('../fixtures/upload_bird.jpg', __dir__)).read(50) } 48 | 49 | it { expect { call }.not_to change { image.type } } 50 | it { expect { call }.not_to change { image.size } } 51 | it { expect(call).to be_falsy } 52 | end 53 | 54 | context 'partial image with enough data' do 55 | let(:data) { File.open(File.expand_path('../fixtures/upload_bird.jpg', __dir__)).read(400) } 56 | 57 | it { expect { call }.to change { image.type }.to eq(:jpeg) } 58 | it { expect { call }.to change { image.size }.to eq([775, 525]) } 59 | it { expect(call).to be_truthy } 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/image_info/processor_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ImageInfo::Processor do 4 | let(:instance) { described_class.new(urls) } 5 | 6 | describe '#process' do 7 | context 'with one valid url' do 8 | let(:urls) { 'http://foo.com' } 9 | let!(:stub) { stub_request(:get, urls).to_return(body: '', status: 200) } 10 | 11 | it 'requests url' do 12 | instance.process 13 | expect(stub).to have_been_requested 14 | end 15 | 16 | it { expect(instance.process.count).to eq(1) } 17 | it { expect(instance.process.first).to be_a(ImageInfo::Image) } 18 | it { expect(instance.process.first).to be_valid } 19 | end 20 | 21 | context 'with one invalid url' do 22 | let(:urls) { 'foo' } 23 | let!(:stub) { stub_request(:get, urls).to_return(body: '', status: 200) } 24 | 25 | it 'does not request url' do 26 | instance.process 27 | expect(stub).not_to have_been_requested 28 | end 29 | 30 | it { expect(instance.process.count).to eq(0) } 31 | end 32 | 33 | context 'with many valid urls' do 34 | let(:uri1) { 'http://foo.com' } 35 | let(:uri2) { '//bar.com' } 36 | let(:urls) { [uri1, uri2] } 37 | let!(:stub1) { stub_request(:get, uri1).to_return(body: '', status: 200) } 38 | let!(:stub2) { stub_request(:get, uri2).to_return(body: '', status: 200) } 39 | 40 | it 'requests url' do 41 | instance.process 42 | expect(stub1).to have_been_requested 43 | expect(stub2).to have_been_requested 44 | end 45 | 46 | it { expect(instance.process.count).to eq(2) } 47 | it { expect(instance.process.first).to be_a(ImageInfo::Image) } 48 | it { expect(instance.process.last).to be_a(ImageInfo::Image) } 49 | it { expect(instance.process.first).to be_valid } 50 | it { expect(instance.process.last).to be_valid } 51 | end 52 | 53 | context 'with some invalid urls' do 54 | let(:uri1) { 'http://foo.com' } 55 | let(:uri2) { 'bar' } 56 | let(:urls) { [uri1, uri2] } 57 | let!(:stub1) { stub_request(:get, uri1).to_return(body: '', status: 200) } 58 | let!(:stub2) { stub_request(:get, uri2).to_return(body: '', status: 200) } 59 | 60 | it 'requests url' do 61 | instance.process 62 | expect(stub1).to have_been_requested 63 | expect(stub2).not_to have_been_requested 64 | end 65 | 66 | it { expect(instance.process.count).to eq(1) } 67 | it { expect(instance.process.first).to be_a(ImageInfo::Image) } 68 | it { expect(instance.process.first).to be_valid } 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/image_info_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ImageInfo do 4 | let(:url) { 'http://foo.com/ad.gif' } 5 | let(:data) { File.read(File.expand_path('fixtures/ad.gif', __dir__)) } 6 | 7 | before do 8 | stub_request(:get, url).to_return(body: data, status: 200) 9 | end 10 | 11 | subject(:image) { described_class.from(url).first } 12 | 13 | it { expect(image.width).to eq(300) } 14 | it { expect(image.height).to eq(250) } 15 | it { expect(image.size).to eq([300, 250]) } 16 | it { expect(image.type).to eq(:gif) } 17 | end 18 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 2 | 3 | require 'image_info' 4 | require 'webmock/rspec' 5 | --------------------------------------------------------------------------------