rack_resize 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09480d8110a0fd174b70144ea5df62c1146f2c16399cd9e173a0638d5cf818b7'
4
- data.tar.gz: 24f936049e0bed57e1ff90c49f588d48480700adae2b2b6829685df011ecb89f
3
+ metadata.gz: 48043ad5846a54c36958ce1d2b4648de14b01c30db46ec8dcdb2b601628118f2
4
+ data.tar.gz: 2ccd0c5703c104cb952bd4e299fc6e1be49ef2e08849f2a9637d661adca13a22
5
5
  SHA512:
6
- metadata.gz: be9835971015420520f16a1332a0ac5664668150f06d6f577148803a7a299142c9edffdd9e33581d64b76b0a0aa8fb2cec9cbd2ce8524cdfa45f966721177610
7
- data.tar.gz: b48663c5733f65c3e55fff126df2a31ff99f64bc0fceccd3c9fbc10261ea0ed5acfd99e7705333e24486ef6464d776df2d7ab9b92c5d8ab198d8646b1ae0d224
6
+ metadata.gz: 9fa7b2853a0701bf476f9730d1aae31ffc9edb28f53d032e8d51acb3c7e82fdd07e64e2225d4195507bfde69956b44bc48152d9d4b1c4e2fa840fe140d592518
7
+ data.tar.gz: e0b76b4d16323c0a87bae2d8c61fa2d44d0a72d1d27c6d8872bd091084ab2a5f9055216a48af9724af5ccff8dba6cf782acf8f7a38ca0216039318a87268c479
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.5
data/Gemfile CHANGED
@@ -6,5 +6,9 @@ gem 'image_processing'
6
6
  gem 'mini_magick'
7
7
  gem 'ruby-vips'
8
8
  gem 'rszr'
9
+
10
+ gem 'railties'
11
+
9
12
  gem 'minitest'
13
+ gem 'minitest-reporters'
10
14
  gem 'rake'
data/README.md CHANGED
@@ -32,7 +32,7 @@ Fastly and bunny.net: (tbd)
32
32
 
33
33
  ```ruby
34
34
  RackResize.configure do |config|
35
- config.assets_folder = Rails.root.join('app', 'assets', 'images')
35
+ config.assets_folders = { "assets" => Rails.root.join('app', 'assets', 'images') }
36
36
  config.processor = :sips / :vips / :mini_magick / :imlib2
37
37
  config.default_quality = 95
38
38
  config.save_resized = false
@@ -3,8 +3,8 @@ require 'pathname'
3
3
  class RackResize::Configuration
4
4
  PROCESSORS = %i[sips vips mini_magick imlib2].freeze
5
5
 
6
- attr_reader :processor
7
- attr_accessor :save_resized, :default_quality, :cache_folder, :assets_folder, :http_cache_max_age
6
+ attr_reader :processor, :assets_folders
7
+ attr_accessor :save_resized, :default_quality, :cache_folder, :http_cache_max_age, :max_dimension, :logger
8
8
 
9
9
  alias_method :save_resized?, :save_resized
10
10
 
@@ -13,12 +13,20 @@ class RackResize::Configuration
13
13
  @save_resized = options.key?(:save_resized) ? options[:save_resized] : false
14
14
  @default_quality = options[:default_quality] || 95
15
15
  @cache_folder = options[:cache_folder]
16
- @assets_folder = options[:assets_folder] ? Pathname.new(options[:assets_folder]) : nil
17
16
  @http_cache_max_age = options[:http_cache_max_age] || 86400 # 1 day
17
+ @max_dimension = options[:max_dimension] || 4000
18
+ @logger = options[:logger]
18
19
 
19
- if defined?(Rails)
20
+ self.assets_folders = options[:assets_folders] if options[:assets_folders]
21
+
22
+ if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
20
23
  @cache_folder ||= Rails.root.join('tmp', 'rack_resize_cache')
21
- @assets_folder ||= Rails.root.join('app', 'assets', 'images')
24
+ unless options.key?(:assets_folders)
25
+ self.assets_folders = {
26
+ "assets" => Rails.root.join("app/assets/images"),
27
+ "uploads" => Rails.root.join("public/uploads"),
28
+ }
29
+ end
22
30
  unless options.key?(:save_resized)
23
31
  @save_resized = true
24
32
  end
@@ -29,6 +37,17 @@ class RackResize::Configuration
29
37
  end
30
38
  end
31
39
 
40
+ def assets_folders=(values)
41
+ if values.is_a?(Array)
42
+ @assets_folders = values.map { |v| [format_asset_path(v), Pathname.new(v)] }.to_h
43
+ elsif values.is_a?(Hash)
44
+ @assets_folders = values.transform_values { |v| v.is_a?(Pathname) ? v : Pathname.new(v) }
45
+ .transform_keys {|k| format_asset_path(k) }
46
+ else
47
+ raise ArgumentError, "RackResize::Configuration.assets_folders can be either hash or array, received #{values.class}"
48
+ end
49
+ end
50
+
32
51
  def processor=(value)
33
52
  value = value.to_sym
34
53
  unless PROCESSORS.include?(value)
@@ -40,7 +59,7 @@ class RackResize::Configuration
40
59
 
41
60
  def processor_instance
42
61
  @processor_instance ||= case @processor
43
- when :mini_magick then RackResize::MiniMagick.new
62
+ when :mini_magick then RackResize::Processors::MiniMagick.new
44
63
  when :vips then RackResize::Processors::Vips.new
45
64
  when :sips then RackResize::Processors::Sips.new
46
65
  when :imlib2 then RackResize::Processors::Imlib2.new
@@ -48,4 +67,10 @@ class RackResize::Configuration
48
67
  raise "RackResize - Unknow image processor #{@processor.inspect}"
49
68
  end
50
69
  end
70
+
71
+ private
72
+
73
+ def format_asset_path(path)
74
+ path.start_with?('/') ? path.to_s : "/#{path}"
75
+ end
51
76
  end
@@ -1,4 +1,22 @@
1
- # frozen_string_literal: true
1
+ module RackResize::InputParsers::Cloudflare
2
+ extend self
2
3
 
3
- class Cloudflare
4
+ # process string like this
5
+ # /cdn-cgi/image/width=426,format=auto/assets/templates/vancouver-27c47f55.jpg
6
+
7
+ def parse_input(fullpath, cf_path_prefix:)
8
+ unless fullpath.start_with?(cf_path_prefix)
9
+ return { route_matched: false, req_params: nil, asset_path: nil }
10
+ end
11
+
12
+ fullpath = fullpath.delete_prefix("/cdn-cgi").delete_prefix("/image").delete_prefix("/")
13
+ file_path_match = fullpath.match(%r{(?<params>[^\/]+)(?<file>\/.+?)(-[\da-f]{8})?(?<ext>\.\w{2,})$})
14
+
15
+ return { route_matched: true, req_params: nil, asset_path: nil } unless file_path_match
16
+
17
+ req_params = file_path_match[:params].split(",").map {|s| s.split("=") }.to_h.transform_keys(&:to_sym)
18
+ asset_path = "#{file_path_match[:file]}#{file_path_match[:ext]}"
19
+
20
+ { route_matched: true, req_params:, asset_path: }
21
+ end
4
22
  end
@@ -1,3 +1,5 @@
1
+ require 'digest'
2
+
1
3
  class RackResize::Processing
2
4
  attr_reader :config
3
5
 
@@ -24,8 +26,11 @@ class RackResize::Processing
24
26
 
25
27
  def process_file(source_file:, req_params:, target_file: nil)
26
28
  dpr = req_params[:dpr]&.to_f || 1.0
27
- target_width = (req_params[:w]&.to_i || req_params[:width]&.to_i)&.*(dpr)
28
- target_height = (req_params[:h]&.to_i || req_params[:height]&.to_i)&.*(dpr)
29
+ dpr = [[dpr, 0.1].max, 10.0].min
30
+
31
+ max = config.max_dimension.to_f
32
+ target_width = (req_params[:w]&.to_i || req_params[:width]&.to_i)&.*(dpr)&.clamp(1, max)
33
+ target_height = (req_params[:h]&.to_i || req_params[:height]&.to_i)&.*(dpr)&.clamp(1, max)
29
34
 
30
35
  start_time = Time.now
31
36
  begin
@@ -37,13 +42,8 @@ class RackResize::Processing
37
42
  end
38
43
 
39
44
  def logger
40
- @logger ||= begin
41
- if defined?(Rails) && Rails.logger
42
- Rails.logger
43
- else
44
- require 'logger'
45
- Logger.new(STDOUT)
46
- end
47
- end
45
+ config.logger ||
46
+ (defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
47
+ (require('logger') && Logger.new(STDOUT))
48
48
  end
49
- end
49
+ end
@@ -11,7 +11,6 @@ class RackResize::Processors::Imlib2
11
11
  image.resize!(target_width || :auto, target_height || :auto) if target_width || target_height
12
12
 
13
13
  if target_file
14
- image.call(destination: target_file)
15
14
  image.save(target_file)
16
15
  else
17
16
  image.save_data(format: source_file.to_s =~ /\.png$/ ? :png : :jpeg)
@@ -45,9 +45,3 @@ class RackResize::Processors::MiniMagick
45
45
  image&.destroy!
46
46
  end
47
47
  end
48
-
49
- # magick /Users/pavel/Work/resume-io/app/assets/images/templates/vancouver.jpg -auto-orient -resize 426.0x> -quality 95 /Users/pavel/Work/resume-io/tmp/cdn_cgi_cache/60f2727cce350874040b321219f05766.jpg
50
-
51
-
52
- # magick mogrify -resize 426.0x> /var/folders/n9/b__lxh7j1_xcwg4k_8j2_39m0000gp/T/mini_magick20260709-58478-qowydk.jpg
53
- # magick mogrify -quality 95 /var/folders/n9/b__lxh7j1_xcwg4k_8j2_39m0000gp/T/mini_magick20260709-58478-qowydk.jpg
@@ -1,37 +1,41 @@
1
- require "shellwords"
1
+ require "open3"
2
2
 
3
3
  class RackResize::Processors::Sips
4
4
 
5
5
  def resize(source_file:, target_width:, target_height:, target_file: nil)
6
- # args = ["sips", "--deleteColorManagementProperties", "--optimizeColorForSharing"]
7
- args = ["sips", "--deleteColorManagementProperties", "--debug"]
8
- args += ["-s formatOptions", RackResize.config.default_quality]
9
- args << "--resampleWidth" << target_width.to_i if target_width
10
- args << "--resampleHeight" << target_height.to_i if target_height
11
-
12
- if target_file
13
- args << "-o" << Shellwords.escape(target_file)
6
+ args = %w[sips --deleteColorManagementProperties]
7
+ args += ["-s", "formatOptions", RackResize.config.default_quality.to_s]
8
+
9
+ if target_width && target_height
10
+ info, status = Open3.capture2("sips", "-g", "pixelWidth", "-g", "pixelHeight", source_file.to_s)
11
+ raise "sips failed to read dimensions for #{source_file}" unless status.success?
12
+ src_w = info[/pixelWidth: (\d+)/, 1]&.to_f
13
+ src_h = info[/pixelHeight: (\d+)/, 1]&.to_f
14
+ if src_w && src_h && (target_width.to_f / src_w) <= (target_height.to_f / src_h)
15
+ args += ["--resampleWidth", target_width.to_i.to_s]
16
+ else
17
+ args += ["--resampleHeight", target_height.to_i.to_s]
18
+ end
14
19
  else
15
- tmp_file = Tempfile.new(["result", File.extname(source_file)])
16
- args << "-o" << tmp_file.path
20
+ args += ["--resampleWidth", target_width.to_i.to_s] if target_width
21
+ args += ["--resampleHeight", target_height.to_i.to_s] if target_height
17
22
  end
18
23
 
19
- args << Shellwords.escape(source_file)
20
-
21
- pp [:args, args, args.join(" ")]
22
-
23
- result = `#{args.join(" ")}`
24
-
25
- puts "sips command result: #{result}"
26
-
27
- unless target_file
28
- begin
29
- return File.open(tmp_file.path, 'rb', &:read)
30
- ensure
31
- tmp_file.unlink
32
- end
24
+ if target_file
25
+ _, status = Open3.capture2(*args, "-o", target_file.to_s, source_file.to_s)
26
+ raise "sips failed (exit #{status.exitstatus}) for #{source_file}" unless status.success?
27
+ return nil
33
28
  end
34
29
 
35
- nil
30
+ tmp = Tempfile.new(["result", File.extname(source_file)])
31
+ tmp_path = tmp.path || raise("tempfile has no path")
32
+ begin
33
+ _, status = Open3.capture2(*args, "-o", tmp_path, source_file.to_s)
34
+ raise "sips failed (exit #{status.exitstatus}) for #{source_file}" unless status.success?
35
+ File.binread(tmp_path)
36
+ ensure
37
+ tmp.close
38
+ tmp.unlink
39
+ end
36
40
  end
37
41
  end
@@ -11,8 +11,6 @@ class RackResize::Processors::Vips
11
11
  image = image.resize_to_limit(target_width, target_height) if target_width || target_height
12
12
  image = image.saver(quality: RackResize.config.default_quality)
13
13
 
14
- # image.call(destination: target_file)
15
-
16
14
  if target_file
17
15
  image.call(destination: target_file)
18
16
  else
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'rack'
4
2
 
5
3
  class RackResize::RackApp
@@ -20,33 +18,38 @@ class RackResize::RackApp
20
18
  request = Rack::Request.new(env)
21
19
  fullpath = request.path_info
22
20
 
23
- unless fullpath.start_with?(@cf_path_prefix)
24
- return @app.call(env)
25
- end
26
-
27
- # process string like this
28
- # /cdn-cgi/image/width=426,format=auto/assets/templates/vancouver-27c47f55.jpg
21
+ RackResize::InputParsers::Cloudflare.parse_input(fullpath, cf_path_prefix: @cf_path_prefix) =>
22
+ {route_matched:, req_params:, asset_path:}
29
23
 
30
- fullpath = fullpath.delete_prefix("/cdn-cgi").delete_prefix("/image").delete_prefix("/")
31
- file_path_match = fullpath.match(%r{(?<params>[^\/]+)(?<file>\/.+?)(-[\da-f]{8})?(?<ext>\.\w{2,})$})
24
+ return @app.call(env) unless route_matched
25
+ return error_resp("can't parse file path") unless asset_path
32
26
 
33
- return error_resp("can't parse file path") unless file_path_match
27
+ return error_resp("no assets folders configured") if config.assets_folders.nil? || config.assets_folders.empty?
34
28
 
35
- req_params = file_path_match[:params].split(",").map {|s| s.split("=") }.to_h.transform_keys(&:to_sym)
36
- asset_path = "#{file_path_match[:file]}#{file_path_match[:ext]}"
37
-
38
- if defined?(Rails)
39
- asset_path = asset_path.delete_prefix("/assets/")
40
- else
41
- asset_path = asset_path.delete_prefix("/#{config.assets_folder}/")
29
+ has_matched = false
30
+ asset_file = nil
31
+ config.assets_folders.each do |prefix, folder|
32
+ if asset_path.start_with?(prefix)
33
+ asset_file = folder.join(asset_path.delete_prefix(prefix + (prefix.end_with?("/") ? "" : "/")))
34
+ if asset_file.expand_path.to_s.start_with?(folder.to_s)
35
+ has_matched = true
36
+ end
37
+ break
38
+ end
42
39
  end
43
40
 
44
- asset_file = config.assets_folder.join(asset_path.sub(%r{^/assets/}, ''))
45
-
46
- return error_resp(".. is not allowed in image path") if asset_path.include?("..")
47
-
48
- return error_resp("invalid file path") unless asset_file.to_s.start_with?(config.assets_folder.to_s)
49
- return error_resp("file not exists on a server") unless asset_file.exist?
41
+ if asset_path.include?("..")
42
+ @processing.logger.info("RackResize::RackApp - File path has invalid byte sequence #{asset_path}")
43
+ return error_resp(".. is not allowed in image path")
44
+ end
45
+ unless has_matched
46
+ @processing.logger.info("RackResize::RackApp - requested file #{asset_path} not match any of configured assets folder #{config.assets_folders.keys}")
47
+ return error_resp("invalid file path")
48
+ end
49
+ unless asset_file.exist?
50
+ @processing.logger.info("RackResize::RackApp - File path fond #{asset_path} => #{asset_file}")
51
+ return error_resp("file not exists on a server")
52
+ end
50
53
 
51
54
  file_content = @processing.process!(source_file: asset_file, req_params:)
52
55
  return send_file(asset_file:, file_content:)
@@ -1,7 +1,7 @@
1
1
  require "rack_resize"
2
2
  require "rails"
3
3
 
4
- module MyGem
4
+ module RackResize
5
5
  class Railtie < Rails::Railtie
6
6
  initializer "rack_resize.auto_register_itself" do |app|
7
7
  app.config.middleware.insert_before 0, RackResize::RackApp
data/lib/rack_resize.rb CHANGED
@@ -7,10 +7,14 @@ module RackResize
7
7
  module Processors
8
8
  autoload :Sips, "#{__dir__}/rack_resize/processors/sips"
9
9
  autoload :Vips, "#{__dir__}/rack_resize/processors/vips"
10
- autoload :MiniMagic, "#{__dir__}/rack_resize/processors/mini_magic"
10
+ autoload :MiniMagick, "#{__dir__}/rack_resize/processors/mini_magick"
11
11
  autoload :Imlib2, "#{__dir__}/rack_resize/processors/imlib2"
12
12
  end
13
13
 
14
+ module InputParsers
15
+ autoload :Cloudflare, "#{__dir__}/rack_resize/input_parsers/cloudflare"
16
+ end
17
+
14
18
  class << self
15
19
  def configuration
16
20
  @configuration ||= Configuration.new
data/rack_resize.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rack_resize"
3
- s.version = "0.1.1"
3
+ s.version = "0.1.2"
4
4
  s.author = ["Pavel Evstigneev"]
5
5
  s.email = ["pavel.evst@gmail.com"]
6
6
  s.homepage = "https://github.com/paxa/rack_resize"
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.license = 'MIT'
9
9
  s.required_ruby_version = ['>= 3.0']
10
10
 
11
- s.files = `git ls-files`.split("\n")
11
+ s.files = `git ls-files`.split("\n").delete_if{|f| f.start_with?('samples', 'test') }
12
12
  s.test_files = []
13
13
 
14
14
  s.require_paths = ["lib"]
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack_resize
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavel Evstigneev
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-07-16 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rack
@@ -37,6 +37,7 @@ extra_rdoc_files: []
37
37
  files:
38
38
  - ".github/workflows/test.yml"
39
39
  - ".gitignore"
40
+ - ".ruby-version"
40
41
  - Gemfile
41
42
  - README.md
42
43
  - Rakefile
@@ -52,13 +53,6 @@ files:
52
53
  - lib/rack_resize/rack_app.rb
53
54
  - lib/rack_resize/rails_autoload.rb
54
55
  - rack_resize.gemspec
55
- - samples/image_1.jpeg
56
- - samples/image_2.jpeg
57
- - samples/image_3.jpeg
58
- - samples/image_4.jpeg
59
- - samples/index.html
60
- - test/rack_app_test.rb
61
- - test/test_helper.rb
62
56
  homepage: https://github.com/paxa/rack_resize
63
57
  licenses:
64
58
  - MIT
@@ -77,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
71
  - !ruby/object:Gem::Version
78
72
  version: '0'
79
73
  requirements: []
80
- rubygems_version: 3.6.5
74
+ rubygems_version: 4.0.10
81
75
  specification_version: 4
82
76
  summary: Image resizing on a fly
83
77
  test_files: []
data/samples/image_1.jpeg DELETED
Binary file
data/samples/image_2.jpeg DELETED
Binary file
data/samples/image_3.jpeg DELETED
Binary file
data/samples/image_4.jpeg DELETED
Binary file
data/samples/index.html DELETED
@@ -1,34 +0,0 @@
1
- <!DOCTYPE html>
2
-
3
- <p>
4
- height 150: <br>
5
- <img src="/cdn-cgi/image/height=150/samples/image_1.jpeg"/>
6
- <img src="/cdn-cgi/image/height=150/samples/image_2.jpeg"/>
7
- <img src="/cdn-cgi/image/height=150/samples/image_3.jpeg"/>
8
- <img src="/cdn-cgi/image/height=150/samples/image_4.jpeg"/>
9
- </p>
10
-
11
- <p>
12
- width 150: <br>
13
- <img src="/cdn-cgi/image/width=150/samples/image_1.jpeg"/>
14
- <img src="/cdn-cgi/image/width=150/samples/image_2.jpeg"/>
15
- <img src="/cdn-cgi/image/width=150/samples/image_3.jpeg"/>
16
- <img src="/cdn-cgi/image/width=150/samples/image_4.jpeg"/>
17
- </p>
18
-
19
- <p>
20
- width 50: <br>
21
- <img src="/cdn-cgi/image/width=50/samples/image_1.jpeg"/>
22
- <img src="/cdn-cgi/image/width=50/samples/image_2.jpeg"/>
23
- <img src="/cdn-cgi/image/width=50/samples/image_3.jpeg"/>
24
- <img src="/cdn-cgi/image/width=50/samples/image_4.jpeg"/>
25
- </p>
26
-
27
- <p>
28
- originals: <br>
29
- <img src="image_1.jpeg"/>
30
- <img src="image_2.jpeg"/>
31
- <img src="image_3.jpeg"/>
32
- <img src="image_4.jpeg"/>
33
- </p>
34
-
@@ -1,146 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'test_helper'
4
-
5
- class RackAppTest < Minitest::Test
6
- UPSTREAM = ->(env) { [200, { 'content-type' => 'text/plain' }, ['upstream']] }
7
-
8
- def setup
9
- @tmpdir = Dir.mktmpdir('rack_resize_test')
10
- File.write(File.join(@tmpdir, 'photo.jpg'), 'fake jpeg content')
11
-
12
- @app = RackResize::RackApp.new(
13
- UPSTREAM,
14
- assets_folder: @tmpdir,
15
- processor: :sips,
16
- save_resized: false
17
- )
18
-
19
- fake_processing = Object.new
20
- def fake_processing.process!(**) = StringIO.new('processed image data')
21
- @app.instance_variable_set(:@processing, fake_processing)
22
- end
23
-
24
- def teardown
25
- FileUtils.rm_rf(@tmpdir)
26
- end
27
-
28
- # --- error_resp ---
29
-
30
- def test_error_resp_default_status
31
- status, headers, body = @app.error_resp('oops')
32
- assert_equal 404, status
33
- assert_equal({}, headers)
34
- assert_equal ['oops'], body
35
- end
36
-
37
- def test_error_resp_custom_status
38
- status, _, body = @app.error_resp('bad input', http_code: 422)
39
- assert_equal 422, status
40
- assert_equal ['bad input'], body
41
- end
42
-
43
- # --- send_file ---
44
-
45
- def test_send_file_returns_200_with_headers
46
- asset_file = Pathname.new(File.join(@tmpdir, 'photo.jpg'))
47
- content = StringIO.new('hello')
48
- status, headers, _ = @app.send_file(asset_file:, file_content: content)
49
-
50
- assert_equal 200, status
51
- assert_equal 'image/jpeg', headers['content-type']
52
- assert_equal '5', headers['content-length']
53
- assert_equal 'inline', headers['content-disposition']
54
- assert_match(/\d+/, headers['cache-control'])
55
- end
56
-
57
- # --- pass-through for non-cdn-cgi paths ---
58
-
59
- def test_passes_through_unrelated_paths
60
- env = Rack::MockRequest.env_for('/foo/bar.jpg')
61
- status, _, body = @app.call(env)
62
- assert_equal 200, status
63
- assert_equal ['upstream'], body
64
- end
65
-
66
- def test_passes_through_root_path
67
- env = Rack::MockRequest.env_for('/')
68
- _, _, body = @app.call(env)
69
- assert_equal ['upstream'], body
70
- end
71
-
72
- # --- path parsing errors ---
73
-
74
- def test_returns_404_for_unparseable_path
75
- env = Rack::MockRequest.env_for('/cdn-cgi/image/bad-path-no-extension')
76
- status, _, body = @app.call(env)
77
- assert_equal 404, status
78
- assert_equal ["can't parse file path"], body
79
- end
80
-
81
- def test_returns_404_for_path_traversal
82
- env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/../etc/photo.jpg')
83
- status, _, body = @app.call(env)
84
- assert_equal 404, status
85
- assert_equal ['.. is not allowed in image path'], body
86
- end
87
-
88
- def test_returns_404_for_missing_file
89
- env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/missing.jpg')
90
- status, _, body = @app.call(env)
91
- assert_equal 404, status
92
- assert_equal ['file not exists on a server'], body
93
- end
94
-
95
- # --- successful processing ---
96
-
97
- def test_returns_200_for_valid_request
98
- env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/photo.jpg')
99
- status, headers, _ = @app.call(env)
100
- assert_equal 200, status
101
- assert_equal 'image/jpeg', headers['content-type']
102
- end
103
-
104
- def test_valid_request_with_multiple_params
105
- env = Rack::MockRequest.env_for('/cdn-cgi/image/width=200,format=auto,quality=80/assets/photo.jpg')
106
- status, = @app.call(env)
107
- assert_equal 200, status
108
- end
109
-
110
- def test_response_body_is_processed_content
111
- env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/photo.jpg')
112
- _, headers, body = @app.call(env)
113
- assert_equal 'processed image data'.bytesize.to_s, headers['content-length']
114
- end
115
-
116
- # --- fingerprinted filenames (Rails asset digest) ---
117
-
118
- def test_strips_digest_fingerprint_from_filename
119
- File.write(File.join(@tmpdir, 'photo.jpg'), 'fake jpeg content')
120
- env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/photo-1a2b3c4d.jpg')
121
- status, _, _ = @app.call(env)
122
- assert_equal 200, status
123
- end
124
-
125
- # --- custom cf_path_prefix ---
126
-
127
- def test_custom_cf_path_prefix_does_not_intercept_default_prefix
128
- app = RackResize::RackApp.new(
129
- UPSTREAM,
130
- assets_folder: @tmpdir,
131
- processor: :sips,
132
- save_resized: false,
133
- cf_path_prefix: '/img'
134
- )
135
- env = Rack::MockRequest.env_for('/cdn-cgi/image/width=100/assets/photo.jpg')
136
- _, _, body = app.call(env)
137
- assert_equal ['upstream'], body
138
- end
139
-
140
- # --- without upstream app ---
141
-
142
- def test_initializes_without_upstream_app
143
- app = RackResize::RackApp.new(assets_folder: @tmpdir, processor: :sips, save_resized: false)
144
- assert_nil app.instance_variable_get(:@app)
145
- end
146
- end
data/test/test_helper.rb DELETED
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'minitest/autorun'
4
- require 'rack/mock'
5
- require_relative '../lib/rack_resize'