diff --git a/backend/internal/handlers/objects.go b/backend/internal/handlers/objects.go index ab43050..3edcee3 100644 --- a/backend/internal/handlers/objects.go +++ b/backend/internal/handlers/objects.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "Noooste/garage-ui/internal/apierr" "Noooste/garage-ui/internal/models" "Noooste/garage-ui/internal/services" @@ -122,9 +123,7 @@ func (h *ObjectHandler) ListObjects(c fiber.Ctx) error { // List objects in the bucket objects, err := h.s3Service.ListObjects(ctx, bucketName, prefix, maxKeys, continuationToken) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON( - models.ErrorResponse(models.ErrCodeListFailed, "Failed to list objects: "+err.Error()), - ) + return apierr.Respond(c, err) } return c.JSON(models.SuccessResponse(objects)) @@ -186,9 +185,7 @@ func (h *ObjectHandler) UploadObject(c fiber.Ctx) error { // Upload to Garage uploadResult, err := h.s3Service.UploadObject(ctx, bucketName, key, fileHandle, contentType) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON( - models.ErrorResponse(models.ErrCodeUploadFailed, "Failed to upload object: "+err.Error()), - ) + return apierr.Respond(c, err) } return c.Status(fiber.StatusCreated).JSON(models.SuccessResponse(uploadResult)) @@ -238,9 +235,7 @@ func (h *ObjectHandler) CreateDirectory(c fiber.Ctx) error { result, err := h.s3Service.CreateDirectoryMarker(ctx, bucketName, key) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON( - models.ErrorResponse(models.ErrCodeUploadFailed, "Failed to create directory: "+err.Error()), - ) + return apierr.Respond(c, err) } return c.Status(fiber.StatusCreated).JSON(models.SuccessResponse(result)) @@ -281,9 +276,7 @@ func (h *ObjectHandler) GetObject(c fiber.Ctx) error { // Get object from Garage body, objectInfo, err := h.s3Service.GetObject(ctx, bucketName, key) if err != nil { - return c.Status(fiber.StatusNotFound).JSON( - models.ErrorResponse(models.ErrCodeObjectNotFound, "Object not found: "+err.Error()), - ) + return apierr.Respond(c, err) } // The uploader controls Content-Type. Rewrite executable MIME types to @@ -345,9 +338,7 @@ func (h *ObjectHandler) DeleteObject(c fiber.Ctx) error { // Check if object exists exists, err := h.s3Service.ObjectExists(ctx, bucketName, key) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON( - models.ErrorResponse(models.ErrCodeInternalError, "Failed to check object existence: "+err.Error()), - ) + return apierr.Respond(c, err) } if !exists { @@ -358,9 +349,7 @@ func (h *ObjectHandler) DeleteObject(c fiber.Ctx) error { // Delete the object if err := h.s3Service.DeleteObject(ctx, bucketName, key); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON( - models.ErrorResponse(models.ErrCodeDeleteFailed, "Failed to delete object: "+err.Error()), - ) + return apierr.Respond(c, err) } // Return success response @@ -407,9 +396,7 @@ func (h *ObjectHandler) GetObjectMetadata(c fiber.Ctx) error { // Get object metadata metadata, err := h.s3Service.GetObjectMetadata(ctx, bucketName, key) if err != nil { - return c.Status(fiber.StatusNotFound).JSON( - models.ErrorResponse(models.ErrCodeObjectNotFound, "Object not found: "+err.Error()), - ) + return apierr.Respond(c, err) } return c.JSON(models.SuccessResponse(metadata)) @@ -467,9 +454,7 @@ func (h *ObjectHandler) GetPresignedURL(c fiber.Ctx) error { // Check if object exists exists, err := h.s3Service.ObjectExists(ctx, bucketName, key) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON( - models.ErrorResponse(models.ErrCodeInternalError, "Failed to check object existence: "+err.Error()), - ) + return apierr.Respond(c, err) } if !exists { @@ -481,9 +466,7 @@ func (h *ObjectHandler) GetPresignedURL(c fiber.Ctx) error { // Generate pre-signed URL url, err := h.s3Service.GetPresignedURL(ctx, bucketName, key, time.Duration(expiresIn)*time.Second) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON( - models.ErrorResponse(models.ErrCodeInternalError, "Failed to generate pre-signed URL: "+err.Error()), - ) + return apierr.Respond(c, err) } response := models.PresignedURLResponse{ @@ -540,9 +523,7 @@ func (h *ObjectHandler) DeleteMultipleObjects(c fiber.Ctx) error { // Delete multiple objects if err := h.s3Service.DeleteMultipleObjects(ctx, bucketName, req.Keys); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON( - models.ErrorResponse(models.ErrCodeDeleteFailed, "Failed to delete objects: "+err.Error()), - ) + return apierr.Respond(c, err) } response := models.ObjectDeleteMultipleResponse{ diff --git a/backend/internal/handlers/objects_test.go b/backend/internal/handlers/objects_test.go index 8cbb3f1..ee066c5 100644 --- a/backend/internal/handlers/objects_test.go +++ b/backend/internal/handlers/objects_test.go @@ -144,18 +144,20 @@ func TestGetObjectMetadata_Success(t *testing.T) { } } -func TestGetObjectMetadata_NotFound404(t *testing.T) { +func TestGetObjectMetadata_ServiceError500(t *testing.T) { app, s3 := newObjectsTestApp(t) s3.GetObjectMetadataFn = func(_ context.Context, _, _ string) (*models.ObjectInfo, error) { - return nil, errors.New("not found") + return nil, errors.New("boom") } resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/buckets/b1/objects/nope/metadata", nil)) if err != nil { t.Fatalf("app.Test: %v", err) } defer resp.Body.Close() - if resp.StatusCode != http.StatusNotFound { - t.Fatalf("status = %d, want 404", resp.StatusCode) + // Generic errors map to 500/INTERNAL_ERROR; typed upstream NoSuchKey → 404 + // is covered by Task 12 tests. + if resp.StatusCode != http.StatusInternalServerError { + t.Fatalf("status = %d, want 500", resp.StatusCode) } } @@ -363,18 +365,20 @@ func TestGetObject_DownloadQuerySetsAttachment(t *testing.T) { } } -func TestGetObject_ServiceErrorReturns404(t *testing.T) { +func TestGetObject_ServiceErrorReturns500(t *testing.T) { app, s3 := newObjectsTestApp(t) s3.GetObjectFn = func(_ context.Context, _, _ string) (io.ReadCloser, *models.ObjectInfo, error) { - return nil, nil, errors.New("not found") + return nil, nil, errors.New("boom") } resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/buckets/b1/objects/nope", nil)) if err != nil { t.Fatalf("app.Test: %v", err) } defer resp.Body.Close() - if resp.StatusCode != http.StatusNotFound { - t.Fatalf("status = %d, want 404", resp.StatusCode) + // Generic errors map to 500/INTERNAL_ERROR; typed upstream NoSuchKey → 404 + // is covered by Task 12 tests. + if resp.StatusCode != http.StatusInternalServerError { + t.Fatalf("status = %d, want 500", resp.StatusCode) } }