diff --git a/lib/cadet/assets/assets.ex b/lib/cadet/assets/assets.ex
index dd1076812..379726b35 100644
--- a/lib/cadet/assets/assets.ex
+++ b/lib/cadet/assets/assets.ex
@@ -9,18 +9,18 @@ defmodule Cadet.Assets.Assets do
if(Mix.env() == :test, do: ["testFolder"], else: [])
@accepted_file_types ~w(.jpg .jpeg .gif .png .wav .mp3 .txt)
- def upload_to_s3(upload_params, course_id, folder_name, file_name) do
+ def upload_to_s3(upload_params, prefix, folder_name, file_name) do
file_type = Path.extname(file_name)
with :ok <- validate_file_name(file_name),
:ok <- validate_folder_name(folder_name),
:ok <- validate_file_type(file_type) do
- if object_exists?(course_id, folder_name, file_name) do
+ if object_exists?(prefix, folder_name, file_name) do
{:error, {:bad_request, "File already exists"}}
else
file = upload_params.path
- s3_path = "#{prefix()}#{course_id}/#{folder_name}/#{file_name}"
+ s3_path = "#{prefix}#{folder_name}/#{file_name}"
file
|> Upload.stream_file()
@@ -32,25 +32,29 @@ defmodule Cadet.Assets.Assets do
end
end
- def list_assets(course_id, folder_name) do
+ def list_assets(prefix, folder_name) do
+ prefix_len = byte_size(prefix)
+
case validate_folder_name(folder_name) do
:ok ->
bucket()
- |> S3.list_objects(prefix: "#{prefix()}#{course_id}/" <> folder_name <> "/")
+ |> S3.list_objects(prefix: prefix <> folder_name <> "/")
|> ExAws.stream!()
- |> Enum.map(fn file -> file.key end)
+ |> Enum.map(fn file ->
+ binary_part(file.key, prefix_len, byte_size(file.key) - prefix_len)
+ end)
{:error, _} = error ->
error
end
end
- def delete_object(course_id, folder_name, file_name) do
+ def delete_object(prefix, folder_name, file_name) do
with :ok <- validate_file_name(file_name),
:ok <- validate_folder_name(folder_name) do
- if object_exists?(course_id, folder_name, file_name) do
+ if object_exists?(prefix, folder_name, file_name) do
bucket()
- |> S3.delete_object("#{prefix()}#{course_id}/#{folder_name}/#{file_name}")
+ |> S3.delete_object("#{prefix}#{folder_name}/#{file_name}")
|> ExAws.request!()
:ok
@@ -60,11 +64,11 @@ defmodule Cadet.Assets.Assets do
end
end
- @spec object_exists?(integer(), binary, binary) :: boolean()
- def object_exists?(course_id, folder_name, file_name) do
+ @spec object_exists?(integer() | binary(), binary, binary) :: boolean()
+ def object_exists?(prefix, folder_name, file_name) do
response =
bucket()
- |> S3.head_object("#{prefix()}#{course_id}/#{folder_name}/#{file_name}")
+ |> S3.head_object("#{prefix}#{folder_name}/#{file_name}")
|> ExAws.request()
case response do
@@ -99,5 +103,6 @@ defmodule Cadet.Assets.Assets do
defp bucket, do: :cadet |> Application.fetch_env!(:uploader) |> Keyword.get(:assets_bucket)
- defp prefix, do: :cadet |> Application.fetch_env!(:uploader) |> Keyword.get(:assets_prefix, "")
+ def assets_prefix,
+ do: :cadet |> Application.fetch_env!(:uploader) |> Keyword.get(:assets_prefix, "")
end
diff --git a/lib/cadet/courses/course.ex b/lib/cadet/courses/course.ex
index e87a62d78..c224c875d 100644
--- a/lib/cadet/courses/course.ex
+++ b/lib/cadet/courses/course.ex
@@ -17,6 +17,9 @@ defmodule Cadet.Courses.Course do
field(:source_variant, :string)
field(:module_help_text, :string)
+ # for now, only settable from database
+ field(:assets_prefix, :string, default: nil)
+
has_many(:assessment_config, AssessmentConfig)
timestamps()
diff --git a/lib/cadet/courses/courses.ex b/lib/cadet/courses/courses.ex
index 431cb4212..1a5f236dd 100644
--- a/lib/cadet/courses/courses.ex
+++ b/lib/cadet/courses/courses.ex
@@ -20,6 +20,7 @@ defmodule Cadet.Courses do
alias Cadet.Assessments
alias Cadet.Assessments.Assessment
+ alias Cadet.Assets.Assets
@doc """
Creates a new course configuration, course registration, and sets
@@ -408,4 +409,9 @@ defmodule Cadet.Courses do
|> Repo.all()
|> Repo.preload(:uploader)
end
+
+ @spec assets_prefix(%Course{}) :: binary()
+ def assets_prefix(course) do
+ course.assets_prefix || "#{Assets.assets_prefix()}#{course.id}/"
+ end
end
diff --git a/lib/cadet_web/admin_controllers/admin_assets_controller.ex b/lib/cadet_web/admin_controllers/admin_assets_controller.ex
index d2eec9ec5..8fd2f7003 100644
--- a/lib/cadet_web/admin_controllers/admin_assets_controller.ex
+++ b/lib/cadet_web/admin_controllers/admin_assets_controller.ex
@@ -2,12 +2,14 @@ defmodule CadetWeb.AdminAssetsController do
use CadetWeb, :controller
use PhoenixSwagger
+
alias Cadet.Assets.Assets
+ alias Cadet.Courses
def index(conn, _params = %{"foldername" => foldername}) do
course_reg = conn.assigns.course_reg
- case Assets.list_assets(course_reg.course_id, foldername) do
+ case Assets.list_assets(Courses.assets_prefix(course_reg.course), foldername) do
{:error, {status, message}} -> conn |> put_status(status) |> text(message)
assets -> render(conn, "index.json", assets: assets)
end
@@ -18,7 +20,7 @@ defmodule CadetWeb.AdminAssetsController do
course_reg = conn.assigns.course_reg
filename = Enum.join(filename, "/")
- case Assets.delete_object(course_reg.course_id, foldername, filename) do
+ case Assets.delete_object(Courses.assets_prefix(course_reg.course), foldername, filename) do
{:error, {status, message}} -> conn |> put_status(status) |> text(message)
_ -> conn |> put_status(204) |> text('')
end
@@ -32,7 +34,12 @@ defmodule CadetWeb.AdminAssetsController do
course_reg = conn.assigns.course_reg
filename = Enum.join(filename, "/")
- case Assets.upload_to_s3(upload_params, course_reg.course_id, foldername, filename) do
+ case Assets.upload_to_s3(
+ upload_params,
+ Courses.assets_prefix(course_reg.course),
+ foldername,
+ filename
+ ) do
{:error, {status, message}} -> conn |> put_status(status) |> text(message)
resp -> render(conn, "show.json", resp: resp)
end
diff --git a/lib/cadet_web/admin_controllers/admin_courses_controller.ex b/lib/cadet_web/admin_controllers/admin_courses_controller.ex
index 2f0a2f7e7..e1b268b1b 100644
--- a/lib/cadet_web/admin_controllers/admin_courses_controller.ex
+++ b/lib/cadet_web/admin_controllers/admin_courses_controller.ex
@@ -84,7 +84,7 @@ defmodule CadetWeb.AdminCoursesController do
end
swagger_path :update_course_config do
- put("/v2/courses/{course_id}/admin/onfig")
+ put("/v2/courses/{course_id}/admin/config")
summary("Updates the course configuration for the specified course")
diff --git a/lib/cadet_web/controllers/user_controller.ex b/lib/cadet_web/controllers/user_controller.ex
index 728e946ab..465bb4bf7 100644
--- a/lib/cadet_web/controllers/user_controller.ex
+++ b/lib/cadet_web/controllers/user_controller.ex
@@ -291,6 +291,7 @@ defmodule CadetWeb.UserController do
source_variant(Schema.ref(:SourceVariant), "Source Variant name", required: true)
module_help_text(:string, "Module help text", required: true)
assessment_types(:list, "Assessment Types", required: true)
+ assets_prefix(:string, "Assets prefix, used by the game")
end
example(%{
@@ -303,7 +304,8 @@ defmodule CadetWeb.UserController do
source_chapter: 1,
source_variant: "default",
module_help_text: "Help text",
- assessment_types: ["Missions", "Quests", "Paths", "Contests", "Others"]
+ assessment_types: ["Missions", "Quests", "Paths", "Contests", "Others"],
+ assets_prefix: "courses-prod/1/"
})
end,
SourceVariant:
diff --git a/lib/cadet_web/views/courses_view.ex b/lib/cadet_web/views/courses_view.ex
index 285e78584..6ab298c73 100644
--- a/lib/cadet_web/views/courses_view.ex
+++ b/lib/cadet_web/views/courses_view.ex
@@ -14,7 +14,8 @@ defmodule CadetWeb.CoursesView do
sourceChapter: :source_chapter,
sourceVariant: :source_variant,
moduleHelpText: :module_help_text,
- assessmentTypes: :assessment_configs
+ assessmentTypes: :assessment_configs,
+ assetsPrefix: :assets_prefix
})
}
end
diff --git a/lib/cadet_web/views/user_view.ex b/lib/cadet_web/views/user_view.ex
index 06cff4af0..f28599024 100644
--- a/lib/cadet_web/views/user_view.ex
+++ b/lib/cadet_web/views/user_view.ex
@@ -1,6 +1,8 @@
defmodule CadetWeb.UserView do
use CadetWeb, :view
+ alias Cadet.Courses
+
def render("index.json", %{
user: user,
courses: courses,
@@ -104,7 +106,8 @@ defmodule CadetWeb.UserView do
enableSourcecast: :enable_sourcecast,
sourceChapter: :source_chapter,
sourceVariant: :source_variant,
- moduleHelpText: :module_help_text
+ moduleHelpText: :module_help_text,
+ assetsPrefix: &Courses.assets_prefix/1
})
end
end
diff --git a/priv/repo/migrations/20210804182725_add_assets_prefix.exs b/priv/repo/migrations/20210804182725_add_assets_prefix.exs
new file mode 100644
index 000000000..fed8f5c81
--- /dev/null
+++ b/priv/repo/migrations/20210804182725_add_assets_prefix.exs
@@ -0,0 +1,9 @@
+defmodule Cadet.Repo.Migrations.AddAssetsPrefix do
+ use Ecto.Migration
+
+ def change do
+ alter table(:courses) do
+ add(:assets_prefix, :string, null: true)
+ end
+ end
+end
diff --git a/test/cadet/assets/assets_test.exs b/test/cadet/assets/assets_test.exs
index 3d16451d2..413861dee 100644
--- a/test/cadet/assets/assets_test.exs
+++ b/test/cadet/assets/assets_test.exs
@@ -7,30 +7,30 @@ defmodule Cadet.Assets.AssetsTest do
describe "Manage assets" do
test "accessible folder" do
use_cassette "aws/model_list_assets#1" do
- assert Assets.list_assets(1, "testFolder") === [
- "1/testFolder/",
- "1/testFolder/test.png",
- "1/testFolder/test2.png"
+ assert Assets.list_assets(prefix(1), "testFolder") === [
+ "testFolder/",
+ "testFolder/test.png",
+ "testFolder/test2.png"
]
end
end
test "access another course with 0 folder" do
use_cassette "aws/model_list_assets#2" do
- assert Assets.list_assets(2, "testFolder") === []
+ assert Assets.list_assets(prefix(2), "testFolder") === []
end
end
test "delete nonexistent file" do
use_cassette "aws/model_delete_asset#1" do
- assert Assets.delete_object(1, "testFolder", "test4.png") ===
+ assert Assets.delete_object(prefix(1), "testFolder", "test4.png") ===
{:error, {:not_found, "File not found"}}
end
end
test "delete ok file" do
use_cassette "aws/model_delete_asset#2" do
- assert Assets.delete_object(1, "testFolder", "test.png") === :ok
+ assert Assets.delete_object(prefix(1), "testFolder", "test.png") === :ok
end
end
@@ -38,7 +38,7 @@ defmodule Cadet.Assets.AssetsTest do
use_cassette "aws/model_upload_asset#1" do
assert Assets.upload_to_s3(
build_upload("test/fixtures/upload.png"),
- 1,
+ prefix(1),
"testFolder",
"test2.png"
) ===
@@ -50,7 +50,7 @@ defmodule Cadet.Assets.AssetsTest do
use_cassette "aws/model_upload_asset#2" do
assert Assets.upload_to_s3(
build_upload("test/fixtures/upload.png"),
- 1,
+ prefix(1),
"testFolder",
"test1.png"
) ===
@@ -64,4 +64,6 @@ defmodule Cadet.Assets.AssetsTest do
end
defp bucket, do: :cadet |> Application.fetch_env!(:uploader) |> Keyword.get(:assets_bucket)
+
+ defp prefix(course_id), do: "#{Assets.assets_prefix()}#{course_id}/"
end
diff --git a/test/cadet_web/admin_controllers/admin_assets_controller_test.exs b/test/cadet_web/admin_controllers/admin_assets_controller_test.exs
index d62b771b7..d2d422361 100644
--- a/test/cadet_web/admin_controllers/admin_assets_controller_test.exs
+++ b/test/cadet_web/admin_controllers/admin_assets_controller_test.exs
@@ -2,8 +2,12 @@ defmodule CadetWeb.AdminAssetsControllerTest do
use CadetWeb.ConnCase
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
+ alias Cadet.Courses.Course
+ alias Cadet.Repo
alias CadetWeb.AdminAssetsController
+ import Ecto.Query, only: [where: 2]
+
setup_all do
HTTPoison.start()
end
@@ -196,6 +200,52 @@ defmodule CadetWeb.AdminAssetsControllerTest do
end
end
+ describe "course with custom assets_prefix" do
+ @tag authenticate: :staff, course_id: 117
+ test "index file", %{conn: conn} do
+ course_id = conn.assigns.course_id
+
+ set_course_prefix(course_id)
+
+ use_cassette "aws/controller_list_assets#2" do
+ conn = get(conn, build_url(course_id, "testFolder"), %{})
+
+ assert json_response(conn, 200) ===
+ ["testFolder/", "testFolder/test.png", "testFolder/test2.png"]
+ end
+ end
+
+ @tag authenticate: :staff, course_id: 117
+ test "delete file", %{conn: conn} do
+ course_id = conn.assigns.course_id
+
+ set_course_prefix(course_id)
+
+ use_cassette "aws/controller_delete_asset#3" do
+ conn = delete(conn, build_url(course_id, "testFolder/nestedFolder/test2.png"))
+
+ assert response(conn, 204)
+ end
+ end
+
+ @tag authenticate: :staff, course_id: 117
+ test "upload file", %{conn: conn} do
+ course_id = conn.assigns.course_id
+
+ set_course_prefix(course_id)
+
+ use_cassette "aws/controller_upload_asset#3" do
+ conn =
+ post(conn, build_url(course_id, "testFolder/nestedFolder/test.png"), %{
+ "upload" => build_upload("test/fixtures/upload.png")
+ })
+
+ assert json_response(conn, 200) ===
+ "https://#{bucket()}.s3.amazonaws.com/#{custom_prefix()}testFolder/nestedFolder/test.png"
+ end
+ end
+ end
+
defp build_url(course_id), do: "/v2/courses/#{course_id}/admin/assets/"
defp build_url(course_id, url), do: "#{build_url(course_id)}/#{url}"
@@ -203,5 +253,13 @@ defmodule CadetWeb.AdminAssetsControllerTest do
%Plug.Upload{path: path, filename: Path.basename(path), content_type: content_type}
end
+ defp set_course_prefix(course_id) do
+ Course
+ |> where(id: ^course_id)
+ |> Repo.update_all(set: [assets_prefix: custom_prefix()])
+ end
+
defp bucket, do: :cadet |> Application.fetch_env!(:uploader) |> Keyword.get(:assets_bucket)
+
+ defp custom_prefix, do: "this-is-a-prefix/"
end
diff --git a/test/cadet_web/controllers/user_controller_test.exs b/test/cadet_web/controllers/user_controller_test.exs
index 99ae00200..cb6770ba7 100644
--- a/test/cadet_web/controllers/user_controller_test.exs
+++ b/test/cadet_web/controllers/user_controller_test.exs
@@ -3,10 +3,10 @@ defmodule CadetWeb.UserControllerTest do
import Cadet.Factory
- alias Cadet.Repo
alias CadetWeb.UserController
# alias Cadet.Assessments.{Assessment, Submission}
alias Cadet.Accounts.{User, CourseRegistration}
+ alias Cadet.{Repo, Courses}
test "swagger" do
assert is_map(UserController.swagger_definitions())
@@ -100,7 +100,8 @@ defmodule CadetWeb.UserControllerTest do
"courseName" => "Programming Methodology",
"sourceChapter" => 1,
"sourceVariant" => "default",
- "viewable" => true
+ "viewable" => true,
+ "assetsPrefix" => Courses.assets_prefix(course)
},
"assessmentConfigurations" => [
%{
@@ -226,7 +227,8 @@ defmodule CadetWeb.UserControllerTest do
"courseName" => "Programming Methodology",
"sourceChapter" => 1,
"sourceVariant" => "default",
- "viewable" => true
+ "viewable" => true,
+ "assetsPrefix" => Courses.assets_prefix(course)
},
"assessmentConfigurations" => []
}
diff --git a/test/fixtures/vcr_cassettes/aws/controller_delete_asset#3.json b/test/fixtures/vcr_cassettes/aws/controller_delete_asset#3.json
new file mode 100644
index 000000000..17c8584c9
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/aws/controller_delete_asset#3.json
@@ -0,0 +1,65 @@
+[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Authorization": "***",
+ "host": "s3.ap-southeast-1.amazonaws.com",
+ "x-amz-date": "20200704T190423Z",
+ "content-length": "0",
+ "x-amz-content-sha256": "***"
+ },
+ "method": "head",
+ "options": {
+ "with_body": "true",
+ "recv_timeout": 660000
+ },
+ "request_body": "",
+ "url": "https://s3.ap-southeast-1.amazonaws.com/test-sa-assets/this-is-a-prefix/testFolder/nestedFolder/test2.png"
+ },
+ "response": {
+ "binary": false,
+ "body": null,
+ "headers": {
+ "Date": "Sat, 04 Jul 2020 19:04:24 GMT",
+ "Last-Modified": "Sat, 04 Jul 2020 18:58:52 GMT",
+ "ETag": "\"3104001edec38fadeb925b9dbddab198\"",
+ "Accept-Ranges": "bytes",
+ "Content-Type": "image/png",
+ "Content-Length": "8035",
+ "Server": "AmazonS3"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ },
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Authorization": "***",
+ "host": "s3.ap-southeast-1.amazonaws.com",
+ "x-amz-date": "20200704T190423Z",
+ "content-length": "0",
+ "x-amz-content-sha256": "***"
+ },
+ "method": "delete",
+ "options": {
+ "with_body": "true",
+ "recv_timeout": 660000
+ },
+ "request_body": "",
+ "url": "https://s3.ap-southeast-1.amazonaws.com/test-sa-assets/this-is-a-prefix/testFolder/nestedFolder/test2.png"
+ },
+ "response": {
+ "binary": false,
+ "body": "",
+ "headers": {
+ "Date": "Sat, 04 Jul 2020 19:04:24 GMT",
+ "Server": "AmazonS3"
+ },
+ "status_code": 204,
+ "type": "ok"
+ }
+ }
+]
diff --git a/test/fixtures/vcr_cassettes/aws/controller_list_assets#1.json b/test/fixtures/vcr_cassettes/aws/controller_list_assets#1.json
index cc525f19b..0f2ecc1c6 100644
--- a/test/fixtures/vcr_cassettes/aws/controller_list_assets#1.json
+++ b/test/fixtures/vcr_cassettes/aws/controller_list_assets#1.json
@@ -18,7 +18,7 @@
},
"response": {
"binary": false,
- "body": "\ntest-sa-assetscourses-test/117/testFolder/1000falsetestFolder/2020-07-04T18:53:11.000Z"d41d8cd98f00b204e9800998ecf8427e"098bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARDtestFolder/test.png2020-07-04T19:04:50.000Z"3104001edec38fadeb925b9dbddab198"803598bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARDtestFolder/test2.png2020-07-04T19:04:50.000Z"3104001edec38fadeb925b9dbddab198"803598bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARD",
+ "body": "\ntest-sa-assetscourses-test/117/testFolder/1000falsecourses-test/117/testFolder/2020-07-04T18:53:11.000Z"d41d8cd98f00b204e9800998ecf8427e"098bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARDcourses-test/117/testFolder/test.png2020-07-04T19:04:50.000Z"3104001edec38fadeb925b9dbddab198"803598bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARDcourses-test/117/testFolder/test2.png2020-07-04T19:04:50.000Z"3104001edec38fadeb925b9dbddab198"803598bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARD",
"headers": {
"Date": "Sat, 04 Jul 2020 19:04:57 GMT",
"x-amz-bucket-region": "ap-southeast-1",
diff --git a/test/fixtures/vcr_cassettes/aws/controller_list_assets#2.json b/test/fixtures/vcr_cassettes/aws/controller_list_assets#2.json
new file mode 100644
index 000000000..ecf77d37b
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/aws/controller_list_assets#2.json
@@ -0,0 +1,33 @@
+[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Authorization": "***",
+ "host": "s3.ap-southeast-1.amazonaws.com",
+ "x-amz-date": "20200704T190455Z",
+ "x-amz-content-sha256": "***"
+ },
+ "method": "get",
+ "options": {
+ "with_body": "true",
+ "recv_timeout": 660000
+ },
+ "request_body": "",
+ "url": "https://s3.ap-southeast-1.amazonaws.com/test-sa-assets/"
+ },
+ "response": {
+ "binary": false,
+ "body": "\ntest-sa-assetsthis-is-a-prefix/testFolder/1000falsethis-is-a-prefix/testFolder/2020-07-04T18:53:11.000Z"d41d8cd98f00b204e9800998ecf8427e"098bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARDthis-is-a-prefix/testFolder/test.png2020-07-04T19:04:50.000Z"3104001edec38fadeb925b9dbddab198"803598bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARDthis-is-a-prefix/testFolder/test2.png2020-07-04T19:04:50.000Z"3104001edec38fadeb925b9dbddab198"803598bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARD",
+ "headers": {
+ "Date": "Sat, 04 Jul 2020 19:04:57 GMT",
+ "x-amz-bucket-region": "ap-southeast-1",
+ "Content-Type": "application/xml",
+ "Transfer-Encoding": "chunked",
+ "Server": "AmazonS3"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ }
+]
diff --git a/test/fixtures/vcr_cassettes/aws/controller_upload_asset#3.json b/test/fixtures/vcr_cassettes/aws/controller_upload_asset#3.json
new file mode 100644
index 000000000..84a8fc07f
--- /dev/null
+++ b/test/fixtures/vcr_cassettes/aws/controller_upload_asset#3.json
@@ -0,0 +1,93 @@
+[
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Authorization": "***",
+ "host": "s3.ap-southeast-1.amazonaws.com",
+ "x-amz-date": "20210802T110004Z",
+ "content-length": "0",
+ "x-amz-content-sha256": "***"
+ },
+ "method": "post",
+ "options": {
+ "with_body": "true",
+ "recv_timeout": 660000
+ },
+ "request_body": "",
+ "url": "https://s3.ap-southeast-1.amazonaws.com/test-sa-assets/this-is-a-prefix/testFolder/nestedFolder/test.png"
+ },
+ "response": {
+ "binary": false,
+ "body": "\ntest-sa-assetsthis-is-a-prefix/testFolder/nestedFolder/test.pngjQOloaBCXjlYK5iRAQKuUVpX4bDPKZ0RSW8T3ndp4N.ZnW_e3eoE.AermyCbfytWRJ.i.tQ4lKSvruRteBTviJ5.492AlTIm6D08VEaszDayFRN_gGrdPclOZgxcT1b.UZ9V.Nku10KjcT7fc3BWbQ--",
+ "headers": {
+ "Date": "Mon, 02 Aug 2021 11:00:04 GMT",
+ "Transfer-Encoding": "chunked",
+ "Server": "AmazonS3"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ },
+ {
+ "request": {
+ "body": "",
+ "headers": {
+ "Authorization": "***",
+ "host": "s3.ap-southeast-1.amazonaws.com",
+ "x-amz-date": "20210802T110004Z",
+ "content-length": "0",
+ "x-amz-content-sha256": "***"
+ },
+ "method": "head",
+ "options": {
+ "with_body": "true",
+ "recv_timeout": 660000
+ },
+ "request_body": "",
+ "url": "https://s3.ap-southeast-1.amazonaws.com/test-sa-assets/this-is-a-prefix/testFolder/nestedFolder/test.png"
+ },
+ "response": {
+ "binary": false,
+ "body": null,
+ "headers": {
+ "Content-Type": "application/xml",
+ "Date": "Mon, 02 Aug 2021 11:00:02 GMT",
+ "Server": "AmazonS3"
+ },
+ "status_code": 404,
+ "type": "ok"
+ }
+ },
+ {
+ "request": {
+ "body": "�PNG\r\n\u001a\n\u0000\u0000\u0000\rIHDR\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0001\u0001\u0003\u0000\u0000\u0000%�V�\u0000\u0000\u0000\u0003PLTE�M\u0000\\58\u0000\u0000\u0000\u0001tRNS��4V�\u0000\u0000\u0000\nIDATx�cb\u0000\u0000\u0000\u0006\u0000\u000367|�\u0000\u0000\u0000\u0000IEND�B`�",
+ "headers": {
+ "Authorization": "***",
+ "host": "s3.ap-southeast-1.amazonaws.com",
+ "x-amz-date": "20210802T110004Z",
+ "content-length": "95",
+ "x-amz-content-sha256": "***"
+ },
+ "method": "put",
+ "options": {
+ "with_body": "true",
+ "recv_timeout": 660000
+ },
+ "request_body": "",
+ "url": "https://s3.ap-southeast-1.amazonaws.com/test-sa-assets/this-is-a-prefix/testFolder/nestedFolder/test.png"
+ },
+ "response": {
+ "binary": false,
+ "body": "",
+ "headers": {
+ "Date": "Mon, 02 Aug 2021 11:00:04 GMT",
+ "ETag": "\"60cf42b4d05caf10cf8bb15c0817a7b4\"",
+ "Server": "AmazonS3",
+ "Content-Length": "0"
+ },
+ "status_code": 200,
+ "type": "ok"
+ }
+ }
+]
diff --git a/test/fixtures/vcr_cassettes/aws/model_list_assets#1.json b/test/fixtures/vcr_cassettes/aws/model_list_assets#1.json
index 788ff2393..819085eff 100644
--- a/test/fixtures/vcr_cassettes/aws/model_list_assets#1.json
+++ b/test/fixtures/vcr_cassettes/aws/model_list_assets#1.json
@@ -18,7 +18,7 @@
},
"response": {
"binary": false,
- "body": "\ntest-sa-assetscourses-test/1/testFolder/1000false1/testFolder/2021-08-02T10:23:39.000Z"d41d8cd98f00b204e9800998ecf8427e"098bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARD1/testFolder/test.png2021-08-02T10:56:36.000Z"33c5d4f094a7727780a3c63babae1083"256898bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARD1/testFolder/test2.png2021-08-02T10:25:15.000Z"af2ab457d8b118efa176bc12cff4895f"299698bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARD",
+ "body": "\ntest-sa-assetscourses-test/1/testFolder/1000falsecourses-test/1/testFolder/2021-08-02T10:23:39.000Z"d41d8cd98f00b204e9800998ecf8427e"098bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARDcourses-test/1/testFolder/test.png2021-08-02T10:56:36.000Z"33c5d4f094a7727780a3c63babae1083"256898bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARDcourses-test/1/testFolder/test2.png2021-08-02T10:25:15.000Z"af2ab457d8b118efa176bc12cff4895f"299698bd2bd2de0c976fb511f741fea454cb1026b9e1f9ac9160fd9f51d07e765f19unixsp+cs1101sSTANDARD",
"headers": {
"Date": "Mon, 02 Aug 2021 10:59:51 GMT",
"x-amz-bucket-region": "ap-southeast-1",