From f042a92531e4c1045bc33cea8561f82727cdc832 Mon Sep 17 00:00:00 2001 From: Eli Perkins Date: Mon, 23 Jan 2017 14:25:02 -0500 Subject: [PATCH] Swift 3 support (#65) * Update to Swift 3 * Bump version 0.4.0 * Rename HTTPURLResponse file too * Prefer private over fileprivate * Fix serializating HTTPURLResponse * Don't say we failed if we didn't * Swift 3.0.1 * tvOS target * Swift 3.0.1 * Fix tvOS deployment target * Fix upload tests * Explicitly set SWIFT_VERSION Without this, Xcode complains about the legacy Swift version not being set. * Remove explicit toolchain from CI * Specify SDK for CI * Add destinations for CI ...because Travis CI is so good. * Use latest version of Xcode * Clean up spacing --- .swift-version | 1 + .travis.yml | 11 +- DVR.xcodeproj/project.pbxproj | 289 +++++++++++++++++- .../xcshareddata/xcschemes/DVR-tvOS.xcscheme | 99 ++++++ DVR/Cassette.swift | 26 +- ...TPResponse.swift => HTTPURLResponse.swift} | 30 +- DVR/Interaction.swift | 48 +-- DVR/Resources/Info.plist | 2 +- DVR/Session.swift | 117 +++---- DVR/SessionDataTask.swift | 22 +- DVR/SessionDownloadTask.swift | 16 +- DVR/SessionUploadTask.swift | 10 +- DVR/Tests/Fixtures/upload-data.json | 2 +- DVR/Tests/Fixtures/upload-file.json | 2 +- DVR/Tests/SessionTests.swift | 124 ++++---- DVR/Tests/SessionUploadTests.swift | 82 +++-- DVR/URLRequest.swift | 56 ++-- DVR/URLResponse.swift | 24 +- 18 files changed, 661 insertions(+), 300 deletions(-) create mode 100644 .swift-version create mode 100644 DVR.xcodeproj/xcshareddata/xcschemes/DVR-tvOS.xcscheme rename DVR/{URLHTTPResponse.swift => HTTPURLResponse.swift} (56%) diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..cb2b00e --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +3.0.1 diff --git a/.travis.yml b/.travis.yml index 2284728..f36edf0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,16 @@ language: objective-c # lol -osx_image: xcode8 +osx_image: xcode8.2 xcode_project: DVR.xcodeproj -script: xcodebuild -scheme "$TRAVIS_XCODE_SCHEME" test +script: xcodebuild -scheme "$TRAVIS_XCODE_SCHEME" -sdk "$TRAVIS_XCODE_SDK" -destination "$DESTINATION" test matrix: include: - xcode_scheme: DVR-iOS xcode_sdk: iphonesimulator + env: + - DESTINATION="OS=10.1,name=iPhone 7 Plus" - xcode_scheme: DVR-OSX xcode_sdk: macosx - -env: - TOOLCHAINS=com.apple.dt.toolchain.Swift_2_3 + env: + - DESTINATION="arch=x86_64" diff --git a/DVR.xcodeproj/project.pbxproj b/DVR.xcodeproj/project.pbxproj index d80fe53..3819ffd 100644 --- a/DVR.xcodeproj/project.pbxproj +++ b/DVR.xcodeproj/project.pbxproj @@ -7,6 +7,26 @@ objects = { /* Begin PBXBuildFile section */ + 2104F5371DC658590039CA14 /* DVR.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2104F52E1DC658580039CA14 /* DVR.framework */; }; + 2104F5451DC658A20039CA14 /* DVR.h in Headers */ = {isa = PBXBuildFile; fileRef = 3647AF9E1B335D5500EF10D4 /* DVR.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 2104F5461DC658A60039CA14 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFB81B335E4A00EF10D4 /* Session.swift */; }; + 2104F5471DC658A60039CA14 /* SessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFB91B335E4A00EF10D4 /* SessionDataTask.swift */; }; + 2104F5481DC658A60039CA14 /* SessionDownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 360F5F721B5C907A001AADD1 /* SessionDownloadTask.swift */; }; + 2104F5491DC658A60039CA14 /* SessionUploadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19D62641CB1860400E16D11 /* SessionUploadTask.swift */; }; + 2104F54A1DC658A60039CA14 /* Cassette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFB51B335E4A00EF10D4 /* Cassette.swift */; }; + 2104F54B1DC658A60039CA14 /* Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFB61B335E4A00EF10D4 /* Interaction.swift */; }; + 2104F54C1DC658A60039CA14 /* URLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFB71B335E4A00EF10D4 /* URLRequest.swift */; }; + 2104F54D1DC658A60039CA14 /* URLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFBF1B33602A00EF10D4 /* URLResponse.swift */; }; + 2104F54E1DC658A60039CA14 /* HTTPURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFC11B3363C400EF10D4 /* HTTPURLResponse.swift */; }; + 2104F54F1DC658A90039CA14 /* SessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFCB1B33689000EF10D4 /* SessionTests.swift */; }; + 2104F5501DC658A90039CA14 /* SessionUploadTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19D626D1CB1A0DD00E16D11 /* SessionUploadTests.swift */; }; + 2104F5511DC658AC0039CA14 /* json-example.json in Resources */ = {isa = PBXBuildFile; fileRef = 36BDDB881B6716AB00878665 /* json-example.json */; }; + 2104F5521DC658AC0039CA14 /* example.json in Resources */ = {isa = PBXBuildFile; fileRef = 215F927C1B33D46C00EDC60F /* example.json */; }; + 2104F5531DC658AC0039CA14 /* text.json in Resources */ = {isa = PBXBuildFile; fileRef = 21E548F11BF69E29004855AE /* text.json */; }; + 2104F5541DC658AC0039CA14 /* multiple.json in Resources */ = {isa = PBXBuildFile; fileRef = 2119D37E1BF6BB2300E91D6F /* multiple.json */; }; + 2104F5551DC658AC0039CA14 /* upload-data.json in Resources */ = {isa = PBXBuildFile; fileRef = B19D62701CB1A27700E16D11 /* upload-data.json */; }; + 2104F5561DC658AC0039CA14 /* upload-file.json in Resources */ = {isa = PBXBuildFile; fileRef = B19D62761CB1A42600E16D11 /* upload-file.json */; }; + 2104F5571DC658AC0039CA14 /* testfile.txt in Resources */ = {isa = PBXBuildFile; fileRef = B19D62661CB18EDB00E16D11 /* testfile.txt */; }; 2119D3801BF6BB2700E91D6F /* multiple.json in Resources */ = {isa = PBXBuildFile; fileRef = 2119D37E1BF6BB2300E91D6F /* multiple.json */; }; 2119D3811BF6BB2700E91D6F /* multiple.json in Resources */ = {isa = PBXBuildFile; fileRef = 2119D37E1BF6BB2300E91D6F /* multiple.json */; }; 215F927D1B33D46C00EDC60F /* example.json in Resources */ = {isa = PBXBuildFile; fileRef = 215F927C1B33D46C00EDC60F /* example.json */; }; @@ -24,7 +44,7 @@ 3647AFBD1B335E4A00EF10D4 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFB81B335E4A00EF10D4 /* Session.swift */; }; 3647AFBE1B335E4A00EF10D4 /* SessionDataTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFB91B335E4A00EF10D4 /* SessionDataTask.swift */; }; 3647AFC01B33602A00EF10D4 /* URLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFBF1B33602A00EF10D4 /* URLResponse.swift */; }; - 3647AFC21B3363C400EF10D4 /* URLHTTPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFC11B3363C400EF10D4 /* URLHTTPResponse.swift */; }; + 3647AFC21B3363C400EF10D4 /* HTTPURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFC11B3363C400EF10D4 /* HTTPURLResponse.swift */; }; 3647AFCD1B33689000EF10D4 /* SessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFCB1B33689000EF10D4 /* SessionTests.swift */; }; 3690A0851B33AA3C00731222 /* DVR.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3690A07B1B33AA3B00731222 /* DVR.framework */; }; 3690A0921B33AA9400731222 /* DVR.h in Headers */ = {isa = PBXBuildFile; fileRef = 3647AF9E1B335D5500EF10D4 /* DVR.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -34,7 +54,7 @@ 3690A0961B33AA9400731222 /* Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFB61B335E4A00EF10D4 /* Interaction.swift */; }; 3690A0971B33AA9400731222 /* URLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFB71B335E4A00EF10D4 /* URLRequest.swift */; }; 3690A0981B33AA9400731222 /* URLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFBF1B33602A00EF10D4 /* URLResponse.swift */; }; - 3690A0991B33AA9400731222 /* URLHTTPResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFC11B3363C400EF10D4 /* URLHTTPResponse.swift */; }; + 3690A0991B33AA9400731222 /* HTTPURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFC11B3363C400EF10D4 /* HTTPURLResponse.swift */; }; 3690A0A21B33AA9E00731222 /* SessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3647AFCB1B33689000EF10D4 /* SessionTests.swift */; }; 36BDDB891B6716AB00878665 /* json-example.json in Resources */ = {isa = PBXBuildFile; fileRef = 36BDDB881B6716AB00878665 /* json-example.json */; }; 36BDDB8A1B6716AB00878665 /* json-example.json in Resources */ = {isa = PBXBuildFile; fileRef = 36BDDB881B6716AB00878665 /* json-example.json */; }; @@ -50,6 +70,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 2104F5381DC658590039CA14 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3647AF921B335D5500EF10D4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 2104F52D1DC658580039CA14; + remoteInfo = "DVR-tvOS"; + }; 3647AFA71B335D5500EF10D4 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 3647AF921B335D5500EF10D4 /* Project object */; @@ -67,6 +94,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 2104F52E1DC658580039CA14 /* DVR.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DVR.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2104F5361DC658590039CA14 /* DVRTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DVRTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 2119D37E1BF6BB2300E91D6F /* multiple.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = multiple.json; sourceTree = ""; }; 215F927C1B33D46C00EDC60F /* example.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = example.json; sourceTree = ""; }; 21E548F11BF69E29004855AE /* text.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = text.json; sourceTree = ""; }; @@ -80,7 +109,7 @@ 3647AFB81B335E4A00EF10D4 /* Session.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; }; 3647AFB91B335E4A00EF10D4 /* SessionDataTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionDataTask.swift; sourceTree = ""; }; 3647AFBF1B33602A00EF10D4 /* URLResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLResponse.swift; sourceTree = ""; }; - 3647AFC11B3363C400EF10D4 /* URLHTTPResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLHTTPResponse.swift; sourceTree = ""; }; + 3647AFC11B3363C400EF10D4 /* HTTPURLResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPURLResponse.swift; sourceTree = ""; }; 3647AFC81B33688A00EF10D4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 3647AFCB1B33689000EF10D4 /* SessionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionTests.swift; sourceTree = ""; }; 3647AFCC1B33689000EF10D4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -95,6 +124,21 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 2104F52A1DC658580039CA14 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2104F5331DC658590039CA14 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 2104F5371DC658590039CA14 /* DVR.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3647AF971B335D5500EF10D4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -143,6 +187,8 @@ 3647AFA51B335D5500EF10D4 /* DVRTests-iOS.xctest */, 3690A07B1B33AA3B00731222 /* DVR.framework */, 3690A0841B33AA3C00731222 /* DVRTests-OSX.xctest */, + 2104F52E1DC658580039CA14 /* DVR.framework */, + 2104F5361DC658590039CA14 /* DVRTests.xctest */, ); name = Products; sourceTree = ""; @@ -159,7 +205,7 @@ 3647AFB61B335E4A00EF10D4 /* Interaction.swift */, 3647AFB71B335E4A00EF10D4 /* URLRequest.swift */, 3647AFBF1B33602A00EF10D4 /* URLResponse.swift */, - 3647AFC11B3363C400EF10D4 /* URLHTTPResponse.swift */, + 3647AFC11B3363C400EF10D4 /* HTTPURLResponse.swift */, 3647AFC71B33688A00EF10D4 /* Resources */, 3647AFCA1B33689000EF10D4 /* Tests */, ); @@ -178,9 +224,9 @@ isa = PBXGroup; children = ( 3647AFCB1B33689000EF10D4 /* SessionTests.swift */, + B19D626D1CB1A0DD00E16D11 /* SessionUploadTests.swift */, 3647AFCF1B33693F00EF10D4 /* Fixtures */, 3647AFCC1B33689000EF10D4 /* Info.plist */, - B19D626D1CB1A0DD00E16D11 /* SessionUploadTests.swift */, ); name = Tests; path = DVR/Tests; @@ -203,6 +249,14 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + 2104F52B1DC658580039CA14 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 2104F5451DC658A20039CA14 /* DVR.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3647AF981B335D5500EF10D4 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -222,6 +276,42 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 2104F52D1DC658580039CA14 /* DVR-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2104F53F1DC658590039CA14 /* Build configuration list for PBXNativeTarget "DVR-tvOS" */; + buildPhases = ( + 2104F5291DC658580039CA14 /* Sources */, + 2104F52A1DC658580039CA14 /* Frameworks */, + 2104F52B1DC658580039CA14 /* Headers */, + 2104F52C1DC658580039CA14 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "DVR-tvOS"; + productName = "DVR-tvOS"; + productReference = 2104F52E1DC658580039CA14 /* DVR.framework */; + productType = "com.apple.product-type.framework"; + }; + 2104F5351DC658590039CA14 /* DVRTests-tvOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = 2104F5421DC658590039CA14 /* Build configuration list for PBXNativeTarget "DVRTests-tvOS" */; + buildPhases = ( + 2104F5321DC658590039CA14 /* Sources */, + 2104F5331DC658590039CA14 /* Frameworks */, + 2104F5341DC658590039CA14 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 2104F5391DC658590039CA14 /* PBXTargetDependency */, + ); + name = "DVRTests-tvOS"; + productName = "DVR-tvOSTests"; + productReference = 2104F5361DC658590039CA14 /* DVRTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 3647AF9A1B335D5500EF10D4 /* DVR-iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 3647AFAF1B335D5500EF10D4 /* Build configuration list for PBXNativeTarget "DVR-iOS" */; @@ -300,10 +390,18 @@ 3647AF921B335D5500EF10D4 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0700; + LastSwiftUpdateCheck = 0810; LastUpgradeCheck = 0800; ORGANIZATIONNAME = Venmo; TargetAttributes = { + 2104F52D1DC658580039CA14 = { + CreatedOnToolsVersion = 8.1; + ProvisioningStyle = Automatic; + }; + 2104F5351DC658590039CA14 = { + CreatedOnToolsVersion = 8.1; + ProvisioningStyle = Automatic; + }; 3647AF9A1B335D5500EF10D4 = { CreatedOnToolsVersion = 7.0; LastSwiftMigration = 0800; @@ -336,11 +434,34 @@ 3647AFA41B335D5500EF10D4 /* DVRTests-iOS */, 3690A07A1B33AA3B00731222 /* DVR-OSX */, 3690A0831B33AA3C00731222 /* DVRTests-OSX */, + 2104F52D1DC658580039CA14 /* DVR-tvOS */, + 2104F5351DC658590039CA14 /* DVRTests-tvOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 2104F52C1DC658580039CA14 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2104F5341DC658590039CA14 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2104F5561DC658AC0039CA14 /* upload-file.json in Resources */, + 2104F5521DC658AC0039CA14 /* example.json in Resources */, + 2104F5551DC658AC0039CA14 /* upload-data.json in Resources */, + 2104F5541DC658AC0039CA14 /* multiple.json in Resources */, + 2104F5511DC658AC0039CA14 /* json-example.json in Resources */, + 2104F5531DC658AC0039CA14 /* text.json in Resources */, + 2104F5571DC658AC0039CA14 /* testfile.txt in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3647AF991B335D5500EF10D4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -386,6 +507,31 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 2104F5291DC658580039CA14 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2104F54B1DC658A60039CA14 /* Interaction.swift in Sources */, + 2104F54C1DC658A60039CA14 /* URLRequest.swift in Sources */, + 2104F5461DC658A60039CA14 /* Session.swift in Sources */, + 2104F54A1DC658A60039CA14 /* Cassette.swift in Sources */, + 2104F5481DC658A60039CA14 /* SessionDownloadTask.swift in Sources */, + 2104F54E1DC658A60039CA14 /* HTTPURLResponse.swift in Sources */, + 2104F5491DC658A60039CA14 /* SessionUploadTask.swift in Sources */, + 2104F54D1DC658A60039CA14 /* URLResponse.swift in Sources */, + 2104F5471DC658A60039CA14 /* SessionDataTask.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 2104F5321DC658590039CA14 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2104F54F1DC658A90039CA14 /* SessionTests.swift in Sources */, + 2104F5501DC658A90039CA14 /* SessionUploadTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3647AF961B335D5500EF10D4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -398,7 +544,7 @@ 3647AFBC1B335E4A00EF10D4 /* URLRequest.swift in Sources */, 3647AFBB1B335E4A00EF10D4 /* Interaction.swift in Sources */, 3647AFBA1B335E4A00EF10D4 /* Cassette.swift in Sources */, - 3647AFC21B3363C400EF10D4 /* URLHTTPResponse.swift in Sources */, + 3647AFC21B3363C400EF10D4 /* HTTPURLResponse.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -423,7 +569,7 @@ 3690A0931B33AA9400731222 /* Session.swift in Sources */, 3690A0941B33AA9400731222 /* SessionDataTask.swift in Sources */, 3690A0971B33AA9400731222 /* URLRequest.swift in Sources */, - 3690A0991B33AA9400731222 /* URLHTTPResponse.swift in Sources */, + 3690A0991B33AA9400731222 /* HTTPURLResponse.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -439,6 +585,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 2104F5391DC658590039CA14 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 2104F52D1DC658580039CA14 /* DVR-tvOS */; + targetProxy = 2104F5381DC658590039CA14 /* PBXContainerItemProxy */; + }; 3647AFA81B335D5500EF10D4 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3647AF9A1B335D5500EF10D4 /* DVR-iOS */; @@ -452,6 +603,88 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 2104F5401DC658590039CA14 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = DVR/Resources/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.venmo.dvr; + PRODUCT_NAME = DVR; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = 3; + }; + name = Debug; + }; + 2104F5411DC658590039CA14 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = DVR/Resources/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.venmo.dvr; + PRODUCT_NAME = DVR; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = 3; + }; + name = Release; + }; + 2104F5431DC658590039CA14 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + INFOPLIST_FILE = DVR/Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.venmo.dvr.tests; + PRODUCT_NAME = DVRTests; + SDKROOT = appletvos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + 2104F5441DC658590039CA14 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + APPLICATION_EXTENSION_API_ONLY = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + INFOPLIST_FILE = DVR/Tests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.venmo.dvr.tests; + PRODUCT_NAME = DVRTests; + SDKROOT = appletvos; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; 3647AFAD1B335D5500EF10D4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -498,9 +731,12 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0.1; TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 9.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; }; @@ -543,10 +779,13 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0.1; TARGETED_DEVICE_FAMILY = "1,2"; + TVOS_DEPLOYMENT_TARGET = 9.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Release; }; @@ -565,7 +804,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.venmo.DVR; PRODUCT_NAME = DVR; SKIP_INSTALL = YES; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -584,7 +823,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.venmo.DVR; PRODUCT_NAME = DVR; SKIP_INSTALL = YES; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -596,7 +835,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.venmo.DVR.iostests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -608,7 +847,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.venmo.DVR.iostests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -628,7 +867,7 @@ PRODUCT_NAME = DVR; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -648,7 +887,7 @@ PRODUCT_NAME = DVR; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -662,7 +901,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.venmo.DVR.osxtests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -676,13 +915,31 @@ PRODUCT_BUNDLE_IDENTIFIER = com.venmo.DVR.osxtests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 2.3; + SWIFT_VERSION = 3.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 2104F53F1DC658590039CA14 /* Build configuration list for PBXNativeTarget "DVR-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2104F5401DC658590039CA14 /* Debug */, + 2104F5411DC658590039CA14 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 2104F5421DC658590039CA14 /* Build configuration list for PBXNativeTarget "DVRTests-tvOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 2104F5431DC658590039CA14 /* Debug */, + 2104F5441DC658590039CA14 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 3647AF951B335D5500EF10D4 /* Build configuration list for PBXProject "DVR" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/DVR.xcodeproj/xcshareddata/xcschemes/DVR-tvOS.xcscheme b/DVR.xcodeproj/xcshareddata/xcschemes/DVR-tvOS.xcscheme new file mode 100644 index 0000000..e9baa1a --- /dev/null +++ b/DVR.xcodeproj/xcshareddata/xcschemes/DVR-tvOS.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DVR/Cassette.swift b/DVR/Cassette.swift index 23f5b55..6c6716c 100644 --- a/DVR/Cassette.swift +++ b/DVR/Cassette.swift @@ -18,12 +18,12 @@ struct Cassette { // MARK: - Functions - func interactionForRequest(request: NSURLRequest) -> Interaction? { + func interactionForRequest(_ request: URLRequest) -> Interaction? { for interaction in interactions { let interactionRequest = interaction.request // Note: We don't check headers right now - if interactionRequest.HTTPMethod == request.HTTPMethod && interactionRequest.URL == request.URL && interactionRequest.hasHTTPBodyEqualToThatOfRequest(request) { + if interactionRequest.httpMethod == request.httpMethod && interactionRequest.url == request.url && interactionRequest.hasHTTPBodyEqualToThatOfRequest(request) { return interaction } } @@ -33,19 +33,19 @@ struct Cassette { extension Cassette { - var dictionary: [String: AnyObject] { + var dictionary: [String: Any] { return [ - "name": name, + "name": name as Any, "interactions": interactions.map { $0.dictionary } ] } - init?(dictionary: [String: AnyObject]) { + init?(dictionary: [String: Any]) { guard let name = dictionary["name"] as? String else { return nil } self.name = name - if let array = dictionary["interactions"] as? [[String: AnyObject]] { + if let array = dictionary["interactions"] as? [[String: Any]] { interactions = array.flatMap { Interaction(dictionary: $0) } } else { interactions = [] @@ -53,14 +53,14 @@ extension Cassette { } } -private extension NSURLRequest { - func hasHTTPBodyEqualToThatOfRequest(request: NSURLRequest) -> Bool { - guard let body1 = self.HTTPBody, - body2 = request.HTTPBody, - encoded1 = Interaction.encodeBody(body1, headers: self.allHTTPHeaderFields), - encoded2 = Interaction.encodeBody(body2, headers: request.allHTTPHeaderFields) +private extension URLRequest { + func hasHTTPBodyEqualToThatOfRequest(_ request: URLRequest) -> Bool { + guard let body1 = self.httpBody, + let body2 = request.httpBody, + let encoded1 = Interaction.encodeBody(body1, headers: self.allHTTPHeaderFields), + let encoded2 = Interaction.encodeBody(body2, headers: request.allHTTPHeaderFields) else { - return self.HTTPBody == request.HTTPBody + return self.httpBody == request.httpBody } return encoded1.isEqual(encoded2) diff --git a/DVR/URLHTTPResponse.swift b/DVR/HTTPURLResponse.swift similarity index 56% rename from DVR/URLHTTPResponse.swift rename to DVR/HTTPURLResponse.swift index 3913b44..f5388f7 100644 --- a/DVR/URLHTTPResponse.swift +++ b/DVR/HTTPURLResponse.swift @@ -1,18 +1,18 @@ import Foundation -// There isn't a mutable NSHTTPURLResponse, so we have to make our own. -class URLHTTPResponse: NSHTTPURLResponse { +// There isn't a mutable HTTPURLResponse, so we have to make our own. +class HTTPURLResponse: Foundation.HTTPURLResponse { // MARK: - Properties - private var _URL: NSURL? - override var URL: NSURL? { + private var _url: URL? + override var url: URL? { get { - return _URL ?? super.URL + return _url ?? super.url } set { - _URL = newValue + _url = newValue } } @@ -27,8 +27,8 @@ class URLHTTPResponse: NSHTTPURLResponse { } } - private var _allHeaderFields: [NSObject : AnyObject]? - override var allHeaderFields: [NSObject : AnyObject] { + private var _allHeaderFields: [AnyHashable: Any]? + override var allHeaderFields: [AnyHashable: Any] { get { return _allHeaderFields ?? super.allHeaderFields } @@ -40,8 +40,8 @@ class URLHTTPResponse: NSHTTPURLResponse { } -extension NSHTTPURLResponse { - override var dictionary: [String: AnyObject] { +extension Foundation.HTTPURLResponse { + override var dictionary: [String: Any] { var dictionary = super.dictionary dictionary["headers"] = allHeaderFields @@ -52,13 +52,11 @@ extension NSHTTPURLResponse { } -extension URLHTTPResponse { - convenience init(dictionary: [String: AnyObject]) { - self.init() +extension HTTPURLResponse { + convenience init(dictionary: [String: Any]) { + let url = URL(string: dictionary["url"] as! String)! - if let string = dictionary["url"] as? String, url = NSURL(string: string) { - URL = url - } + self.init(url: url, mimeType: nil, expectedContentLength: 0, textEncodingName: nil) if let headers = dictionary["headers"] as? [String: String] { allHeaderFields = headers diff --git a/DVR/Interaction.swift b/DVR/Interaction.swift index 4262afe..e32ae00 100644 --- a/DVR/Interaction.swift +++ b/DVR/Interaction.swift @@ -4,15 +4,15 @@ struct Interaction { // MARK: - Properties - let request: NSURLRequest - let response: NSURLResponse - let responseData: NSData? - let recordedAt: NSDate + let request: URLRequest + let response: Foundation.URLResponse + let responseData: Data? + let recordedAt: Date // MARK: - Initializers - init(request: NSURLRequest, response: NSURLResponse, responseData: NSData? = nil, recordedAt: NSDate = NSDate()) { + init(request: URLRequest, response: Foundation.URLResponse, responseData: Data? = nil, recordedAt: Date = Date()) { self.request = request self.response = response self.responseData = responseData @@ -22,18 +22,18 @@ struct Interaction { // MARK: - Encoding - static func encodeBody(body: NSData, headers: [String: String]? = nil) -> AnyObject? { + static func encodeBody(_ body: Data, headers: [String: String]? = nil) -> AnyObject? { if let contentType = headers?["Content-Type"] { // Text if contentType.hasPrefix("text/") { // TODO: Use text encoding if specified in headers - return NSString(data: body, encoding: NSUTF8StringEncoding) + return NSString(data: body, encoding: String.Encoding.utf8.rawValue) } // JSON if contentType.hasPrefix("application/json") { do { - return try NSJSONSerialization.JSONObjectWithData(body, options: []) + return try JSONSerialization.jsonObject(with: body, options: []) as AnyObject } catch { return nil } @@ -41,23 +41,23 @@ struct Interaction { } // Base64 - return body.base64EncodedStringWithOptions([]) + return body.base64EncodedString(options: []) as AnyObject? } - static func dencodeBody(body: AnyObject?, headers: [String: String]? = nil) -> NSData? { + static func dencodeBody(_ body: Any?, headers: [String: String]? = nil) -> Data? { guard let body = body else { return nil } if let contentType = headers?["Content-Type"] { // Text - if let string = body as? String where contentType.hasPrefix("text/") { + if let string = body as? String , contentType.hasPrefix("text/") { // TODO: Use encoding if specified in headers - return string.dataUsingEncoding(NSUTF8StringEncoding) + return string.data(using: String.Encoding.utf8) } // JSON if contentType.hasPrefix("application/json") { do { - return try NSJSONSerialization.dataWithJSONObject(body, options: []) + return try JSONSerialization.data(withJSONObject: body, options: []) } catch { return nil } @@ -66,7 +66,7 @@ struct Interaction { // Base64 if let base64 = body as? String { - return NSData(base64EncodedString: base64, options: []) + return Data(base64Encoded: base64, options: []) } return nil @@ -75,14 +75,14 @@ struct Interaction { extension Interaction { - var dictionary: [String: AnyObject] { - var dictionary: [String: AnyObject] = [ + var dictionary: [String: Any] { + var dictionary: [String: Any] = [ "request": request.dictionary, "recorded_at": recordedAt.timeIntervalSince1970 ] var response = self.response.dictionary - if let data = responseData, body = Interaction.encodeBody(data, headers: response["headers"] as? [String: String]) { + if let data = responseData, let body = Interaction.encodeBody(data, headers: response["headers"] as? [String: String]) { response["body"] = body } dictionary["response"] = response @@ -90,14 +90,14 @@ extension Interaction { return dictionary } - init?(dictionary: [String: AnyObject]) { - guard let request = dictionary["request"] as? [String: AnyObject], - response = dictionary["response"] as? [String: AnyObject], - recordedAt = dictionary["recorded_at"] as? Int else { return nil } + init?(dictionary: [String: Any]) { + guard let request = dictionary["request"] as? [String: Any], + let response = dictionary["response"] as? [String: Any], + let recordedAt = dictionary["recorded_at"] as? Int else { return nil } - self.request = NSMutableURLRequest(dictionary: request) - self.response = URLHTTPResponse(dictionary: response) - self.recordedAt = NSDate(timeIntervalSince1970: NSTimeInterval(recordedAt)) + self.request = NSMutableURLRequest(dictionary: request) as URLRequest + self.response = HTTPURLResponse(dictionary: response) + self.recordedAt = Date(timeIntervalSince1970: TimeInterval(recordedAt)) self.responseData = Interaction.dencodeBody(response["body"], headers: response["headers"] as? [String: String]) } } diff --git a/DVR/Resources/Info.plist b/DVR/Resources/Info.plist index f42ccf5..c941094 100644 --- a/DVR/Resources/Info.plist +++ b/DVR/Resources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.3.0 + 0.4.0 CFBundleSignature ???? CFBundleVersion diff --git a/DVR/Session.swift b/DVR/Session.swift index a09fa2a..058426e 100644 --- a/DVR/Session.swift +++ b/DVR/Session.swift @@ -1,29 +1,29 @@ import Foundation -public class Session: NSURLSession { +open class Session: URLSession { // MARK: - Properties - public var outputDirectory: String - public let cassetteName: String - public let backingSession: NSURLSession - public var recordingEnabled = true + open var outputDirectory: String + open let cassetteName: String + open let backingSession: URLSession + open var recordingEnabled = true - private let testBundle: NSBundle + private let testBundle: Bundle private var recording = false private var needsPersistence = false - private var outstandingTasks = [NSURLSessionTask]() + private var outstandingTasks = [URLSessionTask]() private var completedInteractions = [Interaction]() - private var completionBlock: (Void -> Void)? + private var completionBlock: ((Void) -> Void)? - override public var delegate: NSURLSessionDelegate? { + override open var delegate: URLSessionDelegate? { return backingSession.delegate } // MARK: - Initializers - public init(outputDirectory: String = "~/Desktop/DVR/", cassetteName: String, testBundle: NSBundle = NSBundle.allBundles().filter() { $0.bundlePath.hasSuffix(".xctest") }.first!, backingSession: NSURLSession = NSURLSession.sharedSession()) { + public init(outputDirectory: String = "~/Desktop/DVR/", cassetteName: String, testBundle: Bundle = Bundle.allBundles.filter() { $0.bundlePath.hasSuffix(".xctest") }.first!, backingSession: URLSession = URLSession.shared) { self.outputDirectory = outputDirectory self.cassetteName = cassetteName self.testBundle = testBundle @@ -32,43 +32,43 @@ public class Session: NSURLSession { } - // MARK: - NSURLSession + // MARK: - URLSession - public override func dataTaskWithRequest(request: NSURLRequest) -> NSURLSessionDataTask { + open override func dataTask(with request: URLRequest) -> URLSessionDataTask { return addDataTask(request) } - public override func dataTaskWithRequest(request: NSURLRequest, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDataTask { + open override func dataTask(with request: URLRequest, completionHandler: @escaping ((Data?, Foundation.URLResponse?, Error?) -> Void)) -> URLSessionDataTask { return addDataTask(request, completionHandler: completionHandler) } - public override func downloadTaskWithRequest(request: NSURLRequest) -> NSURLSessionDownloadTask { + open override func downloadTask(with request: URLRequest) -> URLSessionDownloadTask { return addDownloadTask(request) } - public override func downloadTaskWithRequest(request: NSURLRequest, completionHandler: (NSURL?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDownloadTask { + open override func downloadTask(with request: URLRequest, completionHandler: @escaping (URL?, Foundation.URLResponse?, Error?) -> Void) -> URLSessionDownloadTask { return addDownloadTask(request, completionHandler: completionHandler) } - public override func uploadTaskWithRequest(request: NSURLRequest, fromData bodyData: NSData) -> NSURLSessionUploadTask { + open override func uploadTask(with request: URLRequest, from bodyData: Data) -> URLSessionUploadTask { return addUploadTask(request, fromData: bodyData) } - public override func uploadTaskWithRequest(request: NSURLRequest, fromData bodyData: NSData?, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionUploadTask { + open override func uploadTask(with request: URLRequest, from bodyData: Data?, completionHandler: @escaping (Data?, Foundation.URLResponse?, Error?) -> Void) -> URLSessionUploadTask { return addUploadTask(request, fromData: bodyData, completionHandler: completionHandler) } - public override func uploadTaskWithRequest(request: NSURLRequest, fromFile fileURL: NSURL) -> NSURLSessionUploadTask { - let data = NSData(contentsOfURL: fileURL)! + open override func uploadTask(with request: URLRequest, fromFile fileURL: URL) -> URLSessionUploadTask { + let data = try! Data(contentsOf: fileURL) return addUploadTask(request, fromData: data) } - public override func uploadTaskWithRequest(request: NSURLRequest, fromFile fileURL: NSURL, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionUploadTask { - let data = NSData(contentsOfURL: fileURL)! + open override func uploadTask(with request: URLRequest, fromFile fileURL: URL, completionHandler: @escaping (Data?, Foundation.URLResponse?, Error?) -> Void) -> URLSessionUploadTask { + let data = try! Data(contentsOf: fileURL) return addUploadTask(request, fromData: data, completionHandler: completionHandler) } - public override func invalidateAndCancel() { + open override func invalidateAndCancel() { recording = false outstandingTasks.removeAll() backingSession.invalidateAndCancel() @@ -78,7 +78,7 @@ public class Session: NSURLSession { // MARK: - Recording /// You don’t need to call this method if you're only recoding one request. - public func beginRecording() { + open func beginRecording() { if recording { return } @@ -93,7 +93,7 @@ public class Session: NSURLSession { /// This only needs to be called if you call `beginRecording`. `completion` will be called on the main queue after /// the completion block of the last task is called. `completion` is useful for fulfilling an expectation you setup /// before calling `beginRecording`. - public func endRecording(completion: (Void -> Void)? = nil) { + open func endRecording(_ completion: ((Void) -> Void)? = nil) { if !recording { return } @@ -110,20 +110,20 @@ public class Session: NSURLSession { // MARK: - Internal var cassette: Cassette? { - guard let path = testBundle.pathForResource(cassetteName, ofType: "json"), - data = NSData(contentsOfFile: path), - raw = try? NSJSONSerialization.JSONObjectWithData(data, options: []), - json = raw as? [String: AnyObject] + guard let path = testBundle.path(forResource: cassetteName, ofType: "json"), + let data = try? Data(contentsOf: URL(fileURLWithPath: path)), + let raw = try? JSONSerialization.jsonObject(with: data, options: []), + let json = raw as? [String: Any] else { return nil } return Cassette(dictionary: json) } - func finishTask(task: NSURLSessionTask, interaction: Interaction, playback: Bool) { + func finishTask(_ task: URLSessionTask, interaction: Interaction, playback: Bool) { needsPersistence = needsPersistence || !playback - if let index = outstandingTasks.indexOf(task) { - outstandingTasks.removeAtIndex(index) + if let index = outstandingTasks.index(of: task) { + outstandingTasks.remove(at: index) } completedInteractions.append(interaction) @@ -132,41 +132,41 @@ public class Session: NSURLSession { finishRecording() } - if let delegate = delegate as? NSURLSessionDataDelegate, task = task as? NSURLSessionDataTask, data = interaction.responseData { - delegate.URLSession?(self, dataTask: task, didReceiveData: data) + if let delegate = delegate as? URLSessionDataDelegate, let task = task as? URLSessionDataTask, let data = interaction.responseData { + delegate.urlSession?(self, dataTask: task, didReceive: data as Data) } - if let delegate = delegate as? NSURLSessionTaskDelegate { - delegate.URLSession?(self, task: task, didCompleteWithError: nil) + if let delegate = delegate as? URLSessionTaskDelegate { + delegate.urlSession?(self, task: task, didCompleteWithError: nil) } } // MARK: - Private - private func addDataTask(request: NSURLRequest, completionHandler: ((NSData?, NSURLResponse?, NSError?) -> Void)? = nil) -> NSURLSessionDataTask { - let modifiedRequest = backingSession.configuration.HTTPAdditionalHeaders.map(request.requestByAppendingHeaders) ?? request + private func addDataTask(_ request: URLRequest, completionHandler: ((Data?, Foundation.URLResponse?, NSError?) -> Void)? = nil) -> URLSessionDataTask { + let modifiedRequest = backingSession.configuration.httpAdditionalHeaders.map(request.appending) ?? request let task = SessionDataTask(session: self, request: modifiedRequest, completion: completionHandler) addTask(task) return task } - private func addDownloadTask(request: NSURLRequest, completionHandler: SessionDownloadTask.Completion? = nil) -> NSURLSessionDownloadTask { - let modifiedRequest = backingSession.configuration.HTTPAdditionalHeaders.map(request.requestByAppendingHeaders) ?? request + private func addDownloadTask(_ request: URLRequest, completionHandler: SessionDownloadTask.Completion? = nil) -> URLSessionDownloadTask { + let modifiedRequest = backingSession.configuration.httpAdditionalHeaders.map(request.appending) ?? request let task = SessionDownloadTask(session: self, request: modifiedRequest, completion: completionHandler) addTask(task) return task } - private func addUploadTask(request: NSURLRequest, fromData data: NSData?, completionHandler: SessionUploadTask.Completion? = nil) -> NSURLSessionUploadTask { - var modifiedRequest = backingSession.configuration.HTTPAdditionalHeaders.map(request.requestByAppendingHeaders) ?? request - modifiedRequest = data.map(modifiedRequest.requestWithBody) ?? modifiedRequest + private func addUploadTask(_ request: URLRequest, fromData data: Data?, completionHandler: SessionUploadTask.Completion? = nil) -> URLSessionUploadTask { + var modifiedRequest = backingSession.configuration.httpAdditionalHeaders.map(request.appending) ?? request + modifiedRequest = data.map(modifiedRequest.appending) ?? modifiedRequest let task = SessionUploadTask(session: self, request: modifiedRequest, completion: completionHandler) addTask(task.dataTask) return task } - private func addTask(task: NSURLSessionTask) { + private func addTask(_ task: URLSessionTask) { let shouldRecord = !recording if shouldRecord { beginRecording() @@ -179,20 +179,20 @@ public class Session: NSURLSession { } } - private func persist(interactions: [Interaction]) { + private func persist(_ interactions: [Interaction]) { defer { abort() } // Create directory - let outputDirectory = (self.outputDirectory as NSString).stringByExpandingTildeInPath - let fileManager = NSFileManager.defaultManager() - if !fileManager.fileExistsAtPath(outputDirectory) { - do { - try fileManager.createDirectoryAtPath(outputDirectory, withIntermediateDirectories: true, attributes: nil) - } catch { - print("[DVR] Failed to create cassettes directory.") - } + let outputDirectory = (self.outputDirectory as NSString).expandingTildeInPath + let fileManager = FileManager.default + if !fileManager.fileExists(atPath: outputDirectory) { + do { + try fileManager.createDirectory(atPath: outputDirectory, withIntermediateDirectories: true, attributes: nil) + } catch { + print("[DVR] Failed to create cassettes directory.") + } } let cassette = Cassette(name: cassetteName, interactions: interactions) @@ -201,19 +201,20 @@ public class Session: NSURLSession { do { - let outputPath = ((outputDirectory as NSString).stringByAppendingPathComponent(cassetteName) as NSString).stringByAppendingPathExtension("json")! - let data = try NSJSONSerialization.dataWithJSONObject(cassette.dictionary, options: [.PrettyPrinted]) + let outputPath = ((outputDirectory as NSString).appendingPathComponent(cassetteName) as NSString).appendingPathExtension("json")! + let data = try JSONSerialization.data(withJSONObject: cassette.dictionary, options: [.prettyPrinted]) // Add trailing new line - guard var string = NSString(data: data, encoding: NSUTF8StringEncoding) else { + guard var string = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { print("[DVR] Failed to persist cassette.") return } - string = string.stringByAppendingString("\n") + string = string.appending("\n") as NSString - if let data = string.dataUsingEncoding(NSUTF8StringEncoding) { - data.writeToFile(outputPath, atomically: true) + if let data = string.data(using: String.Encoding.utf8.rawValue) { + try? data.write(to: URL(fileURLWithPath: outputPath), options: [.atomic]) print("[DVR] Persisted cassette at \(outputPath). Please add this file to your test target") + return } print("[DVR] Failed to persist cassette.") diff --git a/DVR/SessionDataTask.swift b/DVR/SessionDataTask.swift index 5a12d09..8ac1a3a 100644 --- a/DVR/SessionDataTask.swift +++ b/DVR/SessionDataTask.swift @@ -1,35 +1,35 @@ import Foundation -class SessionDataTask: NSURLSessionDataTask { +final class SessionDataTask: URLSessionDataTask { // MARK: - Types - typealias Completion = (NSData?, NSURLResponse?, NSError?) -> Void + typealias Completion = (Data?, Foundation.URLResponse?, NSError?) -> Void // MARK: - Properties weak var session: Session! - let request: NSURLRequest + let request: URLRequest let completion: Completion? - private let queue = dispatch_queue_create("com.venmo.DVR.sessionDataTaskQueue", nil) + private let queue = DispatchQueue(label: "com.venmo.DVR.sessionDataTaskQueue", attributes: []) private var interaction: Interaction? - override var response: NSURLResponse? { + override var response: Foundation.URLResponse? { return interaction?.response } // MARK: - Initializers - init(session: Session, request: NSURLRequest, completion: (Completion)? = nil) { + init(session: Session, request: URLRequest, completion: (Completion)? = nil) { self.session = session self.request = request self.completion = completion } - // MARK: - NSURLSessionTask + // MARK: - URLSessionTask override func cancel() { // Don't do anything @@ -43,7 +43,7 @@ class SessionDataTask: NSURLSessionDataTask { self.interaction = interaction // Forward completion if let completion = completion { - dispatch_async(queue) { + queue.async { completion(interaction.responseData, interaction.response, nil) } } @@ -60,7 +60,7 @@ class SessionDataTask: NSURLSessionDataTask { fatalError("[DVR] Recording is disabled.") } - let task = session.backingSession.dataTaskWithRequest(request) { [weak self] data, response, error in + let task = session.backingSession.dataTask(with: request, completionHandler: { [weak self] data, response, error in //Ensure we have a response guard let response = response else { @@ -72,14 +72,14 @@ class SessionDataTask: NSURLSessionDataTask { } // Still call the completion block so the user can chain requests while recording. - dispatch_async(this.queue) { + this.queue.async { this.completion?(data, response, nil) } // Create interaction this.interaction = Interaction(request: this.request, response: response, responseData: data) this.session.finishTask(this, interaction: this.interaction!, playback: false) - } + }) task.resume() } } diff --git a/DVR/SessionDownloadTask.swift b/DVR/SessionDownloadTask.swift index c28bce6..d60139b 100644 --- a/DVR/SessionDownloadTask.swift +++ b/DVR/SessionDownloadTask.swift @@ -1,25 +1,25 @@ -class SessionDownloadTask: NSURLSessionDownloadTask { +final class SessionDownloadTask: URLSessionDownloadTask { // MARK: - Types - typealias Completion = (NSURL?, NSURLResponse?, NSError?) -> Void + typealias Completion = (URL?, Foundation.URLResponse?, NSError?) -> Void // MARK: - Properties weak var session: Session! - let request: NSURLRequest + let request: URLRequest let completion: Completion? // MARK: - Initializers - init(session: Session, request: NSURLRequest, completion: Completion? = nil) { + init(session: Session, request: URLRequest, completion: Completion? = nil) { self.session = session self.request = request self.completion = completion } - // MARK: - NSURLSessionTask + // MARK: - URLSessionTask override func cancel() { // Don't do anything @@ -27,11 +27,11 @@ class SessionDownloadTask: NSURLSessionDownloadTask { override func resume() { let task = SessionDataTask(session: session, request: request) { data, response, error in - let location: NSURL? + let location: URL? if let data = data { // Write data to temporary file - let tempURL = NSURL(fileURLWithPath: (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent(NSUUID().UUIDString)) - data.writeToURL(tempURL, atomically: true) + let tempURL = URL(fileURLWithPath: (NSTemporaryDirectory() as NSString).appendingPathComponent(UUID().uuidString)) + try? data.write(to: tempURL, options: [.atomic]) location = tempURL } else { location = nil diff --git a/DVR/SessionUploadTask.swift b/DVR/SessionUploadTask.swift index b6b3b78..b316f86 100644 --- a/DVR/SessionUploadTask.swift +++ b/DVR/SessionUploadTask.swift @@ -1,26 +1,26 @@ -class SessionUploadTask: NSURLSessionUploadTask { +final class SessionUploadTask: URLSessionUploadTask { // MARK: - Types - typealias Completion = (NSData?, NSURLResponse?, NSError?) -> Void + typealias Completion = (Data?, Foundation.URLResponse?, NSError?) -> Void // MARK: - Properties weak var session: Session! - let request: NSURLRequest + let request: URLRequest let completion: Completion? let dataTask: SessionDataTask // MARK: - Initializers - init(session: Session, request: NSURLRequest, completion: Completion? = nil) { + init(session: Session, request: URLRequest, completion: Completion? = nil) { self.session = session self.request = request self.completion = completion dataTask = SessionDataTask(session: session, request: request, completion: completion) } - // MARK: - NSURLSessionTask + // MARK: - URLSessionTask override func cancel() { // Don't do anything diff --git a/DVR/Tests/Fixtures/upload-data.json b/DVR/Tests/Fixtures/upload-data.json index 87cf312..ed8efe5 100644 --- a/DVR/Tests/Fixtures/upload-data.json +++ b/DVR/Tests/Fixtures/upload-data.json @@ -40,7 +40,7 @@ }, "request" : { "method" : "POST", - "body" : "LS08MmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMzMgNmI2YzY2NjUgNmU2MTZjNmIgNzM2YTY2NmMgNmI2YTZmNjkgMzk2MTc1NjYgMzgzOTY1NzMgNjg2MTZhNzMgNmU2YzMzNmIgNmE2ZTc3NjEgNmM+DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImZpbGUiDQoNCnRlc3QgZmlsZQoNCi0tPDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDMzIDZiNmM2NjY1IDZlNjE2YzZiIDczNmE2NjZjIDZiNmE2ZjY5IDM5NjE3NTY2IDM4Mzk2NTczIDY4NjE2YTczIDZlNmMzMzZiIDZhNmU3NzYxIDZjPi0tDQo=", + "body" : "LS02NSBieXRlcw0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWxlIg0KDQp0ZXN0IGZpbGUKDQotLTY1IGJ5dGVzLS0NCg==", "url" : "https:\/\/httpbin.org\/post", "headers" : { "Content-Type" : "multipart\/form-data; boundary=<2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d33 6b6c6665 6e616c6b 736a666c 6b6a6f69 39617566 38396573 68616a73 6e6c336b 6a6e7761 6c>" diff --git a/DVR/Tests/Fixtures/upload-file.json b/DVR/Tests/Fixtures/upload-file.json index ea4a0a3..5a1c278 100644 --- a/DVR/Tests/Fixtures/upload-file.json +++ b/DVR/Tests/Fixtures/upload-file.json @@ -40,7 +40,7 @@ }, "request" : { "method" : "POST", - "body" : "LS08MmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMmQgMmQyZDJkMzMgNmI2YzY2NjUgNmU2MTZjNmIgNzM2YTY2NmMgNmI2YTZmNjkgMzk2MTc1NjYgMzgzOTY1NzMgNjg2MTZhNzMgNmU2YzMzNmIgNmE2ZTc3NjEgNmM+DQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImZpbGUiDQoNCnRlc3QgZmlsZQoNCi0tPDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDJkIDJkMmQyZDMzIDZiNmM2NjY1IDZlNjE2YzZiIDczNmE2NjZjIDZiNmE2ZjY5IDM5NjE3NTY2IDM4Mzk2NTczIDY4NjE2YTczIDZlNmMzMzZiIDZhNmU3NzYxIDZjPi0tDQo=", + "body" : "LS02NSBieXRlcw0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWxlIg0KDQp0ZXN0IGZpbGUKDQotLTY1IGJ5dGVzLS0NCg==", "url" : "https:\/\/httpbin.org\/post", "headers" : { "Content-Type" : "multipart\/form-data; boundary=<2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d2d 2d2d2d33 6b6c6665 6e616c6b 736a666c 6b6a6f69 39617566 38396573 68616a73 6e6c336b 6a6e7761 6c>" diff --git a/DVR/Tests/SessionTests.swift b/DVR/Tests/SessionTests.swift index 3ff4e05..91ede95 100644 --- a/DVR/Tests/SessionTests.swift +++ b/DVR/Tests/SessionTests.swift @@ -3,25 +3,25 @@ import XCTest class SessionTests: XCTestCase { let session: Session = { - let configuration = NSURLSessionConfiguration.defaultSessionConfiguration() - configuration.HTTPAdditionalHeaders = ["testSessionHeader": "testSessionHeaderValue"] - let backingSession = NSURLSession(configuration: configuration) + let configuration = URLSessionConfiguration.default + configuration.httpAdditionalHeaders = ["testSessionHeader": "testSessionHeaderValue"] + let backingSession = URLSession(configuration: configuration) return Session(cassetteName: "example", backingSession: backingSession) }() - let request = NSURLRequest(URL: NSURL(string: "http://example.com")!) + let request = URLRequest(url: URL(string: "http://example.com")!) func testInit() { XCTAssertEqual("example", session.cassetteName) } func testDataTask() { - let request = NSURLRequest(URL: NSURL(string: "http://example.com")!) - let dataTask = session.dataTaskWithRequest(request) + let request = URLRequest(url: URL(string: "http://example.com")!) + let dataTask = session.dataTask(with: request) XCTAssert(dataTask is SessionDataTask) - if let dataTask = dataTask as? SessionDataTask, headers = dataTask.request.allHTTPHeaderFields { + if let dataTask = dataTask as? SessionDataTask, let headers = dataTask.request.allHTTPHeaderFields { XCTAssert(headers["testSessionHeader"] == "testSessionHeaderValue") } else { XCTFail() @@ -29,12 +29,12 @@ class SessionTests: XCTestCase { } func testDataTaskWithCompletion() { - let request = NSURLRequest(URL: NSURL(string: "http://example.com")!) - let dataTask = session.dataTaskWithRequest(request) { _, _, _ in return } + let request = URLRequest(url: URL(string: "http://example.com")!) + let dataTask = session.dataTask(with: request, completionHandler: { _, _, _ in return }) XCTAssert(dataTask is SessionDataTask) - if let dataTask = dataTask as? SessionDataTask, headers = dataTask.request.allHTTPHeaderFields { + if let dataTask = dataTask as? SessionDataTask, let headers = dataTask.request.allHTTPHeaderFields { XCTAssert(headers["testSessionHeader"] == "testSessionHeaderValue") } else { XCTFail() @@ -43,85 +43,85 @@ class SessionTests: XCTestCase { func testPlayback() { session.recordingEnabled = false - let expectation = expectationWithDescription("Network") + let expectation = self.expectation(description: "Network") - session.dataTaskWithRequest(request) { data, response, error in - XCTAssertEqual("hello", String(data: data!, encoding: NSUTF8StringEncoding)) + session.dataTask(with: request, completionHandler: { data, response, error in + XCTAssertEqual("hello", String(data: data!, encoding: String.Encoding.utf8)) - let HTTPResponse = response as! NSHTTPURLResponse - XCTAssertEqual(200, HTTPResponse.statusCode) + let httpResponse = response as! Foundation.HTTPURLResponse + XCTAssertEqual(200, httpResponse.statusCode) expectation.fulfill() - }.resume() + }) .resume() - waitForExpectationsWithTimeout(1, handler: nil) + waitForExpectations(timeout: 1, handler: nil) } func testTextPlayback() { let session = Session(cassetteName: "text") session.recordingEnabled = false - let request = NSMutableURLRequest(URL: NSURL(string: "http://example.com")!) - request.HTTPMethod = "POST" - request.HTTPBody = "Some text.".dataUsingEncoding(NSUTF8StringEncoding) + var request = URLRequest(url: URL(string: "http://example.com")!) + request.httpMethod = "POST" + request.httpBody = "Some text.".data(using: String.Encoding.utf8) request.setValue("text/plain", forHTTPHeaderField: "Content-Type") - let expectation = expectationWithDescription("Network") + let expectation = self.expectation(description: "Network") - session.dataTaskWithRequest(request) { data, response, error in - XCTAssertEqual("hello", String(data: data!, encoding: NSUTF8StringEncoding)) + session.dataTask(with: request, completionHandler: { data, response, error in + XCTAssertEqual("hello", String(data: data!, encoding: String.Encoding.utf8)) - let HTTPResponse = response as! NSHTTPURLResponse - XCTAssertEqual(200, HTTPResponse.statusCode) + let httpResponse = response as! Foundation.HTTPURLResponse + XCTAssertEqual(200, httpResponse.statusCode) expectation.fulfill() - }.resume() + }) .resume() - waitForExpectationsWithTimeout(1, handler: nil) + waitForExpectations(timeout: 1, handler: nil) } func testDownload() { - let expectation = expectationWithDescription("Network") + let expectation = self.expectation(description: "Network") let session = Session(cassetteName: "json-example") session.recordingEnabled = false - let request = NSURLRequest(URL: NSURL(string: "https://www.howsmyssl.com/a/check")!) + let request = URLRequest(url: URL(string: "https://www.howsmyssl.com/a/check")!) - session.downloadTaskWithRequest(request) { location, response, error in - let data = NSData(contentsOfURL: location!)! + session.downloadTask(with: request, completionHandler: { location, response, error in + let data = try! Data(contentsOf: location!) do { - let JSON = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] + let JSON = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] XCTAssertEqual("TLS 1.2", JSON?["tls_version"] as? String) } catch { XCTFail("Failed to read JSON.") } - let HTTPResponse = response as! NSHTTPURLResponse - XCTAssertEqual(200, HTTPResponse.statusCode) + let httpResponse = response as! Foundation.HTTPURLResponse + XCTAssertEqual(200, httpResponse.statusCode) expectation.fulfill() - }.resume() + }) .resume() - waitForExpectationsWithTimeout(1, handler: nil) + waitForExpectations(timeout: 1, handler: nil) } func testMultiple() { - let expectation = expectationWithDescription("Network") + let expectation = self.expectation(description: "Network") let session = Session(cassetteName: "multiple") session.beginRecording() - let apple = expectationWithDescription("Apple") - let google = expectationWithDescription("Google") + let apple = self.expectation(description: "Apple") + let google = self.expectation(description: "Google") - session.dataTaskWithRequest(NSURLRequest(URL: NSURL(string: "http://apple.com")!)) { _, response, _ in - XCTAssertEqual(200, (response as? NSHTTPURLResponse)?.statusCode) + session.dataTask(with: URLRequest(url: URL(string: "http://apple.com")!), completionHandler: { _, response, _ in + XCTAssertEqual(200, (response as? Foundation.HTTPURLResponse)?.statusCode) - dispatch_async(dispatch_get_main_queue()) { - session.dataTaskWithRequest(NSURLRequest(URL: NSURL(string: "http://google.com")!)) { _, response, _ in - XCTAssertEqual(200, (response as? NSHTTPURLResponse)?.statusCode) + DispatchQueue.main.async { + session.dataTask(with: URLRequest(url: URL(string: "http://google.com")!), completionHandler: { _, response, _ in + XCTAssertEqual(200, (response as? Foundation.HTTPURLResponse)?.statusCode) google.fulfill() - }.resume() + }) .resume() session.endRecording() { expectation.fulfill() @@ -129,62 +129,62 @@ class SessionTests: XCTestCase { } apple.fulfill() - }.resume() + }) .resume() - waitForExpectationsWithTimeout(1, handler: nil) + waitForExpectations(timeout: 1, handler: nil) } func testTaskDelegate() { - class Delegate: NSObject, NSURLSessionTaskDelegate { + class Delegate: NSObject, URLSessionTaskDelegate { let expectation: XCTestExpectation - var response: NSURLResponse? + var response: Foundation.URLResponse? init(expectation: XCTestExpectation) { self.expectation = expectation } - @objc private func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) { + @objc fileprivate func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { response = task.response expectation.fulfill() } } - let expectation = expectationWithDescription("didCompleteWithError") + let expectation = self.expectation(description: "didCompleteWithError") let delegate = Delegate(expectation: expectation) - let config = NSURLSessionConfiguration.defaultSessionConfiguration() - let backingSession = NSURLSession(configuration: config, delegate: delegate, delegateQueue: nil) + let config = URLSessionConfiguration.default + let backingSession = URLSession(configuration: config, delegate: delegate, delegateQueue: nil) let session = Session(cassetteName: "example", backingSession: backingSession) session.recordingEnabled = false - let task = session.dataTaskWithRequest(request) + let task = session.dataTask(with: request) task.resume() - waitForExpectationsWithTimeout(1, handler: nil) + waitForExpectations(timeout: 1, handler: nil) } func testDataDelegate() { - class Delegate: NSObject, NSURLSessionDataDelegate { + class Delegate: NSObject, URLSessionDataDelegate { let expectation: XCTestExpectation init(expectation: XCTestExpectation) { self.expectation = expectation } - @objc func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { + @objc func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { expectation.fulfill() } } - let expectation = expectationWithDescription("didCompleteWithError") + let expectation = self.expectation(description: "didCompleteWithError") let delegate = Delegate(expectation: expectation) - let config = NSURLSessionConfiguration.defaultSessionConfiguration() - let backingSession = NSURLSession(configuration: config, delegate: delegate, delegateQueue: nil) + let config = URLSessionConfiguration.default + let backingSession = URLSession(configuration: config, delegate: delegate, delegateQueue: nil) let session = Session(cassetteName: "example", backingSession: backingSession) session.recordingEnabled = false - let task = session.dataTaskWithRequest(request) + let task = session.dataTask(with: request) task.resume() - waitForExpectationsWithTimeout(1, handler: nil) + waitForExpectations(timeout: 1, handler: nil) } } diff --git a/DVR/Tests/SessionUploadTests.swift b/DVR/Tests/SessionUploadTests.swift index 99b841c..bd29045 100644 --- a/DVR/Tests/SessionUploadTests.swift +++ b/DVR/Tests/SessionUploadTests.swift @@ -1,113 +1,111 @@ import XCTest -@testable import DVR +import DVR class SessionUploadTests: XCTestCase { - lazy var request: NSURLRequest = { - let request = NSMutableURLRequest(URL: NSURL(string: "https://httpbin.org/post")!) - request.HTTPMethod = "POST" + lazy var request: URLRequest = { + var request = URLRequest(url: URL(string: "https://httpbin.org/post")!) + request.httpMethod = "POST" let contentType = "multipart/form-data; boundary=\(self.multipartBoundary)" request.addValue(contentType, forHTTPHeaderField: "Content-Type") - return request + return request as URLRequest }() - let multipartBoundary = "---------------------------3klfenalksjflkjoi9auf89eshajsnl3kjnwal".UTF8Data() - lazy var testFile: NSURL = { - return NSBundle(forClass: self.dynamicType).URLForResource("testfile", withExtension: "txt")! + let multipartBoundary = "---------------------------3klfenalksjflkjoi9auf89eshajsnl3kjnwal".utf8Data + lazy var testFile: URL = { + return Bundle(for: type(of: self)).url(forResource: "testfile", withExtension: "txt")! }() func testUploadFile() { let session = Session(cassetteName: "upload-file") session.recordingEnabled = false - let expectation = expectationWithDescription("Network") + let expectation = self.expectation(description: "Network") - let data = encodeMultipartBody(NSData(contentsOfURL: testFile)!, parameters: [:]) + let data = encodeMultipartBody(try! Data(contentsOf: testFile), parameters: [:]) let file = writeDataToFile(data, fileName: "upload-file") - session.uploadTaskWithRequest(request, fromFile: file) { data, response, error in + session.uploadTask(with: request, fromFile: file, completionHandler: { data, response, error in do { - let JSON = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String: AnyObject] - XCTAssertEqual("test file\n", (JSON?["form"] as? [String: AnyObject])?["file"] as? String) + let JSON = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any] + XCTAssertEqual("test file\n", (JSON?["form"] as? [String: Any])?["file"] as? String) } catch { XCTFail("Failed to read JSON.") } - let HTTPResponse = response as! NSHTTPURLResponse + let HTTPResponse = response as! HTTPURLResponse XCTAssertEqual(200, HTTPResponse.statusCode) expectation.fulfill() - }.resume() + }) .resume() - waitForExpectationsWithTimeout(4, handler: nil) + waitForExpectations(timeout: 4, handler: nil) } func testUploadData() { let session = Session(cassetteName: "upload-data") session.recordingEnabled = false - let expectation = expectationWithDescription("Network") + let expectation = self.expectation(description: "Network") - let data = encodeMultipartBody(NSData(contentsOfURL: testFile)!, parameters: [:]) + let data = encodeMultipartBody(try! Data(contentsOf: testFile), parameters: [:]) - session.uploadTaskWithRequest(request, fromData: data) { data, response, error in + session.uploadTask(with: request, from: data) { data, response, error in do { - let JSON = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String: AnyObject] - XCTAssertEqual("test file\n", (JSON?["form"] as? [String: AnyObject])?["file"] as? String) + let JSON = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any] + XCTAssertEqual("test file\n", (JSON?["form"] as? [String: Any])?["file"] as? String) } catch { XCTFail("Failed to read JSON.") } - let HTTPResponse = response as! NSHTTPURLResponse + let HTTPResponse = response as! HTTPURLResponse XCTAssertEqual(200, HTTPResponse.statusCode) expectation.fulfill() }.resume() - waitForExpectationsWithTimeout(4, handler: nil) + waitForExpectations(timeout: 4, handler: nil) } + // MARK: Helpers - func encodeMultipartBody(data: NSData, parameters: [String: AnyObject]) -> NSData { - let delim = "--\(multipartBoundary)\r\n".UTF8Data() + private func encodeMultipartBody(_ data: Data, parameters: [String: Any]) -> Data { + let delim = "--\(multipartBoundary)\r\n".utf8Data let body = NSMutableData() body += delim for (key, value) in parameters { - body += "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n\(value)\r\n".UTF8Data() + body += "Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n\(value)\r\n".utf8Data body += delim } - body += "Content-Disposition: form-data; name=\"file\"\r\n\r\n".UTF8Data() + body += "Content-Disposition: form-data; name=\"file\"\r\n\r\n".utf8Data body += data - body += "\r\n--\(multipartBoundary)--\r\n".UTF8Data() + body += "\r\n--\(multipartBoundary)--\r\n".utf8Data - return body + return body as Data } - func writeDataToFile(data: NSData, fileName: String) -> NSURL { - let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] - let documentsURL = NSURL(fileURLWithPath: documentsPath, isDirectory: true) + private func writeDataToFile(_ data: Data, fileName: String) -> URL { + let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + let documentsURL = URL(fileURLWithPath: documentsPath, isDirectory: true) + let url = documentsURL.appendingPathComponent(fileName + ".tmp") - guard let url = documentsURL.URLByAppendingPathComponent(fileName + ".tmp") else { - XCTFail("Failed to write to file") - fatalError() - } - - data.writeToURL(url, atomically: true) + try? data.write(to: url, options: [.atomic]) return url } } + // MARK: - Helpers extension String { - func UTF8Data() -> NSData { - return dataUsingEncoding(NSUTF8StringEncoding)! + fileprivate var utf8Data: Data { + return data(using: String.Encoding.utf8)! } } -public func +=(lhs: NSMutableData, rhs: NSData) { - lhs.appendData(rhs) +public func +=(lhs: NSMutableData, rhs: Data) { + lhs.append(rhs) } diff --git a/DVR/URLRequest.swift b/DVR/URLRequest.swift index aa84f7d..8299383 100644 --- a/DVR/URLRequest.swift +++ b/DVR/URLRequest.swift @@ -1,22 +1,22 @@ import Foundation -extension NSURLRequest { - var dictionary: [String: AnyObject] { - var dictionary = [String: AnyObject]() +extension URLRequest { + var dictionary: [String: Any] { + var dictionary = [String: Any]() - if let method = HTTPMethod { - dictionary["method"] = method + if let method = httpMethod { + dictionary["method"] = method as Any? } - if let url = URL?.absoluteString { - dictionary["url"] = url + if let url = url?.absoluteString { + dictionary["url"] = url as Any? } if let headers = allHTTPHeaderFields { - dictionary["headers"] = headers + dictionary["headers"] = headers as Any? } - if let data = HTTPBody, body = Interaction.encodeBody(data, headers: allHTTPHeaderFields) { + if let data = httpBody, let body = Interaction.encodeBody(data, headers: allHTTPHeaderFields) { dictionary["body"] = body } @@ -25,31 +25,37 @@ extension NSURLRequest { } -extension NSURLRequest { - func requestByAppendingHeaders(headers: [NSObject: AnyObject]) -> NSURLRequest { - let request = mutableCopy() as! NSMutableURLRequest - request.appendHeaders(headers) - return request.copy() as! NSURLRequest +extension URLRequest { + func appending(headers: [AnyHashable: Any]) -> URLRequest { + guard let headers = headers as? [String: String] else { return self } + + var request = self + + for (key, value) in headers { + request.addValue(value, forHTTPHeaderField: key) + } + + return request } - func requestWithBody(body: NSData) -> NSURLRequest { - let request = mutableCopy() as! NSMutableURLRequest - request.HTTPBody = body - return request.copy() as! NSURLRequest + func appending(body: Data?) -> URLRequest { + var request = self + request.httpBody = body + return request } } extension NSMutableURLRequest { - convenience init(dictionary: [String: AnyObject]) { + convenience init(dictionary: [String: Any]) { self.init() if let method = dictionary["method"] as? String { - HTTPMethod = method + httpMethod = method } - if let string = dictionary["url"] as? String, url = NSURL(string: string) { - URL = url + if let string = dictionary["url"] as? String, let url = URL(string: string) { + self.url = url } if let headers = dictionary["headers"] as? [String: String] { @@ -57,18 +63,18 @@ extension NSMutableURLRequest { } if let body = dictionary["body"] { - HTTPBody = Interaction.dencodeBody(body, headers: allHTTPHeaderFields) + httpBody = Interaction.dencodeBody(body, headers: allHTTPHeaderFields) } } } extension NSMutableURLRequest { - func appendHeaders(headers: [NSObject: AnyObject]) { + func appendHeaders(_ headers: [AnyHashable: Any]) { var existingHeaders = allHTTPHeaderFields ?? [:] headers.forEach { header in - guard let key = header.0 as? String, value = header.1 as? String where existingHeaders[key] == nil else { + guard let key = header.0 as? String, let value = header.1 as? String , existingHeaders[key] == nil else { return } diff --git a/DVR/URLResponse.swift b/DVR/URLResponse.swift index 0f430a4..b5b4ba2 100644 --- a/DVR/URLResponse.swift +++ b/DVR/URLResponse.swift @@ -1,11 +1,11 @@ import Foundation -// There isn't a mutable NSURLResponse, so we have to make our own. -class URLResponse: NSURLResponse { - private var _URL: NSURL? - override var URL: NSURL? { +// There isn't a mutable URLResponse, so we have to make our own. +class URLResponse: Foundation.URLResponse { + private var _URL: Foundation.URL? + override var url: Foundation.URL? { get { - return _URL ?? super.URL + return _URL ?? super.url } set { @@ -15,10 +15,10 @@ class URLResponse: NSURLResponse { } -extension NSURLResponse { - var dictionary: [String: AnyObject] { - if let url = URL?.absoluteString { - return ["url": url] +extension Foundation.URLResponse { + var dictionary: [String: Any] { + if let url = url?.absoluteString { + return ["url": url as Any] } return [:] @@ -27,11 +27,11 @@ extension NSURLResponse { extension URLResponse { - convenience init(dictionary: [String: AnyObject]) { + convenience init(dictionary: [String: Any]) { self.init() - if let string = dictionary["url"] as? String, url = NSURL(string: string) { - URL = url + if let string = dictionary["url"] as? String, let url = Foundation.URL(string: string) { + self.url = url } } }