image_vise 0.1.6 → 0.2.0
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 +4 -4
- data/.gitignore +8 -0
- data/.travis.yml +13 -0
- data/DEVELOPMENT.md +0 -11
- data/Gemfile +2 -20
- data/Rakefile +3 -26
- data/image_vise.gemspec +37 -132
- data/lib/image_vise/file_response.rb +2 -2
- data/lib/image_vise/image_request.rb +2 -0
- data/lib/image_vise/operators/background_fill.rb +18 -0
- data/lib/image_vise/operators/ellipse_stencil.rb +7 -5
- data/lib/image_vise/operators/force_jpg_out.rb +17 -0
- data/lib/image_vise/pipeline.rb +13 -3
- data/lib/image_vise/render_engine.rb +36 -64
- data/lib/image_vise/version.rb +3 -0
- data/lib/image_vise/writers/auto_writer.rb +23 -0
- data/lib/image_vise/writers/jpeg_writer.rb +9 -0
- data/lib/image_vise.rb +19 -19
- metadata +43 -135
- data/spec/image_vise/auto_orient_spec.rb +0 -10
- data/spec/image_vise/crop_spec.rb +0 -20
- data/spec/image_vise/ellipse_stencil_spec.rb +0 -19
- data/spec/image_vise/fetcher_file_spec.rb +0 -48
- data/spec/image_vise/fetcher_http_spec.rb +0 -44
- data/spec/image_vise/file_response_spec.rb +0 -45
- data/spec/image_vise/fit_crop_spec.rb +0 -20
- data/spec/image_vise/geom_spec.rb +0 -33
- data/spec/image_vise/image_request_spec.rb +0 -62
- data/spec/image_vise/pipeline_spec.rb +0 -72
- data/spec/image_vise/render_engine_spec.rb +0 -325
- data/spec/image_vise/sharpen_spec.rb +0 -17
- data/spec/image_vise/srgb_spec.rb +0 -28
- data/spec/image_vise/strip_metadata_spec.rb +0 -14
- data/spec/image_vise_spec.rb +0 -110
- data/spec/layers-with-blending.psd +0 -0
- data/spec/spec_helper.rb +0 -103
- data/spec/test_server.rb +0 -61
- data/spec/waterside_magic_hour.jpg +0 -0
- data/spec/waterside_magic_hour.psd +0 -0
- data/spec/waterside_magic_hour_adobergb.jpg +0 -0
- data/spec/waterside_magic_hour_gray.tif +0 -0
- data/spec/waterside_magic_hour_transp.png +0 -0
@@ -1,33 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe ImageVise::Geom do
|
4
|
-
it 'refuses invalid parameters' do
|
5
|
-
expect { described_class.new(geometry_string: nil) }.to raise_error(ArgumentError)
|
6
|
-
end
|
7
|
-
|
8
|
-
it 'fits to height' do
|
9
|
-
image = Magick::Image.read(test_image_path)[0]
|
10
|
-
crop = described_class.new(geometry_string: 'x200')
|
11
|
-
crop.apply!(image)
|
12
|
-
expect(image.rows).to eq(200)
|
13
|
-
examine_image(image, 'fit-height-200')
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'fits to width' do
|
17
|
-
image = Magick::Image.read(test_image_path)[0]
|
18
|
-
crop = described_class.new(geometry_string: '100x')
|
19
|
-
crop.apply!(image)
|
20
|
-
expect(image.columns).to eq(100)
|
21
|
-
examine_image(image, 'fit-width-100')
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'applies various geometry strings' do
|
25
|
-
%w( ^220x110 !20x20 !10x100 ).each do |geom_string|
|
26
|
-
image = Magick::Image.read(test_image_path)[0]
|
27
|
-
crop = described_class.new(geometry_string: geom_string)
|
28
|
-
|
29
|
-
crop.apply!(image)
|
30
|
-
examine_image(image, 'geom-%s' % geom_string)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe ImageVise::ImageRequest do
|
4
|
-
it 'accepts a set of params and secrets, and returns a Pipeline' do
|
5
|
-
img_params = {src_url: 'http://bucket.s3.aws.com/image.jpg', pipeline: [[:crop, {width: 10, height: 10, gravity: 's'}]]}
|
6
|
-
img_params_json = JSON.dump(img_params)
|
7
|
-
|
8
|
-
q = Base64.encode64(img_params_json)
|
9
|
-
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, 'this is a secret', q)
|
10
|
-
params = {q: q, sig: signature}
|
11
|
-
|
12
|
-
image_request = described_class.from_params(qs_params: params, secrets: ['this is a secret'])
|
13
|
-
request_qs_params = image_request.to_query_string_params('this is a secret')
|
14
|
-
expect(request_qs_params).to be_kind_of(Hash)
|
15
|
-
|
16
|
-
image_request_roundtrip = described_class.from_params(qs_params: request_qs_params, secrets: ['this is a secret'])
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'converts a file:// URL into a URI objectlist' do
|
20
|
-
img_params = {src_url: 'file:///etc/passwd', pipeline: [[:auto_orient, {}]]}
|
21
|
-
img_params_json = JSON.dump(img_params)
|
22
|
-
q = Base64.encode64(img_params_json)
|
23
|
-
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, 'this is a secret', q)
|
24
|
-
params = {q: q, sig: signature}
|
25
|
-
image_request = described_class.from_params(qs_params: params, secrets: ['this is a secret'])
|
26
|
-
expect(image_request.src_url).to be_kind_of(URI)
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'composes path parameters' do
|
30
|
-
parametrized = double(to_params: {foo: 'bar'})
|
31
|
-
uri = URI('http://example.com/image.psd')
|
32
|
-
image_request = described_class.new(src_url: uri, pipeline: parametrized)
|
33
|
-
path = image_request.to_path_params('password')
|
34
|
-
expect(path).to start_with('/eyJwaXB')
|
35
|
-
expect(path).to end_with('f207b')
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'never apppends "="-padding to the Base64-encoded "q"' do
|
39
|
-
parametrized = double(to_params: {foo: 'bar'})
|
40
|
-
(1..12).each do |num_chars_in_url|
|
41
|
-
uri = URI('http://ex.com/%s' % ('i' * num_chars_in_url))
|
42
|
-
image_request = described_class.new(src_url: uri, pipeline: parametrized)
|
43
|
-
q = image_request.to_query_string_params('password').fetch(:q)
|
44
|
-
expect(q).not_to include('=')
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe 'fails with an invalid signature' do
|
49
|
-
it 'when the sig is invalid' do
|
50
|
-
img_params = {src_url: 'http://bucket.s3.aws.com/image.jpg',
|
51
|
-
pipeline: [[:crop, {width: 10, height: 10, gravity: 's'}]]}
|
52
|
-
img_params_json = JSON.dump(img_params)
|
53
|
-
enc = Base64.encode64(img_params_json)
|
54
|
-
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, 'a', enc)
|
55
|
-
params = {q: enc, sig: signature}
|
56
|
-
|
57
|
-
expect {
|
58
|
-
described_class.from_params(qs_params: params, secrets: ['b'])
|
59
|
-
}.to raise_error(/Invalid or missing signature/)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe ImageVise::Pipeline do
|
4
|
-
it 'is empty by default' do
|
5
|
-
expect(subject).to be_empty
|
6
|
-
end
|
7
|
-
|
8
|
-
it 'reinstates the pipeline from the operator list parameters' do
|
9
|
-
params = [
|
10
|
-
["geom", {:geometry_string=>"10x10"}],
|
11
|
-
["crop", {:width=>5, :height=>5, :gravity=>"se"}],
|
12
|
-
["auto_orient", {}],
|
13
|
-
["fit_crop", {:width=>10, :height=>32, :gravity=>"c"}]
|
14
|
-
]
|
15
|
-
pipeline = described_class.from_param(params)
|
16
|
-
expect(pipeline).not_to be_empty
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'produces a usable operator parameter list that can be roundtripped' do
|
20
|
-
operator_list = subject.geom(geometry_string: '10x10').
|
21
|
-
crop(width: 5, height: 5, gravity: 'se').
|
22
|
-
auto_orient.
|
23
|
-
fit_crop(width: 10, height: 32, gravity: 'c').to_params
|
24
|
-
|
25
|
-
expect(operator_list).to eq([
|
26
|
-
["geom", {:geometry_string=>"10x10"}],
|
27
|
-
["crop", {:width=>5, :height=>5, :gravity=>"se"}],
|
28
|
-
["auto_orient", {}],
|
29
|
-
["fit_crop", {:width=>10, :height=>32, :gravity=>"c"}]
|
30
|
-
])
|
31
|
-
|
32
|
-
pipeline = described_class.from_param(operator_list)
|
33
|
-
expect(pipeline).not_to be_empty
|
34
|
-
end
|
35
|
-
|
36
|
-
it 'applies itself to the image' do
|
37
|
-
pipeline = subject.
|
38
|
-
auto_orient.
|
39
|
-
fit_crop(width: 48, height: 48, gravity: 'c').
|
40
|
-
srgb.
|
41
|
-
sharpen(radius: 2, sigma: 0.5).
|
42
|
-
ellipse_stencil.
|
43
|
-
strip_metadata
|
44
|
-
|
45
|
-
image = Magick::Image.read(test_image_path)[0]
|
46
|
-
pipeline.apply! image
|
47
|
-
examine_image(image, "stenciled")
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'raises an exception when an attempt is made to serialize an unknown operator' do
|
51
|
-
unknown_op_class = Class.new
|
52
|
-
subject << unknown_op_class.new
|
53
|
-
expect {
|
54
|
-
subject.to_params
|
55
|
-
}.to raise_error(/not registered/)
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'composes parameters even if one of the operators does not support to_h' do
|
59
|
-
class AnonOp
|
60
|
-
end
|
61
|
-
class ParametricOp
|
62
|
-
def to_h; {a: 133}; end
|
63
|
-
end
|
64
|
-
|
65
|
-
ImageVise.add_operator('t_anon', AnonOp)
|
66
|
-
ImageVise.add_operator('t_parametric', ParametricOp)
|
67
|
-
|
68
|
-
subject << AnonOp.new
|
69
|
-
subject << ParametricOp.new
|
70
|
-
expect(subject.to_params).to eq([["t_anon", {}], ["t_parametric", {:a=>133}]])
|
71
|
-
end
|
72
|
-
end
|
@@ -1,325 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
require 'rack/test'
|
3
|
-
|
4
|
-
describe ImageVise::RenderEngine do
|
5
|
-
include Rack::Test::Methods
|
6
|
-
|
7
|
-
let(:app) { ImageVise::RenderEngine.new }
|
8
|
-
|
9
|
-
context 'when the subclass is configured to raise exceptions' do
|
10
|
-
after :each do
|
11
|
-
ImageVise.reset_allowed_hosts!
|
12
|
-
ImageVise.reset_secret_keys!
|
13
|
-
end
|
14
|
-
|
15
|
-
it 'raises an exception instead of returning an error response' do
|
16
|
-
class << app
|
17
|
-
def raise_exceptions?
|
18
|
-
true
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
p = ImageVise::Pipeline.new.crop(width: 10, height: 10, gravity: 'c')
|
23
|
-
image_request = ImageVise::ImageRequest.new(src_url: 'http://unknown.com/image.jpg', pipeline: p)
|
24
|
-
expect(app).to receive(:handle_generic_error).and_call_original
|
25
|
-
expect {
|
26
|
-
get image_request.to_path_params('l33tness')
|
27
|
-
}.to raise_error(/No keys set/)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
context 'when requesting an image' do
|
32
|
-
before :each do
|
33
|
-
parsed_url = Addressable::URI.parse(public_url)
|
34
|
-
ImageVise.add_allowed_host!(parsed_url.host)
|
35
|
-
end
|
36
|
-
|
37
|
-
after :each do
|
38
|
-
ImageVise.reset_allowed_hosts!
|
39
|
-
ImageVise.reset_secret_keys!
|
40
|
-
end
|
41
|
-
|
42
|
-
it 'halts with 400 when the requested image cannot be opened by ImageMagick' do
|
43
|
-
uri = Addressable::URI.parse(public_url)
|
44
|
-
ImageVise.add_allowed_host!(uri.host)
|
45
|
-
ImageVise.add_secret_key!('l33tness')
|
46
|
-
uri.path = '/___nonexistent_image.jpg'
|
47
|
-
|
48
|
-
p = ImageVise::Pipeline.new.crop(width: 10, height: 10, gravity: 'c')
|
49
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
50
|
-
|
51
|
-
expect_any_instance_of(Patron::Session).to receive(:get_file) {|_self, url, path|
|
52
|
-
File.open(path, 'wb') {|f| f << 'totally not an image' }
|
53
|
-
double(status: 200)
|
54
|
-
}
|
55
|
-
expect(app).to receive(:handle_request_error).and_call_original
|
56
|
-
|
57
|
-
get image_request.to_path_params('l33tness')
|
58
|
-
expect(last_response.status).to eq(400)
|
59
|
-
expect(last_response['Cache-Control']).to match(/public/)
|
60
|
-
expect(last_response.body).to include('Unsupported/unknown')
|
61
|
-
end
|
62
|
-
|
63
|
-
it 'halts with 400 when a file:// URL is given and filesystem access is not enabled' do
|
64
|
-
uri = 'file://' + test_image_path
|
65
|
-
ImageVise.deny_filesystem_sources!
|
66
|
-
ImageVise.add_secret_key!('l33tness')
|
67
|
-
|
68
|
-
p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 10, gravity: 'c')
|
69
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
70
|
-
|
71
|
-
get image_request.to_path_params('l33tness')
|
72
|
-
expect(last_response.status).to eq(403)
|
73
|
-
expect(last_response.body).to include('filesystem access is disabled')
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'responds with 403 when upstream returns it, and includes the URL in the error message' do
|
77
|
-
uri = Addressable::URI.parse(public_url)
|
78
|
-
ImageVise.add_allowed_host!(uri.host)
|
79
|
-
ImageVise.add_secret_key!('l33tness')
|
80
|
-
uri.path = '/forbidden'
|
81
|
-
|
82
|
-
p = ImageVise::Pipeline.new.crop(width: 10, height: 10, gravity: 'c')
|
83
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
84
|
-
|
85
|
-
get image_request.to_path_params('l33tness')
|
86
|
-
expect(last_response.status).to eq(403)
|
87
|
-
expect(last_response.headers['Content-Type']).to eq('application/json')
|
88
|
-
parsed = JSON.load(last_response.body)
|
89
|
-
expect(parsed['errors'].to_s).to include("Unfortunate upstream response")
|
90
|
-
expect(parsed['errors'].to_s).to include(uri.to_s)
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'replays upstream error response codes that are selected to be replayed to the requester' do
|
94
|
-
uri = Addressable::URI.parse(public_url)
|
95
|
-
ImageVise.add_allowed_host!(uri.host)
|
96
|
-
ImageVise.add_secret_key!('l33tness')
|
97
|
-
|
98
|
-
[404, 403, 503, 504, 500].each do | error_code |
|
99
|
-
allow_any_instance_of(Patron::Session).to receive(:get_file).and_return(double(status: error_code))
|
100
|
-
|
101
|
-
p = ImageVise::Pipeline.new.crop(width: 10, height: 10, gravity: 'c')
|
102
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
103
|
-
|
104
|
-
get image_request.to_path_params('l33tness')
|
105
|
-
|
106
|
-
expect(last_response.status).to eq(error_code)
|
107
|
-
expect(last_response.headers).to have_key('Cache-Control')
|
108
|
-
expect(last_response.headers['Cache-Control']).to match(/public/)
|
109
|
-
|
110
|
-
expect(last_response.headers['Content-Type']).to eq('application/json')
|
111
|
-
parsed = JSON.load(last_response.body)
|
112
|
-
expect(parsed['errors'].to_s).to include("Unfortunate upstream response")
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
it 'sets very far caching headers and an ETag, and returns a 304 if any ETag is set' do
|
117
|
-
uri = Addressable::URI.parse(public_url)
|
118
|
-
ImageVise.add_allowed_host!(uri.host)
|
119
|
-
ImageVise.add_secret_key!('l33tness')
|
120
|
-
|
121
|
-
p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 35, gravity: 'c')
|
122
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
123
|
-
|
124
|
-
req_path = image_request.to_path_params('l33tness')
|
125
|
-
|
126
|
-
get req_path, {}
|
127
|
-
expect(last_response).to be_ok
|
128
|
-
expect(last_response['ETag']).not_to be_nil
|
129
|
-
expect(last_response['Cache-Control']).to match(/public/)
|
130
|
-
|
131
|
-
get req_path, {}, {'HTTP_IF_NONE_MATCH' => last_response['ETag']}
|
132
|
-
expect(last_response.status).to eq(304)
|
133
|
-
|
134
|
-
# Should consider _any_ ETag a request to rerender something
|
135
|
-
# that already exists in an upstream cache
|
136
|
-
get req_path, {}, {'HTTP_IF_NONE_MATCH' => SecureRandom.hex(4)}
|
137
|
-
expect(last_response.status).to eq(304)
|
138
|
-
end
|
139
|
-
|
140
|
-
it 'responds with an image that passes through all the processing steps' do
|
141
|
-
uri = Addressable::URI.parse(public_url)
|
142
|
-
ImageVise.add_allowed_host!(uri.host)
|
143
|
-
ImageVise.add_secret_key!('l33tness')
|
144
|
-
|
145
|
-
p = ImageVise::Pipeline.new.geom(geometry_string: '512x335').fit_crop(width: 10, height: 10, gravity: 'c')
|
146
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
147
|
-
|
148
|
-
get image_request.to_path_params('l33tness')
|
149
|
-
expect(last_response.status).to eq(200)
|
150
|
-
|
151
|
-
expect(last_response.headers['Content-Type']).to eq('image/jpeg')
|
152
|
-
expect(last_response.headers).to have_key('Content-Length')
|
153
|
-
parsed_image = Magick::Image.from_blob(last_response.body)[0]
|
154
|
-
expect(parsed_image.columns).to eq(10)
|
155
|
-
end
|
156
|
-
|
157
|
-
it 'properly decodes the image request if its Base64 representation contains masked slashes and plus characters' do
|
158
|
-
ImageVise.add_secret_key!("this is fab")
|
159
|
-
sig = '64759d9ea610d75d9138bfa3ea01595d343ca8994261ae06fca8e6490222f140'
|
160
|
-
q = 'eyJwaXBlbGluZSI6W1sic2hhcnBlbiIseyJyYWRpdXMiO' +
|
161
|
-
'jAuNSwic2lnbWEiOjAuNX1dXSwic3JjX3VybCI6InNoYWRl' +
|
162
|
-
'cmljb246L0NQR1BfRmlyZWJhbGw-Yz1kOWM4ZTMzO'+
|
163
|
-
'TZmNjMwYzM1MjM0MTYwMmM2YzJhYmQyZjAzNTcxMTF'+
|
164
|
-
'jIn0'
|
165
|
-
params = {q: q, sig: sig}
|
166
|
-
req = ImageVise::ImageRequest.from_params(qs_params: params, secrets: ['this is fab'])
|
167
|
-
|
168
|
-
# We do a check based on the raised exception - the request will fail
|
169
|
-
# at the fetcher lookup stage. That stage however takes place _after_ the
|
170
|
-
# signature has been validated, which means that the slash within the
|
171
|
-
# Base64 payload has been taken into account
|
172
|
-
expect(app).to receive(:raise_exceptions?).and_return(true)
|
173
|
-
expect {
|
174
|
-
get req.to_path_params('this is fab')
|
175
|
-
}.to raise_error(/No fetcher registered for shadericon/)
|
176
|
-
end
|
177
|
-
|
178
|
-
it 'calls all of the internal methods during execution' do
|
179
|
-
uri = Addressable::URI.parse(public_url)
|
180
|
-
ImageVise.add_allowed_host!(uri.host)
|
181
|
-
ImageVise.add_secret_key!('l33tness')
|
182
|
-
|
183
|
-
p = ImageVise::Pipeline.new.geom(geometry_string: '512x335').fit_crop(width: 10, height: 10, gravity: 'c')
|
184
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
185
|
-
params = image_request.to_query_string_params('l33tness')
|
186
|
-
|
187
|
-
expect(app).to receive(:parse_env_into_request).and_call_original
|
188
|
-
expect(app).to receive(:process_image_request).and_call_original
|
189
|
-
expect(app).to receive(:extract_params_from_request).and_call_original
|
190
|
-
expect(app).to receive(:image_rack_response).and_call_original
|
191
|
-
expect(app).to receive(:source_file_type_permitted?).and_call_original
|
192
|
-
expect(app).to receive(:output_file_type_permitted?).and_call_original
|
193
|
-
expect(app).to receive(:enable_forking?).and_call_original
|
194
|
-
|
195
|
-
get image_request.to_path_params('l33tness')
|
196
|
-
expect(last_response.status).to eq(200)
|
197
|
-
end
|
198
|
-
|
199
|
-
it 'picks the image from the filesystem if that is permitted' do
|
200
|
-
uri = 'file://' + test_image_path
|
201
|
-
ImageVise.allow_filesystem_source!(File.dirname(test_image_path) + '/*.*')
|
202
|
-
ImageVise.add_secret_key!('l33tness')
|
203
|
-
|
204
|
-
p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 10, gravity: 'c')
|
205
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
206
|
-
|
207
|
-
get image_request.to_path_params('l33tness')
|
208
|
-
expect(last_response.status).to eq(200)
|
209
|
-
expect(last_response.headers['Content-Type']).to eq('image/jpeg')
|
210
|
-
end
|
211
|
-
|
212
|
-
it 'URI-decodes the path in a file:// URL for a file with a Unicode path' do
|
213
|
-
utf8_file_path = File.dirname(test_image_path) + '/картинка.jpg'
|
214
|
-
FileUtils.cp_r(test_image_path, utf8_file_path)
|
215
|
-
uri = 'file://' + URI.encode(utf8_file_path)
|
216
|
-
|
217
|
-
ImageVise.allow_filesystem_source!(File.dirname(test_image_path) + '/*.*')
|
218
|
-
ImageVise.add_secret_key!('l33tness')
|
219
|
-
|
220
|
-
p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 10, gravity: 'c')
|
221
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
222
|
-
|
223
|
-
get image_request.to_path_params('l33tness')
|
224
|
-
File.unlink(utf8_file_path)
|
225
|
-
expect(last_response.status).to eq(200)
|
226
|
-
expect(last_response.headers['Content-Type']).to eq('image/jpeg')
|
227
|
-
end
|
228
|
-
|
229
|
-
it 'forbids a request with an extra GET param' do
|
230
|
-
uri = 'file://' + URI.encode(test_image_path)
|
231
|
-
|
232
|
-
p = ImageVise::Pipeline.new.fit_crop(width: 10, height: 10, gravity: 'c')
|
233
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
234
|
-
|
235
|
-
get image_request.to_path_params('l33tness'), {'extra' => '123'}
|
236
|
-
|
237
|
-
expect(last_response.status).to eq(400)
|
238
|
-
end
|
239
|
-
|
240
|
-
it 'returns the processed JPEG image as a PNG if it had to get an alpha channel during processing' do
|
241
|
-
uri = Addressable::URI.parse(public_url)
|
242
|
-
ImageVise.add_allowed_host!(uri.host)
|
243
|
-
ImageVise.add_secret_key!('l33tness')
|
244
|
-
|
245
|
-
p = ImageVise::Pipeline.new.geom(geometry_string: '220x220').ellipse_stencil
|
246
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
247
|
-
|
248
|
-
get image_request.to_path_params('l33tness')
|
249
|
-
expect(last_response.status).to eq(200)
|
250
|
-
|
251
|
-
expect(last_response.headers['Content-Type']).to eq('image/png')
|
252
|
-
expect(last_response.headers).to have_key('Content-Length')
|
253
|
-
|
254
|
-
examine_image_from_string(last_response.body)
|
255
|
-
end
|
256
|
-
|
257
|
-
it 'permits a PSD file by default' do
|
258
|
-
uri = Addressable::URI.parse(public_url_psd)
|
259
|
-
ImageVise.add_allowed_host!(uri.host)
|
260
|
-
ImageVise.add_secret_key!('l33tness')
|
261
|
-
|
262
|
-
p = ImageVise::Pipeline.new.geom(geometry_string: '220x220').ellipse_stencil
|
263
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
264
|
-
|
265
|
-
get image_request.to_path_params('l33tness')
|
266
|
-
expect(last_response.status).to eq(200)
|
267
|
-
end
|
268
|
-
|
269
|
-
it 'destroys all the loaded PSD layers' do
|
270
|
-
uri = Addressable::URI.parse(public_url_psd_multilayer)
|
271
|
-
ImageVise.add_allowed_host!(uri.host)
|
272
|
-
ImageVise.add_secret_key!('l33tness')
|
273
|
-
|
274
|
-
p = ImageVise::Pipeline.new.geom(geometry_string: '220x220')
|
275
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
276
|
-
|
277
|
-
class << app
|
278
|
-
def raise_exceptions?; true; end
|
279
|
-
end
|
280
|
-
|
281
|
-
# For each layer loaded into the ImageList
|
282
|
-
expect(ImageVise).to receive(:destroy).and_call_original.exactly(5).times
|
283
|
-
|
284
|
-
get image_request.to_path_params('l33tness')
|
285
|
-
|
286
|
-
expect(last_response.status).to eq(200)
|
287
|
-
end
|
288
|
-
|
289
|
-
it 'outputs a converted TIFF file as a PNG' do
|
290
|
-
uri = Addressable::URI.parse(public_url_tif)
|
291
|
-
ImageVise.add_allowed_host!(uri.host)
|
292
|
-
ImageVise.add_secret_key!('l33tness')
|
293
|
-
|
294
|
-
p = ImageVise::Pipeline.new.geom(geometry_string: '220x220')
|
295
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
296
|
-
|
297
|
-
class << app
|
298
|
-
def source_file_type_permitted?(type); true; end
|
299
|
-
end
|
300
|
-
|
301
|
-
get image_request.to_path_params('l33tness')
|
302
|
-
expect(last_response.status).to eq(200)
|
303
|
-
expect(last_response.headers['Content-Type']).to eq('image/png')
|
304
|
-
end
|
305
|
-
|
306
|
-
it 'outputs a converted TIFF file in the TIFF format if it is on the permitted list' do
|
307
|
-
uri = Addressable::URI.parse(public_url_tif)
|
308
|
-
ImageVise.add_allowed_host!(uri.host)
|
309
|
-
ImageVise.add_secret_key!('l33tness')
|
310
|
-
|
311
|
-
p = ImageVise::Pipeline.new.geom(geometry_string: '220x220')
|
312
|
-
image_request = ImageVise::ImageRequest.new(src_url: uri.to_s, pipeline: p)
|
313
|
-
|
314
|
-
|
315
|
-
class << app
|
316
|
-
def source_file_type_permitted?(type); true; end
|
317
|
-
def output_file_type_permitted?(type); true; end
|
318
|
-
end
|
319
|
-
|
320
|
-
get image_request.to_path_params('l33tness')
|
321
|
-
expect(last_response.status).to eq(200)
|
322
|
-
expect(last_response.headers['Content-Type']).to eq('image/tiff')
|
323
|
-
end
|
324
|
-
end
|
325
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe ImageVise::Sharpen do
|
4
|
-
it 'refuses invalid parameters' do
|
5
|
-
expect { described_class.new(sigma: 0, radius: -1) }.to raise_error(ArgumentError)
|
6
|
-
end
|
7
|
-
|
8
|
-
it 'applies the crop with different gravities' do
|
9
|
-
[[1, 1], [4, 2], [0.75, 0.5]].each do |(r, s)|
|
10
|
-
image = Magick::Image.read(test_image_path)[0]
|
11
|
-
expect(ImageVise).to receive(:destroy).with(instance_of(Magick::Image)).and_call_original
|
12
|
-
sharpen = described_class.new(radius: r, sigma: s)
|
13
|
-
sharpen.apply!(image)
|
14
|
-
examine_image(image, "sharpen-rad_%02f-sigma_%02f" % [r, s])
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe ImageVise::SRGB do
|
4
|
-
it 'applies the profile, creating a perceptible difference with the original' do
|
5
|
-
opset = ImageVise::Pipeline.new([
|
6
|
-
ImageVise::FitCrop.new(width: 512, height: 512, gravity: 'c'),
|
7
|
-
described_class.new,
|
8
|
-
])
|
9
|
-
|
10
|
-
# This test will function only if you have RMagick with LCMS2 support
|
11
|
-
# built-in. If you do, the two images will look _very_ much like one
|
12
|
-
# another.
|
13
|
-
#
|
14
|
-
# If you don't, the images will look remarkably different
|
15
|
-
# (the AdobeRGB version has color values that match AdobeRGB
|
16
|
-
# primaries, and will render diffrently in pretty much any
|
17
|
-
# viewer).
|
18
|
-
image = Magick::Image.read(test_image_adobergb_path).first
|
19
|
-
opset.apply!(image)
|
20
|
-
image.strip!
|
21
|
-
examine_image(image, "from-adobergb")
|
22
|
-
|
23
|
-
image = Magick::Image.read(test_image_path).first
|
24
|
-
opset.apply!(image)
|
25
|
-
image.strip!
|
26
|
-
examine_image(image, "from-srgb")
|
27
|
-
end
|
28
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe ImageVise::StripMetadata do
|
4
|
-
it 'applies the strip! method to the image' do
|
5
|
-
image = Magick::Image.read(test_image_path).first
|
6
|
-
expect(image).to receive(:strip!).and_call_original
|
7
|
-
described_class.new.apply!(image)
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'is registered with the operator registry' do
|
11
|
-
op = ImageVise.operator_from('strip_metadata')
|
12
|
-
expect(op).to eq(described_class)
|
13
|
-
end
|
14
|
-
end
|
data/spec/image_vise_spec.rb
DELETED
@@ -1,110 +0,0 @@
|
|
1
|
-
require_relative 'spec_helper'
|
2
|
-
require 'rack/test'
|
3
|
-
|
4
|
-
describe ImageVise do
|
5
|
-
include Rack::Test::Methods
|
6
|
-
|
7
|
-
def app
|
8
|
-
described_class.new
|
9
|
-
end
|
10
|
-
|
11
|
-
context 'ImageVise.allowed_hosts' do
|
12
|
-
it 'returns the allowed hosts and is empty by default' do
|
13
|
-
expect(described_class.allowed_hosts).to be_empty
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'allows add_allowed_host! and reset_allowed_hosts!' do
|
17
|
-
described_class.add_allowed_host!('www.imageboard.im')
|
18
|
-
expect(described_class.allowed_hosts).to include('www.imageboard.im')
|
19
|
-
described_class.reset_allowed_hosts!
|
20
|
-
expect(described_class.allowed_hosts).not_to include('www.imageboard.im')
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
context 'ImageVise.secret_keys' do
|
25
|
-
it 'raises when asked for a key and no keys has been set' do
|
26
|
-
expect {
|
27
|
-
described_class.secret_keys
|
28
|
-
}.to raise_error("No keys set, add a key using `ImageVise.add_secret_key!(key)'")
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'allows add_secret_key!(key) and reset_secret_keys!' do
|
32
|
-
described_class.add_secret_key!('l33t')
|
33
|
-
expect(described_class.secret_keys).to include('l33t')
|
34
|
-
described_class.reset_secret_keys!
|
35
|
-
expect {
|
36
|
-
expect(described_class.secret_keys)
|
37
|
-
}.to raise_error
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
describe 'ImageVise.new.call' do
|
42
|
-
it 'instantiates a new app and performs call() on it' do
|
43
|
-
expect_any_instance_of(ImageVise::RenderEngine).to receive(:call).with(:mock_env) { :yes }
|
44
|
-
ImageVise.new.call(:mock_env)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
describe 'ImageVise.call' do
|
49
|
-
it 'instantiates a new app and performs call() on it' do
|
50
|
-
expect_any_instance_of(ImageVise::RenderEngine).to receive(:call).with(:mock_env) { :yes }
|
51
|
-
ImageVise.call(:mock_env)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe '.image_params' do
|
56
|
-
it 'generates a Hash with paremeters for processing the resized image' do
|
57
|
-
params = ImageVise.image_params(src_url: 'http://host.com/image.jpg', secret: 'l33t') do |pipe|
|
58
|
-
pipe.fit_crop width: 128, height: 256, gravity: 'c'
|
59
|
-
end
|
60
|
-
expect(params).to be_kind_of(Hash)
|
61
|
-
expect(params[:q]).not_to be_empty
|
62
|
-
expect(params[:sig]).not_to be_empty
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
describe 'methods dealing with fetchers' do
|
67
|
-
it 'returns the fetchers for the default schemes' do
|
68
|
-
http = ImageVise.fetcher_for('http')
|
69
|
-
expect(http).to respond_to(:fetch_uri_to_tempfile)
|
70
|
-
file = ImageVise.fetcher_for('file')
|
71
|
-
expect(http).to respond_to(:fetch_uri_to_tempfile)
|
72
|
-
|
73
|
-
expect {
|
74
|
-
ImageVise.fetcher_for('undernet')
|
75
|
-
}.to raise_error(/No fetcher registered/)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
describe '.image_path' do
|
80
|
-
it 'returns the path to the image within the application' do
|
81
|
-
path = ImageVise.image_path(src_url: 'file://tmp/img.jpg', secret: 'a') do |p|
|
82
|
-
p.ellipse_stencil
|
83
|
-
end
|
84
|
-
expect(path).to start_with('/')
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
describe 'methods dealing with the operator list' do
|
89
|
-
it 'have the basic operators already set up' do
|
90
|
-
oplist = ImageVise.defined_operator_names
|
91
|
-
expect(oplist).to include('sharpen')
|
92
|
-
expect(oplist).to include('crop')
|
93
|
-
end
|
94
|
-
|
95
|
-
it 'allows an operator to be added and retrieved' do
|
96
|
-
class CustomOp; end
|
97
|
-
ImageVise.add_operator 'custom_op', CustomOp
|
98
|
-
expect(ImageVise.operator_from(:custom_op)).to eq(CustomOp)
|
99
|
-
expect(ImageVise.operator_name_for(CustomOp.new)).to eq('custom_op')
|
100
|
-
expect(ImageVise.defined_operator_names).to include('custom_op')
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'raises an exception when an operator key is requested that does not exist' do
|
104
|
-
class UnknownOp; end
|
105
|
-
expect {
|
106
|
-
ImageVise.operator_name_for(UnknownOp.new)
|
107
|
-
}.to raise_error(/not registered using ImageVise/)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
Binary file
|