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