httpimagestore 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/Gemfile +10 -12
  2. data/Gemfile.lock +57 -55
  3. data/README.md +829 -0
  4. data/VERSION +1 -1
  5. data/bin/httpimagestore +114 -180
  6. data/features/cache-control.feature +26 -90
  7. data/features/compatibility.feature +129 -0
  8. data/features/error-reporting.feature +207 -0
  9. data/features/health-check.feature +30 -0
  10. data/features/s3-store-and-thumbnail.feature +65 -0
  11. data/features/step_definitions/httpimagestore_steps.rb +66 -26
  12. data/features/support/env.rb +32 -5
  13. data/features/support/test.empty +0 -0
  14. data/httpimagestore.gemspec +60 -47
  15. data/lib/httpimagestore/aws_sdk_regions_hack.rb +23 -0
  16. data/lib/httpimagestore/configuration/file.rb +120 -0
  17. data/lib/httpimagestore/configuration/handler.rb +239 -0
  18. data/lib/httpimagestore/configuration/output.rb +119 -0
  19. data/lib/httpimagestore/configuration/path.rb +77 -0
  20. data/lib/httpimagestore/configuration/s3.rb +194 -0
  21. data/lib/httpimagestore/configuration/thumbnailer.rb +244 -0
  22. data/lib/httpimagestore/configuration.rb +126 -29
  23. data/lib/httpimagestore/error_reporter.rb +36 -0
  24. data/lib/httpimagestore/ruby_string_template.rb +26 -0
  25. data/load_test/load_test.1k.23a022f6e.m1.small-comp.csv +3 -0
  26. data/load_test/load_test.1k.ec9bde794.m1.small.csv +4 -0
  27. data/load_test/load_test.jmx +344 -0
  28. data/load_test/thumbnail_specs.csv +11 -0
  29. data/spec/configuration_file_spec.rb +309 -0
  30. data/spec/configuration_handler_spec.rb +124 -0
  31. data/spec/configuration_output_spec.rb +338 -0
  32. data/spec/configuration_path_spec.rb +92 -0
  33. data/spec/configuration_s3_spec.rb +571 -0
  34. data/spec/configuration_spec.rb +80 -105
  35. data/spec/configuration_thumbnailer_spec.rb +417 -0
  36. data/spec/ruby_string_template_spec.rb +43 -0
  37. data/spec/spec_helper.rb +61 -0
  38. data/spec/support/compute.jpg +0 -0
  39. data/spec/support/cuba_response_env.rb +40 -0
  40. data/spec/support/full.cfg +49 -0
  41. metadata +138 -84
  42. data/README.rdoc +0 -23
  43. data/features/httpimagestore.feature +0 -167
  44. data/lib/httpimagestore/image_path.rb +0 -54
  45. data/lib/httpimagestore/s3_service.rb +0 -37
  46. data/lib/httpimagestore/thumbnail_class.rb +0 -13
  47. data/spec/image_path_spec.rb +0 -72
  48. 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
+