snapimage 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.autotest +3 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +22 -0
  6. data/README.md +29 -0
  7. data/Rakefile +2 -0
  8. data/bin/snapimage_generate_config +63 -0
  9. data/bin/snapimage_server +55 -0
  10. data/lib/snapimage.rb +24 -0
  11. data/lib/snapimage/config.rb +51 -0
  12. data/lib/snapimage/exceptions.rb +25 -0
  13. data/lib/snapimage/image/image.rb +96 -0
  14. data/lib/snapimage/image/image_name_utils.rb +131 -0
  15. data/lib/snapimage/middleware.rb +27 -0
  16. data/lib/snapimage/rack/request.rb +19 -0
  17. data/lib/snapimage/rack/request_file.rb +26 -0
  18. data/lib/snapimage/rack/response.rb +51 -0
  19. data/lib/snapimage/server.rb +50 -0
  20. data/lib/snapimage/server_actions/server_actions.authorize.rb +69 -0
  21. data/lib/snapimage/server_actions/server_actions.delete_resource_images.rb +23 -0
  22. data/lib/snapimage/server_actions/server_actions.generate_image.rb +167 -0
  23. data/lib/snapimage/server_actions/server_actions.list_resource_images.rb +23 -0
  24. data/lib/snapimage/server_actions/server_actions.sync_resource.rb +78 -0
  25. data/lib/snapimage/storage/storage.rb +120 -0
  26. data/lib/snapimage/storage/storage_server.local.rb +120 -0
  27. data/lib/snapimage/storage/storage_server.rb +110 -0
  28. data/lib/snapimage/version.rb +3 -0
  29. data/snapimage.gemspec +27 -0
  30. data/spec/acceptance/delete_resource_images_spec.rb +166 -0
  31. data/spec/acceptance/list_resource_images_spec.rb +158 -0
  32. data/spec/acceptance/modify_spec.rb +165 -0
  33. data/spec/acceptance/sync_spec.rb +260 -0
  34. data/spec/acceptance/upload_spec.rb +235 -0
  35. data/spec/snapimage/config_spec.rb +56 -0
  36. data/spec/snapimage/image/image_name_utils_spec.rb +127 -0
  37. data/spec/snapimage/image/image_spec.rb +71 -0
  38. data/spec/snapimage/middleware_spec.rb +27 -0
  39. data/spec/snapimage/rack/request_file_spec.rb +15 -0
  40. data/spec/snapimage/rack/request_spec.rb +52 -0
  41. data/spec/snapimage/rack/response_spec.rb +33 -0
  42. data/spec/snapimage/server_actions/server_actions.authorize_spec.rb +67 -0
  43. data/spec/snapimage/server_actions/server_actions.generate_image_spec.rb +146 -0
  44. data/spec/snapimage/server_actions/server_actions.sync_resource_spec.rb +91 -0
  45. data/spec/snapimage/server_spec.rb +55 -0
  46. data/spec/snapimage/storage/assets/local/resource_1/12345678-1x1-0x0x1x1-1x1-1.gif +0 -0
  47. data/spec/snapimage/storage/assets/local/resource_1/12345678-1x1-0x0x1x1-300x200-0.jpg +0 -0
  48. data/spec/snapimage/storage/assets/local/resource_1/12345678-1x1.png +0 -0
  49. data/spec/snapimage/storage/assets/local/resource_2/12345678-1x1-0x0x1x1-1x1-1.gif +0 -0
  50. data/spec/snapimage/storage/assets/local/resource_2/12345678-1x1-0x0x1x1-300x200-0.jpg +0 -0
  51. data/spec/snapimage/storage/assets/local/resource_2/12345678-1x1.png +0 -0
  52. data/spec/snapimage/storage/storage_server.local_spec.rb +150 -0
  53. data/spec/snapimage/storage/storage_server_spec.rb +97 -0
  54. data/spec/snapimage/storage/storage_spec.rb +49 -0
  55. data/spec/spec_helper.rb +18 -0
  56. data/spec/support/assets/config.json +8 -0
  57. data/spec/support/assets/config.yml +9 -0
  58. data/spec/support/assets/stub-1x1.png +0 -0
  59. data/spec/support/assets/stub-2048x100.png +0 -0
  60. data/spec/support/assets/stub-300x200.png +0 -0
  61. metadata +272 -0
@@ -0,0 +1,260 @@
1
+ require "spec_helper"
2
+ require "rack/test"
3
+
4
+ describe "Sync" do
5
+ include Rack::Test::Methods
6
+
7
+ before do
8
+ @local_root = File.join(RSpec.root, "storage")
9
+ @image_path = File.join(RSpec.root, "support/assets/stub-300x200.png")
10
+ @resource_id = "abc/123"
11
+ end
12
+
13
+ after do
14
+ FileUtils.rm_rf(@local_root)
15
+ end
16
+
17
+ context "without security tokens" do
18
+ def app
19
+ app = Proc.new do |env|
20
+ [200, {}, ""]
21
+ end
22
+ SnapImage::Middleware.new(
23
+ app,
24
+ path: "/snapimage_api",
25
+ config: {
26
+ "primary_storage_server" => "local",
27
+ "storage_servers" => [
28
+ {
29
+ "name" => "local",
30
+ "type" => "LOCAL",
31
+ "local_root" => File.join(RSpec.root, "storage"),
32
+ "public_url" => "//example.com/images"
33
+ }
34
+ ]
35
+ }
36
+ )
37
+ end
38
+
39
+ before do
40
+ # Store some images.
41
+ json = { action: "generate_image", resource_identifier: @resource_id }.to_json
42
+
43
+ @before_1 = DateTime.now
44
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => json
45
+ @url_1 = JSON.parse(last_response.body)["image_url"]
46
+
47
+ @before_2 = DateTime.now
48
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => json
49
+ @url_2 = "http:#{JSON.parse(last_response.body)["image_url"]}"
50
+
51
+ @before_3 = DateTime.now
52
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => json
53
+ @url_3 = "https:#{JSON.parse(last_response.body)["image_url"]}"
54
+
55
+ sleep 1
56
+ @before_4 = DateTime.now
57
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => json
58
+ @url_4 = JSON.parse(last_response.body)["image_url"]
59
+ end
60
+
61
+ it "does nothing when all the images are in the content" do
62
+ json = {
63
+ action: "sync_resource",
64
+ content: {
65
+ body: "Some #{@url_1} and another '#{@url_2}'",
66
+ footer: "This is #{@url_3} a footer"
67
+ },
68
+ sync_date_time: (DateTime.now.to_time + 3).to_datetime.iso8601,
69
+ resource_identifier: @resource_id
70
+ }.to_json
71
+ post "/snapimage_api", "json" => json
72
+ last_response.should be_successful
73
+ last_response["Content-Type"].should eq "text/json"
74
+ json = JSON.parse(last_response.body)
75
+ json["status_code"].should eq 200
76
+ json["message"].should eq "Image Sync Successful"
77
+ json["deleted_image_urls"].size.should eq 0
78
+ end
79
+
80
+ it "deletes missing images" do
81
+ # Add modified images.
82
+ json = {
83
+ action: "generate_image",
84
+ url: "http:#{@url_1}",
85
+ resource_identifier: @resource_id,
86
+ crop_x: 10,
87
+ crop_y: 50,
88
+ crop_width: 40,
89
+ crop_height: 60,
90
+ width: 400,
91
+ height: 600,
92
+ sharpen: true
93
+ }.to_json
94
+ post "/snapimage_api", "json" => json
95
+ last_response.should be_successful
96
+ json = JSON.parse(last_response.body)
97
+ json["status_code"].should eq 200
98
+ url_1_modified = json["image_url"]
99
+
100
+ json = {
101
+ action: "generate_image",
102
+ url: @url_2,
103
+ resource_identifier: @resource_id,
104
+ crop_x: 10,
105
+ crop_y: 50,
106
+ crop_width: 40,
107
+ crop_height: 60,
108
+ width: 400,
109
+ height: 600,
110
+ sharpen: true
111
+ }.to_json
112
+ post "/snapimage_api", "json" => json
113
+ last_response.should be_successful
114
+ json = JSON.parse(last_response.body)
115
+ json["status_code"].should eq 200
116
+ url_2_modified = json["image_url"]
117
+
118
+ json = {
119
+ action: "generate_image",
120
+ url: @url_3,
121
+ resource_identifier: @resource_id,
122
+ crop_x: 10,
123
+ crop_y: 50,
124
+ crop_width: 40,
125
+ crop_height: 60,
126
+ width: 400,
127
+ height: 600,
128
+ sharpen: true
129
+ }.to_json
130
+ post "/snapimage_api", "json" => json
131
+ last_response.should be_successful
132
+ json = JSON.parse(last_response.body)
133
+ json["status_code"].should eq 200
134
+ url_3_modified = json["image_url"]
135
+
136
+ # Missing url_1_modified and url_1. (Should delete both)
137
+ # Missing url_2_modified but url_2 is there. (Should delete modified only)
138
+ # url_3_modified is there. (Should not delete either)
139
+ # Missing url_4 which has not been modified (Should delete)
140
+ json = {
141
+ action: "sync_resource",
142
+ content: {
143
+ body: "Some #{@url_2} and another '#{url_3_modified}'"
144
+ },
145
+ sync_date_time: (DateTime.now.to_time + 4).to_datetime.iso8601,
146
+ resource_identifier: @resource_id
147
+ }.to_json
148
+ post "/snapimage_api", "json" => json
149
+ last_response.should be_successful
150
+ last_response["Content-Type"].should eq "text/json"
151
+ json = JSON.parse(last_response.body)
152
+ json["status_code"].should eq 200
153
+ json["message"].should eq "Image Sync Successful"
154
+ json["deleted_image_urls"].size.should eq 4
155
+ json["deleted_image_urls"].include?(@url_1).should be_true
156
+ json["deleted_image_urls"].include?(url_1_modified).should be_true
157
+ json["deleted_image_urls"].include?(url_2_modified).should be_true
158
+ json["deleted_image_urls"].include?(@url_4).should be_true
159
+ end
160
+
161
+ it "does not delete missing images that are modified after the timestamp" do
162
+ # Missing url_1 and url_4. (Deletes url_1 but not url_4)
163
+ json = {
164
+ action: "sync_resource",
165
+ content: {
166
+ body: "Some #{@url_2} and #{@url_3}"
167
+ },
168
+ sync_date_time: (@before_4.to_time + 3).to_datetime.iso8601,
169
+ resource_identifier: @resource_id
170
+ }.to_json
171
+ post "/snapimage_api", "json" => json
172
+ last_response.should be_successful
173
+ last_response["Content-Type"].should eq "text/json"
174
+ json = JSON.parse(last_response.body)
175
+ json["status_code"].should eq 200
176
+ json["message"].should eq "Image Sync Successful"
177
+ json["deleted_image_urls"].size.should eq 1
178
+ json["deleted_image_urls"][0].should eq @url_1
179
+ end
180
+ end
181
+
182
+ context "with security tokens" do
183
+ def app
184
+ app = Proc.new do |env|
185
+ [200, {}, ""]
186
+ end
187
+ SnapImage::Middleware.new(
188
+ app,
189
+ path: "/snapimage_api",
190
+ config: {
191
+ "security_salt" => "123456789",
192
+ "primary_storage_server" => "local",
193
+ "storage_servers" => [
194
+ {
195
+ "name" => "local",
196
+ "type" => "LOCAL",
197
+ "local_root" => File.join(RSpec.root, "storage"),
198
+ "public_url" => "//example.com/images"
199
+ }
200
+ ]
201
+ }
202
+ )
203
+ end
204
+
205
+ before do
206
+ @client_security_token = Digest::SHA1.hexdigest("client:#{Time.now.strftime("%Y-%m-%d")}:123456789:#{@resource_id}")
207
+ @server_security_token = Digest::SHA1.hexdigest("server:#{Time.now.strftime("%Y-%m-%d")}:123456789:#{@resource_id}")
208
+
209
+ # Store some images.
210
+ json = {
211
+ action: "generate_image",
212
+ resource_identifier: @resource_id,
213
+ client_security_token: @client_security_token
214
+ }.to_json
215
+
216
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => json
217
+ @url_1 = JSON.parse(last_response.body)["image_url"]
218
+
219
+ @options = {
220
+ action: "sync_resource",
221
+ content: {
222
+ body: "Some #{@url_1}",
223
+ },
224
+ sync_date_time: (DateTime.now.to_time + 3).to_datetime.iso8601,
225
+ resource_identifier: @resource_id
226
+ }
227
+ end
228
+
229
+ it "requires authorization when no security token is provided" do
230
+ request_json = @options.to_json
231
+ post "/snapimage_api", "json" => request_json
232
+ last_response.should be_successful
233
+ last_response["content-type"].should eq "text/json"
234
+ json = JSON.parse(last_response.body)
235
+ json["status_code"].should eq 401
236
+ json["message"].should eq "Authorization Required"
237
+ end
238
+
239
+ it "fails authorization when the security token is invalid" do
240
+ request_json = @options.merge!({"server_security_token" => "abc"}).to_json
241
+ post "/snapimage_api", "json" => request_json
242
+ last_response.should be_successful
243
+ last_response["content-type"].should eq "text/json"
244
+ json = JSON.parse(last_response.body)
245
+ json["status_code"].should eq 402
246
+ json["message"].should eq "Authorization Failed"
247
+ end
248
+
249
+ it "syncs successfully" do
250
+ json = @options.merge!({"server_security_token" => @server_security_token}).to_json
251
+ post "/snapimage_api", "json" => json
252
+ last_response.should be_successful
253
+ last_response["Content-Type"].should eq "text/json"
254
+ json = JSON.parse(last_response.body)
255
+ json["status_code"].should eq 200
256
+ json["message"].should eq "Image Sync Successful"
257
+ json["deleted_image_urls"].size.should eq 0
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,235 @@
1
+ require "spec_helper"
2
+ require "rack/test"
3
+
4
+ describe "Upload" do
5
+ include Rack::Test::Methods
6
+
7
+ before do
8
+ @local_root = File.join(RSpec.root, "storage")
9
+ @image_path = File.join(RSpec.root, "support/assets/stub-300x200.png")
10
+ @large_image_path = File.join(RSpec.root, "support/assets/stub-2048x100.png")
11
+ @resource_id = "abc/123"
12
+ end
13
+
14
+ after do
15
+ FileUtils.rm_rf(@local_root)
16
+ end
17
+
18
+ context "without security tokens" do
19
+ def app
20
+ app = Proc.new do |env|
21
+ [200, {}, ""]
22
+ end
23
+ SnapImage::Middleware.new(
24
+ app,
25
+ path: "/snapimage_api",
26
+ config: {
27
+ "primary_storage_server" => "local",
28
+ "storage_servers" => [
29
+ {
30
+ "name" => "local",
31
+ "type" => "LOCAL",
32
+ "local_root" => File.join(RSpec.root, "storage"),
33
+ "public_url" => "//example.com/images"
34
+ }
35
+ ]
36
+ }
37
+ )
38
+ end
39
+
40
+ context "upload a file" do
41
+ before do
42
+ json = {
43
+ action: "generate_image",
44
+ resource_identifier: @resource_id,
45
+ response_content_type: "text/html",
46
+ response_template: "json = {{json}}"
47
+ }.to_json
48
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => json
49
+ end
50
+
51
+ it "is successful" do
52
+ last_response.should be_successful
53
+ last_response["Content-Type"].should eq "text/html"
54
+ matches = last_response.body.match(/json = ({.*})/)
55
+ matches.should_not be_nil
56
+ matches.size.should eq 2
57
+ json = JSON.parse(matches[1])
58
+ json["status_code"].should eq 200
59
+ json["message"].should eq "Get Modified Image Successful"
60
+ json["image_url"].should match Regexp.new("^//example.com/images/abc/123/[a-z0-9]{8}-300x200.png$")
61
+ json["image_width"].should eq 300
62
+ json["image_height"].should eq 200
63
+ end
64
+
65
+ it "stores the image" do
66
+ matches = last_response.body.match(/json = ({.*})/)
67
+ json = JSON.parse(matches[1])
68
+ path = File.join(@local_root, @resource_id, File.basename(json["image_url"]))
69
+ File.exist?(path).should be_true
70
+ end
71
+ end
72
+
73
+ context "upload from URL" do
74
+ before do
75
+ json = {
76
+ action: "generate_image",
77
+ url: "http://snapeditor.com/assets/se_logo.png",
78
+ resource_identifier: @resource_id
79
+ }.to_json
80
+ post "/snapimage_api", "json" => json
81
+ end
82
+
83
+ it "is successful" do
84
+ last_response.should be_successful
85
+ last_response["Content-Type"].should eq "text/json"
86
+ json = JSON.parse(last_response.body)
87
+ json["status_code"].should eq 200
88
+ json["message"].should eq "Get Modified Image Successful"
89
+ json["image_url"].should match Regexp.new("^//example.com/images/abc/123/[a-z0-9]{8}-54x41.png$")
90
+ json["image_width"].should eq 54
91
+ json["image_height"].should eq 41
92
+ end
93
+
94
+ it "stores the image" do
95
+ json = JSON.parse(last_response.body)
96
+ path = File.join(@local_root, @resource_id, File.basename(json["image_url"]))
97
+ File.exist?(path).should be_true
98
+ end
99
+ end
100
+
101
+ context "upload too large" do
102
+ before do
103
+ json = { action: "generate_image", resource_identifier: @resource_id }.to_json
104
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@large_image_path, "image/png"), "json" => json
105
+ end
106
+
107
+ it "resizes successfully" do
108
+ last_response.should be_successful
109
+ last_response["Content-Type"].should eq "text/json"
110
+ json = JSON.parse(last_response.body)
111
+ json["status_code"].should eq 200
112
+ json["message"].should eq "Get Modified Image Successful"
113
+ json["image_url"].should match Regexp.new("^//example.com/images/abc/123/[a-z0-9]{8}-1024x50.png$")
114
+ json["image_width"].should eq 1024
115
+ json["image_height"].should eq 50
116
+ end
117
+
118
+ it "stores the image" do
119
+ json = JSON.parse(last_response.body)
120
+ path = File.join(@local_root, @resource_id, File.basename(json["image_url"]))
121
+ File.exist?(path).should be_true
122
+ end
123
+ end
124
+ end
125
+
126
+ context "with security tokens" do
127
+ def app
128
+ app = Proc.new do |env|
129
+ [200, {}, ""]
130
+ end
131
+ SnapImage::Middleware.new(
132
+ app,
133
+ path: "/snapimage_api",
134
+ config: {
135
+ "security_salt" => "123456789",
136
+ "primary_storage_server" => "local",
137
+ "storage_servers" => [
138
+ {
139
+ "name" => "local",
140
+ "type" => "LOCAL",
141
+ "local_root" => File.join(RSpec.root, "storage"),
142
+ "public_url" => "//example.com/images"
143
+ }
144
+ ]
145
+ }
146
+ )
147
+ end
148
+
149
+ before do
150
+ @security_token = Digest::SHA1.hexdigest("client:#{Time.now.strftime("%Y-%m-%d")}:123456789:#{@resource_id}")
151
+ end
152
+
153
+ context "upload a file" do
154
+ before do
155
+ @options = { action: "generate_image", resource_identifier: @resource_id }
156
+ end
157
+
158
+ it "requires authorization when no security token is provided" do
159
+ request_json = @options.to_json
160
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => request_json
161
+ last_response.should be_successful
162
+ last_response["content-type"].should eq "text/json"
163
+ json = JSON.parse(last_response.body)
164
+ json["status_code"].should eq 401
165
+ json["message"].should eq "Authorization Required"
166
+ end
167
+
168
+ it "fails authorization when the security token is invalid" do
169
+ request_json = @options.merge!({"client_security_token" => "abc"}).to_json
170
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => request_json
171
+ last_response.should be_successful
172
+ last_response["content-type"].should eq "text/json"
173
+ json = JSON.parse(last_response.body)
174
+ json["status_code"].should eq 402
175
+ json["message"].should eq "Authorization Failed"
176
+ end
177
+
178
+ it "is successful when the security token is valid" do
179
+ request_json = @options.merge!({"client_security_token" => @security_token}).to_json
180
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => request_json
181
+ last_response.should be_successful
182
+ last_response["content-type"].should eq "text/json"
183
+ json = JSON.parse(last_response.body)
184
+ json["status_code"].should eq 200
185
+ json["message"].should eq "Get Modified Image Successful"
186
+ json["image_url"].should match Regexp.new("^//example.com/images/abc/123/[a-z0-9]{8}-300x200.png$")
187
+ json["image_width"].should eq 300
188
+ json["image_height"].should eq 200
189
+ end
190
+ end
191
+
192
+ context "upload from URL" do
193
+ before do
194
+ @options = {
195
+ action: "generate_image",
196
+ url: "http://snapeditor.com/assets/se_logo.png",
197
+ resource_identifier: @resource_id
198
+ }
199
+ end
200
+
201
+ it "requires authorization when no security token is provided" do
202
+ request_json = @options.to_json
203
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => request_json
204
+ last_response.should be_successful
205
+ last_response["content-type"].should eq "text/json"
206
+ json = JSON.parse(last_response.body)
207
+ json["status_code"].should eq 401
208
+ json["message"].should eq "Authorization Required"
209
+ end
210
+
211
+ it "fails authorization when the security token is invalid" do
212
+ request_json = @options.merge!({"client_security_token" => "abc"}).to_json
213
+ post "/snapimage_api", "file" => Rack::Test::UploadedFile.new(@image_path, "image/png"), "json" => request_json
214
+ last_response.should be_successful
215
+ last_response["content-type"].should eq "text/json"
216
+ json = JSON.parse(last_response.body)
217
+ json["status_code"].should eq 402
218
+ json["message"].should eq "Authorization Failed"
219
+ end
220
+
221
+ it "is successful" do
222
+ request_json = @options.merge!({"client_security_token" => @security_token}).to_json
223
+ post "/snapimage_api", "json" => request_json
224
+ last_response.should be_successful
225
+ last_response["Content-Type"].should eq "text/json"
226
+ json = JSON.parse(last_response.body)
227
+ json["status_code"].should eq 200
228
+ json["message"].should eq "Get Modified Image Successful"
229
+ json["image_url"].should match Regexp.new("^//example.com/images/abc/123/[a-z0-9]{8}-54x41.png$")
230
+ json["image_width"].should eq 54
231
+ json["image_height"].should eq 41
232
+ end
233
+ end
234
+ end
235
+ end