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.
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
+