mirror of
https://github.com/discourse/discourse.git
synced 2024-12-11 10:13:50 +08:00
343 lines
13 KiB
Ruby
343 lines
13 KiB
Ruby
# frozen_string_literal: true
|
|
require "swagger_helper"
|
|
|
|
RSpec.describe "uploads" do
|
|
let(:admin) { Fabricate(:admin) }
|
|
let(:logo_file) { file_from_fixtures("logo.png") }
|
|
let(:logo) { Rack::Test::UploadedFile.new(logo_file) }
|
|
|
|
before do
|
|
Jobs.run_immediately!
|
|
sign_in(admin)
|
|
end
|
|
|
|
path "/uploads.json" do
|
|
post "Creates an upload" do
|
|
tags "Uploads"
|
|
operationId "createUpload"
|
|
consumes "multipart/form-data"
|
|
|
|
expected_request_schema = load_spec_schema("upload_create_request")
|
|
parameter name: :params, in: :body, schema: expected_request_schema
|
|
|
|
let(:params) do
|
|
{ "type" => "avatar", "user_id" => admin.id, "synchronous" => true, "file" => logo }
|
|
end
|
|
|
|
produces "application/json"
|
|
response "200", "file uploaded" do
|
|
expected_response_schema = load_spec_schema("upload_create_response")
|
|
schema(expected_response_schema)
|
|
|
|
# Skipping this test for now until https://github.com/rswag/rswag/issues/348
|
|
# is resolved. This still allows the docs to be generated for this endpoint though.
|
|
xit
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "external and multipart uploads" do
|
|
before do
|
|
setup_s3
|
|
SiteSetting.enable_direct_s3_uploads = true
|
|
end
|
|
|
|
path "/uploads/generate-presigned-put.json" do
|
|
post "Initiates a direct external upload" do
|
|
tags "Uploads"
|
|
operationId "generatePresignedPut"
|
|
consumes "application/json"
|
|
description <<~TEXT
|
|
Direct external uploads bypass the usual method of creating uploads
|
|
via the POST /uploads route, and upload directly to an external provider,
|
|
which by default is S3. This route begins the process, and will return
|
|
a unique identifier for the external upload as well as a presigned URL
|
|
which is where the file binary blob should be uploaded to.
|
|
|
|
Once the upload is complete to the external service, you must call the
|
|
POST /complete-external-upload route using the unique identifier returned
|
|
by this route, which will create any required Upload record in the Discourse
|
|
database and also move file from its temporary location to the final
|
|
destination in the external storage service.
|
|
|
|
#{direct_uploads_disclaimer}
|
|
TEXT
|
|
|
|
expected_request_schema = load_spec_schema("upload_generate_presigned_put_request")
|
|
parameter name: :params, in: :body, schema: expected_request_schema
|
|
|
|
produces "application/json"
|
|
response "200", "external upload initialized" do
|
|
expected_response_schema = load_spec_schema("upload_generate_presigned_put_response")
|
|
schema(expected_response_schema)
|
|
|
|
let(:params) do
|
|
{
|
|
"file_name" => "test.png",
|
|
"type" => "composer",
|
|
"file_size" => 4096,
|
|
"metadata" => {
|
|
"sha1-checksum" => "830869e4ed99128e4352aa72ff5b0ffc26fdc390",
|
|
},
|
|
}
|
|
end
|
|
|
|
it_behaves_like "a JSON endpoint", 200 do
|
|
let(:expected_response_schema) { expected_response_schema }
|
|
let(:expected_request_schema) { expected_request_schema }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
path "/uploads/complete-external-upload.json" do
|
|
post "Completes a direct external upload" do
|
|
let(:unique_identifier) { "66e86218-80d9-4bda-b4d5-2b6def968705" }
|
|
let!(:external_stub) { Fabricate(:external_upload_stub, created_by: admin) }
|
|
let!(:upload) { Fabricate(:upload) }
|
|
|
|
before do
|
|
ExternalUploadManager.any_instance.stubs(:transform!).returns(upload)
|
|
ExternalUploadManager.any_instance.stubs(:destroy!)
|
|
external_stub.update(unique_identifier: unique_identifier)
|
|
end
|
|
|
|
tags "Uploads"
|
|
operationId "completeExternalUpload"
|
|
consumes "application/json"
|
|
description <<~TEXT
|
|
Completes an external upload initialized with /get-presigned-put. The
|
|
file will be moved from its temporary location in external storage to
|
|
a final destination in the S3 bucket. An Upload record will also be
|
|
created in the database in most cases.
|
|
|
|
If a sha1-checksum was provided in the initial request it will also
|
|
be compared with the uploaded file in storage to make sure the same
|
|
file was uploaded. The file size will be compared for the same reason.
|
|
|
|
#{direct_uploads_disclaimer}
|
|
TEXT
|
|
|
|
expected_request_schema = load_spec_schema("upload_complete_external_upload_request")
|
|
parameter name: :params, in: :body, schema: expected_request_schema
|
|
|
|
produces "application/json"
|
|
response "200", "external upload initialized" do
|
|
expected_response_schema = load_spec_schema("upload_create_response")
|
|
schema(expected_response_schema)
|
|
|
|
let(:params) { { "unique_identifier" => unique_identifier } }
|
|
|
|
it_behaves_like "a JSON endpoint", 200 do
|
|
let(:expected_response_schema) { expected_response_schema }
|
|
let(:expected_request_schema) { expected_request_schema }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
path "/uploads/create-multipart.json" do
|
|
post "Creates a multipart external upload" do
|
|
before do
|
|
ExternalUploadManager.stubs(:create_direct_multipart_upload).returns(
|
|
{
|
|
external_upload_identifier: "66e86218-80d9-4bda-b4d5-2b6def968705",
|
|
key: "temp/site/uploads/default/12345/67890.jpg",
|
|
unique_identifier:
|
|
"84x83tmxy398t3y._Q_z8CoJYVr69bE6D7f8J6Oo0434QquLFoYdGVerWFx9X5HDEI_TP_95c34n853495x35345394.d.ghQ",
|
|
},
|
|
)
|
|
end
|
|
|
|
tags "Uploads"
|
|
operationId "createMultipartUpload"
|
|
consumes "application/json"
|
|
description <<~TEXT
|
|
Creates a multipart upload in the external storage provider, storing
|
|
a temporary reference to the external upload similar to /get-presigned-put.
|
|
|
|
#{direct_uploads_disclaimer}
|
|
TEXT
|
|
|
|
expected_request_schema = load_spec_schema("upload_create_multipart_request")
|
|
parameter name: :params, in: :body, schema: expected_request_schema
|
|
|
|
produces "application/json"
|
|
response "200", "external upload initialized" do
|
|
expected_response_schema = load_spec_schema("upload_create_multipart_response")
|
|
schema(expected_response_schema)
|
|
|
|
let(:params) do
|
|
{
|
|
"file_name" => "test.png",
|
|
"upload_type" => "composer",
|
|
"file_size" => 4096,
|
|
"metadata" => {
|
|
"sha1-checksum" => "830869e4ed99128e4352aa72ff5b0ffc26fdc390",
|
|
},
|
|
}
|
|
end
|
|
|
|
it_behaves_like "a JSON endpoint", 200 do
|
|
let(:expected_response_schema) { expected_response_schema }
|
|
let(:expected_request_schema) { expected_request_schema }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
path "/uploads/batch-presign-multipart-parts.json" do
|
|
post "Generates batches of presigned URLs for multipart parts" do
|
|
let(:unique_identifier) { "66e86218-80d9-4bda-b4d5-2b6def968705" }
|
|
let!(:external_stub) { Fabricate(:multipart_external_upload_stub, created_by: admin) }
|
|
let!(:upload) { Fabricate(:upload) }
|
|
|
|
before do
|
|
stub_s3_store
|
|
external_stub.update(unique_identifier: unique_identifier)
|
|
end
|
|
|
|
tags "Uploads"
|
|
operationId "batchPresignMultipartParts"
|
|
consumes "application/json"
|
|
description <<~TEXT
|
|
Multipart uploads are uploaded in chunks or parts to individual presigned
|
|
URLs, similar to the one generated by /generate-presigned-put. The part
|
|
numbers provided must be between 1 and 10000. The total number of parts
|
|
will depend on the chunk size in bytes that you intend to use to upload
|
|
each chunk. For example a 12MB file may have 2 5MB chunks and a final
|
|
2MB chunk, for part numbers 1, 2, and 3.
|
|
|
|
This endpoint will return a presigned URL for each part number provided,
|
|
which you can then use to send PUT requests for the binary chunk corresponding
|
|
to that part. When the part is uploaded, the provider should return an
|
|
ETag for the part, and this should be stored along with the part number,
|
|
because this is needed to complete the multipart upload.
|
|
|
|
#{direct_uploads_disclaimer}
|
|
TEXT
|
|
|
|
expected_request_schema = load_spec_schema("upload_batch_presign_multipart_parts_request")
|
|
parameter name: :params, in: :body, schema: expected_request_schema
|
|
|
|
produces "application/json"
|
|
response "200", "external upload initialized" do
|
|
expected_response_schema =
|
|
load_spec_schema("upload_batch_presign_multipart_parts_response")
|
|
schema(expected_response_schema)
|
|
|
|
let(:params) do
|
|
{
|
|
"part_numbers" => [1, 2, 3],
|
|
"unique_identifier" => "66e86218-80d9-4bda-b4d5-2b6def968705",
|
|
}
|
|
end
|
|
|
|
it_behaves_like "a JSON endpoint", 200 do
|
|
let(:expected_response_schema) { expected_response_schema }
|
|
let(:expected_request_schema) { expected_request_schema }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
path "/uploads/abort-multipart.json" do
|
|
post "Abort multipart upload" do
|
|
let(:unique_identifier) { "66e86218-80d9-4bda-b4d5-2b6def968705" }
|
|
let!(:external_stub) { Fabricate(:multipart_external_upload_stub, created_by: admin) }
|
|
let!(:upload) { Fabricate(:upload) }
|
|
|
|
before do
|
|
stub_s3_store
|
|
external_stub.update(
|
|
unique_identifier: unique_identifier,
|
|
external_upload_identifier:
|
|
"84x83tmxy398t3y._Q_z8CoJYVr69bE6D7f8J6Oo0434QquLFoYdGVerWFx9X5HDEI_TP_95c34n853495x35345394.d.ghQ",
|
|
)
|
|
end
|
|
|
|
tags "Uploads"
|
|
operationId "abortMultipart"
|
|
consumes "application/json"
|
|
description <<~TEXT
|
|
This endpoint aborts the multipart upload initiated with /create-multipart.
|
|
This should be used when cancelling the upload. It does not matter if parts
|
|
were already uploaded into the external storage provider.
|
|
|
|
#{direct_uploads_disclaimer}
|
|
TEXT
|
|
|
|
expected_request_schema = load_spec_schema("upload_abort_multipart_request")
|
|
parameter name: :params, in: :body, schema: expected_request_schema
|
|
|
|
produces "application/json"
|
|
response "200", "external upload initialized" do
|
|
expected_response_schema = load_spec_schema("success_ok_response")
|
|
schema(expected_response_schema)
|
|
|
|
let(:params) do
|
|
{
|
|
"external_upload_identifier" =>
|
|
"84x83tmxy398t3y._Q_z8CoJYVr69bE6D7f8J6Oo0434QquLFoYdGVerWFx9X5HDEI_TP_95c34n853495x35345394.d.ghQ",
|
|
}
|
|
end
|
|
|
|
it_behaves_like "a JSON endpoint", 200 do
|
|
let(:expected_response_schema) { expected_response_schema }
|
|
let(:expected_request_schema) { expected_request_schema }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
path "/uploads/complete-multipart.json" do
|
|
post "Complete multipart upload" do
|
|
let(:unique_identifier) { "66e86218-80d9-4bda-b4d5-2b6def968705" }
|
|
let!(:external_stub) { Fabricate(:multipart_external_upload_stub, created_by: admin) }
|
|
let!(:upload) { Fabricate(:upload) }
|
|
|
|
before do
|
|
ExternalUploadManager.any_instance.stubs(:transform!).returns(upload)
|
|
ExternalUploadManager.any_instance.stubs(:destroy!)
|
|
stub_s3_store
|
|
external_stub.update(unique_identifier: unique_identifier)
|
|
end
|
|
|
|
tags "Uploads"
|
|
operationId "completeMultipart"
|
|
consumes "application/json"
|
|
description <<~TEXT
|
|
Completes the multipart upload in the external store, and copies the
|
|
file from its temporary location to its final location in the store.
|
|
All of the parts must have been uploaded to the external storage provider.
|
|
An Upload record will be completed in most cases once the file is copied
|
|
to its final location.
|
|
|
|
#{direct_uploads_disclaimer}
|
|
TEXT
|
|
|
|
expected_request_schema = load_spec_schema("upload_complete_multipart_request")
|
|
parameter name: :params, in: :body, schema: expected_request_schema
|
|
|
|
produces "application/json"
|
|
response "200", "external upload initialized" do
|
|
expected_response_schema = load_spec_schema("upload_create_response")
|
|
schema(expected_response_schema)
|
|
|
|
let(:params) do
|
|
{
|
|
"unique_identifier" => unique_identifier,
|
|
"parts" => [{ "part_number" => 1, "etag" => "0c376dcfcc2606f4335bbc732de93344" }],
|
|
}
|
|
end
|
|
|
|
it_behaves_like "a JSON endpoint", 200 do
|
|
let(:expected_response_schema) { expected_response_schema }
|
|
let(:expected_request_schema) { expected_request_schema }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|