ungulate 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.1.3
@@ -0,0 +1,11 @@
1
+ Feature: Image resize
2
+ As a site owner
3
+ I want Ungulate to resize then composite images
4
+ So that I don't have to watermark images by hand
5
+
6
+ Scenario: Run queue on image key with no path separator
7
+ Given an empty queue
8
+ And a request to resize "image.jpg" and then composite with "https://dmxno528jhfy0.cloudfront.net/superhug-watermark.png"
9
+ When I run Ungulate
10
+ Then there should be a public watermarked version
11
+
@@ -30,3 +30,24 @@ Given /^a request to resize "([^\"]*)" to sizes:$/ do |key, table|
30
30
  @q.send_message(message)
31
31
  end
32
32
 
33
+ Given /^a request to resize "([^"]*)" and then composite with "([^"]*)"$/ do |key, composite_url|
34
+ @bucket_name = "ungulate-test"
35
+
36
+ @s3 = RightAws::S3.new(ENV['AMAZON_ACCESS_KEY_ID'],
37
+ ENV['AMAZON_SECRET_ACCESS_KEY'])
38
+ @bucket = @s3.bucket @bucket_name
39
+ @bucket.put key, File.open('features/camels.jpg').read
40
+
41
+ message = {
42
+ :bucket => @bucket_name,
43
+ :key => key,
44
+ :versions => {
45
+ :watermarked => [
46
+ [:resize_to_fill, 100, 100],
47
+ [:composite, composite_url, :center_gravity, :soft_light_composite_op]
48
+ ]
49
+ }
50
+ }.to_yaml
51
+
52
+ @q.send_message(message)
53
+ end
@@ -7,3 +7,10 @@ Then /^there should be the following public versions:$/ do |table|
7
7
  end
8
8
  end
9
9
 
10
+ Then /^there should be a public watermarked version$/ do
11
+ Net::HTTP.start("#{@bucket_name}.s3.amazonaws.com", 80) do |http|
12
+ response = http.get("/image_watermarked.jpg")
13
+ response.should be_a(Net::HTTPSuccess)
14
+ end
15
+ end
16
+
@@ -2,8 +2,7 @@ require 'active_support/core_ext/class/attribute_accessors'
2
2
  class Ungulate::FileUpload
3
3
  attr_accessor(
4
4
  :bucket_url,
5
- :key,
6
- :policy
5
+ :key
7
6
  )
8
7
 
9
8
  cattr_accessor(
@@ -21,10 +20,13 @@ class Ungulate::FileUpload
21
20
  sqs.queue(queue_name)
22
21
  end
23
22
 
24
- def initialize(params)
25
- self.bucket_url = params[:bucket_url]
26
- self.key = params[:key]
27
- self.policy = params[:policy]
23
+ def initialize(options = {})
24
+ self.bucket_url = options[:bucket_url]
25
+ self.key = options[:key]
26
+
27
+ if options[:policy]
28
+ self.policy = options[:policy]
29
+ end
28
30
  end
29
31
 
30
32
  def acl
@@ -41,11 +43,16 @@ class Ungulate::FileUpload
41
43
  @policy_ruby['conditions'].map {|condition| condition.to_a.flatten}
42
44
  end
43
45
 
44
- def policy=(policy)
45
- @policy_ruby = policy
46
- @policy_ruby['expiration'] = @policy_ruby['expiration'].utc
47
- policy_json = ActiveSupport::JSON.encode(@policy_ruby)
48
- @policy = Base64.encode64(policy_json).gsub("\n", '')
46
+ def policy
47
+ Base64.encode64(
48
+ ActiveSupport::JSON.encode(@policy_ruby)
49
+ ).gsub("\n", '')
50
+ end
51
+
52
+ def policy=(new_policy)
53
+ new_policy['expiration'] = new_policy['expiration'].utc
54
+ @policy_ruby = new_policy
55
+ policy
49
56
  end
50
57
 
51
58
  def success_action_redirect
@@ -55,7 +62,7 @@ class Ungulate::FileUpload
55
62
  def signature
56
63
  Base64.encode64(
57
64
  OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'),
58
- secret_access_key,
65
+ secret_access_key,
59
66
  policy)
60
67
  ).gsub("\n", '')
61
68
  end
@@ -4,6 +4,7 @@ require 'RMagick'
4
4
  require 'mime/types'
5
5
  require 'yaml'
6
6
  require 'active_support/core_ext'
7
+ require 'curb'
7
8
 
8
9
  module Ungulate
9
10
  class Job
@@ -45,15 +46,56 @@ module Ungulate
45
46
  def processed_versions
46
47
  @processed_versions ||=
47
48
  versions.map do |name, instruction|
48
- method, x, y = instruction
49
- image = Magick::Image.from_blob(source).first
50
- @logger.info "Performing #{method} with #{x}, #{y}"
51
- processed_image = image.send(method, x, y)
52
- image.destroy!
53
- [name, processed_image]
49
+ [name, processed_image(source_image, instruction)]
54
50
  end
55
51
  end
56
52
 
53
+ def instruction_args(args)
54
+ args.map do |arg|
55
+ if arg.is_a?(Symbol)
56
+ "Magick::#{arg.to_s.classify}".constantize
57
+ elsif arg.respond_to?(:match) && arg.match(/^http/)
58
+ Magick::Image.from_blob(Curl::Easy.http_get(arg).body_str).first
59
+ else
60
+ arg
61
+ end
62
+ end
63
+ end
64
+
65
+ def image_from_instruction(original, instruction)
66
+ method, *args = instruction
67
+ send_args = instruction_args(args)
68
+
69
+ @logger.info "Performing #{method} with #{args.join(', ')}"
70
+ original.send(method, *send_args).tap do |new_image|
71
+ original.destroy!
72
+ send_args.select {|arg| arg.is_a?(Magick::Image)}.each(&:destroy!)
73
+ end
74
+ end
75
+
76
+ def image_from_instruction_chain(original, chain)
77
+ if chain.one?
78
+ image_from_instruction(original, chain.first)
79
+ else
80
+ image_from_instruction_chain(
81
+ image_from_instruction(original, chain.shift),
82
+ chain
83
+ )
84
+ end
85
+ end
86
+
87
+ def processed_image(original, instruction)
88
+ if instruction.first.respond_to?(:entries)
89
+ image_from_instruction_chain(original, instruction)
90
+ else
91
+ image_from_instruction(original, instruction)
92
+ end
93
+ end
94
+
95
+ def source_image
96
+ Magick::Image.from_blob(source).first
97
+ end
98
+
57
99
  def source
58
100
  if @source
59
101
  @source
@@ -92,12 +134,7 @@ module Ungulate
92
134
  return false if notification_url.blank?
93
135
 
94
136
  @logger.info "Sending notification to #{notification_url}"
95
-
96
- url = URI.parse(notification_url)
97
-
98
- http = Net::HTTP.new(url.host, url.port)
99
- http.use_ssl = true if url.scheme == 'https'
100
- http.start {|http| http.put(url.path, nil) }
137
+ Curl::Easy.http_put(notification_url, '')
101
138
  end
102
139
 
103
140
  def version_key(version)
@@ -3,54 +3,61 @@ require 'ungulate/file_upload'
3
3
 
4
4
  module Ungulate
5
5
  describe FileUpload do
6
+ let(:expiration) { 10.hours.from_now }
7
+ let(:bucket_url) { "http://images.bob.com/" }
8
+ let(:key) { "new-file" }
9
+ let(:access_key_id) { "asdf" }
10
+ let(:secret_access_key) { 'uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o' }
11
+ let(:queue_name) { 'some-queue-name' }
12
+
6
13
  before do
7
- @expiration = 10.hours.from_now
8
- @bucket_url = "http://images.bob.com/"
9
- @key = "new-file"
10
-
11
- @policy = {
12
- "expiration" => @expiration,
13
- "conditions" => [
14
- {"bucket" => "johnsmith" },
15
- ["starts-with", "$key", "user/eric/"],
16
- {"acl" => "public-read" },
17
- {"success_action_redirect" => "http://johnsmith.s3.amazonaws.com/successful_upload.html" },
18
- ["starts-with", "$Content-Type", "image/"],
19
- {"x-amz-meta-uuid" => "14365123651274"},
20
- ["starts-with", "$x-amz-meta-tag", ""]
21
- ]
22
- }
23
-
24
- @access_key_id = "asdf"
25
- @secret_access_key = 'uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o'
26
- FileUpload.access_key_id = @access_key_id
27
- FileUpload.secret_access_key = @secret_access_key
28
- FileUpload.queue_name = 'some-queue-name'
14
+ FileUpload.access_key_id = access_key_id
15
+ FileUpload.secret_access_key = secret_access_key
16
+ FileUpload.queue_name = queue_name
29
17
  end
30
18
 
31
- subject do
32
- FileUpload.new(
33
- :bucket_url => @bucket_url,
34
- :policy => @policy,
35
- :key => @key
36
- )
37
- end
19
+ its(:access_key_id) { should == access_key_id }
20
+ its(:queue_name) { should == queue_name }
21
+ its(:secret_access_key) { should == secret_access_key }
22
+
23
+ context "policy set directly" do
24
+ let(:policy) do
25
+ {
26
+ "expiration" => expiration,
27
+ "conditions" => [
28
+ {"bucket" => "johnsmith" },
29
+ ["starts-with", "$key", "user/eric/"],
30
+ {"acl" => "public-read" },
31
+ {"success_action_redirect" => "http://johnsmith.s3.amazonaws.com/successful_upload.html" },
32
+ ["starts-with", "$Content-Type", "image/"],
33
+ {"x-amz-meta-uuid" => "14365123651274"},
34
+ ["starts-with", "$x-amz-meta-tag", ""]
35
+ ]
36
+ }
37
+ end
38
38
 
39
- its(:acl) { should == 'public-read' }
40
- its(:bucket_url) { should == @bucket_url }
41
- its(:conditions) { should == [
42
- ['bucket', 'johnsmith'],
43
- ['starts-with', '$key', 'user/eric/'],
44
- ['acl', 'public-read'],
45
- ['success_action_redirect', 'http://johnsmith.s3.amazonaws.com/successful_upload.html'],
46
- ['starts-with', '$Content-Type', 'image/'],
47
- ["x-amz-meta-uuid", "14365123651274"],
48
- ["starts-with", "$x-amz-meta-tag", ""]
49
- ] }
50
- its(:access_key_id) { should == @access_key_id }
51
- its(:key) { should == @key }
52
- its(:queue_name) { should == 'some-queue-name' }
53
- its(:success_action_redirect) { should == 'http://johnsmith.s3.amazonaws.com/successful_upload.html' }
39
+ subject do
40
+ FileUpload.new(
41
+ :bucket_url => bucket_url,
42
+ :policy => policy,
43
+ :key => key
44
+ )
45
+ end
46
+
47
+ its(:acl) { should == 'public-read' }
48
+ its(:bucket_url) { should == bucket_url }
49
+ its(:conditions) { should == [
50
+ ['bucket', 'johnsmith'],
51
+ ['starts-with', '$key', 'user/eric/'],
52
+ ['acl', 'public-read'],
53
+ ['success_action_redirect', 'http://johnsmith.s3.amazonaws.com/successful_upload.html'],
54
+ ['starts-with', '$Content-Type', 'image/'],
55
+ ["x-amz-meta-uuid", "14365123651274"],
56
+ ["starts-with", "$x-amz-meta-tag", ""]
57
+ ] }
58
+ its(:key) { should == key }
59
+ its(:success_action_redirect) { should == 'http://johnsmith.s3.amazonaws.com/successful_upload.html' }
60
+ end
54
61
 
55
62
  describe "condition" do
56
63
  before do
@@ -58,28 +65,28 @@ module Ungulate
58
65
  and_return([ ['colour', 'blue'], ['predicate', 'subject', 'object'] ])
59
66
  end
60
67
 
61
- it "should return value of index 1 in a two-item array" do
68
+ it "returns value of index 1 in a two-item array" do
62
69
  subject.condition('colour').should == 'blue'
63
70
  end
64
71
 
65
- it "should cope with missing attribute" do
72
+ it "copes with missing attribute" do
66
73
  subject.condition('bob').should be_nil
67
74
  end
68
75
  end
69
76
 
70
77
  describe "conditions" do
71
- it "should memoize" do
78
+ it "memoizes" do
72
79
  subject.instance_variable_set('@conditions', :cache)
73
80
  subject.conditions.should == :cache
74
81
  end
75
82
 
76
- it "should convert mixed hash and array policy to nested arrays" do
83
+ it "converts mixed hash and array policy to nested arrays" do
77
84
  subject.
78
- instance_variable_set('@policy_ruby',
79
- {
80
- 'conditions' => [
81
- {'colour' => 'blue'},
82
- ['predicate', 'subject', 'object']
85
+ instance_variable_set('@policy_ruby',
86
+ {
87
+ 'conditions' => [
88
+ {'colour' => 'blue'},
89
+ ['predicate', 'subject', 'object']
83
90
  ]
84
91
  })
85
92
  subject.conditions.should == [ ['colour', 'blue'], ['predicate', 'subject', 'object'] ]
@@ -87,73 +94,94 @@ module Ungulate
87
94
  end
88
95
 
89
96
  describe "enqueue" do
90
- before do
91
- @q = mock('queue')
92
- Ungulate::FileUpload.stub(:queue).and_return(@q)
93
- @job_hash = mock('Hash', :to_yaml => :some_yaml)
97
+ let(:q) { stub 'queue' }
98
+ let(:job_hash) { stub('Hash', :to_yaml => :some_yaml) }
99
+ before { Ungulate::FileUpload.stub(:queue).and_return(q) }
100
+
101
+ it "queues the yamlised version of the passed job hash" do
102
+ q.should_receive(:send_message).with(:some_yaml)
103
+ Ungulate::FileUpload.enqueue(job_hash)
94
104
  end
105
+ end
95
106
 
96
- it "should queue the yamlised version of the passed job hash" do
97
- @q.should_receive(:send_message).with(:some_yaml)
98
- Ungulate::FileUpload.enqueue(@job_hash)
107
+ describe "policy" do
108
+ it "returns base64 encoded JSON version of stored policy" do
109
+ subject.instance_variable_set('@policy_ruby', :some_policy)
110
+ ActiveSupport::JSON.stub(:encode).with(:some_policy).and_return(:json)
111
+ Base64.stub(:encode64).with(:json).and_return("ENCODED\nLINE\nLINE")
112
+ subject.policy.should == "ENCODEDLINELINE"
99
113
  end
100
114
  end
101
115
 
102
116
  describe "policy=" do
103
- it "should store the ruby version for later use" do
104
- subject.policy = @policy
105
- subject.instance_variable_get('@policy_ruby').should_not be_blank
117
+ let(:policy) do
118
+ {
119
+ "expiration" => expiration,
120
+ "conditions" => [
121
+ {"bucket" => "johnsmith" },
122
+ ["starts-with", "$key", "user/eric/"],
123
+ {"acl" => "public-read" },
124
+ {"success_action_redirect" => "http://johnsmith.s3.amazonaws.com/successful_upload.html" },
125
+ ["starts-with", "$Content-Type", "image/"],
126
+ {"x-amz-meta-uuid" => "14365123651274"},
127
+ ["starts-with", "$x-amz-meta-tag", ""]
128
+ ]
129
+ }
106
130
  end
107
131
 
108
- it "should store the base64 encoded JSON" do
109
- subject # load subject without stubs
110
-
111
- ActiveSupport::JSON.stub(:encode).with(@policy).and_return(:some_json)
112
- Base64.stub(:encode64).with(:some_json).and_return("ENCODED\nLINE\nLINE")
113
- subject.policy = @policy
114
- subject.policy.should == "ENCODEDLINELINE"
132
+ it "stores the ruby version for later use" do
133
+ subject.policy = policy
134
+ subject.instance_variable_get('@policy_ruby').should_not be_blank
115
135
  end
116
136
 
117
- it "should ensure the expiration is utc" do
137
+ it "ensures the expiration is utc" do
118
138
  utc_time = Time.now.utc
119
139
 
120
- @expiration.stub(:utc).and_return(utc_time)
140
+ expiration.stub(:utc).and_return(utc_time)
121
141
  Base64.stub(:encode64).and_return('')
122
142
 
123
143
  ActiveSupport::JSON.should_receive(:encode).
124
144
  with(hash_including('expiration' => utc_time)).
125
145
  any_number_of_times
126
146
 
127
- subject.policy = @policy
147
+ subject.policy = policy
148
+ end
149
+
150
+ it "returns the encoded policy" do
151
+ subject.stub(:policy).and_return(:encoded_policy)
152
+ subject.send(:policy=, policy).should == :encoded_policy
128
153
  end
129
154
  end
130
155
 
131
156
  describe "queue" do
157
+ let(:sqs) do
158
+ sqs = stub 'SQS'
159
+ sqs.stub(:queue).with(queue_name).and_return(:queue_instance)
160
+ sqs
161
+ end
162
+
163
+ subject { Ungulate::FileUpload.queue }
164
+
132
165
  before do
133
- sqs = mock('sqs')
134
- FileUpload.queue_name = 'somequeuename'
135
- FileUpload.access_key_id = 'someaccesskey'
136
- FileUpload.secret_access_key = 'somesecret'
137
- RightAws::SqsGen2.stub(:new).with('someaccesskey', 'somesecret').
166
+ RightAws::SqsGen2.stub(:new).
167
+ with(access_key_id, secret_access_key).
138
168
  and_return(sqs)
139
- sqs.stub(:queue).with('somequeuename').and_return(:queue_instance)
140
169
  end
141
170
 
142
- it "should return a queue instance" do
143
- Ungulate::FileUpload.queue.should == :queue_instance
144
- end
171
+ it { should == :queue_instance }
145
172
  end
146
173
 
147
174
  describe "signature" do
175
+ let(:sha1) { stub 'SHA1' }
176
+
148
177
  before do
149
178
  subject.stub(:policy).and_return(:policy)
150
- @sha1 = mock('SHA1')
151
- OpenSSL::Digest::Digest.stub(:new).with('sha1').and_return(@sha1)
152
- OpenSSL::HMAC.stub(:digest).with(@sha1, @secret_access_key, :policy).and_return(:digest)
179
+ OpenSSL::Digest::Digest.stub(:new).with('sha1').and_return(sha1)
180
+ OpenSSL::HMAC.stub(:digest).with(sha1, secret_access_key, :policy).and_return(:digest)
153
181
  Base64.stub(:encode64).with(:digest).and_return("str\nipme\n")
154
182
  end
155
183
 
156
- it "should return the stripped base64 encoded digest" do
184
+ it "returns the stripped base64 encoded digest" do
157
185
  subject.signature.should == "stripme"
158
186
  end
159
187
  end
@@ -3,6 +3,11 @@ require 'ungulate/job'
3
3
 
4
4
  module Ungulate
5
5
  describe Job do
6
+ let(:source_image) { stub('Source Image', :destroy! => nil) }
7
+ let(:processed_image_1) { stub('Image 1', :destroy! => nil) }
8
+ let(:processed_image_2) { stub('Image 2', :destroy! => nil) }
9
+ let(:curl_easy) { stub('Curl::Easy', :body_str => body_str) }
10
+
6
11
  before do
7
12
  ENV['AMAZON_ACCESS_KEY_ID'] = 'test-key-id'
8
13
  ENV['AMAZON_SECRET_ACCESS_KEY'] = 'test-secret'
@@ -14,6 +19,8 @@ module Ungulate
14
19
  :thumb => [ :resize_to_fit, 100, 200 ],
15
20
  :large => [ :resize_to_fit, 200, 300 ],
16
21
  }
22
+
23
+ Curl::Easy.stub(:http_get)
17
24
  end
18
25
 
19
26
  its(:versions) { should == [] }
@@ -128,38 +135,136 @@ module Ungulate
128
135
  end
129
136
  end
130
137
 
131
- describe :processed_versions do
138
+ describe :image do
139
+ let(:blob) { 'asdf' }
140
+
141
+ it "returns a Magick::Image from the source" do
142
+ subject.stub(:source).and_return(blob)
143
+ Magick::Image.should_receive(:from_blob).with(blob).and_return([source_image])
144
+ subject.source_image.should == source_image
145
+ end
146
+ end
147
+
148
+ describe :image_from_instruction do
149
+ let(:instruction) { [ :composite, url, arg1, arg2 ] }
150
+ let(:overlay) { stub('Overlay', :destroy! => nil) }
151
+ let(:arg1) { 1 }
152
+ let(:arg2) { 2 }
153
+ let(:url) { 'https://www.some.url/' }
154
+ let(:body_str) { 'blob' }
155
+
132
156
  before do
133
- @job = Job.new
134
- versions = {
157
+ Curl::Easy.stub(:http_get).and_return(curl_easy)
158
+ Magick::Image.stub(:from_blob).and_return([overlay])
159
+ end
160
+
161
+ context "when an argument is a URL" do
162
+ it "converts the URL to an Image" do
163
+ Curl::Easy.should_receive(:http_get).with(url).and_return(curl_easy)
164
+ Magick::Image.should_receive(:from_blob).with('blob').and_return([overlay])
165
+ source_image.should_receive(:composite).
166
+ with(overlay, arg1, arg2).
167
+ and_return(processed_image_1)
168
+
169
+ subject.image_from_instruction(source_image, instruction).
170
+ should == processed_image_1
171
+ end
172
+ end
173
+
174
+ context "when argument isn't a valid http URL" do
175
+ let(:url) { 'somethingwithhttpinit' }
176
+
177
+ it "passes the argument through to the method" do
178
+ source_image.should_receive(:composite).
179
+ with(url, arg1, arg2).
180
+ and_return(processed_image_1)
181
+
182
+ subject.image_from_instruction(source_image, instruction)
183
+ end
184
+ end
185
+
186
+ context "when arguments are symbols" do
187
+ let(:arg1) { :center_gravity }
188
+ let(:arg2) { :soft_light_composite_op }
189
+
190
+ it "converts the symbols to Magick::XxXx constants" do
191
+ source_image.should_receive(:composite).
192
+ with(anything, Magick::CenterGravity, Magick::SoftLightCompositeOp).
193
+ and_return(processed_image_1)
194
+
195
+ subject.image_from_instruction(source_image, instruction)
196
+ end
197
+ end
198
+ end
199
+
200
+ describe :processed_versions do
201
+ let(:versions) do
202
+ {
135
203
  :large => [ :resize_to_fit, 100, 200 ],
136
204
  :small => [ :resize_to_fill, 64, 64 ],
137
205
  }
138
- @job.stub(:versions).and_return(versions)
139
- @job.stub(:key).and_return('someimage.jpg')
206
+ end
140
207
 
141
- @job.stub(:source).and_return(:data)
142
- @source_image = mock('Image', :destroy! => nil)
143
- Magick::Image.stub(:from_blob).with(:data).and_return([@source_image])
208
+ before do
209
+ subject.stub(:versions).and_return(versions)
210
+ subject.stub(:key).and_return('someimage.jpg')
211
+ subject.stub(:source_image).and_return(source_image)
144
212
 
145
- @source_image.stub(:resize_to_fit).with(100, 200).and_return(:large_image)
146
- @source_image.stub(:resize_to_fill).with(64, 64).and_return(:small_image)
213
+ source_image.stub(:resize_to_fit).with(100, 200).and_return(processed_image_1)
214
+ source_image.stub(:resize_to_fill).with(64, 64).and_return(processed_image_2)
147
215
  end
148
216
 
149
- context "result" do
150
- subject { @job.processed_versions }
151
- it { should include([:large, :large_image]) }
152
- it { should include([:small, :small_image]) }
217
+ it "processes multiple versions" do
218
+ subject.processed_versions.should include([:large, processed_image_1])
219
+ subject.processed_versions.should include([:small, processed_image_2])
153
220
  end
154
221
 
155
- it "should destroy the image object" do
156
- @source_image.should_receive(:destroy!)
157
- @job.processed_versions
222
+ it "destroys the image object" do
223
+ source_image.should_receive(:destroy!)
224
+ subject.processed_versions
158
225
  end
159
226
 
160
- it "should memoize" do
161
- @job.instance_variable_set('@processed_versions', :cache)
162
- @job.processed_versions.should == :cache
227
+ it "memoizes" do
228
+ subject.instance_variable_set('@processed_versions', :cache)
229
+ subject.processed_versions.should == :cache
230
+ end
231
+
232
+ context "with three 'method' arguments" do
233
+ let(:versions) do
234
+ { :large => [ :some_method, 'some-value', 1, 2 ] }
235
+ end
236
+
237
+ it "passes each value" do
238
+ source_image.should_receive(:some_method).
239
+ with('some-value', 1, 2).
240
+ and_return(processed_image_1)
241
+ subject.processed_versions.should == [[:large, processed_image_1]]
242
+ end
243
+ end
244
+
245
+ context "with multiple instructions in a version" do
246
+ let(:versions) do
247
+ {
248
+ :large => [
249
+ [ :method_1, 'value-1' ],
250
+ [ :method_2, 'value-2' ]
251
+ ]
252
+ }
253
+ end
254
+
255
+ before do
256
+ source_image.stub(:method_1).and_return(processed_image_1)
257
+ processed_image_1.stub(:method_2).and_return(processed_image_2)
258
+ end
259
+
260
+ it "chains the processing" do
261
+ subject.processed_versions.should == [[:large, processed_image_2]]
262
+ end
263
+
264
+ it "destroys intermediate images" do
265
+ processed_image_1.should_receive(:destroy!)
266
+ subject.processed_versions
267
+ end
163
268
  end
164
269
  end
165
270
 
@@ -233,48 +338,13 @@ module Ungulate
233
338
  end
234
339
 
235
340
  describe :send_notification do
236
- after { subject.send_notification }
237
-
238
- let(:http_instance) { mock('Net::HTTP', :start => nil) }
239
- let(:http_block_instance) { mock('Net::HTTP', :put => nil) }
341
+ let(:url) { 'https://some-url' }
240
342
 
241
343
  context "notification URL provided" do
242
- before do
243
- subject.stub(:notification_url).and_return('http://some.host/processing_images/some/path')
244
- end
245
-
246
344
  it "should PUT to the URL" do
247
- Net::HTTP.should_receive(:new).with('some.host', 80).and_return(http_instance)
248
- http_instance.should_receive(:start).and_yield(http_block_instance)
249
- http_block_instance.should_receive(:put).with('/processing_images/some/path', nil)
250
- end
251
- end
252
-
253
- context "https notification URL provided" do
254
- before do
255
- subject.stub(:notification_url).and_return('https://some.host/processing_images/some/path')
256
- http_instance.stub(:use_ssl=)
257
- end
258
-
259
- it "should PUT to the URL" do
260
- Net::HTTP.should_receive(:new).with('some.host', 443).and_return(http_instance)
261
- http_instance.should_receive(:start).and_yield(http_block_instance)
262
- http_block_instance.should_receive(:put).with('/processing_images/some/path', nil)
263
- end
264
-
265
- it "should use SSL" do
266
- Net::HTTP.should_receive(:new).with('some.host', 443).and_return(http_instance)
267
- http_instance.should_receive(:use_ssl=).with(true)
268
- end
269
- end
270
-
271
- context "notification URL not provided" do
272
- before do
273
- subject.stub(:notification_url).and_return(nil)
274
- end
275
-
276
- it "should not PUT" do
277
- Net::HTTP.should_not_receive(:put)
345
+ subject.stub(:notification_url).and_return(url)
346
+ Curl::Easy.should_receive(:http_put).with(url, '')
347
+ subject.send_notification
278
348
  end
279
349
  end
280
350
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ungulate
3
3
  version: !ruby/object:Gem::Version
4
- hash: 31
4
+ hash: 29
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 2
10
- version: 0.1.2
9
+ - 3
10
+ version: 0.1.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andrew Bruce
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-20 00:00:00 +01:00
18
+ date: 2011-09-17 00:00:00 +01:00
19
19
  default_executable: ungulate_server.rb
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -82,9 +82,25 @@ dependencies:
82
82
  type: :runtime
83
83
  version_requirements: *id004
84
84
  - !ruby/object:Gem::Dependency
85
- name: rake
85
+ name: curb
86
86
  prerelease: false
87
87
  requirement: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 29
93
+ segments:
94
+ - 0
95
+ - 7
96
+ - 15
97
+ version: 0.7.15
98
+ type: :runtime
99
+ version_requirements: *id005
100
+ - !ruby/object:Gem::Dependency
101
+ name: rake
102
+ prerelease: false
103
+ requirement: &id006 !ruby/object:Gem::Requirement
88
104
  none: false
89
105
  requirements:
90
106
  - - ">="
@@ -94,11 +110,11 @@ dependencies:
94
110
  - 0
95
111
  version: "0"
96
112
  type: :development
97
- version_requirements: *id005
113
+ version_requirements: *id006
98
114
  - !ruby/object:Gem::Dependency
99
115
  name: rspec
100
116
  prerelease: false
101
- requirement: &id006 !ruby/object:Gem::Requirement
117
+ requirement: &id007 !ruby/object:Gem::Requirement
102
118
  none: false
103
119
  requirements:
104
120
  - - ">="
@@ -110,11 +126,11 @@ dependencies:
110
126
  - 0
111
127
  version: 2.4.0
112
128
  type: :development
113
- version_requirements: *id006
129
+ version_requirements: *id007
114
130
  - !ruby/object:Gem::Dependency
115
131
  name: cucumber
116
132
  prerelease: false
117
- requirement: &id007 !ruby/object:Gem::Requirement
133
+ requirement: &id008 !ruby/object:Gem::Requirement
118
134
  none: false
119
135
  requirements:
120
136
  - - ">="
@@ -126,11 +142,11 @@ dependencies:
126
142
  - 0
127
143
  version: 0.10.0
128
144
  type: :development
129
- version_requirements: *id007
145
+ version_requirements: *id008
130
146
  - !ruby/object:Gem::Dependency
131
147
  name: i18n
132
148
  prerelease: false
133
- requirement: &id008 !ruby/object:Gem::Requirement
149
+ requirement: &id009 !ruby/object:Gem::Requirement
134
150
  none: false
135
151
  requirements:
136
152
  - - ">="
@@ -142,7 +158,21 @@ dependencies:
142
158
  - 0
143
159
  version: 0.5.0
144
160
  type: :development
145
- version_requirements: *id008
161
+ version_requirements: *id009
162
+ - !ruby/object:Gem::Dependency
163
+ name: ruby-debug
164
+ prerelease: false
165
+ requirement: &id010 !ruby/object:Gem::Requirement
166
+ none: false
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ hash: 3
171
+ segments:
172
+ - 0
173
+ version: "0"
174
+ type: :development
175
+ version_requirements: *id010
146
176
  description: |
147
177
  = Ungulate
148
178
 
@@ -195,16 +225,17 @@ files:
195
225
  - features/camels.jpg
196
226
  - features/cope_with_empty_queue.feature
197
227
  - features/image_resize.feature
228
+ - features/resize_then_composite.feature
198
229
  - features/step_definitions/command_steps.rb
199
230
  - features/step_definitions/queue_steps.rb
200
231
  - features/step_definitions/version_steps.rb
232
+ - features/superhugwatermark.png
201
233
  - features/support.rb
202
234
  - lib/ungulate/file_upload.rb
203
235
  - lib/ungulate/job.rb
204
236
  - lib/ungulate/server.rb
205
237
  - lib/ungulate/view_helpers.rb
206
238
  - lib/ungulate.rb
207
- - spec/spec.opts
208
239
  - spec/spec_helper.rb
209
240
  - spec/ungulate/file_upload_spec.rb
210
241
  - spec/ungulate/job_spec.rb
@@ -1,3 +0,0 @@
1
- --color
2
- --debugger
3
- -b