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,309 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
require 'httpimagestore/configuration'
|
3
|
+
Configuration::Scope.logger = Logger.new('/dev/null')
|
4
|
+
|
5
|
+
require 'httpimagestore/configuration/file'
|
6
|
+
MemoryLimit.logger = Logger.new('/dev/null')
|
7
|
+
|
8
|
+
describe Configuration do
|
9
|
+
let :state do
|
10
|
+
Configuration::RequestState.new('abc')
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Configuration::FileSource do
|
14
|
+
subject do
|
15
|
+
Configuration.read(<<-EOF)
|
16
|
+
path {
|
17
|
+
"in" "test.in"
|
18
|
+
}
|
19
|
+
|
20
|
+
get "small" {
|
21
|
+
source_file "original" root="/tmp" path="in"
|
22
|
+
}
|
23
|
+
EOF
|
24
|
+
end
|
25
|
+
|
26
|
+
let :in_file do
|
27
|
+
Pathname.new("/tmp/test.in")
|
28
|
+
end
|
29
|
+
|
30
|
+
before :each do
|
31
|
+
in_file.open('w'){|io| io.write('abc')}
|
32
|
+
end
|
33
|
+
|
34
|
+
after :each do
|
35
|
+
in_file.unlink
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should source image from file using path spec' do
|
39
|
+
subject.handlers[0].image_sources[0].should be_a Configuration::FileSource
|
40
|
+
subject.handlers[0].image_sources[0].realize(state)
|
41
|
+
|
42
|
+
state.images["original"].should_not be_nil
|
43
|
+
state.images["original"].data.should == 'abc'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should have nil mime type' do
|
47
|
+
subject.handlers[0].image_sources[0].realize(state)
|
48
|
+
|
49
|
+
state.images["original"].mime_type.should be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should have source path and url' do
|
53
|
+
subject.handlers[0].image_sources[0].realize(state)
|
54
|
+
|
55
|
+
state.images['original'].source_path.should == "test.in"
|
56
|
+
state.images['original'].source_url.should == "file://test.in"
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'context locals' do
|
60
|
+
before :all do
|
61
|
+
Pathname.new('/tmp/test-image-name.jpg').open('w') do |io|
|
62
|
+
io.write('hello world')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
subject do
|
67
|
+
Configuration.read(<<-EOF)
|
68
|
+
path "imagename" "\#{imagename}.jpg"
|
69
|
+
|
70
|
+
get "small" {
|
71
|
+
source_file "test-image-name" root="/tmp" path="imagename"
|
72
|
+
}
|
73
|
+
EOF
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should provide image name to be used as #{imagename}' do
|
77
|
+
subject.handlers[0].image_sources[0].realize(state)
|
78
|
+
state.images['test-image-name'].source_path.should == 'test-image-name.jpg'
|
79
|
+
state.images['test-image-name'].data.should == 'hello world'
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'error handling' do
|
84
|
+
it 'should raise StorageOutsideOfRootDirError on bad paths' do
|
85
|
+
subject = Configuration.read(<<-EOF)
|
86
|
+
path {
|
87
|
+
"bad" "../test.in"
|
88
|
+
}
|
89
|
+
|
90
|
+
get "bad_path" {
|
91
|
+
source_file "original" root="/tmp" path="bad"
|
92
|
+
}
|
93
|
+
EOF
|
94
|
+
|
95
|
+
expect {
|
96
|
+
subject.handlers[0].image_sources[0].realize(state)
|
97
|
+
}.to raise_error Configuration::FileStorageOutsideOfRootDirError, %{error while processing image 'original': file storage path '../test.in' outside of root direcotry}
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should raise NoSuchFileError on missing file' do
|
101
|
+
subject = Configuration.read(<<-EOF)
|
102
|
+
path {
|
103
|
+
"missing" "blah"
|
104
|
+
}
|
105
|
+
|
106
|
+
get "bad_path" {
|
107
|
+
source_file "original" root="/tmp" path="missing"
|
108
|
+
}
|
109
|
+
EOF
|
110
|
+
|
111
|
+
expect {
|
112
|
+
subject.handlers[0].image_sources[0].realize(state)
|
113
|
+
}.to raise_error Configuration::NoSuchFileError, %{error while processing image 'original': file 'blah' not found}
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should raise NoValueError on missing image name' do
|
117
|
+
expect {
|
118
|
+
Configuration.read(<<-EOF)
|
119
|
+
get "test" {
|
120
|
+
source_file root="/tmp" path="hash"
|
121
|
+
}
|
122
|
+
EOF
|
123
|
+
}.to raise_error Configuration::NoValueError, %{syntax error while parsing 'source_file path="hash" root="/tmp"': expected image name}
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should raise NoAttributeError on missing root argument' do
|
127
|
+
expect {
|
128
|
+
Configuration.read(<<-EOF)
|
129
|
+
get "test" {
|
130
|
+
source_file "original" path="hash"
|
131
|
+
}
|
132
|
+
EOF
|
133
|
+
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 'source_file "original" path="hash"': expected 'root' attribute to be set}
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'should raise NoAttributeError on missing path argument' do
|
137
|
+
expect {
|
138
|
+
Configuration.read(<<-EOF)
|
139
|
+
get "test" {
|
140
|
+
source_file "original" root="/tmp"
|
141
|
+
}
|
142
|
+
EOF
|
143
|
+
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 'source_file "original" root="/tmp"': expected 'path' attribute to be set}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe 'memory limit' do
|
148
|
+
let :state do
|
149
|
+
Configuration::RequestState.new('abc', {}, MemoryLimit.new(1))
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should rais MemoryLimit::MemoryLimitedExceededError error if limit exceeded runing file sourcing' do
|
153
|
+
expect {
|
154
|
+
subject.handlers[0].image_sources[0].realize(state)
|
155
|
+
}.to raise_error MemoryLimit::MemoryLimitedExceededError, 'memory limit exceeded'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe Configuration::FileStore do
|
161
|
+
subject do
|
162
|
+
Configuration.read(<<-EOF)
|
163
|
+
path {
|
164
|
+
"out" "test.out"
|
165
|
+
}
|
166
|
+
|
167
|
+
post "small" {
|
168
|
+
store_file "input" root="/tmp" path="out"
|
169
|
+
}
|
170
|
+
EOF
|
171
|
+
end
|
172
|
+
|
173
|
+
let :out_file do
|
174
|
+
Pathname.new("/tmp/test.out")
|
175
|
+
end
|
176
|
+
|
177
|
+
before :each do
|
178
|
+
out_file.unlink if out_file.exist?
|
179
|
+
subject.handlers[0].image_sources[0].realize(state)
|
180
|
+
end
|
181
|
+
|
182
|
+
after :each do
|
183
|
+
out_file.unlink if out_file.exist?
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should store image in file using path spec' do
|
187
|
+
subject.handlers[0].stores[0].should be_a Configuration::FileStore
|
188
|
+
subject.handlers[0].stores[0].realize(state)
|
189
|
+
|
190
|
+
out_file.should exist
|
191
|
+
out_file.read.should == 'abc'
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should have store path and url' do
|
195
|
+
subject.handlers[0].stores[0].realize(state)
|
196
|
+
|
197
|
+
state.images['input'].store_path.should == "test.out"
|
198
|
+
state.images['input'].store_url.should == "file://test.out"
|
199
|
+
end
|
200
|
+
|
201
|
+
describe 'conditional inclusion support' do
|
202
|
+
subject do
|
203
|
+
Configuration.read(<<-'EOF')
|
204
|
+
path {
|
205
|
+
"out" "test.out"
|
206
|
+
}
|
207
|
+
|
208
|
+
post "small" {
|
209
|
+
store_file "input1" root="/tmp" path="out" if-image-name-on="#{list}"
|
210
|
+
store_file "input2" root="/tmp" path="out" if-image-name-on="#{list}"
|
211
|
+
store_file "input3" root="/tmp" path="out" if-image-name-on="#{list}"
|
212
|
+
}
|
213
|
+
EOF
|
214
|
+
end
|
215
|
+
|
216
|
+
let :state do
|
217
|
+
Configuration::RequestState.new('abc', list: 'input1,input3')
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'should mark stores to ib included when image name match if-image-name-on list' do
|
221
|
+
subject.handlers[0].stores[0].excluded?(state).should be_false
|
222
|
+
subject.handlers[0].stores[1].excluded?(state).should be_true
|
223
|
+
subject.handlers[0].stores[2].excluded?(state).should be_false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe 'context locals' do
|
228
|
+
subject do
|
229
|
+
Configuration.read(<<-'EOF')
|
230
|
+
path "imagename" "#{imagename}.jpg"
|
231
|
+
path "mimeextension" "test-store-file.#{mimeextension}"
|
232
|
+
|
233
|
+
post "small" {
|
234
|
+
store_file "input" root="/tmp" path="imagename"
|
235
|
+
store_file "input" root="/tmp" path="mimeextension"
|
236
|
+
}
|
237
|
+
EOF
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'should provide image name to be used as #{imagename}' do
|
241
|
+
subject.handlers[0].stores[0].realize(state)
|
242
|
+
|
243
|
+
state.images['input'].store_path.should == 'input.jpg'
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should provide image mime type based file extension to be used as #{mimeextension}' do
|
247
|
+
state.images['input'].mime_type = 'image/jpeg'
|
248
|
+
subject.handlers[0].stores[1].realize(state)
|
249
|
+
|
250
|
+
state.images['input'].store_path.should == 'test-store-file.jpg'
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'should raise NoValueForPathTemplatePlaceholerError if there is on mime type for image defined and path contains #{mimeextension}' do
|
254
|
+
expect {
|
255
|
+
subject.handlers[0].stores[1].realize(state)
|
256
|
+
}.to raise_error Configuration::NoValueForPathTemplatePlaceholerError, %q{cannot generate path 'mimeextension' from template 'test-store-file.#{mimeextension}': no value for '#{mimeextension}'}
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
describe 'error handling' do
|
261
|
+
it 'should raise StorageOutsideOfRootDirError on bad paths' do
|
262
|
+
subject = Configuration.read(<<-EOF)
|
263
|
+
path {
|
264
|
+
"bad" "../test.in"
|
265
|
+
}
|
266
|
+
|
267
|
+
post "bad_path" {
|
268
|
+
store_file "input" root="/tmp" path="bad"
|
269
|
+
}
|
270
|
+
EOF
|
271
|
+
|
272
|
+
expect {
|
273
|
+
subject.handlers[0].stores[0].realize(state)
|
274
|
+
}.to raise_error Configuration::FileStorageOutsideOfRootDirError, %{error while processing image 'input': file storage path '../test.in' outside of root direcotry}
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'should raise NoValueError on missing image name' do
|
278
|
+
expect {
|
279
|
+
Configuration.read(<<-EOF)
|
280
|
+
get "test" {
|
281
|
+
store_file root="/tmp" path="hash"
|
282
|
+
}
|
283
|
+
EOF
|
284
|
+
}.to raise_error Configuration::NoValueError, %{syntax error while parsing 'store_file path="hash" root="/tmp"': expected image name}
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'should raise NoAttributeError on missing root argument' do
|
288
|
+
expect {
|
289
|
+
Configuration.read(<<-EOF)
|
290
|
+
get "test" {
|
291
|
+
store_file "original" path="hash"
|
292
|
+
}
|
293
|
+
EOF
|
294
|
+
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 'store_file "original" path="hash"': expected 'root' attribute to be set}
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'should raise NoAttributeError on missing path argument' do
|
298
|
+
expect {
|
299
|
+
Configuration.read(<<-EOF)
|
300
|
+
get "test" {
|
301
|
+
store_file "original" root="/tmp"
|
302
|
+
}
|
303
|
+
EOF
|
304
|
+
}.to raise_error Configuration::NoAttributeError, %{syntax error while parsing 'store_file "original" root="/tmp"': expected 'path' attribute to be set}
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
@@ -0,0 +1,124 @@
|
|
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/handler'
|
8
|
+
require 'httpimagestore/configuration/output'
|
9
|
+
MemoryLimit.logger = Logger.new('/dev/null')
|
10
|
+
|
11
|
+
describe Configuration do
|
12
|
+
describe Configuration::Handler do
|
13
|
+
subject do
|
14
|
+
Configuration.read(<<-EOF)
|
15
|
+
get "thumbnail" "v1" ":operation" ":width" ":height" ":options" {
|
16
|
+
}
|
17
|
+
|
18
|
+
put "thumbnail" "v1" ":test/.+/" {
|
19
|
+
}
|
20
|
+
|
21
|
+
post {
|
22
|
+
}
|
23
|
+
EOF
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'http method and matchers' do
|
27
|
+
it 'should provide request http_method and uri_matchers' do
|
28
|
+
subject.handlers.length.should == 3
|
29
|
+
|
30
|
+
subject.handlers[0].http_method.should == 'get'
|
31
|
+
subject.handlers[0].uri_matchers.map{|m| m.name}.should == [nil, nil, :operation, :width, :height, :options]
|
32
|
+
|
33
|
+
subject.handlers[1].http_method.should == 'put'
|
34
|
+
subject.handlers[1].uri_matchers.map{|m| m.name}.should == [nil, nil, :test]
|
35
|
+
|
36
|
+
subject.handlers[2].http_method.should == 'post'
|
37
|
+
subject.handlers[2].uri_matchers.should be_empty
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe Configuration::RequestState do
|
42
|
+
it 'should raise ImageNotLoadedError if image lookup fails' do
|
43
|
+
expect {
|
44
|
+
Configuration::RequestState.new.images['test']
|
45
|
+
}.to raise_error Configuration::ImageNotLoadedError, "image 'test' not loaded"
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should free memory limit if overwritting image' do
|
49
|
+
limit = MemoryLimit.new(2)
|
50
|
+
request_state = Configuration::RequestState.new('abc', {}, limit)
|
51
|
+
|
52
|
+
limit.borrow 1
|
53
|
+
request_state.images['test'] = Configuration::Image.new('x')
|
54
|
+
limit.limit.should == 1
|
55
|
+
|
56
|
+
limit.borrow 1
|
57
|
+
limit.limit.should == 0
|
58
|
+
request_state.images['test'] = Configuration::Image.new('x')
|
59
|
+
limit.limit.should == 1
|
60
|
+
|
61
|
+
limit.borrow 1
|
62
|
+
limit.limit.should == 0
|
63
|
+
request_state.images['test'] = Configuration::Image.new('x')
|
64
|
+
limit.limit.should == 1
|
65
|
+
|
66
|
+
limit.borrow 1
|
67
|
+
limit.limit.should == 0
|
68
|
+
request_state.images['test2'] = Configuration::Image.new('x')
|
69
|
+
limit.limit.should == 0
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'sources' do
|
74
|
+
it 'should have implicit InputSource on non get handlers' do
|
75
|
+
subject.handlers[0].image_sources.first.should_not be_a Configuration::InputSource
|
76
|
+
subject.handlers[1].image_sources.first.should be_a Configuration::InputSource
|
77
|
+
subject.handlers[2].image_sources.first.should be_a Configuration::InputSource
|
78
|
+
end
|
79
|
+
|
80
|
+
describe Configuration::InputSource do
|
81
|
+
it 'should copy input data to "input" image when realized' do
|
82
|
+
state = Configuration::RequestState.new('abc')
|
83
|
+
input_source = subject.handlers[1].image_sources[0].realize(state)
|
84
|
+
state.images['input'].data.should == 'abc'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should have nil mime type' do
|
88
|
+
state = Configuration::RequestState.new('abc')
|
89
|
+
input_source = subject.handlers[1].image_sources[0].realize(state)
|
90
|
+
state.images['input'].mime_type.should be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should have nil source path and url' do
|
94
|
+
state = Configuration::RequestState.new('abc')
|
95
|
+
input_source = subject.handlers[1].image_sources[0].realize(state)
|
96
|
+
state.images['input'].source_path.should be_nil
|
97
|
+
state.images['input'].source_url.should be_nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'output' do
|
103
|
+
it 'should default to OutputOK' do
|
104
|
+
subject.handlers[0].output.should be_a Configuration::OutputOK
|
105
|
+
subject.handlers[1].output.should be_a Configuration::OutputOK
|
106
|
+
subject.handlers[2].output.should be_a Configuration::OutputOK
|
107
|
+
end
|
108
|
+
|
109
|
+
describe Configuration::OutputOK do
|
110
|
+
it 'should output 200 with OK text/plain message when realized' do
|
111
|
+
state = Configuration::RequestState.new('abc')
|
112
|
+
subject.handlers[2].output.realize(state)
|
113
|
+
|
114
|
+
env = CubaResponseEnv.new
|
115
|
+
env.instance_eval &state.output_callback
|
116
|
+
env.res.status.should == 200
|
117
|
+
env.res.data.should == "OK\r\n"
|
118
|
+
env.res['Content-Type'].should == 'text/plain'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|