diff --git a/Tests/KeystoneTests/Tests/Services/MediaCreateParamsFilePathTests.swift b/Tests/KeystoneTests/Tests/Services/MediaCreateParamsFilePathTests.swift new file mode 100644 index 000000000000..e1050287d328 --- /dev/null +++ b/Tests/KeystoneTests/Tests/Services/MediaCreateParamsFilePathTests.swift @@ -0,0 +1,41 @@ +import Foundation +import Testing +import WordPressAPI +import WordPressKit + +@testable import WordPress + +/// `URL.path()` percent-encodes by default, unlike the legacy `url.path` property, so the +/// upload API was handed a path that doesn't exist on disk whenever the filename needed +/// encoding. A space is enough, which makes it reachable with ordinary content. +struct MediaCreateParamsFilePathTests { + + @Test func decodesPercentEncodingInTheFilePath() throws { + let media = RemoteMedia() + media.localURL = URL(fileURLWithPath: "/tmp/media/screen recording 1.mp4") + + let params = try #require(MediaCreateParams(media: media)) + + #expect(params.filePath == "/tmp/media/screen recording 1.mp4") + } + + /// Characters beyond the space get encoded too, so check one of those as well. + @Test func decodesPercentEncodingBeyondSpaces() throws { + let media = RemoteMedia() + media.localURL = URL(fileURLWithPath: "/tmp/media/100% done.jpg") + + let params = try #require(MediaCreateParams(media: media)) + + #expect(params.filePath == "/tmp/media/100% done.jpg") + } + + /// A name needing no encoding has to survive untouched. + @Test func leavesAnOrdinaryPathAlone() throws { + let media = RemoteMedia() + media.localURL = URL(fileURLWithPath: "/tmp/media/IMG_0001.jpg") + + let params = try #require(MediaCreateParams(media: media)) + + #expect(params.filePath == "/tmp/media/IMG_0001.jpg") + } +} diff --git a/WordPress/Classes/Services/MediaServiceRemoteCoreREST.swift b/WordPress/Classes/Services/MediaServiceRemoteCoreREST.swift index aa1d060c0066..394caa07214a 100644 --- a/WordPress/Classes/Services/MediaServiceRemoteCoreREST.swift +++ b/WordPress/Classes/Services/MediaServiceRemoteCoreREST.swift @@ -176,7 +176,9 @@ private extension RemoteMedia { } } -private extension MediaCreateParams { +// Not `private`: the file-path handling below is covered by +// `MediaCreateParamsFilePathTests`. +extension MediaCreateParams { init?(media: RemoteMedia) { guard let localURL = media.localURL else { @@ -198,7 +200,9 @@ private extension MediaCreateParams { caption: media.caption, description: media.descriptionText, postId: media.postID?.int64Value, - filePath: localURL.path() + // `path()` percent-encodes by default, so a filename with a space (a screen + // recording, say) becomes a path that doesn't exist on disk. + filePath: localURL.path(percentEncoded: false) ) } }