Skip to content

Commit 0346e7b

Browse files
committed
curl: switch to single quotes in output and fix related tests
1 parent 542415f commit 0346e7b

File tree

4 files changed

+80
-18
lines changed

4 files changed

+80
-18
lines changed

Source/CURLConvertible.swift

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,12 @@ public extension CURLConvertible {
104104
}
105105
}
106106

107-
let curlDisallowedHeaders = SmartNetworkSettings.curlDisallowedHeaders
107+
// `Content-Length` must be removed because `httpBodyData` is modified during cURL formatting
108+
let curlDisallowedHeaders: Set<String> = Set(SmartNetworkSettings.curlDisallowedHeaders + ["Content-Length"])
108109
let headerItems = prettyPrinted ? headers.sorted(by: { $0.key < $1.key }) : headers.rawValues
109110
for header in headerItems {
110111
if !curlDisallowedHeaders.contains(header.key) {
111-
let escapedValue = header.value.replacingOccurrences(of: "\"", with: "\\\"")
112-
components.append("-H \"\(header.key): \(escapedValue)\"")
112+
components.append("-H '\(header.key): \(header.value)'")
113113
}
114114
}
115115

@@ -123,13 +123,10 @@ public extension CURLConvertible {
123123
httpBody = .init(decoding: httpBodyData, as: UTF8.self)
124124
}
125125

126-
var escapedBody = httpBody.replacingOccurrences(of: "\\\"", with: "\\\\\"")
127-
escapedBody = escapedBody.replacingOccurrences(of: "\"", with: "\\\"")
128-
129-
components.append("-d \"\(escapedBody)\"")
126+
components.append("-d '\(httpBody)'")
130127
}
131128

132-
components.append("\"\(url.absoluteString)\"")
129+
components.append("'\(url.absoluteString)'")
133130

134131
var curl = components.joined(separator: " \\\n\t")
135132
if SmartNetworkSettings.curlAddJSON_PP {

Source/SmartNetworkSettings.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ public enum SmartNetworkSettings: Sendable {
5555
/// A set of headers to exclude from generated `cURL` commands.
5656
///
5757
/// These are typically added automatically by the networking stack or not useful for reproduction.
58-
public nonisolated(unsafe) static var curlDisallowedHeaders: Set<String> = [
58+
///
59+
/// - Important: `Content-Length` must be removed and always disallowed because `httpBodyData` is modified during cURL formatting
60+
public nonisolated(unsafe) static var curlDisallowedHeaders: [String] = [
5961
"Accept-Encoding",
62+
"Content-Length",
6063
"Connection",
6164
"Accept",
6265
"Host"
@@ -113,8 +116,11 @@ public enum SmartNetworkSettings {
113116
/// A set of headers to exclude from generated `cURL` commands.
114117
///
115118
/// These are typically added automatically by the networking stack or not useful for reproduction.
116-
public static var curlDisallowedHeaders: Set<String> = [
119+
///
120+
/// - Important: `Content-Length` must be removed and always disallowed because `httpBodyData` is modified during cURL formatting
121+
public static var curlDisallowedHeaders: [String] = [
117122
"Accept-Encoding",
123+
"Content-Length",
118124
"Connection",
119125
"Accept",
120126
"Host"

Tests/CURLConvertibleTests.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import XCTest
2+
@testable import SmartNetwork
3+
4+
final class CURLConvertibleTests: XCTestCase {
5+
struct DummyConvertible: CURLConvertible {}
6+
7+
override func setUp() {
8+
super.setUp()
9+
SmartNetworkSettings.curlAddJSON_PP = false
10+
}
11+
12+
func testBasicGetRequest() {
13+
let session = URLSession(configuration: .ephemeral)
14+
var request = URLRequest(url: URL(string: "https://example.com")!)
15+
request.httpMethod = "GET"
16+
17+
let curl = DummyConvertible().cURLDescription(with: session, request: request, prettyPrinted: false)
18+
19+
XCTAssertTrue(curl.contains("curl -v"))
20+
XCTAssertTrue(curl.contains("-X GET"))
21+
XCTAssertTrue(curl.contains("'https://example.com'"))
22+
}
23+
24+
func testRequestWithHeaders() {
25+
let session = URLSession(configuration: .ephemeral)
26+
var request = URLRequest(url: URL(string: "https://example.com")!)
27+
request.httpMethod = "GET"
28+
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
29+
30+
let curl = DummyConvertible().cURLDescription(with: session, request: request, prettyPrinted: false)
31+
32+
XCTAssertTrue(curl.contains("-H 'Content-Type: application/json'"))
33+
}
34+
35+
func testPostRequestWithBody() {
36+
let session = URLSession(configuration: .ephemeral)
37+
var request = URLRequest(url: URL(string: "https://example.com/api")!)
38+
request.httpMethod = "POST"
39+
request.httpBody = "{\"key\":\"value\"}".data(using: .utf8)
40+
41+
let curl = DummyConvertible().cURLDescription(with: session, request: request, prettyPrinted: false)
42+
43+
XCTAssertTrue(curl.contains("-X POST"))
44+
XCTAssertTrue(curl.contains("-d '{\"key\":\"value\"}'"))
45+
}
46+
47+
func testPrettyPrintedJSON() {
48+
SmartNetworkSettings.curlAddJSON_PP = true
49+
let session = URLSession(configuration: .ephemeral)
50+
var request = URLRequest(url: URL(string: "https://example.com")!)
51+
request.httpMethod = "POST"
52+
request.httpBody = try? JSONSerialization.data(withJSONObject: ["foo": "bar"], options: [])
53+
54+
let curl = DummyConvertible().cURLDescription(with: session, request: request, prettyPrinted: true)
55+
56+
XCTAssertTrue(curl.contains(" | json_pp"))
57+
XCTAssertTrue(curl.contains("-d '{"))
58+
}
59+
}

Tests/Plugins/Plugins.CurlTests.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ final class PluginsCurlTests: XCTestCase {
5555
XCTAssertEqual(actual.value.toTestable(), [
5656
.phase: "willSend",
5757
.url: "http://www.some.com",
58-
.curl: "$ curl -v \\\n\t-X GET \\\n\t-H \"some: value\" \\\n\t-d \"{\n \\\"id\\\" : 2\n}\" \\\n\t\"https://www.some.com?some=value\""
58+
.curl: "$ curl -v \\\n\t-X GET \\\n\t-H \'some: value\' \\\n\t-d \'{\n \"id\" : 2\n}\' \\\n\t\'https://www.some.com?some=value\'"
5959
])
6060
actual.value = .init()
6161
subject.didReceive(parameters: parameters, userInfo: userInfo, request: requestable, data: .testMake())
@@ -69,7 +69,7 @@ final class PluginsCurlTests: XCTestCase {
6969
XCTAssertEqual(actual.value.toTestable(), [
7070
.phase: "didFinish",
7171
.url: "http://www.some.com",
72-
.curl: "$ curl -v \\\n\t-X GET \\\n\t\"http://www.some.com\""
72+
.curl: "$ curl -v \\\n\t-X GET \\\n\t'http://www.some.com'"
7373
])
7474

7575
subject.wasCancelled(parameters: parameters, userInfo: userInfo, request: requestable, session: session)
@@ -88,8 +88,8 @@ final class PluginsCurlTests: XCTestCase {
8888

8989
XCTAssertEqual(actual.value.toTestable(), [
9090
.url: "http://www.some.com",
91-
.curl: "curl -v \\\n\t-X GET \\\n\t-H \"some: value\" \\\n\t-d \"{\\\"id\\\":2}\" \\\n\t\"https://www.some.com?some=value\"",
92-
.phase: "willSend"
91+
.phase: "willSend",
92+
.curl: "curl -v \\\n\t-X GET \\\n\t-H 'some: value' \\\n\t-d '{\"id\":2}' \\\n\t'https://www.some.com?some=value'"
9393
])
9494
actual.value = .init()
9595

@@ -105,7 +105,7 @@ final class PluginsCurlTests: XCTestCase {
105105
.phase: "didFinish",
106106
.url: "https://www.some.com?some=value",
107107
.body: "{\n \"id\" : 2\n}",
108-
.curl: "curl -v \\\n\t-X GET \\\n\t-d \"{\\\"id\\\":2}\" \\\n\t\"https://www.some.com?some=value\""
108+
.curl: "curl -v \\\n\t-X GET \\\n\t-d '{\"id\":2}' \\\n\t'https://www.some.com?some=value'"
109109
])
110110

111111
subject.wasCancelled(parameters: parameters, userInfo: userInfo, request: requestable, session: session)
@@ -123,8 +123,8 @@ final class PluginsCurlTests: XCTestCase {
123123

124124
XCTAssertEqual(actual.value.toTestable(), [
125125
.phase: "willSend",
126-
.curl: "$ curl -v \\\n\t-X GET \\\n\t-H \"some: value\" \\\n\t-d \"{\\\"id\\\":2}\" \\\n\t\"https://www.some.com?some=value\"",
127-
.url: "http://www.some.com"
126+
.url: "http://www.some.com",
127+
.curl: "$ curl -v \\\n\t-X GET \\\n\t-H \'some: value\' \\\n\t-d \'{\"id\":2}\' \\\n\t\'https://www.some.com?some=value\'"
128128
])
129129
actual.value = .init()
130130

@@ -137,7 +137,7 @@ final class PluginsCurlTests: XCTestCase {
137137
XCTAssertNil(data.urlError)
138138
XCTAssertEqual(actual.value.toTestable(), [
139139
.phase: "didFinish",
140-
.curl: "$ curl -v \\\n\t-X GET \\\n\t-H \"some: value\" \\\n\t-d \"{\\\"id\\\":2}\" \\\n\t\"https://www.some.com?some2=value2\"",
140+
.curl: "$ curl -v \\\n\t-X GET \\\n\t-H 'some: value' \\\n\t-d '{\"id\":2}' \\\n\t'https://www.some.com?some2=value2'",
141141
.error: "generic",
142142
.requestError: "generic",
143143
.body: "{\n \"id\" : 2\n}",

0 commit comments

Comments
 (0)