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 +1 -1
- data/features/resize_then_composite.feature +11 -0
- data/features/step_definitions/queue_steps.rb +21 -0
- data/features/step_definitions/version_steps.rb +7 -0
- data/features/superhugwatermark.png +0 -0
- data/lib/ungulate/file_upload.rb +19 -12
- data/lib/ungulate/job.rb +49 -12
- data/spec/ungulate/file_upload_spec.rb +114 -86
- data/spec/ungulate/job_spec.rb +129 -59
- metadata +44 -13
- data/spec/spec.opts +0 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
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
|
+
|
Binary file
|
data/lib/ungulate/file_upload.rb
CHANGED
@@ -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(
|
25
|
-
self.bucket_url =
|
26
|
-
self.key =
|
27
|
-
|
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
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
data/lib/ungulate/job.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
)
|
37
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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 "
|
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 "
|
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 "
|
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 "
|
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
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
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 "
|
109
|
-
subject
|
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 "
|
137
|
+
it "ensures the expiration is utc" do
|
118
138
|
utc_time = Time.now.utc
|
119
139
|
|
120
|
-
|
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 =
|
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
|
-
|
134
|
-
|
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
|
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
|
-
|
151
|
-
OpenSSL::
|
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 "
|
184
|
+
it "returns the stripped base64 encoded digest" do
|
157
185
|
subject.signature.should == "stripme"
|
158
186
|
end
|
159
187
|
end
|
data/spec/ungulate/job_spec.rb
CHANGED
@@ -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 :
|
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
|
-
|
134
|
-
|
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
|
-
|
139
|
-
@job.stub(:key).and_return('someimage.jpg')
|
206
|
+
end
|
140
207
|
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
146
|
-
|
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
|
-
|
150
|
-
subject
|
151
|
-
|
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 "
|
156
|
-
|
157
|
-
|
222
|
+
it "destroys the image object" do
|
223
|
+
source_image.should_receive(:destroy!)
|
224
|
+
subject.processed_versions
|
158
225
|
end
|
159
226
|
|
160
|
-
it "
|
161
|
-
|
162
|
-
|
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
|
-
|
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
|
-
|
248
|
-
|
249
|
-
|
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:
|
4
|
+
hash: 29
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
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-
|
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:
|
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: *
|
113
|
+
version_requirements: *id006
|
98
114
|
- !ruby/object:Gem::Dependency
|
99
115
|
name: rspec
|
100
116
|
prerelease: false
|
101
|
-
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: *
|
129
|
+
version_requirements: *id007
|
114
130
|
- !ruby/object:Gem::Dependency
|
115
131
|
name: cucumber
|
116
132
|
prerelease: false
|
117
|
-
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: *
|
145
|
+
version_requirements: *id008
|
130
146
|
- !ruby/object:Gem::Dependency
|
131
147
|
name: i18n
|
132
148
|
prerelease: false
|
133
|
-
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: *
|
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
|
data/spec/spec.opts
DELETED