From d816655c6b221162e6c124222cf434809332b114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Wed, 9 Sep 2026 14:59:56 +0200 Subject: [PATCH] Implement proxy-download mode in the mock AWS driver The mock driver's ServeFile always redirected to a presigned URL, regardless of the ProxyDownload config option, so the real driver's proxy-download path had no test coverage. Mirrors the real implementation: streams the file content from a local test fixture at "data/" (the same convention used by Stream()) instead of redirecting. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0168YUwGEzEHTqd9CwzXE53d --- .../filesystem/s3filesystem/aws/Aws_mock.go | 28 +++++++- .../s3filesystem/aws/Aws_mock_test.go | 70 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 internal/storage/filesystem/s3filesystem/aws/Aws_mock_test.go diff --git a/internal/storage/filesystem/s3filesystem/aws/Aws_mock.go b/internal/storage/filesystem/s3filesystem/aws/Aws_mock.go index 85c47ad1..9f8df997 100644 --- a/internal/storage/filesystem/s3filesystem/aws/Aws_mock.go +++ b/internal/storage/filesystem/s3filesystem/aws/Aws_mock.go @@ -12,10 +12,12 @@ import ( "strings" "github.com/forceu/gokapi/internal/models" + "github.com/forceu/gokapi/internal/webserver/headers" ) var uploadedFiles []models.File var isCorrectLogin bool +var awsConfig models.AwsConfig const ( region = "mock-region-1" @@ -32,6 +34,7 @@ const IsMockApi = true // Init reads the credentials for AWS func Init(config models.AwsConfig) bool { + awsConfig = config if !isValidCredentials() { return false } @@ -57,6 +60,7 @@ func IsValidLogin(config models.AwsConfig) (bool, error) { // LogOut resets the credentials func LogOut() { + awsConfig = models.AwsConfig{} isCorrectLogin = false } @@ -131,10 +135,32 @@ func isUploaded(file models.File) bool { // ServeFile either redirects the user to a pre-signed download url (default) or downloads the file and serves it as a proxy (depending // on configuration). Returns true if blocking operation (in order to set download status) or false if non-blocking. func ServeFile(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool, forceDecryption bool) (bool, error) { - // TODO implement proxy as well + if awsConfig.ProxyDownload { + return true, proxyDownload(w, file, forceDownload) + } return false, RedirectToDownload(w, r, file, forceDownload) } +// proxyDownload simulates streaming the file content through the server, mirroring the +// real AWS implementation's proxy-download mode. The content is read from a local test +// fixture at "data/", the same convention used by Stream(). +func proxyDownload(w http.ResponseWriter, file models.File, forceDownload bool) error { + if !isValidCredentials() { + return errors.New("invalid credentials / invalid bucket / invalid region") + } + if !isUploaded(file) { + return errors.New("file not found") + } + data, err := os.Open("data/" + file.SHA1) + if err != nil { + return err + } + defer data.Close() + headers.Write(file, w, forceDownload, false) + _, err = io.Copy(w, data) + return err +} + // RedirectToDownload creates a presigned link that is valid for 15 seconds and redirects the // client to this url func RedirectToDownload(w http.ResponseWriter, r *http.Request, file models.File, forceDownload bool) error { diff --git a/internal/storage/filesystem/s3filesystem/aws/Aws_mock_test.go b/internal/storage/filesystem/s3filesystem/aws/Aws_mock_test.go new file mode 100644 index 00000000..d5dcb467 --- /dev/null +++ b/internal/storage/filesystem/s3filesystem/aws/Aws_mock_test.go @@ -0,0 +1,70 @@ +//go:build !noaws && awsmock && test + +package aws + +import ( + "net/http/httptest" + "os" + "testing" + + "github.com/forceu/gokapi/internal/models" + "github.com/forceu/gokapi/internal/test" +) + +func initMockCredentials(t *testing.T) { + t.Helper() + os.Setenv("GOKAPI_AWS_BUCKET", bucketName) + os.Setenv("GOKAPI_AWS_REGION", region) + os.Setenv("GOKAPI_AWS_KEY", accessId) + os.Setenv("GOKAPI_AWS_KEY_SECRET", accessKey) + ok := Init(models.AwsConfig{}) + test.IsEqualBool(t, ok, true) +} + +func TestServeFile_ProxyDownload(t *testing.T) { + initMockCredentials(t) + awsConfig.ProxyDownload = true + defer func() { awsConfig.ProxyDownload = false }() + + file := models.File{Id: "awsTest1234567890123", SHA1: "x341354656543213246465465465432456898794"} + err := os.MkdirAll("data", 0777) + test.IsNil(t, err) + err = os.WriteFile("data/"+file.SHA1, []byte("proxy download content"), 0777) + test.IsNil(t, err) + defer os.Remove("data/" + file.SHA1) + + r := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + isBlocking, err := ServeFile(w, r, file, false, false) + test.IsNil(t, err) + test.IsEqualBool(t, isBlocking, true) + test.ResponseBodyIs(t, w, "proxy download content") +} + +func TestServeFile_ProxyDownload_MissingFixture(t *testing.T) { + initMockCredentials(t) + awsConfig.ProxyDownload = true + defer func() { awsConfig.ProxyDownload = false }() + + // awsTest1234567890123 is registered as uploaded by Init(), but its on-disk fixture + // is never written by this test - proxyDownload must surface that as an error rather + // than panicking. + file := models.File{Id: "awsTest1234567890123", SHA1: "x341354656543213246465465465432456898794"} + r := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + _, err := ServeFile(w, r, file, false, false) + test.IsNotNil(t, err) +} + +func TestServeFile_RedirectDownload(t *testing.T) { + initMockCredentials(t) + awsConfig.ProxyDownload = false + + file := models.File{Id: "awsTest1234567890123", SHA1: "x341354656543213246465465465432456898794"} + r := httptest.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + isBlocking, err := ServeFile(w, r, file, false, false) + test.IsNil(t, err) + test.IsEqualBool(t, isBlocking, false) + test.ResponseBodyContains(t, w, "https://redirect.url") +}