Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/Storage/StorageFileApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ public class StorageFileApi: StorageApi, @unchecked Sendable {
file: FileUpload,
options: FileOptions?
) async throws -> SignedURLUploadResponse {
let options = options ?? defaultFileOptions
let options = options ?? FileOptions()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Update the remaining signed-upload snapshot.

This fallback affects all signed uploads with omitted options. The existing uploadToSignedURLCleansPath snapshot at Lines 1393–1404 still expects Content-Length: 297 and Content-Type: text/plain;charset=UTF-8; with this change it should expect 283 and text/plain, so the test will fail in CI.

Proposed snapshot update
-        	--header "Content-Length: 297" \
+        	--header "Content-Length: 283" \
...
-        Content-Type: text/plain;charset=UTF-8\`#r`
+        Content-Type: text/plain\`#r`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Storage/StorageFileApi.swift` at line 958, Update the
uploadToSignedURLCleansPath snapshot to expect Content-Length 283 and
Content-Type text/plain for omitted FileOptions, while preserving the existing
signed-upload behavior and other snapshot assertions.

var headers = options.headers.map { HTTPFields($0) } ?? HTTPFields()

headers[.xUpsert] = "\(options.upsert)"
Expand Down
74 changes: 72 additions & 2 deletions Tests/StorageTests/StorageFileAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,37 @@ extension StorageMockerTests {
)
}

/// A client whose transport records the request body, for asserting the emitted multipart
/// headers.
private func makeBodyCapturingSUT(body: LockIsolated<Data>) -> SupabaseStorageClient {
let respond: @Sendable (URLRequest) -> (Data, URLResponse) = { request in
(
Data(#"{"Key":"bucket/\#(request.url!.lastPathComponent)"}"#.utf8),
HTTPURLResponse(
url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil
)!
)
}

return SupabaseStorageClient(
configuration: StorageClientConfiguration(
url: url,
headers: [:],
session: StorageHTTPSession(
fetch: { request in
body.setValue(request.httpBody ?? Data())
return respond(request)
},
upload: { request, data in
body.setValue(data)
return respond(request)
}
),
logger: nil
)
)
}

@Test
func listFiles() async throws {
let storage = makeSUT()
Expand Down Expand Up @@ -1410,7 +1441,7 @@ extension StorageMockerTests {
curl \
--request PUT \
--header "Cache-Control: max-age=3600" \
--header "Content-Length: 297" \
--header "Content-Length: 283" \
--header "Content-Type: multipart/form-data; boundary=alamofire.boundary.e56f43407f772505" \
--header "X-Client-Info: storage-swift/0.0.0" \
--header "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0" \
Expand All @@ -1421,7 +1452,7 @@ extension StorageMockerTests {
3600\#r
--alamofire.boundary.e56f43407f772505\#r
Content-Disposition: form-data; name=\"\"; filename=\"file.txt\"\#r
Content-Type: text/plain;charset=UTF-8\#r
Content-Type: text/plain\#r
\#r
hello world\#r
--alamofire.boundary.e56f43407f772505--\#r
Expand All @@ -1438,6 +1469,38 @@ extension StorageMockerTests {
#expect(response.fullPath == "bucket/file.txt")
}

@Test
func uploadToSignedURLDerivesContentTypeFromPathExtensionWhenOptionsOmitted() async throws {
let body = LockIsolated(Data())
let storage = makeBodyCapturingSUT(body: body)

_ = try await storage.from("bucket")
.uploadToSignedURL(
"cat.png",
token: "abc.def.ghi",
data: Data("not-really-a-png".utf8)
)

#expect(body.value.containsBytes(of: "Content-Type: image/png"))
#expect(!body.value.containsBytes(of: "text/plain"))
}

@Test
func uploadToSignedURLFromFileURLDerivesContentTypeWhenOptionsOmitted() async throws {
let body = LockIsolated(Data())
let storage = makeBodyCapturingSUT(body: body)

_ = try await storage.from("bucket")
.uploadToSignedURL(
"cat.jpg",
token: "abc.def.ghi",
fileURL: Bundle.module.url(forResource: "sadcat", withExtension: "jpg")!
)

#expect(body.value.containsBytes(of: "Content-Type: image/jpeg"))
#expect(!body.value.containsBytes(of: "text/plain"))
}

@Test
func uploadToSignedURL_fromFileURL() async throws {
let storage = makeSUT()
Expand Down Expand Up @@ -1603,3 +1666,10 @@ extension StorageMockerTests {
}
}
}

extension Data {
/// Whether the raw bytes contain `string`, for bodies that are not valid UTF-8 as a whole.
fileprivate func containsBytes(of string: String) -> Bool {
range(of: Data(string.utf8)) != nil
}
}
Loading