discourse/spec/requests/admin/backups_controller_spec.rb
Martin Brennan 0568d36133
FIX: Use dualstack S3 endpoint for direct uploads (#29611)
When we added direct S3 uploads to Discourse, which use
presigned URLs, we never took into account the dualstack
endpoints for IPv6 on S3.

This commit fixes the issue by using the dualstack endpoints
for presigned URLs and requests, which are used in the
get-presigned-put and batch-presign-urls endpoints used when
directly uploading to S3.

It also makes regular S3 requests for `put` and so on use
dualstack URLs. It doesn't seem like there is a downside to
doing this, but a bunch of specs needed to be updated to reflect this.
2024-11-07 11:06:39 +10:00

1009 lines
31 KiB
Ruby

# frozen_string_literal: true
RSpec.describe Admin::BackupsController do
fab!(:admin)
fab!(:moderator)
fab!(:user)
let(:backup_filename) { "2014-02-10-065935.tar.gz" }
let(:backup_filename2) { "2014-02-11-065935.tar.gz" }
def create_backup_files(*filenames)
@paths =
filenames.map do |filename|
path = backup_path(filename)
File.open(path, "w") { |f| f.write("test backup") }
path
end
end
def backup_path(filename)
File.join(BackupRestore::LocalBackupStore.base_directory, filename)
end
def map_preloaded
controller
.instance_variable_get("@preloaded")
.map { |key, value| [key, JSON.parse(value)] }
.to_h
end
before { SiteSetting.backup_location = BackupLocationSiteSetting::LOCAL }
after do
Discourse.redis.flushdb
@paths&.each { |path| File.delete(path) if File.exist?(path) }
@paths = nil
end
describe "#index" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "raises an error when backups are disabled" do
SiteSetting.enable_backups = false
get "/admin/backups.json"
expect(response.status).to eq(403)
end
context "with html format" do
it "preloads important data" do
get "/admin/backups.html"
expect(response.status).to eq(200)
preloaded = map_preloaded
expect(preloaded["operations_status"].symbolize_keys).to eq(
BackupRestore.operations_status,
)
expect(preloaded["logs"].size).to eq(BackupRestore.logs.size)
end
end
context "with json format" do
it "returns a list of all the backups" do
begin
create_backup_files(backup_filename, backup_filename2)
get "/admin/backups.json"
expect(response.status).to eq(200)
filenames = response.parsed_body.map { |backup| backup["filename"] }
expect(filenames).to include(backup_filename)
expect(filenames).to include(backup_filename2)
end
end
end
end
shared_examples "backups inaccessible" do
it "denies access with a 404 response" do
get "/admin/backups.html"
expect(response.status).to eq(404)
get "/admin/backups.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "backups inaccessible"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "backups inaccessible"
end
end
describe "#status" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "returns the current backups status" do
get "/admin/backups/status.json"
expect(response.body).to eq(BackupRestore.operations_status.to_json)
expect(response.status).to eq(200)
end
end
shared_examples "status inaccessible" do
it "denies access with a 404 response" do
get "/admin/backups/status.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "status inaccessible"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "status inaccessible"
end
end
describe "#create" do
context "when logged in as an admin" do
before do
sign_in(admin)
BackupRestore.stubs(:backup!)
end
it "starts a backup" do
BackupRestore.expects(:backup!).with(
admin.id,
{ publish_to_message_bus: true, with_uploads: false, client_id: "foo" },
)
post "/admin/backups.json", params: { with_uploads: false, client_id: "foo" }
expect(response.status).to eq(200)
end
context "with rate limiting enabled" do
before { RateLimiter.enable }
after { RateLimiter.disable }
it "is rate limited" do
post "/admin/backups.json", params: { with_uploads: false, client_id: "foo" }
post "/admin/backups.json", params: { with_uploads: false, client_id: "foo" }
expect(response).to have_http_status :too_many_requests
end
end
end
shared_examples "backups creation not allowed" do
it "prevents backups creation with a 404 response" do
post "/admin/backups.json", params: { with_uploads: false, client_id: "foo" }
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "backups creation not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "backups creation not allowed"
end
end
describe "#show" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "uses send_file to transmit the backup" do
begin
token = EmailBackupToken.set(admin.id)
create_backup_files(backup_filename)
expect do
get "/admin/backups/#{backup_filename}.json", params: { token: token }
end.to change {
UserHistory.where(action: UserHistory.actions[:backup_download]).count
}.by(1)
expect(response.headers["Content-Length"]).to eq("11")
expect(response.headers["Content-Disposition"]).to match(/attachment; filename/)
end
end
it "returns 422 when token is bad" do
begin
get "/admin/backups/#{backup_filename}.json", params: { token: "bad_value" }
expect(response.status).to eq(422)
expect(response.headers["Content-Disposition"]).not_to match(/attachment; filename/)
expect(response.body).to include(I18n.t("download_backup_mailer.no_token"))
end
end
it "returns 404 when the backup does not exist" do
token = EmailBackupToken.set(admin.id)
get "/admin/backups/#{backup_filename}.json", params: { token: token }
expect(response.status).to eq(404)
end
end
shared_examples "backup inaccessible" do
it "denies access with a 404 response" do
begin
token = EmailBackupToken.set(admin.id)
create_backup_files(backup_filename)
expect do
get "/admin/backups/#{backup_filename}.json", params: { token: token }
end.not_to change {
UserHistory.where(action: UserHistory.actions[:backup_download]).count
}
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
expect(response.headers["Content-Disposition"]).not_to match(/attachment; filename/)
end
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "backup inaccessible"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "backup inaccessible"
end
end
describe "#destroy" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "removes the backup if found" do
begin
path = backup_path(backup_filename)
create_backup_files(backup_filename)
expect(File.exist?(path)).to eq(true)
expect do delete "/admin/backups/#{backup_filename}.json" end.to change {
UserHistory.where(action: UserHistory.actions[:backup_destroy]).count
}.by(1)
expect(response.status).to eq(200)
expect(File.exist?(path)).to eq(false)
end
end
it "doesn't remove the backup if not found" do
delete "/admin/backups/#{backup_filename}.json"
expect(response.status).to eq(404)
end
end
shared_examples "backup deletion not allowed" do
it "prevents deletion with a 404 response" do
begin
path = backup_path(backup_filename)
create_backup_files(backup_filename)
expect(File.exist?(path)).to eq(true)
expect do delete "/admin/backups/#{backup_filename}.json" end.not_to change {
UserHistory.where(action: UserHistory.actions[:backup_destroy]).count
}
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
expect(File.exist?(path)).to eq(true)
end
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "backup deletion not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "backup deletion not allowed"
end
end
describe "#logs" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "preloads important data" do
get "/admin/backups/logs.html"
expect(response.status).to eq(200)
preloaded = map_preloaded
expect(preloaded["operations_status"].symbolize_keys).to eq(BackupRestore.operations_status)
expect(preloaded["logs"].size).to eq(BackupRestore.logs.size)
end
end
shared_examples "backup logs inaccessible" do
it "denies access with a 404 response" do
get "/admin/backups/logs.html"
expect(response.status).to eq(404)
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "backup logs inaccessible"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "backup logs inaccessible"
end
end
describe "#restore" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "starts a restore" do
BackupRestore.expects(:restore!).with(
admin.id,
{ filename: backup_filename, publish_to_message_bus: true, client_id: "foo" },
)
post "/admin/backups/#{backup_filename}/restore.json", params: { client_id: "foo" }
expect(response.status).to eq(200)
end
end
shared_examples "backup restoration not allowed" do
it "prevents restoration with a 404 response" do
post "/admin/backups/#{backup_filename}/restore.json", params: { client_id: "foo" }
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "backup restoration not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "backup restoration not allowed"
end
end
describe "#readonly" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "enables readonly mode" do
expect(Discourse.readonly_mode?).to eq(false)
expect { put "/admin/backups/readonly.json", params: { enable: true } }.to change {
UserHistory.where(
action: UserHistory.actions[:change_readonly_mode],
new_value: "t",
).count
}.by(1)
expect(Discourse.readonly_mode?).to eq(true)
expect(response.status).to eq(200)
end
it "disables readonly mode" do
Discourse.enable_readonly_mode(Discourse::USER_READONLY_MODE_KEY)
expect(Discourse.readonly_mode?).to eq(true)
expect { put "/admin/backups/readonly.json", params: { enable: false } }.to change {
UserHistory.where(
action: UserHistory.actions[:change_readonly_mode],
new_value: "f",
).count
}.by(1)
expect(response.status).to eq(200)
expect(Discourse.readonly_mode?).to eq(false)
end
end
shared_examples "enabling readonly mode not allowed" do
it "prevents enabling readonly mode with a 404 response" do
expect(Discourse.readonly_mode?).to eq(false)
expect do put "/admin/backups/readonly.json", params: { enable: true } end.not_to change {
UserHistory.where(
action: UserHistory.actions[:change_readonly_mode],
new_value: "t",
).count
}
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
expect(Discourse.readonly_mode?).to eq(false)
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "enabling readonly mode not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "enabling readonly mode not allowed"
end
end
describe "#upload_backup_chunk" do
context "when logged in as an admin" do
before { sign_in(admin) }
describe "when filename contains invalid characters" do
it "should raise an error" do
["灰色.tar.gz", '; echo \'haha\'.tar.gz'].each do |invalid_filename|
described_class.any_instance.expects(:has_enough_space_on_disk?).returns(true)
post "/admin/backups/upload",
params: {
resumableFilename: invalid_filename,
resumableTotalSize: 1,
resumableIdentifier: "test",
}
expect(response.status).to eq(415)
expect(response.body).to eq(I18n.t("backup.invalid_filename"))
end
end
end
describe "when resumableIdentifier is invalid" do
it "should raise an error" do
filename = "test_site-0123456789.tar.gz"
@paths = [backup_path(File.join("tmp", "test", "#{filename}.part1"))]
post "/admin/backups/upload.json",
params: {
resumableFilename: filename,
resumableTotalSize: 1,
resumableIdentifier: "../test",
resumableChunkNumber: "1",
resumableChunkSize: "1",
resumableCurrentChunkSize: "1",
file: fixture_file_upload(Tempfile.new),
}
expect(response.status).to eq(400)
end
end
describe "when filename is valid" do
it "should upload the file successfully" do
freeze_time
described_class.any_instance.expects(:has_enough_space_on_disk?).returns(true)
filename = "test_Site-0123456789.tar.gz"
post "/admin/backups/upload.json",
params: {
resumableFilename: filename,
resumableTotalSize: 1,
resumableIdentifier: "test",
resumableChunkNumber: "1",
resumableChunkSize: "1",
resumableCurrentChunkSize: "1",
file: fixture_file_upload(Tempfile.new),
}
expect_job_enqueued(
job: :backup_chunks_merger,
args: {
filename: filename,
identifier: "test",
chunks: 1,
},
at: 5.seconds.from_now,
)
expect(response.status).to eq(200)
expect(response.body).to eq("")
end
end
describe "completing an upload by enqueuing backup_chunks_merger" do
let(:filename) { "test_Site-0123456789.tar.gz" }
it "works with a single chunk" do
freeze_time
described_class.any_instance.expects(:has_enough_space_on_disk?).returns(true)
# 2MB file, 2MB chunks = 1x 2MB chunk
post "/admin/backups/upload.json",
params: {
resumableFilename: filename,
resumableTotalSize: "2097152",
resumableIdentifier: "test",
resumableChunkNumber: "1",
resumableChunkSize: "2097152",
resumableCurrentChunkSize: "2097152",
file: fixture_file_upload(Tempfile.new),
}
expect_job_enqueued(
job: :backup_chunks_merger,
args: {
filename: filename,
identifier: "test",
chunks: 1,
},
at: 5.seconds.from_now,
)
end
it "works with multiple chunks when the final chunk is chunk_size + remainder" do
freeze_time
described_class.any_instance.expects(:has_enough_space_on_disk?).twice.returns(true)
# 5MB file, 2MB chunks = 1x 2MB chunk + 1x 3MB chunk with resumable.js
post "/admin/backups/upload.json",
params: {
resumableFilename: filename,
resumableTotalSize: "5242880",
resumableIdentifier: "test",
resumableChunkNumber: "1",
resumableChunkSize: "2097152",
resumableCurrentChunkSize: "2097152",
file: fixture_file_upload(Tempfile.new),
}
post "/admin/backups/upload.json",
params: {
resumableFilename: filename,
resumableTotalSize: "5242880",
resumableIdentifier: "test",
resumableChunkNumber: "2",
resumableChunkSize: "2097152",
resumableCurrentChunkSize: "3145728",
file: fixture_file_upload(Tempfile.new),
}
expect_job_enqueued(
job: :backup_chunks_merger,
args: {
filename: filename,
identifier: "test",
chunks: 2,
},
at: 5.seconds.from_now,
)
end
it "works with multiple chunks when the final chunk is just the remainder" do
freeze_time
described_class.any_instance.expects(:has_enough_space_on_disk?).times(3).returns(true)
# 5MB file, 2MB chunks = 2x 2MB chunk + 1x 1MB chunk with uppy.js
post "/admin/backups/upload.json",
params: {
resumableFilename: filename,
resumableTotalSize: "5242880",
resumableIdentifier: "test",
resumableChunkNumber: "1",
resumableChunkSize: "2097152",
resumableCurrentChunkSize: "2097152",
file: fixture_file_upload(Tempfile.new),
}
post "/admin/backups/upload.json",
params: {
resumableFilename: filename,
resumableTotalSize: "5242880",
resumableIdentifier: "test",
resumableChunkNumber: "2",
resumableChunkSize: "2097152",
resumableCurrentChunkSize: "2097152",
file: fixture_file_upload(Tempfile.new),
}
post "/admin/backups/upload.json",
params: {
resumableFilename: filename,
resumableTotalSize: "5242880",
resumableIdentifier: "test",
resumableChunkNumber: "3",
resumableChunkSize: "2097152",
resumableCurrentChunkSize: "1048576",
file: fixture_file_upload(Tempfile.new),
}
expect_job_enqueued(
job: :backup_chunks_merger,
args: {
filename: filename,
identifier: "test",
chunks: 3,
},
at: 5.seconds.from_now,
)
end
end
end
shared_examples "uploading backup chunk not allowed" do
it "prevents uploading of backup chunk with a 404 response" do
freeze_time
filename = "test_Site-0123456789.tar.gz"
post "/admin/backups/upload.json",
params: {
resumableFilename: filename,
resumableTotalSize: 1,
resumableIdentifier: "test",
resumableChunkNumber: "1",
resumableChunkSize: "1",
resumableCurrentChunkSize: "1",
file: fixture_file_upload(Tempfile.new),
}
expect_not_enqueued_with(
job: :backup_chunks_merger,
args: {
filename: filename,
identifier: "test",
chunks: 1,
},
at: 5.seconds.from_now,
)
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "uploading backup chunk not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "uploading backup chunk not allowed"
end
end
describe "#check_backup_chunk" do
context "when logged in as an admin" do
before { sign_in(admin) }
describe "when resumableIdentifier is invalid" do
it "should raise an error" do
get "/admin/backups/upload",
params: {
resumableidentifier: "../some_file",
resumablefilename: "test_site-0123456789.tar.gz",
resumablechunknumber: "1",
resumablecurrentchunksize: "1",
}
expect(response.status).to eq(400)
end
end
end
shared_examples "checking backup chunk not allowed" do
it "denies access with a 404 response" do
get "/admin/backups/upload",
params: {
resumableidentifier: "../some_file",
resumablefilename: "test_site-0123456789.tar.gz",
resumablechunknumber: "1",
resumablecurrentchunksize: "1",
}
expect(response.status).to eq(404)
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "checking backup chunk not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "checking backup chunk not allowed"
end
end
describe "#rollback" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "should rollback the restore" do
BackupRestore.expects(:rollback!)
post "/admin/backups/rollback.json"
expect(response.status).to eq(200)
end
it "should not allow rollback via a GET request" do
get "/admin/backups/rollback.json"
expect(response.status).to eq(404)
end
end
shared_examples "backup rollback not allowed" do
it "prevents rollbacks with a 404 response" do
post "/admin/backups/rollback.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "backup rollback not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "backup rollback not allowed"
end
end
describe "#cancel" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "should cancel an backup" do
BackupRestore.expects(:cancel!)
delete "/admin/backups/cancel.json"
expect(response.status).to eq(200)
end
it "should not allow cancel via a GET request" do
get "/admin/backups/cancel.json"
expect(response.status).to eq(404)
end
end
shared_examples "backup cancellation not allowed" do
it "prevents cancellation with a 404 response" do
delete "/admin/backups/cancel.json"
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "backup cancellation not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "backup cancellation not allowed"
end
end
describe "#email" do
context "when logged in as an admin" do
before { sign_in(admin) }
it "enqueues email job" do
# might as well test this here if we really want www.example.com
SiteSetting.force_hostname = "www.example.com"
create_backup_files(backup_filename)
expect { put "/admin/backups/#{backup_filename}.json" }.to change {
Jobs::DownloadBackupEmail.jobs.size
}.by(1)
job_args = Jobs::DownloadBackupEmail.jobs.last["args"].first
expect(job_args["user_id"]).to eq(admin.id)
expect(job_args["backup_file_path"]).to eq(
"http://www.example.com/admin/backups/#{backup_filename}",
)
expect(response.status).to eq(200)
end
it "returns 404 when the backup does not exist" do
put "/admin/backups/#{backup_filename}.json"
expect(response).to be_not_found
end
end
shared_examples "backup emails not allowed" do
it "prevents sending backup emails with a 404 response" do
SiteSetting.force_hostname = "www.example.com"
create_backup_files(backup_filename)
expect do put "/admin/backups/#{backup_filename}.json" end.not_to change {
Jobs::DownloadBackupEmail.jobs.size
}
expect(response.status).to eq(404)
expect(response.parsed_body["errors"]).to include(I18n.t("not_found"))
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "backup emails not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "backup emails not allowed"
end
end
describe "S3 multipart uploads" do
let(:upload_type) { "backup" }
let(:test_bucket_prefix) { "test_#{ENV["TEST_ENV_NUMBER"].presence || "0"}" }
let(:backup_file_exists_response) { { status: 404 } }
let(:mock_multipart_upload_id) do
"ibZBv_75gd9r8lH_gqXatLdxMVpAlj6CFTR.OwyF3953YdwbcQnMA2BLGn8Lx12fQNICtMw5KyteFeHw.Sjng--"
end
before do
setup_s3
SiteSetting.enable_direct_s3_uploads = true
SiteSetting.s3_backup_bucket = "s3-backup-bucket"
SiteSetting.backup_location = BackupLocationSiteSetting::S3
stub_request(
:head,
"https://s3-backup-bucket.s3.dualstack.us-west-1.amazonaws.com/",
).to_return(status: 200, body: "", headers: {})
stub_request(
:head,
"https://s3-backup-bucket.s3.dualstack.us-west-1.amazonaws.com/default/test.tar.gz",
).to_return(backup_file_exists_response)
end
shared_examples "multipart uploads not allowed" do
it "prevents multipart uploads with a 404 response" do
post "/admin/backups/create-multipart.json",
params: {
file_name: "test.tar.gz",
upload_type: upload_type,
file_size: 4098,
}
expect(response.status).to eq(404)
end
end
context "when logged in as a moderator" do
before { sign_in(moderator) }
include_examples "multipart uploads not allowed"
end
context "when logged in as a non-staff user" do
before { sign_in(user) }
include_examples "multipart uploads not allowed"
end
context "when the user is admin" do
before { sign_in(admin) }
def stub_create_multipart_backup_request
BackupRestore::S3BackupStore
.any_instance
.stubs(:temporary_upload_path)
.returns(
"temp/default/#{test_bucket_prefix}/28fccf8259bbe75b873a2bd2564b778c/2u98j832nx93272x947823.gz",
)
create_multipart_result = <<~XML
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n
<InitiateMultipartUploadResult>
<Bucket>s3-backup-bucket</Bucket>
<Key>temp/default/#{test_bucket_prefix}/28fccf8259bbe75b873a2bd2564b778c/2u98j832nx93272x947823.gz</Key>
<UploadId>#{mock_multipart_upload_id}</UploadId>
</InitiateMultipartUploadResult>
XML
stub_request(
:post,
"https://s3-backup-bucket.s3.dualstack.us-west-1.amazonaws.com/temp/default/#{test_bucket_prefix}/28fccf8259bbe75b873a2bd2564b778c/2u98j832nx93272x947823.gz?uploads",
).to_return(status: 200, body: create_multipart_result)
end
it "creates the multipart upload" do
stub_create_multipart_backup_request
post "/admin/backups/create-multipart.json",
params: {
file_name: "test.tar.gz",
upload_type: upload_type,
file_size: 4098,
}
expect(response.status).to eq(200)
result = response.parsed_body
external_upload_stub =
ExternalUploadStub.where(
unique_identifier: result["unique_identifier"],
original_filename: "test.tar.gz",
created_by: admin,
upload_type: upload_type,
key: result["key"],
multipart: true,
)
expect(external_upload_stub.exists?).to eq(true)
end
context "when backup of same filename already exists" do
let(:backup_file_exists_response) { { status: 200, body: "" } }
it "throws an error" do
post "/admin/backups/create-multipart.json",
params: {
file_name: "test.tar.gz",
upload_type: upload_type,
file_size: 4098,
}
expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to include(I18n.t("backup.file_exists"))
end
end
context "when filename is invalid" do
it "throws an error" do
post "/admin/backups/create-multipart.json",
params: {
file_name: "blah $$##.tar.gz",
upload_type: upload_type,
file_size: 4098,
}
expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to include(I18n.t("backup.invalid_filename"))
end
end
context "when extension is invalid" do
it "throws an error" do
post "/admin/backups/create-multipart.json",
params: {
file_name: "test.png",
upload_type: upload_type,
file_size: 4098,
}
expect(response.status).to eq(422)
expect(response.parsed_body["errors"]).to include(
I18n.t("backup.backup_file_should_be_tar_gz"),
)
end
end
end
end
end