mongoid-direct-s3-upload 0.1.0
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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +250 -0
- data/Rakefile +31 -0
- data/app/assets/javascripts/s3_relay.coffee +118 -0
- data/app/assets/stylesheets/s3_relay.css +31 -0
- data/app/controllers/s3_relay/uploads_controller.rb +71 -0
- data/app/helpers/s3_relay/uploads_helper.rb +24 -0
- data/app/models/s3_relay/upload.rb +73 -0
- data/config/routes.rb +5 -0
- data/lib/s3_relay.rb +4 -0
- data/lib/s3_relay/base.rb +35 -0
- data/lib/s3_relay/engine.rb +16 -0
- data/lib/s3_relay/model.rb +51 -0
- data/lib/s3_relay/private_url.rb +37 -0
- data/lib/s3_relay/s3_relay.rb +4 -0
- data/lib/s3_relay/upload_presigner.rb +61 -0
- data/lib/s3_relay/version.rb +3 -0
- data/test/controllers/s3_relay/uploads_controller_test.rb +144 -0
- data/test/dummy/README.md +24 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/config/manifest.js +3 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/javascripts/cable.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/channels/application_cable/channel.rb +4 -0
- data/test/dummy/app/channels/application_cable/connection.rb +4 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/jobs/application_job.rb +2 -0
- data/test/dummy/app/mailers/application_mailer.rb +4 -0
- data/test/dummy/app/models/application_record.rb +3 -0
- data/test/dummy/app/models/product.rb +6 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +9 -0
- data/test/dummy/bin/rake +9 -0
- data/test/dummy/bin/setup +38 -0
- data/test/dummy/bin/spring +17 -0
- data/test/dummy/bin/update +29 -0
- data/test/dummy/bin/yarn +11 -0
- data/test/dummy/config.ru +5 -0
- data/test/dummy/config/application.rb +19 -0
- data/test/dummy/config/boot.rb +3 -0
- data/test/dummy/config/cable.yml +10 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +54 -0
- data/test/dummy/config/environments/production.rb +91 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
- data/test/dummy/config/initializers/assets.rb +14 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +33 -0
- data/test/dummy/config/puma.rb +56 -0
- data/test/dummy/config/routes.rb +7 -0
- data/test/dummy/config/secrets.yml +32 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/db/migrate/20141021002149_create_products.rb +9 -0
- data/test/dummy/db/schema.rb +41 -0
- data/test/dummy/db/seeds.rb +7 -0
- data/test/dummy/package.json +5 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/test/dummy/public/apple-touch-icon.png +0 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/robots.txt +1 -0
- data/test/dummy/test/application_system_test_case.rb +5 -0
- data/test/dummy/test/test_helper.rb +9 -0
- data/test/factories/products.rb +5 -0
- data/test/factories/uploads.rb +23 -0
- data/test/helpers/s3_relay/uploads_helper_test.rb +29 -0
- data/test/lib/s3_relay/model_test.rb +66 -0
- data/test/lib/s3_relay/private_url_test.rb +28 -0
- data/test/lib/s3_relay/upload_presigner_test.rb +38 -0
- data/test/models/s3_relay/upload_test.rb +142 -0
- data/test/support/database_cleaner.rb +14 -0
- data/test/test_helper.rb +29 -0
- metadata +356 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
class S3Relay::UploadsController < ApplicationController
|
2
|
+
|
3
|
+
before_action :authenticate
|
4
|
+
skip_before_action :verify_authenticity_token
|
5
|
+
|
6
|
+
def new
|
7
|
+
render json: S3Relay::UploadPresigner.new.form_data
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
@upload = S3Relay::Upload.new(upload_attrs)
|
12
|
+
|
13
|
+
if @upload.save
|
14
|
+
data = {
|
15
|
+
private_url: @upload.private_url,
|
16
|
+
parent_type: @upload.parent_type,
|
17
|
+
parent_id: @upload.parent_id,
|
18
|
+
user_id: user_attrs[:user_id]
|
19
|
+
}
|
20
|
+
render json: data, status: 201
|
21
|
+
else
|
22
|
+
render json: { errors: @upload.errors }, status: 422
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def authenticate
|
29
|
+
if respond_to?(:authenticate_for_s3_relay)
|
30
|
+
authenticate_for_s3_relay
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def parent_attrs
|
35
|
+
type = params[:parent_type].try(:classify)
|
36
|
+
id = params[:parent_id]
|
37
|
+
|
38
|
+
return {} unless type.present? && id.present? &&
|
39
|
+
parent = type.constantize.find(id)
|
40
|
+
|
41
|
+
begin
|
42
|
+
public_send(
|
43
|
+
"#{type.underscore.downcase}_#{params[:association]}_params",
|
44
|
+
parent
|
45
|
+
)
|
46
|
+
rescue NoMethodError
|
47
|
+
{ parent: parent }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def upload_attrs
|
52
|
+
attrs = {
|
53
|
+
upload_type: params[:association].try(:classify),
|
54
|
+
uuid: params[:uuid],
|
55
|
+
filename: params[:filename],
|
56
|
+
content_type: params[:content_type]
|
57
|
+
}
|
58
|
+
|
59
|
+
attrs.merge!(parent_attrs)
|
60
|
+
attrs.merge!(user_attrs)
|
61
|
+
end
|
62
|
+
|
63
|
+
def user_attrs
|
64
|
+
if respond_to?(:current_user) && (id = current_user&.id)
|
65
|
+
{ user_id: id }
|
66
|
+
else
|
67
|
+
{}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module S3Relay
|
2
|
+
module UploadsHelper
|
3
|
+
|
4
|
+
def s3_relay_field(parent, association, opts={})
|
5
|
+
file_field = file_field_tag(:file, opts.merge(class: "s3r-field"))
|
6
|
+
progress_table = content_tag(:table, "", class: "s3r-upload-list")
|
7
|
+
content = [file_field, progress_table].join
|
8
|
+
parent_type = parent.class.to_s.underscore.downcase
|
9
|
+
|
10
|
+
content_tag(:div, raw(content),
|
11
|
+
{
|
12
|
+
class: "s3r-container",
|
13
|
+
data: {
|
14
|
+
parent_type: parent_type,
|
15
|
+
parent_id: parent.id.to_s,
|
16
|
+
association: association.to_s,
|
17
|
+
disposition: opts.fetch(:disposition, "inline")
|
18
|
+
}
|
19
|
+
}
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module S3Relay
|
2
|
+
class Upload
|
3
|
+
|
4
|
+
include Mongoid::Document
|
5
|
+
|
6
|
+
field :uuid
|
7
|
+
field :user_id, type: Integer
|
8
|
+
field :parent_type, type: String
|
9
|
+
field :parent_id, type: Integer
|
10
|
+
field :upload_type, type: String
|
11
|
+
field :filename, type: String
|
12
|
+
field :content_type, type: String
|
13
|
+
field :state, type: String
|
14
|
+
field :data, type: Hash, default: Hash.new
|
15
|
+
field :pending_at, type: DateTime
|
16
|
+
field :imported_at, type: DateTime
|
17
|
+
|
18
|
+
belongs_to :parent, polymorphic: true, optional: true
|
19
|
+
|
20
|
+
validates :uuid, presence: true, uniqueness: true
|
21
|
+
validates :upload_type, presence: true
|
22
|
+
validates :filename, presence: true
|
23
|
+
validates :content_type, presence: true
|
24
|
+
validates :pending_at, presence: true
|
25
|
+
|
26
|
+
after_initialize :finalize
|
27
|
+
after_create :notify_parent
|
28
|
+
|
29
|
+
def self.pending
|
30
|
+
where(state: "pending")
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.imported
|
34
|
+
where(state: "imported")
|
35
|
+
end
|
36
|
+
|
37
|
+
def pending?
|
38
|
+
state == "pending"
|
39
|
+
end
|
40
|
+
|
41
|
+
def imported?
|
42
|
+
state == "imported"
|
43
|
+
end
|
44
|
+
|
45
|
+
def mark_imported!
|
46
|
+
update_attributes(state: "imported", imported_at: Time.now)
|
47
|
+
end
|
48
|
+
|
49
|
+
def notify_parent
|
50
|
+
return unless parent.present?
|
51
|
+
|
52
|
+
if parent.respond_to?(:import_upload)
|
53
|
+
parent.import_upload(id)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def public_url
|
58
|
+
S3Relay::PrivateUrl.new(uuid, filename).public_url
|
59
|
+
end
|
60
|
+
|
61
|
+
def private_url
|
62
|
+
S3Relay::PrivateUrl.new(uuid, filename).generate
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def finalize
|
68
|
+
self.state ||= "pending"
|
69
|
+
self.pending_at ||= Time.now
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
data/config/routes.rb
ADDED
data/lib/s3_relay.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module S3Relay
|
2
|
+
class Base
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def access_key_id
|
7
|
+
ENV["S3_RELAY_ACCESS_KEY_ID"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def secret_access_key
|
11
|
+
ENV["S3_RELAY_SECRET_ACCESS_KEY"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def region
|
15
|
+
ENV["S3_RELAY_REGION"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def bucket
|
19
|
+
ENV["S3_RELAY_BUCKET"]
|
20
|
+
end
|
21
|
+
|
22
|
+
def acl
|
23
|
+
ENV["S3_RELAY_ACL"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def endpoint
|
27
|
+
"https://#{bucket}.s3-#{region}.amazonaws.com"
|
28
|
+
end
|
29
|
+
|
30
|
+
def digest
|
31
|
+
OpenSSL::Digest.new("sha1")
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module S3Relay
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace S3Relay
|
4
|
+
|
5
|
+
initializer "s3_relay.action_controller" do |app|
|
6
|
+
ActiveSupport.on_load :action_controller do
|
7
|
+
helper S3Relay::UploadsHelper
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
require "s3_relay/base"
|
14
|
+
require "s3_relay/model"
|
15
|
+
require "s3_relay/private_url"
|
16
|
+
require "s3_relay/upload_presigner"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module S3Relay
|
2
|
+
module Model
|
3
|
+
|
4
|
+
def s3_relay(attribute, has_many=false)
|
5
|
+
upload_type = attribute.to_s.classify
|
6
|
+
|
7
|
+
if has_many
|
8
|
+
has_many attribute, as: :parent, class_name: "S3Relay::Upload"
|
9
|
+
|
10
|
+
define_method attribute do
|
11
|
+
S3Relay::Upload
|
12
|
+
.where(
|
13
|
+
parent_type: self.class.to_s,
|
14
|
+
parent_id: self.id,
|
15
|
+
upload_type: upload_type
|
16
|
+
)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
has_one attribute, as: :parent, class_name: "S3Relay::Upload"
|
20
|
+
|
21
|
+
define_method attribute do
|
22
|
+
S3Relay::Upload
|
23
|
+
.where(
|
24
|
+
parent_type: self.class.to_s,
|
25
|
+
parent_id: self.id,
|
26
|
+
upload_type: upload_type
|
27
|
+
)
|
28
|
+
.order(:pending_at => 'desc').last
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
virtual_attribute = "new_#{attribute}_uuids"
|
33
|
+
attr_accessor virtual_attribute
|
34
|
+
|
35
|
+
association_method = "associate_#{attribute}"
|
36
|
+
|
37
|
+
after_save association_method.to_sym
|
38
|
+
|
39
|
+
define_method association_method do
|
40
|
+
new_uuids = send(virtual_attribute)
|
41
|
+
return if new_uuids.blank?
|
42
|
+
|
43
|
+
S3Relay::Upload.where(uuid: new_uuids, upload_type: upload_type)
|
44
|
+
.update_all(parent_type: self.class.to_s, parent_id: self.id)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module S3Relay
|
2
|
+
class PrivateUrl < S3Relay::Base
|
3
|
+
|
4
|
+
attr_reader :expires, :path
|
5
|
+
|
6
|
+
def initialize(uuid, file, options={})
|
7
|
+
filename = Addressable::URI.escape(file).gsub("+", "%2B")
|
8
|
+
@path = [uuid, filename].join("/")
|
9
|
+
@expires = (options[:expires] || 10.minutes.from_now).to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate
|
13
|
+
"#{public_url}?#{params}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def public_url
|
17
|
+
"#{endpoint}/#{path}"
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def params
|
23
|
+
[
|
24
|
+
"AWSAccessKeyId=#{access_key_id}",
|
25
|
+
"Expires=#{expires}",
|
26
|
+
"Signature=#{signature}"
|
27
|
+
].join("&")
|
28
|
+
end
|
29
|
+
|
30
|
+
def signature
|
31
|
+
string = "GET\n\n\n#{expires}\n/#{bucket}/#{path}"
|
32
|
+
hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string)
|
33
|
+
CGI.escape(Base64.encode64(hmac).strip)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module S3Relay
|
2
|
+
class UploadPresigner < S3Relay::Base
|
3
|
+
|
4
|
+
attr_reader :expires, :uuid
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
@expires = (options[:expires] || 1.minute.from_now).utc.xmlschema
|
8
|
+
@uuid = SecureRandom.uuid
|
9
|
+
end
|
10
|
+
|
11
|
+
def form_data
|
12
|
+
fields.keys.inject({}) { |h,k| h[k.downcase.underscore] = fields[k]; h }
|
13
|
+
.merge(
|
14
|
+
"endpoint" => endpoint,
|
15
|
+
"policy" => encoded_policy,
|
16
|
+
"signature" => signature,
|
17
|
+
"uuid" => uuid
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def fields
|
24
|
+
{
|
25
|
+
"AWSAccessKeyID" => access_key_id,
|
26
|
+
"x-amz-server-side-encryption" => "AES256",
|
27
|
+
"key" => "#{uuid}/${filename}",
|
28
|
+
"success_action_status" => "201",
|
29
|
+
"acl" => acl
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def hmac
|
34
|
+
lambda { |data| OpenSSL::HMAC.digest(digest, secret_access_key, data) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def policy_document
|
38
|
+
{
|
39
|
+
"expiration" => expires,
|
40
|
+
"conditions" => [
|
41
|
+
{ "bucket" => bucket },
|
42
|
+
{ "acl" => acl },
|
43
|
+
{ "x-amz-server-side-encryption" => "AES256" },
|
44
|
+
{ "success_action_status" => "201" },
|
45
|
+
["starts-with", "$content-type", ""],
|
46
|
+
["starts-with", "$content-disposition", ""],
|
47
|
+
["starts-with", "$key", "#{uuid}/"]
|
48
|
+
]
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def encoded_policy
|
53
|
+
Base64.strict_encode64(policy_document.to_json)
|
54
|
+
end
|
55
|
+
|
56
|
+
def signature
|
57
|
+
Base64.strict_encode64(hmac[encoded_policy])
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
module S3Relay
|
4
|
+
describe UploadsController do
|
5
|
+
before do
|
6
|
+
S3Relay::Base.any_instance.stubs(:access_key_id)
|
7
|
+
.returns("access-key-id")
|
8
|
+
S3Relay::Base.any_instance.stubs(:secret_access_key)
|
9
|
+
.returns("secret-access-key")
|
10
|
+
S3Relay::Base.any_instance.stubs(:region)
|
11
|
+
.returns("region")
|
12
|
+
S3Relay::Base.any_instance.stubs(:bucket)
|
13
|
+
.returns("bucket")
|
14
|
+
S3Relay::Base.any_instance.stubs(:acl)
|
15
|
+
.returns("acl")
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "GET new" do
|
19
|
+
it do
|
20
|
+
uuid = "123-456-789"
|
21
|
+
time = Time.parse("2014-12-01 12:00am")
|
22
|
+
S3Relay::UploadPresigner.any_instance.stubs(:uuid).returns(uuid)
|
23
|
+
S3Relay::UploadPresigner.any_instance.stubs(:expires).returns(time)
|
24
|
+
|
25
|
+
get new_s3_relay_upload_url
|
26
|
+
assert_response 200
|
27
|
+
|
28
|
+
data = JSON.parse(response.body)
|
29
|
+
|
30
|
+
data["awsaccesskeyid"].must_equal "access-key-id"
|
31
|
+
data["x_amz_server_side_encryption"].must_equal "AES256"
|
32
|
+
data["key"].must_equal "#{uuid}/${filename}"
|
33
|
+
data["success_action_status"].must_equal "201"
|
34
|
+
data["acl"].must_equal "acl"
|
35
|
+
data["endpoint"].must_equal "https://bucket.s3-region.amazonaws.com"
|
36
|
+
data["policy"].length.must_equal 380 # TODO: Improve this
|
37
|
+
data["signature"].length.must_equal 28 # TODO: Improve this
|
38
|
+
data["uuid"].must_equal uuid
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "POST create" do
|
43
|
+
|
44
|
+
describe "success" do
|
45
|
+
it do
|
46
|
+
assert_difference "S3Relay::Upload.count", 1 do
|
47
|
+
post s3_relay_uploads_url, params: {
|
48
|
+
association: "photo_uploads",
|
49
|
+
uuid: SecureRandom.uuid,
|
50
|
+
filename: "cat.png",
|
51
|
+
content_type: "image/png"
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
assert_response 201
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "with parent attributes" do
|
59
|
+
describe "matching an object" do
|
60
|
+
before { @product = FactoryGirl.create(:product) }
|
61
|
+
|
62
|
+
it do
|
63
|
+
assert_difference "@product.photo_uploads.count", 1 do
|
64
|
+
post s3_relay_uploads_url, params: {
|
65
|
+
association: "photo_uploads",
|
66
|
+
uuid: SecureRandom.uuid,
|
67
|
+
filename: "cat.png",
|
68
|
+
content_type: "image/png",
|
69
|
+
parent_type: @product.class.to_s,
|
70
|
+
parent_id: @product.id.to_s
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
assert_response 201
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "not matching an object" do
|
79
|
+
it do
|
80
|
+
assert_difference "S3Relay::Upload.count" do
|
81
|
+
post s3_relay_uploads_url, params: {
|
82
|
+
association: "photo_uploads",
|
83
|
+
uuid: SecureRandom.uuid,
|
84
|
+
filename: "cat.png",
|
85
|
+
content_type: "image/png",
|
86
|
+
parent_type: "Product",
|
87
|
+
parent_id: "10000000"
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
assert_response 201
|
92
|
+
body = JSON.parse(response.body)
|
93
|
+
|
94
|
+
assert body["parent_type"] == nil
|
95
|
+
assert body["parent_id"] == nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "with a current_user" do
|
100
|
+
before do
|
101
|
+
@user = OpenStruct.new(id: 123)
|
102
|
+
UploadsController.any_instance.stubs(:current_user).returns(@user)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "associates the upload with the user" do
|
106
|
+
assert_difference "S3Relay::Upload.count", 1 do
|
107
|
+
post s3_relay_uploads_url, params: {
|
108
|
+
association: "photo_uploads",
|
109
|
+
uuid: SecureRandom.uuid,
|
110
|
+
filename: "cat.png",
|
111
|
+
content_type: "image/png"
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
assert_response 201
|
116
|
+
body = JSON.parse(response.body)
|
117
|
+
body["user_id"].must_equal @user.id
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "error" do
|
125
|
+
it do
|
126
|
+
assert_no_difference "S3Relay::Upload.count" do
|
127
|
+
post s3_relay_uploads_url, params: {
|
128
|
+
uuid: SecureRandom.uuid,
|
129
|
+
filename: "cat.png",
|
130
|
+
content_type: "image/png"
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
assert_response 422
|
135
|
+
|
136
|
+
JSON.parse(response.body)["errors"]["upload_type"]
|
137
|
+
.must_include "can't be blank"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|