ungulate 0.1.2 → 0.1.3

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/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