httpimagestore 0.5.0 → 1.0.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.
- data/Gemfile +10 -12
- data/Gemfile.lock +57 -55
- data/README.md +829 -0
- data/VERSION +1 -1
- data/bin/httpimagestore +114 -180
- data/features/cache-control.feature +26 -90
- data/features/compatibility.feature +129 -0
- data/features/error-reporting.feature +207 -0
- data/features/health-check.feature +30 -0
- data/features/s3-store-and-thumbnail.feature +65 -0
- data/features/step_definitions/httpimagestore_steps.rb +66 -26
- data/features/support/env.rb +32 -5
- data/features/support/test.empty +0 -0
- data/httpimagestore.gemspec +60 -47
- data/lib/httpimagestore/aws_sdk_regions_hack.rb +23 -0
- data/lib/httpimagestore/configuration/file.rb +120 -0
- data/lib/httpimagestore/configuration/handler.rb +239 -0
- data/lib/httpimagestore/configuration/output.rb +119 -0
- data/lib/httpimagestore/configuration/path.rb +77 -0
- data/lib/httpimagestore/configuration/s3.rb +194 -0
- data/lib/httpimagestore/configuration/thumbnailer.rb +244 -0
- data/lib/httpimagestore/configuration.rb +126 -29
- data/lib/httpimagestore/error_reporter.rb +36 -0
- data/lib/httpimagestore/ruby_string_template.rb +26 -0
- data/load_test/load_test.1k.23a022f6e.m1.small-comp.csv +3 -0
- data/load_test/load_test.1k.ec9bde794.m1.small.csv +4 -0
- data/load_test/load_test.jmx +344 -0
- data/load_test/thumbnail_specs.csv +11 -0
- data/spec/configuration_file_spec.rb +309 -0
- data/spec/configuration_handler_spec.rb +124 -0
- data/spec/configuration_output_spec.rb +338 -0
- data/spec/configuration_path_spec.rb +92 -0
- data/spec/configuration_s3_spec.rb +571 -0
- data/spec/configuration_spec.rb +80 -105
- data/spec/configuration_thumbnailer_spec.rb +417 -0
- data/spec/ruby_string_template_spec.rb +43 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/support/compute.jpg +0 -0
- data/spec/support/cuba_response_env.rb +40 -0
- data/spec/support/full.cfg +49 -0
- metadata +138 -84
- data/README.rdoc +0 -23
- data/features/httpimagestore.feature +0 -167
- data/lib/httpimagestore/image_path.rb +0 -54
- data/lib/httpimagestore/s3_service.rb +0 -37
- data/lib/httpimagestore/thumbnail_class.rb +0 -13
- data/spec/image_path_spec.rb +0 -72
- data/spec/test.cfg +0 -8
@@ -0,0 +1,338 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require_relative 'support/cuba_response_env'
|
3
|
+
|
4
|
+
require 'httpimagestore/configuration'
|
5
|
+
Configuration::Scope.logger = Logger.new('/dev/null')
|
6
|
+
|
7
|
+
require 'httpimagestore/configuration/output'
|
8
|
+
require 'httpimagestore/configuration/file'
|
9
|
+
MemoryLimit.logger = Logger.new('/dev/null')
|
10
|
+
|
11
|
+
describe Configuration do
|
12
|
+
let :state do
|
13
|
+
Configuration::RequestState.new('abc')
|
14
|
+
end
|
15
|
+
|
16
|
+
let :env do
|
17
|
+
CubaResponseEnv.new
|
18
|
+
end
|
19
|
+
|
20
|
+
describe Configuration::OutputImage do
|
21
|
+
subject do
|
22
|
+
Configuration.read(<<-EOF)
|
23
|
+
put "test" {
|
24
|
+
output_image "input"
|
25
|
+
}
|
26
|
+
EOF
|
27
|
+
end
|
28
|
+
|
29
|
+
before :each do
|
30
|
+
subject.handlers[0].image_sources[0].realize(state)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should provide given image' do
|
34
|
+
subject.handlers[0].output.should be_a Configuration::OutputImage
|
35
|
+
subject.handlers[0].output.realize(state)
|
36
|
+
|
37
|
+
env.instance_eval &state.output_callback
|
38
|
+
env.res.status.should == 200
|
39
|
+
env.res.data.should == 'abc'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should use default content type if not defined on image' do
|
43
|
+
subject.handlers[0].output.realize(state)
|
44
|
+
|
45
|
+
env.instance_eval &state.output_callback
|
46
|
+
env.res['Content-Type'].should == 'application/octet-stream'
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should use image mime type if available' do
|
50
|
+
state.images['input'].mime_type = 'image/jpeg'
|
51
|
+
|
52
|
+
subject.handlers[0].output.realize(state)
|
53
|
+
|
54
|
+
env.instance_eval &state.output_callback
|
55
|
+
env.res['Content-Type'].should == 'image/jpeg'
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'Cache-Control header support' do
|
59
|
+
subject do
|
60
|
+
Configuration.read(<<-EOF)
|
61
|
+
put "test" {
|
62
|
+
output_image "input" cache-control="public, max-age=999, s-maxage=666"
|
63
|
+
}
|
64
|
+
EOF
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should allow setting Cache-Control header' do
|
68
|
+
subject.handlers[0].output.realize(state)
|
69
|
+
|
70
|
+
env.instance_eval &state.output_callback
|
71
|
+
env.res['Cache-Control'].should == 'public, max-age=999, s-maxage=666'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'output store paths and URLs' do
|
77
|
+
let :in_file do
|
78
|
+
Pathname.new("/tmp/test.in")
|
79
|
+
end
|
80
|
+
|
81
|
+
let :out_file do
|
82
|
+
Pathname.new("/tmp/test.out")
|
83
|
+
end
|
84
|
+
|
85
|
+
let :out2_file do
|
86
|
+
Pathname.new("/tmp/test.out2")
|
87
|
+
end
|
88
|
+
|
89
|
+
before :each do
|
90
|
+
in_file.open('w'){|io| io.write('abc')}
|
91
|
+
out_file.unlink if out_file.exist?
|
92
|
+
out2_file.unlink if out2_file.exist?
|
93
|
+
end
|
94
|
+
|
95
|
+
after :each do
|
96
|
+
out_file.unlink if out_file.exist?
|
97
|
+
out2_file.unlink if out2_file.exist?
|
98
|
+
in_file.unlink
|
99
|
+
end
|
100
|
+
|
101
|
+
describe Configuration::OutputStorePath do
|
102
|
+
it 'should provide file store path' do
|
103
|
+
subject = Configuration.read(<<-EOF)
|
104
|
+
path {
|
105
|
+
"out" "test.out"
|
106
|
+
}
|
107
|
+
|
108
|
+
post "single" {
|
109
|
+
store_file "input" root="/tmp" path="out"
|
110
|
+
|
111
|
+
output_store_path "input"
|
112
|
+
}
|
113
|
+
EOF
|
114
|
+
|
115
|
+
subject.handlers[0].image_sources[0].realize(state)
|
116
|
+
subject.handlers[0].stores[0].realize(state)
|
117
|
+
subject.handlers[0].output.realize(state)
|
118
|
+
|
119
|
+
env.instance_eval &state.output_callback
|
120
|
+
env.res['Content-Type'].should == 'text/plain'
|
121
|
+
env.res.data.should == "test.out\r\n"
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should provide multiple file store paths' do
|
125
|
+
subject = Configuration.read(<<-EOF)
|
126
|
+
path {
|
127
|
+
"in" "test.in"
|
128
|
+
"out" "test.out"
|
129
|
+
"out2" "test.out2"
|
130
|
+
}
|
131
|
+
|
132
|
+
post "multi" {
|
133
|
+
source_file "original" root="/tmp" path="in"
|
134
|
+
|
135
|
+
store_file "input" root="/tmp" path="out"
|
136
|
+
store_file "original" root="/tmp" path="out2"
|
137
|
+
|
138
|
+
output_store_path {
|
139
|
+
"input"
|
140
|
+
"original"
|
141
|
+
}
|
142
|
+
}
|
143
|
+
EOF
|
144
|
+
|
145
|
+
subject.handlers[0].image_sources[0].realize(state)
|
146
|
+
subject.handlers[0].image_sources[1].realize(state)
|
147
|
+
subject.handlers[0].stores[0].realize(state)
|
148
|
+
subject.handlers[0].stores[1].realize(state)
|
149
|
+
subject.handlers[0].output.realize(state)
|
150
|
+
|
151
|
+
env.instance_eval &state.output_callback
|
152
|
+
env.res['Content-Type'].should == 'text/plain'
|
153
|
+
env.res.data.should == "test.out\r\ntest.out2\r\n"
|
154
|
+
end
|
155
|
+
|
156
|
+
describe 'conditional inclusion support' do
|
157
|
+
let :state do
|
158
|
+
Configuration::RequestState.new('abc', list: 'input,image2')
|
159
|
+
end
|
160
|
+
|
161
|
+
subject do
|
162
|
+
Configuration.read(<<-'EOF')
|
163
|
+
path {
|
164
|
+
"in" "test.in"
|
165
|
+
"out1" "test.out1"
|
166
|
+
"out2" "test.out2"
|
167
|
+
"out3" "test.out3"
|
168
|
+
}
|
169
|
+
|
170
|
+
post "multi" {
|
171
|
+
source_file "image1" root="/tmp" path="in"
|
172
|
+
source_file "image2" root="/tmp" path="in"
|
173
|
+
|
174
|
+
store_file "input" root="/tmp" path="out1"
|
175
|
+
store_file "image1" root="/tmp" path="out2"
|
176
|
+
store_file "image2" root="/tmp" path="out3"
|
177
|
+
|
178
|
+
output_store_path {
|
179
|
+
"input" if-image-name-on="#{list}"
|
180
|
+
"image1" if-image-name-on="#{list}"
|
181
|
+
"image2" if-image-name-on="#{list}"
|
182
|
+
}
|
183
|
+
}
|
184
|
+
EOF
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'should output store path only for images that names match if-image-name-on list' do
|
188
|
+
subject.handlers[0].image_sources[0].realize(state)
|
189
|
+
subject.handlers[0].image_sources[1].realize(state)
|
190
|
+
subject.handlers[0].image_sources[2].realize(state)
|
191
|
+
subject.handlers[0].stores[0].realize(state)
|
192
|
+
subject.handlers[0].stores[1].realize(state)
|
193
|
+
subject.handlers[0].stores[2].realize(state)
|
194
|
+
subject.handlers[0].output.realize(state)
|
195
|
+
|
196
|
+
env.instance_eval &state.output_callback
|
197
|
+
env.res['Content-Type'].should == 'text/plain'
|
198
|
+
env.res.data.should == "test.out1\r\ntest.out3\r\n"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe 'error handling' do
|
203
|
+
it 'should raise StorePathNotSetForImage for output of not stored image' do
|
204
|
+
subject = Configuration.read(<<-EOF)
|
205
|
+
post "single" {
|
206
|
+
output_store_path "input"
|
207
|
+
}
|
208
|
+
EOF
|
209
|
+
|
210
|
+
subject.handlers[0].image_sources[0].realize(state)
|
211
|
+
|
212
|
+
expect {
|
213
|
+
subject.handlers[0].output.realize(state)
|
214
|
+
}.to raise_error Configuration::StorePathNotSetForImage, %{store path not set for image 'input'}
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe Configuration::OutputStoreURL do
|
220
|
+
it 'should provide file store URL' do
|
221
|
+
subject = Configuration.read(<<-EOF)
|
222
|
+
path {
|
223
|
+
"out" "test.out"
|
224
|
+
}
|
225
|
+
|
226
|
+
post "single" {
|
227
|
+
store_file "input" root="/tmp" path="out"
|
228
|
+
|
229
|
+
output_store_url "input"
|
230
|
+
}
|
231
|
+
EOF
|
232
|
+
|
233
|
+
subject.handlers[0].image_sources[0].realize(state)
|
234
|
+
subject.handlers[0].stores[0].realize(state)
|
235
|
+
subject.handlers[0].output.realize(state)
|
236
|
+
|
237
|
+
env.instance_eval &state.output_callback
|
238
|
+
env.res['Content-Type'].should == 'text/uri-list'
|
239
|
+
env.res.data.should == "file://test.out\r\n"
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'should provide multiple file store URLs' do
|
243
|
+
subject = Configuration.read(<<-EOF)
|
244
|
+
path {
|
245
|
+
"in" "test.in"
|
246
|
+
"out" "test.out"
|
247
|
+
"out2" "test.out2"
|
248
|
+
}
|
249
|
+
|
250
|
+
post "multi" {
|
251
|
+
source_file "original" root="/tmp" path="in"
|
252
|
+
|
253
|
+
store_file "input" root="/tmp" path="out"
|
254
|
+
store_file "original" root="/tmp" path="out2"
|
255
|
+
|
256
|
+
output_store_url {
|
257
|
+
"input"
|
258
|
+
"original"
|
259
|
+
}
|
260
|
+
}
|
261
|
+
EOF
|
262
|
+
|
263
|
+
subject.handlers[0].image_sources[0].realize(state)
|
264
|
+
subject.handlers[0].image_sources[1].realize(state)
|
265
|
+
subject.handlers[0].stores[0].realize(state)
|
266
|
+
subject.handlers[0].stores[1].realize(state)
|
267
|
+
subject.handlers[0].output.realize(state)
|
268
|
+
|
269
|
+
env.instance_eval &state.output_callback
|
270
|
+
env.res['Content-Type'].should == 'text/uri-list'
|
271
|
+
env.res.data.should == "file://test.out\r\nfile://test.out2\r\n"
|
272
|
+
end
|
273
|
+
|
274
|
+
describe 'conditional inclusion support' do
|
275
|
+
let :state do
|
276
|
+
Configuration::RequestState.new('abc', list: 'input,image2')
|
277
|
+
end
|
278
|
+
|
279
|
+
subject do
|
280
|
+
Configuration.read(<<-'EOF')
|
281
|
+
path {
|
282
|
+
"in" "test.in"
|
283
|
+
"out1" "test.out1"
|
284
|
+
"out2" "test.out2"
|
285
|
+
"out3" "test.out3"
|
286
|
+
}
|
287
|
+
|
288
|
+
post "multi" {
|
289
|
+
source_file "image1" root="/tmp" path="in"
|
290
|
+
source_file "image2" root="/tmp" path="in"
|
291
|
+
|
292
|
+
store_file "input" root="/tmp" path="out1"
|
293
|
+
store_file "image1" root="/tmp" path="out2"
|
294
|
+
store_file "image2" root="/tmp" path="out3"
|
295
|
+
|
296
|
+
output_store_url {
|
297
|
+
"input" if-image-name-on="#{list}"
|
298
|
+
"image1" if-image-name-on="#{list}"
|
299
|
+
"image2" if-image-name-on="#{list}"
|
300
|
+
}
|
301
|
+
}
|
302
|
+
EOF
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'should output store url only for images that names match if-image-name-on list' do
|
306
|
+
subject.handlers[0].image_sources[0].realize(state)
|
307
|
+
subject.handlers[0].image_sources[1].realize(state)
|
308
|
+
subject.handlers[0].image_sources[2].realize(state)
|
309
|
+
subject.handlers[0].stores[0].realize(state)
|
310
|
+
subject.handlers[0].stores[1].realize(state)
|
311
|
+
subject.handlers[0].stores[2].realize(state)
|
312
|
+
subject.handlers[0].output.realize(state)
|
313
|
+
|
314
|
+
env.instance_eval &state.output_callback
|
315
|
+
env.res['Content-Type'].should == 'text/uri-list'
|
316
|
+
env.res.data.should == "file://test.out1\r\nfile://test.out3\r\n"
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
describe 'error handling' do
|
321
|
+
it 'should raise StoreURLNotSetForImage for output of not stored image' do
|
322
|
+
subject = Configuration.read(<<-EOF)
|
323
|
+
post "single" {
|
324
|
+
output_store_url "input"
|
325
|
+
}
|
326
|
+
EOF
|
327
|
+
|
328
|
+
subject.handlers[0].image_sources[0].realize(state)
|
329
|
+
|
330
|
+
expect {
|
331
|
+
subject.handlers[0].output.realize(state)
|
332
|
+
}.to raise_error Configuration::StoreURLNotSetForImage, %{store URL not set for image 'input'}
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'httpimagestore/configuration'
|
3
|
+
Configuration::Scope.logger = Logger.new('/dev/null')
|
4
|
+
|
5
|
+
require 'httpimagestore/configuration/path'
|
6
|
+
|
7
|
+
describe Configuration do
|
8
|
+
describe 'path rendering' do
|
9
|
+
it 'should load paths form single line and multi line declarations and render spec templates' do
|
10
|
+
subject = Configuration.read(<<-'EOF')
|
11
|
+
path "uri" "#{path}"
|
12
|
+
path "hash" "#{digest}.#{extension}"
|
13
|
+
path {
|
14
|
+
"hash-name" "#{digest}/#{imagename}.#{extension}"
|
15
|
+
"structured" "#{dirname}/#{digest}/#{basename}.#{extension}"
|
16
|
+
"structured-name" "#{dirname}/#{digest}/#{basename}-#{imagename}.#{extension}"
|
17
|
+
}
|
18
|
+
EOF
|
19
|
+
|
20
|
+
subject.paths['uri'].render(path: 'test/abc.jpg').should == 'test/abc.jpg'
|
21
|
+
subject.paths['hash'].render(path: 'test/abc.jpg', body: 'hello').should == '2cf24dba5fb0a30e.jpg'
|
22
|
+
subject.paths['hash-name'].render(path: 'test/abc.jpg', body: 'hello', imagename: 'xbrna').should == '2cf24dba5fb0a30e/xbrna.jpg'
|
23
|
+
subject.paths['structured'].render(path: 'test/abc.jpg', body: 'hello').should == 'test/2cf24dba5fb0a30e/abc.jpg'
|
24
|
+
subject.paths['structured-name'].render(path: 'test/abc.jpg', body: 'hello', imagename: 'xbrna').should == 'test/2cf24dba5fb0a30e/abc-xbrna.jpg'
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'error handling' do
|
28
|
+
it 'should raise NoValueError on missing path name' do
|
29
|
+
expect {
|
30
|
+
Configuration.read(<<-'EOF')
|
31
|
+
path
|
32
|
+
EOF
|
33
|
+
}.to raise_error Configuration::NoValueError, %{syntax error while parsing 'path': expected path name}
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should raise NoValueError on missing path template' do
|
37
|
+
expect {
|
38
|
+
Configuration.read(<<-'EOF')
|
39
|
+
path {
|
40
|
+
"blah"
|
41
|
+
}
|
42
|
+
EOF
|
43
|
+
}.to raise_error Configuration::NoValueError, %{syntax error while parsing '"blah"': expected path template}
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should raise PathNotDefinedError if path lookup fails' do
|
47
|
+
subject = Configuration.read('')
|
48
|
+
|
49
|
+
expect {
|
50
|
+
subject.paths['blah']
|
51
|
+
}.to raise_error Configuration::PathNotDefinedError, "path 'blah' not defined"
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should raise NoValueForPathTemplatePlaceholerError if locals value is not found' do
|
55
|
+
subject = Configuration.read(<<-'EOF')
|
56
|
+
path {
|
57
|
+
"test" "#{abc}#{xyz}"
|
58
|
+
}
|
59
|
+
EOF
|
60
|
+
|
61
|
+
expect {
|
62
|
+
subject.paths['test'].render
|
63
|
+
}.to raise_error Configuration::NoValueForPathTemplatePlaceholerError, %q{cannot generate path 'test' from template '#{abc}#{xyz}': no value for '#{abc}'}
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should raise NoValueForPathTemplatePlaceholerError if path value is not found' do
|
67
|
+
subject = Configuration.read(<<-'EOF')
|
68
|
+
path {
|
69
|
+
"test" "#{dirname}#{basename}"
|
70
|
+
}
|
71
|
+
EOF
|
72
|
+
|
73
|
+
expect {
|
74
|
+
subject.paths['test'].render
|
75
|
+
}.to raise_error Configuration::NoMetaValueForPathTemplatePlaceholerError, %q{cannot generate path 'test' from template '#{dirname}#{basename}': need 'path' to generate value for '#{dirname}'}
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should raise NoValueForPathTemplatePlaceholerError if body value is not found' do
|
79
|
+
subject = Configuration.read(<<-'EOF')
|
80
|
+
path {
|
81
|
+
"test" "#{digest}"
|
82
|
+
}
|
83
|
+
EOF
|
84
|
+
|
85
|
+
expect {
|
86
|
+
subject.paths['test'].render(path: '')
|
87
|
+
}.to raise_error Configuration::NoMetaValueForPathTemplatePlaceholerError, %q{cannot generate path 'test' from template '#{digest}': need 'body' to generate value for '#{digest}'}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|