snapimage 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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