resizing 0.8.3.pre → 1.0.1

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +61 -0
  3. data/Gemfile +4 -0
  4. data/lib/resizing/active_storage/service/resizing_service.rb +2 -0
  5. data/lib/resizing/active_storage/service.rb +9 -0
  6. data/lib/resizing/active_storage.rb +7 -0
  7. data/lib/resizing/carrier_wave/storage/file.rb +35 -27
  8. data/lib/resizing/carrier_wave/storage/remote.rb +5 -3
  9. data/lib/resizing/carrier_wave.rb +11 -0
  10. data/lib/resizing/client.rb +33 -29
  11. data/lib/resizing/configuration.rb +5 -1
  12. data/lib/resizing/version.rb +1 -1
  13. data/lib/resizing.rb +2 -0
  14. data/resizing.gemspec +8 -5
  15. data/test/resizing/active_storage_service_test.rb +98 -0
  16. data/test/resizing/carrier_wave/storage/file_test.rb +149 -8
  17. data/test/resizing/carrier_wave/storage/remote_test.rb +75 -0
  18. data/test/resizing/carrier_wave_test.rb +63 -28
  19. data/test/resizing/client_test.rb +30 -1
  20. data/test/resizing/configurable_test.rb +82 -0
  21. data/test/resizing/configuration_test.rb +117 -0
  22. data/test/resizing/constants_test.rb +25 -0
  23. data/test/resizing/error_test.rb +73 -0
  24. data/test/resizing/http_clientable_test.rb +84 -0
  25. data/test/resizing/mock_client_test.rb +75 -0
  26. data/test/resizing_module_test.rb +124 -0
  27. data/test/test_helper.rb +20 -0
  28. metadata +84 -36
  29. data/.circleci/config.yml +0 -71
  30. data/lib/resizing/video/client.rb +0 -116
  31. data/lib/resizing/video.rb +0 -8
  32. data/test/resizing/video/client_test.rb +0 -158
  33. data/test/vcr/video/metadata/success.yml +0 -47
  34. data/test/vcr/video/prepare/success.yml +0 -47
  35. data/test/vcr/video/upload_completed/success.yml +0 -47
  36. /data/{bin → exe}/console +0 -0
  37. /data/{bin → exe}/generate-changelog +0 -0
  38. /data/{bin → exe}/setup +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8351713d4b33f84d7bff196edaa7a842ba34cb157a56aa652067b003cc459b3d
4
- data.tar.gz: d40a7f4d588283bc1535ed5f31f94f592e86430a8fb7724554b9ec502067071a
3
+ metadata.gz: c8fa9990c253b1b932fb5b2c599aa93f045ad22dafbeef6e5b2d298f6fa1a59e
4
+ data.tar.gz: c58047443368aee638f48b0b4c899fcbd8a79b73c111655942ffa63ef73f9d74
5
5
  SHA512:
6
- metadata.gz: 2d015cf9ce5e436630618fa540523b3fe8449060844938eaf3d3ea5b455c1cede10a0993a80c2b90c967da2e3399e79837001ca4f315fee3694db0e657c8ebf0
7
- data.tar.gz: 4fdefce63e3f9cdc5c6629fdf769928a6ca030b2715398df4a0ddb1ac09ca1492dd25a414f2b8e7750d591e92b86fa66c2394433dbcd4ba69dfaa8d33a573600
6
+ metadata.gz: 0d287c87564b117378394d19d4c5e5172afa056b1865ba9f4e54e4d9811aa70a88e6e70b5e76b720c06c5f6731d040350d08f4170a336e9bffc325f815de8ea9
7
+ data.tar.gz: 5b4a247eb8747d23632c65c078a338a8db3dfc5db9cceffce67845941dbf36386ac981f93f90203b155afdee39e14b156d5adc7dba5766710464ffd96bf0eb20
@@ -0,0 +1,61 @@
1
+ name: test
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches:
7
+ - master
8
+
9
+ permissions:
10
+ id-token: write # This is required for requesting the JWT
11
+ contents: read # This is required for actions/checkout
12
+
13
+ jobs:
14
+ test:
15
+ name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }}
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ ruby: ['3.1']
20
+ rails: ['6.1', '7.0']
21
+
22
+ runs-on: ubuntu-22.04
23
+
24
+ env:
25
+ RAILS_VERSION: ${{ matrix.rails }}
26
+
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+
30
+ - uses: mirromutth/mysql-action@v1.1
31
+ with:
32
+ host port: 3306
33
+ container port: 3306
34
+ character set server: 'utf8mb4'
35
+ collation server: 'utf8mb4_general_ci'
36
+ mysql version: '5.7'
37
+ mysql database: 'resizing_gem_test'
38
+ mysql root password: secret
39
+
40
+ - name: "Install ruby dependencies"
41
+ run: |
42
+ sudo apt update
43
+ sudo apt install -y libyaml-dev curl libmysqlclient-dev
44
+
45
+ - uses: ruby/setup-ruby@v1
46
+ with:
47
+ ruby-version: ${{ matrix.ruby }}
48
+ bundler-cache: false
49
+
50
+ - name: bundle install
51
+ run: |
52
+ bundle install
53
+
54
+ - name: Run test
55
+ run: |
56
+ bundle exec rake test
57
+
58
+ - name: Upload coverage reports to Codecov
59
+ uses: codecov/codecov-action@v3
60
+ env:
61
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
data/Gemfile CHANGED
@@ -5,6 +5,10 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in resizing.gemspec
6
6
  gemspec
7
7
 
8
+ # Allow testing against different Rails versions
9
+ rails_version = ENV['RAILS_VERSION'] || '7.0'
10
+ gem 'rails', "~> #{rails_version}"
11
+
8
12
  gem 'rake', '~> 13.0'
9
13
  gem 'github_changelog_generator'
10
14
  gem 'mysql2'
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_storage/service'
4
+
3
5
  module Resizing
4
6
  module ActiveStorage
5
7
  module Service
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resizing
4
+ module ActiveStorage
5
+ module Service
6
+ autoload :ResizingService, 'resizing/active_storage/service/resizing_service'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Resizing
4
+ module ActiveStorage
5
+ autoload :Service, 'resizing/active_storage/service'
6
+ end
7
+ end
@@ -6,15 +6,16 @@ module Resizing
6
6
  class File
7
7
  include ::CarrierWave::Utilities::Uri
8
8
 
9
+ attr_reader :public_id
10
+
9
11
  def initialize(uploader, identifier = nil)
10
12
  @uploader = uploader
11
- @file = nil
12
13
  @content_type = nil
13
- @public_id = Resizing::PublicId.new identifier
14
+ @public_id = Resizing::PublicId.new(identifier)
15
+ @file = nil
16
+ @metadata = nil
14
17
  end
15
18
 
16
- attr_reader :public_id
17
-
18
19
  def attributes
19
20
  file.attributes
20
21
  end
@@ -28,9 +29,22 @@ module Resizing
28
29
  end
29
30
 
30
31
  def delete
31
- @public_id = Resizing::PublicId.new(model.send :read_attribute, serialization_column)
32
+ # Use the identifier from constructor if available, otherwise try to get from model
33
+ if @public_id.present?
34
+ # Already set from constructor or retrieve
35
+ elsif model.respond_to?(:attribute_was)
36
+ # Try to get the value before changes (for remove! scenario)
37
+ column_value = model.attribute_was(serialization_column) || model.send(:read_attribute,
38
+ serialization_column)
39
+ @public_id = Resizing::PublicId.new(column_value)
40
+ else
41
+ column_value = model.send(:read_attribute, serialization_column)
42
+ @public_id = Resizing::PublicId.new(column_value)
43
+ end
44
+
32
45
  return if @public_id.empty? # do nothing
33
46
 
47
+ # 以下、既存のコード(変更なし)
34
48
  resp = client.delete(@public_id.image_id)
35
49
  if resp['error'] == 'ActiveRecord::RecordNotFound' # 404 not found
36
50
  model.send :write_attribute, serialization_column, nil unless model.destroyed?
@@ -88,30 +102,29 @@ module Resizing
88
102
  raise NotImplementedError, 'new file is required duplicating'
89
103
  end
90
104
 
91
- if new_file.respond_to? :content_type
92
- @content_type ||= new_file.content_type
93
- else
94
- # guess content-type from extension
95
- @content_type ||= MIME::Types.type_for(new_file.path).first.content_type
96
- end
105
+ @content_type ||= if new_file.respond_to? :content_type
106
+ new_file.content_type
107
+ else
108
+ # guess content-type from extension
109
+ MIME::Types.type_for(new_file.path).first.content_type
110
+ end
97
111
 
98
112
  original_filename = new_file.try(:original_filename) || new_file.try(:filename) || new_file.try(:path)
99
- if original_filename.present?
100
- original_filename = ::File.basename(original_filename)
101
- end
113
+ original_filename = ::File.basename(original_filename) if original_filename.present?
102
114
 
103
115
  content = if new_file.respond_to?(:to_io)
104
116
  new_file.to_io.tap(&:rewind)
105
117
  elsif new_file.respond_to?(:read) && new_file.respond_to?(:rewind)
106
- new_file.read.tap do
107
- new_file.rewind
108
- end
118
+ # Pass the IO object itself, not the read result
119
+ new_file.rewind
120
+ new_file
109
121
  else
110
122
  new_file
111
123
  end
112
124
 
113
125
  @response = Resizing.post(content, { content_type: @content_type, filename: original_filename })
114
126
  @public_id = Resizing::PublicId.new(@response['public_id'])
127
+ @content_type = @response['content_type']
115
128
 
116
129
  # force update column
117
130
  # model_class
@@ -125,7 +138,7 @@ module Resizing
125
138
  end
126
139
 
127
140
  def name(options = {})
128
- @public_id = PublicId.new(model.send :read_attribute, serialization_column)
141
+ @public_id = PublicId.new(model.send(:read_attribute, serialization_column))
129
142
  CGI.unescape(@public_id.filename)
130
143
  end
131
144
 
@@ -133,6 +146,10 @@ module Resizing
133
146
  # CarrierWave::Storage::Fog::File.new(@uploader, @base, new_path)
134
147
  # end
135
148
 
149
+ def retrieve(identifier)
150
+ @public_id = Resizing::PublicId.new(identifier)
151
+ end
152
+
136
153
  private
137
154
 
138
155
  attr_reader :uploader
@@ -190,15 +207,6 @@ module Resizing
190
207
  parameters.count == 2 && parameters[1].include?(:options)
191
208
  end
192
209
 
193
- def retrieve!(identifier)
194
- raise NotImplementedError, "retrieve! #{identifier}"
195
- end
196
-
197
- # def cache!(new_file)
198
- # raise NotImplementedError,
199
- # "Need to implement #cache! if you want to use #{self.class.name} as a cache storage."
200
- # end
201
-
202
210
  # def retrieve_from_cache!(identifier)
203
211
  # raise NotImplementedError,
204
212
  # "Need to implement #retrieve_from_cache! if you want to use #{self.class.name} as a cache storage."
@@ -9,19 +9,21 @@ module Resizing
9
9
  def store!(file)
10
10
  f = Resizing::CarrierWave::Storage::File.new(uploader)
11
11
  f.store(file)
12
- @filename = f.public_id.to_s
13
12
  f
14
13
  end
15
14
 
16
15
  def remove!(file)
17
16
  f = Resizing::CarrierWave::Storage::File.new(uploader)
18
17
  f.delete(file)
19
- @filename = f.public_id.to_s
20
18
  f
21
19
  end
22
20
 
23
21
  def retrieve!(identifier)
24
- Resizing::CarrierWave::Storage::File.new(uploader, identifier)
22
+ return nil if identifier.blank?
23
+
24
+ f = Resizing::CarrierWave::Storage::File.new(uploader, identifier)
25
+ f.retrieve(identifier)
26
+ f
25
27
  end
26
28
 
27
29
  def cache!(new_file)
@@ -31,12 +31,23 @@ module Resizing
31
31
  super
32
32
  end
33
33
 
34
+ def file
35
+ file_identifier = identifier || read_column
36
+
37
+ return nil if file_identifier.blank?
38
+
39
+ @file ||= Resizing::CarrierWave::Storage::File.new(self)
40
+ @file.retrieve(file_identifier)
41
+ @file
42
+ end
43
+
34
44
  def url(*args)
35
45
  return default_url unless read_column.present?
36
46
 
37
47
  transforms = args.map do |version|
38
48
  version = version.intern
39
49
  raise "No version is found: #{version}, #{versions.keys} are exists." unless versions.has_key? version
50
+
40
51
  versions[version].transform_string
41
52
  end.compact
42
53
 
@@ -37,16 +37,14 @@ module Resizing
37
37
  raise NotImplementedError
38
38
  end
39
39
 
40
- def post(file_or_binary, options = {})
40
+ def post(filename_or_io, options = {})
41
41
  ensure_content_type(options)
42
+ ensure_filename_or_io(filename_or_io)
43
+ filename = gather_filename filename_or_io, options
42
44
 
43
45
  url = build_post_url
44
-
45
- filename = gather_filename file_or_binary, options
46
-
47
- body = to_io(file_or_binary)
48
46
  params = {
49
- image: Faraday::UploadIO.new(body, options[:content_type], filename)
47
+ image: Faraday::Multipart::FilePart.new(filename_or_io, options[:content_type], filename)
50
48
  }
51
49
 
52
50
  response = handle_faraday_error do
@@ -59,16 +57,14 @@ module Resizing
59
57
  result
60
58
  end
61
59
 
62
- def put(image_id, file_or_binary, options)
60
+ def put(image_id, filename_or_io, options)
63
61
  ensure_content_type(options)
62
+ ensure_filename_or_io(filename_or_io)
63
+ filename = gather_filename filename_or_io, options
64
64
 
65
65
  url = build_put_url(image_id)
66
-
67
- filename = gather_filename file_or_binary, options
68
-
69
- body = to_io(file_or_binary)
70
66
  params = {
71
- image: Faraday::UploadIO.new(body, options[:content_type], filename)
67
+ image: Faraday::Multipart::FilePart.new(filename_or_io, options[:content_type], filename)
72
68
  }
73
69
 
74
70
  response = handle_faraday_error do
@@ -103,7 +99,7 @@ module Resizing
103
99
  end
104
100
  end
105
101
 
106
- result = handle_metadata_response(response)
102
+ result = handle_metadata_response(response, options)
107
103
  result
108
104
  end
109
105
 
@@ -117,9 +113,9 @@ module Resizing
117
113
  "#{config.image_host}/projects/#{config.project_id}/upload/images/"
118
114
  end
119
115
 
120
- def gather_filename file_or_binary, options
116
+ def gather_filename(filename_or_io, options)
121
117
  filename = options[:filename]
122
- filename ||= file_or_binary.respond_to?(:path) ? File.basename(file_or_binary.path) : nil
118
+ filename ||= filename_or_io.respond_to?(:path) ? File.basename(filename_or_io.path) : nil
123
119
  end
124
120
 
125
121
  def build_put_url(image_id)
@@ -134,24 +130,27 @@ module Resizing
134
130
  "#{config.image_host}/projects/#{config.project_id}/upload/images/#{image_id}/metadata"
135
131
  end
136
132
 
133
+ def ensure_content_type(options)
134
+ raise ArgumentError, "need options[:content_type] for #{options.inspect}" unless options[:content_type]
135
+ end
136
+
137
+ def ensure_filename_or_io(filename_or_io)
138
+ return if filename_or_io.is_a?(File)
137
139
 
138
- def to_io(data)
139
- return data.to_io if data.respond_to? :to_io
140
+ # Accept IO-like objects (StringIO, Tempfile, etc.)
141
+ return if filename_or_io.respond_to?(:read) && filename_or_io.respond_to?(:rewind)
140
142
 
141
- case data
142
- when String
143
- StringIO.new(data)
144
- else
145
- raise ArgumentError, "file_or_binary is required IO class or String:#{data.class}"
143
+ if filename_or_io.is_a?(String)
144
+ if File.exist?(filename_or_io)
145
+ return
146
+ end
146
147
  end
147
- end
148
148
 
149
- def ensure_content_type(options)
150
- raise ArgumentError, "need options[:content_type] for #{options.inspect}" unless options[:content_type]
149
+ raise ArgumentError, "filename_or_io must be a File object, an IO-like object, or a path to a file (#{filename_or_io.class})"
151
150
  end
152
151
 
153
152
  def handle_create_response(response)
154
- raise APIError, "no response is returned" if response.nil?
153
+ raise APIError, "No response is returned" if response.nil?
155
154
 
156
155
  case response.status
157
156
  when HTTP_STATUS_OK, HTTP_STATUS_CREATED
@@ -162,7 +161,7 @@ module Resizing
162
161
  end
163
162
 
164
163
  def handle_delete_response(response)
165
- raise APIError, "no response is returned" if response.nil?
164
+ raise APIError, "No response is returned" if response.nil?
166
165
 
167
166
  case response.status
168
167
  when HTTP_STATUS_OK, HTTP_STATUS_NOT_FOUND
@@ -172,12 +171,17 @@ module Resizing
172
171
  end
173
172
  end
174
173
 
175
- def handle_metadata_response(response)
176
- raise APIError, "no response is returned" if response.nil?
174
+ def handle_metadata_response(response, options = {})
175
+ when_not_found = options[:when_not_found] || nil
176
+
177
+ raise APIError, "No response is returned" if response.nil?
177
178
 
178
179
  case response.status
179
180
  when HTTP_STATUS_OK, HTTP_STATUS_NOT_FOUND
180
181
  JSON.parse(response.body)
182
+ when HTTP_STATUS_NOT_FOUND
183
+ raise decode_error_from(response) if when_not_found == :raise
184
+ nil
181
185
  else
182
186
  raise decode_error_from(response)
183
187
  end
@@ -103,8 +103,12 @@ module Resizing
103
103
  end
104
104
 
105
105
  def initialize_by_hash(attr)
106
- # @host = attr[:host].dup.freeze || DEFAULT_HOST
107
106
  @image_host = attr[:image_host].dup.freeze || DEFAULT_IMAGE_HOST
107
+ if attr[:host].present?
108
+ Kernel.warn "[DEPRECATED] The host on configration is deprecated. Use image_host, video_host" if attr[:host].present?
109
+ @image_host ||= attr[:host].dup.freeze || DEFAULT_HOST # for backward compatible
110
+ end
111
+
108
112
  @video_host = attr[:video_host].dup.freeze || DEFAULT_VIDEO_HOST
109
113
  @project_id = attr[:project_id].dup.freeze
110
114
  @secret_token = attr[:secret_token].dup.freeze
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Resizing
4
- VERSION = '0.8.3.pre'
4
+ VERSION = '1.0.1'
5
5
  end
data/lib/resizing.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'resizing/version'
4
4
  require 'faraday'
5
+ require 'faraday/multipart'
5
6
  require 'json'
6
7
 
7
8
  module Resizing
@@ -14,6 +15,7 @@ module Resizing
14
15
  autoload :CarrierWave, 'resizing/carrier_wave'
15
16
  autoload :PublicId, 'resizing/public_id'
16
17
  autoload :Video, 'resizing/video'
18
+ autoload :ActiveStorage, 'resizing/active_storage'
17
19
 
18
20
  class Error < StandardError; end
19
21
  class ConfigurationError < Error; end
data/resizing.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Client and utilities for Resizing '
13
13
  spec.homepage = 'https://github.com/jksy/resizing-gem'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
16
16
 
17
17
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
18
18
 
@@ -28,13 +28,16 @@ Gem::Specification.new do |spec|
28
28
  spec.bindir = 'exe'
29
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ['lib']
31
- spec.add_runtime_dependency 'faraday', '>= 1.0.1', '< 1.4.0'
32
- spec.add_development_dependency 'carrierwave', '~> 2.2.0'
31
+ spec.add_runtime_dependency 'faraday', '~> 1.0'
32
+ spec.add_development_dependency 'rails', ">= 6.0", '< 7.1'
33
+ spec.add_development_dependency 'carrierwave', '~> 1.3.2'
33
34
  spec.add_development_dependency 'fog-aws'
34
- spec.add_development_dependency 'minitest'
35
+ spec.add_development_dependency 'minitest', '~> 5.16'
35
36
  spec.add_development_dependency 'minitest-ci'
36
- spec.add_development_dependency 'rails', '~> 5.2.3'
37
37
  spec.add_development_dependency 'rubocop'
38
38
  spec.add_development_dependency 'timecop'
39
39
  spec.add_development_dependency 'vcr'
40
+ spec.add_development_dependency 'mysql2'
41
+ spec.add_development_dependency 'simplecov'
42
+ spec.add_development_dependency 'simplecov-cobertura'
40
43
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ module Resizing
6
+ module ActiveStorage
7
+ module Service
8
+ class ResizingServiceTest < Minitest::Test
9
+ def setup
10
+ @service = Resizing::ActiveStorage::Service::ResizingService.new
11
+ end
12
+
13
+ def teardown
14
+ # NOP
15
+ end
16
+
17
+ def test_service_initialization
18
+ assert_instance_of Resizing::ActiveStorage::Service::ResizingService, @service
19
+ end
20
+
21
+ def test_upload_raises_not_implemented_error
22
+ assert_raises NotImplementedError do
23
+ @service.upload('test_key', StringIO.new('test data'))
24
+ end
25
+ end
26
+
27
+ def test_download_raises_not_implemented_error
28
+ assert_raises NotImplementedError do
29
+ @service.download('test_key')
30
+ end
31
+ end
32
+
33
+ def test_download_chunk_raises_not_implemented_error
34
+ assert_raises NotImplementedError do
35
+ @service.download_chunk('test_key', 0..99)
36
+ end
37
+ end
38
+
39
+ def test_delete_raises_not_implemented_error
40
+ assert_raises NotImplementedError do
41
+ @service.delete('test_key')
42
+ end
43
+ end
44
+
45
+ def test_exist_raises_not_implemented_error
46
+ assert_raises NotImplementedError do
47
+ @service.exist?('test_key')
48
+ end
49
+ end
50
+
51
+ def test_url_for_direct_upload_raises_not_implemented_error
52
+ assert_raises NotImplementedError do
53
+ @service.url_for_direct_upload(
54
+ 'test_key',
55
+ expires_in: 3600,
56
+ content_type: 'image/png',
57
+ conteont_length: 1024,
58
+ checksum: 'test_checksum'
59
+ )
60
+ end
61
+ end
62
+
63
+ def test_headers_for_direct_upload_raises_not_implemented_error
64
+ assert_raises NotImplementedError do
65
+ @service.headers_for_direct_upload(
66
+ 'test_key',
67
+ content_type: 'image/png',
68
+ checksum: 'test_checksum'
69
+ )
70
+ end
71
+ end
72
+
73
+ def test_private_url_raises_not_implemented_error
74
+ assert_raises NotImplementedError do
75
+ @service.send(
76
+ :private_url,
77
+ 'test_key',
78
+ expires_in: 3600,
79
+ filename: 'test.png',
80
+ content_type: 'image/png',
81
+ disposition: :attachment
82
+ )
83
+ end
84
+ end
85
+
86
+ def test_public_url_raises_not_implemented_error
87
+ assert_raises NotImplementedError do
88
+ @service.send(
89
+ :public_url,
90
+ 'test_key',
91
+ filename: 'test.png'
92
+ )
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end