diff --git a/.bundle/config b/.bundle/config new file mode 100644 index 0000000..ba922fd --- /dev/null +++ b/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_PATH: "vendor" diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..55af22c --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @wildlink/ios diff --git a/.github/workflows/actions-publish.yml b/.github/workflows/actions-publish.yml new file mode 100644 index 0000000..7aaad1e --- /dev/null +++ b/.github/workflows/actions-publish.yml @@ -0,0 +1,67 @@ +name: CI Publish +on: + push: + tags: + - '*' + +jobs: + pod-lint: + runs-on: macos-11 + steps: + - name: Select Xcode 13 + uses: devbotsxyz/xcode-select@v1 + with: + version: "13" + - name: Check out repository + uses: actions/checkout@v2 + - name: Cache CocoaPods data + uses: actions/cache@v2 + with: + path: Example/Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: Run pod lib lint + run: | + bundle install + bundle exec fastlane lint + unit-tests: + runs-on: macos-11 + needs: pod-lint + steps: + - name: Select Xcode 13 + uses: devbotsxyz/xcode-select@v1 + with: + version: "13" + - name: Check out repository + uses: actions/checkout@v2 + - name: Cache CocoaPods data + uses: actions/cache@v2 + with: + path: Example/Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: Run the unit tests + run: | + bundle install + bundle exec fastlane test + # Since this runs free on hosted runners, don't optimize for minimum + # runtime. Instead, make sure we never publish something that doesn't + # pass both lint and unit testing, even after merges. + publish: + runs-on: macOS-11 + needs: unit-tests + steps: + - name: Select Xcode 13 + uses: devbotsxyz/xcode-select@v1 + with: + version: "13" + - name: Check out repository + uses: actions/checkout@v2 + - name: Publish to CocoaPods trunk + env: + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} + run: | + bundle install + bundle exec fastlane publish diff --git a/.github/workflows/actions-test.yml b/.github/workflows/actions-test.yml new file mode 100644 index 0000000..cf60c64 --- /dev/null +++ b/.github/workflows/actions-test.yml @@ -0,0 +1,44 @@ +name: GitHub Actions CI +on: [push] +jobs: + pod-lint: + runs-on: macos-11 + steps: + - name: Select Xcode 13 + uses: devbotsxyz/xcode-select@v1 + with: + version: "13" + - name: Check out repository + uses: actions/checkout@v2 + - name: Cache CocoaPods data + uses: actions/cache@v2 + with: + path: Example/Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: Run pod lib lint + run: | + bundle install + bundle exec fastlane lint + unit-tests: + runs-on: macos-11 + needs: pod-lint + steps: + - name: Select Xcode 13 + uses: devbotsxyz/xcode-select@v1 + with: + version: "13" + - name: Check out repository + uses: actions/checkout@v2 + - name: Cache CocoaPods data + uses: actions/cache@v2 + with: + path: Example/Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: Run the unit tests + run: | + bundle install + bundle exec fastlane test diff --git a/.gitignore b/.gitignore index 877f701..28f6c81 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,7 @@ fastlane/report.xml fastlane/Preview.html fastlane/screenshots/**/*.png fastlane/test_output + +**/.DS_Store + +vendor diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..957b409 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "proseWrap": "always", + "arrowParens": "always", + "printWidth": 100, + "singleQuote": true, + "trailingComma": "all" +} diff --git a/Example/Podfile b/Example/Podfile index 19b6b22..88df9a5 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -9,3 +9,11 @@ target 'Wildlink_Example' do pod 'Wildlink', :path => '../', :testspecs => ['Tests'] end end + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '10.0' + end + end +end diff --git a/Example/Podfile.lock b/Example/Podfile.lock index e0cf5e5..458b850 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,9 +1,26 @@ PODS: - - Alamofire (4.8.2) - - Wildlink (1.0.6): - - Alamofire (~> 4.8.2) - - Wildlink/Tests (1.0.6): - - Alamofire (~> 4.8.2) + - Alamofire (5.4.4) + - OHHTTPStubs (9.1.0): + - OHHTTPStubs/Default (= 9.1.0) + - OHHTTPStubs/Core (9.1.0) + - OHHTTPStubs/Default (9.1.0): + - OHHTTPStubs/Core + - OHHTTPStubs/JSON + - OHHTTPStubs/NSURLSession + - OHHTTPStubs/OHPathHelpers + - OHHTTPStubs/JSON (9.1.0): + - OHHTTPStubs/Core + - OHHTTPStubs/NSURLSession (9.1.0): + - OHHTTPStubs/Core + - OHHTTPStubs/OHPathHelpers (9.1.0) + - OHHTTPStubs/Swift (9.1.0): + - OHHTTPStubs/Default + - Wildlink (2.0.0): + - Alamofire (~> 5.4.4) + - Wildlink/Tests (2.0.0): + - Alamofire (~> 5.4.4) + - OHHTTPStubs (~> 9.1.0) + - OHHTTPStubs/Swift (~> 9.1.0) DEPENDENCIES: - Wildlink (from `../`) @@ -12,15 +29,17 @@ DEPENDENCIES: SPEC REPOS: trunk: - Alamofire + - OHHTTPStubs EXTERNAL SOURCES: Wildlink: :path: "../" SPEC CHECKSUMS: - Alamofire: ae5c501addb7afdbb13687d7f2f722c78734c2d3 - Wildlink: 1698c8bde239ea342722aa7633b851354e3f530f + Alamofire: f3b09a368f1582ab751b3fff5460276e0d2cf5c9 + OHHTTPStubs: 90eac6d8f2c18317baeca36698523dc67c513831 + Wildlink: 675f527710f13d09b44a7681349eb224cc8789f4 -PODFILE CHECKSUM: ebc74e7f624558182cd7f10e0ae346ced9843b6a +PODFILE CHECKSUM: e82b0dfe349c98fec62bc27a250c046f2ccedf50 -COCOAPODS: 1.8.4 +COCOAPODS: 1.11.2 diff --git a/Example/Wildlink.xcodeproj/project.pbxproj b/Example/Wildlink.xcodeproj/project.pbxproj index fc2689d..70580c0 100644 --- a/Example/Wildlink.xcodeproj/project.pbxproj +++ b/Example/Wildlink.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; + 9BD439F3270F8F0200A476D9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD439F2270F8F0200A476D9 /* Constants.swift */; }; + 9BD439F4270F8F0900A476D9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BD439F2270F8F0200A476D9 /* Constants.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -44,6 +46,8 @@ 7789AF2650C0A232C4B5F607 /* Pods-Wildlink_Example-Wildlink_ExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Wildlink_Example-Wildlink_ExampleTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Wildlink_Example-Wildlink_ExampleTests/Pods-Wildlink_Example-Wildlink_ExampleTests.debug.xcconfig"; sourceTree = ""; }; 7A0238D73297FC83C4EE233B /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 807ECB05FF0DE7F330B29B79 /* Pods-Wildlink_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Wildlink_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Wildlink_Example/Pods-Wildlink_Example.debug.xcconfig"; sourceTree = ""; }; + 9BC1A843270BA31600A1AC18 /* Wildlink.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = Wildlink.xctestplan; sourceTree = ""; }; + 9BD439F2270F8F0200A476D9 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 9E71604798F2009321675667 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; C63CEDC72C5FAE02F609F824 /* Pods-Wildlink_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Wildlink_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Wildlink_Example/Pods-Wildlink_Example.release.xcconfig"; sourceTree = ""; }; FBC867498DEB3E16D4821594 /* Pods-Wildlink_Example-Wildlink_ExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Wildlink_Example-Wildlink_ExampleTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Wildlink_Example-Wildlink_ExampleTests/Pods-Wildlink_Example-Wildlink_ExampleTests.release.xcconfig"; sourceTree = ""; }; @@ -101,6 +105,7 @@ 607FACC71AFB9204008FA782 = { isa = PBXGroup; children = ( + 9BC1A843270BA31600A1AC18 /* Wildlink.xctestplan */, 607FACF51AFB993E008FA782 /* Podspec Metadata */, 607FACD21AFB9204008FA782 /* Example for Wildlink */, 02A0F22A22D43ABA00979F78 /* Wildlink_ExampleTests */, @@ -123,6 +128,7 @@ isa = PBXGroup; children = ( 607FACD51AFB9204008FA782 /* AppDelegate.swift */, + 9BD439F2270F8F0200A476D9 /* Constants.swift */, 607FACD71AFB9204008FA782 /* ViewController.swift */, 607FACD91AFB9204008FA782 /* Main.storyboard */, 607FACDC1AFB9204008FA782 /* Images.xcassets */, @@ -344,6 +350,7 @@ buildActionMask = 2147483647; files = ( 02A0F22C22D43ABA00979F78 /* Wildlink_ExampleTests.swift in Sources */, + 9BD439F4270F8F0900A476D9 /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -353,6 +360,7 @@ files = ( 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, + 9BD439F3270F8F0200A476D9 /* Constants.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Example/Wildlink.xcodeproj/xcshareddata/xcschemes/Wildlink-Example.xcscheme b/Example/Wildlink.xcodeproj/xcshareddata/xcschemes/Wildlink-Example.xcscheme index f81012e..8ed2d15 100644 --- a/Example/Wildlink.xcodeproj/xcshareddata/xcschemes/Wildlink-Example.xcscheme +++ b/Example/Wildlink.xcodeproj/xcshareddata/xcschemes/Wildlink-Example.xcscheme @@ -40,8 +40,23 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - codeCoverageEnabled = "YES" - shouldUseLaunchSchemeArgsEnv = "YES"> + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + + + + + @@ -68,17 +83,6 @@ - - - - - - - - Bool { // Override point for customization after application launch. //initialize wildlink Wildlink.shared.delegate = self let defaults = UserDefaults.standard - Wildlink.shared.initialize(appId: "APP_ID", appSecret: "APP_SECRET", wildlinkDeviceToken: defaults.string(forKey: "wildlinkDeviceToken"), wildlinkDeviceKey: defaults.string(forKey: "wildlinkDeviceKey")) + Wildlink.shared.initialize(appId: Constants.appId, appSecret: Constants.appSecret, wildlinkDeviceToken: defaults.string(forKey: "wildlinkDeviceToken"), wildlinkDeviceKey: defaults.string(forKey: "wildlinkDeviceKey")) return true } diff --git a/Example/Wildlink/Constants.swift b/Example/Wildlink/Constants.swift new file mode 100644 index 0000000..35dee28 --- /dev/null +++ b/Example/Wildlink/Constants.swift @@ -0,0 +1,14 @@ +// +// Constants.swift +// Wildlink_Example +// +// Created by Kyle Kurz - Wildfire on 10/7/21. +// Copyright © 2021 CocoaPods. All rights reserved. +// + +import Foundation + +struct Constants { + static let appId = "" + static let appSecret = "" +} diff --git a/Example/Wildlink/ViewController.swift b/Example/Wildlink/ViewController.swift index 0f4ad3f..7833c40 100644 --- a/Example/Wildlink/ViewController.swift +++ b/Example/Wildlink/ViewController.swift @@ -6,6 +6,7 @@ // import UIKit +import Alamofire import Wildlink class ViewController: UIViewController { @@ -23,32 +24,27 @@ class ViewController: UIViewController { Wildlink.shared.createVanityURL(from: urlText) { (url, error) in if let url = url { DispatchQueue.main.async { - self.urlOutlet.text = url.absoluteString + self.urlOutlet.text = url.vanityURL.absoluteString } sleep(2) - Wildlink.shared.getClickStats(from: Date(timeIntervalSinceNow: -604800), with: .hour, completion: { (results, error) in - if let error = error { - log(error, with: "Click stats") - } - print("Click stats results: \(String(describing: results))") - }) Wildlink.shared.getCommissionSummary({ (stats, error) in if let error = error { log(error, with: "Commission summary") } print("Commission summary results: \(String(describing: stats))") }) - Wildlink.shared.getMerchantByID("5476062", { (merchant, error) in + Wildlink.shared.getMerchantBy("5476062", { (merchant, error) in if let error = error { log(error, with: "Merchant data") } print("Merchant data results: \(String(describing: merchant))") }) - Wildlink.shared.searchMerchants(ids: [], names: [], q: nil, disabled: nil, featured: true, sortBy: nil, sortOrder: nil, limit: nil, { (merchants, error) in + Wildlink.shared.searchMerchants(ids: [], names: [], q: nil, disabled: nil, featured: nil, sortBy: nil, sortOrder: nil, limit: nil, { (merchants, error) in if let error = error { log(error, with: "List of merchants") } - print("List of merchants results: \(merchants)") + print("List of merchants results: \(merchants.count)") + print("\(String(describing: merchants.first(where: { $0.images.count > 0 }))) ") }) } } diff --git a/Example/Wildlink_ExampleTests/Wildlink_ExampleTests.swift b/Example/Wildlink_ExampleTests/Wildlink_ExampleTests.swift index 2507ddb..cec5439 100644 --- a/Example/Wildlink_ExampleTests/Wildlink_ExampleTests.swift +++ b/Example/Wildlink_ExampleTests/Wildlink_ExampleTests.swift @@ -9,25 +9,9 @@ import XCTest class Wildlink_ExampleTests: XCTestCase { - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } + func testConstantsSecretLeak() { + XCTAssertEqual(Constants.appId, "") + XCTAssertEqual(Constants.appSecret, "") + } - } diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..498a3e5 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem "fastlane", "~>2.195.0" +gem "cocoapods", "~>1.11.0" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..740b5c1 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,280 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.4) + rexml + activesupport (6.1.4.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + artifactory (3.0.15) + atomos (0.1.3) + aws-eventstream (1.2.0) + aws-partitions (1.513.0) + aws-sdk-core (3.121.1) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.49.0) + aws-sdk-core (~> 3, >= 3.120.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.103.0) + aws-sdk-core (~> 3, >= 3.120.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.4) + aws-sigv4 (1.4.0) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + claide (1.0.3) + cocoapods (1.11.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.11.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 1.0, < 3.0) + xcodeproj (>= 1.21.0, < 2.0) + cocoapods-core (1.11.2) + activesupport (>= 5.0, < 7) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (1.5.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + concurrent-ruby (1.1.9) + declarative (0.0.20) + digest-crc (0.6.4) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.6) + emoji_regex (3.2.3) + escape (0.0.4) + ethon (0.14.0) + ffi (>= 1.15.0) + excon (0.87.0) + faraday (1.8.0) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0.1) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.1) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + multipart-post (>= 1.2, < 3) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday_middleware (1.1.0) + faraday (~> 1.0) + fastimage (2.2.5) + fastlane (2.195.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (~> 2.0.0) + naturally (~> 2.2) + optparse (~> 0.1.1) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + ffi (1.15.4) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.12.0) + google-apis-core (>= 0.4, < 2.a) + google-apis-core (0.4.1) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.7.0) + google-apis-core (>= 0.4, < 2.a) + google-apis-playcustomapp_v1 (0.5.0) + google-apis-core (>= 0.4, < 2.a) + google-apis-storage_v1 (0.8.0) + google-apis-core (>= 0.4, < 2.a) + google-cloud-core (1.6.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.5.0) + faraday (>= 0.17.3, < 2.0) + google-cloud-errors (1.2.0) + google-cloud-storage (1.34.1) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.1) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.0.0) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.4) + domain_name (~> 0.5) + httpclient (2.8.3) + i18n (1.8.10) + concurrent-ruby (~> 1.0) + jmespath (1.4.0) + json (2.5.1) + jwt (2.3.0) + memoist (0.16.2) + mini_magick (4.11.0) + mini_mime (1.1.2) + minitest (5.14.4) + molinillo (0.8.0) + multi_json (1.15.0) + multipart-post (2.0.0) + nanaimo (0.3.0) + nap (1.1.0) + naturally (2.2.1) + netrc (0.11.0) + optparse (0.1.1) + os (1.1.1) + plist (3.6.0) + public_suffix (4.0.6) + rake (13.0.6) + representable (3.1.1) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.5) + rouge (2.0.7) + ruby-macho (2.5.1) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + security (0.1.3) + signet (0.16.0) + addressable (~> 2.8) + faraday (>= 0.17.3, < 2.0) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.8) + CFPropertyList + naturally + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + trailblazer-option (0.1.1) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.8) + unicode-display_width (1.8.0) + webrick (1.7.0) + word_wrap (1.0.0) + xcodeproj (1.21.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + zeitwerk (2.4.2) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods (~> 1.11.0) + fastlane (~> 2.195.0) + +BUNDLED WITH + 2.1.4 diff --git a/README.md b/README.md index f9e0a7e..b264190 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,21 @@ # Wildlink API for iOS + [![Build Status](https://travis-ci.com/wildlink/wildlink-api-ios.svg?branch=master)](https://travis-ci.com/wildlink/wildlink-api-ios) -[![Swift Version][swift-image-5]][swift-url] -[![Swift Version][swift-image-4]][swift-url] +[![Swift Version][swift-image-5]][swift-url] [![Swift Version][swift-image-4]][swift-url] [![License][license-image]][license-url] + + [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/EZSwiftExtensions.svg)](https://img.shields.io/cocoapods/v/LFAlertController.svg) [![Platform](https://img.shields.io/cocoapods/p/LFAlertController.svg?style=flat)](http://cocoapods.org/pods/LFAlertController) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) ## Features -This repository allows you to integrate the following Wildlink functionality into your Swift iOS application. + +This repository allows you to integrate the following Wildlink functionality into your Swift iOS +application. - [x] Create vanity URLs -- [x] Query vanity URL click statistics - [x] Query your personal commission information - [x] Query our merchant database - [x] Search for a specific merchant by name or ID @@ -25,6 +28,7 @@ This repository allows you to integrate the following Wildlink functionality int ## Installation #### CocoaPods + You can use [CocoaPods](http://cocoapods.org/) to install `Wildlink` by adding it to your `Podfile`: ```ruby @@ -37,17 +41,21 @@ target 'Wildlink_Example' do end ``` -Once you've added the cocoapod, run `pod install` to set up your project and open your .xcworkspace in Xcode +Once you've added the cocoapod, run `pod install` to set up your project and open your .xcworkspace +in Xcode #### Manually -1. Download and drop ```Wildlink/Classes/*.swift``` in your project. -2. Congratulations! + +1. Download and drop `Wildlink/Classes/*.swift` in your project. +2. Congratulations! ## Usage example ### Initialize the SDK -Somewhere in your application (We suggest in your AppDelegate:didFinishLaunchingWithOptions function) you need -to call the initialize method on the SDK and set yourself up as the Wildlink delegate: + +Somewhere in your application (We suggest in your AppDelegate:didFinishLaunchingWithOptions +function) you need to call the initialize method on the SDK and set yourself up as the Wildlink +delegate: ```swift Wildlink.shared.delegate = self @@ -55,9 +63,11 @@ Wildlink.shared.initialize(appId: "", appSecret: "", appSecret: "", appSecret: "", wildlinkDeviceToken: defaults.string(forKey: "wildlinkDeviceToken"), wildlinkDeviceKey: defaults.string(forKey: "wildlinkDeviceKey")) return true } - + ... - + } extension AppDelegate : WildlinkDelegate { @@ -107,31 +117,31 @@ extension AppDelegate : WildlinkDelegate { } ``` -In the above example, you'll notice that we're using NSUserDefaults to store and retrieve the device tokens and keys returned by Wildlink -as part of conforming to the WildlinkDelegate protocol. Other requests from the Wildlink SDK are handled via completion handlers as shown -below. +In the above example, you'll notice that we're using NSUserDefaults to store and retrieve the device +tokens and keys returned by Wildlink as part of conforming to the WildlinkDelegate protocol. Other +requests from the Wildlink SDK are handled via completion handlers as shown below. + +## Request things from the SDK + +Once you've initialized the SDK to identify your application and register the device it's running +on, you can begin making requests. -#### Request things from the SDK -Once you've initialized the SDK to identify your application and register the device it's running on, you can begin making requests. +#### Request a Vanity URL -##### Request a Vanity URL ```swift import Wildlink -Wildlink.shared.createVanityURL(from: urlOutlet.text) { (url, error) in +Wildlink.shared.createVanityURL(from: url) { (url, error) in // do something interesting with the result } -``` -##### Request your click statistics -```swift -import Wildlink -//get the hourly results of your clicks for the last week -Wildlink.shared.getClickStats(from: Date(timeIntervalSinceNow: -604800), with: .hour, completion: { (results, error) in +// Alternatively, you can pass the URL directly +Wildlink.shared.createVanityURL(from: URL(string: "https://wildfire-corp.com")!, { (url, error) in // do something interesting with the result }) ``` -##### Request your commission statistics +#### Request your commission statistics + ```swift import Wildlink Wildlink.shared.getCommissionSummary({ (stats, error) in @@ -139,15 +149,17 @@ Wildlink.shared.getCommissionSummary({ (stats, error) in }) ``` -##### Get details about a specific merchant +#### Get details about a specific merchant + ```swift import Wildlink -Wildlink.shared.getMerchantByID("5476062", { (merchant, error) in +Wildlink.shared.getMerchantBy("5476062", { (merchant, error) in // do something interesting with the result }) ``` -##### Search our merchant listings +#### Search our merchant listings + ```swift import Wildlink //this example lists *all* of our merchants @@ -158,22 +170,23 @@ Wildlink.shared.searchMerchants(ids: [], names: [], q: nil, disabled: nil, featu ## Contribute -We would love for you to contribution to **Wildlink**, check the ``LICENSE`` file for more info. +We would love for you to contribution to **Wildlink**, check the `LICENSE` file for more info. ## Meta © Copyright 2019 Wildfire Systems, Inc. -Distributed under the MIT license. See ``LICENSE`` for more information. +Distributed under the MIT license. See `LICENSE` for more information. [https://github.com/wildlink/wildlink-api-ios](https://github.com/wildlink/) -[swift-image-5]:https://img.shields.io/badge/swift-5.0-orange.svg -[swift-image-4]:https://img.shields.io/badge/swift-4.2-orange.svg +[swift-image-5]: https://img.shields.io/badge/swift-5.0-orange.svg +[swift-image-4]: https://img.shields.io/badge/swift-4.2-orange.svg [swift-url]: https://swift.org/ [license-image]: https://img.shields.io/badge/License-MIT-blue.svg [license-url]: LICENSE -[travis-image]: https://img.shields.io/travis/dbader/node-datadog-metrics/master.svg?style=flat-square +[travis-image]: + https://img.shields.io/travis/dbader/node-datadog-metrics/master.svg?style=flat-square [travis-url]: https://travis-ci.org/dbader/node-datadog-metrics [codebeat-image]: https://codebeat.co/badges/c19b47ea-2f9d-45df-8458-b2d952fe9dad [codebeat-url]: https://codebeat.co/projects/github-com-vsouza-awesomeios-com diff --git a/Wildlink.podspec b/Wildlink.podspec index 49b78d5..742de50 100644 --- a/Wildlink.podspec +++ b/Wildlink.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Wildlink' - s.version = '1.0.6' + s.version = '2.0.0' s.summary = 'You can use this CocoaPod to interact with the Wildlink API' s.homepage = 'https://wildlink.me' @@ -15,10 +15,12 @@ Pod::Spec.new do |s| s.source_files = 'Wildlink/Classes/**/*' #dependencies - s.dependency 'Alamofire', '~> 4.8.2' + s.dependency 'Alamofire', '~> 5.4.4' #tests s.test_spec 'Tests' do |test_spec| test_spec.source_files = 'Wildlink/Tests/*.swift' + test_spec.dependency 'OHHTTPStubs', '~> 9.1.0' + test_spec.dependency 'OHHTTPStubs/Swift', '~> 9.1.0' end end diff --git a/Wildlink/Classes/ClickStats.swift b/Wildlink/Classes/ClickStats.swift deleted file mode 100644 index f5980ba..0000000 --- a/Wildlink/Classes/ClickStats.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// ClickStats.swift -// Wildlink -// -// Copyright © 2019 Wildfire, Systems. All rights reserved. -// - -import Foundation - -public struct ClickStats { - //Total commissions for user's device(s) not yet confirmed by networks. - public let period: String - //Total commissions for user's device(s) confirmed by networks, but not yet paid by Wildfire. - public let clickDate: Date - //Total commissions for user's device(s) already paid by Wildfire. - public let clickCount: Int - - //create a dictionary from the struct. Note that the dictionary keys are PascalCasing to match - //the data returned from the Wildfire servers. - public var dictionary: [String: Any] { - return [ - "Period": period, - "ClickDate": clickDate.utc, - "ClickCount": clickCount - ] - } -} - -extension ClickStats : JSONSerializable { - public init?(dictionary: [String : Any]) { - guard let period = dictionary["Period"] as? String, - let clickDateString = dictionary["ClickDate"] as? String, - let clickCount = dictionary["ClickCount"] as? Int, - let clickDate = clickDateString.dateFromUTC else { - return nil - } - - self.init(period: period, clickDate: clickDate, clickCount: clickCount) - } -} diff --git a/Wildlink/Classes/CommissionDetail.swift b/Wildlink/Classes/CommissionDetail.swift new file mode 100644 index 0000000..9d817ca --- /dev/null +++ b/Wildlink/Classes/CommissionDetail.swift @@ -0,0 +1,38 @@ +// +// CommissionDetails.swift +// Wildlink +// +// Copyright © 2019 Wildfire, Systems. All rights reserved. +// + +import Foundation + +public enum CommissionStatus: String, Codable { + case ready = "READY" + case pending = "PENDING" + case paid = "PAID" +} + +public struct CommissionDetail: Codable { + //identifier for this detail object + public let id: Int + //identifiers for any associated commission detail objects + public let commissionIds: [Int] + //the date this commission occurred + public let date: Date + //how much commission was earned + public let amount: String + //current status, see CommissionStatus enum for possible options + public let status: CommissionStatus + //the merchant where this commission was earned + public let merchant: String + + enum CodingKeys: String, CodingKey { + case id = "ID" + case commissionIds = "CommissionIDs" + case date = "Date" + case amount = "Amount" + case status = "Status" + case merchant = "Merchant" + } +} diff --git a/Wildlink/Classes/CommissionDetails.swift b/Wildlink/Classes/CommissionDetails.swift deleted file mode 100644 index 5cea546..0000000 --- a/Wildlink/Classes/CommissionDetails.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// CommissionDetails.swift -// Wildlink -// -// Copyright © 2019 Wildfire, Systems. All rights reserved. -// - -import Foundation - -public enum CommissionStatus: String { - case ready = "READY" - case pending = "PENDING" - case paid = "PAID" -} - -public struct CommissionDetails { - //identifier for this detail object - public let id: Int - //identifiers for any associated commission detail objects - public let commissionIds: [Int] - //the date this commission occurred - public let date: Date - //how much commission was earned - public let amount: String - //current status, see CommissionStatus enum for possible options - public let status: CommissionStatus - //the merchant where this commission was earned - public let merchant: String - - public var dictionary: [String: Any] { - return [ - "ID": id, - "CommissionIDs": commissionIds, - "Date": date.utc, - "Amount": amount, - "Status": status.rawValue, - "Merchant": merchant - ] - } -} - -extension CommissionDetails : JSONSerializable { - public init?(dictionary: [String : Any]) { - guard let id = dictionary["ID"] as? Int, - let commissionIds = dictionary["CommissionIDs"] as? [Int], - let dateString = dictionary["Date"] as? String, - let date = dateString.dateFromUTC, - let amount = dictionary["Amount"] as? String, - let statusString = dictionary["Status"] as? String, - let status = CommissionStatus(rawValue: statusString), - let merchant = dictionary["Merchant"] as? String else { - return nil - } - - self.init(id: id, commissionIds: commissionIds, date: date, amount: amount, status: status, merchant: merchant) - } -} diff --git a/Wildlink/Classes/CommissionStats.swift b/Wildlink/Classes/CommissionStats.swift index aaf2c45..7b25357 100644 --- a/Wildlink/Classes/CommissionStats.swift +++ b/Wildlink/Classes/CommissionStats.swift @@ -7,7 +7,7 @@ import Foundation -public struct CommissionStats { +public struct CommissionStats: Codable { //Total commissions for user's device(s) not yet confirmed by networks. public let pendingAmount: String //Total commissions for user's device(s) confirmed by networks, but not yet paid by Wildfire. @@ -15,25 +15,9 @@ public struct CommissionStats { //Total commissions for user's device(s) already paid by Wildfire. public let paidAmount: String - //create a dictionary from the struct. Note that the dictionary keys are PascalCasing to match - //the data returned from the Wildfire servers. - public var dictionary: [String: Any] { - return [ - "PendingAmount": pendingAmount, - "ReadyAmount": readyAmount, - "PaidAmount": paidAmount - ] - } -} - -extension CommissionStats : JSONSerializable { - public init?(dictionary: [String : Any]) { - guard let pendingAmount = dictionary["PendingAmount"] as? String, - let readyAmount = dictionary["ReadyAmount"] as? String, - let paidAmount = dictionary["PaidAmount"] as? String else { - return nil - } - - self.init(pendingAmount: pendingAmount, readyAmount: readyAmount, paidAmount: paidAmount) + enum CodingKeys: String, CodingKey { + case pendingAmount = "PendingAmount" + case readyAmount = "ReadyAmount" + case paidAmount = "PaidAmount" } } diff --git a/Wildlink/Classes/Device.swift b/Wildlink/Classes/Device.swift new file mode 100644 index 0000000..9674c80 --- /dev/null +++ b/Wildlink/Classes/Device.swift @@ -0,0 +1,20 @@ +// +// Device.swift +// Wildlink +// +// Created by Kyle Kurz - Wildfire on 10/5/21. +// + +import Foundation + +public struct Device: Codable { + public let token: String + public let key: String + public let id: UInt64 + + enum CodingKeys: String, CodingKey { + case token = "DeviceToken" + case key = "DeviceKey" + case id = "DeviceID" + } +} diff --git a/Wildlink/Classes/Image.swift b/Wildlink/Classes/Image.swift new file mode 100644 index 0000000..dce0083 --- /dev/null +++ b/Wildlink/Classes/Image.swift @@ -0,0 +1,34 @@ +// +// Image.swift +// Wildlink +// +// Created by Kyle Kurz - Wildfire on 10/5/21. +// + +import Foundation + +public enum ImageKind: String, Codable { + case general = "GENERAL" + case featured = "FEATURED" + case logo = "LOGO" +} + +public struct Image: Codable { + public let id: UInt64 + public let kind: ImageKind + public let ordinal: Int64 + public let imageId: UInt64 + public let url: URL + public let width: UInt64 + public let height: UInt64 + + enum CodingKeys: String, CodingKey { + case id = "ID" + case kind = "Kind" + case ordinal = "Ordinal" + case imageId = "ImageID" + case url = "URL" + case width = "Width" + case height = "Height" + } +} diff --git a/Wildlink/Classes/Merchant.swift b/Wildlink/Classes/Merchant.swift index 1e1ba01..61da236 100644 --- a/Wildlink/Classes/Merchant.swift +++ b/Wildlink/Classes/Merchant.swift @@ -7,43 +7,30 @@ import Foundation -public struct Merchant { +public struct Merchant: Codable { public let id: Int public let name: String public let disabled: Bool public let featured: Bool public let shortCode: String public let shortURL: URL - public let images: [String] + public let images: [Image] - //create a dictionary from the struct. Note that the dictionary keys are PascalCasing to match - //the data returned from the Wildfire servers. - public var dictionary: [String: Any] { - return [ - "ID": id, - "Name": name, - "Disabled": disabled, - "Featured": featured, - "ShortCode": shortCode, - "ShortURL": shortURL.absoluteString, - "Images": images - ] + enum CodingKeys: String, CodingKey { + case id = "ID" + case name = "Name" + case disabled = "Disabled" + case featured = "Featured" + case shortCode = "ShortCode" + case shortURL = "ShortURL" + case images = "Images" } } -extension Merchant : JSONSerializable { - public init?(dictionary: [String : Any]) { - guard let id = dictionary["ID"] as? Int, - let name = dictionary["Name"] as? String, - let disabled = dictionary["Disabled"] as? Bool, - let featured = dictionary["Featured"] as? Bool, - let shortCode = dictionary["ShortCode"] as? String, - let shortUrlString = dictionary["ShortURL"] as? String, - let shortURL = URL(string: shortUrlString), - let images = dictionary["Images"] as? [String] else { - return nil - } - - self.init(id: id, name: name, disabled: disabled, featured: featured, shortCode: shortCode, shortURL: shortURL, images: images) +public struct MerchantList: Codable { + public let merchants: [Merchant] + + enum CodingKeys: String, CodingKey { + case merchants = "Merchants" } } diff --git a/Wildlink/Classes/StringEx.swift b/Wildlink/Classes/StringEx.swift index 6d00c07..f50603b 100644 --- a/Wildlink/Classes/StringEx.swift +++ b/Wildlink/Classes/StringEx.swift @@ -16,6 +16,7 @@ extension String { let digestLen = Int(CC_SHA256_DIGEST_LENGTH) let result = UnsafeMutablePointer.allocate(capacity: digestLen) + result.initialize(repeating: 0, count: digestLen) let keyStr = key.cString(using: String.Encoding.utf8) let keyLen = key.lengthOfBytes(using: String.Encoding.utf8) @@ -24,7 +25,7 @@ extension String { CCHmac(algorithm, keyStr!, keyLen, str!, strLen, result) - let digest = stringFromResult(result: result, length: digestLen) + let digest = String.stringFrom(result, with: digestLen) result.deallocate() @@ -32,10 +33,10 @@ extension String { } - private func stringFromResult(result: UnsafeMutablePointer, length: Int) -> String { + static func stringFrom(_ ptr: UnsafeMutablePointer, with length: Int) -> String { let hash = NSMutableString() for i in 0.. Void + internal typealias RefreshCompletion = (_ succeeded: Bool, _ deviceToken: String?, _ deviceKey: String?, _ deviceId: UInt64?) -> Void + internal typealias RequestRetryCompletion = (RetryResult) -> Void - private let sessionManager: SessionManager = { - let configuration = URLSessionConfiguration.default - configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders - return SessionManager(configuration: configuration) - }() + internal let lock = NSLock() + internal let decoder = JSONDecoder() - private let lock = NSLock() + internal var baseUrl: URL + internal var deviceToken = "" + internal var deviceKey: String? + internal var deviceId: UInt64? + internal var senderToken = "" + static internal var apiKey = "" + static internal var appID = "" + internal var isRefreshing = false + internal var requestsToRetry: [RequestRetryCompletion] = [] + internal let queue = DispatchQueue(label: "com.wildlink.response-queue", qos: .utility, attributes: [.concurrent]) - private var baseUrl: URL - private var deviceToken = "" - private var deviceKey: String? - private var deviceId: UInt64? - private var senderToken = "" - static var apiKey = "" - static var appID = "" - private var isRefreshing = false - private var requestsToRetry: [RequestRetryCompletion] = [] - private let queue = DispatchQueue(label: "com.wildlink.response-queue", qos: .utility, attributes: [.concurrent]) + private let headers = HTTPHeaders(["Content-Type": "application/json"]) //public variables, methods public static let shared = Wildlink() public weak var delegate: WildlinkDelegate? - private init(){ + internal init(){ self.baseUrl = APIConstants.baseUrlProd + decoder.dateDecodingStrategy = .iso8601 } // Initialize the Wildlink SDK using an AppID and App Secret. Optionally accepts a UUID previously given by the SDK. @@ -98,8 +91,6 @@ public class Wildlink: RequestAdapter, RequestRetrier { self.deviceKey = wildlinkDeviceKey Wildlink.appID = appId Wildlink.apiKey = appSecret - sessionManager.adapter = self - sessionManager.retrier = self guard let token = wildlinkDeviceToken else { refreshDeviceToken { (success, token, key, id) in if success, let token = token { @@ -121,45 +112,32 @@ public class Wildlink: RequestAdapter, RequestRetrier { // // - parameter originalURL: The URL object the user would like to convert to a Wildlink. // - parameter completion: Completion closure to be called once the URL is converted to a Wildlink - public func createVanityURL(from originalURL: URL, _ completion: @escaping(_ url: URL?, _ error: WildlinkError?) -> ()) { + public func createVanityURL(from originalURL: URL, _ completion: @escaping(_ url: VanityURL?, _ error: WildlinkError?) -> ()) { let queryUrl = baseUrl.appendingPathComponent("vanity") - // Add Headers - let headers = [ - "Content-Type":"application/json", - ] - // JSON Body let parameters: [String : Any] = [ "URL": originalURL.absoluteString ] - sessionManager.request(queryUrl, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers) + AF.request(queryUrl, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers, interceptor: self) .validate(statusCode: 200..<300) .validate(contentType: ["application/json"]) - .responseJSON(queue: queue, options: .allowFragments, completionHandler: { response in + .responseDecodable(of: VanityURL.self, decoder: decoder) { [unowned self] response in switch response.result { - case .success(let value): - if let json = value as? [String: Any], let vanityUrlString = json["VanityURL"] as? String { - let vanityUrl = URL(string:vanityUrlString) - completion(vanityUrl, nil) - self.parseResponseHeaders(response.response) - } - else { - completion(nil, WildlinkError(errorData: [:], kind: .invalidResponse)) - } - + case .success(let url): + completion(url, nil) case .failure(let error): - completion(nil, self.generateWildlinkError(from: response.data, with: error)) + completion(nil, generateWildlinkError(from: response.data, with: error)) } - }) + } } // Generate a Wildlink URL string from a string object. Helper wrapper around the URL version. // // - parameter originalURL: The URL object the user would like to convert to a Wildlink. // - parameter completion: Completion closure to be called once the URL is converted to a Wildlink - public func createVanityURL(from originalURL: String, _ completion: @escaping (_ url: URL?, _ error: WildlinkError?) -> ()) { + public func createVanityURL(from originalURL: String, _ completion: @escaping (_ url: VanityURL?, _ error: WildlinkError?) -> ()) { guard let url = URL(string: originalURL) else { completion(nil, WildlinkError(errorData: [:], kind: .invalidURL)) return @@ -167,110 +145,43 @@ public class Wildlink: RequestAdapter, RequestRetrier { createVanityURL(from: url, completion) } - // Get the device click stats across a time period (potentially open-ended). Use the segmentation parameter to break - // the results down into consumable buckets. - // - // - parameter start: Date object defining the beginning of the query period. - // - parameter end: Optional Date object defining the end of the query period. If `nil`, Date.Now is used. - // - parameter segmentation: Separate the response by hour, day, month or year. Defaults to day. - // - parameter completion: Completion closure to be called once the stats are computed and downloaded. - public func getClickStats(from start: Date, to end: Date? = nil, with segmentation: TimePeriod = .day, completion: @escaping (_ stats: [ClickStats]?, _ error: WildlinkError?) -> ()) { - let queryUrl = baseUrl.appendingPathComponent("device/stats/clicks") - - // Add Headers - let headers = [ - "Content-Type":"application/json", - ] - - // JSON Body - var parameters: [String : Any] = [ - "by": segmentation, - "start": start.utc - ] - if let end = end { - parameters["end"] = end.utc - } - - sessionManager.request(queryUrl, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: headers) - .validate(statusCode: 200..<300) - .validate(contentType: ["application/json"]) - .responseJSON(queue: queue, options: .allowFragments, completionHandler: { response in - switch response.result { - case .success(let value): - if let json = value as? Array<[String: Any]> { - let arr = json.compactMap { ClickStats(dictionary: $0) } - completion(arr, nil) - self.parseResponseHeaders(response.response) - } else { - completion(nil, WildlinkError(errorData: [:], kind: .invalidResponse)) - } - - case .failure(let error): - completion(nil, self.generateWildlinkError(from: response.data, with: error)) - } - }) - } - // Get the commission statistics for this user. // // - parameter completion: Completion closure to be called once the commission statisticas are computed and downloaded. public func getCommissionSummary(_ completion: @escaping (_ stats: CommissionStats?, _ error: WildlinkError?) -> ()) { let queryUrl = baseUrl.appendingPathComponent("device/stats/commission-summary") - // Add Headers - let headers = [ - "Content-Type":"application/json", - ] - - sessionManager.request(queryUrl, method: .get, parameters: [:], encoding: URLEncoding.default, headers: headers) + AF.request(queryUrl, method: .get, parameters: [:], encoding: URLEncoding.default, headers: headers, interceptor: self) .validate(statusCode: 200..<300) .validate(contentType: ["application/json"]) - .responseJSON(queue: queue, options: .allowFragments, completionHandler: { response in + .responseDecodable(of: CommissionStats.self, decoder: decoder) { [unowned self] response in switch response.result { - case .success(let value): - if let json = value as? [String: Any] { - let stats = CommissionStats(dictionary: json) - completion(stats, nil) - self.parseResponseHeaders(response.response) - } else { - completion(nil, WildlinkError(errorData: [:], kind: .invalidResponse)) - } - + case .success(let stats): + completion(stats, nil) + self.parseHeaders(from: response.response) case .failure(let error): - completion(nil, self.generateWildlinkError(from: response.data, with: error)) + completion(nil, generateWildlinkError(from: response.data, with: error)) } - }) + } } // Get the details about commissions earned by the user. // // - parameter completion: Completion closure to be called when the results have been downloaded. - public func getCommissionDetails(_ completion: @escaping (_ details: [CommissionDetails]?, _ error: WildlinkError?) -> ()) { + public func getCommissionDetails(_ completion: @escaping (_ details: [CommissionDetail], _ error: WildlinkError?) -> ()) { let queryUrl = baseUrl.appendingPathComponent("device/stats/commission-detail") - // Add Headers - let headers = [ - "Content-Type":"application/json", - ] - - sessionManager.request(queryUrl, method: .get, parameters: [:], encoding: URLEncoding.default, headers: headers) + AF.request(queryUrl, method: .get, parameters: [:], encoding: URLEncoding.default, headers: headers, interceptor: self) .validate(statusCode: 200..<300) .validate(contentType: ["application/json"]) - .responseJSON(queue: queue, options: .allowFragments, completionHandler: { response in + .responseDecodable(of: [CommissionDetail].self, decoder: decoder) { [unowned self] response in switch response.result { - case .success(let value): - if let json = value as? Array<[String: Any]> { - let arr = json.compactMap { CommissionDetails(dictionary: $0) } - completion(arr, nil) - self.parseResponseHeaders(response.response) - } else { - completion(nil, WildlinkError(errorData: [:], kind: .invalidResponse)) - } - + case .success(let detailsArray): + completion(detailsArray, nil) case .failure(let error): - completion(nil, self.generateWildlinkError(from: response.data, with: error)) + completion([], self.generateWildlinkError(from: response.data, with: error)) } - }) + } } public func searchMerchants(ids: [String], names: [String], q: String?, disabled: Bool?, featured: Bool?, sortBy: WildlinkSortBy?, sortOrder: WildlinkSortOrder?, limit: Int?, _ completion: @escaping (_ merchants: [Merchant], _ error: WildlinkError?) -> ()) { @@ -288,11 +199,11 @@ public class Wildlink: RequestAdapter, RequestRetrier { } //if we were passed a disabled flag, build that if let disabled = disabled { - queryItems.append(URLQueryItem(name: "disabled", value: disabled ? "false" : "true")) + queryItems.append(URLQueryItem(name: "disabled", value: disabled ? "true" : "false")) } //if we were passed a featured flag, build that if let featured = featured { - queryItems.append(URLQueryItem(name: "featured", value: featured ? "false" : "true")) + queryItems.append(URLQueryItem(name: "featured", value: featured ? "true" : "false")) } //if sortBy was passed in, build it if let sortBy = sortBy { @@ -308,59 +219,39 @@ public class Wildlink: RequestAdapter, RequestRetrier { components?.path = "/v2/merchant" let queryUrl = components!.url! - // Add Headers - let headers = [ - "Content-Type":"application/json", - ] - - sessionManager.request(queryUrl, method: .get, parameters: [:], encoding: URLEncoding.default, headers: headers) + AF.request(queryUrl, method: .get, parameters: [:], encoding: URLEncoding.default, headers: headers, interceptor: self) .validate(statusCode: 200..<300) .validate(contentType: ["application/json"]) - .responseJSON(queue: queue, options: .allowFragments, completionHandler: { response in + .responseDecodable(of: MerchantList.self, decoder: decoder) { [unowned self] response in switch response.result { - case .success(let value): - if let json = value as? [String : Any], let array = json["Merchants"] as? Array<[String:Any]> { - let merchants = array.compactMap { Merchant(dictionary: $0) } - completion(merchants, nil) - } - + case .success(let merchantList): + completion(merchantList.merchants, nil) case .failure(let error): - completion([], self.generateWildlinkError(from: response.data, with: error)) + completion([], generateWildlinkError(from: response.data, with: error)) } - }) + } } - public func getMerchantByID(_ id: String, _ completion: @escaping (_ merchant: Merchant?, _ error: WildlinkError?) -> ()) { + public func getMerchantBy(_ id: String, _ completion: @escaping (_ merchant: Merchant?, _ error: WildlinkError?) -> ()) { let queryUrl = baseUrl.appendingPathComponent("merchant/\(id)") - // Add Headers - let headers = [ - "Content-Type":"application/json", - ] - - sessionManager.request(queryUrl, method: .get, parameters: [:], encoding: URLEncoding.default, headers: headers) + AF.request(queryUrl, method: .get, parameters: [:], encoding: URLEncoding.default, headers: headers, interceptor: self) .validate(statusCode: 200..<300) .validate(contentType: ["application/json"]) - .responseJSON(queue: queue, options: .allowFragments, completionHandler: { response in + .responseDecodable(of: Merchant.self, decoder: decoder) { [unowned self] response in switch response.result { - case .success(let value): - if let json = value as? [String: Any] { - let merchant = Merchant(dictionary: json) - completion(merchant, nil) - self.parseResponseHeaders(response.response) - } else { - completion(nil, WildlinkError(errorData: [:], kind: .invalidResponse)) - } - + case .success(let merchant): + completion(merchant, nil) + self.parseHeaders(from: response.response) case .failure(let error): - completion(nil, self.generateWildlinkError(from: response.data, with: error)) + completion(nil, generateWildlinkError(from: response.data, with: error)) } - }) + } } - // MARK: - RequestAdapter - - public func adapt(_ urlRequest: URLRequest) throws -> URLRequest { + // MARK: - RequestInterceptor + public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> Void) { + var urlRequest = urlRequest if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseUrl.absoluteString) { let iso8601DateString = Date().iso8601 @@ -373,42 +264,35 @@ public class Wildlink: RequestAdapter, RequestRetrier { let os = UIDevice.current.systemVersion let userAgent = "WildlinkSDK/\(fullVersionString) (\(device)) iOS \(os)" - var urlRequest = urlRequest urlRequest.setValue(userAgent, forHTTPHeaderField: "User-Agent") urlRequest.setValue(authString, forHTTPHeaderField: "Authorization") urlRequest.setValue(iso8601DateString, forHTTPHeaderField: "X-WF-DateTime") - return urlRequest } - - return urlRequest + completion(.success(urlRequest)) } - // MARK: - RequestRetrier - - public func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) { + public func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) { lock.lock() ; defer { lock.unlock() } if let response = request.task?.response as? HTTPURLResponse, //only retry on 5XX errors - (response.statusCode >= 500 && response.statusCode < 600) { + 500..<600 ~= response.statusCode { requestsToRetry.append(completion) if !isRefreshing { - refreshDeviceToken { [weak self] succeeded, deviceToken, deviceKey, deviceId in - guard let strongSelf = self else { return } - - strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() } + refreshDeviceToken { [unowned self] succeeded, deviceToken, deviceKey, deviceId in + lock.lock() ; defer { lock.unlock() } if let localDeviceToken = deviceToken { - strongSelf.deviceToken = localDeviceToken + self.deviceToken = localDeviceToken } - strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) } - strongSelf.requestsToRetry.removeAll() + requestsToRetry.forEach { $0(.doNotRetry) } + requestsToRetry.removeAll() } } } else { - completion(false, 0.0) + completion(.doNotRetryWithError(error)) } } @@ -417,23 +301,13 @@ public class Wildlink: RequestAdapter, RequestRetrier { // Requests a device token. // Reference: https://github.com/wildlink/deviceapi // - private func refreshDeviceToken(completion: @escaping RefreshCompletion) { + internal func refreshDeviceToken(completion: @escaping RefreshCompletion) { guard !isRefreshing else { return } isRefreshing = true let queryUrl = baseUrl.appendingPathComponent("device") - let iso8601DateString = Date().iso8601 - let authString = getAuthorizationString(dateString: iso8601DateString) - - // Add Headers - let headers = [ - "Content-Type":"application/json", - "Authorization": authString, - "X-WF-DateTime": iso8601DateString - ] - // JSON Body var parameters: [String : Any] = [ "OS": UIDevice.current.systemName @@ -443,35 +317,26 @@ public class Wildlink: RequestAdapter, RequestRetrier { parameters["DeviceKey"] = key } - sessionManager.request(queryUrl, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers) + AF.request(queryUrl, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers, interceptor: self) .validate(statusCode: 200..<300) .validate(contentType: ["application/json"]) - .responseJSON(queue: queue, options: .allowFragments, completionHandler: { [weak self] response in - guard let strongSelf = self else { return } + .responseDecodable(of: Device.self, decoder: decoder) { [unowned self] response in switch response.result { - case .success(let value): - - if let json = value as? [String: Any], - let deviceToken = json["DeviceToken"] as? String { - let deviceKey = json["DeviceKey"] as? String - let deviceId = json["DeviceID"] as? UInt64 - completion(true, deviceToken, deviceKey, deviceId) - } else { - Logger.error("Failed to refresh device token: \(String(describing: response.result))") - completion(false, nil, nil, nil) - } - case .failure: - Logger.error("Failed to refresh device token: \(String(describing: response.result))") + case .success(let device): + completion(true, device.token, device.key, device.id) + case .failure(let error): + print(error) completion(false, nil, nil, nil) } - strongSelf.isRefreshing = false - }) + self.isRefreshing = false + } } - private func parseResponseHeaders(_ response: HTTPURLResponse?) { + internal func parseHeaders(from response: HTTPURLResponse?) { guard let localResponse = response else { return } - if let localDeviceToken = localResponse.allHeaderFields["x-wf-devicetoken"], let token = localDeviceToken as? String { + if let localDeviceToken = localResponse.allHeaderFields["x-wf-devicetoken"], + let token = localDeviceToken as? String { update(token: token) } } @@ -513,8 +378,7 @@ public class Wildlink: RequestAdapter, RequestRetrier { } extension Wildlink { - - fileprivate func getSignatureKey(dateString: String, deviceToken: String? = nil, senderToken: String? = nil) -> String { + internal func getSignatureKey(dateString: String, deviceToken: String? = nil, senderToken: String? = nil) -> String { let stringToSign = "\(dateString)\n\(deviceToken ?? "")\n\(senderToken ?? "")\n" @@ -524,12 +388,11 @@ extension Wildlink { return hmac256 } - fileprivate func getAuthorizationString(dateString: String, deviceToken: String? = nil, senderToken: String? = nil) -> String { + internal func getAuthorizationString(dateString: String, deviceToken: String? = nil, senderToken: String? = nil) -> String { let hmac256 = getSignatureKey(dateString: dateString, deviceToken: deviceToken, senderToken: senderToken) let appToken = Wildlink.appID return "WFAV1 \(appToken):\(hmac256):\(deviceToken ?? ""):\(senderToken ?? "")" } - } diff --git a/Wildlink/Tests/ClickStatsTests.swift b/Wildlink/Tests/ClickStatsTests.swift deleted file mode 100644 index 4f54e52..0000000 --- a/Wildlink/Tests/ClickStatsTests.swift +++ /dev/null @@ -1,50 +0,0 @@ -//Tests for the ClickStats object in the Wildlink Cocoapod - -import XCTest -import Wildlink - -class ClickStatsTests: XCTestCase { - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testGoodDictionaryCreatesValidObject() { - let dictionary: [String : Any] = [ - "Period": "hour", - "ClickDate": "2017-09-12T22:00:00Z", - "ClickCount": 351 - ] - let stats: ClickStats? = ClickStats(dictionary: dictionary) - XCTAssertNotNil(stats) - } - - func testBadDictionaryCreatesNilObject() { - let dictionary: [String : Any] = [ - "Peroid": "hour", - "ClickDate": "2017-09-12T22:00:00Z", - "ClickCount": 351 - ] - let stats: ClickStats? = ClickStats(dictionary: dictionary) - XCTAssertNil(stats) - } - - func testDictionaryReturn() { - let dictionary: [String : Any] = [ - "Period": "hour", - "ClickDate": "2017-09-12T22:00:00Z", - "ClickCount": 351 - ] - let stats: ClickStats? = ClickStats(dictionary: dictionary) - if let returnDictionary = stats?.dictionary { - XCTAssertEqual(dictionary["Period"] as? String, returnDictionary["Period"] as? String) - XCTAssertEqual(dictionary["ClickDate"] as? String, returnDictionary["ClickDate"] as? String) - XCTAssertEqual(dictionary["ClickCount"] as? Int, returnDictionary["ClickCount"] as? Int) - } else { - XCTFail() - } - } -} diff --git a/Wildlink/Tests/CommisionStatsTests.swift b/Wildlink/Tests/CommisionStatsTests.swift index a9528f8..f307383 100644 --- a/Wildlink/Tests/CommisionStatsTests.swift +++ b/Wildlink/Tests/CommisionStatsTests.swift @@ -18,38 +18,21 @@ class CommissionStatsTests: XCTestCase { } func testGoodDictionaryCreatesValidObject() { - let dictionary: [String : Any] = [ - "PendingAmount": "0.49", - "ReadyAmount": "0.25", - "PaidAmount": "3.62" - ] - let stats: CommissionStats? = CommissionStats(dictionary: dictionary) + let data = """ +{"PendingAmount": "0.49", "ReadyAmount": "0.25", "PaidAmount": "3.62"} +""".data(using: .utf8)! + let stats = try? JSONDecoder().decode(CommissionStats.self, from: data) XCTAssertNotNil(stats) + XCTAssertEqual(stats?.pendingAmount, "0.49") + XCTAssertEqual(stats?.readyAmount, "0.25") + XCTAssertEqual(stats?.paidAmount, "3.62") } func testBadDictionaryCreatesNilObject() { - let dictionary: [String : Any] = [ - "Peroid": "hour", - "ClickDate": "2017-09-12T22:00:00Z", - "ClickCount": 351 - ] - let stats: CommissionStats? = CommissionStats(dictionary: dictionary) + let badData = """ +{"Period": "hour", "click": "xyz"} +""".data(using: .utf8)! + let stats = try? JSONDecoder().decode(CommissionStats.self, from: badData) XCTAssertNil(stats) } - - func testDictionaryReturn() { - let dictionary: [String : Any] = [ - "PendingAmount": "0.49", - "ReadyAmount": "0.25", - "PaidAmount": "3.62" - ] - let stats: CommissionStats? = CommissionStats(dictionary: dictionary) - if let returnDictionary = stats?.dictionary { - XCTAssertEqual(dictionary["PendingAmount"] as? String, returnDictionary["PendingAmount"] as? String) - XCTAssertEqual(dictionary["ReadyAmount"] as? String, returnDictionary["ReadyAmount"] as? String) - XCTAssertEqual(dictionary["PaidAmount"] as? Int, returnDictionary["PaidAmount"] as? Int) - } else { - XCTFail() - } - } } diff --git a/Wildlink/Tests/CommissionDetailTests.swift b/Wildlink/Tests/CommissionDetailTests.swift new file mode 100644 index 0000000..b40c6c7 --- /dev/null +++ b/Wildlink/Tests/CommissionDetailTests.swift @@ -0,0 +1,44 @@ +// +// CommissionDetailTests.swift +// Wildlink-Unit-Tests +// +// Created by Kyle Kurz on 7/15/19. +// + +import XCTest +import Wildlink + +class CommissionDetailTests: XCTestCase { + let decoder = JSONDecoder() + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + decoder.dateDecodingStrategy = .iso8601 + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func testGoodDictionaryCreatesValidObject() { + let data = """ +{"ID": 84785, "CommissionIDs": [84785], "Date": "2017-12-03T10:29:45Z", "Amount": "0.49", "Status": "READY", "Merchant": "Volcom"} +""".data(using: .utf8)! + let details = try? decoder.decode(CommissionDetail.self, from: data) + XCTAssertNotNil(details) + XCTAssertEqual(details?.id, 84785) + XCTAssertEqual(details?.commissionIds, [84785]) + XCTAssertEqual(details?.date.utc, "2017-12-03T10:29:45Z") + XCTAssertEqual(details?.amount, "0.49") + XCTAssertEqual(details?.status, .ready) + XCTAssertEqual(details?.merchant, "Volcom") + } + + func testBadDictionaryCreatesNilObject() { + let badData = """ +{"Period": "hour", "click": "xyz"} +""".data(using: .utf8)! + let details = try? decoder.decode(CommissionDetail.self, from: badData) + XCTAssertNil(details) + } +} diff --git a/Wildlink/Tests/CommissionDetailsTests.swift b/Wildlink/Tests/CommissionDetailsTests.swift deleted file mode 100644 index 195f9c0..0000000 --- a/Wildlink/Tests/CommissionDetailsTests.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// CommissionDetailsTests.swift -// Wildlink-Unit-Tests -// -// Created by Kyle Kurz on 7/15/19. -// - -import XCTest -import Wildlink - -class CommissionDetailsTests: XCTestCase { - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testGoodDictionaryCreatesValidObject() { - let dictionary: [String : Any] = [ - "ID": 84785, - "CommissionIDs": [84785], - "Date": "2017-12-03T10:29:45Z", - "Amount": "0.49", - "Status": "READY", - "Merchant": "Volcom" - ] - let stats: CommissionDetails? = CommissionDetails(dictionary: dictionary) - XCTAssertNotNil(stats) - } - - func testBadDictionaryCreatesNilObject() { - let dictionary: [String : Any] = [ - "Peroid": "hour", - "ClickDate": "2017-09-12T22:00:00Z", - "ClickCount": 351 - ] - let stats: CommissionDetails? = CommissionDetails(dictionary: dictionary) - XCTAssertNil(stats) - } - - func testDictionaryReturn() { - let dictionary: [String : Any] = [ - "ID": 84785, - "CommissionIDs": [84785], - "Date": "2017-12-03T10:29:45Z", - "Amount": "0.49", - "Status": "READY", - "Merchant": "Volcom" - ] - let stats: CommissionDetails? = CommissionDetails(dictionary: dictionary) - if let returnDictionary = stats?.dictionary { - XCTAssertEqual(dictionary["ID"] as? Int, returnDictionary["ID"] as? Int) - XCTAssertEqual(dictionary["CommissionIDs"] as? [Int], returnDictionary["CommissionIDs"] as? [Int]) - XCTAssertEqual(dictionary["Date"] as? String, returnDictionary["Date"] as? String) - XCTAssertEqual(dictionary["Amount"] as? String, returnDictionary["Amount"] as? String) - XCTAssertEqual(dictionary["Status"] as? String, returnDictionary["Status"] as? String) - XCTAssertEqual(dictionary["Merchant"] as? String, returnDictionary["Merchant"] as? String) - } else { - XCTFail() - } - } -} diff --git a/Wildlink/Tests/ConstantsTests.swift b/Wildlink/Tests/ConstantsTests.swift new file mode 100644 index 0000000..2ffa593 --- /dev/null +++ b/Wildlink/Tests/ConstantsTests.swift @@ -0,0 +1,20 @@ +// +// ConstantsTests.swift +// Wildlink-Unit-Tests +// +// Created by Kyle Kurz - Wildfire on 10/6/21. +// + +import Foundation +import XCTest +@testable import Wildlink + +class ConstantsTests: XCTestCase { + func testBaseUrlProd() { + XCTAssertEqual(APIConstants.baseUrlProd , URL(string: "https://api.wfi.re/v2")) + } + + func testBaseUrlDev() { + XCTAssertEqual(APIConstants.baseUrlDev, URL(string: "https://dev-api.wfi.re/v2")) + } +} diff --git a/Wildlink/Tests/DeviceTests.swift b/Wildlink/Tests/DeviceTests.swift new file mode 100644 index 0000000..d06a94a --- /dev/null +++ b/Wildlink/Tests/DeviceTests.swift @@ -0,0 +1,31 @@ +// +// DeviceTests.swift +// Wildlink-Unit-Tests +// +// Created by Kyle Kurz - Wildfire on 10/6/21. +// + +import Foundation +import XCTest +@testable import Wildlink + +class DeviceTests: XCTestCase { + func testGoodDictionaryCreatesValidObject() { + let data = """ +{"DeviceToken": "abcdef123456", "DeviceKey": "654321", "DeviceID": 12345} +""".data(using: .utf8)! + let device = try? JSONDecoder().decode(Device.self, from: data) + XCTAssertNotNil(device) + XCTAssertEqual(device?.id, 12345) + XCTAssertEqual(device?.token, "abcdef123456") + XCTAssertEqual(device?.key, "654321") + } + + func testBadDictionaryCreatesNilObject() { + let badData = """ +{"Period": "hour", "click": "xyz"} +""".data(using: .utf8)! + let device = try? JSONDecoder().decode(Device.self, from: badData) + XCTAssertNil(device) + } +} diff --git a/Wildlink/Tests/ImageTests.swift b/Wildlink/Tests/ImageTests.swift new file mode 100644 index 0000000..98f1cd6 --- /dev/null +++ b/Wildlink/Tests/ImageTests.swift @@ -0,0 +1,62 @@ +// +// ImageTests.swift +// Wildlink-Unit-Tests +// +// Created by Kyle Kurz - Wildfire on 10/6/21. +// + +import Foundation +import XCTest +@testable import Wildlink + +class ImageTests: XCTestCase { + func testGoodDictionaryCreatesValidObject() { + let data = """ +{"ID": 1508, "Kind": "LOGO", "Ordinal": 1, "ImageID": 1509, "URL": "https://dev-images.wildlink.me/wl-image/ecf9466c132d140bcc1af7bf74cb8c20fde1ebe3.jpeg", "Width": 200, "Height": 200} +""".data(using: .utf8)! + let image = try? JSONDecoder().decode(Image.self, from: data) + XCTAssertNotNil(image) + XCTAssertEqual(image?.id, 1508) + XCTAssertEqual(image?.height, 200) + XCTAssertEqual(image?.width, 200) + XCTAssertEqual(image?.ordinal, 1) + XCTAssertEqual(image?.imageId, 1509) + XCTAssertEqual(image?.kind, .logo) + } + + func testGoodDictionaryCreatesValidObjectFeaturedImage() { + let data = """ +{"ID": 1508, "Kind": "FEATURED", "Ordinal": 1, "ImageID": 1509, "URL": "https://dev-images.wildlink.me/wl-image/ecf9466c132d140bcc1af7bf74cb8c20fde1ebe3.jpeg", "Width": 200, "Height": 200} +""".data(using: .utf8)! + let image = try? JSONDecoder().decode(Image.self, from: data) + XCTAssertNotNil(image) + XCTAssertEqual(image?.id, 1508) + XCTAssertEqual(image?.height, 200) + XCTAssertEqual(image?.width, 200) + XCTAssertEqual(image?.ordinal, 1) + XCTAssertEqual(image?.imageId, 1509) + XCTAssertEqual(image?.kind, .featured) + } + + func testGoodDictionaryCreatesValidObjectGeneralImage() { + let data = """ +{"ID": 1508, "Kind": "GENERAL", "Ordinal": 1, "ImageID": 1509, "URL": "https://dev-images.wildlink.me/wl-image/ecf9466c132d140bcc1af7bf74cb8c20fde1ebe3.jpeg", "Width": 200, "Height": 200} +""".data(using: .utf8)! + let image = try? JSONDecoder().decode(Image.self, from: data) + XCTAssertNotNil(image) + XCTAssertEqual(image?.id, 1508) + XCTAssertEqual(image?.height, 200) + XCTAssertEqual(image?.width, 200) + XCTAssertEqual(image?.ordinal, 1) + XCTAssertEqual(image?.imageId, 1509) + XCTAssertEqual(image?.kind, .general) + } + + func testBadDictionaryCreatesNilObject() { + let badData = """ +{"Period": "hour", "click": "xyz"} +""".data(using: .utf8)! + let image = try? JSONDecoder().decode(Image.self, from: badData) + XCTAssertNil(image) + } +} diff --git a/Wildlink/Tests/MerchantTests.swift b/Wildlink/Tests/MerchantTests.swift index cbd90f3..8545c97 100644 --- a/Wildlink/Tests/MerchantTests.swift +++ b/Wildlink/Tests/MerchantTests.swift @@ -18,50 +18,40 @@ class MerchantTests: XCTestCase { } func testGoodDictionaryCreatesValidObject() { - let dictionary: [String : Any] = [ - "ID": 5476062, - "Name": "B.O.R.N. Night Owl Forex Robot", - "Disabled": true, - "Featured": false, - "ShortCode": "2dUB3p3OAgw", - "ShortURL": "http://example.com/2dUB3p3OAgw", - "Images": [] - ] - let stats: Merchant? = Merchant(dictionary: dictionary) - XCTAssertNotNil(stats) + let data = """ + { + "ID": 8969, + "Name": "MAGIX u0026 VEGAS Creative Software", + "Disabled": false, + "Featured": false, + "ShortCode": "uOpXiUYg", + "ShortURL": "https://dev.wild.link/uOpXiUYg", + "BrowserExtensionDisabled": false, + "CashbackDisabled": false, + "ShareAndEarnDisabled": false, + "DeeplinkDisabled": false, + "Images": [ + { + "ID": 1226, + "Kind": "LOGO", + "Ordinal": 1, + "ImageID": 1227, + "URL": "https://dev-images.wildlink.me/wl-image/e6bf024f8ebfdb49fc7926b2fac620c4b069e64e.jpeg", + "Width": 200, + "Height": 200 + } + ] } - - func testBadDictionaryCreatesNilObject() { - let dictionary: [String : Any] = [ - "Peroid": "hour", - "ClickDate": "2017-09-12T22:00:00Z", - "ClickCount": 351 - ] - let stats: Merchant? = Merchant(dictionary: dictionary) - XCTAssertNil(stats) +""".data(using: .utf8)! + let merchant = try? JSONDecoder().decode(Merchant.self, from: data) + XCTAssertNotNil(merchant) } - func testDictionaryReturn() { - let dictionary: [String : Any] = [ - "ID": 5476062, - "Name": "B.O.R.N. Night Owl Forex Robot", - "Disabled": true, - "Featured": false, - "ShortCode": "2dUB3p3OAgw", - "ShortURL": "http://example.com/2dUB3p3OAgw", - "Images": [] - ] - let stats: Merchant? = Merchant(dictionary: dictionary) - if let returnDictionary = stats?.dictionary { - XCTAssertEqual(dictionary["ID"] as? Int, returnDictionary["ID"] as? Int) - XCTAssertEqual(dictionary["Name"] as? String, returnDictionary["Name"] as? String) - XCTAssertEqual(dictionary["Disabled"] as? Bool, returnDictionary["Disabled"] as? Bool) - XCTAssertEqual(dictionary["Featured"] as? Bool, returnDictionary["Featured"] as? Bool) - XCTAssertEqual(dictionary["ShortCode"] as? String, returnDictionary["ShortCode"] as? String) - XCTAssertEqual(dictionary["ShortURL"] as? String, returnDictionary["ShortURL"] as? String) - XCTAssertEqual(dictionary["Images"] as? [String], returnDictionary["Images"] as? [String]) - } else { - XCTFail() - } + func testBadDictionaryCreatesNilObject() { + let badData = """ +{"Period": "hour", "click": "xyz"} +""".data(using: .utf8)! + let merchant = try? JSONDecoder().decode(Merchant.self, from: badData) + XCTAssertNil(merchant) } } diff --git a/Wildlink/Tests/StringExTests.swift b/Wildlink/Tests/StringExTests.swift new file mode 100644 index 0000000..75982c6 --- /dev/null +++ b/Wildlink/Tests/StringExTests.swift @@ -0,0 +1,30 @@ +// +// StringExTests.swift +// Wildlink-Unit-Tests +// +// Created by Kyle Kurz - Wildfire on 10/6/21. +// + +import CommonCrypto +import Foundation +import XCTest +@testable import Wildlink + +class StringExTests: XCTestCase { + func testStringFromResult() { + let string = "this is a long string to test" + let ptr = UnsafeMutablePointer.allocate(capacity: Int(CC_SHA256_DIGEST_LENGTH)) + ptr.initialize(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) + for i in 0..