diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..76fdd158 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug Report +about: This template is for reporting bugs you have found. +title: "[DATE]: [BUG TITLE]" +labels: Needs Triage +assignees: jeremyauclert, nurito-burrito +--- + +## Information + +Device: + +iOS version: + +App Version: + +Environment: + +Frequency (How often does the bug occur?): + +## Expected Behaviour +*What do you expect to happen?* + +## Actual Behaviour +*What actually happens?* + +### Screenshots +*Screenshot or video of the bug* + +## Reproduction Steps +*Steps on how to reproduce?* diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..cea14c89 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Karhoo Developer Site + url: developer.karhoo.com + about: You may find answers to your questions here + - name: Karhoo website + url: karhoo.com + about: The main Karhoo Website. You can contact us from here. diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md new file mode 100644 index 00000000..5d257033 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/custom.md @@ -0,0 +1,10 @@ +--- +name: Custom Template +about: This template can be used for anything that does fit a feature request nor bug report. +title: "[DATE]: [TITLE]" +labels: +assignees: +--- + +*Please feel free to include any information you consider important to better explain your ticket/request. Please do try to keep everything formatted* +[Markdown Cheat Sheet](https://guides.github.com/pdfs/markdown-cheatsheet-online.pdf) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..f58dcb66 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature Request +about: This is a template for requesting a feature into the Network SDK. +title: "[DATE]: [Feature Title]" +labels: +assignees: +--- + +## What kind of feature do you want? +*Explain the kind and type of feature you would like to see in the Network* + +## How will it work? +*Explain how you think your feature idea will work* + +## Why would it be a good idea to implement this? +*Explain why you think we should go ahead and add this into to our Network SDK?* + +### Examples: +*Show any examples you may have about what you are asking for here* diff --git a/.github/delete-merged-branch-config.yml b/.github/delete-merged-branch-config.yml new file mode 100644 index 00000000..cc1d931f --- /dev/null +++ b/.github/delete-merged-branch-config.yml @@ -0,0 +1,5 @@ +exclude: + - develop + - master + - release + - release/* diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..787d4154 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,30 @@ +## JIRA +[MOB-XXXX](https://karhoo.atlassian.net/browse/MOB-XXXX) + +## DESCRIPTION +[Create a description of what you have changed in this branch] + +## Types of changes + +What types of changes does your code introduce to the project? +_Put an `x` in the boxes that apply_ + +- [ ] Bugfix +- [ ] New feature +- [ ] Tech Debt +- [ ] Breaking change +- [ ] Documentation Update +- [ ] Other: *define what type of change it is* + +## CHECKLIST + +_Put an `x` in the boxes that apply. You can also fill these out after creating the PR. This is simply a reminder of what we are going to look for before merging code._ +Please review the guidelines for contributing to this repository. + +- [ ] All work relating to the ticket is complete (Code complete/Acceptance Criteria met) +- [ ] Lint and unit tests pass locally (if appropriate) +- [ ] Added tests that prove the fix is effective or that feature works (if appropriate) +- [ ] Documentation (if appropriate) + +## SCREENSHOTS +[Add screenshots if any for changes you have made to the App/UI] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..d653ba3e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI-Development + +on: + push: + branches-ignore: # disable workflows + - '**' + #branches: + #- '*' + #- '!master' + +jobs: + Unit_tests: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Bundle install + run: bundle install + - name: Run Unit Tests + run: + xcodebuild -workspace 'KarhooSDK.xcworkspace' -scheme 'KarhooSDK' -destination 'platform=iOS Simulator,name=iPhone 11' test | xcpretty --test --color + - name: Fastlane Xcov report + run: fastlane ios XcovReport + + Build_Project: + runs-on: macos-latest + needs: [Unit_tests] + steps: + - uses: actions/checkout@v2 + - name: Build SDK + run: + xcodebuild -workspace 'KarhooSDK.xcworkspace' -scheme 'KarhooSDK' -destination 'generic/platform=iOS' -configuration Release build CODE_SIGNING_ALLOWED=NO | xcpretty diff --git a/.github/workflows/ios-develop-ci.yml b/.github/workflows/ios-develop-ci.yml new file mode 100644 index 00000000..fa27ef51 --- /dev/null +++ b/.github/workflows/ios-develop-ci.yml @@ -0,0 +1,28 @@ +name: Develop Branch CI + +on: + push: + branches: + - 'develop' + +jobs: + Unit_tests: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Bundle install + run: bundle install + - name: Run Unit Tests + run: + xcodebuild -workspace 'KarhooSDK.xcworkspace' -scheme 'KarhooSDK' -destination 'platform=iOS Simulator,name=iPhone 11' test | xcpretty --test --color +# - name: Fastlane Xcov report +# run: fastlane ios XcovReport + + Build_Project: + runs-on: macos-latest + needs: [Unit_tests] + steps: + - uses: actions/checkout@v2 + - name: Build SDK + run: + xcodebuild -workspace 'KarhooSDK.xcworkspace' -scheme 'KarhooSDK' -destination 'generic/platform=iOS' -configuration Release build CODE_SIGNING_ALLOWED=NO | xcpretty diff --git a/.github/workflows/ios-feature-ci.yml b/.github/workflows/ios-feature-ci.yml new file mode 100644 index 00000000..0072396b --- /dev/null +++ b/.github/workflows/ios-feature-ci.yml @@ -0,0 +1,31 @@ +name: Feature Branch CI + +on: + push: + branches: + - '*' + - '!develop' + - '!master' + - '!release*' + +jobs: + Unit_tests: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - name: Bundle install + run: bundle install + - name: Run Unit Tests + run: + xcodebuild -workspace 'KarhooSDK.xcworkspace' -scheme 'KarhooSDK' -destination 'platform=iOS Simulator,name=iPhone 11' test | xcpretty --test --color +# - name: Fastlane Xcov report +# run: fastlane ios XcovReport + + Build_Project: + runs-on: macos-latest + needs: [Unit_tests] + steps: + - uses: actions/checkout@v2 + - name: Build SDK + run: + xcodebuild -workspace 'KarhooSDK.xcworkspace' -scheme 'KarhooSDK' -destination 'generic/platform=iOS' -configuration Release build CODE_SIGNING_ALLOWED=NO | xcpretty diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fbf9089d --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*.DS_Store +KarhooSDK.xcodeproj/xcuserdata/ +KarhooSDK.xcodeproj/project.xcworkspace/xcuserdata +KarhooSDK.xcworkspace/xcuserdata/ +KarhooSDK.framework +KarhooSDK.framework/ +build/ +IDEWorkspaceChecks.plist +WorkspaceSettings.xcsettings + +# CocoaPods +Pods/ + +# Fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..0d0ccaa5 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,8 @@ +excluded: # paths to ignore during linting. Takes precedence over `included`. + - Pods +disabled_rules: # rule identifiers to exclude from running + - identifier_name + - force_try + - redundant_string_enum_value + - trailing_whitespace + - line_length diff --git a/Client/AppDelegate.swift b/Client/AppDelegate.swift new file mode 100644 index 00000000..eb141e07 --- /dev/null +++ b/Client/AppDelegate.swift @@ -0,0 +1,42 @@ +// +// AppDelegate.swift +// Client +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import UIKit +import KarhooSDK + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + Karhoo.set(configuration: SDKConfig()) + return true + } + + // MARK: UISceneSession Lifecycle + + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } +} + +struct SDKConfig: KarhooSDKConfiguration { + + func environment() -> KarhooEnvironment { + return .sandbox + } + + func authenticationMethod() -> AuthenticationMethod { + /* let tokenSettings = TokenExchangeSettings(clientId: "", + scope: "")*/ + + let guestSettings = GuestSettings(identifier: "", + referer: "") + return .guest(settings: guestSettings) + } +} diff --git a/Client/Assets.xcassets/AppIcon.appiconset/Contents.json b/Client/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d8db8d65 --- /dev/null +++ b/Client/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Client/Assets.xcassets/Contents.json b/Client/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Client/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Client/AuthRequestsTesterViewController.swift b/Client/AuthRequestsTesterViewController.swift new file mode 100644 index 00000000..87d25d56 --- /dev/null +++ b/Client/AuthRequestsTesterViewController.swift @@ -0,0 +1,103 @@ +// +// AuthRequestsTesterViewController.swift +// Client +// +// Created by Tiziano Bruni on 04/03/2020. +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import UIKit +import KarhooSDK + +enum AuthRequestType: String { + case login = "Login" + case revoke = "Revoke" +} + +class AuthRequestsTesterViewController: UIViewController { + + var responseLabelTitle: UILabel! + var responseLabel: UILabel! + var requestType: AuthRequestType + + init(requestType: AuthRequestType) { + self.requestType = requestType + super.init(nibName: nil, bundle: nil) + self.setUpView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUpView() { + view.backgroundColor = .white + + responseLabelTitle = UILabel() + responseLabelTitle.translatesAutoresizingMaskIntoConstraints = false + responseLabelTitle.text = "Request response" + responseLabelTitle.textAlignment = .center + responseLabelTitle.font = UIFont.boldSystemFont(ofSize: 22.0) + view.addSubview(responseLabelTitle) + + _ = [responseLabelTitle.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10.0), + responseLabelTitle.leadingAnchor.constraint(equalTo: view.leadingAnchor), + responseLabelTitle.trailingAnchor.constraint(equalTo: view.trailingAnchor), + responseLabelTitle.heightAnchor.constraint(equalToConstant: 25.0)].map { $0.isActive = true } + + responseLabel = UILabel() + responseLabel.translatesAutoresizingMaskIntoConstraints = false + responseLabel.text = "Waiting for response..." + responseLabel.font = UIFont.systemFont(ofSize: 18.0) + responseLabel.numberOfLines = 0 + view.addSubview(responseLabel) + + _ = [responseLabel.topAnchor.constraint(equalTo: responseLabelTitle.bottomAnchor, constant: 20.0), + responseLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10.0), + responseLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10.0), + responseLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor)].map { $0.isActive = true } + + performRequest() + } + + private func performRequest() { + switch requestType { + case .login: + login() + case .revoke: + revoke() + } + } + + private func login() { + Karhoo.getAuthService().login(token: "").execute(callback: { [weak self] result in + if result.isSuccess() { + self?.responseLabel.textColor = .green + let output = """ + Success! + Payload: \(result.successValue().debugDescription) + """ + self?.responseLabel.text = output + } else { + let output = """ + Fail! + Payload: \(result.errorValue().debugDescription) + """ + self?.responseLabel.textColor = .red + self?.responseLabel.text = output + } + }) + } + + private func revoke() { + Karhoo.getAuthService().revoke().execute(callback: { [weak self] result in + if result.isSuccess() { + self?.responseLabel.textColor = .green + self?.responseLabel.text = "Success: \(result.isSuccess())" + } else { + self?.responseLabel.textColor = .red + self?.responseLabel.text = "Failed\nError: \(String(describing: result.errorValue().debugDescription))" + } + }) + } +} diff --git a/Client/Base.lproj/LaunchScreen.storyboard b/Client/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..865e9329 --- /dev/null +++ b/Client/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Client/Info.plist b/Client/Info.plist new file mode 100644 index 00000000..cb967d41 --- /dev/null +++ b/Client/Info.plist @@ -0,0 +1,58 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Client/SceneDelegate.swift b/Client/SceneDelegate.swift new file mode 100644 index 00000000..9d5d611e --- /dev/null +++ b/Client/SceneDelegate.swift @@ -0,0 +1,54 @@ +// +// SceneDelegate.swift +// Client +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + @available(iOS 13.0, *) + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let windowScene = (scene as? UIWindowScene) else { return } + + window = UIWindow(frame: windowScene.coordinateSpace.bounds) + window?.windowScene = windowScene + window?.rootViewController = UINavigationController(rootViewController: ViewController()) + window?.makeKeyAndVisible() + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } +} diff --git a/Client/ViewController.swift b/Client/ViewController.swift new file mode 100644 index 00000000..e9a62251 --- /dev/null +++ b/Client/ViewController.swift @@ -0,0 +1,121 @@ +// +// ViewController.swift +// Client +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import UIKit +import KarhooSDK + +class ViewController: UIViewController { + + var table: UITableView! + let endpoints = [ + ["title": "Login", "type": "Token Exchange"], + ["title": "Revoke", "type": "Token Exchange"] + ] + + var data = [TableSection: [[String: String]]]() + + let SectionHeaderHeight: CGFloat = 25 + + enum TableSection: Int { + case tokenExchange = 0 + } + + init() { + super.init(nibName: nil, bundle: nil) + self.setUpView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setUpView() { + + view.backgroundColor = .white + title = "Endpoints" + + sortData() + + table = UITableView() + table.accessibilityIdentifier = "tableView" + table.translatesAutoresizingMaskIntoConstraints = false + table.dataSource = self + table.delegate = self + table.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + table.tableFooterView = UIView() + + view.addSubview(table) + _ = [table.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + table.leadingAnchor.constraint(equalTo: view.leadingAnchor), + table.trailingAnchor.constraint(equalTo: view.trailingAnchor), + table.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)].map { $0.isActive = true } + } + + private func sortData() { + data[.tokenExchange] = endpoints.filter({ $0["type"] == "Token Exchange"}) + } + + override func viewDidLoad() { + super.viewDidLoad() + } +} + +extension ViewController: UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return endpoints.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + cell.accessoryType = .disclosureIndicator + cell.selectionStyle = .none + + if let tableSection = TableSection(rawValue: indexPath.section), let endpoints = data[tableSection]?[indexPath.row] { + cell.textLabel?.text = endpoints["title"] + } + + return cell + } +} + +extension ViewController: UITableViewDelegate { + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return SectionHeaderHeight + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + + let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.bounds.width, height: SectionHeaderHeight)) + view.backgroundColor = .lightGray + + let label = UILabel(frame: CGRect(x: 15, y: 0, width: tableView.bounds.width - 30, height: SectionHeaderHeight)) + label.font = UIFont.boldSystemFont(ofSize: 15) + label.textColor = UIColor.black + + if let tableSection = TableSection(rawValue: section) { + switch tableSection { + case .tokenExchange: + label.text = "Token Exchange" + } + } + view.addSubview(label) + + return view + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let title = tableView.cellForRow(at: indexPath)?.textLabel?.text else { return } + let testViewController = AuthRequestsTesterViewController(requestType: AuthRequestType(rawValue: title)!) + navigationController?.pushViewController(testViewController, animated: true) + } +} diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 00000000..3e693d64 --- /dev/null +++ b/Dangerfile @@ -0,0 +1,2 @@ +junit.parse "fastlane/test_output/NetworkSDKTests.xml" +junit.report diff --git a/DangerfilePRMetaValidation b/DangerfilePRMetaValidation new file mode 100644 index 00000000..f4ccfcc5 --- /dev/null +++ b/DangerfilePRMetaValidation @@ -0,0 +1,8 @@ + +# Make it more obvious that a PR is a work in progress and shouldn't be merged yet +warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]" + +# Warn when there is a big PR +warn("Big PR") if git.lines_of_code > 500 + +warn "PR title does not include a ticket number (IOS-XXX)" if !github.pr_title.include? "IOS-" \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..a71d5640 --- /dev/null +++ b/Gemfile @@ -0,0 +1,14 @@ +source "https://rubygems.org" + +gem 'fastlane' +gem 'cocoapods' +gem 'slather' +gem 'xcov' + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'danger' +gem 'danger-xcov' +gem 'danger-swiftlint' +gem 'danger-xcode_summary' +gem 'danger-junit' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..521fa874 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,296 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.2) + activesupport (4.2.11.1) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.1) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + babosa (1.0.3) + claide (1.0.3) + claide-plugins (0.9.2) + cork + nap + open4 (~> 1.3) + clamp (1.3.1) + cocoapods (1.8.4) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.8.4) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 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.6.6) + nap (~> 1.0) + ruby-macho (~> 1.4) + xcodeproj (>= 1.11.1, < 2.0) + cocoapods-core (1.8.4) + activesupport (>= 4.0.2, < 6) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.4) + cocoapods-downloader (1.3.0) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.1.0) + cocoapods-trunk (1.4.1) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + concurrent-ruby (1.1.5) + cork (0.3.0) + colored2 (~> 3.1) + danger (6.2.0) + claide (~> 1.0) + claide-plugins (>= 0.9.2) + colored2 (~> 3.1) + cork (~> 0.1) + faraday (~> 0.9) + faraday-http-cache (~> 2.0) + git (~> 1.5) + kramdown (~> 2.0) + kramdown-parser-gfm (~> 1.0) + no_proxy_fix + octokit (~> 4.7) + terminal-table (~> 1) + danger-junit (1.0.0) + danger (> 2.0) + ox (~> 2.0) + danger-plugin-api (1.0.0) + danger (> 2.0) + danger-swiftlint (0.24.0) + danger + rake (> 10) + thor (~> 0.19) + danger-xcode_summary (0.5.1) + danger-plugin-api (~> 1.0) + danger-xcov (0.4.1) + danger (>= 2.1) + xcov (>= 1.1.2) + declarative (0.0.10) + declarative-option (0.1.0) + digest-crc (0.4.1) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.5) + emoji_regex (1.0.1) + escape (0.0.4) + excon (0.72.0) + faraday (0.17.3) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) + faraday-http-cache (2.0.0) + faraday (~> 0.8) + faraday_middleware (0.13.1) + faraday (>= 0.7.4, < 1.0) + fastimage (2.1.7) + fastlane (2.141.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + babosa (>= 1.0.2, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 2.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 0.17) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 0.13.1) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.29.2, < 0.37.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + jwt (~> 2.1.0) + mini_magick (>= 4.9.4, < 5.0.0) + multi_xml (~> 0.5) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + public_suffix (~> 2.0.0) + rubyzip (>= 1.3.0, < 2.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + 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) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + git (1.6.0) + rchardet (~> 1.8) + google-api-client (0.36.4) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 0.9) + httpclient (>= 2.8.1, < 3.0) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.12) + google-cloud-core (1.5.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.3.0) + faraday (~> 0.11) + google-cloud-errors (1.0.0) + google-cloud-storage (1.25.1) + addressable (~> 2.5) + digest-crc (~> 0.4) + google-api-client (~> 0.33) + google-cloud-core (~> 1.2) + googleauth (~> 0.9) + mini_mime (~> 1.0) + googleauth (0.10.0) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.12) + highline (1.7.10) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + json (2.3.0) + jwt (2.1.0) + kramdown (2.1.0) + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + memoist (0.16.2) + mini_magick (4.10.1) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + minitest (5.14.0) + molinillo (0.6.6) + multi_json (1.14.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nanaimo (0.2.6) + nap (1.1.0) + naturally (2.2.0) + netrc (0.11.0) + no_proxy_fix (0.1.2) + nokogiri (1.10.7) + mini_portile2 (~> 2.4.0) + octokit (4.15.0) + faraday (>= 0.9) + sawyer (~> 0.8.0, >= 0.5.3) + open4 (1.3.4) + os (1.0.1) + ox (2.13.1) + plist (3.5.0) + public_suffix (2.0.5) + rake (13.0.1) + rchardet (1.8.0) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rouge (2.0.7) + ruby-macho (1.4.0) + rubyzip (1.3.0) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) + security (0.1.3) + signet (0.12.0) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.7) + CFPropertyList + naturally + slack-notifier (2.3.2) + slather (2.4.7) + CFPropertyList (>= 2.2, < 4) + activesupport (>= 4.0.2, < 5) + clamp (~> 1.3) + nokogiri (~> 1.8) + xcodeproj (~> 1.7) + terminal-notifier (2.0.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thor (0.20.3) + thread_safe (0.3.6) + tty-cursor (0.7.1) + tty-screen (0.7.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + tzinfo (1.2.6) + thread_safe (~> 0.1) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.6) + unicode-display_width (1.6.1) + word_wrap (1.0.0) + xcodeproj (1.14.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + xcov (1.7.2) + fastlane (>= 2.141.0, < 3.0.0) + multipart-post + slack-notifier + terminal-table + xcodeproj + xcresult (~> 0.2.0) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.0) + xcpretty (~> 0.2, >= 0.0.7) + xcresult (0.2.0) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods + danger + danger-junit + danger-swiftlint + danger-xcode_summary + danger-xcov + fastlane + slather + xcov + +BUNDLED WITH + 2.0.2 diff --git a/KarhooSDK.podspec b/KarhooSDK.podspec new file mode 100644 index 00000000..5907ae5f --- /dev/null +++ b/KarhooSDK.podspec @@ -0,0 +1,18 @@ +Pod::Spec.new do |s| + + s.name = "KarhooSDK" + s.version = "1.0.1" + s.summary = "Karhoo Network SDK" + s.homepage = "https://docs.stg.karhoo.net/v1/mobilesdk/network" + s.license = 'MIT' + s.author = { "Karhoo" => "ios@karhoo.com" } + + s.source = { :git => "git@github.com:karhoo/Karhoo-iOS-SDK.git", :tag => s.version } + s.source_files = 'KarhooSDK/**/*.swift' + s.platform = :ios, '10.0' + s.requires_arc = true + + s.dependency 'ReachabilitySwift', '5.0.0' + s.dependency 'KeychainSwift', '12.0.0' + +end diff --git a/KarhooSDK.xcodeproj/project.pbxproj b/KarhooSDK.xcodeproj/project.pbxproj new file mode 100644 index 00000000..0eba6130 --- /dev/null +++ b/KarhooSDK.xcodeproj/project.pbxproj @@ -0,0 +1,3865 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0900D34920D2B17B0082D1E8 /* RefreshTokenInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900D34820D2B17B0082D1E8 /* RefreshTokenInteractor.swift */; }; + 0900D34B20D2B1920082D1E8 /* RefreshTokenError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0900D34A20D2B1920082D1E8 /* RefreshTokenError.swift */; }; + 090670E820A4787600CAFA86 /* KarhooPaymentSDKTokenInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A3313412237721C4C7B5 /* KarhooPaymentSDKTokenInteractorSpec.swift */; }; + 0907B98A20D7B794008915B1 /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0907B98920D7B794008915B1 /* Reachability.framework */; }; + 0907B98C20D7B901008915B1 /* CancelTripInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0907B98B20D7B901008915B1 /* CancelTripInteractor.swift */; }; + 0907B98F20D7B964008915B1 /* KarhooCancelTripInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0907B98E20D7B964008915B1 /* KarhooCancelTripInteractor.swift */; }; + 0907B99220D7B98A008915B1 /* KarhooCancelTripInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0907B99120D7B98A008915B1 /* KarhooCancelTripInteractorSpec.swift */; }; + 090881DF20E65F7D00EE2C67 /* KarhooPlaceSearchInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090881DE20E65F7D00EE2C67 /* KarhooPlaceSearchInteractorSpec.swift */; }; + 090881E120E65FAD00EE2C67 /* MockPlaceSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090881E020E65FAD00EE2C67 /* MockPlaceSearchInteractor.swift */; }; + 090881E320E662D200EE2C67 /* KarhooLocationInfoInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090881E220E662D200EE2C67 /* KarhooLocationInfoInteractor.swift */; }; + 090881E520E663E600EE2C67 /* KarhooLocationInfoInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090881E420E663E500EE2C67 /* KarhooLocationInfoInteractorSpec.swift */; }; + 090E8226235DAD5900B447A2 /* KarhooEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E8225235DAD5900B447A2 /* KarhooEnvironment.swift */; }; + 0916BB67235DEA580064A5D6 /* MockSDKConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09430E4C22410037009846A6 /* MockSDKConfig.swift */; }; + 091A2D0E207D23F600F06E28 /* LocationInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091A2D0D207D23F600F06E28 /* LocationInfo.swift */; }; + 091BE6EA2085192800BA34D0 /* PlaceSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091BE6E92085192800BA34D0 /* PlaceSearch.swift */; }; + 091BE6EC2086562900BA34D0 /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091BE6EB2086562900BA34D0 /* Position.swift */; }; + 091CAE3E20D7A81F005F1DB6 /* MockURLSessionSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 091CAE3D20D7A81F005F1DB6 /* MockURLSessionSender.swift */; }; + 092F27C722B1353D00AF8E0E /* PickUpType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 092F27C622B1353D00AF8E0E /* PickUpType.swift */; }; + 09430E4B2240054A009846A6 /* IntegrationTestSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09430E4A2240054A009846A6 /* IntegrationTestSetup.swift */; }; + 09430E4D22410037009846A6 /* MockSDKConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09430E4C22410037009846A6 /* MockSDKConfig.swift */; }; + 09430E4F224109D6009846A6 /* UnitTestSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09430E4E224109D6009846A6 /* UnitTestSetup.swift */; }; + 0947FD3B2121D51900E8BE4A /* KarhooErrorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0947FD3A2121D51900E8BE4A /* KarhooErrorType.swift */; }; + 094B449E2285AC66002D2506 /* PaymentSDKTokenPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094B449D2285AC65002D2506 /* PaymentSDKTokenPayload.swift */; }; + 094B44B42285B8DE002D2506 /* AddPaymentDetails.json in Resources */ = {isa = PBXBuildFile; fileRef = 094B44B32285B8DE002D2506 /* AddPaymentDetails.json */; }; + 094C1F7F20B2C956001FFE7F /* MockNetworkDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 094C1F7E20B2C956001FFE7F /* MockNetworkDateFormatter.swift */; }; + 095A03B923339721007D805E /* ConfigService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03B823339721007D805E /* ConfigService.swift */; }; + 095A03BB2333ADC2007D805E /* KarhooConfigService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03BA2333ADC2007D805E /* KarhooConfigService.swift */; }; + 095A03BE2333AF4A007D805E /* UIConfigInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03BD2333AF4A007D805E /* UIConfigInteractor.swift */; }; + 095A03C02333AF64007D805E /* KarhooUIConfigInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03BF2333AF64007D805E /* KarhooUIConfigInteractor.swift */; }; + 095A03C22333AF98007D805E /* UIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03C12333AF98007D805E /* UIConfig.swift */; }; + 095A03C42333AFED007D805E /* UIConfigRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03C32333AFED007D805E /* UIConfigRequest.swift */; }; + 095A03C72333B115007D805E /* UIConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03C62333B115007D805E /* UIConfigProvider.swift */; }; + 095A03CB2333B3E6007D805E /* KarhooConfigServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03CA2333B3E6007D805E /* KarhooConfigServiceSpec.swift */; }; + 095A03CD2333B3F8007D805E /* KarhooUIConfigInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03CC2333B3F8007D805E /* KarhooUIConfigInteractorSpec.swift */; }; + 095A03D02333BABD007D805E /* KarhooUIConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03CF2333BABD007D805E /* KarhooUIConfigProvider.swift */; }; + 095A03D22333C119007D805E /* KarhooUIConfigProviderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03D12333C119007D805E /* KarhooUIConfigProviderSpec.swift */; }; + 095A03D52333C235007D805E /* MockUIConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03D42333C235007D805E /* MockUIConfigProvider.swift */; }; + 095A03DE2333D926007D805E /* UIConfigMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03DD2333D926007D805E /* UIConfigMethodSpec.swift */; }; + 095A03E02333DFCB007D805E /* MockUIConfigInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095A03DF2333DFCB007D805E /* MockUIConfigInteractor.swift */; }; + 095CEC0620F7573700C8AC4B /* TripCancellation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095CEC0520F7573700C8AC4B /* TripCancellation.swift */; }; + 095E406D20B32DAE008EAF0F /* NetworkDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095E406C20B32DAE008EAF0F /* NetworkDateFormatter.swift */; }; + 096A2C2D208F676000C7B7D0 /* PositionExtSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096A2C2C208F676000C7B7D0 /* PositionExtSpec.swift */; }; + 096B31BC20B702AE002B41E9 /* MockBookingInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096B31BB20B702AE002B41E9 /* MockBookingInteractor.swift */; }; + 096B31C020B702E8002B41E9 /* MockTripSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 096B31BF20B702E8002B41E9 /* MockTripSearchInteractor.swift */; }; + 0970BA6F21136F870015A170 /* Executable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA6A21136F870015A170 /* Executable.swift */; }; + 0970BA7021136F870015A170 /* KarhooPollableExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA6B21136F870015A170 /* KarhooPollableExecutor.swift */; }; + 0970BA7121136F870015A170 /* KarhooPollCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA6C21136F870015A170 /* KarhooPollCall.swift */; }; + 0970BA7221136F870015A170 /* KarhooCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA6D21136F870015A170 /* KarhooCall.swift */; }; + 0970BA7321136F870015A170 /* KarhooObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA6E21136F870015A170 /* KarhooObservable.swift */; }; + 0970BA78211371600015A170 /* KarhooDriverTrackingInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA77211371600015A170 /* KarhooDriverTrackingInteractor.swift */; }; + 0970BA7B21145E550015A170 /* KarhooCallSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA7A21145E550015A170 /* KarhooCallSpec.swift */; }; + 0970BA7E211464C40015A170 /* MockExecutable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA7D211464C40015A170 /* MockExecutable.swift */; }; + 0970BA802114671A0015A170 /* KarhooObservableSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA7F2114671A0015A170 /* KarhooObservableSpec.swift */; }; + 0970BA822114685B0015A170 /* MockKarhooCodableModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA812114685B0015A170 /* MockKarhooCodableModel.swift */; }; + 0970BA8821146DF80015A170 /* MockPollExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA8721146DF70015A170 /* MockPollExecutor.swift */; }; + 0970BA8A2114970A0015A170 /* KarhooPollCallSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA892114970A0015A170 /* KarhooPollCallSpec.swift */; }; + 0970BA8C21149FC60015A170 /* KarhooPollableExecutorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0970BA8B21149FC60015A170 /* KarhooPollableExecutorSpec.swift */; }; + 097BF2B72358710400BBE418 /* KarhooSDKConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097BF2B62358710400BBE418 /* KarhooSDKConfiguration.swift */; }; + 097D472321511208008D8D94 /* TripStatusMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D472221511208008D8D94 /* TripStatusMethodSpec.swift */; }; + 097D4725215128E0008D8D94 /* TripStatus.json in Resources */ = {isa = PBXBuildFile; fileRef = 097D4724215128E0008D8D94 /* TripStatus.json */; }; + 097D472721512D5E008D8D94 /* TripSearchMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 097D472621512D5E008D8D94 /* TripSearchMethodSpec.swift */; }; + 09811A1820D7BB8C001DB06F /* CancelTripRequestPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09811A1720D7BB8C001DB06F /* CancelTripRequestPayload.swift */; }; + 09811A1A20D7BD77001DB06F /* CancelReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09811A1920D7BD77001DB06F /* CancelReason.swift */; }; + 09811A1E20D7C12D001DB06F /* MockCancelTripInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09811A1D20D7C12D001DB06F /* MockCancelTripInteractor.swift */; }; + 0981F6C720CEC27A00BF7FB5 /* MockRequestSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0981F6C620CEC27A00BF7FB5 /* MockRequestSender.swift */; }; + 0981F6C920CEC3DE00BF7FB5 /* KarhooLoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0981F6C820CEC3DE00BF7FB5 /* KarhooLoginInteractor.swift */; }; + 0981F6CB20CEE1AD00BF7FB5 /* KarhooLoginInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0981F6CA20CEE1AD00BF7FB5 /* KarhooLoginInteractorSpec.swift */; }; + 0981F6CD20CEEB1000BF7FB5 /* AuthTokenMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0981F6CC20CEEB1000BF7FB5 /* AuthTokenMock.swift */; }; + 0981F6CF20CFDA9B00BF7FB5 /* KarhooRequestSenderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0981F6CE20CFDA9B00BF7FB5 /* KarhooRequestSenderSpec.swift */; }; + 0981F6D520CFE1B300BF7FB5 /* MockLoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0981F6D420CFE1B300BF7FB5 /* MockLoginInteractor.swift */; }; + 09820E6A20D7E5E800782530 /* KarhooRegisterInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09820E6920D7E5E800782530 /* KarhooRegisterInteractor.swift */; }; + 09820E7020D7F07800782530 /* KarhooRegisterInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09820E6F20D7F07800782530 /* KarhooRegisterInteractorSpec.swift */; }; + 09820E7220D7F60100782530 /* UserRegistrationMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09820E7120D7F60100782530 /* UserRegistrationMock.swift */; }; + 09820E7420D809E000782530 /* MockRegisterInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09820E7320D809E000782530 /* MockRegisterInteractor.swift */; }; + 09820E7820D8172D00782530 /* MockLogoutInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09820E7720D8172D00782530 /* MockLogoutInteractor.swift */; }; + 098C91F92147CDC500F02DE3 /* GeneralErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098C91F82147CDC500F02DE3 /* GeneralErrorSpec.swift */; }; + 098C91FB2147FF9900F02DE3 /* RawKarhooErrorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098C91FA2147FF9900F02DE3 /* RawKarhooErrorFactory.swift */; }; + 098D524F23FB14FA00059086 /* AuthLoginMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098D524E23FB14FA00059086 /* AuthLoginMethodSpec.swift */; }; + 098D525223FB157B00059086 /* KarhooAuthRevokeInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 098D525123FB157B00059086 /* KarhooAuthRevokeInteractorSpec.swift */; }; + 099202A720DBB1D20009C845 /* Organisation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099202A620DBB1D20009C845 /* Organisation.swift */; }; + 09928D2520EFAABA00B9F462 /* CategoryQuoteMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D1820EFAABA00B9F462 /* CategoryQuoteMapper.swift */; }; + 09928D2920EFAABA00B9F462 /* QuoteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D1D20EFAABA00B9F462 /* QuoteService.swift */; }; + 09928D2A20EFAABA00B9F462 /* KarhooQuoteService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D1E20EFAABA00B9F462 /* KarhooQuoteService.swift */; }; + 09928D2B20EFAABA00B9F462 /* KarhooQuoteInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D2020EFAABA00B9F462 /* KarhooQuoteInteractor.swift */; }; + 09928D2C20EFAABA00B9F462 /* QuoteInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D2120EFAABA00B9F462 /* QuoteInteractor.swift */; }; + 09928D3720EFAAEE00B9F462 /* KarhooQuoteServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D3220EFAAEE00B9F462 /* KarhooQuoteServiceSpec.swift */; }; + 09928D3820EFAAEE00B9F462 /* KarhooQuoteInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D3420EFAAEE00B9F462 /* KarhooQuoteInteractorSpec.swift */; }; + 09928D4520EFAB1300B9F462 /* MockQuoteInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D4120EFAB1300B9F462 /* MockQuoteInteractor.swift */; }; + 09928D4720EFAB3000B9F462 /* QuoteSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D4620EFAB3000B9F462 /* QuoteSearch.swift */; }; + 09928D4920EFAB9300B9F462 /* Quotes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09928D4820EFAB9300B9F462 /* Quotes.swift */; }; + 099613712408359A00C8C88E /* RevokeMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099613702408359A00C8C88E /* RevokeMethodSpec.swift */; }; + 0996137324083B6F00C8C88E /* AccessTokenProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0996137224083B6E00C8C88E /* AccessTokenProvider.swift */; }; + 099AF2F920F8BA0000A2AA57 /* TripState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099AF2F820F8BA0000A2AA57 /* TripState.swift */; }; + 099AF2FB20F8BDF000A2AA57 /* TripInfoMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099AF2FA20F8BDF000A2AA57 /* TripInfoMock.swift */; }; + 099DFC6C223BEDF800401542 /* AddPaymentDetailsPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099DFC6B223BEDF800401542 /* AddPaymentDetailsPayload.swift */; }; + 09A18BD921185080009F927B /* KarhooDriverTrackingInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A18BD821185080009F927B /* KarhooDriverTrackingInteractorSpec.swift */; }; + 09A56DFC20922D5C00891F02 /* MeetingPointType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A56DFB20922D5C00891F02 /* MeetingPointType.swift */; }; + 09A56E002097162C00891F02 /* LocationDetailsExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A56DFF2097162C00891F02 /* LocationDetailsExt.swift */; }; + 09A9B28E2405901900823FB0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A9B28D2405901900823FB0 /* AppDelegate.swift */; }; + 09A9B2902405901900823FB0 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A9B28F2405901900823FB0 /* SceneDelegate.swift */; }; + 09A9B2922405901900823FB0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A9B2912405901900823FB0 /* ViewController.swift */; }; + 09A9B2972405901B00823FB0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09A9B2962405901B00823FB0 /* Assets.xcassets */; }; + 09A9B29A2405901B00823FB0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 09A9B2982405901B00823FB0 /* LaunchScreen.storyboard */; }; + 09A9B2A42405916400823FB0 /* KarhooSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4387A6411E3BAF6D0024FD55 /* KarhooSDK.framework */; }; + 09ACCE50207BBB5E0057B0CC /* QuoteListIdRequestPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09ACCE4F207BBB5E0057B0CC /* QuoteListIdRequestPayload.swift */; }; + 09AF9AA920D174150046D725 /* RefreshToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AF9AA820D174150046D725 /* RefreshToken.swift */; }; + 09AF9AAB20D1751D0046D725 /* RefreshTokenRequestPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AF9AAA20D1751D0046D725 /* RefreshTokenRequestPayload.swift */; }; + 09AF9AAD20D17B7D0046D725 /* RefreshTokenMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AF9AAC20D17B7D0046D725 /* RefreshTokenMock.swift */; }; + 09B204B121412C3200063632 /* KarhooSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4387A6411E3BAF6D0024FD55 /* KarhooSDK.framework */; }; + 09B204BC2141366C00063632 /* InvalidData.json in Resources */ = {isa = PBXBuildFile; fileRef = 09B204BB2141366C00063632 /* InvalidData.json */; }; + 09BDE053212F128800798330 /* MockLocationInfoInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BDE052212F128700798330 /* MockLocationInfoInteractor.swift */; }; + 09BDE055212F147600798330 /* MockReverseGeocodeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BDE054212F147600798330 /* MockReverseGeocodeInteractor.swift */; }; + 09CA785820EE5ABD00D3BB6F /* Fare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CA785720EE5ABC00D3BB6F /* Fare.swift */; }; + 09CE9A5020FCE87800874092 /* TripBooking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE9A4F20FCE87800874092 /* TripBooking.swift */; }; + 09CE9A5420FE1F2E00874092 /* TripSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE9A5320FE1F2E00874092 /* TripSearch.swift */; }; + 09CE9A5620FE530700874092 /* TripType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE9A5520FE530700874092 /* TripType.swift */; }; + 09D0757520D826A700F75E32 /* KarhooLogoutInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D0757420D826A700F75E32 /* KarhooLogoutInteractor.swift */; }; + 09D293C222C530170051C455 /* QuoteSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09D293C122C530170051C455 /* QuoteSource.swift */; }; + 09EE81E72152585D004B2B09 /* TripSearch.json in Resources */ = {isa = PBXBuildFile; fileRef = 09EE81E62152585D004B2B09 /* TripSearch.json */; }; + 09EE81EA2152617B004B2B09 /* QuoteSearchMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EE81E92152617B004B2B09 /* QuoteSearchMethodSpec.swift */; }; + 09EE81EB21526350004B2B09 /* LocationInfoMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2856F120E3B1DC009B9464 /* LocationInfoMock.swift */; }; + 09EE81F021528791004B2B09 /* QuoteListId.json in Resources */ = {isa = PBXBuildFile; fileRef = 09EE81ED21528791004B2B09 /* QuoteListId.json */; }; + 09EE81F121528791004B2B09 /* Availability.json in Resources */ = {isa = PBXBuildFile; fileRef = 09EE81EE21528791004B2B09 /* Availability.json */; }; + 09EE81F221528791004B2B09 /* Quotes.json in Resources */ = {isa = PBXBuildFile; fileRef = 09EE81EF21528791004B2B09 /* Quotes.json */; }; + 09F79E34223952F900D5B0B8 /* Nonce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E33223952F900D5B0B8 /* Nonce.swift */; }; + 09F79E362239546E00D5B0B8 /* GetNonceInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E352239546E00D5B0B8 /* GetNonceInteractor.swift */; }; + 09F79E382239547A00D5B0B8 /* KarhooGetNonceInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E372239547A00D5B0B8 /* KarhooGetNonceInteractor.swift */; }; + 09F79E3A223955FF00D5B0B8 /* NonceRequestPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E39223955FF00D5B0B8 /* NonceRequestPayload.swift */; }; + 09F79E3C2239564600D5B0B8 /* Payer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E3B2239564600D5B0B8 /* Payer.swift */; }; + 09F79E3F2239723200D5B0B8 /* KarhooGetNonceInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E3E2239723200D5B0B8 /* KarhooGetNonceInteractorSpec.swift */; }; + 09F79E41223A63DA00D5B0B8 /* NonceRequestPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E40223A63DA00D5B0B8 /* NonceRequestPayload.swift */; }; + 09F79E44223A99E700D5B0B8 /* GetNonceMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E43223A99E700D5B0B8 /* GetNonceMethodSpec.swift */; }; + 09F79E47223AAA6600D5B0B8 /* MockGetNonceInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E46223AAA6600D5B0B8 /* MockGetNonceInteractor.swift */; }; + 09F79E48223AB40000D5B0B8 /* NonceRequestPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79E40223A63DA00D5B0B8 /* NonceRequestPayload.swift */; }; + 09F79E4B223ABD5B00D5B0B8 /* GetNonce.json in Resources */ = {isa = PBXBuildFile; fileRef = 09F79E4A223ABD5A00D5B0B8 /* GetNonce.json */; }; + 09F8ABE9214A9B1500E293B7 /* DriverTracking.json in Resources */ = {isa = PBXBuildFile; fileRef = 09F8ABE8214A9B1500E293B7 /* DriverTracking.json */; }; + 09F8ABEC214A9B5700E293B7 /* DriverTrackingMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F8ABEB214A9B5700E293B7 /* DriverTrackingMethodSpec.swift */; }; + 09F8ABEE214AABBD00E293B7 /* NetworkStubFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F8ABED214AABBD00E293B7 /* NetworkStubFactory.swift */; }; + 09F8ABF6214BF4F200E293B7 /* PlaceSearchMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F8ABF5214BF4F200E293B7 /* PlaceSearchMethodSpec.swift */; }; + 09F8ABF8214BF69400E293B7 /* Places.json in Resources */ = {isa = PBXBuildFile; fileRef = 09F8ABF7214BF69400E293B7 /* Places.json */; }; + 09F8ABFB214FB4C900E293B7 /* PaymentSDKTokenMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F8ABFA214FB4C900E293B7 /* PaymentSDKTokenMethod.swift */; }; + 09F8ABFD214FB61100E293B7 /* PaymentSDKToken.json in Resources */ = {isa = PBXBuildFile; fileRef = 09F8ABFC214FB61100E293B7 /* PaymentSDKToken.json */; }; + 09F8AC00214FDF4200E293B7 /* LoginMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F8ABFF214FDF4200E293B7 /* LoginMethodSpec.swift */; }; + 09F8AC05214FE6DF00E293B7 /* UnauthorisedUserInfo.json in Resources */ = {isa = PBXBuildFile; fileRef = 09F8AC02214FE6DF00E293B7 /* UnauthorisedUserInfo.json */; }; + 09F8AC06214FE6DF00E293B7 /* AuthToken.json in Resources */ = {isa = PBXBuildFile; fileRef = 09F8AC03214FE6DF00E293B7 /* AuthToken.json */; }; + 09F8AC07214FE6DF00E293B7 /* AuthorisedUserInfo.json in Resources */ = {isa = PBXBuildFile; fileRef = 09F8AC04214FE6DF00E293B7 /* AuthorisedUserInfo.json */; }; + 09F90F912150FA2C003BC867 /* PasswordResetMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F90F902150FA2C003BC867 /* PasswordResetMethodSpec.swift */; }; + 09F90F942151008C003BC867 /* CancelTripMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F90F932151008C003BC867 /* CancelTripMethodSpec.swift */; }; + 233DB46723433EC200BCBC3F /* MockUpdateUserDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233DB46623433EC200BCBC3F /* MockUpdateUserDetailsInteractor.swift */; }; + 233DB46923433F0400BCBC3F /* UserUpdateMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 233DB46823433F0400BCBC3F /* UserUpdateMock.swift */; }; + 2359B45B234251FB008433A1 /* KarhooUpdateUserDetailsInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2359B45A234251FA008433A1 /* KarhooUpdateUserDetailsInteractorSpec.swift */; }; + 2359B45F2342522E008433A1 /* KarhooUpdateUserDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2359B45D2342522E008433A1 /* KarhooUpdateUserDetailsInteractor.swift */; }; + 2359B4602342522E008433A1 /* UpdateUserDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2359B45E2342522E008433A1 /* UpdateUserDetailsInteractor.swift */; }; + 2359B46223425299008433A1 /* UserUpdatePayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2359B46123425299008433A1 /* UserUpdatePayload.swift */; }; + 237E0C572347823A00798AA0 /* FareService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237E0C562347823A00798AA0 /* FareService.swift */; }; + 237E0C59234782B400798AA0 /* KarhooFareService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237E0C58234782B400798AA0 /* KarhooFareService.swift */; }; + 237E0C5C2347838600798AA0 /* FareInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237E0C5B2347838600798AA0 /* FareInteractor.swift */; }; + 237E0C5E234783BE00798AA0 /* KarhooFareInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237E0C5D234783BE00798AA0 /* KarhooFareInteractor.swift */; }; + 237E0C612347852500798AA0 /* Fare.json in Resources */ = {isa = PBXBuildFile; fileRef = 237E0C602347852500798AA0 /* Fare.json */; }; + 237E0C64234788F000798AA0 /* KarhooFareServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237E0C63234788F000798AA0 /* KarhooFareServiceSpec.swift */; }; + 237E0C672347897B00798AA0 /* MockFareInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237E0C662347897B00798AA0 /* MockFareInteractor.swift */; }; + 237E0C692347931D00798AA0 /* KarhooFareInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237E0C682347931D00798AA0 /* KarhooFareInteractorSpec.swift */; }; + 274211D129AC87080586E523 /* DriverTrackingInfoMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27421D16D44DEC18A835BDB0 /* DriverTrackingInfoMock.swift */; }; + 2742125CDD868E90FDAED0B4 /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27421AB287B1A33F00AC4119 /* UserInfo.swift */; }; + 274212DB927ADC241604D704 /* UserDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27421A91EE9BE3C99E54F313 /* UserDataStore.swift */; }; + 274215324F8425FAEC59FF36 /* CredentialParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2742136485E56D7E2AEF11AB /* CredentialParser.swift */; }; + 274217A11D83A12132022A6A /* HeaderProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274213D38799FE5D607435B2 /* HeaderProvider.swift */; }; + 274217A4E1433D9270511C44 /* QuoteListMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274215B1B4ADECE9D9CE2EA2 /* QuoteListMock.swift */; }; + 274217F5C000E1C615D797C9 /* Categories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27421E2976A85D5A0FB95991 /* Categories.swift */; }; + 27421847E08F35A3DC467C6D /* HeaderConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27421CBA8C72F37A4F143580 /* HeaderConstants.swift */; }; + 2742197A13ACCCDBAE32D13A /* HeaderProviderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2742119D00DE30C9E3590055 /* HeaderProviderSpec.swift */; }; + 274219FB67E45564E6C421C7 /* QuoteListId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274217092FA03DAFAD7776E1 /* QuoteListId.swift */; }; + 27421AAB7F1B71B7D55D5B6D /* QuoteMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27421D9042A4F39DF7D69941 /* QuoteMock.swift */; }; + 27421B8B6B1B4722C040B399 /* APIEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27421EDE7F5329B3837E9DFC /* APIEndpoint.swift */; }; + 27421C3B80CE03E230AB3863 /* UserRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27421639BE5688EF2127E119 /* UserRegistration.swift */; }; + 27421DAC3421BFA9669A5DBB /* KarhooAvailabilityInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27421CA2A060894A7D859830 /* KarhooAvailabilityInteractor.swift */; }; + 27421E022DB3FB60B83DEFAE /* AvailabilityInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274216467197574CB8746A19 /* AvailabilityInteractor.swift */; }; + 27421FA3FF444C478139288D /* KarhooHeaderProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2742100A1C88263575619D5D /* KarhooHeaderProvider.swift */; }; + 31005DA71FCEE699006D2A7D /* RequestTesting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31005DA61FCEE699006D2A7D /* RequestTesting.swift */; }; + 310D44AE1FA1F8890038BFDA /* TimestampFormatterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D44AD1FA1F8890038BFDA /* TimestampFormatterSpec.swift */; }; + 312909E21FA0E0C900F5147D /* TimestampFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312909E11FA0E0C900F5147D /* TimestampFormatter.swift */; }; + 312B1AAC1FA097F900BB27CF /* BatteryMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312B1AAB1FA097F900BB27CF /* BatteryMonitor.swift */; }; + 312B1AAE1FA0982800BB27CF /* NetworkConnectionTypeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312B1AAD1FA0982800BB27CF /* NetworkConnectionTypeProvider.swift */; }; + 312CDFB61F6A984A006F48DD /* AppStateNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 312CDFB51F6A984A006F48DD /* AppStateNotifier.swift */; }; + 313DC2D120221A0800D9DD1B /* Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313DC2D020221A0800D9DD1B /* Driver.swift */; }; + 313DC2DD20221D0100D9DD1B /* Vehicle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313DC2DC20221D0100D9DD1B /* Vehicle.swift */; }; + 313DC2E420221E7B00D9DD1B /* Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313DC2E320221E7B00D9DD1B /* Place.swift */; }; + 313DC2F020221FCF00D9DD1B /* QuoteType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313DC2EF20221FCE00D9DD1B /* QuoteType.swift */; }; + 313DC2F220221FDB00D9DD1B /* Quote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313DC2F120221FDB00D9DD1B /* Quote.swift */; }; + 3150CE2C1F9F7B6000DE6D62 /* UserLocationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3150CE2B1F9F7B6000DE6D62 /* UserLocationProvider.swift */; }; + 3150CE2F1F9F7BBA00DE6D62 /* UserLocationProviderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3150CE2E1F9F7BBA00DE6D62 /* UserLocationProviderSpec.swift */; }; + 3150CE311F9F7D8E00DE6D62 /* MockCLLocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3150CE301F9F7D8E00DE6D62 /* MockCLLocationManager.swift */; }; + 317210C21FFD2476004CE129 /* MockAppStateNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317210C11FFD2476004CE129 /* MockAppStateNotifier.swift */; }; + 317210CF1FFE7330004CE129 /* CredentialsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317210CE1FFE7330004CE129 /* CredentialsSpec.swift */; }; + 317210D11FFE86B4004CE129 /* KarhooRefreshTokenInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317210D01FFE86B4004CE129 /* KarhooRefreshTokenInteractorSpec.swift */; }; + 317610EC1FD84B1E00D2DB75 /* KarhooNetworkDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317610EB1FD84B1E00D2DB75 /* KarhooNetworkDateFormatter.swift */; }; + 317610EE1FD84CD200D2DB75 /* KarhooNetworkDateFormatterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317610ED1FD84CD200D2DB75 /* KarhooNetworkDateFormatterSpec.swift */; }; + 317610F21FD86A9600D2DB75 /* KarhooAvailabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317610F11FD86A9600D2DB75 /* KarhooAvailabilityService.swift */; }; + 317610F41FD9855D00D2DB75 /* KarhooAvailabilityServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317610F31FD9855D00D2DB75 /* KarhooAvailabilityServiceSpec.swift */; }; + 31904CD41FFFC04400BA7402 /* KarhooRefreshTokenInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31904CD31FFFC04400BA7402 /* KarhooRefreshTokenInteractor.swift */; }; + 31904CD61FFFE26F00BA7402 /* TokenRefreshingHttpClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31904CD51FFFE26F00BA7402 /* TokenRefreshingHttpClient.swift */; }; + 31904CD82003800000BA7402 /* TokenRefreshingHttpClientSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31904CD72003800000BA7402 /* TokenRefreshingHttpClientSpec.swift */; }; + 31904CDA200380A400BA7402 /* MockAccessTokenProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31904CD9200380A400BA7402 /* MockAccessTokenProvider.swift */; }; + 31904CDC2003EFFE00BA7402 /* MockNetworkRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31904CDB2003EFFE00BA7402 /* MockNetworkRequest.swift */; }; + 31A1AD631F39E1E200BFE24B /* MockHttpClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A1AD621F39E1E200BFE24B /* MockHttpClient.swift */; }; + 31ACC66E1FF566C400E8B24F /* KarhooTimingScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ACC66D1FF566C400E8B24F /* KarhooTimingScheduler.swift */; }; + 31EC3F611F9E4C24008CD637 /* DeviceIdentifierProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EC3F601F9E4C24008CD637 /* DeviceIdentifierProvider.swift */; }; + 31EC3F651F9E50AD008CD637 /* AnalyticsPayloadBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31EC3F641F9E50AD008CD637 /* AnalyticsPayloadBuilder.swift */; }; + 31F9168A2023559700104777 /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31F916892023559700104777 /* AnalyticsService.swift */; }; + 31FC37FF1F459A780012A57E /* DefaultAuthTokenSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31FC37FE1F459A780012A57E /* DefaultAuthTokenSpec.swift */; }; + 400555712350AEDD008AD78F /* TripFare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400555702350AEDC008AD78F /* TripFare.swift */; }; + 400BB94323F3027A00AEAAFD /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400BB94223F3027A00AEAAFD /* AuthService.swift */; }; + 400BB94623F3066500AEAAFD /* KarhooAuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400BB94523F3066500AEAAFD /* KarhooAuthService.swift */; }; + 400BB94823F308BE00AEAAFD /* AuthLoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400BB94723F308BE00AEAAFD /* AuthLoginInteractor.swift */; }; + 400BB94B23F308F100AEAAFD /* KarhooAuthLoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400BB94A23F308F100AEAAFD /* KarhooAuthLoginInteractor.swift */; }; + 4032BD1023FC47B600C2409B /* AuthUserInfo.json in Resources */ = {isa = PBXBuildFile; fileRef = 4032BD0D23FC47B600C2409B /* AuthUserInfo.json */; }; + 4032BD1223FC491500C2409B /* AuthExchangeToken.json in Resources */ = {isa = PBXBuildFile; fileRef = 4032BD1123FC491500C2409B /* AuthExchangeToken.json */; }; + 4032BD1423FC5B2300C2409B /* KarhooAuthLoginInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4032BD1323FC5B2300C2409B /* KarhooAuthLoginInteractorSpec.swift */; }; + 404484E223FB00A900CD0347 /* KarhooAuthRevokeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 404484E123FB00A900CD0347 /* KarhooAuthRevokeInteractor.swift */; }; + 4095037B234CCF5A00C6F7A5 /* FareMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4095037A234CCF5A00C6F7A5 /* FareMethodSpec.swift */; }; + 40F4058F2334C50100D384A6 /* LocationInfoSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40F4058E2334C50000D384A6 /* LocationInfoSearch.swift */; }; + 40FBF607240EAFD9007F6A54 /* AuthHeaderKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FBF606240EAFD9007F6A54 /* AuthHeaderKeys.swift */; }; + 40FBF609240FBB2E007F6A54 /* AuthRequestsTesterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40FBF608240FBB2E007F6A54 /* AuthRequestsTesterViewController.swift */; }; + 4231E9D1B9C7769699C2EECC /* Pods_KarhooSDKTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE6485A8A424D85E5FF8A2BB /* Pods_KarhooSDKTests.framework */; }; + 4310E4711E9928C800625A0E /* MockBroadcaster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E4701E9928C800625A0E /* MockBroadcaster.swift */; }; + 4310E4B81E99395D00625A0E /* AddressService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E47F1E99395D00625A0E /* AddressService.swift */; }; + 4310E4B91E99395D00625A0E /* AvailabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E4801E99395D00625A0E /* AvailabilityService.swift */; }; + 4310E4C21E99395D00625A0E /* Karhoo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E48A1E99395D00625A0E /* Karhoo.swift */; }; + 4310E4DB1E99395D00625A0E /* Broadcaster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E4B61E99395D00625A0E /* Broadcaster.swift */; }; + 4310E4DC1E99395D00625A0E /* WeakReferenceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E4B71E99395D00625A0E /* WeakReferenceWrapper.swift */; }; + 4310E5061E99397A00625A0E /* UserDataStoreSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E4E01E99397A00625A0E /* UserDataStoreSpec.swift */; }; + 4310E5071E99397A00625A0E /* KarhooSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E4E11E99397A00625A0E /* KarhooSpec.swift */; }; + 4310E51A1E99397A00625A0E /* BroadcasterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E5031E99397A00625A0E /* BroadcasterSpec.swift */; }; + 4310E51B1E99397A00625A0E /* WeakReferenceWrapperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310E5041E99397A00625A0E /* WeakReferenceWrapperSpec.swift */; }; + 431740D51ECB257A00CD217F /* KarhooUserServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431740CD1ECB257A00CD217F /* KarhooUserServiceSpec.swift */; }; + 4322B48C1E69A57B002108D5 /* MockAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4322B48B1E69A57B002108D5 /* MockAnalyticsService.swift */; }; + 43288A5F1EF3BEB10079506B /* DriverTrackingInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43288A5E1EF3BEB10079506B /* DriverTrackingInfo.swift */; }; + 432D47871ECC31940085DBBE /* KarhooLogoutInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432D47831ECC31940085DBBE /* KarhooLogoutInteractorSpec.swift */; }; + 432D478A1ECC80E10085DBBE /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 432D47891ECC80E10085DBBE /* Result.swift */; }; + 43466AE81EDECEED0087BC51 /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43466AE71EDECEED0087BC51 /* Credentials.swift */; }; + 434B58431E434E5F009E8FB8 /* MockUserDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434B58421E434E5F009E8FB8 /* MockUserDataStore.swift */; }; + 434D30A91EE150BA00CEE07B /* CredentiasParserSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 434D30A81EE150BA00CEE07B /* CredentiasParserSpec.swift */; }; + 43584E6E1EE54D6400B4DFAB /* TripInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43584E6D1EE54D6400B4DFAB /* TripInfo.swift */; }; + 43584E7A1EE56EDC00B4DFAB /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43584E791EE56EDC00B4DFAB /* Errors.swift */; }; + 43584E871EE590DE00B4DFAB /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43584E861EE590DE00B4DFAB /* Context.swift */; }; + 43584E891EE590FC00B4DFAB /* ContextSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43584E881EE590FC00B4DFAB /* ContextSpec.swift */; }; + 43584E8D1EE591C700B4DFAB /* ObjectTestFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43584E8C1EE591C700B4DFAB /* ObjectTestFactory.swift */; }; + 43584E8F1EE5960700B4DFAB /* TripService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43584E8E1EE5960700B4DFAB /* TripService.swift */; }; + 43584E931EE5993700B4DFAB /* KarhooTripServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43584E921EE5993700B4DFAB /* KarhooTripServiceSpec.swift */; }; + 43584E9F1EE6A3D400B4DFAB /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43584E9E1EE6A3D400B4DFAB /* MockUserDefaults.swift */; }; + 43588C661EBB675100D03765 /* TestUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43588C651EBB675100D03765 /* TestUtil.swift */; }; + 4371C5951EB0E7F70059429D /* KarhooAddressService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4371C57B1EB0E7F70059429D /* KarhooAddressService.swift */; }; + 4371C59B1EB0E7F70059429D /* AnalyticsConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4371C5831EB0E7F70059429D /* AnalyticsConstants.swift */; }; + 4371C59C1EB0E7F70059429D /* KarhooAnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4371C5841EB0E7F70059429D /* KarhooAnalyticsService.swift */; }; + 4371C59D1EB0E7F70059429D /* AnalyticsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4371C5861EB0E7F70059429D /* AnalyticsProvider.swift */; }; + 4371C59E1EB0E7F70059429D /* LogAnalyticsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4371C5871EB0E7F70059429D /* LogAnalyticsProvider.swift */; }; + 4371C5BE1EB0E8070059429D /* KarhooAddressServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4371C5A91EB0E8070059429D /* KarhooAddressServiceSpec.swift */; }; + 4371C5C11EB0E8070059429D /* AnalyticsServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4371C5AE1EB0E8070059429D /* AnalyticsServiceSpec.swift */; }; + 4371C5C21EB0E8070059429D /* LogAnalyticsProviderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4371C5B01EB0E8070059429D /* LogAnalyticsProviderSpec.swift */; }; + 43850F3F1ECB003E009CB8FD /* UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43850F3E1ECB003E009CB8FD /* UserService.swift */; }; + 43850F4C1ECB0056009CB8FD /* KarhooUserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43850F431ECB0056009CB8FD /* KarhooUserService.swift */; }; + 4387A6521E3BAF6D0024FD55 /* KarhooSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 4387A6441E3BAF6D0024FD55 /* KarhooSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 4387A67F1E3BB6070024FD55 /* KarhooSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4387A6411E3BAF6D0024FD55 /* KarhooSDK.framework */; }; + 43D251B61F45C76E005ECB85 /* ReachabilityWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D251B51F45C76E005ECB85 /* ReachabilityWrapper.swift */; }; + 43D251B81F46E29C005ECB85 /* ReachabilityWrapperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D251B71F46E29C005ECB85 /* ReachabilityWrapperSpec.swift */; }; + 43D395C71ECF9ECD00499E8F /* PaymentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D395C61ECF9ECD00499E8F /* PaymentService.swift */; }; + 43D395CA1ECF9F5B00499E8F /* KarhooPaymentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D395C91ECF9F5B00499E8F /* KarhooPaymentService.swift */; }; + 43D395D51ECFA31900499E8F /* KarhooPaymentServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43D395D41ECFA31900499E8F /* KarhooPaymentServiceSpec.swift */; }; + 43DD68F61ECD83F2005159C8 /* ResultSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43DD68F51ECD83F2005159C8 /* ResultSpec.swift */; }; + 43EF91691EE93DF6006ECB75 /* KarhooTripService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EF91671EE93DF6006ECB75 /* KarhooTripService.swift */; }; + 43F9399E1F3C85A300914E18 /* KarhooURLSessionSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F939951F3C85A300914E18 /* KarhooURLSessionSender.swift */; }; + 43F9399F1F3C85A300914E18 /* HttpClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F939961F3C85A300914E18 /* HttpClient.swift */; }; + 43F939A01F3C85A300914E18 /* HttpConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F939971F3C85A300914E18 /* HttpConstants.swift */; }; + 43F939A11F3C85A300914E18 /* Json.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F939981F3C85A300914E18 /* Json.swift */; }; + 43F939A21F3C85A300914E18 /* JsonHttpClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F939991F3C85A300914E18 /* JsonHttpClient.swift */; }; + 43F939A31F3C85A300914E18 /* JsonHttpRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F9399A1F3C85A300914E18 /* JsonHttpRequestBuilder.swift */; }; + 43F939A41F3C85A300914E18 /* KarhooEnvironmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F9399B1F3C85A300914E18 /* KarhooEnvironmentDetails.swift */; }; + 43F939A51F3C85A300914E18 /* URLSessionSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F9399C1F3C85A300914E18 /* URLSessionSender.swift */; }; + 43F939B51F3CD33200914E18 /* JsonHttpClientSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F939B01F3CD33200914E18 /* JsonHttpClientSpec.swift */; }; + 43F939B61F3CD33200914E18 /* JsonHttpRequestBuilderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43F939B11F3CD33200914E18 /* JsonHttpRequestBuilderSpec.swift */; }; + 5C076FFF20F23B5F00A35537 /* CategoryQuoteMapperSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C076FFE20F23B5F00A35537 /* CategoryQuoteMapperSpec.swift */; }; + 5C08DFB120E5A8D7002203CB /* MockContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C08DFB020E5A8D7002203CB /* MockContext.swift */; }; + 5C08DFB420E5A92A002203CB /* MockAnalyticsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C08DFB320E5A92A002203CB /* MockAnalyticsProvider.swift */; }; + 5C103FA22411C31F00F5F2F9 /* TokenExchangeSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C103FA12411C31F00F5F2F9 /* TokenExchangeSettings.swift */; }; + 5C103FA42411DF7F00F5F2F9 /* GuestSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C103FA32411DF7F00F5F2F9 /* GuestSettings.swift */; }; + 5C15D5EA203711D200B47A71 /* BookingSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C15D5E9203711D100B47A71 /* BookingSearch.swift */; }; + 5C1E32A4211F015D00651BB9 /* KarhooVoid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C1E32A3211F015D00651BB9 /* KarhooVoid.swift */; }; + 5C224EAF223DCA9900C1CFCE /* AddPaymentDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C224EAE223DCA9900C1CFCE /* AddPaymentDetailsInteractor.swift */; }; + 5C224EB1223DCD2000C1CFCE /* KarhooAddPaymentDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C224EB0223DCD2000C1CFCE /* KarhooAddPaymentDetailsInteractor.swift */; }; + 5C224EB4223DD83000C1CFCE /* MockAddPaymentDetailsInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C224EB3223DD83000C1CFCE /* MockAddPaymentDetailsInteractor.swift */; }; + 5C224EB7223DDB8A00C1CFCE /* AddPaymentDetailsMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C224EB6223DDB8A00C1CFCE /* AddPaymentDetailsMethodSpec.swift */; }; + 5C2856CE20E3B127009B9464 /* LocationInfoInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2856C620E3B127009B9464 /* LocationInfoInteractor.swift */; }; + 5C2856CF20E3B127009B9464 /* PlaceSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2856C820E3B127009B9464 /* PlaceSearchInteractor.swift */; }; + 5C2856D020E3B127009B9464 /* KarhooPlaceSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2856C920E3B127009B9464 /* KarhooPlaceSearchInteractor.swift */; }; + 5C2856D120E3B127009B9464 /* KarhooReverseGeocodeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2856CB20E3B127009B9464 /* KarhooReverseGeocodeInteractor.swift */; }; + 5C2856D220E3B127009B9464 /* ReverseGeocodeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2856CC20E3B127009B9464 /* ReverseGeocodeInteractor.swift */; }; + 5C2856E220E3B184009B9464 /* KarhooReverseGeocodeProviderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2856DD20E3B183009B9464 /* KarhooReverseGeocodeProviderSpec.swift */; }; + 5C2856F220E3B1DD009B9464 /* LocationInfoMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2856F120E3B1DC009B9464 /* LocationInfoMock.swift */; }; + 5C2E265024282B0400B1FF0C /* KarhooAddPaymentDetailsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 090E0164223FAB6E005D1CB7 /* KarhooAddPaymentDetailsSpec.swift */; }; + 5C5BE90D2411A513005DD14E /* AuthenticationMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5BE90C2411A513005DD14E /* AuthenticationMethod.swift */; }; + 5C75D6572080E4E60065133B /* KarhooCodableModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C75D6562080E4E60065133B /* KarhooCodableModel.swift */; }; + 5C75D65D208CC84E0065133B /* LocationDetailsAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C75D65C208CC84E0065133B /* LocationDetailsAddress.swift */; }; + 5C870D2920CDBC9C0039A2BF /* LoginRequestPayloadMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C870D2820CDBC9C0039A2BF /* LoginRequestPayloadMock.swift */; }; + 5CA2CE422343510E000AEC1E /* OrganisationMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA2CE412343510E000AEC1E /* OrganisationMock.swift */; }; + 5CA2CE45234368B7000AEC1E /* UISettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA2CE44234368B7000AEC1E /* UISettings.swift */; }; + 8AF5210BF925EC94C12D5D34 /* Pods_KarhooSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE2A6E7031AD7206EDBD999A /* Pods_KarhooSDK.framework */; }; + AE5058823A162206E237F5FA /* Pods_Client.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 491F52E3C4EE22C2B1A9B930 /* Pods_Client.framework */; }; + B2B1738EC35E4790DEAAA7EC /* Pods_KarhooSDKIntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3843568322BB0FCF9D45FE9 /* Pods_KarhooSDKIntegrationTests.framework */; }; + BAA0A0478693DADB025AE19A /* KarhooPasswordResetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AAD70E6AE0FF8C70BEC1 /* KarhooPasswordResetInteractor.swift */; }; + BAA0A0D9B4689C44C4517E85 /* Places.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AAC1104384B3CDE0FED2 /* Places.swift */; }; + BAA0A113CE860049AD045890 /* KarhooTripStatusInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A7C9A052655223E880D3 /* KarhooTripStatusInteractor.swift */; }; + BAA0A1372FE5B2F6B605114B /* AuthToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AB9B6A8BB01E3CF959C0 /* AuthToken.swift */; }; + BAA0A144BA8E2FA9BBCECF9A /* QuoteList.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A316925D2F14D6EEEAE6 /* QuoteList.swift */; }; + BAA0A1625DACE366FC991EE7 /* TripStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AEEB35537AC7936CC8B7 /* TripStatus.swift */; }; + BAA0A16B38001BD6031859AF /* CategoriesMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AA4DA19A464EF80F00F6 /* CategoriesMock.swift */; }; + BAA0A20863C722DEDBD8AB81 /* RequestSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AE3182D554B65C66EC52 /* RequestSender.swift */; }; + BAA0A28127E0831F332F8FD7 /* FareComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A5C6D495C7A6B997BB5E /* FareComponent.swift */; }; + BAA0A2B39435CDDCC9E3B772 /* KarhooBookingInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A3541731789F935644A7 /* KarhooBookingInteractorSpec.swift */; }; + BAA0A2C1640158EABC2C8409 /* MockPasswordResetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A5FC9FBDD3F60C6FE30E /* MockPasswordResetInteractor.swift */; }; + BAA0A2F241A3762CCC60A9ED /* KarhooBookingInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AFBC32DB1629C3BB1D01 /* KarhooBookingInteractor.swift */; }; + BAA0A32E25B5969B08BEACBD /* PoiDetailsType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0ACCCE0E8BFB2852A3143 /* PoiDetailsType.swift */; }; + BAA0A33C16B003E2D9F99AB3 /* PoiType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AED770E3100C742E4BE1 /* PoiType.swift */; }; + BAA0A3538DD38C00232154EB /* MeetingPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A3A0B72938FD19C2911A /* MeetingPoint.swift */; }; + BAA0A3A43066DD0F628543FE /* FleetInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AEEDE0D01BA897FB6ECA /* FleetInfo.swift */; }; + BAA0A47EBFAADA9C274FAF83 /* UserLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A499BF991C5FC4735A7D /* UserLogin.swift */; }; + BAA0A49004C72C6E7EADBED9 /* QuoteIdMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A819AC6AB43365A4C081 /* QuoteIdMock.swift */; }; + BAA0A4B940D784C81D26467F /* KarhooPaymentSDKTokenInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A0172A18CDF485FF7519 /* KarhooPaymentSDKTokenInteractor.swift */; }; + BAA0A52F9D9380CE153EA7C6 /* PaymentSDKToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0ADCEB2BBB622745A315B /* PaymentSDKToken.swift */; }; + BAA0A599E1EB8C3F8C2F8821 /* PassengerDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AA1BB35C2D341BB66986 /* PassengerDetails.swift */; }; + BAA0A5ADE570C5E9F2BDAE4D /* Passengers.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A069D5D958868761AAF7 /* Passengers.swift */; }; + BAA0A5F9C452FEC2B08B557A /* Availability.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A965007F063C8346E974 /* Availability.swift */; }; + BAA0A6A2B3C0BB130896465E /* KarhooPasswordResetInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A8260B3FAD817582FADE /* KarhooPasswordResetInteractorSpec.swift */; }; + BAA0A6AFAE41A51A35679357 /* PoiDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A263663CFFE1DE2F5E76 /* PoiDetails.swift */; }; + BAA0A6EAF352449EA13B4994 /* KarhooRequestSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A33AC9CF7967569962A2 /* KarhooRequestSender.swift */; }; + BAA0A7BF125DFEEECCA06B44 /* PasswordResetRequestPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AD752CF0001B84C65B6E /* PasswordResetRequestPayload.swift */; }; + BAA0A8B83EF64CFCC23678EC /* MockTripStatusInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AC8C40120E475F87ECB2 /* MockTripStatusInteractor.swift */; }; + BAA0A91AB3B45A6A56034A0F /* UserInfoMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AACF77649D0AA514D127 /* UserInfoMock.swift */; }; + BAA0A99196851950BE007EC3 /* BookingInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A7CF7A18FDC822E15AFD /* BookingInteractor.swift */; }; + BAA0A9B82B56E7732880DF88 /* PositionExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A1DD7F2A93BCD18DD3FE /* PositionExt.swift */; }; + BAA0A9E6A724F31D9BCFD880 /* KarhooTripStatusInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AE54DAE03275821D43A7 /* KarhooTripStatusInteractorSpec.swift */; }; + BAA0AA88F01A05227A1CC69A /* MockTimingScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A5929BD792349F51A637 /* MockTimingScheduler.swift */; }; + BAA0AA94DD5A84DFCB2F757C /* TripBookingMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A2BC090EFF81F2F9B9AC /* TripBookingMock.swift */; }; + BAA0ABF896D0A71FBF3482B7 /* PaymentSDKTokenInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AC190CF1C7C0CBDC717E /* PaymentSDKTokenInteractor.swift */; }; + BAA0AC4CCFCCFBAAE287219C /* PasswordResetRequestPayloadMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A26ED6144AF54A930CFB /* PasswordResetRequestPayloadMock.swift */; }; + BAA0AD4ECBE829DE71C521FF /* TripLocationDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A2498417189117D6F286 /* TripLocationDetails.swift */; }; + BAA0AD6977BF4A2BF4E00673 /* PaymentSDKTokenMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A2139A85B9FF7F56BED7 /* PaymentSDKTokenMock.swift */; }; + BAA0AD9A7DADB9A66E5705F3 /* MockPaymentSDKTokenInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0A6F016D7940CF293B9BC /* MockPaymentSDKTokenInteractor.swift */; }; + BAA0ADC3A54466A62DCB5412 /* TripStatusInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0AF4654D1B9211454EEBD /* TripStatusInteractor.swift */; }; + BAA0AE32283EBDE47DBE35BD /* TripStatusMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAA0ADA5AD439AA7E9B9ED6C /* TripStatusMock.swift */; }; + FC0BF05B20E39D2D0004DF61 /* MockTripUpdateInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC0BF05A20E39D2D0004DF61 /* MockTripUpdateInteractor.swift */; }; + FC0BF09C20E3CB4F0004DF61 /* MockRefreshTokenRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC0BF09B20E3CB4F0004DF61 /* MockRefreshTokenRequest.swift */; }; + FC1202452146A4F000161081 /* Empty.json in Resources */ = {isa = PBXBuildFile; fileRef = FC1202442146A4F000161081 /* Empty.json */; }; + FC1202472146A7CA00161081 /* Error.json in Resources */ = {isa = PBXBuildFile; fileRef = FC1202462146A7CA00161081 /* Error.json */; }; + FC12024C214A840E00161081 /* LocationInfoMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC12024B214A840E00161081 /* LocationInfoMethodSpec.swift */; }; + FC12024F214A950B00161081 /* LocationInfo.json in Resources */ = {isa = PBXBuildFile; fileRef = FC12024E214A950B00161081 /* LocationInfo.json */; }; + FC120251214AB3C000161081 /* InvalidJson.json in Resources */ = {isa = PBXBuildFile; fileRef = FC120250214AB3C000161081 /* InvalidJson.json */; }; + FC120253214BF82400161081 /* ReverseGeocodeMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC120252214BF82400161081 /* ReverseGeocodeMethodSpec.swift */; }; + FC120256214FF0AE00161081 /* RegisterMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC120255214FF0AE00161081 /* RegisterMethodSpec.swift */; }; + FC12025D215129DC00161081 /* TrackTripMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC12025C215129DC00161081 /* TrackTripMethodSpec.swift */; }; + FC1202612152567C00161081 /* BookTripMethodSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1202602152567C00161081 /* BookTripMethodSpec.swift */; }; + FC1B01ED2209D0D20069D7F3 /* QuoteCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1B01EC2209D0D20069D7F3 /* QuoteCategory.swift */; }; + FC2D95722113612300201BDB /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2D95712113612300201BDB /* Error.swift */; }; + FC2EE934212425F6001B36D2 /* SDKErrorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC2EE933212425F6001B36D2 /* SDKErrorFactory.swift */; }; + FC321EE020F8D54E00B2C3CD /* DriverTrackingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC321EDF20F8D54E00B2C3CD /* DriverTrackingService.swift */; }; + FC321EE220F8D56400B2C3CD /* KarhooDriverTrackingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC321EE120F8D56400B2C3CD /* KarhooDriverTrackingService.swift */; }; + FC321EE420F8D72C00B2C3CD /* KarhooDriverTrackingServiceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC321EE320F8D72C00B2C3CD /* KarhooDriverTrackingServiceSpec.swift */; }; + FC57F943212EEC56009193CB /* TripUpdateInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC57F942212EEC56009193CB /* TripUpdateInteractor.swift */; }; + FC57F945213015E7009193CB /* PollCallFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC57F944213015E7009193CB /* PollCallFactory.swift */; }; + FC57F94821301637009193CB /* MockPollCallFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC57F94721301637009193CB /* MockPollCallFactory.swift */; }; + FC57F94A21301759009193CB /* MockPollCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC57F94921301759009193CB /* MockPollCall.swift */; }; + FC861B2C21107727004CC47A /* KarhooError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC861B2B21107727004CC47A /* KarhooError.swift */; }; + FC861B2E21107734004CC47A /* KarhooSDKError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC861B2D21107734004CC47A /* KarhooSDKError.swift */; }; + FC861B5E2110B7C0004CC47A /* HTTPError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC861B5D2110B7C0004CC47A /* HTTPError.swift */; }; + FC8EB5C9216507B7009DC8DE /* TripQuote.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8EB5C8216507B7009DC8DE /* TripQuote.swift */; }; + FC8EB5CB21651AA0009DC8DE /* VehicleAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8EB5CA21651AA0009DC8DE /* VehicleAttributes.swift */; }; + FC8F474F2154F9D6007841FB /* ObserverBroadcaster.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8F474E2154F9D6007841FB /* ObserverBroadcaster.swift */; }; + FC8F475121553184007841FB /* Observer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8F475021553184007841FB /* Observer.swift */; }; + FC8F475321555025007841FB /* MockObserverBroadcaster.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC8F475221555025007841FB /* MockObserverBroadcaster.swift */; }; + FC987C7D20EF7949001B8B79 /* CLLocationExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC987C7C20EF7949001B8B79 /* CLLocationExt.swift */; }; + FCBD67B3211C9CBD005AA332 /* LoginInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCBD67B2211C9CBD005AA332 /* LoginInteractor.swift */; }; + FCBD67B5211C9CE6005AA332 /* PasswordResetInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCBD67B4211C9CE6005AA332 /* PasswordResetInteractor.swift */; }; + FCBD67B7211C9D7E005AA332 /* RegisterInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCBD67B6211C9D7E005AA332 /* RegisterInteractor.swift */; }; + FCBD67B9211DDD8E005AA332 /* MockInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCBD67B8211DDD8E005AA332 /* MockInteractor.swift */; }; + FCC17D792118984B00A25B87 /* KarhooPollCallFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC17D782118984B00A25B87 /* KarhooPollCallFactory.swift */; }; + FCD32FE920DA9C4F0044BB64 /* TripSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD32FE820DA9C4F0044BB64 /* TripSearchInteractor.swift */; }; + FCD32FEB20DA9C5F0044BB64 /* KarhooTripSearchInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD32FEA20DA9C5F0044BB64 /* KarhooTripSearchInteractor.swift */; }; + FCD32FED20DAA1470044BB64 /* TripsRequestPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD32FEC20DAA1470044BB64 /* TripsRequestPayload.swift */; }; + FCD32FEF20DAA33A0044BB64 /* KarhooTripsListInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD32FEE20DAA33A0044BB64 /* KarhooTripsListInteractorSpec.swift */; }; + FCD32FF720E252680044BB64 /* KarhooTripUpdateInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD32FF620E252680044BB64 /* KarhooTripUpdateInteractor.swift */; }; + FCD32FFA20E26DC10044BB64 /* KarhooTripUpdateInteractortSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD32FF920E26DC10044BB64 /* KarhooTripUpdateInteractortSpec.swift */; }; + FCD4DA5720F37FB2007D4374 /* AvailabilitySearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD4DA5620F37FB2007D4374 /* AvailabilitySearch.swift */; }; + FCD4DA5A20F39AD0007D4374 /* MockAvailabilityInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD4DA5920F39AD0007D4374 /* MockAvailabilityInteractor.swift */; }; + FCD4DA5C20F3A5C5007D4374 /* KarhooAvailabilityInteractorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD4DA5B20F3A5C5007D4374 /* KarhooAvailabilityInteractorSpec.swift */; }; + FCE11B3820F6272400DFC641 /* AvailabilitySearchMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE11B3720F6272400DFC641 /* AvailabilitySearchMock.swift */; }; + FCE694242166517D00FAD591 /* TripInfo.json in Resources */ = {isa = PBXBuildFile; fileRef = 097D4720215111ED008D8D94 /* TripInfo.json */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 09B204B221412C3200063632 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4387A6381E3BAF6D0024FD55 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4387A6401E3BAF6D0024FD55; + remoteInfo = KarhooSDK; + }; + 4387A64C1E3BAF6D0024FD55 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4387A6381E3BAF6D0024FD55 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4387A6401E3BAF6D0024FD55; + remoteInfo = KarhooSDK; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0900D34820D2B17B0082D1E8 /* RefreshTokenInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshTokenInteractor.swift; sourceTree = ""; }; + 0900D34A20D2B1920082D1E8 /* RefreshTokenError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshTokenError.swift; sourceTree = ""; }; + 0907B98920D7B794008915B1 /* Reachability.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Reachability.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0907B98B20D7B901008915B1 /* CancelTripInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelTripInteractor.swift; sourceTree = ""; }; + 0907B98E20D7B964008915B1 /* KarhooCancelTripInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooCancelTripInteractor.swift; sourceTree = ""; }; + 0907B99120D7B98A008915B1 /* KarhooCancelTripInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooCancelTripInteractorSpec.swift; sourceTree = ""; }; + 090881DE20E65F7D00EE2C67 /* KarhooPlaceSearchInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPlaceSearchInteractorSpec.swift; sourceTree = ""; }; + 090881E020E65FAD00EE2C67 /* MockPlaceSearchInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPlaceSearchInteractor.swift; sourceTree = ""; }; + 090881E220E662D200EE2C67 /* KarhooLocationInfoInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooLocationInfoInteractor.swift; sourceTree = ""; }; + 090881E420E663E500EE2C67 /* KarhooLocationInfoInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooLocationInfoInteractorSpec.swift; sourceTree = ""; }; + 090E0164223FAB6E005D1CB7 /* KarhooAddPaymentDetailsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAddPaymentDetailsSpec.swift; sourceTree = ""; }; + 090E8225235DAD5900B447A2 /* KarhooEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooEnvironment.swift; sourceTree = ""; }; + 091A2D0D207D23F600F06E28 /* LocationInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationInfo.swift; sourceTree = ""; }; + 091BE6E92085192800BA34D0 /* PlaceSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceSearch.swift; sourceTree = ""; }; + 091BE6EB2086562900BA34D0 /* Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Position.swift; sourceTree = ""; }; + 091CAE3D20D7A81F005F1DB6 /* MockURLSessionSender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockURLSessionSender.swift; sourceTree = ""; }; + 092F27C622B1353D00AF8E0E /* PickUpType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickUpType.swift; sourceTree = ""; }; + 09430E4A2240054A009846A6 /* IntegrationTestSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegrationTestSetup.swift; sourceTree = ""; }; + 09430E4C22410037009846A6 /* MockSDKConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSDKConfig.swift; sourceTree = ""; }; + 09430E4E224109D6009846A6 /* UnitTestSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitTestSetup.swift; sourceTree = ""; }; + 0947FD3A2121D51900E8BE4A /* KarhooErrorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooErrorType.swift; sourceTree = ""; }; + 094B449D2285AC65002D2506 /* PaymentSDKTokenPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSDKTokenPayload.swift; sourceTree = ""; }; + 094B44B32285B8DE002D2506 /* AddPaymentDetails.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AddPaymentDetails.json; sourceTree = ""; }; + 094C1F7E20B2C956001FFE7F /* MockNetworkDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNetworkDateFormatter.swift; sourceTree = ""; }; + 095A03B823339721007D805E /* ConfigService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigService.swift; sourceTree = ""; }; + 095A03BA2333ADC2007D805E /* KarhooConfigService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooConfigService.swift; sourceTree = ""; }; + 095A03BD2333AF4A007D805E /* UIConfigInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConfigInteractor.swift; sourceTree = ""; }; + 095A03BF2333AF64007D805E /* KarhooUIConfigInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooUIConfigInteractor.swift; sourceTree = ""; }; + 095A03C12333AF98007D805E /* UIConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConfig.swift; sourceTree = ""; }; + 095A03C32333AFED007D805E /* UIConfigRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConfigRequest.swift; sourceTree = ""; }; + 095A03C62333B115007D805E /* UIConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConfigProvider.swift; sourceTree = ""; }; + 095A03CA2333B3E6007D805E /* KarhooConfigServiceSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooConfigServiceSpec.swift; sourceTree = ""; }; + 095A03CC2333B3F8007D805E /* KarhooUIConfigInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooUIConfigInteractorSpec.swift; sourceTree = ""; }; + 095A03CF2333BABD007D805E /* KarhooUIConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooUIConfigProvider.swift; sourceTree = ""; }; + 095A03D12333C119007D805E /* KarhooUIConfigProviderSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooUIConfigProviderSpec.swift; sourceTree = ""; }; + 095A03D42333C235007D805E /* MockUIConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUIConfigProvider.swift; sourceTree = ""; }; + 095A03DD2333D926007D805E /* UIConfigMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConfigMethodSpec.swift; sourceTree = ""; }; + 095A03DF2333DFCB007D805E /* MockUIConfigInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUIConfigInteractor.swift; sourceTree = ""; }; + 095CEC0520F7573700C8AC4B /* TripCancellation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripCancellation.swift; sourceTree = ""; }; + 095E406C20B32DAE008EAF0F /* NetworkDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDateFormatter.swift; sourceTree = ""; }; + 096A2C2C208F676000C7B7D0 /* PositionExtSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionExtSpec.swift; sourceTree = ""; }; + 096B31BB20B702AE002B41E9 /* MockBookingInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBookingInteractor.swift; sourceTree = ""; }; + 096B31BF20B702E8002B41E9 /* MockTripSearchInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTripSearchInteractor.swift; sourceTree = ""; }; + 0970BA6A21136F870015A170 /* Executable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Executable.swift; sourceTree = ""; }; + 0970BA6B21136F870015A170 /* KarhooPollableExecutor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPollableExecutor.swift; sourceTree = ""; }; + 0970BA6C21136F870015A170 /* KarhooPollCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPollCall.swift; sourceTree = ""; }; + 0970BA6D21136F870015A170 /* KarhooCall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooCall.swift; sourceTree = ""; }; + 0970BA6E21136F870015A170 /* KarhooObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooObservable.swift; sourceTree = ""; }; + 0970BA77211371600015A170 /* KarhooDriverTrackingInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooDriverTrackingInteractor.swift; sourceTree = ""; }; + 0970BA7A21145E550015A170 /* KarhooCallSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooCallSpec.swift; sourceTree = ""; }; + 0970BA7D211464C40015A170 /* MockExecutable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockExecutable.swift; sourceTree = ""; }; + 0970BA7F2114671A0015A170 /* KarhooObservableSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooObservableSpec.swift; sourceTree = ""; }; + 0970BA812114685B0015A170 /* MockKarhooCodableModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockKarhooCodableModel.swift; sourceTree = ""; }; + 0970BA8721146DF70015A170 /* MockPollExecutor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPollExecutor.swift; sourceTree = ""; }; + 0970BA892114970A0015A170 /* KarhooPollCallSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooPollCallSpec.swift; sourceTree = ""; }; + 0970BA8B21149FC60015A170 /* KarhooPollableExecutorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooPollableExecutorSpec.swift; sourceTree = ""; }; + 097BF2B62358710400BBE418 /* KarhooSDKConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooSDKConfiguration.swift; sourceTree = ""; }; + 097D4720215111ED008D8D94 /* TripInfo.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = TripInfo.json; sourceTree = ""; }; + 097D472221511208008D8D94 /* TripStatusMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripStatusMethodSpec.swift; sourceTree = ""; }; + 097D4724215128E0008D8D94 /* TripStatus.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = TripStatus.json; sourceTree = ""; }; + 097D472621512D5E008D8D94 /* TripSearchMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripSearchMethodSpec.swift; sourceTree = ""; }; + 09811A1720D7BB8C001DB06F /* CancelTripRequestPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelTripRequestPayload.swift; sourceTree = ""; }; + 09811A1920D7BD77001DB06F /* CancelReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelReason.swift; sourceTree = ""; }; + 09811A1D20D7C12D001DB06F /* MockCancelTripInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCancelTripInteractor.swift; sourceTree = ""; }; + 0981F6C620CEC27A00BF7FB5 /* MockRequestSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRequestSender.swift; sourceTree = ""; }; + 0981F6C820CEC3DE00BF7FB5 /* KarhooLoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooLoginInteractor.swift; sourceTree = ""; }; + 0981F6CA20CEE1AD00BF7FB5 /* KarhooLoginInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooLoginInteractorSpec.swift; sourceTree = ""; }; + 0981F6CC20CEEB1000BF7FB5 /* AuthTokenMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTokenMock.swift; sourceTree = ""; }; + 0981F6CE20CFDA9B00BF7FB5 /* KarhooRequestSenderSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooRequestSenderSpec.swift; sourceTree = ""; }; + 0981F6D420CFE1B300BF7FB5 /* MockLoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLoginInteractor.swift; sourceTree = ""; }; + 09820E6920D7E5E800782530 /* KarhooRegisterInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooRegisterInteractor.swift; sourceTree = ""; }; + 09820E6F20D7F07800782530 /* KarhooRegisterInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooRegisterInteractorSpec.swift; sourceTree = ""; }; + 09820E7120D7F60100782530 /* UserRegistrationMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRegistrationMock.swift; sourceTree = ""; }; + 09820E7320D809E000782530 /* MockRegisterInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRegisterInteractor.swift; sourceTree = ""; }; + 09820E7720D8172D00782530 /* MockLogoutInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockLogoutInteractor.swift; sourceTree = ""; }; + 098C91F82147CDC500F02DE3 /* GeneralErrorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralErrorSpec.swift; sourceTree = ""; }; + 098C91FA2147FF9900F02DE3 /* RawKarhooErrorFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawKarhooErrorFactory.swift; sourceTree = ""; }; + 098D524E23FB14FA00059086 /* AuthLoginMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthLoginMethodSpec.swift; sourceTree = ""; }; + 098D525123FB157B00059086 /* KarhooAuthRevokeInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAuthRevokeInteractorSpec.swift; sourceTree = ""; }; + 099202A620DBB1D20009C845 /* Organisation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Organisation.swift; sourceTree = ""; }; + 09928D1820EFAABA00B9F462 /* CategoryQuoteMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryQuoteMapper.swift; sourceTree = ""; }; + 09928D1D20EFAABA00B9F462 /* QuoteService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteService.swift; sourceTree = ""; }; + 09928D1E20EFAABA00B9F462 /* KarhooQuoteService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooQuoteService.swift; sourceTree = ""; }; + 09928D2020EFAABA00B9F462 /* KarhooQuoteInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooQuoteInteractor.swift; sourceTree = ""; }; + 09928D2120EFAABA00B9F462 /* QuoteInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteInteractor.swift; sourceTree = ""; }; + 09928D3220EFAAEE00B9F462 /* KarhooQuoteServiceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooQuoteServiceSpec.swift; sourceTree = ""; }; + 09928D3420EFAAEE00B9F462 /* KarhooQuoteInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooQuoteInteractorSpec.swift; sourceTree = ""; }; + 09928D4120EFAB1300B9F462 /* MockQuoteInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockQuoteInteractor.swift; sourceTree = ""; }; + 09928D4620EFAB3000B9F462 /* QuoteSearch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteSearch.swift; sourceTree = ""; }; + 09928D4820EFAB9300B9F462 /* Quotes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Quotes.swift; sourceTree = ""; }; + 099613702408359A00C8C88E /* RevokeMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevokeMethodSpec.swift; sourceTree = ""; }; + 0996137224083B6E00C8C88E /* AccessTokenProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessTokenProvider.swift; sourceTree = ""; }; + 099AF2F820F8BA0000A2AA57 /* TripState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripState.swift; sourceTree = ""; }; + 099AF2FA20F8BDF000A2AA57 /* TripInfoMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripInfoMock.swift; sourceTree = ""; }; + 099DFC6B223BEDF800401542 /* AddPaymentDetailsPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddPaymentDetailsPayload.swift; sourceTree = ""; }; + 09A18BD821185080009F927B /* KarhooDriverTrackingInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooDriverTrackingInteractorSpec.swift; sourceTree = ""; }; + 09A56DFB20922D5C00891F02 /* MeetingPointType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingPointType.swift; sourceTree = ""; }; + 09A56DFF2097162C00891F02 /* LocationDetailsExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailsExt.swift; sourceTree = ""; }; + 09A9B28B2405901900823FB0 /* Client.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Client.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 09A9B28D2405901900823FB0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 09A9B28F2405901900823FB0 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 09A9B2912405901900823FB0 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 09A9B2962405901B00823FB0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 09A9B2992405901B00823FB0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 09A9B29B2405901B00823FB0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 09ACCE4F207BBB5E0057B0CC /* QuoteListIdRequestPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteListIdRequestPayload.swift; sourceTree = ""; }; + 09AF9AA820D174150046D725 /* RefreshToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshToken.swift; sourceTree = ""; }; + 09AF9AAA20D1751D0046D725 /* RefreshTokenRequestPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshTokenRequestPayload.swift; sourceTree = ""; }; + 09AF9AAC20D17B7D0046D725 /* RefreshTokenMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshTokenMock.swift; sourceTree = ""; }; + 09B204AC21412C3100063632 /* KarhooSDKIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KarhooSDKIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 09B204B021412C3200063632 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 09B204BB2141366C00063632 /* InvalidData.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = InvalidData.json; sourceTree = ""; }; + 09BDE052212F128700798330 /* MockLocationInfoInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockLocationInfoInteractor.swift; sourceTree = ""; }; + 09BDE054212F147600798330 /* MockReverseGeocodeInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockReverseGeocodeInteractor.swift; sourceTree = ""; }; + 09CA785720EE5ABC00D3BB6F /* Fare.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fare.swift; sourceTree = ""; }; + 09CE9A4F20FCE87800874092 /* TripBooking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripBooking.swift; sourceTree = ""; }; + 09CE9A5320FE1F2E00874092 /* TripSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripSearch.swift; sourceTree = ""; }; + 09CE9A5520FE530700874092 /* TripType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripType.swift; sourceTree = ""; }; + 09D0757420D826A700F75E32 /* KarhooLogoutInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooLogoutInteractor.swift; sourceTree = ""; }; + 09D293C122C530170051C455 /* QuoteSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteSource.swift; sourceTree = ""; }; + 09E40C17E5179F413E52C435 /* Pods-KarhooSDKTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KarhooSDKTests.debug.xcconfig"; path = "Target Support Files/Pods-KarhooSDKTests/Pods-KarhooSDKTests.debug.xcconfig"; sourceTree = ""; }; + 09EE81E62152585D004B2B09 /* TripSearch.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = TripSearch.json; sourceTree = ""; }; + 09EE81E92152617B004B2B09 /* QuoteSearchMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteSearchMethodSpec.swift; sourceTree = ""; }; + 09EE81ED21528791004B2B09 /* QuoteListId.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = QuoteListId.json; sourceTree = ""; }; + 09EE81EE21528791004B2B09 /* Availability.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Availability.json; sourceTree = ""; }; + 09EE81EF21528791004B2B09 /* Quotes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Quotes.json; sourceTree = ""; }; + 09F79E33223952F900D5B0B8 /* Nonce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nonce.swift; sourceTree = ""; }; + 09F79E352239546E00D5B0B8 /* GetNonceInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetNonceInteractor.swift; sourceTree = ""; }; + 09F79E372239547A00D5B0B8 /* KarhooGetNonceInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooGetNonceInteractor.swift; sourceTree = ""; }; + 09F79E39223955FF00D5B0B8 /* NonceRequestPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceRequestPayload.swift; sourceTree = ""; }; + 09F79E3B2239564600D5B0B8 /* Payer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Payer.swift; sourceTree = ""; }; + 09F79E3E2239723200D5B0B8 /* KarhooGetNonceInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooGetNonceInteractorSpec.swift; sourceTree = ""; }; + 09F79E40223A63DA00D5B0B8 /* NonceRequestPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceRequestPayload.swift; sourceTree = ""; }; + 09F79E43223A99E700D5B0B8 /* GetNonceMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetNonceMethodSpec.swift; sourceTree = ""; }; + 09F79E46223AAA6600D5B0B8 /* MockGetNonceInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGetNonceInteractor.swift; sourceTree = ""; }; + 09F79E4A223ABD5A00D5B0B8 /* GetNonce.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = GetNonce.json; sourceTree = ""; }; + 09F8ABE8214A9B1500E293B7 /* DriverTracking.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = DriverTracking.json; sourceTree = ""; }; + 09F8ABEB214A9B5700E293B7 /* DriverTrackingMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DriverTrackingMethodSpec.swift; sourceTree = ""; }; + 09F8ABED214AABBD00E293B7 /* NetworkStubFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkStubFactory.swift; sourceTree = ""; }; + 09F8ABF5214BF4F200E293B7 /* PlaceSearchMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceSearchMethodSpec.swift; sourceTree = ""; }; + 09F8ABF7214BF69400E293B7 /* Places.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Places.json; sourceTree = ""; }; + 09F8ABFA214FB4C900E293B7 /* PaymentSDKTokenMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSDKTokenMethod.swift; sourceTree = ""; }; + 09F8ABFC214FB61100E293B7 /* PaymentSDKToken.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = PaymentSDKToken.json; sourceTree = ""; }; + 09F8ABFF214FDF4200E293B7 /* LoginMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMethodSpec.swift; sourceTree = ""; }; + 09F8AC02214FE6DF00E293B7 /* UnauthorisedUserInfo.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = UnauthorisedUserInfo.json; sourceTree = ""; }; + 09F8AC03214FE6DF00E293B7 /* AuthToken.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AuthToken.json; sourceTree = ""; }; + 09F8AC04214FE6DF00E293B7 /* AuthorisedUserInfo.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AuthorisedUserInfo.json; sourceTree = ""; }; + 09F90F902150FA2C003BC867 /* PasswordResetMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordResetMethodSpec.swift; sourceTree = ""; }; + 09F90F932151008C003BC867 /* CancelTripMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelTripMethodSpec.swift; sourceTree = ""; }; + 13B19E142647D37690651605 /* Pods-KarhooSDK.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KarhooSDK.release.xcconfig"; path = "Target Support Files/Pods-KarhooSDK/Pods-KarhooSDK.release.xcconfig"; sourceTree = ""; }; + 233DB46623433EC200BCBC3F /* MockUpdateUserDetailsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockUpdateUserDetailsInteractor.swift; sourceTree = ""; }; + 233DB46823433F0400BCBC3F /* UserUpdateMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserUpdateMock.swift; sourceTree = ""; }; + 2359B45A234251FA008433A1 /* KarhooUpdateUserDetailsInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooUpdateUserDetailsInteractorSpec.swift; sourceTree = ""; }; + 2359B45D2342522E008433A1 /* KarhooUpdateUserDetailsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooUpdateUserDetailsInteractor.swift; sourceTree = ""; }; + 2359B45E2342522E008433A1 /* UpdateUserDetailsInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateUserDetailsInteractor.swift; sourceTree = ""; }; + 2359B46123425299008433A1 /* UserUpdatePayload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserUpdatePayload.swift; sourceTree = ""; }; + 237E0C562347823A00798AA0 /* FareService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FareService.swift; sourceTree = ""; }; + 237E0C58234782B400798AA0 /* KarhooFareService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooFareService.swift; sourceTree = ""; }; + 237E0C5B2347838600798AA0 /* FareInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FareInteractor.swift; sourceTree = ""; }; + 237E0C5D234783BE00798AA0 /* KarhooFareInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooFareInteractor.swift; sourceTree = ""; }; + 237E0C602347852500798AA0 /* Fare.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Fare.json; sourceTree = ""; }; + 237E0C63234788F000798AA0 /* KarhooFareServiceSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooFareServiceSpec.swift; sourceTree = ""; }; + 237E0C662347897B00798AA0 /* MockFareInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFareInteractor.swift; sourceTree = ""; }; + 237E0C682347931D00798AA0 /* KarhooFareInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooFareInteractorSpec.swift; sourceTree = ""; }; + 2742100A1C88263575619D5D /* KarhooHeaderProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooHeaderProvider.swift; sourceTree = ""; }; + 2742119D00DE30C9E3590055 /* HeaderProviderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderProviderSpec.swift; sourceTree = ""; }; + 2742136485E56D7E2AEF11AB /* CredentialParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialParser.swift; sourceTree = ""; }; + 274213D38799FE5D607435B2 /* HeaderProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderProvider.swift; sourceTree = ""; }; + 274215B1B4ADECE9D9CE2EA2 /* QuoteListMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteListMock.swift; sourceTree = ""; }; + 27421639BE5688EF2127E119 /* UserRegistration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserRegistration.swift; sourceTree = ""; }; + 274216467197574CB8746A19 /* AvailabilityInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvailabilityInteractor.swift; sourceTree = ""; }; + 274217092FA03DAFAD7776E1 /* QuoteListId.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteListId.swift; sourceTree = ""; }; + 27421A91EE9BE3C99E54F313 /* UserDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDataStore.swift; sourceTree = ""; }; + 27421AB287B1A33F00AC4119 /* UserInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = ""; }; + 27421CA2A060894A7D859830 /* KarhooAvailabilityInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooAvailabilityInteractor.swift; sourceTree = ""; }; + 27421CBA8C72F37A4F143580 /* HeaderConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderConstants.swift; sourceTree = ""; }; + 27421D16D44DEC18A835BDB0 /* DriverTrackingInfoMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DriverTrackingInfoMock.swift; sourceTree = ""; }; + 27421D9042A4F39DF7D69941 /* QuoteMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteMock.swift; sourceTree = ""; }; + 27421E2976A85D5A0FB95991 /* Categories.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Categories.swift; sourceTree = ""; }; + 27421EDE7F5329B3837E9DFC /* APIEndpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIEndpoint.swift; sourceTree = ""; }; + 31005DA61FCEE699006D2A7D /* RequestTesting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTesting.swift; sourceTree = ""; }; + 310D44AD1FA1F8890038BFDA /* TimestampFormatterSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimestampFormatterSpec.swift; sourceTree = ""; }; + 312909E11FA0E0C900F5147D /* TimestampFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimestampFormatter.swift; sourceTree = ""; }; + 312B1AAB1FA097F900BB27CF /* BatteryMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryMonitor.swift; sourceTree = ""; }; + 312B1AAD1FA0982800BB27CF /* NetworkConnectionTypeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnectionTypeProvider.swift; sourceTree = ""; }; + 312CDFB51F6A984A006F48DD /* AppStateNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateNotifier.swift; sourceTree = ""; }; + 313DC2D020221A0800D9DD1B /* Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Driver.swift; sourceTree = ""; }; + 313DC2DC20221D0100D9DD1B /* Vehicle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vehicle.swift; sourceTree = ""; }; + 313DC2E320221E7B00D9DD1B /* Place.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Place.swift; sourceTree = ""; }; + 313DC2EF20221FCE00D9DD1B /* QuoteType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteType.swift; sourceTree = ""; }; + 313DC2F120221FDB00D9DD1B /* Quote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Quote.swift; sourceTree = ""; }; + 3150CE2B1F9F7B6000DE6D62 /* UserLocationProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserLocationProvider.swift; sourceTree = ""; }; + 3150CE2E1F9F7BBA00DE6D62 /* UserLocationProviderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserLocationProviderSpec.swift; sourceTree = ""; }; + 3150CE301F9F7D8E00DE6D62 /* MockCLLocationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockCLLocationManager.swift; sourceTree = ""; }; + 317210C11FFD2476004CE129 /* MockAppStateNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAppStateNotifier.swift; sourceTree = ""; }; + 317210CE1FFE7330004CE129 /* CredentialsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsSpec.swift; sourceTree = ""; }; + 317210D01FFE86B4004CE129 /* KarhooRefreshTokenInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooRefreshTokenInteractorSpec.swift; sourceTree = ""; }; + 317610EB1FD84B1E00D2DB75 /* KarhooNetworkDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooNetworkDateFormatter.swift; sourceTree = ""; }; + 317610ED1FD84CD200D2DB75 /* KarhooNetworkDateFormatterSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooNetworkDateFormatterSpec.swift; sourceTree = ""; }; + 317610F11FD86A9600D2DB75 /* KarhooAvailabilityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAvailabilityService.swift; sourceTree = ""; }; + 317610F31FD9855D00D2DB75 /* KarhooAvailabilityServiceSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAvailabilityServiceSpec.swift; sourceTree = ""; }; + 31904CD31FFFC04400BA7402 /* KarhooRefreshTokenInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooRefreshTokenInteractor.swift; sourceTree = ""; }; + 31904CD51FFFE26F00BA7402 /* TokenRefreshingHttpClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRefreshingHttpClient.swift; sourceTree = ""; }; + 31904CD72003800000BA7402 /* TokenRefreshingHttpClientSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRefreshingHttpClientSpec.swift; sourceTree = ""; }; + 31904CD9200380A400BA7402 /* MockAccessTokenProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAccessTokenProvider.swift; sourceTree = ""; }; + 31904CDB2003EFFE00BA7402 /* MockNetworkRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNetworkRequest.swift; sourceTree = ""; }; + 31A1AD621F39E1E200BFE24B /* MockHttpClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockHttpClient.swift; sourceTree = ""; }; + 31ACC66D1FF566C400E8B24F /* KarhooTimingScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooTimingScheduler.swift; sourceTree = ""; }; + 31EC3F601F9E4C24008CD637 /* DeviceIdentifierProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceIdentifierProvider.swift; sourceTree = ""; }; + 31EC3F641F9E50AD008CD637 /* AnalyticsPayloadBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPayloadBuilder.swift; sourceTree = ""; }; + 31F916892023559700104777 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = ""; }; + 31FC37FE1F459A780012A57E /* DefaultAuthTokenSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultAuthTokenSpec.swift; sourceTree = ""; }; + 400555702350AEDC008AD78F /* TripFare.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TripFare.swift; sourceTree = ""; }; + 400BB94223F3027A00AEAAFD /* AuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + 400BB94523F3066500AEAAFD /* KarhooAuthService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAuthService.swift; sourceTree = ""; }; + 400BB94723F308BE00AEAAFD /* AuthLoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthLoginInteractor.swift; sourceTree = ""; }; + 400BB94A23F308F100AEAAFD /* KarhooAuthLoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAuthLoginInteractor.swift; sourceTree = ""; }; + 4032BD0D23FC47B600C2409B /* AuthUserInfo.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AuthUserInfo.json; sourceTree = ""; }; + 4032BD1123FC491500C2409B /* AuthExchangeToken.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = AuthExchangeToken.json; sourceTree = ""; }; + 4032BD1323FC5B2300C2409B /* KarhooAuthLoginInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAuthLoginInteractorSpec.swift; sourceTree = ""; }; + 404484E123FB00A900CD0347 /* KarhooAuthRevokeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAuthRevokeInteractor.swift; sourceTree = ""; }; + 4095037A234CCF5A00C6F7A5 /* FareMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FareMethodSpec.swift; sourceTree = ""; }; + 40F4058E2334C50000D384A6 /* LocationInfoSearch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationInfoSearch.swift; sourceTree = ""; }; + 40FBF606240EAFD9007F6A54 /* AuthHeaderKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthHeaderKeys.swift; sourceTree = ""; }; + 40FBF608240FBB2E007F6A54 /* AuthRequestsTesterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthRequestsTesterViewController.swift; sourceTree = ""; }; + 4310E4701E9928C800625A0E /* MockBroadcaster.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockBroadcaster.swift; sourceTree = ""; }; + 4310E47F1E99395D00625A0E /* AddressService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressService.swift; sourceTree = ""; }; + 4310E4801E99395D00625A0E /* AvailabilityService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = AvailabilityService.swift; sourceTree = ""; }; + 4310E48A1E99395D00625A0E /* Karhoo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Karhoo.swift; sourceTree = ""; }; + 4310E4B61E99395D00625A0E /* Broadcaster.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Broadcaster.swift; sourceTree = ""; }; + 4310E4B71E99395D00625A0E /* WeakReferenceWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakReferenceWrapper.swift; sourceTree = ""; }; + 4310E4E01E99397A00625A0E /* UserDataStoreSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserDataStoreSpec.swift; sourceTree = ""; }; + 4310E4E11E99397A00625A0E /* KarhooSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooSpec.swift; sourceTree = ""; }; + 4310E5031E99397A00625A0E /* BroadcasterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BroadcasterSpec.swift; sourceTree = ""; }; + 4310E5041E99397A00625A0E /* WeakReferenceWrapperSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakReferenceWrapperSpec.swift; sourceTree = ""; }; + 431740CD1ECB257A00CD217F /* KarhooUserServiceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooUserServiceSpec.swift; sourceTree = ""; }; + 4322B48B1E69A57B002108D5 /* MockAnalyticsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockAnalyticsService.swift; sourceTree = ""; }; + 43288A5E1EF3BEB10079506B /* DriverTrackingInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DriverTrackingInfo.swift; sourceTree = ""; }; + 432D47831ECC31940085DBBE /* KarhooLogoutInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooLogoutInteractorSpec.swift; sourceTree = ""; }; + 432D47891ECC80E10085DBBE /* Result.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + 43466AE71EDECEED0087BC51 /* Credentials.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Credentials.swift; sourceTree = ""; }; + 434B58421E434E5F009E8FB8 /* MockUserDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockUserDataStore.swift; sourceTree = ""; }; + 434D30A81EE150BA00CEE07B /* CredentiasParserSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentiasParserSpec.swift; sourceTree = ""; }; + 43584E6D1EE54D6400B4DFAB /* TripInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TripInfo.swift; sourceTree = ""; }; + 43584E791EE56EDC00B4DFAB /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 43584E861EE590DE00B4DFAB /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = ""; }; + 43584E881EE590FC00B4DFAB /* ContextSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextSpec.swift; sourceTree = ""; }; + 43584E8C1EE591C700B4DFAB /* ObjectTestFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectTestFactory.swift; sourceTree = ""; }; + 43584E8E1EE5960700B4DFAB /* TripService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TripService.swift; sourceTree = ""; }; + 43584E921EE5993700B4DFAB /* KarhooTripServiceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooTripServiceSpec.swift; sourceTree = ""; }; + 43584E9E1EE6A3D400B4DFAB /* MockUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = ""; }; + 43588C651EBB675100D03765 /* TestUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtil.swift; sourceTree = ""; }; + 4371C57B1EB0E7F70059429D /* KarhooAddressService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooAddressService.swift; sourceTree = ""; }; + 4371C5831EB0E7F70059429D /* AnalyticsConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalyticsConstants.swift; sourceTree = ""; }; + 4371C5841EB0E7F70059429D /* KarhooAnalyticsService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooAnalyticsService.swift; sourceTree = ""; }; + 4371C5861EB0E7F70059429D /* AnalyticsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalyticsProvider.swift; sourceTree = ""; }; + 4371C5871EB0E7F70059429D /* LogAnalyticsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogAnalyticsProvider.swift; sourceTree = ""; }; + 4371C5A91EB0E8070059429D /* KarhooAddressServiceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooAddressServiceSpec.swift; sourceTree = ""; }; + 4371C5AE1EB0E8070059429D /* AnalyticsServiceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnalyticsServiceSpec.swift; sourceTree = ""; }; + 4371C5B01EB0E8070059429D /* LogAnalyticsProviderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogAnalyticsProviderSpec.swift; sourceTree = ""; }; + 43850F3E1ECB003E009CB8FD /* UserService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = ""; }; + 43850F431ECB0056009CB8FD /* KarhooUserService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooUserService.swift; sourceTree = ""; }; + 4387A6411E3BAF6D0024FD55 /* KarhooSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KarhooSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4387A6441E3BAF6D0024FD55 /* KarhooSDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KarhooSDK.h; sourceTree = ""; }; + 4387A6451E3BAF6D0024FD55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4387A64A1E3BAF6D0024FD55 /* KarhooSDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = KarhooSDKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4387A6511E3BAF6D0024FD55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 43D251B51F45C76E005ECB85 /* ReachabilityWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityWrapper.swift; sourceTree = ""; }; + 43D251B71F46E29C005ECB85 /* ReachabilityWrapperSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReachabilityWrapperSpec.swift; sourceTree = ""; }; + 43D395C61ECF9ECD00499E8F /* PaymentService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentService.swift; sourceTree = ""; }; + 43D395C91ECF9F5B00499E8F /* KarhooPaymentService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPaymentService.swift; sourceTree = ""; }; + 43D395D41ECFA31900499E8F /* KarhooPaymentServiceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPaymentServiceSpec.swift; sourceTree = ""; }; + 43DD68F51ECD83F2005159C8 /* ResultSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResultSpec.swift; sourceTree = ""; }; + 43EF91671EE93DF6006ECB75 /* KarhooTripService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooTripService.swift; sourceTree = ""; }; + 43F939951F3C85A300914E18 /* KarhooURLSessionSender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooURLSessionSender.swift; sourceTree = ""; }; + 43F939961F3C85A300914E18 /* HttpClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpClient.swift; sourceTree = ""; }; + 43F939971F3C85A300914E18 /* HttpConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HttpConstants.swift; sourceTree = ""; }; + 43F939981F3C85A300914E18 /* Json.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Json.swift; sourceTree = ""; }; + 43F939991F3C85A300914E18 /* JsonHttpClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonHttpClient.swift; sourceTree = ""; }; + 43F9399A1F3C85A300914E18 /* JsonHttpRequestBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonHttpRequestBuilder.swift; sourceTree = ""; }; + 43F9399B1F3C85A300914E18 /* KarhooEnvironmentDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooEnvironmentDetails.swift; sourceTree = ""; }; + 43F9399C1F3C85A300914E18 /* URLSessionSender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionSender.swift; sourceTree = ""; }; + 43F939B01F3CD33200914E18 /* JsonHttpClientSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonHttpClientSpec.swift; sourceTree = ""; }; + 43F939B11F3CD33200914E18 /* JsonHttpRequestBuilderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JsonHttpRequestBuilderSpec.swift; sourceTree = ""; }; + 491F52E3C4EE22C2B1A9B930 /* Pods_Client.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Client.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5616C6061BCA5EB676D5A133 /* Pods-KarhooSDKIntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KarhooSDKIntegrationTests.debug.xcconfig"; path = "Target Support Files/Pods-KarhooSDKIntegrationTests/Pods-KarhooSDKIntegrationTests.debug.xcconfig"; sourceTree = ""; }; + 5C076FFE20F23B5F00A35537 /* CategoryQuoteMapperSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryQuoteMapperSpec.swift; sourceTree = ""; }; + 5C08DFB020E5A8D7002203CB /* MockContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockContext.swift; sourceTree = ""; }; + 5C08DFB320E5A92A002203CB /* MockAnalyticsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAnalyticsProvider.swift; sourceTree = ""; }; + 5C103FA12411C31F00F5F2F9 /* TokenExchangeSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenExchangeSettings.swift; sourceTree = ""; }; + 5C103FA32411DF7F00F5F2F9 /* GuestSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestSettings.swift; sourceTree = ""; }; + 5C15D5E9203711D100B47A71 /* BookingSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookingSearch.swift; sourceTree = ""; }; + 5C1E32A3211F015D00651BB9 /* KarhooVoid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooVoid.swift; sourceTree = ""; }; + 5C224EAE223DCA9900C1CFCE /* AddPaymentDetailsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddPaymentDetailsInteractor.swift; sourceTree = ""; }; + 5C224EB0223DCD2000C1CFCE /* KarhooAddPaymentDetailsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAddPaymentDetailsInteractor.swift; sourceTree = ""; }; + 5C224EB3223DD83000C1CFCE /* MockAddPaymentDetailsInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAddPaymentDetailsInteractor.swift; sourceTree = ""; }; + 5C224EB6223DDB8A00C1CFCE /* AddPaymentDetailsMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddPaymentDetailsMethodSpec.swift; sourceTree = ""; }; + 5C2856C620E3B127009B9464 /* LocationInfoInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationInfoInteractor.swift; sourceTree = ""; }; + 5C2856C820E3B127009B9464 /* PlaceSearchInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlaceSearchInteractor.swift; sourceTree = ""; }; + 5C2856C920E3B127009B9464 /* KarhooPlaceSearchInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPlaceSearchInteractor.swift; sourceTree = ""; }; + 5C2856CB20E3B127009B9464 /* KarhooReverseGeocodeInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooReverseGeocodeInteractor.swift; sourceTree = ""; }; + 5C2856CC20E3B127009B9464 /* ReverseGeocodeInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReverseGeocodeInteractor.swift; sourceTree = ""; }; + 5C2856DD20E3B183009B9464 /* KarhooReverseGeocodeProviderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooReverseGeocodeProviderSpec.swift; sourceTree = ""; }; + 5C2856F120E3B1DC009B9464 /* LocationInfoMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationInfoMock.swift; sourceTree = ""; }; + 5C5BE90C2411A513005DD14E /* AuthenticationMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationMethod.swift; sourceTree = ""; }; + 5C75D6562080E4E60065133B /* KarhooCodableModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooCodableModel.swift; sourceTree = ""; }; + 5C75D65C208CC84E0065133B /* LocationDetailsAddress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationDetailsAddress.swift; sourceTree = ""; }; + 5C870D2820CDBC9C0039A2BF /* LoginRequestPayloadMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRequestPayloadMock.swift; sourceTree = ""; }; + 5CA2CE412343510E000AEC1E /* OrganisationMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganisationMock.swift; sourceTree = ""; }; + 5CA2CE44234368B7000AEC1E /* UISettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISettings.swift; sourceTree = ""; }; + 6184F30666F41BD1192B66BC /* Pods-Client.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Client.debug.xcconfig"; path = "Target Support Files/Pods-Client/Pods-Client.debug.xcconfig"; sourceTree = ""; }; + 8604EAC56835802F4E0F0F56 /* Pods-KarhooSDKIntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KarhooSDKIntegrationTests.release.xcconfig"; path = "Target Support Files/Pods-KarhooSDKIntegrationTests/Pods-KarhooSDKIntegrationTests.release.xcconfig"; sourceTree = ""; }; + 90BAB2DA56DC0C76920A8C20 /* Pods-KarhooSDK.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KarhooSDK.debug.xcconfig"; path = "Target Support Files/Pods-KarhooSDK/Pods-KarhooSDK.debug.xcconfig"; sourceTree = ""; }; + B051EBD7153DFB0DE9C784A0 /* Pods-Client.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Client.release.xcconfig"; path = "Target Support Files/Pods-Client/Pods-Client.release.xcconfig"; sourceTree = ""; }; + B709CD873DABF2A4D66BCE0C /* Pods-KarhooSDKTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KarhooSDKTests.release.xcconfig"; path = "Target Support Files/Pods-KarhooSDKTests/Pods-KarhooSDKTests.release.xcconfig"; sourceTree = ""; }; + BAA0A0172A18CDF485FF7519 /* KarhooPaymentSDKTokenInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPaymentSDKTokenInteractor.swift; sourceTree = ""; }; + BAA0A069D5D958868761AAF7 /* Passengers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Passengers.swift; sourceTree = ""; }; + BAA0A0EB15002AB0D857748A /* AddPaymentMethodInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddPaymentMethodInteractor.swift; sourceTree = ""; }; + BAA0A1365EB1CC10CA0F94DE /* KarhooAddPaymentMethodInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooAddPaymentMethodInteractor.swift; sourceTree = ""; }; + BAA0A1DD7F2A93BCD18DD3FE /* PositionExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PositionExt.swift; sourceTree = ""; }; + BAA0A2139A85B9FF7F56BED7 /* PaymentSDKTokenMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentSDKTokenMock.swift; sourceTree = ""; }; + BAA0A2498417189117D6F286 /* TripLocationDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TripLocationDetails.swift; sourceTree = ""; }; + BAA0A263663CFFE1DE2F5E76 /* PoiDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PoiDetails.swift; sourceTree = ""; }; + BAA0A26ED6144AF54A930CFB /* PasswordResetRequestPayloadMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordResetRequestPayloadMock.swift; sourceTree = ""; }; + BAA0A2BC090EFF81F2F9B9AC /* TripBookingMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TripBookingMock.swift; sourceTree = ""; }; + BAA0A316925D2F14D6EEEAE6 /* QuoteList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteList.swift; sourceTree = ""; }; + BAA0A3313412237721C4C7B5 /* KarhooPaymentSDKTokenInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPaymentSDKTokenInteractorSpec.swift; sourceTree = ""; }; + BAA0A33AC9CF7967569962A2 /* KarhooRequestSender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooRequestSender.swift; sourceTree = ""; }; + BAA0A3541731789F935644A7 /* KarhooBookingInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooBookingInteractorSpec.swift; sourceTree = ""; }; + BAA0A3A0B72938FD19C2911A /* MeetingPoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingPoint.swift; sourceTree = ""; }; + BAA0A499BF991C5FC4735A7D /* UserLogin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserLogin.swift; sourceTree = ""; }; + BAA0A5929BD792349F51A637 /* MockTimingScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockTimingScheduler.swift; sourceTree = ""; }; + BAA0A5C6D495C7A6B997BB5E /* FareComponent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FareComponent.swift; sourceTree = ""; }; + BAA0A5FC9FBDD3F60C6FE30E /* MockPasswordResetInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPasswordResetInteractor.swift; sourceTree = ""; }; + BAA0A6F016D7940CF293B9BC /* MockPaymentSDKTokenInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPaymentSDKTokenInteractor.swift; sourceTree = ""; }; + BAA0A7C9A052655223E880D3 /* KarhooTripStatusInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooTripStatusInteractor.swift; sourceTree = ""; }; + BAA0A7CF7A18FDC822E15AFD /* BookingInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookingInteractor.swift; sourceTree = ""; }; + BAA0A819AC6AB43365A4C081 /* QuoteIdMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuoteIdMock.swift; sourceTree = ""; }; + BAA0A8260B3FAD817582FADE /* KarhooPasswordResetInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPasswordResetInteractorSpec.swift; sourceTree = ""; }; + BAA0A965007F063C8346E974 /* Availability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Availability.swift; sourceTree = ""; }; + BAA0AA1BB35C2D341BB66986 /* PassengerDetails.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PassengerDetails.swift; sourceTree = ""; }; + BAA0AA4DA19A464EF80F00F6 /* CategoriesMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoriesMock.swift; sourceTree = ""; }; + BAA0AAC1104384B3CDE0FED2 /* Places.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Places.swift; sourceTree = ""; }; + BAA0AACF77649D0AA514D127 /* UserInfoMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserInfoMock.swift; sourceTree = ""; }; + BAA0AAD70E6AE0FF8C70BEC1 /* KarhooPasswordResetInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooPasswordResetInteractor.swift; sourceTree = ""; }; + BAA0AB9B6A8BB01E3CF959C0 /* AuthToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthToken.swift; sourceTree = ""; }; + BAA0AC190CF1C7C0CBDC717E /* PaymentSDKTokenInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentSDKTokenInteractor.swift; sourceTree = ""; }; + BAA0AC8C40120E475F87ECB2 /* MockTripStatusInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockTripStatusInteractor.swift; sourceTree = ""; }; + BAA0ACCCE0E8BFB2852A3143 /* PoiDetailsType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PoiDetailsType.swift; sourceTree = ""; }; + BAA0AD752CF0001B84C65B6E /* PasswordResetRequestPayload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordResetRequestPayload.swift; sourceTree = ""; }; + BAA0ADA5AD439AA7E9B9ED6C /* TripStatusMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TripStatusMock.swift; sourceTree = ""; }; + BAA0ADCEB2BBB622745A315B /* PaymentSDKToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaymentSDKToken.swift; sourceTree = ""; }; + BAA0AE3182D554B65C66EC52 /* RequestSender.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestSender.swift; sourceTree = ""; }; + BAA0AE54DAE03275821D43A7 /* KarhooTripStatusInteractorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooTripStatusInteractorSpec.swift; sourceTree = ""; }; + BAA0AE7DDE5CEAF699394EB3 /* Price.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Price.swift; sourceTree = ""; }; + BAA0AED770E3100C742E4BE1 /* PoiType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PoiType.swift; sourceTree = ""; }; + BAA0AEEB35537AC7936CC8B7 /* TripStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TripStatus.swift; sourceTree = ""; }; + BAA0AEEDE0D01BA897FB6ECA /* FleetInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FleetInfo.swift; sourceTree = ""; }; + BAA0AF4654D1B9211454EEBD /* TripStatusInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TripStatusInteractor.swift; sourceTree = ""; }; + BAA0AFBC32DB1629C3BB1D01 /* KarhooBookingInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KarhooBookingInteractor.swift; sourceTree = ""; }; + CE2A6E7031AD7206EDBD999A /* Pods_KarhooSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KarhooSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D3843568322BB0FCF9D45FE9 /* Pods_KarhooSDKIntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KarhooSDKIntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DE6485A8A424D85E5FF8A2BB /* Pods_KarhooSDKTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_KarhooSDKTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FC0BF05A20E39D2D0004DF61 /* MockTripUpdateInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTripUpdateInteractor.swift; sourceTree = ""; }; + FC0BF09B20E3CB4F0004DF61 /* MockRefreshTokenRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRefreshTokenRequest.swift; sourceTree = ""; }; + FC1202442146A4F000161081 /* Empty.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Empty.json; sourceTree = ""; }; + FC1202462146A7CA00161081 /* Error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = Error.json; sourceTree = ""; }; + FC12024B214A840E00161081 /* LocationInfoMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationInfoMethodSpec.swift; sourceTree = ""; }; + FC12024E214A950B00161081 /* LocationInfo.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LocationInfo.json; sourceTree = ""; }; + FC120250214AB3C000161081 /* InvalidJson.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = InvalidJson.json; sourceTree = ""; }; + FC120252214BF82400161081 /* ReverseGeocodeMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReverseGeocodeMethodSpec.swift; sourceTree = ""; }; + FC120255214FF0AE00161081 /* RegisterMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterMethodSpec.swift; sourceTree = ""; }; + FC12025C215129DC00161081 /* TrackTripMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackTripMethodSpec.swift; sourceTree = ""; }; + FC1202602152567C00161081 /* BookTripMethodSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookTripMethodSpec.swift; sourceTree = ""; }; + FC1B01EC2209D0D20069D7F3 /* QuoteCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteCategory.swift; sourceTree = ""; }; + FC2D95712113612300201BDB /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; + FC2EE933212425F6001B36D2 /* SDKErrorFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKErrorFactory.swift; sourceTree = ""; }; + FC321EDF20F8D54E00B2C3CD /* DriverTrackingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DriverTrackingService.swift; sourceTree = ""; }; + FC321EE120F8D56400B2C3CD /* KarhooDriverTrackingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooDriverTrackingService.swift; sourceTree = ""; }; + FC321EE320F8D72C00B2C3CD /* KarhooDriverTrackingServiceSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooDriverTrackingServiceSpec.swift; sourceTree = ""; }; + FC57F942212EEC56009193CB /* TripUpdateInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripUpdateInteractor.swift; sourceTree = ""; }; + FC57F944213015E7009193CB /* PollCallFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollCallFactory.swift; sourceTree = ""; }; + FC57F94721301637009193CB /* MockPollCallFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPollCallFactory.swift; sourceTree = ""; }; + FC57F94921301759009193CB /* MockPollCall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPollCall.swift; sourceTree = ""; }; + FC861B2B21107727004CC47A /* KarhooError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooError.swift; sourceTree = ""; }; + FC861B2D21107734004CC47A /* KarhooSDKError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooSDKError.swift; sourceTree = ""; }; + FC861B5D2110B7C0004CC47A /* HTTPError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPError.swift; sourceTree = ""; }; + FC8EB5C8216507B7009DC8DE /* TripQuote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripQuote.swift; sourceTree = ""; }; + FC8EB5CA21651AA0009DC8DE /* VehicleAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleAttributes.swift; sourceTree = ""; }; + FC8F474E2154F9D6007841FB /* ObserverBroadcaster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObserverBroadcaster.swift; sourceTree = ""; }; + FC8F475021553184007841FB /* Observer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Observer.swift; sourceTree = ""; }; + FC8F475221555025007841FB /* MockObserverBroadcaster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockObserverBroadcaster.swift; sourceTree = ""; }; + FC987C7C20EF7949001B8B79 /* CLLocationExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLLocationExt.swift; sourceTree = ""; }; + FCBD67B2211C9CBD005AA332 /* LoginInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInteractor.swift; sourceTree = ""; }; + FCBD67B4211C9CE6005AA332 /* PasswordResetInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordResetInteractor.swift; sourceTree = ""; }; + FCBD67B6211C9D7E005AA332 /* RegisterInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterInteractor.swift; sourceTree = ""; }; + FCBD67B8211DDD8E005AA332 /* MockInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockInteractor.swift; sourceTree = ""; }; + FCC17D782118984B00A25B87 /* KarhooPollCallFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooPollCallFactory.swift; sourceTree = ""; }; + FCD32FE820DA9C4F0044BB64 /* TripSearchInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripSearchInteractor.swift; sourceTree = ""; }; + FCD32FEA20DA9C5F0044BB64 /* KarhooTripSearchInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooTripSearchInteractor.swift; sourceTree = ""; }; + FCD32FEC20DAA1470044BB64 /* TripsRequestPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TripsRequestPayload.swift; sourceTree = ""; }; + FCD32FEE20DAA33A0044BB64 /* KarhooTripsListInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooTripsListInteractorSpec.swift; sourceTree = ""; }; + FCD32FF620E252680044BB64 /* KarhooTripUpdateInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooTripUpdateInteractor.swift; sourceTree = ""; }; + FCD32FF920E26DC10044BB64 /* KarhooTripUpdateInteractortSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooTripUpdateInteractortSpec.swift; sourceTree = ""; }; + FCD4DA5620F37FB2007D4374 /* AvailabilitySearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvailabilitySearch.swift; sourceTree = ""; }; + FCD4DA5920F39AD0007D4374 /* MockAvailabilityInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAvailabilityInteractor.swift; sourceTree = ""; }; + FCD4DA5B20F3A5C5007D4374 /* KarhooAvailabilityInteractorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KarhooAvailabilityInteractorSpec.swift; sourceTree = ""; }; + FCE11B3720F6272400DFC641 /* AvailabilitySearchMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvailabilitySearchMock.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 09A9B2882405901900823FB0 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 09A9B2A42405916400823FB0 /* KarhooSDK.framework in Frameworks */, + AE5058823A162206E237F5FA /* Pods_Client.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 09B204A921412C3100063632 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 09B204B121412C3200063632 /* KarhooSDK.framework in Frameworks */, + B2B1738EC35E4790DEAAA7EC /* Pods_KarhooSDKIntegrationTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4387A63D1E3BAF6D0024FD55 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0907B98A20D7B794008915B1 /* Reachability.framework in Frameworks */, + 8AF5210BF925EC94C12D5D34 /* Pods_KarhooSDK.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4387A6471E3BAF6D0024FD55 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4387A67F1E3BB6070024FD55 /* KarhooSDK.framework in Frameworks */, + 4231E9D1B9C7769699C2EECC /* Pods_KarhooSDKTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 090E0163223FAB56005D1CB7 /* AddPaymentDetailsSpec */ = { + isa = PBXGroup; + children = ( + 090E0164223FAB6E005D1CB7 /* KarhooAddPaymentDetailsSpec.swift */, + ); + path = AddPaymentDetailsSpec; + sourceTree = ""; + }; + 094B449F2285B223002D2506 /* AddPaymentDetails */ = { + isa = PBXGroup; + children = ( + 094B44B32285B8DE002D2506 /* AddPaymentDetails.json */, + ); + path = AddPaymentDetails; + sourceTree = ""; + }; + 094B44B22285B8C8002D2506 /* PaymentSDKToken */ = { + isa = PBXGroup; + children = ( + 09F8ABFC214FB61100E293B7 /* PaymentSDKToken.json */, + ); + path = PaymentSDKToken; + sourceTree = ""; + }; + 095A03B723339708007D805E /* Config */ = { + isa = PBXGroup; + children = ( + 095A03C52333B107007D805E /* Provider */, + 095A03B823339721007D805E /* ConfigService.swift */, + 095A03BA2333ADC2007D805E /* KarhooConfigService.swift */, + 095A03BC2333AF21007D805E /* UIConfigInteractor */, + ); + path = Config; + sourceTree = ""; + }; + 095A03BC2333AF21007D805E /* UIConfigInteractor */ = { + isa = PBXGroup; + children = ( + 095A03BD2333AF4A007D805E /* UIConfigInteractor.swift */, + 095A03BF2333AF64007D805E /* KarhooUIConfigInteractor.swift */, + ); + path = UIConfigInteractor; + sourceTree = ""; + }; + 095A03C52333B107007D805E /* Provider */ = { + isa = PBXGroup; + children = ( + 095A03C62333B115007D805E /* UIConfigProvider.swift */, + 095A03CF2333BABD007D805E /* KarhooUIConfigProvider.swift */, + 5CA2CE44234368B7000AEC1E /* UISettings.swift */, + ); + path = Provider; + sourceTree = ""; + }; + 095A03C92333B3CF007D805E /* Config */ = { + isa = PBXGroup; + children = ( + 095A03CE2333BA58007D805E /* Provider */, + 095A03CA2333B3E6007D805E /* KarhooConfigServiceSpec.swift */, + 095A03CC2333B3F8007D805E /* KarhooUIConfigInteractorSpec.swift */, + ); + path = Config; + sourceTree = ""; + }; + 095A03CE2333BA58007D805E /* Provider */ = { + isa = PBXGroup; + children = ( + 095A03D12333C119007D805E /* KarhooUIConfigProviderSpec.swift */, + ); + path = Provider; + sourceTree = ""; + }; + 095A03D32333C1A9007D805E /* Config */ = { + isa = PBXGroup; + children = ( + 095A03D42333C235007D805E /* MockUIConfigProvider.swift */, + 095A03DF2333DFCB007D805E /* MockUIConfigInteractor.swift */, + ); + path = Config; + sourceTree = ""; + }; + 095A03DC2333D8FC007D805E /* Config */ = { + isa = PBXGroup; + children = ( + 095A03DD2333D926007D805E /* UIConfigMethodSpec.swift */, + ); + path = Config; + sourceTree = ""; + }; + 096B31BD20B702D7002B41E9 /* TripSearch */ = { + isa = PBXGroup; + children = ( + 096B31BF20B702E8002B41E9 /* MockTripSearchInteractor.swift */, + ); + path = TripSearch; + sourceTree = ""; + }; + 0970BA6921136F4D0015A170 /* Observable */ = { + isa = PBXGroup; + children = ( + 0970BA6A21136F870015A170 /* Executable.swift */, + 0970BA6D21136F870015A170 /* KarhooCall.swift */, + 0970BA6E21136F870015A170 /* KarhooObservable.swift */, + 0970BA6C21136F870015A170 /* KarhooPollCall.swift */, + FC57F944213015E7009193CB /* PollCallFactory.swift */, + FCC17D782118984B00A25B87 /* KarhooPollCallFactory.swift */, + 0970BA6B21136F870015A170 /* KarhooPollableExecutor.swift */, + FC8F475021553184007841FB /* Observer.swift */, + FC8F474E2154F9D6007841FB /* ObserverBroadcaster.swift */, + ); + path = Observable; + sourceTree = ""; + }; + 0970BA76211371480015A170 /* Interactor */ = { + isa = PBXGroup; + children = ( + 0970BA77211371600015A170 /* KarhooDriverTrackingInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 0970BA7921145CE10015A170 /* Observable */ = { + isa = PBXGroup; + children = ( + 0970BA7A21145E550015A170 /* KarhooCallSpec.swift */, + 0970BA892114970A0015A170 /* KarhooPollCallSpec.swift */, + 0970BA7F2114671A0015A170 /* KarhooObservableSpec.swift */, + 0970BA8B21149FC60015A170 /* KarhooPollableExecutorSpec.swift */, + ); + path = Observable; + sourceTree = ""; + }; + 0970BA7C211464600015A170 /* Observable */ = { + isa = PBXGroup; + children = ( + 0970BA8721146DF70015A170 /* MockPollExecutor.swift */, + 0970BA7D211464C40015A170 /* MockExecutable.swift */, + 0970BA812114685B0015A170 /* MockKarhooCodableModel.swift */, + FCBD67B8211DDD8E005AA332 /* MockInteractor.swift */, + FC57F94721301637009193CB /* MockPollCallFactory.swift */, + FC57F94921301759009193CB /* MockPollCall.swift */, + FC8F475221555025007841FB /* MockObserverBroadcaster.swift */, + ); + path = Observable; + sourceTree = ""; + }; + 09811A1B20D7C119001DB06F /* CancelTrip */ = { + isa = PBXGroup; + children = ( + 09811A1D20D7C12D001DB06F /* MockCancelTripInteractor.swift */, + ); + path = CancelTrip; + sourceTree = ""; + }; + 0981F6C520CEC26000BF7FB5 /* Networking */ = { + isa = PBXGroup; + children = ( + 0981F6C620CEC27A00BF7FB5 /* MockRequestSender.swift */, + 09430E4C22410037009846A6 /* MockSDKConfig.swift */, + 31A1AD621F39E1E200BFE24B /* MockHttpClient.swift */, + 091CAE3D20D7A81F005F1DB6 /* MockURLSessionSender.swift */, + FC0BF09B20E3CB4F0004DF61 /* MockRefreshTokenRequest.swift */, + 31904CDB2003EFFE00BA7402 /* MockNetworkRequest.swift */, + 094C1F7E20B2C956001FFE7F /* MockNetworkDateFormatter.swift */, + 31904CD9200380A400BA7402 /* MockAccessTokenProvider.swift */, + ); + path = Networking; + sourceTree = ""; + }; + 0981F6D320CFE1A600BF7FB5 /* Login */ = { + isa = PBXGroup; + children = ( + 0981F6D420CFE1B300BF7FB5 /* MockLoginInteractor.swift */, + ); + path = Login; + sourceTree = ""; + }; + 0981F6D820D0317A00BF7FB5 /* Login */ = { + isa = PBXGroup; + children = ( + 0981F6CA20CEE1AD00BF7FB5 /* KarhooLoginInteractorSpec.swift */, + ); + name = Login; + path = "New Group"; + sourceTree = ""; + }; + 09820E6B20D7EA6600782530 /* Logout */ = { + isa = PBXGroup; + children = ( + 432D47831ECC31940085DBBE /* KarhooLogoutInteractorSpec.swift */, + ); + path = Logout; + sourceTree = ""; + }; + 09820E6C20D7EAF200782530 /* Register */ = { + isa = PBXGroup; + children = ( + 09820E6F20D7F07800782530 /* KarhooRegisterInteractorSpec.swift */, + ); + path = Register; + sourceTree = ""; + }; + 09820E7520D811B200782530 /* Signup */ = { + isa = PBXGroup; + children = ( + 09820E7320D809E000782530 /* MockRegisterInteractor.swift */, + ); + path = Signup; + sourceTree = ""; + }; + 09820E7620D8172000782530 /* Logout */ = { + isa = PBXGroup; + children = ( + 09820E7720D8172D00782530 /* MockLogoutInteractor.swift */, + ); + path = Logout; + sourceTree = ""; + }; + 098C91F22147C4C900F02DE3 /* Payment */ = { + isa = PBXGroup; + children = ( + 094B449F2285B223002D2506 /* AddPaymentDetails */, + 094B44B22285B8C8002D2506 /* PaymentSDKToken */, + 09F79E49223ABD3600D5B0B8 /* GetNonce */, + ); + path = Payment; + sourceTree = ""; + }; + 098C91F52147CAFE00F02DE3 /* Payment */ = { + isa = PBXGroup; + children = ( + 5C224EB5223DDB6D00C1CFCE /* AddPaymentDetails */, + 09F79E42223A99BD00D5B0B8 /* GetNonceMethod */, + 09F8ABF9214FB4A100E293B7 /* PaymentSDKToken */, + ); + path = Payment; + sourceTree = ""; + }; + 098C91F72147CDB200F02DE3 /* Error */ = { + isa = PBXGroup; + children = ( + 098C91F82147CDC500F02DE3 /* GeneralErrorSpec.swift */, + ); + path = Error; + sourceTree = ""; + }; + 098D524B23FB14CC00059086 /* Auth */ = { + isa = PBXGroup; + children = ( + 098D524E23FB14FA00059086 /* AuthLoginMethodSpec.swift */, + 099613702408359A00C8C88E /* RevokeMethodSpec.swift */, + ); + path = Auth; + sourceTree = ""; + }; + 098D525023FB156600059086 /* Auth */ = { + isa = PBXGroup; + children = ( + 098D525123FB157B00059086 /* KarhooAuthRevokeInteractorSpec.swift */, + 4032BD1323FC5B2300C2409B /* KarhooAuthLoginInteractorSpec.swift */, + ); + path = Auth; + sourceTree = ""; + }; + 09928D0D20EFAABA00B9F462 /* Quote */ = { + isa = PBXGroup; + children = ( + 09928D1D20EFAABA00B9F462 /* QuoteService.swift */, + 09928D1E20EFAABA00B9F462 /* KarhooQuoteService.swift */, + 09928D1F20EFAABA00B9F462 /* Interactor */, + 09928D1820EFAABA00B9F462 /* CategoryQuoteMapper.swift */, + ); + path = Quote; + sourceTree = ""; + }; + 09928D1F20EFAABA00B9F462 /* Interactor */ = { + isa = PBXGroup; + children = ( + 09928D2020EFAABA00B9F462 /* KarhooQuoteInteractor.swift */, + 09928D2120EFAABA00B9F462 /* QuoteInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 09928D2D20EFAAEE00B9F462 /* Quote */ = { + isa = PBXGroup; + children = ( + 09928D3220EFAAEE00B9F462 /* KarhooQuoteServiceSpec.swift */, + 5C076FFE20F23B5F00A35537 /* CategoryQuoteMapperSpec.swift */, + 09928D3320EFAAEE00B9F462 /* Interactor */, + ); + path = Quote; + sourceTree = ""; + }; + 09928D3320EFAAEE00B9F462 /* Interactor */ = { + isa = PBXGroup; + children = ( + 09928D3420EFAAEE00B9F462 /* KarhooQuoteInteractorSpec.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 09928D3920EFAB1300B9F462 /* Quote */ = { + isa = PBXGroup; + children = ( + 09928D4120EFAB1300B9F462 /* MockQuoteInteractor.swift */, + ); + path = Quote; + sourceTree = ""; + }; + 09A9B28C2405901900823FB0 /* Client */ = { + isa = PBXGroup; + children = ( + 09A9B28D2405901900823FB0 /* AppDelegate.swift */, + 09A9B28F2405901900823FB0 /* SceneDelegate.swift */, + 09A9B2912405901900823FB0 /* ViewController.swift */, + 40FBF608240FBB2E007F6A54 /* AuthRequestsTesterViewController.swift */, + 09A9B2962405901B00823FB0 /* Assets.xcassets */, + 09A9B2982405901B00823FB0 /* LaunchScreen.storyboard */, + 09A9B29B2405901B00823FB0 /* Info.plist */, + ); + path = Client; + sourceTree = ""; + }; + 09B204AD21412C3100063632 /* KarhooSDKIntegrationTests */ = { + isa = PBXGroup; + children = ( + FC1202412146A0B000161081 /* Utils */, + 098C91F72147CDB200F02DE3 /* Error */, + FC1202402146A03E00161081 /* Service */, + 09B204B7214135C000063632 /* JSON */, + 09B204B021412C3200063632 /* Info.plist */, + 09430E4A2240054A009846A6 /* IntegrationTestSetup.swift */, + ); + path = KarhooSDKIntegrationTests; + sourceTree = ""; + }; + 09B204B7214135C000063632 /* JSON */ = { + isa = PBXGroup; + children = ( + 4032BD0A23FC47B600C2409B /* Auth */, + 237E0C5F2347851500798AA0 /* Fare */, + 09EE81EC215283BD004B2B09 /* Quote */, + 09F90F9521510E70003BC867 /* Trip */, + 09F8AC01214FE4CF00E293B7 /* User */, + 09F8ABE7214A9AF200E293B7 /* DriverTracking */, + FC12024D214A94E300161081 /* AddressService */, + 098C91F22147C4C900F02DE3 /* Payment */, + 09B204BB2141366C00063632 /* InvalidData.json */, + FC1202442146A4F000161081 /* Empty.json */, + FC1202462146A7CA00161081 /* Error.json */, + FC120250214AB3C000161081 /* InvalidJson.json */, + ); + path = JSON; + sourceTree = ""; + }; + 09C1A232210725E2004B6406 /* Response */ = { + isa = PBXGroup; + children = ( + BAA0AB9B6A8BB01E3CF959C0 /* AuthToken.swift */, + BAA0A965007F063C8346E974 /* Availability.swift */, + 5C15D5E9203711D100B47A71 /* BookingSearch.swift */, + 27421E2976A85D5A0FB95991 /* Categories.swift */, + 313DC2D020221A0800D9DD1B /* Driver.swift */, + 43288A5E1EF3BEB10079506B /* DriverTrackingInfo.swift */, + 09CA785720EE5ABC00D3BB6F /* Fare.swift */, + BAA0A5C6D495C7A6B997BB5E /* FareComponent.swift */, + BAA0AEEDE0D01BA897FB6ECA /* FleetInfo.swift */, + 5C75D65C208CC84E0065133B /* LocationDetailsAddress.swift */, + 091A2D0D207D23F600F06E28 /* LocationInfo.swift */, + BAA0A3A0B72938FD19C2911A /* MeetingPoint.swift */, + 09A56DFB20922D5C00891F02 /* MeetingPointType.swift */, + 09F79E33223952F900D5B0B8 /* Nonce.swift */, + 099202A620DBB1D20009C845 /* Organisation.swift */, + BAA0AA1BB35C2D341BB66986 /* PassengerDetails.swift */, + BAA0A069D5D958868761AAF7 /* Passengers.swift */, + BAA0ADCEB2BBB622745A315B /* PaymentSDKToken.swift */, + 092F27C622B1353D00AF8E0E /* PickUpType.swift */, + 313DC2E320221E7B00D9DD1B /* Place.swift */, + BAA0AAC1104384B3CDE0FED2 /* Places.swift */, + BAA0A263663CFFE1DE2F5E76 /* PoiDetails.swift */, + BAA0ACCCE0E8BFB2852A3143 /* PoiDetailsType.swift */, + BAA0AED770E3100C742E4BE1 /* PoiType.swift */, + 091BE6EB2086562900BA34D0 /* Position.swift */, + 313DC2F120221FDB00D9DD1B /* Quote.swift */, + FC1B01EC2209D0D20069D7F3 /* QuoteCategory.swift */, + BAA0A316925D2F14D6EEEAE6 /* QuoteList.swift */, + 274217092FA03DAFAD7776E1 /* QuoteListId.swift */, + 09928D4820EFAB9300B9F462 /* Quotes.swift */, + 09D293C122C530170051C455 /* QuoteSource.swift */, + 313DC2EF20221FCE00D9DD1B /* QuoteType.swift */, + 09AF9AA820D174150046D725 /* RefreshToken.swift */, + 400555702350AEDC008AD78F /* TripFare.swift */, + 43584E6D1EE54D6400B4DFAB /* TripInfo.swift */, + BAA0A2498417189117D6F286 /* TripLocationDetails.swift */, + FC8EB5C8216507B7009DC8DE /* TripQuote.swift */, + 099AF2F820F8BA0000A2AA57 /* TripState.swift */, + BAA0AEEB35537AC7936CC8B7 /* TripStatus.swift */, + 095A03C12333AF98007D805E /* UIConfig.swift */, + 27421AB287B1A33F00AC4119 /* UserInfo.swift */, + 313DC2DC20221D0100D9DD1B /* Vehicle.swift */, + FC8EB5CA21651AA0009DC8DE /* VehicleAttributes.swift */, + ); + path = Response; + sourceTree = ""; + }; + 09C1A23321072628004B6406 /* Util */ = { + isa = PBXGroup; + children = ( + 3150CE2A1F9F7B6000DE6D62 /* UserLocation */, + 4310E4B51E99395D00625A0E /* Broadcaster */, + 312CDFB41F6A981F006F48DD /* AppStateNotifier */, + BAA0A29CABA7ACA838A7B9D0 /* Extention */, + 317610EB1FD84B1E00D2DB75 /* KarhooNetworkDateFormatter.swift */, + 095E406C20B32DAE008EAF0F /* NetworkDateFormatter.swift */, + 43D251B51F45C76E005ECB85 /* ReachabilityWrapper.swift */, + 432D47891ECC80E10085DBBE /* Result.swift */, + 31ACC66D1FF566C400E8B24F /* KarhooTimingScheduler.swift */, + 4310E4B71E99395D00625A0E /* WeakReferenceWrapper.swift */, + 312909E11FA0E0C900F5147D /* TimestampFormatter.swift */, + 312B1AAB1FA097F900BB27CF /* BatteryMonitor.swift */, + 31EC3F601F9E4C24008CD637 /* DeviceIdentifierProvider.swift */, + 312B1AAD1FA0982800BB27CF /* NetworkConnectionTypeProvider.swift */, + ); + path = Util; + sourceTree = ""; + }; + 09C1A23421072A9D004B6406 /* Common */ = { + isa = PBXGroup; + children = ( + FC4B92E5222D3F7700FE33B4 /* Environment */, + BAA0AEB5E96A9CD5B7615E83 /* RequestSender */, + 5C75D6562080E4E60065133B /* KarhooCodableModel.swift */, + 5C1E32A3211F015D00651BB9 /* KarhooVoid.swift */, + 09C1A23521072AB1004B6406 /* Error */, + ); + path = Common; + sourceTree = ""; + }; + 09C1A23521072AB1004B6406 /* Error */ = { + isa = PBXGroup; + children = ( + FC861B2B21107727004CC47A /* KarhooError.swift */, + 0947FD3A2121D51900E8BE4A /* KarhooErrorType.swift */, + FC861B2D21107734004CC47A /* KarhooSDKError.swift */, + FC861B5D2110B7C0004CC47A /* HTTPError.swift */, + 43584E791EE56EDC00B4DFAB /* Errors.swift */, + FC2EE933212425F6001B36D2 /* SDKErrorFactory.swift */, + ); + path = Error; + sourceTree = ""; + }; + 09C1A23621072DCD004B6406 /* Interactor */ = { + isa = PBXGroup; + children = ( + 31904CD31FFFC04400BA7402 /* KarhooRefreshTokenInteractor.swift */, + 0900D34820D2B17B0082D1E8 /* RefreshTokenInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + 09C1A23721072DE1004B6406 /* JsonHttpClient */ = { + isa = PBXGroup; + children = ( + 43F939991F3C85A300914E18 /* JsonHttpClient.swift */, + 43F939981F3C85A300914E18 /* Json.swift */, + 43F9399A1F3C85A300914E18 /* JsonHttpRequestBuilder.swift */, + ); + path = JsonHttpClient; + sourceTree = ""; + }; + 09C1A2382107367C004B6406 /* PaymentSDKTokenInteractor */ = { + isa = PBXGroup; + children = ( + BAA0AC190CF1C7C0CBDC717E /* PaymentSDKTokenInteractor.swift */, + BAA0A0172A18CDF485FF7519 /* KarhooPaymentSDKTokenInteractor.swift */, + ); + path = PaymentSDKTokenInteractor; + sourceTree = ""; + }; + 09C1A23B2107527A004B6406 /* Parser */ = { + isa = PBXGroup; + children = ( + 2742136485E56D7E2AEF11AB /* CredentialParser.swift */, + ); + path = Parser; + sourceTree = ""; + }; + 09C1A23E2107598B004B6406 /* Extension */ = { + isa = PBXGroup; + children = ( + 096A2C2C208F676000C7B7D0 /* PositionExtSpec.swift */, + ); + path = Extension; + sourceTree = ""; + }; + 09C1A23F21075A7E004B6406 /* Parser */ = { + isa = PBXGroup; + children = ( + 434D30A81EE150BA00CEE07B /* CredentiasParserSpec.swift */, + ); + path = Parser; + sourceTree = ""; + }; + 09CA785420EE59B200D3BB6F /* Recovered References */ = { + isa = PBXGroup; + children = ( + BAA0AE7DDE5CEAF699394EB3 /* Price.swift */, + ); + name = "Recovered References"; + sourceTree = ""; + }; + 09EB3DFF2108C3A4004571DF /* HttpClient */ = { + isa = PBXGroup; + children = ( + 43F939B11F3CD33200914E18 /* JsonHttpRequestBuilderSpec.swift */, + 31904CD72003800000BA7402 /* TokenRefreshingHttpClientSpec.swift */, + 43F939B01F3CD33200914E18 /* JsonHttpClientSpec.swift */, + ); + path = HttpClient; + sourceTree = ""; + }; + 09EB3E002108C3C5004571DF /* TokenRefresh */ = { + isa = PBXGroup; + children = ( + 317210D01FFE86B4004CE129 /* KarhooRefreshTokenInteractorSpec.swift */, + ); + path = TokenRefresh; + sourceTree = ""; + }; + 09EB3E012108C3DD004571DF /* Util */ = { + isa = PBXGroup; + children = ( + 4310E4701E9928C800625A0E /* MockBroadcaster.swift */, + 3150CE301F9F7D8E00DE6D62 /* MockCLLocationManager.swift */, + 317210C11FFD2476004CE129 /* MockAppStateNotifier.swift */, + BAA0A5929BD792349F51A637 /* MockTimingScheduler.swift */, + ); + path = Util; + sourceTree = ""; + }; + 09EE81E82152615D004B2B09 /* Quote */ = { + isa = PBXGroup; + children = ( + 09EE81E92152617B004B2B09 /* QuoteSearchMethodSpec.swift */, + ); + path = Quote; + sourceTree = ""; + }; + 09EE81EC215283BD004B2B09 /* Quote */ = { + isa = PBXGroup; + children = ( + 09EE81EE21528791004B2B09 /* Availability.json */, + 09EE81ED21528791004B2B09 /* QuoteListId.json */, + 09EE81EF21528791004B2B09 /* Quotes.json */, + ); + path = Quote; + sourceTree = ""; + }; + 09F79E322239520D00D5B0B8 /* GetNonceInteractor */ = { + isa = PBXGroup; + children = ( + 09F79E352239546E00D5B0B8 /* GetNonceInteractor.swift */, + 09F79E372239547A00D5B0B8 /* KarhooGetNonceInteractor.swift */, + ); + path = GetNonceInteractor; + sourceTree = ""; + }; + 09F79E3D2239720E00D5B0B8 /* GetNonceMethod */ = { + isa = PBXGroup; + children = ( + 09F79E3E2239723200D5B0B8 /* KarhooGetNonceInteractorSpec.swift */, + ); + path = GetNonceMethod; + sourceTree = ""; + }; + 09F79E42223A99BD00D5B0B8 /* GetNonceMethod */ = { + isa = PBXGroup; + children = ( + 09F79E43223A99E700D5B0B8 /* GetNonceMethodSpec.swift */, + ); + path = GetNonceMethod; + sourceTree = ""; + }; + 09F79E45223AAA2D00D5B0B8 /* GetNonce */ = { + isa = PBXGroup; + children = ( + 09F79E46223AAA6600D5B0B8 /* MockGetNonceInteractor.swift */, + ); + path = GetNonce; + sourceTree = ""; + }; + 09F79E49223ABD3600D5B0B8 /* GetNonce */ = { + isa = PBXGroup; + children = ( + 09F79E4A223ABD5A00D5B0B8 /* GetNonce.json */, + ); + path = GetNonce; + sourceTree = ""; + }; + 09F8ABE7214A9AF200E293B7 /* DriverTracking */ = { + isa = PBXGroup; + children = ( + 09F8ABE8214A9B1500E293B7 /* DriverTracking.json */, + ); + path = DriverTracking; + sourceTree = ""; + }; + 09F8ABEA214A9B4100E293B7 /* DriverTracking */ = { + isa = PBXGroup; + children = ( + 09F8ABEB214A9B5700E293B7 /* DriverTrackingMethodSpec.swift */, + ); + path = DriverTracking; + sourceTree = ""; + }; + 09F8ABF9214FB4A100E293B7 /* PaymentSDKToken */ = { + isa = PBXGroup; + children = ( + 09F8ABFA214FB4C900E293B7 /* PaymentSDKTokenMethod.swift */, + ); + path = PaymentSDKToken; + sourceTree = ""; + }; + 09F8ABFE214FDF3200E293B7 /* User */ = { + isa = PBXGroup; + children = ( + 09F8ABFF214FDF4200E293B7 /* LoginMethodSpec.swift */, + FC120255214FF0AE00161081 /* RegisterMethodSpec.swift */, + 09F90F902150FA2C003BC867 /* PasswordResetMethodSpec.swift */, + ); + path = User; + sourceTree = ""; + }; + 09F8AC01214FE4CF00E293B7 /* User */ = { + isa = PBXGroup; + children = ( + 09F8AC04214FE6DF00E293B7 /* AuthorisedUserInfo.json */, + 09F8AC03214FE6DF00E293B7 /* AuthToken.json */, + 09F8AC02214FE6DF00E293B7 /* UnauthorisedUserInfo.json */, + ); + path = User; + sourceTree = ""; + }; + 09F90F922151006E003BC867 /* Trip */ = { + isa = PBXGroup; + children = ( + 09F90F932151008C003BC867 /* CancelTripMethodSpec.swift */, + 097D472221511208008D8D94 /* TripStatusMethodSpec.swift */, + 097D472621512D5E008D8D94 /* TripSearchMethodSpec.swift */, + FC12025C215129DC00161081 /* TrackTripMethodSpec.swift */, + FC1202602152567C00161081 /* BookTripMethodSpec.swift */, + ); + path = Trip; + sourceTree = ""; + }; + 09F90F9521510E70003BC867 /* Trip */ = { + isa = PBXGroup; + children = ( + 097D4720215111ED008D8D94 /* TripInfo.json */, + 097D4724215128E0008D8D94 /* TripStatus.json */, + 09EE81E62152585D004B2B09 /* TripSearch.json */, + ); + path = Trip; + sourceTree = ""; + }; + 18DBF3907EAF3E4DE145B9C0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 0907B98920D7B794008915B1 /* Reachability.framework */, + CE2A6E7031AD7206EDBD999A /* Pods_KarhooSDK.framework */, + D3843568322BB0FCF9D45FE9 /* Pods_KarhooSDKIntegrationTests.framework */, + DE6485A8A424D85E5FF8A2BB /* Pods_KarhooSDKTests.framework */, + 491F52E3C4EE22C2B1A9B930 /* Pods_Client.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 233DB46523433EC200BCBC3F /* UpdateUserDetails */ = { + isa = PBXGroup; + children = ( + 233DB46623433EC200BCBC3F /* MockUpdateUserDetailsInteractor.swift */, + ); + path = UpdateUserDetails; + sourceTree = ""; + }; + 2359B459234251FA008433A1 /* Update */ = { + isa = PBXGroup; + children = ( + 2359B45A234251FA008433A1 /* KarhooUpdateUserDetailsInteractorSpec.swift */, + ); + path = Update; + sourceTree = ""; + }; + 2359B45C2342522E008433A1 /* UpdateUserDetailsInteractor */ = { + isa = PBXGroup; + children = ( + 2359B45D2342522E008433A1 /* KarhooUpdateUserDetailsInteractor.swift */, + 2359B45E2342522E008433A1 /* UpdateUserDetailsInteractor.swift */, + ); + name = UpdateUserDetailsInteractor; + path = KarhooSDK/Service/User/UpdateUserDetailsInteractor; + sourceTree = SOURCE_ROOT; + }; + 237E0C552347821F00798AA0 /* Fares */ = { + isa = PBXGroup; + children = ( + 237E0C5A2347837400798AA0 /* FaresInteractor */, + 237E0C562347823A00798AA0 /* FareService.swift */, + 237E0C58234782B400798AA0 /* KarhooFareService.swift */, + ); + path = Fares; + sourceTree = ""; + }; + 237E0C5A2347837400798AA0 /* FaresInteractor */ = { + isa = PBXGroup; + children = ( + 237E0C5B2347838600798AA0 /* FareInteractor.swift */, + 237E0C5D234783BE00798AA0 /* KarhooFareInteractor.swift */, + ); + path = FaresInteractor; + sourceTree = ""; + }; + 237E0C5F2347851500798AA0 /* Fare */ = { + isa = PBXGroup; + children = ( + 237E0C602347852500798AA0 /* Fare.json */, + ); + path = Fare; + sourceTree = ""; + }; + 237E0C62234788E000798AA0 /* Fares */ = { + isa = PBXGroup; + children = ( + 237E0C63234788F000798AA0 /* KarhooFareServiceSpec.swift */, + 237E0C682347931D00798AA0 /* KarhooFareInteractorSpec.swift */, + ); + path = Fares; + sourceTree = ""; + }; + 237E0C652347896E00798AA0 /* Fare */ = { + isa = PBXGroup; + children = ( + 237E0C662347897B00798AA0 /* MockFareInteractor.swift */, + ); + path = Fare; + sourceTree = ""; + }; + 274213A546BD3548026ADD7A /* Service */ = { + isa = PBXGroup; + children = ( + 237E0C652347896E00798AA0 /* Fare */, + 095A03D32333C1A9007D805E /* Config */, + FCD4DA5820F39AA7007D4374 /* Availability */, + 09928D3920EFAB1300B9F462 /* Quote */, + 5C08DFB220E5A917002203CB /* Analytics */, + 5C2856E420E3B1A5009B9464 /* Address */, + BAA0ADFA31768B401CD88B01 /* Payment */, + BAA0AFBFE9AAE9A8F8977D60 /* User */, + BAA0A6362846902D8A6A592A /* Trips */, + ); + path = Service; + sourceTree = ""; + }; + 2742148A9FD97E247A763D97 /* User */ = { + isa = PBXGroup; + children = ( + 27421A91EE9BE3C99E54F313 /* UserDataStore.swift */, + 43466AE71EDECEED0087BC51 /* Credentials.swift */, + ); + path = User; + sourceTree = ""; + }; + 274215E3D5E72339EFA3464F /* Header */ = { + isa = PBXGroup; + children = ( + 274213D38799FE5D607435B2 /* HeaderProvider.swift */, + 2742100A1C88263575619D5D /* KarhooHeaderProvider.swift */, + 27421CBA8C72F37A4F143580 /* HeaderConstants.swift */, + ); + path = Header; + sourceTree = ""; + }; + 27421663BE80D6753896079B /* Headers */ = { + isa = PBXGroup; + children = ( + 2742119D00DE30C9E3590055 /* HeaderProviderSpec.swift */, + ); + path = Headers; + sourceTree = ""; + }; + 274218A9245CD4AE406F4F35 /* AvailabilityInteractor */ = { + isa = PBXGroup; + children = ( + 274216467197574CB8746A19 /* AvailabilityInteractor.swift */, + 27421CA2A060894A7D859830 /* KarhooAvailabilityInteractor.swift */, + ); + path = AvailabilityInteractor; + sourceTree = ""; + }; + 27421CF9086B5B502DA0012E /* Request */ = { + isa = PBXGroup; + children = ( + 2359B46123425299008433A1 /* UserUpdatePayload.swift */, + 094B449D2285AC65002D2506 /* PaymentSDKTokenPayload.swift */, + 095A03C32333AFED007D805E /* UIConfigRequest.swift */, + 099DFC6B223BEDF800401542 /* AddPaymentDetailsPayload.swift */, + 09CE9A4F20FCE87800874092 /* TripBooking.swift */, + 09F79E39223955FF00D5B0B8 /* NonceRequestPayload.swift */, + 09F79E3B2239564600D5B0B8 /* Payer.swift */, + 095CEC0520F7573700C8AC4B /* TripCancellation.swift */, + FCD4DA5620F37FB2007D4374 /* AvailabilitySearch.swift */, + 09811A1720D7BB8C001DB06F /* CancelTripRequestPayload.swift */, + 09811A1920D7BD77001DB06F /* CancelReason.swift */, + BAA0A499BF991C5FC4735A7D /* UserLogin.swift */, + BAA0AD752CF0001B84C65B6E /* PasswordResetRequestPayload.swift */, + 40F4058E2334C50000D384A6 /* LocationInfoSearch.swift */, + 091BE6E92085192800BA34D0 /* PlaceSearch.swift */, + 09ACCE4F207BBB5E0057B0CC /* QuoteListIdRequestPayload.swift */, + 09928D4620EFAB3000B9F462 /* QuoteSearch.swift */, + 09CE9A5320FE1F2E00874092 /* TripSearch.swift */, + 09CE9A5520FE530700874092 /* TripType.swift */, + 09AF9AAA20D1751D0046D725 /* RefreshTokenRequestPayload.swift */, + 27421639BE5688EF2127E119 /* UserRegistration.swift */, + FCD32FEC20DAA1470044BB64 /* TripsRequestPayload.swift */, + ); + path = Request; + sourceTree = ""; + }; + 312CDFB41F6A981F006F48DD /* AppStateNotifier */ = { + isa = PBXGroup; + children = ( + 312CDFB51F6A984A006F48DD /* AppStateNotifier.swift */, + ); + path = AppStateNotifier; + sourceTree = ""; + }; + 3150CE2A1F9F7B6000DE6D62 /* UserLocation */ = { + isa = PBXGroup; + children = ( + 3150CE2B1F9F7B6000DE6D62 /* UserLocationProvider.swift */, + ); + path = UserLocation; + sourceTree = ""; + }; + 3150CE2D1F9F7BBA00DE6D62 /* Location */ = { + isa = PBXGroup; + children = ( + 3150CE2E1F9F7BBA00DE6D62 /* UserLocationProviderSpec.swift */, + ); + path = Location; + sourceTree = ""; + }; + 31F9168F20235F6600104777 /* TokenRefreshingHttpClient */ = { + isa = PBXGroup; + children = ( + 31904CD51FFFE26F00BA7402 /* TokenRefreshingHttpClient.swift */, + 09C1A23621072DCD004B6406 /* Interactor */, + 0900D34A20D2B1920082D1E8 /* RefreshTokenError.swift */, + ); + path = TokenRefreshingHttpClient; + sourceTree = ""; + }; + 31F9169020235F8700104777 /* HttpClient */ = { + isa = PBXGroup; + children = ( + 43F939961F3C85A300914E18 /* HttpClient.swift */, + 09C1A23721072DE1004B6406 /* JsonHttpClient */, + 31F9168F20235F6600104777 /* TokenRefreshingHttpClient */, + ); + path = HttpClient; + sourceTree = ""; + }; + 31F9169120235FB000104777 /* URLSessionSender */ = { + isa = PBXGroup; + children = ( + 43F9399C1F3C85A300914E18 /* URLSessionSender.swift */, + 43F939951F3C85A300914E18 /* KarhooURLSessionSender.swift */, + ); + path = URLSessionSender; + sourceTree = ""; + }; + 400BB94423F3058800AEAAFD /* Auth */ = { + isa = PBXGroup; + children = ( + 400BB94223F3027A00AEAAFD /* AuthService.swift */, + 400BB94523F3066500AEAAFD /* KarhooAuthService.swift */, + 400BB94923F308D200AEAAFD /* LoginInteractor */, + 404484E323FB00B300CD0347 /* RevokeInteractor */, + 40FBF606240EAFD9007F6A54 /* AuthHeaderKeys.swift */, + ); + path = Auth; + sourceTree = ""; + }; + 400BB94923F308D200AEAAFD /* LoginInteractor */ = { + isa = PBXGroup; + children = ( + 400BB94723F308BE00AEAAFD /* AuthLoginInteractor.swift */, + 400BB94A23F308F100AEAAFD /* KarhooAuthLoginInteractor.swift */, + ); + path = LoginInteractor; + sourceTree = ""; + }; + 4032BD0A23FC47B600C2409B /* Auth */ = { + isa = PBXGroup; + children = ( + 4032BD1123FC491500C2409B /* AuthExchangeToken.json */, + 4032BD0D23FC47B600C2409B /* AuthUserInfo.json */, + ); + path = Auth; + sourceTree = ""; + }; + 404484E323FB00B300CD0347 /* RevokeInteractor */ = { + isa = PBXGroup; + children = ( + 404484E123FB00A900CD0347 /* KarhooAuthRevokeInteractor.swift */, + ); + path = RevokeInteractor; + sourceTree = ""; + }; + 4310E47E1E99395D00625A0E /* Api */ = { + isa = PBXGroup; + children = ( + 0970BA6921136F4D0015A170 /* Observable */, + 4310E48A1E99395D00625A0E /* Karhoo.swift */, + 5C5BE90B2411A4F1005DD14E /* SDKConfiguration */, + 43584E861EE590DE00B4DFAB /* Context.swift */, + 4387A6441E3BAF6D0024FD55 /* KarhooSDK.h */, + 09C1A23B2107527A004B6406 /* Parser */, + 4310E48C1E99395D00625A0E /* DataStore */, + 09C1A232210725E2004B6406 /* Response */, + 27421CF9086B5B502DA0012E /* Request */, + 09C1A23321072628004B6406 /* Util */, + ); + path = Api; + sourceTree = ""; + }; + 4310E48C1E99395D00625A0E /* DataStore */ = { + isa = PBXGroup; + children = ( + 2742148A9FD97E247A763D97 /* User */, + ); + path = DataStore; + sourceTree = ""; + }; + 4310E4A41E99395D00625A0E /* Network */ = { + isa = PBXGroup; + children = ( + 0996137224083B6E00C8C88E /* AccessTokenProvider.swift */, + 27421EDE7F5329B3837E9DFC /* APIEndpoint.swift */, + 43F9399B1F3C85A300914E18 /* KarhooEnvironmentDetails.swift */, + 43F939971F3C85A300914E18 /* HttpConstants.swift */, + 31F9169020235F8700104777 /* HttpClient */, + 274215E3D5E72339EFA3464F /* Header */, + 31F9169120235FB000104777 /* URLSessionSender */, + 09C1A23421072A9D004B6406 /* Common */, + ); + path = Network; + sourceTree = ""; + }; + 4310E4B51E99395D00625A0E /* Broadcaster */ = { + isa = PBXGroup; + children = ( + 4310E4B61E99395D00625A0E /* Broadcaster.swift */, + ); + path = Broadcaster; + sourceTree = ""; + }; + 4310E4DD1E99397A00625A0E /* Api */ = { + isa = PBXGroup; + children = ( + 0970BA7921145CE10015A170 /* Observable */, + 4310E4E11E99397A00625A0E /* KarhooSpec.swift */, + 5C08DFB020E5A8D7002203CB /* MockContext.swift */, + 43584E881EE590FC00B4DFAB /* ContextSpec.swift */, + 09C1A23F21075A7E004B6406 /* Parser */, + 4310E4DF1E99397A00625A0E /* DataStore */, + 4310E5011E99397A00625A0E /* Util */, + ); + path = Api; + sourceTree = ""; + }; + 4310E4DF1E99397A00625A0E /* DataStore */ = { + isa = PBXGroup; + children = ( + 4310E4E01E99397A00625A0E /* UserDataStoreSpec.swift */, + 317210CE1FFE7330004CE129 /* CredentialsSpec.swift */, + ); + path = DataStore; + sourceTree = ""; + }; + 4310E4F61E99397A00625A0E /* Networking */ = { + isa = PBXGroup; + children = ( + 31005DA61FCEE699006D2A7D /* RequestTesting.swift */, + 09EB3E002108C3C5004571DF /* TokenRefresh */, + 09EB3DFF2108C3A4004571DF /* HttpClient */, + 0981F6CE20CFDA9B00BF7FB5 /* KarhooRequestSenderSpec.swift */, + 43D251B71F46E29C005ECB85 /* ReachabilityWrapperSpec.swift */, + 31FC37FE1F459A780012A57E /* DefaultAuthTokenSpec.swift */, + 317610ED1FD84CD200D2DB75 /* KarhooNetworkDateFormatterSpec.swift */, + 27421663BE80D6753896079B /* Headers */, + ); + path = Networking; + sourceTree = ""; + }; + 4310E5011E99397A00625A0E /* Util */ = { + isa = PBXGroup; + children = ( + 09C1A23E2107598B004B6406 /* Extension */, + 4310E5021E99397A00625A0E /* Broadcaster */, + 4310E5041E99397A00625A0E /* WeakReferenceWrapperSpec.swift */, + 43DD68F51ECD83F2005159C8 /* ResultSpec.swift */, + ); + path = Util; + sourceTree = ""; + }; + 4310E5021E99397A00625A0E /* Broadcaster */ = { + isa = PBXGroup; + children = ( + 4310E5031E99397A00625A0E /* BroadcasterSpec.swift */, + ); + path = Broadcaster; + sourceTree = ""; + }; + 431740CB1ECB257A00CD217F /* User */ = { + isa = PBXGroup; + children = ( + 2359B459234251FA008433A1 /* Update */, + 431740CD1ECB257A00CD217F /* KarhooUserServiceSpec.swift */, + 09820E6C20D7EAF200782530 /* Register */, + 09820E6B20D7EA6600782530 /* Logout */, + 0981F6D820D0317A00BF7FB5 /* Login */, + BAA0A4542C2A0B4B3F859321 /* PasswordReset */, + ); + path = User; + sourceTree = ""; + }; + 434B57D01E3FB1FB009E8FB8 /* TestCases */ = { + isa = PBXGroup; + children = ( + 4310E4DD1E99397A00625A0E /* Api */, + 4371C5A71EB0E8070059429D /* Service */, + 4310E4F61E99397A00625A0E /* Networking */, + ); + path = TestCases; + sourceTree = ""; + }; + 434B57D41E3FB1FB009E8FB8 /* Mocks */ = { + isa = PBXGroup; + children = ( + 43588C651EBB675100D03765 /* TestUtil.swift */, + 43584E8C1EE591C700B4DFAB /* ObjectTestFactory.swift */, + 43584E9E1EE6A3D400B4DFAB /* MockUserDefaults.swift */, + FC2D95712113612300201BDB /* Error.swift */, + BAA0A4B5934264D356A0E7FA /* Api */, + 0981F6C520CEC26000BF7FB5 /* Networking */, + 274213A546BD3548026ADD7A /* Service */, + ); + path = Mocks; + sourceTree = ""; + }; + 43584E7D1EE576B400B4DFAB /* Trips */ = { + isa = PBXGroup; + children = ( + 43584E921EE5993700B4DFAB /* KarhooTripServiceSpec.swift */, + 4371C5B41EB0E8070059429D /* BookingInteractor */, + BAA0A8A25BA03B5204A38F5B /* CancelTripInteractor */, + BAA0AAD7A86DD849B6BEECDC /* TripSearchInteractor */, + BAA0A7CC95AB61B10CCBF2B5 /* TripUpdate */, + BAA0A202AFFF33AFCE5BEB29 /* TripStatusUpdate */, + ); + path = Trips; + sourceTree = ""; + }; + 4371C5791EB0E7F70059429D /* Service */ = { + isa = PBXGroup; + children = ( + 237E0C552347821F00798AA0 /* Fares */, + 095A03B723339708007D805E /* Config */, + 4371C57A1EB0E7F70059429D /* Address */, + 4371C5821EB0E7F70059429D /* Analytics */, + 4371C5891EB0E7F70059429D /* Availability */, + 09928D0D20EFAABA00B9F462 /* Quote */, + 5C9FEB85204328F200E68092 /* DriverTracking */, + 43EF91651EE93DF6006ECB75 /* Trip */, + 43D395C81ECF9F3300499E8F /* Payment */, + 43850F401ECB0056009CB8FD /* User */, + 400BB94423F3058800AEAAFD /* Auth */, + ); + path = Service; + sourceTree = ""; + }; + 4371C57A1EB0E7F70059429D /* Address */ = { + isa = PBXGroup; + children = ( + 4310E47F1E99395D00625A0E /* AddressService.swift */, + 4371C57B1EB0E7F70059429D /* KarhooAddressService.swift */, + 5C2856C420E3B127009B9464 /* LocationInfoInteractor */, + 5C2856CA20E3B127009B9464 /* ReverseGeocodeInteractor */, + 5C2856C720E3B127009B9464 /* PlaceSearchInteractor */, + ); + path = Address; + sourceTree = ""; + }; + 4371C5821EB0E7F70059429D /* Analytics */ = { + isa = PBXGroup; + children = ( + 31F916892023559700104777 /* AnalyticsService.swift */, + 4371C5841EB0E7F70059429D /* KarhooAnalyticsService.swift */, + 4371C5831EB0E7F70059429D /* AnalyticsConstants.swift */, + 31EC3F641F9E50AD008CD637 /* AnalyticsPayloadBuilder.swift */, + 4371C5851EB0E7F70059429D /* Providers */, + ); + path = Analytics; + sourceTree = ""; + }; + 4371C5851EB0E7F70059429D /* Providers */ = { + isa = PBXGroup; + children = ( + 4371C5871EB0E7F70059429D /* LogAnalyticsProvider.swift */, + ); + path = Providers; + sourceTree = ""; + }; + 4371C5891EB0E7F70059429D /* Availability */ = { + isa = PBXGroup; + children = ( + 317610F11FD86A9600D2DB75 /* KarhooAvailabilityService.swift */, + 4310E4801E99395D00625A0E /* AvailabilityService.swift */, + 274218A9245CD4AE406F4F35 /* AvailabilityInteractor */, + ); + path = Availability; + sourceTree = ""; + }; + 4371C5A71EB0E8070059429D /* Service */ = { + isa = PBXGroup; + children = ( + 098D525023FB156600059086 /* Auth */, + 237E0C62234788E000798AA0 /* Fares */, + 095A03C92333B3CF007D805E /* Config */, + 09928D2D20EFAAEE00B9F462 /* Quote */, + 5C9FEB8A20432B1D00E68092 /* DriverTracking */, + 43584E7D1EE576B400B4DFAB /* Trips */, + 43D395D31ECFA2FF00499E8F /* Payment */, + 431740CB1ECB257A00CD217F /* User */, + 4371C5A81EB0E8070059429D /* Address */, + 4371C5AD1EB0E8070059429D /* Analytics */, + 4371C5B21EB0E8070059429D /* Availability */, + 3150CE2D1F9F7BBA00DE6D62 /* Location */, + ); + path = Service; + sourceTree = ""; + }; + 4371C5A81EB0E8070059429D /* Address */ = { + isa = PBXGroup; + children = ( + 5C2856DA20E3B183009B9464 /* LocationInfo */, + 5C2856DC20E3B183009B9464 /* ReverseGeocode */, + 5C2856D820E3B183009B9464 /* PlaceSearch */, + 4371C5A91EB0E8070059429D /* KarhooAddressServiceSpec.swift */, + ); + path = Address; + sourceTree = ""; + }; + 4371C5AD1EB0E8070059429D /* Analytics */ = { + isa = PBXGroup; + children = ( + 4371C5AE1EB0E8070059429D /* AnalyticsServiceSpec.swift */, + 310D44AD1FA1F8890038BFDA /* TimestampFormatterSpec.swift */, + 4371C5AF1EB0E8070059429D /* Providers */, + ); + path = Analytics; + sourceTree = ""; + }; + 4371C5AF1EB0E8070059429D /* Providers */ = { + isa = PBXGroup; + children = ( + 4371C5B01EB0E8070059429D /* LogAnalyticsProviderSpec.swift */, + ); + path = Providers; + sourceTree = ""; + }; + 4371C5B21EB0E8070059429D /* Availability */ = { + isa = PBXGroup; + children = ( + 317610F31FD9855D00D2DB75 /* KarhooAvailabilityServiceSpec.swift */, + FCD4DA5B20F3A5C5007D4374 /* KarhooAvailabilityInteractorSpec.swift */, + ); + path = Availability; + sourceTree = ""; + }; + 4371C5B41EB0E8070059429D /* BookingInteractor */ = { + isa = PBXGroup; + children = ( + BAA0A3541731789F935644A7 /* KarhooBookingInteractorSpec.swift */, + ); + path = BookingInteractor; + sourceTree = ""; + }; + 43850F401ECB0056009CB8FD /* User */ = { + isa = PBXGroup; + children = ( + 2359B45C2342522E008433A1 /* UpdateUserDetailsInteractor */, + 43850F3E1ECB003E009CB8FD /* UserService.swift */, + 43850F431ECB0056009CB8FD /* KarhooUserService.swift */, + BAA0AF95317D2B3FB9BD4E1F /* PasswordResetInteractor */, + BAA0A8006ED391098F238542 /* LoginInteractor */, + BAA0A8232C637F2E28518AAA /* RegisterInteractor */, + BAA0A3ECF28F88C6C493BA56 /* LogoutInteractor */, + ); + path = User; + sourceTree = ""; + }; + 4387A6371E3BAF6D0024FD55 = { + isa = PBXGroup; + children = ( + 4387A6431E3BAF6D0024FD55 /* KarhooSDK */, + 4387A64E1E3BAF6D0024FD55 /* KarhooSDKTests */, + 09B204AD21412C3100063632 /* KarhooSDKIntegrationTests */, + 09A9B28C2405901900823FB0 /* Client */, + 4387A6421E3BAF6D0024FD55 /* Products */, + 18DBF3907EAF3E4DE145B9C0 /* Frameworks */, + 09CA785420EE59B200D3BB6F /* Recovered References */, + AC7DB22BA038B454399F81BE /* Pods */, + ); + sourceTree = ""; + }; + 4387A6421E3BAF6D0024FD55 /* Products */ = { + isa = PBXGroup; + children = ( + 4387A6411E3BAF6D0024FD55 /* KarhooSDK.framework */, + 4387A64A1E3BAF6D0024FD55 /* KarhooSDKTests.xctest */, + 09B204AC21412C3100063632 /* KarhooSDKIntegrationTests.xctest */, + 09A9B28B2405901900823FB0 /* Client.app */, + ); + name = Products; + sourceTree = ""; + }; + 4387A6431E3BAF6D0024FD55 /* KarhooSDK */ = { + isa = PBXGroup; + children = ( + 4310E47E1E99395D00625A0E /* Api */, + 4371C5791EB0E7F70059429D /* Service */, + 4310E4A41E99395D00625A0E /* Network */, + 4387A6451E3BAF6D0024FD55 /* Info.plist */, + ); + path = KarhooSDK; + sourceTree = ""; + }; + 4387A64E1E3BAF6D0024FD55 /* KarhooSDKTests */ = { + isa = PBXGroup; + children = ( + 434B57D01E3FB1FB009E8FB8 /* TestCases */, + 434B57D41E3FB1FB009E8FB8 /* Mocks */, + 4387A6511E3BAF6D0024FD55 /* Info.plist */, + 09430E4E224109D6009846A6 /* UnitTestSetup.swift */, + ); + path = KarhooSDKTests; + sourceTree = ""; + }; + 43D395C81ECF9F3300499E8F /* Payment */ = { + isa = PBXGroup; + children = ( + 43D395C61ECF9ECD00499E8F /* PaymentService.swift */, + 43D395C91ECF9F5B00499E8F /* KarhooPaymentService.swift */, + 09F79E322239520D00D5B0B8 /* GetNonceInteractor */, + 09C1A2382107367C004B6406 /* PaymentSDKTokenInteractor */, + 5C224EAD223DC9E000C1CFCE /* AddPaymentDetailsInteractor */, + BAA0A94E30C5D3D2AC6F6466 /* AddPaymentMethodInteractor */, + ); + path = Payment; + sourceTree = ""; + }; + 43D395D31ECFA2FF00499E8F /* Payment */ = { + isa = PBXGroup; + children = ( + 090E0163223FAB56005D1CB7 /* AddPaymentDetailsSpec */, + 43D395D41ECFA31900499E8F /* KarhooPaymentServiceSpec.swift */, + 09F79E3D2239720E00D5B0B8 /* GetNonceMethod */, + BAA0AE0F713F6AB11F295960 /* PaymentSDKToken */, + ); + path = Payment; + sourceTree = ""; + }; + 43EF91651EE93DF6006ECB75 /* Trip */ = { + isa = PBXGroup; + children = ( + 43584E8E1EE5960700B4DFAB /* TripService.swift */, + 43EF91671EE93DF6006ECB75 /* KarhooTripService.swift */, + BAA0A78B612E3EC4D301EB1B /* BookingInteractor */, + BAA0A5C7CB3AEF80898C7CDE /* CancelTripnteractor */, + BAA0A9CC915480FA5B3811E7 /* TripSearchInteractor */, + BAA0A09FEA1B89384EFDA915 /* TripUpdate */, + BAA0A37F3E26727986159BF0 /* TripStatus */, + ); + path = Trip; + sourceTree = ""; + }; + 5C08DFB220E5A917002203CB /* Analytics */ = { + isa = PBXGroup; + children = ( + 4322B48B1E69A57B002108D5 /* MockAnalyticsService.swift */, + 5C08DFB320E5A92A002203CB /* MockAnalyticsProvider.swift */, + ); + path = Analytics; + sourceTree = ""; + }; + 5C224EAD223DC9E000C1CFCE /* AddPaymentDetailsInteractor */ = { + isa = PBXGroup; + children = ( + 5C224EAE223DCA9900C1CFCE /* AddPaymentDetailsInteractor.swift */, + 5C224EB0223DCD2000C1CFCE /* KarhooAddPaymentDetailsInteractor.swift */, + ); + path = AddPaymentDetailsInteractor; + sourceTree = ""; + }; + 5C224EB2223DD81C00C1CFCE /* AddPaymentDetails */ = { + isa = PBXGroup; + children = ( + 5C224EB3223DD83000C1CFCE /* MockAddPaymentDetailsInteractor.swift */, + ); + path = AddPaymentDetails; + sourceTree = ""; + }; + 5C224EB5223DDB6D00C1CFCE /* AddPaymentDetails */ = { + isa = PBXGroup; + children = ( + 5C224EB6223DDB8A00C1CFCE /* AddPaymentDetailsMethodSpec.swift */, + ); + path = AddPaymentDetails; + sourceTree = ""; + }; + 5C2856C420E3B127009B9464 /* LocationInfoInteractor */ = { + isa = PBXGroup; + children = ( + 090881E220E662D200EE2C67 /* KarhooLocationInfoInteractor.swift */, + 5C2856C620E3B127009B9464 /* LocationInfoInteractor.swift */, + ); + path = LocationInfoInteractor; + sourceTree = ""; + }; + 5C2856C720E3B127009B9464 /* PlaceSearchInteractor */ = { + isa = PBXGroup; + children = ( + 5C2856C820E3B127009B9464 /* PlaceSearchInteractor.swift */, + 5C2856C920E3B127009B9464 /* KarhooPlaceSearchInteractor.swift */, + ); + path = PlaceSearchInteractor; + sourceTree = ""; + }; + 5C2856CA20E3B127009B9464 /* ReverseGeocodeInteractor */ = { + isa = PBXGroup; + children = ( + 5C2856CB20E3B127009B9464 /* KarhooReverseGeocodeInteractor.swift */, + 5C2856CC20E3B127009B9464 /* ReverseGeocodeInteractor.swift */, + ); + path = ReverseGeocodeInteractor; + sourceTree = ""; + }; + 5C2856D820E3B183009B9464 /* PlaceSearch */ = { + isa = PBXGroup; + children = ( + 090881DE20E65F7D00EE2C67 /* KarhooPlaceSearchInteractorSpec.swift */, + ); + path = PlaceSearch; + sourceTree = ""; + }; + 5C2856DA20E3B183009B9464 /* LocationInfo */ = { + isa = PBXGroup; + children = ( + 090881E420E663E500EE2C67 /* KarhooLocationInfoInteractorSpec.swift */, + ); + path = LocationInfo; + sourceTree = ""; + }; + 5C2856DC20E3B183009B9464 /* ReverseGeocode */ = { + isa = PBXGroup; + children = ( + 5C2856DD20E3B183009B9464 /* KarhooReverseGeocodeProviderSpec.swift */, + ); + path = ReverseGeocode; + sourceTree = ""; + }; + 5C2856E420E3B1A5009B9464 /* Address */ = { + isa = PBXGroup; + children = ( + 5C2856E520E3B1A5009B9464 /* LocationInfo */, + 5C2856E920E3B1A5009B9464 /* ReverseGeocode */, + 5C2856EB20E3B1A5009B9464 /* PlaceSearch */, + ); + path = Address; + sourceTree = ""; + }; + 5C2856E520E3B1A5009B9464 /* LocationInfo */ = { + isa = PBXGroup; + children = ( + 09BDE052212F128700798330 /* MockLocationInfoInteractor.swift */, + ); + path = LocationInfo; + sourceTree = ""; + }; + 5C2856E920E3B1A5009B9464 /* ReverseGeocode */ = { + isa = PBXGroup; + children = ( + 09BDE054212F147600798330 /* MockReverseGeocodeInteractor.swift */, + ); + path = ReverseGeocode; + sourceTree = ""; + }; + 5C2856EB20E3B1A5009B9464 /* PlaceSearch */ = { + isa = PBXGroup; + children = ( + 090881E020E65FAD00EE2C67 /* MockPlaceSearchInteractor.swift */, + ); + path = PlaceSearch; + sourceTree = ""; + }; + 5C5BE90B2411A4F1005DD14E /* SDKConfiguration */ = { + isa = PBXGroup; + children = ( + 097BF2B62358710400BBE418 /* KarhooSDKConfiguration.swift */, + 5C5BE90C2411A513005DD14E /* AuthenticationMethod.swift */, + 5C103FA12411C31F00F5F2F9 /* TokenExchangeSettings.swift */, + 4371C5861EB0E7F70059429D /* AnalyticsProvider.swift */, + 5C103FA32411DF7F00F5F2F9 /* GuestSettings.swift */, + ); + path = SDKConfiguration; + sourceTree = ""; + }; + 5C9FEB85204328F200E68092 /* DriverTracking */ = { + isa = PBXGroup; + children = ( + FC321EDF20F8D54E00B2C3CD /* DriverTrackingService.swift */, + FC321EE120F8D56400B2C3CD /* KarhooDriverTrackingService.swift */, + 0970BA76211371480015A170 /* Interactor */, + ); + path = DriverTracking; + sourceTree = ""; + }; + 5C9FEB8A20432B1D00E68092 /* DriverTracking */ = { + isa = PBXGroup; + children = ( + FC321EE320F8D72C00B2C3CD /* KarhooDriverTrackingServiceSpec.swift */, + 09A18BD821185080009F927B /* KarhooDriverTrackingInteractorSpec.swift */, + ); + path = DriverTracking; + sourceTree = ""; + }; + 5CEB7A05210922680016B7F2 /* DataStore */ = { + isa = PBXGroup; + children = ( + 434B58421E434E5F009E8FB8 /* MockUserDataStore.swift */, + ); + path = DataStore; + sourceTree = ""; + }; + AC7DB22BA038B454399F81BE /* Pods */ = { + isa = PBXGroup; + children = ( + 90BAB2DA56DC0C76920A8C20 /* Pods-KarhooSDK.debug.xcconfig */, + 13B19E142647D37690651605 /* Pods-KarhooSDK.release.xcconfig */, + 5616C6061BCA5EB676D5A133 /* Pods-KarhooSDKIntegrationTests.debug.xcconfig */, + 8604EAC56835802F4E0F0F56 /* Pods-KarhooSDKIntegrationTests.release.xcconfig */, + 09E40C17E5179F413E52C435 /* Pods-KarhooSDKTests.debug.xcconfig */, + B709CD873DABF2A4D66BCE0C /* Pods-KarhooSDKTests.release.xcconfig */, + 6184F30666F41BD1192B66BC /* Pods-Client.debug.xcconfig */, + B051EBD7153DFB0DE9C784A0 /* Pods-Client.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + BAA0A09FEA1B89384EFDA915 /* TripUpdate */ = { + isa = PBXGroup; + children = ( + FC57F942212EEC56009193CB /* TripUpdateInteractor.swift */, + FCD32FF620E252680044BB64 /* KarhooTripUpdateInteractor.swift */, + ); + path = TripUpdate; + sourceTree = ""; + }; + BAA0A16C33556F8407404DA8 /* PaymentSDKToken */ = { + isa = PBXGroup; + children = ( + BAA0A6F016D7940CF293B9BC /* MockPaymentSDKTokenInteractor.swift */, + ); + path = PaymentSDKToken; + sourceTree = ""; + }; + BAA0A1CC107331C379CBB9C5 /* Booking */ = { + isa = PBXGroup; + children = ( + 096B31BB20B702AE002B41E9 /* MockBookingInteractor.swift */, + ); + path = Booking; + sourceTree = ""; + }; + BAA0A202AFFF33AFCE5BEB29 /* TripStatusUpdate */ = { + isa = PBXGroup; + children = ( + BAA0AE54DAE03275821D43A7 /* KarhooTripStatusInteractorSpec.swift */, + ); + path = TripStatusUpdate; + sourceTree = ""; + }; + BAA0A29CABA7ACA838A7B9D0 /* Extention */ = { + isa = PBXGroup; + children = ( + BAA0A1DD7F2A93BCD18DD3FE /* PositionExt.swift */, + 09A56DFF2097162C00891F02 /* LocationDetailsExt.swift */, + FC987C7C20EF7949001B8B79 /* CLLocationExt.swift */, + ); + path = Extention; + sourceTree = ""; + }; + BAA0A37F3E26727986159BF0 /* TripStatus */ = { + isa = PBXGroup; + children = ( + BAA0AF4654D1B9211454EEBD /* TripStatusInteractor.swift */, + BAA0A7C9A052655223E880D3 /* KarhooTripStatusInteractor.swift */, + ); + path = TripStatus; + sourceTree = ""; + }; + BAA0A3ECF28F88C6C493BA56 /* LogoutInteractor */ = { + isa = PBXGroup; + children = ( + 09D0757420D826A700F75E32 /* KarhooLogoutInteractor.swift */, + ); + path = LogoutInteractor; + sourceTree = ""; + }; + BAA0A4542C2A0B4B3F859321 /* PasswordReset */ = { + isa = PBXGroup; + children = ( + BAA0A8260B3FAD817582FADE /* KarhooPasswordResetInteractorSpec.swift */, + ); + path = PasswordReset; + sourceTree = ""; + }; + BAA0A4B5934264D356A0E7FA /* Api */ = { + isa = PBXGroup; + children = ( + 0970BA7C211464600015A170 /* Observable */, + 5CEB7A05210922680016B7F2 /* DataStore */, + 09EB3E012108C3DD004571DF /* Util */, + BAA0A89B80D6633B0C17300D /* Request */, + BAA0AD8949D321E435448F81 /* Response */, + ); + path = Api; + sourceTree = ""; + }; + BAA0A4D292BCBF7ACA64EA18 /* PasswordReset */ = { + isa = PBXGroup; + children = ( + BAA0A5FC9FBDD3F60C6FE30E /* MockPasswordResetInteractor.swift */, + ); + path = PasswordReset; + sourceTree = ""; + }; + BAA0A5C7CB3AEF80898C7CDE /* CancelTripnteractor */ = { + isa = PBXGroup; + children = ( + 0907B98B20D7B901008915B1 /* CancelTripInteractor.swift */, + 0907B98E20D7B964008915B1 /* KarhooCancelTripInteractor.swift */, + ); + path = CancelTripnteractor; + sourceTree = ""; + }; + BAA0A5DDC917295B74ADE7D4 /* TripStatusUpdate */ = { + isa = PBXGroup; + children = ( + BAA0AC8C40120E475F87ECB2 /* MockTripStatusInteractor.swift */, + ); + path = TripStatusUpdate; + sourceTree = ""; + }; + BAA0A6362846902D8A6A592A /* Trips */ = { + isa = PBXGroup; + children = ( + 09811A1B20D7C119001DB06F /* CancelTrip */, + 096B31BD20B702D7002B41E9 /* TripSearch */, + BAA0A1CC107331C379CBB9C5 /* Booking */, + BAA0A5DDC917295B74ADE7D4 /* TripStatusUpdate */, + BAA0A71DF30F73CD025D5E1F /* TripUpdate */, + ); + path = Trips; + sourceTree = ""; + }; + BAA0A71DF30F73CD025D5E1F /* TripUpdate */ = { + isa = PBXGroup; + children = ( + FC0BF05A20E39D2D0004DF61 /* MockTripUpdateInteractor.swift */, + ); + path = TripUpdate; + sourceTree = ""; + }; + BAA0A78B612E3EC4D301EB1B /* BookingInteractor */ = { + isa = PBXGroup; + children = ( + BAA0A7CF7A18FDC822E15AFD /* BookingInteractor.swift */, + BAA0AFBC32DB1629C3BB1D01 /* KarhooBookingInteractor.swift */, + ); + path = BookingInteractor; + sourceTree = ""; + }; + BAA0A7CC95AB61B10CCBF2B5 /* TripUpdate */ = { + isa = PBXGroup; + children = ( + FCD32FF920E26DC10044BB64 /* KarhooTripUpdateInteractortSpec.swift */, + ); + path = TripUpdate; + sourceTree = ""; + }; + BAA0A8006ED391098F238542 /* LoginInteractor */ = { + isa = PBXGroup; + children = ( + FCBD67B2211C9CBD005AA332 /* LoginInteractor.swift */, + 0981F6C820CEC3DE00BF7FB5 /* KarhooLoginInteractor.swift */, + ); + path = LoginInteractor; + sourceTree = ""; + }; + BAA0A8232C637F2E28518AAA /* RegisterInteractor */ = { + isa = PBXGroup; + children = ( + 09820E6920D7E5E800782530 /* KarhooRegisterInteractor.swift */, + FCBD67B6211C9D7E005AA332 /* RegisterInteractor.swift */, + ); + path = RegisterInteractor; + sourceTree = ""; + }; + BAA0A89B80D6633B0C17300D /* Request */ = { + isa = PBXGroup; + children = ( + 233DB46823433F0400BCBC3F /* UserUpdateMock.swift */, + FCE11B3720F6272400DFC641 /* AvailabilitySearchMock.swift */, + BAA0A2BC090EFF81F2F9B9AC /* TripBookingMock.swift */, + 5C870D2820CDBC9C0039A2BF /* LoginRequestPayloadMock.swift */, + 09F79E40223A63DA00D5B0B8 /* NonceRequestPayload.swift */, + BAA0A26ED6144AF54A930CFB /* PasswordResetRequestPayloadMock.swift */, + 09820E7120D7F60100782530 /* UserRegistrationMock.swift */, + ); + path = Request; + sourceTree = ""; + }; + BAA0A8A25BA03B5204A38F5B /* CancelTripInteractor */ = { + isa = PBXGroup; + children = ( + 0907B99120D7B98A008915B1 /* KarhooCancelTripInteractorSpec.swift */, + ); + path = CancelTripInteractor; + sourceTree = ""; + }; + BAA0A94E30C5D3D2AC6F6466 /* AddPaymentMethodInteractor */ = { + isa = PBXGroup; + children = ( + BAA0A0EB15002AB0D857748A /* AddPaymentMethodInteractor.swift */, + BAA0A1365EB1CC10CA0F94DE /* KarhooAddPaymentMethodInteractor.swift */, + ); + path = AddPaymentMethodInteractor; + sourceTree = ""; + }; + BAA0A9CC915480FA5B3811E7 /* TripSearchInteractor */ = { + isa = PBXGroup; + children = ( + FCD32FE820DA9C4F0044BB64 /* TripSearchInteractor.swift */, + FCD32FEA20DA9C5F0044BB64 /* KarhooTripSearchInteractor.swift */, + ); + path = TripSearchInteractor; + sourceTree = ""; + }; + BAA0AAD7A86DD849B6BEECDC /* TripSearchInteractor */ = { + isa = PBXGroup; + children = ( + FCD32FEE20DAA33A0044BB64 /* KarhooTripsListInteractorSpec.swift */, + ); + path = TripSearchInteractor; + sourceTree = ""; + }; + BAA0AD8949D321E435448F81 /* Response */ = { + isa = PBXGroup; + children = ( + BAA0AA4DA19A464EF80F00F6 /* CategoriesMock.swift */, + 0981F6CC20CEEB1000BF7FB5 /* AuthTokenMock.swift */, + 5C2856F120E3B1DC009B9464 /* LocationInfoMock.swift */, + BAA0AACF77649D0AA514D127 /* UserInfoMock.swift */, + 5CA2CE412343510E000AEC1E /* OrganisationMock.swift */, + BAA0A819AC6AB43365A4C081 /* QuoteIdMock.swift */, + BAA0A2139A85B9FF7F56BED7 /* PaymentSDKTokenMock.swift */, + BAA0ADA5AD439AA7E9B9ED6C /* TripStatusMock.swift */, + 27421D16D44DEC18A835BDB0 /* DriverTrackingInfoMock.swift */, + 09AF9AAC20D17B7D0046D725 /* RefreshTokenMock.swift */, + 27421D9042A4F39DF7D69941 /* QuoteMock.swift */, + 099AF2FA20F8BDF000A2AA57 /* TripInfoMock.swift */, + 274215B1B4ADECE9D9CE2EA2 /* QuoteListMock.swift */, + ); + path = Response; + sourceTree = ""; + }; + BAA0ADFA31768B401CD88B01 /* Payment */ = { + isa = PBXGroup; + children = ( + 5C224EB2223DD81C00C1CFCE /* AddPaymentDetails */, + 09F79E45223AAA2D00D5B0B8 /* GetNonce */, + BAA0A16C33556F8407404DA8 /* PaymentSDKToken */, + ); + path = Payment; + sourceTree = ""; + }; + BAA0AE0F713F6AB11F295960 /* PaymentSDKToken */ = { + isa = PBXGroup; + children = ( + BAA0A3313412237721C4C7B5 /* KarhooPaymentSDKTokenInteractorSpec.swift */, + ); + path = PaymentSDKToken; + sourceTree = ""; + }; + BAA0AEB5E96A9CD5B7615E83 /* RequestSender */ = { + isa = PBXGroup; + children = ( + BAA0AE3182D554B65C66EC52 /* RequestSender.swift */, + BAA0A33AC9CF7967569962A2 /* KarhooRequestSender.swift */, + ); + path = RequestSender; + sourceTree = ""; + }; + BAA0AF95317D2B3FB9BD4E1F /* PasswordResetInteractor */ = { + isa = PBXGroup; + children = ( + BAA0AAD70E6AE0FF8C70BEC1 /* KarhooPasswordResetInteractor.swift */, + FCBD67B4211C9CE6005AA332 /* PasswordResetInteractor.swift */, + ); + path = PasswordResetInteractor; + sourceTree = ""; + }; + BAA0AFBFE9AAE9A8F8977D60 /* User */ = { + isa = PBXGroup; + children = ( + 233DB46523433EC200BCBC3F /* UpdateUserDetails */, + 09820E7620D8172000782530 /* Logout */, + 09820E7520D811B200782530 /* Signup */, + 0981F6D320CFE1A600BF7FB5 /* Login */, + BAA0A4D292BCBF7ACA64EA18 /* PasswordReset */, + ); + path = User; + sourceTree = ""; + }; + FC1202402146A03E00161081 /* Service */ = { + isa = PBXGroup; + children = ( + 098D524B23FB14CC00059086 /* Auth */, + 095A03DC2333D8FC007D805E /* Config */, + 09EE81E82152615D004B2B09 /* Quote */, + 09F90F922151006E003BC867 /* Trip */, + 09F8ABFE214FDF3200E293B7 /* User */, + 09F8ABEA214A9B4100E293B7 /* DriverTracking */, + FC12024A214A83E000161081 /* Address */, + 098C91F52147CAFE00F02DE3 /* Payment */, + 4095037A234CCF5A00C6F7A5 /* FareMethodSpec.swift */, + ); + path = Service; + sourceTree = ""; + }; + FC1202412146A0B000161081 /* Utils */ = { + isa = PBXGroup; + children = ( + 09F8ABED214AABBD00E293B7 /* NetworkStubFactory.swift */, + 098C91FA2147FF9900F02DE3 /* RawKarhooErrorFactory.swift */, + ); + path = Utils; + sourceTree = ""; + }; + FC12024A214A83E000161081 /* Address */ = { + isa = PBXGroup; + children = ( + FC12024B214A840E00161081 /* LocationInfoMethodSpec.swift */, + FC120252214BF82400161081 /* ReverseGeocodeMethodSpec.swift */, + 09F8ABF5214BF4F200E293B7 /* PlaceSearchMethodSpec.swift */, + ); + path = Address; + sourceTree = ""; + }; + FC12024D214A94E300161081 /* AddressService */ = { + isa = PBXGroup; + children = ( + FC12024E214A950B00161081 /* LocationInfo.json */, + 09F8ABF7214BF69400E293B7 /* Places.json */, + ); + path = AddressService; + sourceTree = ""; + }; + FC4B92E5222D3F7700FE33B4 /* Environment */ = { + isa = PBXGroup; + children = ( + 090E8225235DAD5900B447A2 /* KarhooEnvironment.swift */, + ); + path = Environment; + sourceTree = ""; + }; + FCD4DA5820F39AA7007D4374 /* Availability */ = { + isa = PBXGroup; + children = ( + FCD4DA5920F39AD0007D4374 /* MockAvailabilityInteractor.swift */, + ); + path = Availability; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 4387A63E1E3BAF6D0024FD55 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 4387A6521E3BAF6D0024FD55 /* KarhooSDK.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 09A9B28A2405901900823FB0 /* Client */ = { + isa = PBXNativeTarget; + buildConfigurationList = 09A9B29C2405901B00823FB0 /* Build configuration list for PBXNativeTarget "Client" */; + buildPhases = ( + 32FFC923FC0F66E8A9589C93 /* [CP] Check Pods Manifest.lock */, + 09A9B2872405901900823FB0 /* Sources */, + 09A9B2882405901900823FB0 /* Frameworks */, + 09A9B2892405901900823FB0 /* Resources */, + B280F30C5CEA625D85EA4597 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Client; + productName = Client; + productReference = 09A9B28B2405901900823FB0 /* Client.app */; + productType = "com.apple.product-type.application"; + }; + 09B204AB21412C3100063632 /* KarhooSDKIntegrationTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 09B204B621412C3200063632 /* Build configuration list for PBXNativeTarget "KarhooSDKIntegrationTests" */; + buildPhases = ( + 06BC11CA2D549EF713263C56 /* [CP] Check Pods Manifest.lock */, + 09B204A821412C3100063632 /* Sources */, + 09B204A921412C3100063632 /* Frameworks */, + 09B204AA21412C3100063632 /* Resources */, + EA73028E782494613EF1B9E2 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 09B204B321412C3200063632 /* PBXTargetDependency */, + ); + name = KarhooSDKIntegrationTests; + productName = KarhooSDKIntegrationTests; + productReference = 09B204AC21412C3100063632 /* KarhooSDKIntegrationTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 4387A6401E3BAF6D0024FD55 /* KarhooSDK */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4387A6551E3BAF6D0024FD55 /* Build configuration list for PBXNativeTarget "KarhooSDK" */; + buildPhases = ( + 2014A37392CD9E66BD9E2383 /* [CP] Check Pods Manifest.lock */, + 4387A63C1E3BAF6D0024FD55 /* Sources */, + 4387A63D1E3BAF6D0024FD55 /* Frameworks */, + 4387A63E1E3BAF6D0024FD55 /* Headers */, + 4387A63F1E3BAF6D0024FD55 /* Resources */, + 431301B31E5B20EF00059E51 /* Run Script: Swift lint */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = KarhooSDK; + productName = KarhooSDK; + productReference = 4387A6411E3BAF6D0024FD55 /* KarhooSDK.framework */; + productType = "com.apple.product-type.framework"; + }; + 4387A6491E3BAF6D0024FD55 /* KarhooSDKTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4387A6581E3BAF6D0024FD55 /* Build configuration list for PBXNativeTarget "KarhooSDKTests" */; + buildPhases = ( + 0BF8BB3613BD1C8CF483C305 /* [CP] Check Pods Manifest.lock */, + 4387A6461E3BAF6D0024FD55 /* Sources */, + 4387A6471E3BAF6D0024FD55 /* Frameworks */, + 4387A6481E3BAF6D0024FD55 /* Resources */, + C38E9B8E57F0ED6EBC169BC6 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 4387A64D1E3BAF6D0024FD55 /* PBXTargetDependency */, + ); + name = KarhooSDKTests; + productName = KarhooSDKTests; + productReference = 4387A64A1E3BAF6D0024FD55 /* KarhooSDKTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4387A6381E3BAF6D0024FD55 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1100; + ORGANIZATIONNAME = "Flit Technologies Ltd"; + TargetAttributes = { + 09A9B28A2405901900823FB0 = { + CreatedOnToolsVersion = 11.3.1; + DevelopmentTeam = U7U4Q7YGDH; + ProvisioningStyle = Automatic; + }; + 09B204AB21412C3100063632 = { + CreatedOnToolsVersion = 9.4.1; + DevelopmentTeam = U7U4Q7YGDH; + LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; + }; + 4387A6401E3BAF6D0024FD55 = { + CreatedOnToolsVersion = 8.2.1; + DevelopmentTeam = U7U4Q7YGDH; + LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; + }; + 4387A6491E3BAF6D0024FD55 = { + CreatedOnToolsVersion = 8.2.1; + DevelopmentTeam = U7U4Q7YGDH; + LastSwiftMigration = 1020; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 4387A63B1E3BAF6D0024FD55 /* Build configuration list for PBXProject "KarhooSDK" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 4387A6371E3BAF6D0024FD55; + productRefGroup = 4387A6421E3BAF6D0024FD55 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4387A6401E3BAF6D0024FD55 /* KarhooSDK */, + 4387A6491E3BAF6D0024FD55 /* KarhooSDKTests */, + 09B204AB21412C3100063632 /* KarhooSDKIntegrationTests */, + 09A9B28A2405901900823FB0 /* Client */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 09A9B2892405901900823FB0 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 09A9B29A2405901B00823FB0 /* LaunchScreen.storyboard in Resources */, + 09A9B2972405901B00823FB0 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 09B204AA21412C3100063632 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 237E0C612347852500798AA0 /* Fare.json in Resources */, + 09EE81F221528791004B2B09 /* Quotes.json in Resources */, + 09F8AC05214FE6DF00E293B7 /* UnauthorisedUserInfo.json in Resources */, + FCE694242166517D00FAD591 /* TripInfo.json in Resources */, + 09EE81F121528791004B2B09 /* Availability.json in Resources */, + 094B44B42285B8DE002D2506 /* AddPaymentDetails.json in Resources */, + 09F8AC06214FE6DF00E293B7 /* AuthToken.json in Resources */, + FC1202472146A7CA00161081 /* Error.json in Resources */, + 09B204BC2141366C00063632 /* InvalidData.json in Resources */, + 09F8ABFD214FB61100E293B7 /* PaymentSDKToken.json in Resources */, + 09EE81F021528791004B2B09 /* QuoteListId.json in Resources */, + 09F8ABE9214A9B1500E293B7 /* DriverTracking.json in Resources */, + 09F8AC07214FE6DF00E293B7 /* AuthorisedUserInfo.json in Resources */, + 09EE81E72152585D004B2B09 /* TripSearch.json in Resources */, + FC120251214AB3C000161081 /* InvalidJson.json in Resources */, + FC12024F214A950B00161081 /* LocationInfo.json in Resources */, + 097D4725215128E0008D8D94 /* TripStatus.json in Resources */, + 4032BD1223FC491500C2409B /* AuthExchangeToken.json in Resources */, + 09F8ABF8214BF69400E293B7 /* Places.json in Resources */, + 09F79E4B223ABD5B00D5B0B8 /* GetNonce.json in Resources */, + FC1202452146A4F000161081 /* Empty.json in Resources */, + 4032BD1023FC47B600C2409B /* AuthUserInfo.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4387A63F1E3BAF6D0024FD55 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4387A6481E3BAF6D0024FD55 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 06BC11CA2D549EF713263C56 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-KarhooSDKIntegrationTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 0BF8BB3613BD1C8CF483C305 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-KarhooSDKTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2014A37392CD9E66BD9E2383 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-KarhooSDK-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 32FFC923FC0F66E8A9589C93 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Client-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 431301B31E5B20EF00059E51 /* Run Script: Swift lint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script: Swift lint"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; + }; + B280F30C5CEA625D85EA4597 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Client/Pods-Client-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", + "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Client/Pods-Client-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C38E9B8E57F0ED6EBC169BC6 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-KarhooSDKTests/Pods-KarhooSDKTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", + "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-KarhooSDKTests/Pods-KarhooSDKTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + EA73028E782494613EF1B9E2 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-KarhooSDKIntegrationTests/Pods-KarhooSDKIntegrationTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/OHHTTPStubs/OHHTTPStubs.framework", + "${BUILT_PRODUCTS_DIR}/KeychainSwift/KeychainSwift.framework", + "${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OHHTTPStubs.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/KeychainSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-KarhooSDKIntegrationTests/Pods-KarhooSDKIntegrationTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 09A9B2872405901900823FB0 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 09A9B2922405901900823FB0 /* ViewController.swift in Sources */, + 40FBF609240FBB2E007F6A54 /* AuthRequestsTesterViewController.swift in Sources */, + 09A9B28E2405901900823FB0 /* AppDelegate.swift in Sources */, + 09A9B2902405901900823FB0 /* SceneDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 09B204A821412C3100063632 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FC120256214FF0AE00161081 /* RegisterMethodSpec.swift in Sources */, + 09F8ABF6214BF4F200E293B7 /* PlaceSearchMethodSpec.swift in Sources */, + FC12024C214A840E00161081 /* LocationInfoMethodSpec.swift in Sources */, + 5C224EB7223DDB8A00C1CFCE /* AddPaymentDetailsMethodSpec.swift in Sources */, + 095A03DE2333D926007D805E /* UIConfigMethodSpec.swift in Sources */, + FC12025D215129DC00161081 /* TrackTripMethodSpec.swift in Sources */, + 098C91F92147CDC500F02DE3 /* GeneralErrorSpec.swift in Sources */, + FC120253214BF82400161081 /* ReverseGeocodeMethodSpec.swift in Sources */, + 098D524F23FB14FA00059086 /* AuthLoginMethodSpec.swift in Sources */, + 099613712408359A00C8C88E /* RevokeMethodSpec.swift in Sources */, + 0916BB67235DEA580064A5D6 /* MockSDKConfig.swift in Sources */, + 097D472721512D5E008D8D94 /* TripSearchMethodSpec.swift in Sources */, + 09F90F912150FA2C003BC867 /* PasswordResetMethodSpec.swift in Sources */, + 09F8ABEC214A9B5700E293B7 /* DriverTrackingMethodSpec.swift in Sources */, + FC1202612152567C00161081 /* BookTripMethodSpec.swift in Sources */, + 09F90F942151008C003BC867 /* CancelTripMethodSpec.swift in Sources */, + 098C91FB2147FF9900F02DE3 /* RawKarhooErrorFactory.swift in Sources */, + 09F8ABEE214AABBD00E293B7 /* NetworkStubFactory.swift in Sources */, + 09430E4B2240054A009846A6 /* IntegrationTestSetup.swift in Sources */, + 09EE81EB21526350004B2B09 /* LocationInfoMock.swift in Sources */, + 09F8AC00214FDF4200E293B7 /* LoginMethodSpec.swift in Sources */, + 097D472321511208008D8D94 /* TripStatusMethodSpec.swift in Sources */, + 09F79E44223A99E700D5B0B8 /* GetNonceMethodSpec.swift in Sources */, + 09F79E48223AB40000D5B0B8 /* NonceRequestPayload.swift in Sources */, + 4095037B234CCF5A00C6F7A5 /* FareMethodSpec.swift in Sources */, + 09EE81EA2152617B004B2B09 /* QuoteSearchMethodSpec.swift in Sources */, + 09F8ABFB214FB4C900E293B7 /* PaymentSDKTokenMethod.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4387A63C1E3BAF6D0024FD55 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FCD32FF720E252680044BB64 /* KarhooTripUpdateInteractor.swift in Sources */, + 5C15D5EA203711D200B47A71 /* BookingSearch.swift in Sources */, + 313DC2DD20221D0100D9DD1B /* Vehicle.swift in Sources */, + 5C75D6572080E4E60065133B /* KarhooCodableModel.swift in Sources */, + FC861B5E2110B7C0004CC47A /* HTTPError.swift in Sources */, + 09AF9AA920D174150046D725 /* RefreshToken.swift in Sources */, + 5C224EB1223DCD2000C1CFCE /* KarhooAddPaymentDetailsInteractor.swift in Sources */, + 43EF91691EE93DF6006ECB75 /* KarhooTripService.swift in Sources */, + 312B1AAE1FA0982800BB27CF /* NetworkConnectionTypeProvider.swift in Sources */, + 43584E7A1EE56EDC00B4DFAB /* Errors.swift in Sources */, + 2359B46223425299008433A1 /* UserUpdatePayload.swift in Sources */, + 09928D2520EFAABA00B9F462 /* CategoryQuoteMapper.swift in Sources */, + FC321EE220F8D56400B2C3CD /* KarhooDriverTrackingService.swift in Sources */, + 31EC3F611F9E4C24008CD637 /* DeviceIdentifierProvider.swift in Sources */, + 4371C59E1EB0E7F70059429D /* LogAnalyticsProvider.swift in Sources */, + 31ACC66E1FF566C400E8B24F /* KarhooTimingScheduler.swift in Sources */, + 0996137324083B6F00C8C88E /* AccessTokenProvider.swift in Sources */, + 090881E320E662D200EE2C67 /* KarhooLocationInfoInteractor.swift in Sources */, + 43F939A31F3C85A300914E18 /* JsonHttpRequestBuilder.swift in Sources */, + 43F939A11F3C85A300914E18 /* Json.swift in Sources */, + 097BF2B72358710400BBE418 /* KarhooSDKConfiguration.swift in Sources */, + 237E0C59234782B400798AA0 /* KarhooFareService.swift in Sources */, + 432D478A1ECC80E10085DBBE /* Result.swift in Sources */, + 09811A1820D7BB8C001DB06F /* CancelTripRequestPayload.swift in Sources */, + 43850F4C1ECB0056009CB8FD /* KarhooUserService.swift in Sources */, + 5C2856D220E3B127009B9464 /* ReverseGeocodeInteractor.swift in Sources */, + 095A03C02333AF64007D805E /* KarhooUIConfigInteractor.swift in Sources */, + 090E8226235DAD5900B447A2 /* KarhooEnvironment.swift in Sources */, + 09ACCE50207BBB5E0057B0CC /* QuoteListIdRequestPayload.swift in Sources */, + 400BB94323F3027A00AEAAFD /* AuthService.swift in Sources */, + 091A2D0E207D23F600F06E28 /* LocationInfo.swift in Sources */, + 4310E4DC1E99395D00625A0E /* WeakReferenceWrapper.swift in Sources */, + 2359B4602342522E008433A1 /* UpdateUserDetailsInteractor.swift in Sources */, + 0970BA6F21136F870015A170 /* Executable.swift in Sources */, + 4310E4B91E99395D00625A0E /* AvailabilityService.swift in Sources */, + 400BB94823F308BE00AEAAFD /* AuthLoginInteractor.swift in Sources */, + FCBD67B5211C9CE6005AA332 /* PasswordResetInteractor.swift in Sources */, + 099202A720DBB1D20009C845 /* Organisation.swift in Sources */, + 312B1AAC1FA097F900BB27CF /* BatteryMonitor.swift in Sources */, + 312909E21FA0E0C900F5147D /* TimestampFormatter.swift in Sources */, + 4310E4DB1E99395D00625A0E /* Broadcaster.swift in Sources */, + 40F4058F2334C50100D384A6 /* LocationInfoSearch.swift in Sources */, + FC8F475121553184007841FB /* Observer.swift in Sources */, + FC57F945213015E7009193CB /* PollCallFactory.swift in Sources */, + 3150CE2C1F9F7B6000DE6D62 /* UserLocationProvider.swift in Sources */, + 4371C59C1EB0E7F70059429D /* KarhooAnalyticsService.swift in Sources */, + 43D251B61F45C76E005ECB85 /* ReachabilityWrapper.swift in Sources */, + 43F9399F1F3C85A300914E18 /* HttpClient.swift in Sources */, + 43584E8F1EE5960700B4DFAB /* TripService.swift in Sources */, + 31EC3F651F9E50AD008CD637 /* AnalyticsPayloadBuilder.swift in Sources */, + FCD32FE920DA9C4F0044BB64 /* TripSearchInteractor.swift in Sources */, + 4371C59D1EB0E7F70059429D /* AnalyticsProvider.swift in Sources */, + 0900D34B20D2B1920082D1E8 /* RefreshTokenError.swift in Sources */, + 43F9399E1F3C85A300914E18 /* KarhooURLSessionSender.swift in Sources */, + 5CA2CE45234368B7000AEC1E /* UISettings.swift in Sources */, + 313DC2F020221FCF00D9DD1B /* QuoteType.swift in Sources */, + 5C5BE90D2411A513005DD14E /* AuthenticationMethod.swift in Sources */, + 09A56E002097162C00891F02 /* LocationDetailsExt.swift in Sources */, + 43584E871EE590DE00B4DFAB /* Context.swift in Sources */, + 43288A5F1EF3BEB10079506B /* DriverTrackingInfo.swift in Sources */, + 4371C5951EB0E7F70059429D /* KarhooAddressService.swift in Sources */, + 313DC2F220221FDB00D9DD1B /* Quote.swift in Sources */, + 5C75D65D208CC84E0065133B /* LocationDetailsAddress.swift in Sources */, + 313DC2E420221E7B00D9DD1B /* Place.swift in Sources */, + 237E0C5E234783BE00798AA0 /* KarhooFareInteractor.swift in Sources */, + 095E406D20B32DAE008EAF0F /* NetworkDateFormatter.swift in Sources */, + 09928D2A20EFAABA00B9F462 /* KarhooQuoteService.swift in Sources */, + 2359B45F2342522E008433A1 /* KarhooUpdateUserDetailsInteractor.swift in Sources */, + 40FBF607240EAFD9007F6A54 /* AuthHeaderKeys.swift in Sources */, + 09F79E382239547A00D5B0B8 /* KarhooGetNonceInteractor.swift in Sources */, + 091BE6EA2085192800BA34D0 /* PlaceSearch.swift in Sources */, + 095A03C72333B115007D805E /* UIConfigProvider.swift in Sources */, + 091BE6EC2086562900BA34D0 /* Position.swift in Sources */, + FCD4DA5720F37FB2007D4374 /* AvailabilitySearch.swift in Sources */, + 099AF2F920F8BA0000A2AA57 /* TripState.swift in Sources */, + 43850F3F1ECB003E009CB8FD /* UserService.swift in Sources */, + 09928D2C20EFAABA00B9F462 /* QuoteInteractor.swift in Sources */, + 09F79E3A223955FF00D5B0B8 /* NonceRequestPayload.swift in Sources */, + FC861B2E21107734004CC47A /* KarhooSDKError.swift in Sources */, + 43D395C71ECF9ECD00499E8F /* PaymentService.swift in Sources */, + 313DC2D120221A0800D9DD1B /* Driver.swift in Sources */, + 4371C59B1EB0E7F70059429D /* AnalyticsConstants.swift in Sources */, + 31F9168A2023559700104777 /* AnalyticsService.swift in Sources */, + 09CE9A5620FE530700874092 /* TripType.swift in Sources */, + 31904CD41FFFC04400BA7402 /* KarhooRefreshTokenInteractor.swift in Sources */, + 09928D4720EFAB3000B9F462 /* QuoteSearch.swift in Sources */, + 43F939A01F3C85A300914E18 /* HttpConstants.swift in Sources */, + 5C103FA42411DF7F00F5F2F9 /* GuestSettings.swift in Sources */, + 0907B98C20D7B901008915B1 /* CancelTripInteractor.swift in Sources */, + 43D395CA1ECF9F5B00499E8F /* KarhooPaymentService.swift in Sources */, + FC8EB5C9216507B7009DC8DE /* TripQuote.swift in Sources */, + 317610EC1FD84B1E00D2DB75 /* KarhooNetworkDateFormatter.swift in Sources */, + FCBD67B7211C9D7E005AA332 /* RegisterInteractor.swift in Sources */, + 5C224EAF223DCA9900C1CFCE /* AddPaymentDetailsInteractor.swift in Sources */, + FC8EB5CB21651AA0009DC8DE /* VehicleAttributes.swift in Sources */, + 317610F21FD86A9600D2DB75 /* KarhooAvailabilityService.swift in Sources */, + 312CDFB61F6A984A006F48DD /* AppStateNotifier.swift in Sources */, + 43F939A41F3C85A300914E18 /* KarhooEnvironmentDetails.swift in Sources */, + 43F939A51F3C85A300914E18 /* URLSessionSender.swift in Sources */, + 09820E6A20D7E5E800782530 /* KarhooRegisterInteractor.swift in Sources */, + 4310E4C21E99395D00625A0E /* Karhoo.swift in Sources */, + 43584E6E1EE54D6400B4DFAB /* TripInfo.swift in Sources */, + 31904CD61FFFE26F00BA7402 /* TokenRefreshingHttpClient.swift in Sources */, + 092F27C722B1353D00AF8E0E /* PickUpType.swift in Sources */, + 43466AE81EDECEED0087BC51 /* Credentials.swift in Sources */, + 43F939A21F3C85A300914E18 /* JsonHttpClient.swift in Sources */, + 09928D2920EFAABA00B9F462 /* QuoteService.swift in Sources */, + 5C103FA22411C31F00F5F2F9 /* TokenExchangeSettings.swift in Sources */, + 4310E4B81E99395D00625A0E /* AddressService.swift in Sources */, + 095A03C42333AFED007D805E /* UIConfigRequest.swift in Sources */, + 5C2856CF20E3B127009B9464 /* PlaceSearchInteractor.swift in Sources */, + 237E0C572347823A00798AA0 /* FareService.swift in Sources */, + 274215324F8425FAEC59FF36 /* CredentialParser.swift in Sources */, + 5C1E32A4211F015D00651BB9 /* KarhooVoid.swift in Sources */, + 274212DB927ADC241604D704 /* UserDataStore.swift in Sources */, + 0970BA7021136F870015A170 /* KarhooPollableExecutor.swift in Sources */, + 27421B8B6B1B4722C040B399 /* APIEndpoint.swift in Sources */, + 400BB94623F3066500AEAAFD /* KarhooAuthService.swift in Sources */, + 0970BA7321136F870015A170 /* KarhooObservable.swift in Sources */, + 09A56DFC20922D5C00891F02 /* MeetingPointType.swift in Sources */, + 09CE9A5420FE1F2E00874092 /* TripSearch.swift in Sources */, + 404484E223FB00A900CD0347 /* KarhooAuthRevokeInteractor.swift in Sources */, + 274217A11D83A12132022A6A /* HeaderProvider.swift in Sources */, + 09F79E3C2239564600D5B0B8 /* Payer.swift in Sources */, + 27421FA3FF444C478139288D /* KarhooHeaderProvider.swift in Sources */, + 095A03B923339721007D805E /* ConfigService.swift in Sources */, + 27421847E08F35A3DC467C6D /* HeaderConstants.swift in Sources */, + BAA0A6AFAE41A51A35679357 /* PoiDetails.swift in Sources */, + 0907B98F20D7B964008915B1 /* KarhooCancelTripInteractor.swift in Sources */, + BAA0A3538DD38C00232154EB /* MeetingPoint.swift in Sources */, + 27421E022DB3FB60B83DEFAE /* AvailabilityInteractor.swift in Sources */, + 27421DAC3421BFA9669A5DBB /* KarhooAvailabilityInteractor.swift in Sources */, + 274217F5C000E1C615D797C9 /* Categories.swift in Sources */, + FC57F943212EEC56009193CB /* TripUpdateInteractor.swift in Sources */, + 274219FB67E45564E6C421C7 /* QuoteListId.swift in Sources */, + 2742125CDD868E90FDAED0B4 /* UserInfo.swift in Sources */, + 0970BA7221136F870015A170 /* KarhooCall.swift in Sources */, + 27421C3B80CE03E230AB3863 /* UserRegistration.swift in Sources */, + BAA0AD4ECBE829DE71C521FF /* TripLocationDetails.swift in Sources */, + 09811A1A20D7BD77001DB06F /* CancelReason.swift in Sources */, + 237E0C5C2347838600798AA0 /* FareInteractor.swift in Sources */, + BAA0A599E1EB8C3F8C2F8821 /* PassengerDetails.swift in Sources */, + 095A03BE2333AF4A007D805E /* UIConfigInteractor.swift in Sources */, + BAA0A3A43066DD0F628543FE /* FleetInfo.swift in Sources */, + FCBD67B3211C9CBD005AA332 /* LoginInteractor.swift in Sources */, + 09928D2B20EFAABA00B9F462 /* KarhooQuoteInteractor.swift in Sources */, + 099DFC6C223BEDF800401542 /* AddPaymentDetailsPayload.swift in Sources */, + BAA0A28127E0831F332F8FD7 /* FareComponent.swift in Sources */, + 0947FD3B2121D51900E8BE4A /* KarhooErrorType.swift in Sources */, + BAA0A5ADE570C5E9F2BDAE4D /* Passengers.swift in Sources */, + FCD32FED20DAA1470044BB64 /* TripsRequestPayload.swift in Sources */, + 09928D4920EFAB9300B9F462 /* Quotes.swift in Sources */, + 095A03D02333BABD007D805E /* KarhooUIConfigProvider.swift in Sources */, + BAA0A9B82B56E7732880DF88 /* PositionExt.swift in Sources */, + BAA0A0D9B4689C44C4517E85 /* Places.swift in Sources */, + BAA0A33C16B003E2D9F99AB3 /* PoiType.swift in Sources */, + FC8F474F2154F9D6007841FB /* ObserverBroadcaster.swift in Sources */, + BAA0A32E25B5969B08BEACBD /* PoiDetailsType.swift in Sources */, + 09F79E34223952F900D5B0B8 /* Nonce.swift in Sources */, + BAA0A52F9D9380CE153EA7C6 /* PaymentSDKToken.swift in Sources */, + FC2EE934212425F6001B36D2 /* SDKErrorFactory.swift in Sources */, + 09D0757520D826A700F75E32 /* KarhooLogoutInteractor.swift in Sources */, + FC1B01ED2209D0D20069D7F3 /* QuoteCategory.swift in Sources */, + FC321EE020F8D54E00B2C3CD /* DriverTrackingService.swift in Sources */, + BAA0ABF896D0A71FBF3482B7 /* PaymentSDKTokenInteractor.swift in Sources */, + 095A03C22333AF98007D805E /* UIConfig.swift in Sources */, + BAA0A4B940D784C81D26467F /* KarhooPaymentSDKTokenInteractor.swift in Sources */, + 09F79E362239546E00D5B0B8 /* GetNonceInteractor.swift in Sources */, + 0970BA78211371600015A170 /* KarhooDriverTrackingInteractor.swift in Sources */, + 0970BA7121136F870015A170 /* KarhooPollCall.swift in Sources */, + BAA0A7BF125DFEEECCA06B44 /* PasswordResetRequestPayload.swift in Sources */, + 09CA785820EE5ABD00D3BB6F /* Fare.swift in Sources */, + BAA0A0478693DADB025AE19A /* KarhooPasswordResetInteractor.swift in Sources */, + FC987C7D20EF7949001B8B79 /* CLLocationExt.swift in Sources */, + BAA0A2F241A3762CCC60A9ED /* KarhooBookingInteractor.swift in Sources */, + BAA0A99196851950BE007EC3 /* BookingInteractor.swift in Sources */, + FCC17D792118984B00A25B87 /* KarhooPollCallFactory.swift in Sources */, + FCD32FEB20DA9C5F0044BB64 /* KarhooTripSearchInteractor.swift in Sources */, + 0981F6C920CEC3DE00BF7FB5 /* KarhooLoginInteractor.swift in Sources */, + 0900D34920D2B17B0082D1E8 /* RefreshTokenInteractor.swift in Sources */, + 09D293C222C530170051C455 /* QuoteSource.swift in Sources */, + 094B449E2285AC66002D2506 /* PaymentSDKTokenPayload.swift in Sources */, + BAA0ADC3A54466A62DCB5412 /* TripStatusInteractor.swift in Sources */, + BAA0A113CE860049AD045890 /* KarhooTripStatusInteractor.swift in Sources */, + BAA0A1625DACE366FC991EE7 /* TripStatus.swift in Sources */, + 5C2856D020E3B127009B9464 /* KarhooPlaceSearchInteractor.swift in Sources */, + 09AF9AAB20D1751D0046D725 /* RefreshTokenRequestPayload.swift in Sources */, + 09CE9A5020FCE87800874092 /* TripBooking.swift in Sources */, + 095CEC0620F7573700C8AC4B /* TripCancellation.swift in Sources */, + BAA0A144BA8E2FA9BBCECF9A /* QuoteList.swift in Sources */, + BAA0A5F9C452FEC2B08B557A /* Availability.swift in Sources */, + BAA0A1372FE5B2F6B605114B /* AuthToken.swift in Sources */, + BAA0A47EBFAADA9C274FAF83 /* UserLogin.swift in Sources */, + 5C2856D120E3B127009B9464 /* KarhooReverseGeocodeInteractor.swift in Sources */, + 400BB94B23F308F100AEAAFD /* KarhooAuthLoginInteractor.swift in Sources */, + 400555712350AEDD008AD78F /* TripFare.swift in Sources */, + BAA0A20863C722DEDBD8AB81 /* RequestSender.swift in Sources */, + BAA0A6EAF352449EA13B4994 /* KarhooRequestSender.swift in Sources */, + FC861B2C21107727004CC47A /* KarhooError.swift in Sources */, + 5C2856CE20E3B127009B9464 /* LocationInfoInteractor.swift in Sources */, + 095A03BB2333ADC2007D805E /* KarhooConfigService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4387A6461E3BAF6D0024FD55 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FC57F94821301637009193CB /* MockPollCallFactory.swift in Sources */, + 432D47871ECC31940085DBBE /* KarhooLogoutInteractorSpec.swift in Sources */, + 4371C5C11EB0E8070059429D /* AnalyticsServiceSpec.swift in Sources */, + 4322B48C1E69A57B002108D5 /* MockAnalyticsService.swift in Sources */, + 091CAE3E20D7A81F005F1DB6 /* MockURLSessionSender.swift in Sources */, + 31FC37FF1F459A780012A57E /* DefaultAuthTokenSpec.swift in Sources */, + 09811A1E20D7C12D001DB06F /* MockCancelTripInteractor.swift in Sources */, + 434B58431E434E5F009E8FB8 /* MockUserDataStore.swift in Sources */, + FC321EE420F8D72C00B2C3CD /* KarhooDriverTrackingServiceSpec.swift in Sources */, + 43584E891EE590FC00B4DFAB /* ContextSpec.swift in Sources */, + 090881DF20E65F7D00EE2C67 /* KarhooPlaceSearchInteractorSpec.swift in Sources */, + 09BDE053212F128800798330 /* MockLocationInfoInteractor.swift in Sources */, + 4310E51A1E99397A00625A0E /* BroadcasterSpec.swift in Sources */, + FCE11B3820F6272400DFC641 /* AvailabilitySearchMock.swift in Sources */, + 096B31BC20B702AE002B41E9 /* MockBookingInteractor.swift in Sources */, + 43F939B61F3CD33200914E18 /* JsonHttpRequestBuilderSpec.swift in Sources */, + 431740D51ECB257A00CD217F /* KarhooUserServiceSpec.swift in Sources */, + 237E0C672347897B00798AA0 /* MockFareInteractor.swift in Sources */, + 317210CF1FFE7330004CE129 /* CredentialsSpec.swift in Sources */, + FCD4DA5A20F39AD0007D4374 /* MockAvailabilityInteractor.swift in Sources */, + 5C2856F220E3B1DD009B9464 /* LocationInfoMock.swift in Sources */, + 4310E5061E99397A00625A0E /* UserDataStoreSpec.swift in Sources */, + 43584E8D1EE591C700B4DFAB /* ObjectTestFactory.swift in Sources */, + 0981F6C720CEC27A00BF7FB5 /* MockRequestSender.swift in Sources */, + 4310E4711E9928C800625A0E /* MockBroadcaster.swift in Sources */, + 09F79E41223A63DA00D5B0B8 /* NonceRequestPayload.swift in Sources */, + 09820E7220D7F60100782530 /* UserRegistrationMock.swift in Sources */, + 095A03E02333DFCB007D805E /* MockUIConfigInteractor.swift in Sources */, + 4371C5C21EB0E8070059429D /* LogAnalyticsProviderSpec.swift in Sources */, + 5C2856E220E3B184009B9464 /* KarhooReverseGeocodeProviderSpec.swift in Sources */, + 4032BD1423FC5B2300C2409B /* KarhooAuthLoginInteractorSpec.swift in Sources */, + 43588C661EBB675100D03765 /* TestUtil.swift in Sources */, + 31A1AD631F39E1E200BFE24B /* MockHttpClient.swift in Sources */, + 0970BA7E211464C40015A170 /* MockExecutable.swift in Sources */, + 31005DA71FCEE699006D2A7D /* RequestTesting.swift in Sources */, + 43584E931EE5993700B4DFAB /* KarhooTripServiceSpec.swift in Sources */, + 09928D3820EFAAEE00B9F462 /* KarhooQuoteInteractorSpec.swift in Sources */, + 31904CDA200380A400BA7402 /* MockAccessTokenProvider.swift in Sources */, + 4371C5BE1EB0E8070059429D /* KarhooAddressServiceSpec.swift in Sources */, + 317610EE1FD84CD200D2DB75 /* KarhooNetworkDateFormatterSpec.swift in Sources */, + 096A2C2D208F676000C7B7D0 /* PositionExtSpec.swift in Sources */, + 09928D3720EFAAEE00B9F462 /* KarhooQuoteServiceSpec.swift in Sources */, + 43D395D51ECFA31900499E8F /* KarhooPaymentServiceSpec.swift in Sources */, + 0907B99220D7B98A008915B1 /* KarhooCancelTripInteractorSpec.swift in Sources */, + 090881E520E663E600EE2C67 /* KarhooLocationInfoInteractorSpec.swift in Sources */, + 5C870D2920CDBC9C0039A2BF /* LoginRequestPayloadMock.swift in Sources */, + 09820E7020D7F07800782530 /* KarhooRegisterInteractorSpec.swift in Sources */, + 09928D4520EFAB1300B9F462 /* MockQuoteInteractor.swift in Sources */, + 5C224EB4223DD83000C1CFCE /* MockAddPaymentDetailsInteractor.swift in Sources */, + 4310E5071E99397A00625A0E /* KarhooSpec.swift in Sources */, + 317610F41FD9855D00D2DB75 /* KarhooAvailabilityServiceSpec.swift in Sources */, + 317210C21FFD2476004CE129 /* MockAppStateNotifier.swift in Sources */, + 233DB46923433F0400BCBC3F /* UserUpdateMock.swift in Sources */, + 43DD68F61ECD83F2005159C8 /* ResultSpec.swift in Sources */, + 095A03D52333C235007D805E /* MockUIConfigProvider.swift in Sources */, + FC0BF05B20E39D2D0004DF61 /* MockTripUpdateInteractor.swift in Sources */, + 095A03D22333C119007D805E /* KarhooUIConfigProviderSpec.swift in Sources */, + 0981F6CF20CFDA9B00BF7FB5 /* KarhooRequestSenderSpec.swift in Sources */, + 094C1F7F20B2C956001FFE7F /* MockNetworkDateFormatter.swift in Sources */, + 0970BA802114671A0015A170 /* KarhooObservableSpec.swift in Sources */, + 090670E820A4787600CAFA86 /* KarhooPaymentSDKTokenInteractorSpec.swift in Sources */, + FCD32FEF20DAA33A0044BB64 /* KarhooTripsListInteractorSpec.swift in Sources */, + 098D525223FB157B00059086 /* KarhooAuthRevokeInteractorSpec.swift in Sources */, + 434D30A91EE150BA00CEE07B /* CredentiasParserSpec.swift in Sources */, + 43D251B81F46E29C005ECB85 /* ReachabilityWrapperSpec.swift in Sources */, + 31904CDC2003EFFE00BA7402 /* MockNetworkRequest.swift in Sources */, + 237E0C692347931D00798AA0 /* KarhooFareInteractorSpec.swift in Sources */, + 3150CE2F1F9F7BBA00DE6D62 /* UserLocationProviderSpec.swift in Sources */, + 09BDE055212F147600798330 /* MockReverseGeocodeInteractor.swift in Sources */, + 5C076FFF20F23B5F00A35537 /* CategoryQuoteMapperSpec.swift in Sources */, + 317210D11FFE86B4004CE129 /* KarhooRefreshTokenInteractorSpec.swift in Sources */, + 099AF2FB20F8BDF000A2AA57 /* TripInfoMock.swift in Sources */, + FCD32FFA20E26DC10044BB64 /* KarhooTripUpdateInteractortSpec.swift in Sources */, + 5C08DFB120E5A8D7002203CB /* MockContext.swift in Sources */, + 4310E51B1E99397A00625A0E /* WeakReferenceWrapperSpec.swift in Sources */, + 3150CE311F9F7D8E00DE6D62 /* MockCLLocationManager.swift in Sources */, + 2359B45B234251FB008433A1 /* KarhooUpdateUserDetailsInteractorSpec.swift in Sources */, + 09430E4F224109D6009846A6 /* UnitTestSetup.swift in Sources */, + 0981F6D520CFE1B300BF7FB5 /* MockLoginInteractor.swift in Sources */, + 09F79E47223AAA6600D5B0B8 /* MockGetNonceInteractor.swift in Sources */, + 31904CD82003800000BA7402 /* TokenRefreshingHttpClientSpec.swift in Sources */, + 43584E9F1EE6A3D400B4DFAB /* MockUserDefaults.swift in Sources */, + 43F939B51F3CD33200914E18 /* JsonHttpClientSpec.swift in Sources */, + 310D44AE1FA1F8890038BFDA /* TimestampFormatterSpec.swift in Sources */, + 095A03CD2333B3F8007D805E /* KarhooUIConfigInteractorSpec.swift in Sources */, + 2742197A13ACCCDBAE32D13A /* HeaderProviderSpec.swift in Sources */, + 09F79E3F2239723200D5B0B8 /* KarhooGetNonceInteractorSpec.swift in Sources */, + 5C2E265024282B0400B1FF0C /* KarhooAddPaymentDetailsSpec.swift in Sources */, + 5C08DFB420E5A92A002203CB /* MockAnalyticsProvider.swift in Sources */, + BAA0AD9A7DADB9A66E5705F3 /* MockPaymentSDKTokenInteractor.swift in Sources */, + FCD4DA5C20F3A5C5007D4374 /* KarhooAvailabilityInteractorSpec.swift in Sources */, + BAA0A6A2B3C0BB130896465E /* KarhooPasswordResetInteractorSpec.swift in Sources */, + BAA0A2C1640158EABC2C8409 /* MockPasswordResetInteractor.swift in Sources */, + 095A03CB2333B3E6007D805E /* KarhooConfigServiceSpec.swift in Sources */, + 0970BA7B21145E550015A170 /* KarhooCallSpec.swift in Sources */, + BAA0A2B39435CDDCC9E3B772 /* KarhooBookingInteractorSpec.swift in Sources */, + 0970BA8821146DF80015A170 /* MockPollExecutor.swift in Sources */, + 096B31C020B702E8002B41E9 /* MockTripSearchInteractor.swift in Sources */, + BAA0A9E6A724F31D9BCFD880 /* KarhooTripStatusInteractorSpec.swift in Sources */, + 0981F6CB20CEE1AD00BF7FB5 /* KarhooLoginInteractorSpec.swift in Sources */, + 0970BA8A2114970A0015A170 /* KarhooPollCallSpec.swift in Sources */, + 237E0C64234788F000798AA0 /* KarhooFareServiceSpec.swift in Sources */, + 09820E7420D809E000782530 /* MockRegisterInteractor.swift in Sources */, + BAA0AA94DD5A84DFCB2F757C /* TripBookingMock.swift in Sources */, + FC2D95722113612300201BDB /* Error.swift in Sources */, + BAA0AC4CCFCCFBAAE287219C /* PasswordResetRequestPayloadMock.swift in Sources */, + BAA0A16B38001BD6031859AF /* CategoriesMock.swift in Sources */, + BAA0A91AB3B45A6A56034A0F /* UserInfoMock.swift in Sources */, + BAA0A49004C72C6E7EADBED9 /* QuoteIdMock.swift in Sources */, + BAA0AD6977BF4A2BF4E00673 /* PaymentSDKTokenMock.swift in Sources */, + 09820E7820D8172D00782530 /* MockLogoutInteractor.swift in Sources */, + FC0BF09C20E3CB4F0004DF61 /* MockRefreshTokenRequest.swift in Sources */, + BAA0AE32283EBDE47DBE35BD /* TripStatusMock.swift in Sources */, + 090881E120E65FAD00EE2C67 /* MockPlaceSearchInteractor.swift in Sources */, + 09A18BD921185080009F927B /* KarhooDriverTrackingInteractorSpec.swift in Sources */, + BAA0A8B83EF64CFCC23678EC /* MockTripStatusInteractor.swift in Sources */, + BAA0AA88F01A05227A1CC69A /* MockTimingScheduler.swift in Sources */, + 0970BA8C21149FC60015A170 /* KarhooPollableExecutorSpec.swift in Sources */, + 0970BA822114685B0015A170 /* MockKarhooCodableModel.swift in Sources */, + 09AF9AAD20D17B7D0046D725 /* RefreshTokenMock.swift in Sources */, + 0981F6CD20CEEB1000BF7FB5 /* AuthTokenMock.swift in Sources */, + FC8F475321555025007841FB /* MockObserverBroadcaster.swift in Sources */, + 274211D129AC87080586E523 /* DriverTrackingInfoMock.swift in Sources */, + FCBD67B9211DDD8E005AA332 /* MockInteractor.swift in Sources */, + 5CA2CE422343510E000AEC1E /* OrganisationMock.swift in Sources */, + 233DB46723433EC200BCBC3F /* MockUpdateUserDetailsInteractor.swift in Sources */, + 27421AAB7F1B71B7D55D5B6D /* QuoteMock.swift in Sources */, + 274217A4E1433D9270511C44 /* QuoteListMock.swift in Sources */, + FC57F94A21301759009193CB /* MockPollCall.swift in Sources */, + 09430E4D22410037009846A6 /* MockSDKConfig.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 09B204B321412C3200063632 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4387A6401E3BAF6D0024FD55 /* KarhooSDK */; + targetProxy = 09B204B221412C3200063632 /* PBXContainerItemProxy */; + }; + 4387A64D1E3BAF6D0024FD55 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4387A6401E3BAF6D0024FD55 /* KarhooSDK */; + targetProxy = 4387A64C1E3BAF6D0024FD55 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 09A9B2982405901B00823FB0 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 09A9B2992405901B00823FB0 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 09A9B29D2405901B00823FB0 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6184F30666F41BD1192B66BC /* Pods-Client.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = U7U4Q7YGDH; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Client/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.karhoo.Client; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Debug; + }; + 09A9B29E2405901B00823FB0 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B051EBD7153DFB0DE9C784A0 /* Pods-Client.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = U7U4Q7YGDH; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = Client/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.karhoo.Client; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + }; + name = Release; + }; + 09B204B421412C3200063632 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5616C6061BCA5EB676D5A133 /* Pods-KarhooSDKIntegrationTests.debug.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = U7U4Q7YGDH; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = KarhooSDKIntegrationTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.karhoo.KarhooSDKIntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 09B204B521412C3200063632 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8604EAC56835802F4E0F0F56 /* Pods-KarhooSDKIntegrationTests.release.xcconfig */; + buildSettings = { + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = U7U4Q7YGDH; + GCC_C_LANGUAGE_STANDARD = gnu11; + INFOPLIST_FILE = KarhooSDKIntegrationTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.4; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.karhoo.KarhooSDKIntegrationTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 4387A6531E3BAF6D0024FD55 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 12; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 4387A6541E3BAF6D0024FD55 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 12; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SWIFT3_OBJC_INFERENCE = Off; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 4387A6561E3BAF6D0024FD55 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 90BAB2DA56DC0C76920A8C20 /* Pods-KarhooSDK.debug.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 12; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = U7U4Q7YGDH; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 12; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/SwiftLint\"", + ); + INFOPLIST_FILE = KarhooSDK/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 1.0.0; + MODULEMAP_PRIVATE_FILE = ""; + PRODUCT_BUNDLE_IDENTIFIER = se.bespokecode.KarhooSDK; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = NO; + SWIFT_INCLUDE_PATHS = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 4387A6571E3BAF6D0024FD55 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 13B19E142647D37690651605 /* Pods-KarhooSDK.release.xcconfig */; + buildSettings = { + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 12; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = U7U4Q7YGDH; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 12; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/SwiftLint\"", + ); + INFOPLIST_FILE = KarhooSDK/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 1.0.0; + MODULEMAP_PRIVATE_FILE = ""; + PRODUCT_BUNDLE_IDENTIFIER = se.bespokecode.KarhooSDK; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = NO; + SWIFT_INCLUDE_PATHS = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 4387A6591E3BAF6D0024FD55 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 09E40C17E5179F413E52C435 /* Pods-KarhooSDKTests.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = U7U4Q7YGDH; + GCC_OPTIMIZATION_LEVEL = 0; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/SwiftLint\"", + "\"$(SRCROOT)/KarhooSDK/Networking/GRPC/Compiled/\"", + ); + INFOPLIST_FILE = KarhooSDKTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = se.bespokecode.KarhooSDKTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/KarhooSDK/Api/Networking/GRPC/"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 4387A65A1E3BAF6D0024FD55 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B709CD873DABF2A4D66BCE0C /* Pods-KarhooSDKTests.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEVELOPMENT_TEAM = U7U4Q7YGDH; + GCC_OPTIMIZATION_LEVEL = 0; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/SwiftLint\"", + "\"$(SRCROOT)/KarhooSDK/Networking/GRPC/Compiled/\"", + ); + INFOPLIST_FILE = KarhooSDKTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = se.bespokecode.KarhooSDKTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_INCLUDE_PATHS = "$(SRCROOT)/KarhooSDK/Api/Networking/GRPC/"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 09A9B29C2405901B00823FB0 /* Build configuration list for PBXNativeTarget "Client" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 09A9B29D2405901B00823FB0 /* Debug */, + 09A9B29E2405901B00823FB0 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 09B204B621412C3200063632 /* Build configuration list for PBXNativeTarget "KarhooSDKIntegrationTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 09B204B421412C3200063632 /* Debug */, + 09B204B521412C3200063632 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 4387A63B1E3BAF6D0024FD55 /* Build configuration list for PBXProject "KarhooSDK" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4387A6531E3BAF6D0024FD55 /* Debug */, + 4387A6541E3BAF6D0024FD55 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 4387A6551E3BAF6D0024FD55 /* Build configuration list for PBXNativeTarget "KarhooSDK" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4387A6561E3BAF6D0024FD55 /* Debug */, + 4387A6571E3BAF6D0024FD55 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 4387A6581E3BAF6D0024FD55 /* Build configuration list for PBXNativeTarget "KarhooSDKTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4387A6591E3BAF6D0024FD55 /* Debug */, + 4387A65A1E3BAF6D0024FD55 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4387A6381E3BAF6D0024FD55 /* Project object */; +} diff --git a/KarhooSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/KarhooSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..7f6c69c4 --- /dev/null +++ b/KarhooSDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/KarhooSDK.xcodeproj/xcshareddata/xcschemes/KarhooSDK.xcscheme b/KarhooSDK.xcodeproj/xcshareddata/xcschemes/KarhooSDK.xcscheme new file mode 100644 index 00000000..960a6dec --- /dev/null +++ b/KarhooSDK.xcodeproj/xcshareddata/xcschemes/KarhooSDK.xcscheme @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KarhooSDK.xcodeproj/xcshareddata/xcschemes/KarhooSDKIntegrationTests.xcscheme b/KarhooSDK.xcodeproj/xcshareddata/xcschemes/KarhooSDKIntegrationTests.xcscheme new file mode 100644 index 00000000..dbdb9c6f --- /dev/null +++ b/KarhooSDK.xcodeproj/xcshareddata/xcschemes/KarhooSDKIntegrationTests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/KarhooSDK.xcodeproj/xcshareddata/xcschemes/KarhooSDKTests.xcscheme b/KarhooSDK.xcodeproj/xcshareddata/xcschemes/KarhooSDKTests.xcscheme new file mode 100644 index 00000000..e95bfce3 --- /dev/null +++ b/KarhooSDK.xcodeproj/xcshareddata/xcschemes/KarhooSDKTests.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/KarhooSDK.xcworkspace/contents.xcworkspacedata b/KarhooSDK.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..a19163a9 --- /dev/null +++ b/KarhooSDK.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/KarhooSDK/Api/Context.swift b/KarhooSDK/Api/Context.swift new file mode 100644 index 00000000..6e36a2bb --- /dev/null +++ b/KarhooSDK/Api/Context.swift @@ -0,0 +1,47 @@ +// +// Context.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol Context { + func getSdkBundle() -> Bundle + func getCurrentBundle() -> Bundle + + func isTestflightBuild() -> Bool +} + +public final class CurrentContext: Context { + private static let sdkBundle = Bundle(for: CurrentContext.self) + + private var braintreeToken: String? + + public init() { + } + + func getSdkBundle() -> Bundle { + return CurrentContext.sdkBundle + } + + func getCurrentBundle() -> Bundle { + return Bundle.main + } + + public func isDebugBuild() -> Bool { + #if DEBUG + return true + #else + return false + #endif + } + + internal func isTestflightBuild() -> Bool { + let bundle = getSdkBundle() + let receiptUrl = bundle.appStoreReceiptURL?.path ?? "" + return receiptUrl.contains("sandboxReceipt") + } +} diff --git a/KarhooSDK/Api/DataStore/User/Credentials.swift b/KarhooSDK/Api/DataStore/User/Credentials.swift new file mode 100644 index 00000000..988b735d --- /dev/null +++ b/KarhooSDK/Api/DataStore/User/Credentials.swift @@ -0,0 +1,28 @@ +// +// Credentials.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct Credentials { + let accessToken: String + let expiryDate: Date? + let refreshToken: String? + + init(accessToken: String, expiryDate: Date?, refreshToken: String?) { + self.accessToken = accessToken + self.expiryDate = expiryDate + self.refreshToken = refreshToken + } + + init(accessToken: String, expiresIn: TimeInterval, refreshToken: String?) { + let expiryDate = Date().addingTimeInterval(Double(expiresIn)) + self.init(accessToken: accessToken, + expiryDate: expiryDate, + refreshToken: refreshToken) + } +} diff --git a/KarhooSDK/Api/DataStore/User/UserDataStore.swift b/KarhooSDK/Api/DataStore/User/UserDataStore.swift new file mode 100644 index 00000000..0ee6cda6 --- /dev/null +++ b/KarhooSDK/Api/DataStore/User/UserDataStore.swift @@ -0,0 +1,130 @@ +// +// LoginDataStore.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol UserDataStore { + func getCurrentUser() -> UserInfo? + func getCurrentCredentials() -> Credentials? + func setCurrentUser(user: UserInfo, credentials: Credentials) + func updateCurrentUserNonce(nonce: Nonce?) + func updateUser(user: inout UserInfo) + func removeCurrentUserAndCredentials() + func set(credentials: Credentials) + func add(observer: UserStateObserver) + func remove(observer: UserStateObserver) +} + +private let staticBroadcaster = Broadcaster() + +final class DefaultUserDataStore: UserDataStore { + + static let currentUserKey = "currentUser" + static let currentCredentialsKey = "currentCredentials" + + private let persistantStore: UserDefaults + private let credentialsParser: CredentialsParser + private let broadcaster: Broadcaster + + init(persistantStore: UserDefaults = UserDefaults.standard, + credentialsParser: CredentialsParser = DefaultCredentialsParser(), + broadcaster: Broadcaster = staticBroadcaster) { + self.persistantStore = persistantStore + self.credentialsParser = credentialsParser + self.broadcaster = broadcaster + } + + func getCurrentUser() -> UserInfo? { + guard let rawData = persistantStore.value(forKey: DefaultUserDataStore.currentUserKey) as? Data else { + return nil + } + + let user = try? JSONDecoder().decode(UserInfo.self, from: rawData) + return user?.userId.isEmpty == true ? nil : user + } + + func getCurrentCredentials() -> Credentials? { + let rawData = persistantStore.value(forKey: DefaultUserDataStore.currentCredentialsKey) as? [String: Any] + let credentials = credentialsParser.from(dictionary: rawData) + + return credentials + } + + func set(credentials: Credentials) { + let updatedCredentials = credentialsParser.from(credentials: credentials) + persistantStore.set(updatedCredentials, forKey: DefaultUserDataStore.currentCredentialsKey) + persistantStore.synchronize() + } + + func setCurrentUser(user: UserInfo, credentials: Credentials) { + guard let userData = user.encode() else { + return + } + persistantStore.set(userData, forKey: DefaultUserDataStore.currentUserKey) + + let credentials = credentialsParser.from(credentials: credentials) + persistantStore.set(credentials, forKey: DefaultUserDataStore.currentCredentialsKey) + persistantStore.synchronize() + + broadcastChange() + } + + func updateCurrentUserNonce(nonce: Nonce?) { + guard var user = getCurrentUser() else { + return + } + + user.nonce = nonce + + guard let updatedUserData = user.encode() else { + return + } + + persistantStore.set(updatedUserData, forKey: DefaultUserDataStore.currentUserKey) + broadcastChange() + } + + func updateUser( user: inout UserInfo) { + if let currentNonce = getCurrentUser()?.nonce { + user.nonce = currentNonce + } + + guard let newUserData = user.encode() else { + return + } + + persistantStore.set(newUserData, forKey: DefaultUserDataStore.currentUserKey) + persistantStore.synchronize() + broadcastChange() + } + + func removeCurrentUserAndCredentials() { + persistantStore.removeObject(forKey: DefaultUserDataStore.currentUserKey) + persistantStore.removeObject(forKey: DefaultUserDataStore.currentCredentialsKey) + persistantStore.synchronize() + + broadcastChange() + } + + func add(observer: UserStateObserver) { + broadcaster.add(listener: observer) + } + + func remove(observer: UserStateObserver) { + broadcaster.remove(listener: observer) + } + + private func broadcastChange() { + broadcaster.broadcast { (observer: AnyObject) in + guard let observer = observer as? UserStateObserver else { + return + } + observer.userStateUpdated(user: getCurrentUser()) + } + } +} diff --git a/KarhooSDK/Api/Karhoo.swift b/KarhooSDK/Api/Karhoo.swift new file mode 100644 index 00000000..ea15f9aa --- /dev/null +++ b/KarhooSDK/Api/Karhoo.swift @@ -0,0 +1,111 @@ +// +// Karhoo.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public final class Karhoo { + + public private(set) static var configuration: KarhooSDKConfiguration! + + public class func set(configuration: KarhooSDKConfiguration) { + self.configuration = configuration + } + + private static var userService: UserService? + public class func getUserService() -> UserService { + if userService == nil { + userService = KarhooUserService() + } + return userService! + } + + private static var availabilityService: AvailabilityService? + public static func getAvailabilityService() -> AvailabilityService { + if availabilityService == nil { + availabilityService = KarhooAvailabilityService() + } + return availabilityService! + } + + private static var addressService: AddressService? + public static func getAddressService() -> AddressService { + if addressService == nil { + addressService = KarhooAddressService() + } + return addressService! + } + + public static func getAnalyticsService() -> AnalyticsService { + return KarhooAnalyticsService() + } + + private static var quoteService: KarhooQuoteService? + public static func getQuoteService() -> QuoteService { + if quoteService == nil { + quoteService = KarhooQuoteService() + } + return quoteService! + } + + private static var paymentService: PaymentService? + public static func getPaymentService() -> PaymentService { + if paymentService == nil { + paymentService = KarhooPaymentService() + } + return paymentService! + } + + private static var tripService: KarhooTripService? + public static func getTripService() -> TripService { + + if tripService == nil { + tripService = KarhooTripService() + } + return tripService! + } + + private static var driverTrackingService: KarhooDriverTrackingService? + public static func getDriverTrackingService() -> DriverTrackingService { + if driverTrackingService == nil { + driverTrackingService = KarhooDriverTrackingService() + } + return driverTrackingService! + } + + private static var configService: ConfigService? + public static func getConfigService() -> ConfigService { + if configService == nil { + configService = KarhooConfigService() + } + + return configService! + } + + private static var fareService: FareService? + public static func getFareService() -> FareService { + if fareService == nil { + fareService = KarhooFareService() + } + + return fareService! + } + + private static var authService: AuthService? + public class func getAuthService() -> AuthService { + if authService == nil { + authService = KarhooAuthService() + } + return authService! + } + + public final class Utils { + public class func getBroadcaster(ofType: T.Type) -> Broadcaster { + return Broadcaster() + } + } +} diff --git a/KarhooSDK/Api/KarhooSDK.h b/KarhooSDK/Api/KarhooSDK.h new file mode 100644 index 00000000..bfef63cf --- /dev/null +++ b/KarhooSDK/Api/KarhooSDK.h @@ -0,0 +1,18 @@ +// +// KarhooSDK.h +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +#import + +//! Project version number for KarhooSDK. +FOUNDATION_EXPORT double KarhooSDKVersionNumber; + +//! Project version string for KarhooSDK. +FOUNDATION_EXPORT const unsigned char KarhooSDKVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + diff --git a/KarhooSDK/Api/Observable/Executable.swift b/KarhooSDK/Api/Observable/Executable.swift new file mode 100644 index 00000000..0edf7bfd --- /dev/null +++ b/KarhooSDK/Api/Observable/Executable.swift @@ -0,0 +1,14 @@ +// +// KarhooExecutable.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol KarhooExecutable { + func execute(callback: @escaping CallbackClosure) + func cancel() +} diff --git a/KarhooSDK/Api/Observable/KarhooCall.swift b/KarhooSDK/Api/Observable/KarhooCall.swift new file mode 100644 index 00000000..e0d8087f --- /dev/null +++ b/KarhooSDK/Api/Observable/KarhooCall.swift @@ -0,0 +1,22 @@ +// +// Call.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +open class Call { + + private let executable: KarhooExecutable + + public init(executable: KarhooExecutable) { + self.executable = executable + } + + open func execute(callback: @escaping CallbackClosure) { + executable.execute(callback: callback) + } +} diff --git a/KarhooSDK/Api/Observable/KarhooObservable.swift b/KarhooSDK/Api/Observable/KarhooObservable.swift new file mode 100644 index 00000000..0a2a987d --- /dev/null +++ b/KarhooSDK/Api/Observable/KarhooObservable.swift @@ -0,0 +1,43 @@ +// +// KarhooObservable.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +open class Observable { + + private let pollExecutor: KarhooPollExecutor + private let broadcaster: ObserverBroadcaster + private let pollTime: TimeInterval + + public init(pollExecutor: KarhooPollExecutor, + pollTime: TimeInterval, + broadcaster: ObserverBroadcaster = ObserverBroadcaster()) { + self.pollExecutor = pollExecutor + self.pollTime = pollTime + self.broadcaster = broadcaster + } + + open func subscribe(observer: Observer) { + let shouldStartPolling = broadcaster.hasListeners() == false + broadcaster.add(listener: observer) + + if shouldStartPolling { + pollExecutor.startPolling(pollTime: pollTime) { result in + // Intentionally strong reference to self, KarhooDisposable is taking care of disposal + self.broadcaster.broadcast(result: result) + } + } + } + + open func unsubscribe(observer: Observer) { + broadcaster.remove(listener: observer) + if broadcaster.hasListeners() == false { + pollExecutor.stopPolling() + } + } +} diff --git a/KarhooSDK/Api/Observable/KarhooPollCall.swift b/KarhooSDK/Api/Observable/KarhooPollCall.swift new file mode 100644 index 00000000..c3fc4d91 --- /dev/null +++ b/KarhooSDK/Api/Observable/KarhooPollCall.swift @@ -0,0 +1,28 @@ +// +// PollCall.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +open class PollCall: Call { + + private let pollExecutor: KarhooPollExecutor + private var observablesForPolltime: [TimeInterval: Observable] = [:] + + public init(pollExecutor: KarhooPollExecutor) { + self.pollExecutor = pollExecutor + super.init(executable: pollExecutor.executable) + } + + open func observable(pollTime: TimeInterval = 5) -> Observable { + if observablesForPolltime[pollTime] == nil { + observablesForPolltime[pollTime] = Observable(pollExecutor: pollExecutor, + pollTime: pollTime) + } + return observablesForPolltime[pollTime]! + } +} diff --git a/KarhooSDK/Api/Observable/KarhooPollCallFactory.swift b/KarhooSDK/Api/Observable/KarhooPollCallFactory.swift new file mode 100644 index 00000000..d8cc582b --- /dev/null +++ b/KarhooSDK/Api/Observable/KarhooPollCallFactory.swift @@ -0,0 +1,25 @@ +// +// KarhooPollCallFactory.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooPollCallFactory: PollCallFactory { + private var sharedCalls: [String: AnyObject] = [:] + + func shared(identifier: String, + executable: KarhooExecutable) -> PollCall { + if let call = sharedCalls[identifier] as? PollCall { + return call + } + + let pollExecutor = PollExecutor(executable: executable) + let call = PollCall(pollExecutor: pollExecutor) + sharedCalls[identifier] = call + return call + } +} diff --git a/KarhooSDK/Api/Observable/KarhooPollableExecutor.swift b/KarhooSDK/Api/Observable/KarhooPollableExecutor.swift new file mode 100644 index 00000000..7de75f22 --- /dev/null +++ b/KarhooSDK/Api/Observable/KarhooPollableExecutor.swift @@ -0,0 +1,45 @@ +// +// KarhooPollExecutor +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol KarhooPollExecutor { + var executable: KarhooExecutable { get } + + func startPolling(pollTime: TimeInterval, + callback: @escaping CallbackClosure) + func stopPolling() +} + +final class PollExecutor: KarhooPollExecutor { + + public let executable: KarhooExecutable + private let pollingScheduler: TimingScheduler + + init(pollingScheduler: TimingScheduler = KarhooTimingScheduler(), + executable: KarhooExecutable) { + self.pollingScheduler = pollingScheduler + self.executable = executable + } + + func startPolling(pollTime: TimeInterval, + callback: @escaping CallbackClosure) { + let executableBlock: (() -> Void) = { [weak self] in + self?.executable.execute(callback: callback) + } + + pollingScheduler.fireAndSchedule(block: executableBlock, + in: pollTime, + repeats: true) + } + + func stopPolling() { + pollingScheduler.invalidate() + executable.cancel() + } +} diff --git a/KarhooSDK/Api/Observable/Observer.swift b/KarhooSDK/Api/Observable/Observer.swift new file mode 100644 index 00000000..96a39be9 --- /dev/null +++ b/KarhooSDK/Api/Observable/Observer.swift @@ -0,0 +1,19 @@ +// +// Observer.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public final class Observer { + let uuid: String + public let closure: CallbackClosure + + public init(_ closure: @escaping CallbackClosure) { + self.uuid = UUID().uuidString + self.closure = closure + } +} diff --git a/KarhooSDK/Api/Observable/ObserverBroadcaster.swift b/KarhooSDK/Api/Observable/ObserverBroadcaster.swift new file mode 100644 index 00000000..b21d581a --- /dev/null +++ b/KarhooSDK/Api/Observable/ObserverBroadcaster.swift @@ -0,0 +1,79 @@ +// +// ObserverBroadcaster.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +open class ObserverBroadcaster { + + private var listeners: [String: WeakReferenceWrapper>] = [:] + + // MARK: - Public functions + + required public init() { } + + /** + * @breif Adds a listener to the broadcaster + * @param listener The listener to add. Will not be retained. + * @return Returns the identifier of the listener added + */ + func add(listener: Observer) { + listeners[listener.uuid] = WeakReferenceWrapper(listener) + } + + /** + * @breif Broadcasts closure to all listeners + * @param closure to be broadcasted + */ + func broadcast(result: Result) { + var zombieFound = false + + listeners.forEach { + if let reference = $0.value.getReference() { + reference.closure(result) + } else { + zombieFound = true + } + } + + if zombieFound { + compact() + } + } + + /** + * @breif Removes listener with key + * @param key of the listener to be removed + */ + func remove(listener: Observer) { + listeners.removeValue(forKey: listener.uuid) + } + + /** + * @breif Removes all listeners + */ + func removeAllListeners() { + listeners = [:] + } + + /** + * @breif Returns true if there are any listeners left + */ + func hasListeners() -> Bool { + compact() + return listeners.count > 0 + } + + // MARK: - Private functions + + private func compact() { + let compacted = listeners.filter { + return $0.value.getReference() != nil + } + listeners = compacted + } +} diff --git a/KarhooSDK/Api/Observable/PollCallFactory.swift b/KarhooSDK/Api/Observable/PollCallFactory.swift new file mode 100644 index 00000000..6b77f674 --- /dev/null +++ b/KarhooSDK/Api/Observable/PollCallFactory.swift @@ -0,0 +1,14 @@ +// +// PollCallFactory.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol PollCallFactory { + func shared(identifier: String, + executable: KarhooExecutable) -> PollCall +} diff --git a/KarhooSDK/Api/Parser/CredentialParser.swift b/KarhooSDK/Api/Parser/CredentialParser.swift new file mode 100644 index 00000000..c8ccd718 --- /dev/null +++ b/KarhooSDK/Api/Parser/CredentialParser.swift @@ -0,0 +1,58 @@ +// +// CredentialParser.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol CredentialsParser { + func from(dictionary: [String: Any]?) -> Credentials? + func from(credentials: Credentials?) -> [String: Any]? +} + +final class DefaultCredentialsParser: CredentialsParser { + + func from(dictionary: [String: Any]?) -> Credentials? { + + guard let dictionary = dictionary else { + return nil + } + + return createCredentials(dictionary) + } + + private func createCredentials(_ dictionary: [String: Any]) -> Credentials? { + guard let accessToken = dictionary[CredentialsStoreKeys.accessToken.rawValue] as? String else { + return nil + } + + let expiryDate = dictionary[CredentialsStoreKeys.expiryDate.rawValue] as? Date + let refreshToken = dictionary[CredentialsStoreKeys.refreshToken.rawValue] as? String + + return Credentials(accessToken: accessToken, + expiryDate: expiryDate, + refreshToken: refreshToken) + } + + func from(credentials: Credentials?) -> [String: Any]? { + guard let credentials = credentials else { + return nil + } + + var dictionary: [String: Any] = [:] + + dictionary[CredentialsStoreKeys.accessToken.rawValue] = credentials.accessToken + dictionary[CredentialsStoreKeys.expiryDate.rawValue] = credentials.expiryDate + dictionary[CredentialsStoreKeys.refreshToken.rawValue] = credentials.refreshToken + return dictionary + } +} + +enum CredentialsStoreKeys: String { + case accessToken + case expiryDate + case refreshToken +} diff --git a/KarhooSDK/Api/Request/AddPaymentDetailsPayload.swift b/KarhooSDK/Api/Request/AddPaymentDetailsPayload.swift new file mode 100644 index 00000000..aee0ef90 --- /dev/null +++ b/KarhooSDK/Api/Request/AddPaymentDetailsPayload.swift @@ -0,0 +1,31 @@ +// +// AddPaymentDetailsPayload.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct AddPaymentDetailsPayload: KarhooCodableModel { + + public let nonce: String + public let payer: Payer + public let organisationId: String + + public init(nonce: String = "", + payer: Payer = Payer(), + organisationId: String = "") { + + self.nonce = nonce + self.payer = payer + self.organisationId = organisationId + } + + enum CodingKeys: String, CodingKey { + case nonce + case payer + case organisationId = "organisation_id" + } +} diff --git a/KarhooSDK/Api/Request/AvailabilitySearch.swift b/KarhooSDK/Api/Request/AvailabilitySearch.swift new file mode 100644 index 00000000..32ed6b78 --- /dev/null +++ b/KarhooSDK/Api/Request/AvailabilitySearch.swift @@ -0,0 +1,40 @@ +// +// AvailabilitySearch.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +/** + Used to get availability + + @param originPlaceId: The pickup location place id + + @param destinationPlaceId: The drop off location + + @param dateScheduled: UTC-0 date and time for prebooks, leave blank for ASAP quotes. + + */ + +public struct AvailabilitySearch: KarhooCodableModel, Equatable { + let originPlaceId: String + let destinationPlaceId: String + let dateScheduled: String? + + init(origin: String = "", + destination: String = "", + dateScheduled: String? = nil) { + self.originPlaceId = origin + self.destinationPlaceId = destination + self.dateScheduled = dateScheduled + } + + enum CodingKeys: String, CodingKey { + case originPlaceId = "origin_place_id" + case destinationPlaceId = "destination_place_id" + case dateScheduled = "date_required" + } +} diff --git a/KarhooSDK/Api/Request/CancelReason.swift b/KarhooSDK/Api/Request/CancelReason.swift new file mode 100644 index 00000000..0ce91e9e --- /dev/null +++ b/KarhooSDK/Api/Request/CancelReason.swift @@ -0,0 +1,19 @@ +// +// CancelReason.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum CancelReason: String { + case askedByDriverToCancel = "ASKED_BY_DRIVER_TO_CANCEL" + case notNeededAnymore = "NOT_NEEDED_ANYMORE" + case cannotFindVehicle = "CAN_NOT_FIND_VEHICLE" + case driverIsLate = "DRIVER_IS_LATE" + case etaTooLong = "ETA_TOO_LONG" + case driverDidntShowUp = "DRIVER_DIDNT_SHOW_UP" + case otherUserReason = "OTHER_USER_REASON" +} diff --git a/KarhooSDK/Api/Request/CancelTripRequestPayload.swift b/KarhooSDK/Api/Request/CancelTripRequestPayload.swift new file mode 100644 index 00000000..183eb539 --- /dev/null +++ b/KarhooSDK/Api/Request/CancelTripRequestPayload.swift @@ -0,0 +1,21 @@ +// +// CancelTripRequestPayload.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct CancelTripRequestPayload: KarhooCodableModel { + let reason: String + + init(reason: CancelReason) { + self.reason = reason.rawValue + } + + enum CodingKeys: String, CodingKey { + case reason = "reason" + } +} diff --git a/KarhooSDK/Api/Request/LocationDetailsRequestPayload.swift b/KarhooSDK/Api/Request/LocationDetailsRequestPayload.swift new file mode 100644 index 00000000..201f0c93 --- /dev/null +++ b/KarhooSDK/Api/Request/LocationDetailsRequestPayload.swift @@ -0,0 +1,16 @@ +// +// Created by Jeevan on 24/04/2018. +// Copyright (c) 2018 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +struct LocationInfoRequestPayload: KarhooCodableModel { + let placeId: String + let sessionToken: String + + enum CodingKeys: String, CodingKey { + case placeId = "place_id" + case sessionToken = "session_token" + } +} diff --git a/KarhooSDK/Api/Request/LocationInfoSearch.swift b/KarhooSDK/Api/Request/LocationInfoSearch.swift new file mode 100644 index 00000000..17e362e5 --- /dev/null +++ b/KarhooSDK/Api/Request/LocationInfoSearch.swift @@ -0,0 +1,26 @@ +// +// LocationInfoSearch.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct LocationInfoSearch: KarhooCodableModel { + + public let placeId: String + public let sessionToken: String + + public init(placeId: String, + sessionToken: String) { + self.placeId = placeId + self.sessionToken = sessionToken + } + + enum CodingKeys: String, CodingKey { + case placeId = "place_id" + case sessionToken = "session_token" + } +} diff --git a/KarhooSDK/Api/Request/NonceRequestPayload.swift b/KarhooSDK/Api/Request/NonceRequestPayload.swift new file mode 100644 index 00000000..04ccf6ab --- /dev/null +++ b/KarhooSDK/Api/Request/NonceRequestPayload.swift @@ -0,0 +1,26 @@ +// +// NonceRequestPayload.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct NonceRequestPayload: KarhooCodableModel { + + public let payer: Payer + public let organisationId: String + + public init(payer: Payer = Payer(), + organisationId: String = "") { + self.payer = payer + self.organisationId = organisationId + } + + enum CodingKeys: String, CodingKey { + case payer + case organisationId = "organisation_id" + } +} diff --git a/KarhooSDK/Api/Request/PasswordResetRequestPayload.swift b/KarhooSDK/Api/Request/PasswordResetRequestPayload.swift new file mode 100644 index 00000000..17228749 --- /dev/null +++ b/KarhooSDK/Api/Request/PasswordResetRequestPayload.swift @@ -0,0 +1,15 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct PasswordResetRequestPayload: KarhooCodableModel { + + let email: String + + init(email: String = "") { + self.email = email + } +} diff --git a/KarhooSDK/Api/Request/Payer.swift b/KarhooSDK/Api/Request/Payer.swift new file mode 100644 index 00000000..ee329ded --- /dev/null +++ b/KarhooSDK/Api/Request/Payer.swift @@ -0,0 +1,41 @@ +// +// Payer.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Payer: KarhooCodableModel { + + public let id: String + public let firstName: String + public let lastName: String + public let email: String + + public init(id: String = "", + firstName: String = "", + lastName: String = "", + email: String = "") { + self.id = id + self.firstName = firstName + self.lastName = lastName + self.email = email + } + + public init(user: UserInfo) { + self.init(id: user.userId, + firstName: user.firstName, + lastName: user.lastName, + email: user.email) + } + + enum CodingKeys: String, CodingKey { + case id + case firstName = "first_name" + case lastName = "last_name" + case email + } +} diff --git a/KarhooSDK/Api/Request/PaymentSDKTokenPayload.swift b/KarhooSDK/Api/Request/PaymentSDKTokenPayload.swift new file mode 100644 index 00000000..56b2f03d --- /dev/null +++ b/KarhooSDK/Api/Request/PaymentSDKTokenPayload.swift @@ -0,0 +1,26 @@ +// +// PaymentSDKTokenPayload.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct PaymentSDKTokenPayload: KarhooCodableModel { + + let organisationId: String + let currency: String + + public init(organisationId: String = "", + currency: String = "") { + self.organisationId = organisationId + self.currency = currency + } + + enum CodingKeys: String, CodingKey { + case organisationId = "organisation_id" + case currency + } +} diff --git a/KarhooSDK/Api/Request/PlaceSearch.swift b/KarhooSDK/Api/Request/PlaceSearch.swift new file mode 100644 index 00000000..0a79fe65 --- /dev/null +++ b/KarhooSDK/Api/Request/PlaceSearch.swift @@ -0,0 +1,37 @@ +// +// PlaceSearch.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct PlaceSearch: KarhooCodableModel, Equatable { + + public let position: Position + public let query: String + public let sessionToken: String + + public init(position: Position = Position(), + query: String = "", + sessionToken: String) { + self.position = position + self.query = query + self.sessionToken = sessionToken + } + + enum CodingKeys: String, CodingKey { + case sessionToken = "session_token" + case position + case query + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.position = (try? container.decode(Position.self, forKey: .position)) ?? Position() + self.query = (try? container.decode(String.self, forKey: .query)) ?? "" + self.sessionToken = (try? container.decode(String.self, forKey: .sessionToken)) ?? "" + } +} diff --git a/KarhooSDK/Api/Request/QuoteListIdRequestPayload.swift b/KarhooSDK/Api/Request/QuoteListIdRequestPayload.swift new file mode 100644 index 00000000..d6fddb59 --- /dev/null +++ b/KarhooSDK/Api/Request/QuoteListIdRequestPayload.swift @@ -0,0 +1,29 @@ +// +// QuoteListIdRequestPayload.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct QuoteListIdRequestPayload: Codable, KarhooCodableModel { + let origin: String + let destination: String + let dateScheduled: String? + + init(origin: String = "", + destination: String = "", + dateScheduled: String? = nil) { + self.origin = origin + self.destination = destination + self.dateScheduled = dateScheduled + } + + enum CodingKeys: String, CodingKey { + case origin = "origin_place_id" + case destination = "destination_place_id" + case dateScheduled = "local_time_of_pickup" + } +} diff --git a/KarhooSDK/Api/Request/QuoteSearch.swift b/KarhooSDK/Api/Request/QuoteSearch.swift new file mode 100644 index 00000000..c17a0c5a --- /dev/null +++ b/KarhooSDK/Api/Request/QuoteSearch.swift @@ -0,0 +1,40 @@ +// +// QuoteSearch.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +/** + Used to get a list of Quotes. + + @param origin: The pickup location for a booking. + + @param destination: The drop off location for a booking. + + @param dateScheduled: UTC-0 date and time for prebooks, leave blank for ASAP quotes. + + */ +public struct QuoteSearch: KarhooCodableModel { + + public let origin: LocationInfo + public let destination: LocationInfo + + /** + The date/time you would like to book for in UTC-0. + That Will be converted to a ISO-8601 string + Leave nil for asap quotes. + */ + public let dateScheduled: Date? + + public init(origin: LocationInfo = LocationInfo(), + destination: LocationInfo = LocationInfo(), + dateScheduled: Date? = nil) { + self.origin = origin + self.destination = destination + self.dateScheduled = dateScheduled + } +} diff --git a/KarhooSDK/Api/Request/RefreshTokenRequestPayload.swift b/KarhooSDK/Api/Request/RefreshTokenRequestPayload.swift new file mode 100644 index 00000000..83b95bfc --- /dev/null +++ b/KarhooSDK/Api/Request/RefreshTokenRequestPayload.swift @@ -0,0 +1,22 @@ +// +// RefreshTokenRequestPayload.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct RefreshTokenRequestPayload: KarhooCodableModel { + + let refreshToken: String + + init(refreshToken: String) { + self.refreshToken = refreshToken + } + + enum CodingKeys: String, CodingKey { + case refreshToken = "refresh_token" + } +} diff --git a/KarhooSDK/Api/Request/TripBooking.swift b/KarhooSDK/Api/Request/TripBooking.swift new file mode 100644 index 00000000..ba009437 --- /dev/null +++ b/KarhooSDK/Api/Request/TripBooking.swift @@ -0,0 +1,34 @@ +// +// TripBooking.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct TripBooking: KarhooCodableModel, Equatable { + + public let quoteId: String + public let passengers: Passengers + public let flightNumber: String? + public var paymentNonce: String? + + public init(quoteId: String = "", + passengers: Passengers = Passengers(), + flightNumber: String? = nil, + paymentNonce: String? = nil) { + self.quoteId = quoteId + self.passengers = passengers + self.flightNumber = flightNumber + self.paymentNonce = paymentNonce + } + + enum CodingKeys: String, CodingKey { + case quoteId = "quote_id" + case passengers + case flightNumber = "flight_number" + case paymentNonce = "payment_nonce" + } +} diff --git a/KarhooSDK/Api/Request/TripCancellation.swift b/KarhooSDK/Api/Request/TripCancellation.swift new file mode 100644 index 00000000..da102970 --- /dev/null +++ b/KarhooSDK/Api/Request/TripCancellation.swift @@ -0,0 +1,21 @@ +// +// TripCancellation.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct TripCancellation { + + public let tripId: String + public let cancelReason: CancelReason + + public init(tripId: String = "", + cancelReason: CancelReason = .otherUserReason) { + self.tripId = tripId + self.cancelReason = cancelReason + } +} diff --git a/KarhooSDK/Api/Request/TripSearch.swift b/KarhooSDK/Api/Request/TripSearch.swift new file mode 100644 index 00000000..03811180 --- /dev/null +++ b/KarhooSDK/Api/Request/TripSearch.swift @@ -0,0 +1,34 @@ +// +// TripSearch.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct TripSearch: KarhooCodableModel { + + public let tripStates: [TripState]? + public let tripType: TripType? + public let paginationRowCount: Int? + public let paginationOffset: Int? + + enum CodingKeys: String, CodingKey { + case tripStates = "trip_states" + case tripType = "trip_type" + case paginationRowCount = "pagination_row_count" + case paginationOffset = "pagination_offset" + } + + public init(tripStates: [TripState]? = nil, + tripType: TripType? = .both, + paginationRowCount: Int? = nil, + paginationOffset: Int? = nil) { + self.tripStates = tripStates + self.tripType = tripType + self.paginationRowCount = paginationRowCount + self.paginationOffset = paginationOffset + } +} diff --git a/KarhooSDK/Api/Request/TripType.swift b/KarhooSDK/Api/Request/TripType.swift new file mode 100644 index 00000000..e8ca05a6 --- /dev/null +++ b/KarhooSDK/Api/Request/TripType.swift @@ -0,0 +1,15 @@ +// +// TripType.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum TripType: String, Codable { + case both = "BOTH" + case asap = "ASAP" + case prebook = "PREBOOK" +} diff --git a/KarhooSDK/Api/Request/TripsRequestPayload.swift b/KarhooSDK/Api/Request/TripsRequestPayload.swift new file mode 100644 index 00000000..b5425cf3 --- /dev/null +++ b/KarhooSDK/Api/Request/TripsRequestPayload.swift @@ -0,0 +1,17 @@ +// +// TripsRequestPayload.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct TripsListRequestPayload: KarhooCodableModel { + let tripStates: [TripState] + + enum CodingKeys: String, CodingKey { + case tripStates = "trip_states" + } +} diff --git a/KarhooSDK/Api/Request/UIConfigRequest.swift b/KarhooSDK/Api/Request/UIConfigRequest.swift new file mode 100644 index 00000000..6b220cc9 --- /dev/null +++ b/KarhooSDK/Api/Request/UIConfigRequest.swift @@ -0,0 +1,18 @@ +// +// UIConfigRequest.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct UIConfigRequest { + + public let viewId: String? + + public init(viewId: String?) { + self.viewId = viewId + } +} diff --git a/KarhooSDK/Api/Request/UserLogin.swift b/KarhooSDK/Api/Request/UserLogin.swift new file mode 100644 index 00000000..755a6d60 --- /dev/null +++ b/KarhooSDK/Api/Request/UserLogin.swift @@ -0,0 +1,18 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct UserLogin: KarhooCodableModel { + + public let username: String + public let password: String + + public init(username: String = "", + password: String = "") { + self.username = username + self.password = password + } +} diff --git a/KarhooSDK/Api/Request/UserRegistration.swift b/KarhooSDK/Api/Request/UserRegistration.swift new file mode 100644 index 00000000..64a6147f --- /dev/null +++ b/KarhooSDK/Api/Request/UserRegistration.swift @@ -0,0 +1,34 @@ +import Foundation + +public struct UserRegistration: KarhooCodableModel { + + public let firstName: String + public let lastName: String + public let email: String + public let phoneNumber: String + public let locale: String? + public let password: String + + public init(firstName: String = "", + lastName: String = "", + email: String = "", + phoneNumber: String = "", + locale: String? = nil, + password: String = "") { + self.firstName = firstName + self.lastName = lastName + self.email = email + self.phoneNumber = phoneNumber + self.locale = locale + self.password = password + } + + enum CodingKeys: String, CodingKey { + case firstName = "first_name" + case lastName = "last_name" + case email + case password + case phoneNumber = "phone_number" + case locale + } +} diff --git a/KarhooSDK/Api/Request/UserUpdatePayload.swift b/KarhooSDK/Api/Request/UserUpdatePayload.swift new file mode 100644 index 00000000..2b5ea560 --- /dev/null +++ b/KarhooSDK/Api/Request/UserUpdatePayload.swift @@ -0,0 +1,38 @@ +// +// UserUpdatePayload.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct UserDetailsUpdateRequest: KarhooCodableModel { + + public let firstName: String + public let lastName: String + public let phoneNumber: String + public let locale: String? + public let avatarURL: String? + + public init(firstName: String = "", + lastName: String = "", + phoneNumber: String = "", + locale: String? = nil, + avatarURL: String? = nil) { + self.firstName = firstName + self.lastName = lastName + self.phoneNumber = phoneNumber + self.locale = locale + self.avatarURL = avatarURL + } + + enum CodingKeys: String, CodingKey { + case firstName = "first_name" + case lastName = "last_name" + case phoneNumber = "phone_number" + case locale = "locale" + case avatarURL = "avatar_url" + } +} diff --git a/KarhooSDK/Api/Response/AuthToken.swift b/KarhooSDK/Api/Response/AuthToken.swift new file mode 100644 index 00000000..e110c03e --- /dev/null +++ b/KarhooSDK/Api/Response/AuthToken.swift @@ -0,0 +1,55 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +typealias AuthTokenKeys = AuthToken.CodingKeys + +struct AuthToken: KarhooCodableModel { + + var accessToken: String + var expiresIn: Int + var refreshToken: String + var refreshExpiresIn: Int + + enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case expiresIn = "expires_in" + case refreshToken = "refresh_token" + case refreshExpiresIn = "refresh_expires_in" + } + + init(accessToken: String = "", + expiresIn: Int = 0, + refreshToken: String = "", + refreshExpiresIn: Int = 0) { + self.accessToken = accessToken + self.refreshToken = refreshToken + self.expiresIn = expiresIn + self.refreshExpiresIn = refreshExpiresIn + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.accessToken = (try? container.decode(String.self, forKey: .accessToken)) ?? "" + self.expiresIn = (try? container.decode(Int.self, forKey: .expiresIn)) ?? 0 + self.refreshToken = (try? container.decode(String.self, forKey: .refreshToken)) ?? "" + self.refreshExpiresIn = (try? container.decode(Int.self, forKey: .refreshExpiresIn)) ?? 0 + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(accessToken, forKey: .accessToken) + try container.encode(expiresIn, forKey: .expiresIn) + try container.encode(refreshToken, forKey: .refreshToken) + try container.encode(refreshExpiresIn, forKey: .refreshExpiresIn) + } + + func toCredentials(withRefreshToken refreshToken: String? = nil) -> Credentials { + return Credentials(accessToken: accessToken, + expiresIn: TimeInterval(expiresIn), + refreshToken: refreshToken ?? self.refreshToken) + } +} diff --git a/KarhooSDK/Api/Response/Availability.swift b/KarhooSDK/Api/Response/Availability.swift new file mode 100644 index 00000000..4f65f56b --- /dev/null +++ b/KarhooSDK/Api/Response/Availability.swift @@ -0,0 +1,13 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Availability { + + var categories: [String] + let quotes: [Quote] + let quoteListId: String +} diff --git a/KarhooSDK/Api/Response/BookingSearch.swift b/KarhooSDK/Api/Response/BookingSearch.swift new file mode 100644 index 00000000..6a20491a --- /dev/null +++ b/KarhooSDK/Api/Response/BookingSearch.swift @@ -0,0 +1,18 @@ +// +// BookingSearch.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct BookingSearch: KarhooCodableModel { + + let trips: [TripInfo] + + enum CodingKeys: String, CodingKey { + case trips = "bookings" + } +} diff --git a/KarhooSDK/Api/Response/Categories.swift b/KarhooSDK/Api/Response/Categories.swift new file mode 100644 index 00000000..8c7e3810 --- /dev/null +++ b/KarhooSDK/Api/Response/Categories.swift @@ -0,0 +1,29 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Categories: KarhooCodableModel, Equatable { + + public let categories: [String] + + public init(categories: [String] = []) { + self.categories = categories + } + + enum CodingKeys: String, CodingKey { + case categories + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.categories = (try? container.decode([String].self, forKey: .categories)) ?? [] + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(categories, forKey: .categories) + } +} diff --git a/KarhooSDK/Api/Response/Driver.swift b/KarhooSDK/Api/Response/Driver.swift new file mode 100644 index 00000000..bbd21137 --- /dev/null +++ b/KarhooSDK/Api/Response/Driver.swift @@ -0,0 +1,56 @@ +// +// Driver.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Driver: Codable { + + public let firstName: String + public let lastName: String + public let phoneNumber: String + public let photoUrl: String + public let licenseNumber: String + + public init(firstName: String = "", + lastName: String = "", + phoneNumber: String = "", + photoUrl: String = "", + licenseNumber: String = "") { + self.firstName = firstName + self.lastName = lastName + self.phoneNumber = phoneNumber + self.photoUrl = photoUrl + self.licenseNumber = licenseNumber + } + + enum CodingKeys: String, CodingKey { + case firstName = "first_name" + case lastName = "last_name" + case phoneNumber = "phone_number" + case photoUrl = "photo_url" + case licenseNumber = "license_number" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.firstName = (try? container.decode(String.self, forKey: .firstName)) ?? "" + self.lastName = (try? container.decode(String.self, forKey: .lastName)) ?? "" + self.phoneNumber = (try? container.decode(String.self, forKey: .phoneNumber)) ?? "" + self.photoUrl = (try? container.decode(String.self, forKey: .photoUrl)) ?? "" + self.licenseNumber = (try? container.decode(String.self, forKey: .licenseNumber)) ?? "" + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(firstName, forKey: .firstName) + try container.encode(lastName, forKey: .lastName) + try container.encode(phoneNumber, forKey: .phoneNumber) + try container.encode(photoUrl, forKey: .photoUrl) + try container.encode(licenseNumber, forKey: .licenseNumber) + } +} diff --git a/KarhooSDK/Api/Response/DriverTrackingInfo.swift b/KarhooSDK/Api/Response/DriverTrackingInfo.swift new file mode 100644 index 00000000..9b7e3d8d --- /dev/null +++ b/KarhooSDK/Api/Response/DriverTrackingInfo.swift @@ -0,0 +1,46 @@ +// +// DriverTrackingInfo.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +typealias DriverTrackingInfoKeys = DriverTrackingInfo.CodingKeys + +public struct DriverTrackingInfo: KarhooCodableModel { + + public let position: Position + public let originEta: Int + public let destinationEta: Int + + public init(position: Position, + originEta: Int, + destinationEta: Int) { + self.position = position + self.originEta = originEta + self.destinationEta = destinationEta + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.position = (try? container.decode(Position.self, forKey: .position)) ?? Position(latitude: 0, longitude: 0) + self.originEta = (try? container.decode(Int.self, forKey: .originEta)) ?? 0 + self.destinationEta = (try? container.decode(Int.self, forKey: .destinationEta)) ?? 0 + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(position, forKey: .position) + try container.encode(originEta, forKey: .originEta) + try container.encode(destinationEta, forKey: .destinationEta) + } + + enum CodingKeys: String, CodingKey { + case position + case originEta = "origin_eta" + case destinationEta = "destination_eta" + } +} diff --git a/KarhooSDK/Api/Response/Fare.swift b/KarhooSDK/Api/Response/Fare.swift new file mode 100644 index 00000000..c1023529 --- /dev/null +++ b/KarhooSDK/Api/Response/Fare.swift @@ -0,0 +1,54 @@ +// +// Fare.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Fare: KarhooCodableModel { + + public let state: String + public let expectedFinalTime: String + public let expectedIn: String + public let breakdown: FareComponent + + public init(state: String = "", + expectedFinalTime: String = "", + expectedIn: String = "", + breakdown: FareComponent = FareComponent()) { + self.state = state + self.expectedFinalTime = expectedFinalTime + self.expectedIn = expectedIn + self.breakdown = breakdown + } + + enum CodingKeys: String, CodingKey { + case state + case expectedFinalTime = "expected_final_time" + case expectedIn = "expected_in" + case breakdown + } + + public init(from decoder: Decoder) throws { + + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.state = (try? container.decode(String.self, forKey: .state)) ?? "" + self.expectedFinalTime = (try? container.decode(String.self, forKey: .expectedFinalTime)) ?? "" + self.expectedIn = (try? container.decode(String.self, forKey: .expectedIn)) ?? "" + self.breakdown = (try? container.decode(FareComponent.self, forKey: .breakdown)) ?? FareComponent() + } + + public func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(state, forKey: .state) + try container.encode(expectedFinalTime, forKey: .expectedFinalTime) + try container.encode(expectedIn, forKey: .expectedIn) + try container.encode(breakdown, forKey: .breakdown) + } +} diff --git a/KarhooSDK/Api/Response/FareComponent.swift b/KarhooSDK/Api/Response/FareComponent.swift new file mode 100644 index 00000000..bfd1643c --- /dev/null +++ b/KarhooSDK/Api/Response/FareComponent.swift @@ -0,0 +1,41 @@ +// +// FareComponent.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct FareComponent: Codable, Equatable { + + public let total: Double + public let currency: String + + public init(total: Double = 0.0, + currency: String = "") { + self.total = total + self.currency = currency + } + + enum CodingKeys: String, CodingKey { + case total + case currency + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.total = (try? container.decode(Double.self, forKey: .total)) ?? 0.0 + self.currency = (try? container.decode(String.self, forKey: .currency)) ?? "" + } + + public func encode(to encoder: Encoder) throws { + + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(total, forKey: .total) + try container.encode(currency, forKey: .currency) + } +} diff --git a/KarhooSDK/Api/Response/FleetInfo.swift b/KarhooSDK/Api/Response/FleetInfo.swift new file mode 100644 index 00000000..05441b61 --- /dev/null +++ b/KarhooSDK/Api/Response/FleetInfo.swift @@ -0,0 +1,68 @@ +// +// FleetInfo.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct FleetInfo: Codable { + + public let fleetId: String + public let name: String + public let logoUrl: String + public let description: String + public let phoneNumber: String + public let termsConditionsUrl: String + public let email: String + + public init(fleetId: String = "", + name: String = "", + description: String = "", + phoneNumber: String = "", + termsConditionsUrl: String = "", + logoUrl: String = "", + email: String = "") { + self.fleetId = fleetId + self.name = name + self.logoUrl = logoUrl + self.description = description + self.phoneNumber = phoneNumber + self.termsConditionsUrl = termsConditionsUrl + self.email = email + } + + enum CodingKeys: String, CodingKey { + case fleetId = "fleet_id" + case name + case logoUrl = "logo_url" + case description + case phoneNumber = "phone_number" + case termsConditionsUrl = "terms_conditions_url" + case email + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.fleetId = (try? container.decode(String.self, forKey: .fleetId)) ?? "" + self.name = (try? container.decode(String.self, forKey: .name)) ?? "" + self.logoUrl = (try? container.decode(String.self, forKey: .logoUrl)) ?? "" + self.description = (try? container.decode(String.self, forKey: .description)) ?? "" + self.phoneNumber = (try? container.decode(String.self, forKey: .phoneNumber)) ?? "" + self.termsConditionsUrl = (try? container.decode(String.self, forKey: .termsConditionsUrl)) ?? "" + self.email = (try? container.decode(String.self, forKey: .email)) ?? "" + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fleetId, forKey: .fleetId) + try container.encode(name, forKey: .name) + try container.encode(logoUrl, forKey: .logoUrl) + try container.encode(description, forKey: .description) + try container.encode(phoneNumber, forKey: .phoneNumber) + try container.encode(termsConditionsUrl, forKey: .termsConditionsUrl) + try container.encode(email, forKey: .email) + } +} diff --git a/KarhooSDK/Api/Response/LocationDetailsAddress.swift b/KarhooSDK/Api/Response/LocationDetailsAddress.swift new file mode 100644 index 00000000..94963b93 --- /dev/null +++ b/KarhooSDK/Api/Response/LocationDetailsAddress.swift @@ -0,0 +1,80 @@ +// +// LocationInfoAddress.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct LocationInfoAddress: KarhooCodableModel { + + public let displayAddress: String + public let lineOne: String + public let lineTwo: String + public let buildingNumber: String + public let streetName: String + public let city: String + public let postalCode: String + public let region: String + public let countryCode: String + + public init(displayAddress: String = "", + lineOne: String = "", + lineTwo: String = "", + buildingNumber: String = "", + streetName: String = "", + city: String = "", + postalCode: String = "", + countryCode: String = "", + region: String = "") { + self.displayAddress = displayAddress + self.lineOne = lineOne + self.lineTwo = lineTwo + self.buildingNumber = buildingNumber + self.streetName = streetName + self.city = city + self.postalCode = postalCode + self.region = region + self.countryCode = countryCode + } + + enum CodingKeys: String, CodingKey { + case displayAddress = "display_address" + case lineOne = "line_1" + case lineTwo = "line_2" + case buildingNumber = "building_number" + case streetName = "street_name" + case city + case postalCode = "postal_code" + case region + case countryCode = "country_code" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.displayAddress = (try? container.decode(String.self, forKey: .displayAddress)) ?? "" + self.lineOne = (try? container.decode(String.self, forKey: .lineOne)) ?? "" + self.lineTwo = (try? container.decode(String.self, forKey: .lineTwo)) ?? "" + self.buildingNumber = (try? container.decode(String.self, forKey: .buildingNumber)) ?? "" + self.streetName = (try? container.decode(String.self, forKey: .streetName)) ?? "" + self.city = (try? container.decode(String.self, forKey: .city)) ?? "" + self.postalCode = (try? container.decode(String.self, forKey: .postalCode)) ?? "" + self.region = (try? container.decode(String.self, forKey: .region)) ?? "" + self.countryCode = (try? container.decode(String.self, forKey: .countryCode)) ?? "" + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(displayAddress, forKey: .displayAddress) + try container.encode(lineOne, forKey: .lineOne) + try container.encode(lineTwo, forKey: .lineTwo) + try container.encode(buildingNumber, forKey: .buildingNumber) + try container.encode(streetName, forKey: .streetName) + try container.encode(city, forKey: .city) + try container.encode(postalCode, forKey: .postalCode) + try container.encode(region, forKey: .region) + try container.encode(countryCode, forKey: .countryCode) + } +} diff --git a/KarhooSDK/Api/Response/LocationInfo.swift b/KarhooSDK/Api/Response/LocationInfo.swift new file mode 100644 index 00000000..ec09ef0c --- /dev/null +++ b/KarhooSDK/Api/Response/LocationInfo.swift @@ -0,0 +1,74 @@ +// +// LocationInfo.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct LocationInfo: KarhooCodableModel { + + public let position: Position + public let placeId: String + public let poiType: PoiType + public let address: LocationInfoAddress + public let timeZoneIdentifier: String + public let details: PoiDetails + public let meetingPoint: MeetingPoint + public let instructions: String + + public init(placeId: String = "", + timeZoneIdentifier: String = "", + position: Position = Position(), + poiType: PoiType = .notSetPoiType, + address: LocationInfoAddress = LocationInfoAddress(), + details: PoiDetails = PoiDetails(), + meetingPoint: MeetingPoint = MeetingPoint(), + instructions: String = "") { + self.placeId = placeId + self.timeZoneIdentifier = timeZoneIdentifier + self.position = position + self.poiType = poiType + self.address = address + self.details = details + self.meetingPoint = meetingPoint + self.instructions = instructions + } + + enum CodingKeys: String, CodingKey { + case position + case placeId = "place_id" + case poiType = "poi_type" + case address + case timeZoneIdentifier = "time_zone" + case details + case meetingPoint = "meeting_point" + case instructions + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.position = (try? container.decode(Position.self, forKey: .position)) ?? Position() + self.placeId = (try? container.decode(String.self, forKey: .placeId)) ?? "" + self.poiType = (try? container.decode(PoiType.self, forKey: .poiType)) ?? .notSetPoiType + self.address = (try? container.decode(LocationInfoAddress.self, forKey: .address)) ?? LocationInfoAddress() + self.timeZoneIdentifier = (try? container.decode(String.self, forKey: .timeZoneIdentifier)) ?? "" + self.details = (try? container.decode(PoiDetails.self, forKey: .details)) ?? PoiDetails() + self.meetingPoint = (try? container.decode(MeetingPoint.self, forKey: .meetingPoint)) ?? MeetingPoint() + self.instructions = (try? container.decode(String.self, forKey: .instructions)) ?? "" + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(position, forKey: .position) + try container.encode(placeId, forKey: .placeId) + try container.encode(poiType, forKey: .poiType) + try container.encode(address, forKey: .address) + try container.encode(timeZoneIdentifier, forKey: .timeZoneIdentifier) + try container.encode(details, forKey: .details) + try container.encode(meetingPoint, forKey: .meetingPoint) + try container.encode(instructions, forKey: .instructions) + } +} diff --git a/KarhooSDK/Api/Response/MeetingPoint.swift b/KarhooSDK/Api/Response/MeetingPoint.swift new file mode 100644 index 00000000..b333cf7d --- /dev/null +++ b/KarhooSDK/Api/Response/MeetingPoint.swift @@ -0,0 +1,41 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct MeetingPoint: Codable { + + public let position: Position + public let instructions: String + public let type: MeetingPointType + + public init(position: Position = Position(), + instructions: String = "", + type: MeetingPointType = .notSet) { + self.position = position + self.instructions = instructions + self.type = type + } + + enum CodingKeys: String, CodingKey { + case position + case instructions + case type + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.position = (try? container.decode(Position.self, forKey: .position)) ?? Position() + self.instructions = (try? container.decode(String.self, forKey: .instructions)) ?? "" + self.type = (try? container.decode(MeetingPointType.self, forKey: .type)) ?? .notSet + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(position, forKey: .position) + try container.encode(instructions, forKey: .instructions) + try container.encode(type, forKey: .type) + } +} diff --git a/KarhooSDK/Api/Response/MeetingPointType.swift b/KarhooSDK/Api/Response/MeetingPointType.swift new file mode 100644 index 00000000..47e275a4 --- /dev/null +++ b/KarhooSDK/Api/Response/MeetingPointType.swift @@ -0,0 +1,19 @@ +// +// MeetingPointType.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum MeetingPointType: String, Codable { + case `default` = "DEFAULT", + pickup = "PICK_UP", + dropOff = "DROP_OFF", + meetAndGreet = "MEET_AND_GREET", + curbSide = "CURB_SIDE", + standBy = "STAND_BY", + notSet = "NOT_SET" +} diff --git a/KarhooSDK/Api/Response/Nonce.swift b/KarhooSDK/Api/Response/Nonce.swift new file mode 100644 index 00000000..c32b924c --- /dev/null +++ b/KarhooSDK/Api/Response/Nonce.swift @@ -0,0 +1,30 @@ +// +// Nonce.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Nonce: KarhooCodableModel, Equatable { + + public let nonce: String + public let cardType: String + public let lastFour: String + + public init(nonce: String = "", + cardType: String = "", + lastFour: String = "") { + self.nonce = nonce + self.cardType = cardType + self.lastFour = lastFour + } + + enum CodingKeys: String, CodingKey { + case cardType = "card_type" + case lastFour = "last_four" + case nonce + } +} diff --git a/KarhooSDK/Api/Response/Organisation.swift b/KarhooSDK/Api/Response/Organisation.swift new file mode 100644 index 00000000..0f1f3499 --- /dev/null +++ b/KarhooSDK/Api/Response/Organisation.swift @@ -0,0 +1,44 @@ +// +// Organisation.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Organisation: KarhooCodableModel { + + public let id: String + public let name: String + public let roles: [String] + + internal init(id: String = "", + name: String = "", + roles: [String] = []) { + self.id = id + self.name = name + self.roles = roles + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = (try? container.decode(String.self, forKey: .id)) ?? "" + self.name = (try? container.decode(String.self, forKey: .name)) ?? "" + self.roles = (try? container.decode([String].self, forKey: .roles)) ?? [] + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(name, forKey: .name) + try container.encode(roles, forKey: .roles) + } + + enum CodingKeys: String, CodingKey { + case id + case name + case roles + } +} diff --git a/KarhooSDK/Api/Response/PassengerDetails.swift b/KarhooSDK/Api/Response/PassengerDetails.swift new file mode 100644 index 00000000..47e3e301 --- /dev/null +++ b/KarhooSDK/Api/Response/PassengerDetails.swift @@ -0,0 +1,68 @@ +// +// PassengerDetails.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct PassengerDetails: KarhooCodableModel, Equatable { + + public let firstName: String + public let lastName: String + public let email: String + public let phoneNumber: String + public let locale: String + + enum CodingKeys: String, CodingKey { + case firstName = "first_name" + case lastName = "last_name" + case email + case phoneNumber = "phone_number" + case locale + } + + public init(firstName: String = "", + lastName: String = "", + email: String = "", + phoneNumber: String = "", + locale: String = "") { + + self.firstName = firstName + self.lastName = lastName + self.email = email + self.phoneNumber = phoneNumber + self.locale = locale + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.firstName = (try? container.decode(String.self, forKey: .firstName)) ?? "" + self.lastName = (try? container.decode(String.self, forKey: .lastName)) ?? "" + self.email = (try? container.decode(String.self, forKey: .email)) ?? "" + self.phoneNumber = (try? container.decode(String.self, forKey: .phoneNumber)) ?? "" + self.locale = (try? container.decode(String.self, forKey: .locale)) ?? "" + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(firstName, forKey: .firstName) + try container.encode(lastName, forKey: .lastName) + try container.encode(email, forKey: .email) + try container.encode(phoneNumber, forKey: .phoneNumber) + try container.encode(locale, forKey: .locale) + } + +} + +public extension PassengerDetails { + init(user: UserInfo) { + self.init(firstName: user.firstName, + lastName: user.lastName, + email: user.email, + phoneNumber: user.mobileNumber, + locale: user.locale) + } +} diff --git a/KarhooSDK/Api/Response/Passengers.swift b/KarhooSDK/Api/Response/Passengers.swift new file mode 100644 index 00000000..abbfbf0b --- /dev/null +++ b/KarhooSDK/Api/Response/Passengers.swift @@ -0,0 +1,38 @@ +// +// Passengers.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Passengers: KarhooCodableModel, Equatable { + + public let additionalPassengers: Int + public let passengerDetails: [PassengerDetails] + + public init(additionalPassengers: Int = 0, + passengerDetails: [PassengerDetails] = [PassengerDetails()]) { + self.additionalPassengers = additionalPassengers + self.passengerDetails = passengerDetails + } + + enum CodingKeys: String, CodingKey { + case additionalPassengers = "additional_passengers" + case passengerDetails = "passenger_details" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.additionalPassengers = (try? container.decode(Int.self, forKey: .additionalPassengers)) ?? 0 + self.passengerDetails = (try? container.decode([PassengerDetails].self, forKey: .passengerDetails)) ?? [] + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(additionalPassengers, forKey: .additionalPassengers) + try container.encode(passengerDetails, forKey: .passengerDetails) + } +} diff --git a/KarhooSDK/Api/Response/PaymentSDKToken.swift b/KarhooSDK/Api/Response/PaymentSDKToken.swift new file mode 100644 index 00000000..c77dc248 --- /dev/null +++ b/KarhooSDK/Api/Response/PaymentSDKToken.swift @@ -0,0 +1,14 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct PaymentSDKToken: KarhooCodableModel { + + public let token: String + public init(token: String = "") { + self.token = token + } +} diff --git a/KarhooSDK/Api/Response/PickUpType.swift b/KarhooSDK/Api/Response/PickUpType.swift new file mode 100644 index 00000000..ae4cc572 --- /dev/null +++ b/KarhooSDK/Api/Response/PickUpType.swift @@ -0,0 +1,17 @@ +// +// PickupType.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum PickUpType: String, KarhooCodableModel { + + case `default` = "DEFAULT" + case meetAndGreet = "MEET_AND_GREET" + case curbside = "CURBSIDE" + case standyBy = "STAND_BY" +} diff --git a/KarhooSDK/Api/Response/Place.swift b/KarhooSDK/Api/Response/Place.swift new file mode 100644 index 00000000..502637f5 --- /dev/null +++ b/KarhooSDK/Api/Response/Place.swift @@ -0,0 +1,39 @@ +// +// Place.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Place: KarhooCodableModel { + + public let placeId: String + public let displayAddress: String + public let poiDetailsType: PoiDetailsType + + public init(placeId: String = "", + displayAddress: String = "", + poiDetailsType: PoiDetailsType = .notSetDetailsType) { + self.placeId = placeId + self.displayAddress = displayAddress + self.poiDetailsType = poiDetailsType + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.placeId = (try? container.decode(String.self, forKey: .placeId)) ?? "" + self.displayAddress = (try? container.decode(String.self, forKey: .displayAddress)) ?? "" + self.poiDetailsType = (try? container.decode(PoiDetailsType.self, + forKey: .poiDetailsType)) ?? .notSetDetailsType + } + + enum CodingKeys: String, CodingKey { + case placeId = "place_id" + case displayAddress = "display_address" + case poiDetailsType = "type" + } +} diff --git a/KarhooSDK/Api/Response/Places.swift b/KarhooSDK/Api/Response/Places.swift new file mode 100644 index 00000000..854aac9a --- /dev/null +++ b/KarhooSDK/Api/Response/Places.swift @@ -0,0 +1,19 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Places: KarhooCodableModel { + + public let places: [Place] + + public init(places: [Place] = []) { + self.places = places + } + + enum CodingKeys: String, CodingKey { + case places = "locations" + } +} diff --git a/KarhooSDK/Api/Response/PoiDetails.swift b/KarhooSDK/Api/Response/PoiDetails.swift new file mode 100644 index 00000000..c5a7e749 --- /dev/null +++ b/KarhooSDK/Api/Response/PoiDetails.swift @@ -0,0 +1,40 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct PoiDetails: Codable { + public let iata: String + public let terminal: String + public let type: PoiDetailsType + + public init(iata: String = "", + terminal: String = "", + type: PoiDetailsType = .notSetDetailsType) { + self.iata = iata + self.terminal = terminal + self.type = type + } + + enum CodingKeys: String, CodingKey { + case iata + case terminal + case type + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.iata = (try? container.decode(String.self, forKey: .iata)) ?? "" + self.terminal = (try? container.decode(String.self, forKey: .terminal)) ?? "" + self.type = (try? container.decode(PoiDetailsType.self, forKey: .type)) ?? .notSetDetailsType + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(iata, forKey: .iata) + try container.encode(terminal, forKey: .terminal) + try container.encode(type, forKey: .type) + } +} diff --git a/KarhooSDK/Api/Response/PoiDetailsType.swift b/KarhooSDK/Api/Response/PoiDetailsType.swift new file mode 100644 index 00000000..86941769 --- /dev/null +++ b/KarhooSDK/Api/Response/PoiDetailsType.swift @@ -0,0 +1,12 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum PoiDetailsType: String, Codable { + case notSetDetailsType = "NOT_SET_DETAILS_TYPE" + case airport = "AIRPORT" + case trainStation = "TRAIN_STATION" +} diff --git a/KarhooSDK/Api/Response/PoiType.swift b/KarhooSDK/Api/Response/PoiType.swift new file mode 100644 index 00000000..86f5fb6b --- /dev/null +++ b/KarhooSDK/Api/Response/PoiType.swift @@ -0,0 +1,13 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum PoiType: String, Codable { + case notSetPoiType = "NOT_SET_POI_TYPE" + case enriched = "ENRICHED" + case regulated = "REGULATED" + case nearest = "NEAREST" +} diff --git a/KarhooSDK/Api/Response/Position.swift b/KarhooSDK/Api/Response/Position.swift new file mode 100644 index 00000000..043c96b3 --- /dev/null +++ b/KarhooSDK/Api/Response/Position.swift @@ -0,0 +1,32 @@ +// +// Position.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Position: KarhooCodableModel, Equatable { + + public let latitude: Double + public let longitude: Double + + public init(latitude: Double = 0, + longitude: Double = 0) { + self.latitude = latitude + self.longitude = longitude + } + + enum CodingKeys: String, CodingKey { + case latitude + case longitude + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.latitude = (try? container.decode(Double.self, forKey: .latitude)) ?? 0 + self.longitude = (try? container.decode(Double.self, forKey: .longitude)) ?? 0 + } +} diff --git a/KarhooSDK/Api/Response/Quote.swift b/KarhooSDK/Api/Response/Quote.swift new file mode 100644 index 00000000..3de1f7fc --- /dev/null +++ b/KarhooSDK/Api/Response/Quote.swift @@ -0,0 +1,118 @@ +// +// Quote.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Quote: KarhooCodableModel, Equatable { + + public let quoteId: String + public let fleetId: String + public let availabilityId: String + public let phoneNumber: String + public let fleetName: String + public let supplierLogoUrl: String + public let vehicleClass: String + public let quoteType: QuoteType + public let highPrice: Double + public let lowPrice: Double + public let currencyCode: String + public let qtaHighMinutes: Int + public let qtaLowMinutes: Int + public let termsConditionsUrl: String + public let categoryName: String + public let pickUpType: PickUpType + public let source: QuoteSource + public let vehicleAttributes: VehicleAttributes + + public init(quoteId: String = "", + fleetId: String = "", + availabilityId: String = "", + fleetName: String = "", + phoneNumber: String = "", + supplierLogoUrl: String = "", + vehicleClass: String = "", + quoteType: QuoteType = .estimated, + highPrice: Int = 0, + lowPrice: Int = 0, + currencyCode: String = "", + qtaHighMinutes: Int = 0, + qtaLowMinutes: Int = 0, + termsConditionsURL: String = "", + categoryName: String = "", + source: QuoteSource = .fleet, + pickUpType: PickUpType = .default, + vehicleAttributes: VehicleAttributes = VehicleAttributes()) { + self.quoteId = quoteId + self.fleetId = fleetId + self.availabilityId = availabilityId + self.fleetName = fleetName + self.phoneNumber = phoneNumber + self.supplierLogoUrl = supplierLogoUrl + self.vehicleClass = vehicleClass + self.quoteType = quoteType + self.highPrice = Double(highPrice)*0.01 + self.lowPrice = Double(lowPrice)*0.01 + self.currencyCode = currencyCode + self.qtaHighMinutes = qtaHighMinutes + self.qtaLowMinutes = qtaLowMinutes + self.termsConditionsUrl = termsConditionsURL + self.categoryName = categoryName + self.pickUpType = pickUpType + self.source = source + self.vehicleAttributes = vehicleAttributes + } + + enum CodingKeys: String, CodingKey { + case quoteId = "quote_id" + case fleetId = "fleet_id" + case availabilityId = "availability_id" + case fleetName = "fleet_name" + case phoneNumber = "phone_number" + case supplierLogoUrl = "supplier_logo_url" + case vehicleClass = "vehicle_class" + case quoteType = "quote_type" + case highPrice = "high_price" + case lowPrice = "low_price" + case currencyCode = "currency_code" + case qtaHighMinutes = "qta_high_minutes" + case qtaLowMinutes = "qta_low_minutes" + case termsConditionsUrl = "terms_conditions_url" + case categoryName = "category_name" + case pickUpType = "pick_up_type" + case source + case vehicleAttributes = "vehicle_attributes" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.quoteId = (try? container.decode(String.self, forKey: .quoteId)) ?? "" + self.fleetId = (try? container.decode(String.self, forKey: .fleetId)) ?? "" + self.availabilityId = (try? container.decode(String.self, forKey: .availabilityId)) ?? "" + self.fleetName = (try? container.decode(String.self, forKey: .fleetName)) ?? "" + self.phoneNumber = (try? container.decode(String.self, forKey: .phoneNumber)) ?? "" + self.supplierLogoUrl = (try? container.decode(String.self, forKey: .supplierLogoUrl)) ?? "" + self.vehicleClass = (try? container.decode(String.self, forKey: .vehicleClass)) ?? "" + self.quoteType = (try? container.decode(QuoteType.self, forKey: .quoteType)) ?? .estimated + let intHighPrice: Int = (try? container.decode(Int.self, forKey: .highPrice)) ?? 0 + self.highPrice = Double(intHighPrice) * 0.01 + let intLowPrice: Int = (try? container.decode(Int.self, forKey: .lowPrice)) ?? 0 + self.lowPrice = Double(intLowPrice) * 0.01 + self.currencyCode = (try? container.decode(String.self, forKey: .currencyCode)) ?? "" + self.qtaHighMinutes = (try? container.decode(Int.self, forKey: .qtaHighMinutes)) ?? 0 + self.qtaLowMinutes = (try? container.decode(Int.self, forKey: .qtaLowMinutes)) ?? 0 + self.termsConditionsUrl = (try? container.decode(String.self, forKey: .termsConditionsUrl)) ?? "" + self.categoryName = (try? container.decode(String.self, forKey: .categoryName)) ?? "" + self.pickUpType = (try? container.decode(PickUpType.self, forKey: .pickUpType)) ?? .default + self.source = (try? container.decode(QuoteSource.self, forKey: .source)) ?? .fleet + self.vehicleAttributes = (try? container.decode(VehicleAttributes.self, forKey: .vehicleAttributes)) ?? VehicleAttributes() + } + + public static func == (lhs: Quote, rhs: Quote) -> Bool { + return lhs.quoteId == rhs.quoteId + } +} diff --git a/KarhooSDK/Api/Response/QuoteCategory.swift b/KarhooSDK/Api/Response/QuoteCategory.swift new file mode 100644 index 00000000..fba6f8f3 --- /dev/null +++ b/KarhooSDK/Api/Response/QuoteCategory.swift @@ -0,0 +1,20 @@ +// +// QuoteCategory.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct QuoteCategory: KarhooCodableModel, Equatable { + public let categoryName: String + public let quotes: [Quote] + + public init(name: String = "", + quotes: [Quote] = []) { + self.categoryName = name + self.quotes = quotes + } +} diff --git a/KarhooSDK/Api/Response/QuoteList.swift b/KarhooSDK/Api/Response/QuoteList.swift new file mode 100644 index 00000000..0c74d51d --- /dev/null +++ b/KarhooSDK/Api/Response/QuoteList.swift @@ -0,0 +1,49 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +typealias QuoteListKeys = QuoteList.CodingKeys + +public struct QuoteList: KarhooCodableModel { + + public let quoteItems: [Quote] + public let listId: String + public let status: String + let validity: Int + + internal init(quoteItems: [Quote] = [], + listId: String = "", + status: String = "", + validity: Int = 0) { + self.quoteItems = quoteItems + self.listId = listId + self.status = status + self.validity = validity + } + + enum CodingKeys: String, CodingKey { + case quoteItems = "quote_items" + case listId = "id" + case status + case validity + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.quoteItems = (try? container.decode([Quote].self, forKey: .quoteItems)) ?? [] + self.listId = try container.decode(String.self, forKey: .listId) + self.status = (try? container.decode(String.self, forKey: .status)) ?? "" + self.validity = (try? container.decode(Int.self, forKey: .validity)) ?? 0 + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(quoteItems, forKey: .quoteItems) + try container.encode(listId, forKey: .listId) + try container.encode(status, forKey: .status) + try container.encode(validity, forKey: .validity) + } +} diff --git a/KarhooSDK/Api/Response/QuoteListId.swift b/KarhooSDK/Api/Response/QuoteListId.swift new file mode 100644 index 00000000..f535d309 --- /dev/null +++ b/KarhooSDK/Api/Response/QuoteListId.swift @@ -0,0 +1,34 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct QuoteListId: KarhooCodableModel { + + public let identifier: String + public let validityTime: Int + + init(identifier: String, validityTime: Int) { + self.identifier = identifier + self.validityTime = validityTime + } + + enum CodingKeys: String, CodingKey { + case identifier = "id" + case validityTime = "validity" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.identifier = (try? container.decode(String.self, forKey: .identifier)) ?? "" + self.validityTime = (try? container.decode(Int.self, forKey: .validityTime)) ?? 0 + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(identifier, forKey: .identifier) + try container.encode(validityTime, forKey: .validityTime) + } +} diff --git a/KarhooSDK/Api/Response/QuoteSource.swift b/KarhooSDK/Api/Response/QuoteSource.swift new file mode 100644 index 00000000..b7949cf1 --- /dev/null +++ b/KarhooSDK/Api/Response/QuoteSource.swift @@ -0,0 +1,14 @@ +// +// QuoteSource.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum QuoteSource: String, Codable { + case fleet = "FLEET" + case market = "MARKET" +} diff --git a/KarhooSDK/Api/Response/QuoteType.swift b/KarhooSDK/Api/Response/QuoteType.swift new file mode 100644 index 00000000..9abc5388 --- /dev/null +++ b/KarhooSDK/Api/Response/QuoteType.swift @@ -0,0 +1,15 @@ +// +// QuoteType.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum QuoteType: String, Codable { + case fixed = "FIXED" + case estimated = "ESTIMATED" + case metered = "METERED" +} diff --git a/KarhooSDK/Api/Response/Quotes.swift b/KarhooSDK/Api/Response/Quotes.swift new file mode 100644 index 00000000..a795f3ff --- /dev/null +++ b/KarhooSDK/Api/Response/Quotes.swift @@ -0,0 +1,28 @@ +// +// Quotes.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Quotes: KarhooCodableModel { + + public let quoteListId: String + public let quoteCategories: [QuoteCategory] + public let all: [Quote] + + public func quotes(for category: String) -> [Quote] { + return quoteCategories.filter { $0.categoryName == category }.first?.quotes ?? [] + } + + public init(quoteListId: String = "", + quoteCategories: [QuoteCategory] = [], + all: [Quote] = []) { + self.quoteCategories = quoteCategories + self.all = all + self.quoteListId = quoteListId + } +} diff --git a/KarhooSDK/Api/Response/RefreshToken.swift b/KarhooSDK/Api/Response/RefreshToken.swift new file mode 100644 index 00000000..998fe293 --- /dev/null +++ b/KarhooSDK/Api/Response/RefreshToken.swift @@ -0,0 +1,26 @@ +// +// RefreshToken.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct RefreshToken: KarhooCodableModel { + + var accessToken: String + var expiresIn: Int + + enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case expiresIn = "expires_in" + } + + func toCredentials(withRefreshToken refreshToken: String?) -> Credentials { + return Credentials(accessToken: accessToken, + expiresIn: TimeInterval(expiresIn), + refreshToken: refreshToken) + } +} diff --git a/KarhooSDK/Api/Response/TripFare.swift b/KarhooSDK/Api/Response/TripFare.swift new file mode 100644 index 00000000..70eb62c4 --- /dev/null +++ b/KarhooSDK/Api/Response/TripFare.swift @@ -0,0 +1,46 @@ +// +// TripFare.swift +// Analytics +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct TripFare: KarhooCodableModel { + + public let total: Int + public let currency: String + public let gratuityPercent: Int + public var denominateTotal: Double { + return Double(total) * 0.01 + } + + public init(total: Int = 0, + currency: String = "", + gratuityPercent: Int = 0) { + self.total = total + self.currency = currency + self.gratuityPercent = gratuityPercent + } + + enum CodingKeys: String, CodingKey { + case total + case currency + case gratuityPercent = "gratuity_percent" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.total = (try? container.decode(Int.self, forKey: .total)) ?? 0 + self.currency = (try? container.decode(String.self, forKey: .currency)) ?? "" + self.gratuityPercent = (try? container.decode(Int.self, forKey: .gratuityPercent)) ?? 0 + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(total, forKey: .total) + try container.encode(currency, forKey: .currency) + try container.encode(gratuityPercent, forKey: .gratuityPercent) + } +} diff --git a/KarhooSDK/Api/Response/TripInfo.swift b/KarhooSDK/Api/Response/TripInfo.swift new file mode 100644 index 00000000..a11db55f --- /dev/null +++ b/KarhooSDK/Api/Response/TripInfo.swift @@ -0,0 +1,112 @@ +// +// TripInfo.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct TripInfo: KarhooCodableModel { + + public let tripId: String + public let displayId: String + public let state: TripState + public let vehicle: Vehicle + public let fleetInfo: FleetInfo + public let tripQuote: TripQuote + public let flightNumber: String + public let origin: TripLocationDetails + public let destination: TripLocationDetails? + // TODO: Switch to new Fare Service + public let fare: TripFare + /* The time of pick up in UTC. Use origin.timeZoneIdentifier + to localise the pick up time for the user. */ + public let dateScheduled: Date? + + public let meetingPoint: MeetingPoint? + + public init(tripId: String = "", + displayId: String = "", + origin: TripLocationDetails = TripLocationDetails(), + destination: TripLocationDetails? = nil, + dateScheduled: Date? = nil, + state: TripState = .unknown, + quote: TripQuote = TripQuote(), + vehicle: Vehicle = Vehicle(), + fleetInfo: FleetInfo = FleetInfo(), + flightNumber: String = "", + meetingPoint: MeetingPoint? = nil, + fare: TripFare = TripFare()) { + self.tripId = tripId + self.displayId = displayId + self.state = state + self.vehicle = vehicle + self.tripQuote = quote + self.flightNumber = flightNumber + self.origin = origin + self.destination = destination + self.dateScheduled = dateScheduled + self.meetingPoint = meetingPoint + self.fleetInfo = fleetInfo + self.fare = fare + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.tripId = (try? container.decode(String.self, forKey: .tripId)) ?? "" + self.displayId = (try? container.decode(String.self, forKey: .displayId)) ?? "" + self.origin = (try? container.decode(TripLocationDetails.self, forKey: .origin)) ?? TripLocationDetails() + self.destination = (try? container.decode(TripLocationDetails.self, + forKey: .destination)) ?? TripLocationDetails() + self.state = (try? container.decode(TripState.self, forKey: .state)) ?? .unknown + self.tripQuote = (try? container.decode(TripQuote.self, forKey: .quote)) ?? TripQuote() + self.vehicle = (try? container.decode(Vehicle.self, forKey: .vehicle)) ?? Vehicle() + self.fleetInfo = (try? container.decode(FleetInfo.self, forKey: .fleetInfo)) ?? FleetInfo() + self.flightNumber = (try? container.decode(String.self, forKey: .flightNumber)) ?? "" + self.meetingPoint = (try? container.decode(MeetingPoint.self, forKey: .meetingPoint)) + self.fare = (try? container.decode(TripFare.self, forKey: .fare)) ?? TripFare() + + let utcDate = (try? container.decode(String.self, forKey: .dateScheduled)) + + if let utcDate = utcDate { + self.dateScheduled = KarhooNetworkDateFormatter(formatType: .booking).toDate(from: utcDate) + } else { + self.dateScheduled = nil + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(tripId, forKey: .tripId) + try container.encode(displayId, forKey: .displayId) + try container.encode(dateScheduled, forKey: .dateScheduled) + try container.encode(origin, forKey: .origin) + try container.encode(destination, forKey: .destination) + try container.encode(state, forKey: .state) + try container.encode(tripQuote, forKey: .quote) + try container.encode(vehicle, forKey: .vehicle) + try container.encode(fleetInfo, forKey: .fleetInfo) + try container.encode(flightNumber, forKey: .flightNumber) + try container.encode(meetingPoint, forKey: .meetingPoint) + try container.encode(fare, forKey: .fare) + } + + enum CodingKeys: String, CodingKey { + case tripId = "id" + case displayId = "display_trip_id" + case state = "status" + case origin + case destination = "destination" + case dateScheduled = "date_scheduled" + case vehicle + case quote + case fleetInfo = "fleet_info" + case meetingPoint = "meeting_point" + case flightNumber = "flight_number" + case fare + } +} diff --git a/KarhooSDK/Api/Response/TripLocationDetails.swift b/KarhooSDK/Api/Response/TripLocationDetails.swift new file mode 100644 index 00000000..ae83783b --- /dev/null +++ b/KarhooSDK/Api/Response/TripLocationDetails.swift @@ -0,0 +1,53 @@ +// +// TripLocationDetails.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct TripLocationDetails: Codable { + + public let displayAddress: String + public let placeId: String + public let position: Position + public let timeZoneIdentifier: String + + public init(displayAddress: String = "", + placeId: String = "", + position: Position = Position(), + timeZoneIdentifier: String = "") { + self.displayAddress = displayAddress + self.placeId = placeId + self.position = position + self.timeZoneIdentifier = timeZoneIdentifier + } + + enum CodingKeys: String, CodingKey { + case displayAddress = "display_address" + case placeId = "place_id" + + //Backend should really send us "time_zone" (https://jira.flit.tech/browse/PLATFORM-1859) + case timeZoneIdentifier = "timezone" + + case position = "position" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.displayAddress = (try? container.decode(String.self, forKey: .displayAddress)) ?? "" + self.placeId = (try? container.decode(String.self, forKey: .placeId)) ?? "" + self.position = (try? container.decode(Position.self, forKey: .position)) ?? Position() + self.timeZoneIdentifier = (try? container.decode(String.self, forKey: .timeZoneIdentifier)) ?? "" + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(displayAddress, forKey: .displayAddress) + try container.encode(placeId, forKey: .placeId) + try container.encode(position, forKey: .position) + try container.encode(timeZoneIdentifier, forKey: .timeZoneIdentifier) + } +} diff --git a/KarhooSDK/Api/Response/TripQuote.swift b/KarhooSDK/Api/Response/TripQuote.swift new file mode 100644 index 00000000..80cb6cc4 --- /dev/null +++ b/KarhooSDK/Api/Response/TripQuote.swift @@ -0,0 +1,84 @@ +// +// TripQuote.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct TripQuote: Codable { + + public let total: Int + public let currency: String + public let gratuityPercent: Int + public let breakdown: [FareComponent?] + public var denominateTotal: Double { + return Double(total) * 0.01 + } + public let qtaHighMinutes: Int + public let qtaLowMinutes: Int + public let type: QuoteType + public let vehicleClass: String + public let vehicleAttributes: VehicleAttributes + + public init(total: Int = 0, + currency: String = "", + gratuityPercent: Int = 0, + breakdown: [FareComponent?] = [FareComponent()], + qtaHighMinutes: Int = 0, + qtaLowMinutes: Int = 0, + type: QuoteType = .estimated, + vehicleClass: String = "", + vehicleAttributes: VehicleAttributes = VehicleAttributes()) { + self.total = total + self.currency = currency + self.gratuityPercent = gratuityPercent + self.breakdown = breakdown + self.qtaHighMinutes = qtaHighMinutes + self.qtaLowMinutes = qtaLowMinutes + self.type = type + self.vehicleClass = vehicleClass + self.vehicleAttributes = vehicleAttributes + } + + enum CodingKeys: String, CodingKey { + case total + case currency + case gratuityPercent = "gratuity_percent" + case breakdown + case qtaHighMinutes = "qta_high_minutes" + case qtaLowMinutes = "qta_low_minutes" + case type + case vehicleClass = "vehicle_class" + case vehicleAttributes = "vehicle_attributes" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.total = (try? container.decode(Int.self, forKey: .total)) ?? 0 + self.currency = (try? container.decode(String.self, forKey: .currency)) ?? "" + self.gratuityPercent = (try? container.decode(Int.self, forKey: .gratuityPercent)) ?? 0 + self.breakdown = (try? container.decode([FareComponent?].self, forKey: .breakdown)) ?? [] + self.qtaHighMinutes = (try? container.decode(Int.self, forKey: .qtaHighMinutes)) ?? 0 + self.qtaLowMinutes = (try? container.decode(Int.self, forKey: .qtaLowMinutes)) ?? 0 + self.type = (try? container.decode(QuoteType.self, forKey: .type)) ?? .estimated + self.vehicleClass = (try? container.decode(String.self, forKey: .vehicleClass)) ?? "" + self.vehicleAttributes = (try? container.decode(VehicleAttributes.self, forKey: .vehicleAttributes)) + ?? VehicleAttributes() + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(total, forKey: .total) + try container.encode(currency, forKey: .currency) + try container.encode(gratuityPercent, forKey: .gratuityPercent) + try container.encode(breakdown, forKey: .breakdown) + try container.encode(qtaHighMinutes, forKey: .qtaHighMinutes) + try container.encode(qtaLowMinutes, forKey: .qtaLowMinutes) + try container.encode(type, forKey: .type) + try container.encode(vehicleClass, forKey: .vehicleClass) + try container.encode(vehicleAttributes, forKey: .vehicleAttributes) + } +} diff --git a/KarhooSDK/Api/Response/TripState.swift b/KarhooSDK/Api/Response/TripState.swift new file mode 100644 index 00000000..70347397 --- /dev/null +++ b/KarhooSDK/Api/Response/TripState.swift @@ -0,0 +1,25 @@ +// +// TripState.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum TripState: String, KarhooCodableModel { + case requested = "REQUESTED" + case noDriversAvailable = "NO_DRIVERS_AVAILABLE" + case confirmed = "CONFIRMED" + case driverEnRoute = "DRIVER_EN_ROUTE" + case arrived = "ARRIVED" + case passengerOnBoard = "POB" + case completed = "COMPLETED" + case bookerCancelled = "BOOKER_CANCELLED" + case driverCancelled = "DRIVER_CANCELLED" + case karhooCancelled = "KARHOO_CANCELLED" + case failed = "FAILED" + case incomplete = "INCOMPLETE" + case unknown = "UNKNOWN" +} diff --git a/KarhooSDK/Api/Response/TripStatus.swift b/KarhooSDK/Api/Response/TripStatus.swift new file mode 100644 index 00000000..20437ce3 --- /dev/null +++ b/KarhooSDK/Api/Response/TripStatus.swift @@ -0,0 +1,29 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct TripStatus: KarhooCodableModel { + + public let status: TripState + + public init(status: TripState) { + self.status = status + } + + enum CodingKeys: String, CodingKey { + case status + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.status = (try? container.decode(TripState.self, forKey: .status)) ?? .unknown + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(status, forKey: .status) + } +} diff --git a/KarhooSDK/Api/Response/UIConfig.swift b/KarhooSDK/Api/Response/UIConfig.swift new file mode 100644 index 00000000..cc7d8ecc --- /dev/null +++ b/KarhooSDK/Api/Response/UIConfig.swift @@ -0,0 +1,14 @@ +// +// UIConfig.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct UIConfig: KarhooCodableModel { + + public let hidden: Bool +} diff --git a/KarhooSDK/Api/Response/UserInfo.swift b/KarhooSDK/Api/Response/UserInfo.swift new file mode 100644 index 00000000..0b835574 --- /dev/null +++ b/KarhooSDK/Api/Response/UserInfo.swift @@ -0,0 +1,107 @@ +// +// UserInfo.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct UserInfo: KarhooCodableModel, Equatable { + + public let userId: String + public let firstName: String + public let lastName: String + public let email: String + public let mobileNumber: String + public let locale: String + public let metadata: String + public let organisations: [Organisation] + public let primaryOrganisationID: String + public let avatarURL: String + public var nonce: Nonce? + public let externalId: String + + public init(userId: String = "", + firstName: String = "", + lastName: String = "", + email: String = "", + mobileNumber: String = "", + organisations: [Organisation] = [], + nonce: Nonce? = nil, + locale: String = "", + externalId: String = "") { + self.userId = userId + self.firstName = firstName + self.lastName = lastName + self.email = email + self.mobileNumber = mobileNumber + self.locale = "" + self.metadata = "" + self.organisations = organisations + self.primaryOrganisationID = "" + self.avatarURL = "" + self.nonce = nonce + self.externalId = externalId + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.userId = try container.decode(String.self, forKey: .userId) + self.firstName = (try? container.decode(String.self, forKey: .firstName)) ?? "" + self.lastName = (try? container.decode(String.self, forKey: .lastName)) ?? "" + self.email = (try? container.decode(String.self, forKey: .email)) ?? "" + self.mobileNumber = (try? container.decode(String.self, forKey: .mobileNumber)) ?? "" + self.locale = (try? container.decode(String.self, forKey: .locale)) ?? "" + self.metadata = (try? container.decode(String.self, forKey: .metadata)) ?? "" + self.organisations = (try? container.decode([Organisation].self, forKey: .organisations)) ?? [] + self.primaryOrganisationID = (try? container.decode(String.self, forKey: .primaryOrganisationID)) ?? "" + self.avatarURL = (try? container.decode(String.self, forKey: .avatarURL)) ?? "" + self.nonce = (try? container.decode(Nonce.self, forKey: .nonce)) ?? nil + if container.contains(.upstream) { + let upstream = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .upstream) + self.externalId = (try? upstream.decode(String.self, forKey: .externalId)) ?? "" + } else { + self.externalId = "" + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(userId, forKey: .userId) + try container.encode(firstName, forKey: .firstName) + try container.encode(lastName, forKey: .lastName) + try container.encode(email, forKey: .email) + try container.encode(mobileNumber, forKey: .mobileNumber) + try container.encode(locale, forKey: .locale) + try container.encode(metadata, forKey: .metadata) + try container.encode(organisations, forKey: .organisations) + try container.encode(primaryOrganisationID, forKey: .primaryOrganisationID) + try container.encode(avatarURL, forKey: .avatarURL) + try container.encode(nonce, forKey: .nonce) + var upstream = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .upstream) + try upstream.encode(externalId, forKey: .externalId) + } + + enum CodingKeys: String, CodingKey { + case userId = "user_id" + case firstName = "first_name" + case lastName = "last_name" + case email + case mobileNumber = "phone_number" + case locale + case metadata + case organisations + case primaryOrganisationID = "primary_organisation_id" + case avatarURL = "avatar_url" + case nonce + case upstream + case externalId = "sub" + } + + public static func == (lhs: UserInfo, rhs: UserInfo) -> Bool { + return lhs.userId == rhs.userId + } +} diff --git a/KarhooSDK/Api/Response/Vehicle.swift b/KarhooSDK/Api/Response/Vehicle.swift new file mode 100644 index 00000000..22092a32 --- /dev/null +++ b/KarhooSDK/Api/Response/Vehicle.swift @@ -0,0 +1,34 @@ +// +// Vehicle.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct Vehicle: Codable { + + public let vehicleClass: String + public let vehicleLicensePlate: String + public let description: String + public let driver: Driver + + public init(vehicleClass: String = "", + vehicleLicensePlate: String = "", + description: String = "", + driver: Driver = Driver()) { + self.vehicleClass = vehicleClass + self.vehicleLicensePlate = vehicleLicensePlate + self.description = description + self.driver = driver + } + + enum CodingKeys: String, CodingKey { + case vehicleClass = "vehicle_class" + case vehicleLicensePlate = "vehicle_license_plate" + case driver + case description + } +} diff --git a/KarhooSDK/Api/Response/VehicleAttributes.swift b/KarhooSDK/Api/Response/VehicleAttributes.swift new file mode 100644 index 00000000..ca90b595 --- /dev/null +++ b/KarhooSDK/Api/Response/VehicleAttributes.swift @@ -0,0 +1,56 @@ +// +// VehicleAttributes.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct VehicleAttributes: Codable, Equatable { + + public let childSeat: Bool + public let electric: Bool + public let hybrid: Bool + public let luggageCapacity: Int + public let passengerCapacity: Int + + public init(childSeat: Bool = false, + electric: Bool = false, + hybrid: Bool = false, + luggageCapacity: Int = 0, + passengerCapacity: Int = 0) { + self.childSeat = childSeat + self.electric = electric + self.hybrid = hybrid + self.luggageCapacity = luggageCapacity + self.passengerCapacity = passengerCapacity + } + + enum CodingKeys: String, CodingKey { + case childSeat = "child_seat" + case electric + case hybrid + case luggageCapacity = "luggage_capacity" + case passengerCapacity = "passenger_capacity" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.childSeat = (try? container.decode(Bool.self, forKey: .childSeat)) ?? false + self.electric = (try? container.decode(Bool.self, forKey: .electric)) ?? false + self.hybrid = (try? container.decode(Bool.self, forKey: .hybrid)) ?? false + self.luggageCapacity = (try? container.decode(Int.self, forKey: .luggageCapacity)) ?? 0 + self.passengerCapacity = (try? container.decode(Int.self, forKey: .passengerCapacity)) ?? 0 + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(childSeat, forKey: .childSeat) + try container.encode(electric, forKey: .electric) + try container.encode(hybrid, forKey: .hybrid) + try container.encode(luggageCapacity, forKey: .luggageCapacity) + try container.encode(passengerCapacity, forKey: .passengerCapacity) + } +} diff --git a/KarhooSDK/Api/SDKConfiguration/AnalyticsProvider.swift b/KarhooSDK/Api/SDKConfiguration/AnalyticsProvider.swift new file mode 100644 index 00000000..53249344 --- /dev/null +++ b/KarhooSDK/Api/SDKConfiguration/AnalyticsProvider.swift @@ -0,0 +1,19 @@ +// +// AnalyticsProviderProtocol.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol AnalyticsProvider { + func trackEvent(name: String) + func trackEvent(name: String, payload: [String: Any]?) +} + +internal struct DefaultAnalyticsProvider: AnalyticsProvider { + func trackEvent(name: String) {} + func trackEvent(name: String, payload: [String: Any]?) {} +} diff --git a/KarhooSDK/Api/SDKConfiguration/AuthenticationMethod.swift b/KarhooSDK/Api/SDKConfiguration/AuthenticationMethod.swift new file mode 100644 index 00000000..c1871d17 --- /dev/null +++ b/KarhooSDK/Api/SDKConfiguration/AuthenticationMethod.swift @@ -0,0 +1,29 @@ +// +// AuthenticationMethod.swift +// KarhooSDK +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +public enum AuthenticationMethod { + case karhooUser + case tokenExchange(settings: TokenExchangeSettings) + case guest(settings: GuestSettings) + + public var tokenExchangeSettings: TokenExchangeSettings? { + switch self { + case .tokenExchange(let settings): + return settings + default: return nil + } + } + + public var guestSettings: GuestSettings? { + switch self { + case .guest(let settings): + return settings + default: return nil + } + } +} diff --git a/KarhooSDK/Api/SDKConfiguration/GuestSettings.swift b/KarhooSDK/Api/SDKConfiguration/GuestSettings.swift new file mode 100644 index 00000000..1f88bb4d --- /dev/null +++ b/KarhooSDK/Api/SDKConfiguration/GuestSettings.swift @@ -0,0 +1,22 @@ +// +// GuestSettings.swift +// KarhooSDK +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +public struct GuestSettings { + + public let identifier: String + public let referer: String + public let organisationId: String + + public init(identifier: String, + referer: String, + organisationId: String) { + self.identifier = identifier + self.referer = referer + self.organisationId = organisationId + } +} diff --git a/KarhooSDK/Api/SDKConfiguration/KarhooSDKConfiguration.swift b/KarhooSDK/Api/SDKConfiguration/KarhooSDKConfiguration.swift new file mode 100644 index 00000000..49318780 --- /dev/null +++ b/KarhooSDK/Api/SDKConfiguration/KarhooSDKConfiguration.swift @@ -0,0 +1,25 @@ +// +// KarhooSDKConfiguration.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol KarhooSDKConfiguration { + + func environment() -> KarhooEnvironment + + func authenticationMethod() -> AuthenticationMethod + + func analyticsProvider() -> AnalyticsProvider +} + +public extension KarhooSDKConfiguration { + + func analyticsProvider() -> AnalyticsProvider { + return DefaultAnalyticsProvider() + } +} diff --git a/KarhooSDK/Api/SDKConfiguration/TokenExchangeSettings.swift b/KarhooSDK/Api/SDKConfiguration/TokenExchangeSettings.swift new file mode 100644 index 00000000..fa03f6cd --- /dev/null +++ b/KarhooSDK/Api/SDKConfiguration/TokenExchangeSettings.swift @@ -0,0 +1,17 @@ +// +// TokenExchangeSettings.swift +// KarhooSDK +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +public struct TokenExchangeSettings { + let clientId: String + let scope: String + + public init(clientId: String, scope: String) { + self.clientId = clientId + self.scope = scope + } +} diff --git a/KarhooSDK/Api/Util/AppStateNotifier/AppStateNotifier.swift b/KarhooSDK/Api/Util/AppStateNotifier/AppStateNotifier.swift new file mode 100644 index 00000000..f29ae821 --- /dev/null +++ b/KarhooSDK/Api/Util/AppStateNotifier/AppStateNotifier.swift @@ -0,0 +1,94 @@ +// +// AppStateNotifier.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import UIKit + +public protocol AppStateChangeDelegate: class { + func appDidBecomeActive() + func appWillResignActive() + func appDidEnterBackground() + func appWillEnterForeground() + func appWillTerminate() +} + +public extension AppStateChangeDelegate { + func appDidBecomeActive() { } + func appWillResignActive() { } + func appWillTerminate() { } + func appDidEnterBackground() { } + func appWillEnterForeground() { } +} + +public protocol AppStateNotifierProtocol { + func register(listener: AppStateChangeDelegate) + func remove(listener: AppStateChangeDelegate) +} + +public final class AppStateNotifier: AppStateNotifierProtocol { + private let broadcaster = Broadcaster() + public static let shared = AppStateNotifier() + + public init() { + NotificationCenter.default.addObserver(self, + selector: #selector(appDidBecomeActive), + name: UIApplication.didBecomeActiveNotification, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(appWillResignActive), + name: UIApplication.willResignActiveNotification, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(appWillTerminate), + name: UIApplication.willTerminateNotification, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(appDidEnterBackground), + name: UIApplication.didEnterBackgroundNotification, + object: nil) + + NotificationCenter.default.addObserver(self, + selector: #selector(appWillEnterForeground), + name: UIApplication.willEnterForegroundNotification, + object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + public func register(listener: AppStateChangeDelegate) { + broadcaster.add(listener: listener) + } + + public func remove(listener: AppStateChangeDelegate) { + broadcaster.remove(listener: listener) + } + + @objc private func appDidBecomeActive() { + broadcaster.broadcast { ($0 as? AppStateChangeDelegate)?.appDidBecomeActive() } + } + + @objc private func appWillResignActive() { + broadcaster.broadcast { ($0 as? AppStateChangeDelegate)?.appWillResignActive() } + } + + @objc private func appWillTerminate() { + broadcaster.broadcast { ($0 as? AppStateChangeDelegate)?.appWillTerminate() } + } + + @objc private func appDidEnterBackground() { + broadcaster.broadcast { ($0 as? AppStateChangeDelegate)?.appDidEnterBackground() } + } + + @objc private func appWillEnterForeground() { + broadcaster.broadcast { ($0 as? AppStateChangeDelegate)?.appWillEnterForeground() } + } +} diff --git a/KarhooSDK/Api/Util/BatteryMonitor.swift b/KarhooSDK/Api/Util/BatteryMonitor.swift new file mode 100644 index 00000000..23778287 --- /dev/null +++ b/KarhooSDK/Api/Util/BatteryMonitor.swift @@ -0,0 +1,20 @@ +// +// BatteryMonitor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import UIKit + +final class BatteryMonitor { + public var batteryLevel: Float { + return UIDevice.current.batteryLevel + } + + init() { + UIDevice.current.isBatteryMonitoringEnabled = true + } +} diff --git a/KarhooSDK/Api/Util/Broadcaster/Broadcaster.swift b/KarhooSDK/Api/Util/Broadcaster/Broadcaster.swift new file mode 100755 index 00000000..ea18fc1b --- /dev/null +++ b/KarhooSDK/Api/Util/Broadcaster/Broadcaster.swift @@ -0,0 +1,76 @@ +// +// Broadcaster.swift +// YMBroadcaster +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +/** + * @brief "Broadcasts" method invocations to it's added listeners. Does not + * retain the listeners. Meant to be an easier-to-follow and less crash-prone + * replacement for NSNotifications + */ +open class Broadcaster { + + private var listeners = [WeakReferenceWrapper]() + + // MARK: - Public functions + + required public init() { } + + /** + * @breif Adds a listener to the broadcaster + * @param listener The listener to add. Will not be retained. + */ + open func add(listener: T) { + let entry = WeakReferenceWrapper(listener) + listeners.append(entry) + } + + /** + * @breif Removes a listener from the broadcaster + * @param listener The listener to remove. + */ + open func remove(listener: T) { + for (index, element) in listeners.enumerated() { + if element.getReference() === listener { + listeners.remove(at: index) + } + } + } + + open func broadcast(closure: (T) -> Void) { + var zombieFound = false + listeners.forEach { (entry: WeakReferenceWrapper) in + if let reference = entry.getReference() { + closure(reference) + } else { + zombieFound = true + } + } + + if zombieFound { + compact() + } + } + + /** + * @breif Returns true if there are any listeners left + */ + open func hasListeners() -> Bool { + compact() + return listeners.count > 0 + } + + // MARK: - Private functions + + private func compact() { + let compacted = listeners.filter { (wrapper: WeakReferenceWrapper) -> Bool in + return wrapper.getReference() != nil + } + listeners = compacted + } +} diff --git a/KarhooSDK/Api/Util/DeviceIdentifierProvider.swift b/KarhooSDK/Api/Util/DeviceIdentifierProvider.swift new file mode 100644 index 00000000..3c83e14e --- /dev/null +++ b/KarhooSDK/Api/Util/DeviceIdentifierProvider.swift @@ -0,0 +1,82 @@ +// +// DeviceIdentifierProvider.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import KeychainSwift + +protocol KeychainWrapperProtocol { + func set(_ value: String, forKey: String) + func get(_ key: String) -> String? +} + +final class KeychainWrapper: KeychainWrapperProtocol { + private let keychain = KeychainSwift() + + func set(_ value: String, forKey key: String) { + keychain.set(value, forKey: key) + } + + func get(_ key: String) -> String? { + return keychain.get(key) + } +} + +public final class DeviceIdentifierProvider { + public static let shared = DeviceIdentifierProvider() + + private var sessionId: String? + public var restartSessionManually = false + private let appStateNotifier: AppStateNotifier + + private init(appStateNotifier: AppStateNotifier = AppStateNotifier.shared) { + self.appStateNotifier = appStateNotifier + self.appStateNotifier.register(listener: self) + } + + deinit { + self.appStateNotifier.remove(listener: self) + } + + /** + * This UUID stays the same even after app closes + * or gets reinstalled + */ + public func getPermamentID() -> String { + let keychainKey = "karhoo_uuid" + let keychain = KeychainWrapper() + + var uuid = keychain.get(keychainKey) + if uuid?.isEmpty != false { + uuid = UUID().uuidString + keychain.set(uuid ?? "", forKey: keychainKey) + } + return uuid ?? "" + } + + /** + * This UUID changes every time app goes into background + */ + public func getSessionID() -> String { + if sessionId == nil { + restartSessionID() + } + return sessionId ?? "" + } + + public func restartSessionID() { + sessionId = UUID().uuidString + } +} + +extension DeviceIdentifierProvider: AppStateChangeDelegate { + public func appDidEnterBackground() { + if !restartSessionManually { + restartSessionID() + } + } +} diff --git a/KarhooSDK/Api/Util/Extention/CLLocationExt.swift b/KarhooSDK/Api/Util/Extention/CLLocationExt.swift new file mode 100644 index 00000000..a71e8eea --- /dev/null +++ b/KarhooSDK/Api/Util/Extention/CLLocationExt.swift @@ -0,0 +1,17 @@ +// +// CLLocationExt.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import CoreLocation + +public extension CLLocation { + func toPosition() -> Position { + return Position(latitude: self.coordinate.latitude, + longitude: self.coordinate.longitude) + } +} diff --git a/KarhooSDK/Api/Util/Extention/LocationDetailsExt.swift b/KarhooSDK/Api/Util/Extention/LocationDetailsExt.swift new file mode 100644 index 00000000..49c297b8 --- /dev/null +++ b/KarhooSDK/Api/Util/Extention/LocationDetailsExt.swift @@ -0,0 +1,20 @@ +// +// LocationInfoExt.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public extension LocationInfo { + + func timezone() -> TimeZone { + guard let defaultTimeZone = TimeZone(identifier: self.timeZoneIdentifier) else { + return TimeZone.current + } + + return defaultTimeZone + } +} diff --git a/KarhooSDK/Api/Util/Extention/PositionExt.swift b/KarhooSDK/Api/Util/Extention/PositionExt.swift new file mode 100644 index 00000000..c67d0eb8 --- /dev/null +++ b/KarhooSDK/Api/Util/Extention/PositionExt.swift @@ -0,0 +1,18 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import CoreLocation + +public extension Position { + func isValid() -> Bool { + return self.latitude == 0 && self.longitude == 0 ? false : true + } + + func toCLLocation() -> CLLocation { + return CLLocation(latitude: self.latitude, + longitude: self.longitude) + } +} diff --git a/KarhooSDK/Api/Util/KarhooNetworkDateFormatter.swift b/KarhooSDK/Api/Util/KarhooNetworkDateFormatter.swift new file mode 100644 index 00000000..4961af59 --- /dev/null +++ b/KarhooSDK/Api/Util/KarhooNetworkDateFormatter.swift @@ -0,0 +1,46 @@ +// +// KarhooNetworkDateFormatter.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +/** + * date format: ISO-8601 + * date_required has to be sent in local time + * date_scheduled is UTC time. So when we parse dates, + * we have to provide formatting context (KarhooDateFormat) +-------- + * local time: https://docs.stg.karhoo.net/v1/quotes#quoterequest (date_required) + * UTC time: https://docs.stg.karhoo.net/v1/bookings#booking (date_scheduled) + */ + +public enum KarhooDateFormat: String { + case booking = "yyyy-MM-dd'T'HH:mm:ssz" + case availability = "yyyy-MM-dd'T'HH:mm" +} + +public final class KarhooNetworkDateFormatter: NetworkDateFormatter { + + private let dateFormatter: DateFormatter + + public init(timeZone: TimeZone? = TimeZone(secondsFromGMT: 0)!, + formatType: KarhooDateFormat) { + dateFormatter = DateFormatter() + dateFormatter.calendar = Calendar(identifier: .iso8601) + dateFormatter.locale = Locale(identifier: "en_US_POSIX") + dateFormatter.timeZone = timeZone + dateFormatter.dateFormat = formatType.rawValue + } + + func toString(from date: Date) -> String { + return dateFormatter.string(from: date) + } + + public func toDate(from string: String) -> Date? { + return dateFormatter.date(from: string) + } +} diff --git a/KarhooSDK/Api/Util/KarhooTimingScheduler.swift b/KarhooSDK/Api/Util/KarhooTimingScheduler.swift new file mode 100644 index 00000000..7efd5f86 --- /dev/null +++ b/KarhooSDK/Api/Util/KarhooTimingScheduler.swift @@ -0,0 +1,43 @@ +// +// KarhooTimingScheduler.swift +// Karhoo +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol TimingScheduler { + func fireAndSchedule(block: @escaping () -> Void, in timeInterval: TimeInterval, repeats: Bool) + func invalidate() +} + +public final class KarhooTimingScheduler: TimingScheduler { + private var timer: Timer? + private var block: (() -> Void)? + private var repeats: Bool? + + public init() { } + + public func fireAndSchedule(block: @escaping () -> Void, in timeInterval: TimeInterval, repeats: Bool) { + invalidate() + self.block = block + self.repeats = repeats + fire() + timer = Timer.scheduledTimer(timeInterval: timeInterval, + target: self, + selector: #selector(KarhooTimingScheduler.fire), + userInfo: nil, + repeats: repeats) + } + + @objc private func fire() { + block?() + } + + public func invalidate() { + timer?.invalidate() + timer = nil + } +} diff --git a/KarhooSDK/Api/Util/NetworkConnectionTypeProvider.swift b/KarhooSDK/Api/Util/NetworkConnectionTypeProvider.swift new file mode 100644 index 00000000..df828362 --- /dev/null +++ b/KarhooSDK/Api/Util/NetworkConnectionTypeProvider.swift @@ -0,0 +1,45 @@ +// +// NetworkConnectionTypeProvider.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import CoreTelephony + +final class NetworkConnectionTypeProvider { + func connectionType() -> String { + let status = ReachabilityWrapper.shared.currentReachabilityStatus + var networkType = status.description + + if status == .cellular { + networkType = coreTelephonyNetworkStatus() + } + return networkType + } + + private func coreTelephonyNetworkStatus() -> String { + let networkString = CTTelephonyNetworkInfo().currentRadioAccessTechnology ?? "" + + switch networkString { + case CTRadioAccessTechnologyLTE: + return "4G" + case CTRadioAccessTechnologyGPRS, + CTRadioAccessTechnologyEdge, + CTRadioAccessTechnologyCDMA1x: + return "2G" + case CTRadioAccessTechnologyWCDMA, + CTRadioAccessTechnologyHSDPA, + CTRadioAccessTechnologyHSUPA, + CTRadioAccessTechnologyCDMAEVDORev0, + CTRadioAccessTechnologyCDMAEVDORevA, + CTRadioAccessTechnologyCDMAEVDORevB, + CTRadioAccessTechnologyeHRPD: + return "3G" + default: + return "" + } + } +} diff --git a/KarhooSDK/Api/Util/NetworkDateFormatter.swift b/KarhooSDK/Api/Util/NetworkDateFormatter.swift new file mode 100644 index 00000000..f85f7376 --- /dev/null +++ b/KarhooSDK/Api/Util/NetworkDateFormatter.swift @@ -0,0 +1,15 @@ +// +// NetworkDateFormatter.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol NetworkDateFormatter { + + func toString(from: Date) -> String + +} diff --git a/KarhooSDK/Api/Util/ReachabilityWrapper.swift b/KarhooSDK/Api/Util/ReachabilityWrapper.swift new file mode 100644 index 00000000..dd25bb04 --- /dev/null +++ b/KarhooSDK/Api/Util/ReachabilityWrapper.swift @@ -0,0 +1,112 @@ +// +// ReachabilityWrapper.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import Reachability + +public protocol ReachabilityProvider { + func add(listener: ReachabilityListener) + func remove(listener: ReachabilityListener) + func isReachable() -> Bool + var currentReachabilityStatus: Reachability.Connection.Type { get } +} + +public extension ReachabilityProvider { + var currentReachabilityStatus: Reachability.Connection.Type { + return Reachability.Connection.self + } +} + +public protocol ReachabilityListener: class { + func reachabilityChanged(isReachable: Bool) +} + +protocol ReachabilityProtocol { + func startNotifier() throws + func stopNotifier() + var currentReachabilityStatus: Reachability.Connection { get } + var whenReachable: Reachability.NetworkReachable? { get set } + var whenUnreachable: Reachability.NetworkUnreachable? { get set } +} + +extension Reachability: ReachabilityProtocol { + var currentReachabilityStatus: Reachability.Connection { + return self.connection + } +} + +public final class ReachabilityWrapper: ReachabilityProvider { + + public static let shared = ReachabilityWrapper(reachability: try! Reachability()) + + private(set) var reachability: ReachabilityProtocol + private let broadcaster: Broadcaster + private var lastStatus: Reachability.Connection + + public var currentReachabilityStatus: Reachability.Connection { + return reachability.currentReachabilityStatus + } + + init(reachability: ReachabilityProtocol, + broadcaster: Broadcaster = Broadcaster()) { + self.reachability = reachability + lastStatus = reachability.currentReachabilityStatus + self.broadcaster = broadcaster + + self.reachability.whenReachable = { [weak self] (_) in + self?.reactToPotentialNetworkStatusChange() + } + + self.reachability.whenUnreachable = { [weak self] (_) in + self?.reactToPotentialNetworkStatusChange() + } + } + + public func add(listener: ReachabilityListener) { + if !broadcaster.hasListeners() { + try? reachability.startNotifier() + } + + broadcaster.add(listener: listener) + listener.reachabilityChanged(isReachable: reachability.currentReachabilityStatus != .unavailable) + } + + public func remove(listener: ReachabilityListener) { + broadcaster.remove(listener: listener) + + if !broadcaster.hasListeners() { + reachability.stopNotifier() + } + } + + public func isReachable() -> Bool { + return reachability.currentReachabilityStatus != .unavailable + } + + private func reactToPotentialNetworkStatusChange() { + let currentStatus = reachability.currentReachabilityStatus + + let wasReachable = lastStatus != .unavailable + let isReachable = currentStatus != .unavailable + guard wasReachable != isReachable else { + return + } + + lastStatus = currentStatus + broadcast(isReachable: currentStatus != .unavailable) + } + + private func broadcast(isReachable: Bool) { + broadcaster.broadcast { (listener: AnyObject) in + guard let listener = listener as? ReachabilityListener else { + return + } + listener.reachabilityChanged(isReachable: isReachable) + } + } +} diff --git a/KarhooSDK/Api/Util/Result.swift b/KarhooSDK/Api/Util/Result.swift new file mode 100644 index 00000000..13c6dfa9 --- /dev/null +++ b/KarhooSDK/Api/Util/Result.swift @@ -0,0 +1,56 @@ +// +// Result.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum Result { + case success(result: T) + case failure(error: KarhooError?) + + public func errorValue() -> KarhooError? { + switch self { + case .failure(let error): + return error + + default: + return nil + } + } + + public func successValue() -> T? { + switch self { + case .success(let result): + return result + + default: + return nil + } + } + + public func isSuccess() -> Bool { + switch self { + case .success: + return true + + default: + return false + } + } + + public func successValue(orErrorCallback: CallbackClosure) -> T? { + switch self { + case .success(let result): + return result + case .failure(let error): + orErrorCallback(.failure(error: error)) + return nil + } + } +} + +public typealias CallbackClosure = (Result) -> Void diff --git a/KarhooSDK/Api/Util/TimestampFormatter.swift b/KarhooSDK/Api/Util/TimestampFormatter.swift new file mode 100644 index 00000000..ab7655d7 --- /dev/null +++ b/KarhooSDK/Api/Util/TimestampFormatter.swift @@ -0,0 +1,23 @@ +// +// TimestampFormatter.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public final class TimestampFormatter { + private let timestampFormatter: DateFormatter + + public init() { + timestampFormatter = DateFormatter() + timestampFormatter.timeZone = TimeZone.current + timestampFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + } + + public func formattedDate(_ date: Date) -> String { + return timestampFormatter.string(from: date) + } +} diff --git a/KarhooSDK/Api/Util/UserLocation/UserLocationProvider.swift b/KarhooSDK/Api/Util/UserLocation/UserLocationProvider.swift new file mode 100644 index 00000000..4b4c4a0e --- /dev/null +++ b/KarhooSDK/Api/Util/UserLocation/UserLocationProvider.swift @@ -0,0 +1,49 @@ +// +// UserLocationProvider.swift +// Karhoo +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import CoreLocation + +public typealias LocationProvidedClosure = (CLLocation) -> Void + +public protocol UserLocationProvider { + func set(locationChangedCallback: LocationProvidedClosure?) + func getLastKnownLocation() -> CLLocation? +} + +public class KarhooUserLocationProvider: NSObject, CLLocationManagerDelegate, UserLocationProvider { + private let locationManager: CLLocationManager + private var locationChangedCallback: LocationProvidedClosure? + public static let shared = KarhooUserLocationProvider() + + init(locationManager: CLLocationManager = CLLocationManager()) { + self.locationManager = locationManager + super.init() + + locationManager.activityType = .fitness + locationManager.desiredAccuracy = kCLLocationAccuracyBest + locationManager.delegate = self + locationManager.startUpdatingLocation() + } + + public func set(locationChangedCallback: LocationProvidedClosure?) { + self.locationChangedCallback = locationChangedCallback + } + + public func getLastKnownLocation() -> CLLocation? { + return self.locationManager.location + } + + public func locationManager(_ manager: CLLocationManager, + didUpdateLocations locations: [CLLocation]) { + guard let location = locations.first else { + return + } + locationChangedCallback?(location) + } +} diff --git a/KarhooSDK/Api/Util/WeakReferenceWrapper.swift b/KarhooSDK/Api/Util/WeakReferenceWrapper.swift new file mode 100755 index 00000000..e825aa69 --- /dev/null +++ b/KarhooSDK/Api/Util/WeakReferenceWrapper.swift @@ -0,0 +1,29 @@ +// +// WeakReferenceWrapper.swift +// YMBroadcaster +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +/** + * @brief Class which wraps a pointer weakly to prevent retention + */ +class WeakReferenceWrapper { + + private weak var weakReference: T? + + // MARK: - Init functions + + required init(_ reference: T) { + self.weakReference = reference + } + + // MARK: - Public functions + + func getReference() -> T? { + return self.weakReference + } +} diff --git a/KarhooSDK/Info.plist b/KarhooSDK/Info.plist new file mode 100644 index 00000000..d2d7d0bb --- /dev/null +++ b/KarhooSDK/Info.plist @@ -0,0 +1,37 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleURLTypes + + + CFBundleTypeRole + None + CFBundleURLName + auth0 + CFBundleURLSchemes + + $(PRODUCT_BUNDLE_IDENTIFIER) + + + + CFBundleVersion + 12 + NSPrincipalClass + + + diff --git a/KarhooSDK/Network/APIEndpoint.swift b/KarhooSDK/Network/APIEndpoint.swift new file mode 100644 index 00000000..f0b6e315 --- /dev/null +++ b/KarhooSDK/Network/APIEndpoint.swift @@ -0,0 +1,157 @@ +import Foundation + +enum APIEndpoint { + case availability + case quoteListId + case quotes(identifier: String) + case bookTrip + case bookTripWithNonce + case cancelTrip(identifier: String) + case trackDriver(identifier: String) + case trackTrip(identifier: String) + case tripSearch + case tripStatus(identifier: String) + case getFareDetails(identifier: String) + case locationInfo + case placeSearch + case reverseGeocode(position: Position) + case login + case register + case userProfile + case userProfileUpdate(identifier: String) + case passwordReset + case paymentSDKToken(payload: PaymentSDKTokenPayload) + case addPaymentDetails + case getNonce + case karhooUserTokenRefresh + case custom(path: String, method: HttpMethod) + case authTokenExchange + case authRevoke + case authUserInfo + case authRefresh + + var path: String { + switch self { + case .custom(let path, _): + return path + default: + return "/\(version)\(relativePath)" + } + } + + var relativePath: String { + switch self { + case .availability: + return "/quotes/availability" + case .quoteListId: + return "/quotes/" + case .quotes(let identifier): + return "/quotes/\(identifier)" + case .bookTrip: + return "/bookings" + case .bookTripWithNonce: + return "/bookings/with-nonce" + case .cancelTrip(let identifier): + return "/bookings/\(identifier)/cancel" + case .trackDriver(let identifier): + return "/bookings/\(identifier)/track" + case .trackTrip(let identifier): + return "/bookings/\(identifier)" + case .tripSearch: + return "/bookings/search" + case .tripStatus(let identifier): + return "/bookings/\(identifier)/status" + case .getFareDetails(let identifier): + return "/fares/trip/\(identifier)" + case .locationInfo: + return "/locations/place-details" + case .placeSearch: + return "/locations/address-autocomplete" + case .reverseGeocode(let position): + return "/locations/reverse-geocode?latitude=\(position.latitude)&longitude=\(position.longitude)" + case .login: + return "/auth/token" + case .register: + return "/directory/users" + case .userProfile: + return "/directory/users/me" + case .userProfileUpdate(let indentifier): + return "/directory/users/\(indentifier)" + case .passwordReset: + return "/directory/users/password-reset" + case .paymentSDKToken(let payload): + return "/payments/payment-methods/braintree/client-tokens?organisation_id=\(payload.organisationId)" + + "¤cy=\(payload.currency)" + case .getNonce: + return "/payments/payment-methods/braintree/get-nonce" + case .addPaymentDetails: + return "/payments/payment-methods/braintree/add-payment-details" + case .karhooUserTokenRefresh: + return "/auth/refresh" + case .custom(let path, _): + return path + case .authTokenExchange: + return "/karhoo/anonymous/token-exchange" + case .authRevoke: + return "/oauth/v2/revoke" + case .authUserInfo: + return "/oauth/v2/userinfo" + case .authRefresh: + return "/oauth/v2/token" + } + } + + var method: HttpMethod { + switch self { + case .availability: return .post + case .quoteListId: return .post + case .quotes: return .get + case .bookTrip: return .post + case .bookTripWithNonce: return .post + case .cancelTrip: return .post + case .trackDriver: return .get + case .trackTrip: return .get + case .tripSearch: return .post + case .tripStatus: return .get + case .getFareDetails: return .get + case .locationInfo: return .post + case .placeSearch: return .post + case .reverseGeocode: return .get + case .login: return .post + case .register: return .post + case .userProfile: return .get + case .userProfileUpdate: return .put + case .passwordReset: return .post + case .paymentSDKToken: return .post + case .getNonce: return .post + case .addPaymentDetails: return .post + case .karhooUserTokenRefresh: return .post + case .authTokenExchange: return .post + case .authRevoke: return .post + case .authUserInfo: return .get + case .authRefresh: return .post + case .custom(_, let method): return method + } + } + + private var version: String { + switch self { + case .addPaymentDetails: return "v2" + case .getNonce: return "v2" + case .paymentSDKToken: return "v2" + default: return "v1" + } + } +} + +extension APIEndpoint: Equatable { + public static func == (lhs: APIEndpoint, rhs: APIEndpoint) -> Bool { + return lhs.path == rhs.path + } + public static func == (lhs: APIEndpoint?, rhs: APIEndpoint) -> Bool { + return lhs?.path == rhs.path + } + public static func == (lhs: APIEndpoint, rhs: APIEndpoint?) -> Bool { + return lhs.path == rhs?.path + } +} diff --git a/KarhooSDK/Network/AccessTokenProvider.swift b/KarhooSDK/Network/AccessTokenProvider.swift new file mode 100644 index 00000000..ea994023 --- /dev/null +++ b/KarhooSDK/Network/AccessTokenProvider.swift @@ -0,0 +1,30 @@ +import Foundation + +public struct AccessToken { + let token: String + + public init(token: String) { + self.token = token + } +} + +public protocol AccessTokenProvider { + var accessToken: AccessToken? { get } +} + +public class DefaultAccessTokenProvider: AccessTokenProvider { + public var accessToken: AccessToken? { + if let token = userStore.getCurrentCredentials()?.accessToken { + return AccessToken(token: token) + } + return nil + } + + static let shared = DefaultAccessTokenProvider() + + private let userStore: UserDataStore + + init(userStore: UserDataStore = DefaultUserDataStore()) { + self.userStore = userStore + } +} diff --git a/KarhooSDK/Network/Common/Environment/KarhooEnvironment.swift b/KarhooSDK/Network/Common/Environment/KarhooEnvironment.swift new file mode 100644 index 00000000..63ec38c7 --- /dev/null +++ b/KarhooSDK/Network/Common/Environment/KarhooEnvironment.swift @@ -0,0 +1,15 @@ +// +// KarhooEnvironment.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum KarhooEnvironment { + case sandbox + case production + case custom(environment: KarhooEnvironmentDetails) +} diff --git a/KarhooSDK/Network/Common/Error/Errors.swift b/KarhooSDK/Network/Common/Error/Errors.swift new file mode 100644 index 00000000..276139f2 --- /dev/null +++ b/KarhooSDK/Network/Common/Error/Errors.swift @@ -0,0 +1,27 @@ +// +// Errors.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +extension KarhooError { + + public func isConnectionError() -> Bool { + guard let httpError = self as? HTTPError else { return false } + return httpError.errorType == .connectionLost || httpError.errorType == .notConnectedToInternet + } + + func isUnauthorizedError() -> Bool { + var statusCode = 0 + if let sdkError = self as? KarhooSDKError { + statusCode = sdkError.statusCode + } else if let httpError = self as? HTTPError { + statusCode = httpError.statusCode + } + return statusCode == 401 + } +} diff --git a/KarhooSDK/Network/Common/Error/HTTPError.swift b/KarhooSDK/Network/Common/Error/HTTPError.swift new file mode 100644 index 00000000..f11da441 --- /dev/null +++ b/KarhooSDK/Network/Common/Error/HTTPError.swift @@ -0,0 +1,99 @@ +// +// HTTPError.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public struct HTTPError: KarhooError, Equatable { + public let statusCode: Int + public let errorType: ErrorType + public let message: String + + public var code: String { + return "HTTP\(statusCode)" + } + + public init(statusCode: Int, + error: NSError?) { + self.statusCode = statusCode + self.errorType = ErrorType(rawValue: error?.code ?? 0) ?? .unknown + self.message = error?.localizedDescription ?? "" + } + + public init(statusCode: Int, + errorType: ErrorType, + message: String = "") { + self.statusCode = statusCode + self.errorType = errorType + self.message = message + } + + static var unauthorizedError: HTTPError { + return HTTPError(statusCode: 401, errorType: .userAuthenticationRequired) + } + + public enum ErrorType: Int { + case unknown = -1 + case cancelled = -999 + case badURL = -1000 + case timedOut = -1001 + case unsupportedURL = -1002 + case cannotFindHost = -1003 + case cannotConnectToHost = -1004 + case connectionLost = -1005 + case lookupFailed = -1006 + case HTTPTooManyRedirects = -1007 + case resourceUnavailable = -1008 + case notConnectedToInternet = -1009 + case redirectToNonExistentLocation = -1010 + case badServerResponse = -1011 + case userCancelledAuthentication = -1012 + case userAuthenticationRequired = -1013 + case zeroByteResource = -1014 + case cannotDecodeRawData = -1015 + case cannotDecodeContentData = -1016 + case cannotParseResponse = -1017 + case appTransportSecurityRequiresSecureConnection = -1022 + case fileDoesNotExist = -1100 + case fileIsDirectory = -1101 + case noPermissionsToReadFile = -1102 + case dataLengthExceedsMaximum = -1103 + + // SSL errors + case secureConnectionFailed = -1200 + case serverCertificateHasBadDate = -1201 + case serverCertificateUntrusted = -1202 + case serverCertificateHasUnknownRoot = -1203 + case serverCertificateNotYetValid = -1204 + case clientCertificateRejected = -1205 + case clientCertificateRequired = -1206 + case cannotLoadFromNetwork = -2000 + + // Download and file I/O errors + case cannotCreateFile = -3000 + case cannotOpenFile = -3001 + case cannotCloseFile = -3002 + case cannotWriteToFile = -3003 + case cannotRemoveFile = -3004 + case cannotMoveFile = -3005 + case downloadDecodingFailedMidStream = -3006 + case downloadDecodingFailedToComplete = -3007 + + case internationalRoamingOff = -1018 + case callIsActive = -1019 + case dataNotAllowed = -1020 + case requestBodyStreamExhausted = -1021 + + case backgroundSessionRequiresSharedContainer = -995 + case backgroundSessionInUseByAnotherProcess = -996 + case backgroundSessionWasDisconnected = -997 + } + + public static func == (lhs: HTTPError, rhs: HTTPError) -> Bool { + return lhs.statusCode == rhs.statusCode && lhs.errorType == rhs.errorType + } +} diff --git a/KarhooSDK/Network/Common/Error/KarhooError.swift b/KarhooSDK/Network/Common/Error/KarhooError.swift new file mode 100644 index 00000000..4f58fa65 --- /dev/null +++ b/KarhooSDK/Network/Common/Error/KarhooError.swift @@ -0,0 +1,36 @@ +// +// KarhooError.swift +// KarhooSDK +// +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +public protocol KarhooError: Error { + var code: String { get } + var message: String { get } + var userMessage: String { get } + var type: KarhooErrorType { get } +} + +extension KarhooError { + public var userMessage: String { + return message + } + + // TODO: Temporary solution before all errors implement KarhooError + public var code: String { return "needs to be implemented" } + public var message: String { return "needs to be implemented" } + + public func equals(_ error: KarhooError?) -> Bool { + return self.code == error?.code && + self.message == error?.message && + self.userMessage == error?.userMessage + } + + public var type: KarhooErrorType { + return KarhooErrorType(error: self) + } +} diff --git a/KarhooSDK/Network/Common/Error/KarhooErrorType.swift b/KarhooSDK/Network/Common/Error/KarhooErrorType.swift new file mode 100644 index 00000000..bb5b22b1 --- /dev/null +++ b/KarhooSDK/Network/Common/Error/KarhooErrorType.swift @@ -0,0 +1,133 @@ +// +// KarhooErrorType.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +//swiftlint:disable cyclomatic_complexity function_body_length + +import Foundation + +public enum KarhooErrorType { + + // KSDKxxx Internal SDK + case unknownError + case missingUserPermission + case userAlreadyLoggedIn + + // K0xxx General Errors + case generalRequestError + case invalidRequestPayload + case couldNotReadAuthorisationToken + case couldNotParseAuthorisationToken + case authenticationIsRequiredForThisPath + case missingRequiredRoleForThisRequest + case rateLimitExceeded + case circuitBreakerHasTriggeredForThisRoute + // K1xxx Users + case couldNotRegister + case couldNotGetUserDetails + case couldNotAddToOrganisation + case organisationDoesntExist + case roleDoesntExist + case missingPermissionsToViewProfile + case passwordInWrongFormat + + // K2xxx Location + case couldNotGetAddress + case couldNotAutocompleteAddress + + // K3xxx Quotes + case couldNotGetEstimates + case couldNotFindSpecifiedQuote + case originAndDestinationAreTheSame + + // K4xxx Booking + case couldNotBookTrip + case couldNotBookTripInvalidRequest + case couldNotBookTripCouldNotFindQuote + case couldNotBookTripAttemptToBookExpiredQuote + case couldNotBookTripPermissionsDenied + case couldNotBookTripPaymentPreAuthFailed + case couldNotCancelTrip + case couldNotCancelTripCouldNotFindSpecifiedTrip + case couldNotCancelTripPermissionsDenied + case couldNotCancelTripAlreadyCanceled + case couldNotGetTrip + case couldNotGetTripCouldNotFindSpecifiedTrip + case couldNotGetTripPermissionsDenied + case couldNotBookTripCouldNotBookTripAsAgent + case couldNotBookTripCouldNotBookTripAsTraveller + case couldNotBookTripQuoteNoLongerAvailable + + // K5xxx Availability + case noAvailabilityInRequestedArea + case noAvailableCategoriesInRequestedArea +} + +extension KarhooErrorType { + init(error: KarhooError) { + switch error.code { + + // KSDKxxx Internal SDK + case "KSDK01": self = .unknownError + case "KSDK02": self = .missingUserPermission + case "KSDK03": self = .userAlreadyLoggedIn + + // K0xxx General Errors + case "K0001": self = .generalRequestError + case "K0002": self = .invalidRequestPayload + case "K0003": self = .couldNotReadAuthorisationToken + case "K0004": self = .couldNotParseAuthorisationToken + case "K0005": self = .missingRequiredRoleForThisRequest + case "K0006": self = .rateLimitExceeded + case "K0007": self = .circuitBreakerHasTriggeredForThisRoute + + // K1xxx Users + case "K1001", "K1003", "K1004": self = .couldNotRegister + case "K1005", "K1006": self = .couldNotGetUserDetails + case "K1007": self = .couldNotAddToOrganisation + case "K1008": self = .organisationDoesntExist + case "K1009": self = .roleDoesntExist + case "K1010": self = .missingPermissionsToViewProfile + case "K1011": self = .passwordInWrongFormat + + // K2xxx Location + case "K2001": self = .couldNotGetAddress + case "K2002": self = .couldNotAutocompleteAddress + + // K3xxx Quotes + case "K3001": self = .couldNotGetEstimates + case "K3002", "K5002": self = .noAvailabilityInRequestedArea + case "K3003": self = .couldNotFindSpecifiedQuote + + case "Q0001": self = .originAndDestinationAreTheSame + + // K4xxx Booking + case "K4001": self = .couldNotBookTrip + case "K4002": self = .couldNotBookTripInvalidRequest + case "K4003": self = .couldNotBookTripCouldNotFindQuote + case "K4004": self = .couldNotBookTripAttemptToBookExpiredQuote + case "K4005": self = .couldNotBookTripPermissionsDenied + case "K4006": self = .couldNotBookTripPaymentPreAuthFailed + case "K4007": self = .couldNotCancelTrip + case "K4008": self = .couldNotCancelTripCouldNotFindSpecifiedTrip + case "K4009": self = .couldNotCancelTripPermissionsDenied + case "K4010": self = .couldNotCancelTripAlreadyCanceled + case "K4011": self = .couldNotGetTrip + case "K4012": self = .couldNotGetTripCouldNotFindSpecifiedTrip + case "K4013": self = .couldNotGetTripPermissionsDenied + case "K4014": self = .couldNotBookTripCouldNotBookTripAsAgent + case "K4015": self = .couldNotBookTripCouldNotBookTripAsTraveller + case "K4018": self = .couldNotBookTripQuoteNoLongerAvailable + + // K5xxx Availability + case "K5001": self = .couldNotGetEstimates + case "K5003": self = .noAvailableCategoriesInRequestedArea + + default: self = .unknownError + } + } +} diff --git a/KarhooSDK/Network/Common/Error/KarhooSDKError.swift b/KarhooSDK/Network/Common/Error/KarhooSDKError.swift new file mode 100644 index 00000000..43531a6e --- /dev/null +++ b/KarhooSDK/Network/Common/Error/KarhooSDKError.swift @@ -0,0 +1,49 @@ +// +// KarhooSDKError.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct KarhooSDKError: KarhooError, KarhooCodableModel { + let code: String + let message: String + let userMessage: String + var statusCode: Int = 0 + + init(code: String, + message: String, + userMessage: String? = nil) { + self.code = code + self.message = message + self.userMessage = userMessage ?? message + } + + enum CodingKeys: String, CodingKey { + case code + case message + case userMessage + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.code = (try? container.decode(String.self, forKey: .code)) ?? "" + let message = (try? container.decode(String.self, forKey: .message)) ?? "" + let userMessage = (try? container.decode(String.self, forKey: .userMessage)) + + self.message = message + self.userMessage = userMessage ?? message + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(code, forKey: .code) + try container.encode(message, forKey: .message) + try container.encode(userMessage, forKey: .userMessage) + } +} diff --git a/KarhooSDK/Network/Common/Error/SDKErrorFactory.swift b/KarhooSDK/Network/Common/Error/SDKErrorFactory.swift new file mode 100644 index 00000000..9e8a7e06 --- /dev/null +++ b/KarhooSDK/Network/Common/Error/SDKErrorFactory.swift @@ -0,0 +1,28 @@ +// +// SDKErrorFactory.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +class SDKErrorFactory { + + static func unexpectedError() -> KarhooError { + return KarhooSDKError(code: "KSDK01", message: "Something went wrong but we don't know what it was") + } + + static func getLoginPermissionError() -> KarhooError { + return KarhooSDKError(code: "KSDK02", message: "Missing user permissions") + } + + static func userAlreadyLoggedIn() -> KarhooError { + return KarhooSDKError(code: "KSDK03", message: "User already logged in") + } + + static func noConfigAvailableForView() -> KarhooError { + return KarhooSDKError(code: "KSDK05", message: "There is no view config avialable for this view") + } +} diff --git a/KarhooSDK/Network/Common/KarhooCodableModel.swift b/KarhooSDK/Network/Common/KarhooCodableModel.swift new file mode 100644 index 00000000..caa2cdc8 --- /dev/null +++ b/KarhooSDK/Network/Common/KarhooCodableModel.swift @@ -0,0 +1,33 @@ +// +// KarhooCodableModel.Swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol KarhooCodableModel: Codable { + func encode() -> Data? + + func equals(_ item: KarhooCodableModel) -> Bool +} + +extension KarhooCodableModel { + + public func encode() -> Data? { + do { + return try JSONEncoder().encode(self) + } catch let error { + print("----Error Encoding model: \(self) | Reason: \(error.localizedDescription)") + return nil + } + } + + public func equals(_ item: KarhooCodableModel) -> Bool { + return self.encode() == item.encode() + } +} + +extension Array: KarhooCodableModel where Element: KarhooCodableModel {} diff --git a/KarhooSDK/Network/Common/KarhooVoid.swift b/KarhooSDK/Network/Common/KarhooVoid.swift new file mode 100644 index 00000000..4e7b3042 --- /dev/null +++ b/KarhooSDK/Network/Common/KarhooVoid.swift @@ -0,0 +1,19 @@ +// +// KarhooVoid.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +/** + * A way to represent 'Void' in KarhooCall/KarhooPollableCall result values + * as Void is not codable. + */ +public struct KarhooVoid: KarhooCodableModel { + + public init() {} + +} diff --git a/KarhooSDK/Network/Common/RequestSender/KarhooRequestSender.swift b/KarhooSDK/Network/Common/RequestSender/KarhooRequestSender.swift new file mode 100644 index 00000000..ef79ffa8 --- /dev/null +++ b/KarhooSDK/Network/Common/RequestSender/KarhooRequestSender.swift @@ -0,0 +1,63 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooRequestSender: RequestSender { + + private let httpClient: HttpClient + private var networkRequest: NetworkRequest? + + init(httpClient: HttpClient) { + self.httpClient = httpClient + } + + func request(payload: KarhooCodableModel?, + endpoint: APIEndpoint, + callback: @escaping CallbackClosure) { + networkRequest = httpClient.sendRequest(endpoint: endpoint, + data: payload?.encode(), + urlComponents: nil, + completion: callback) + } + + func requestAndDecode(payload: KarhooCodableModel?, + endpoint: APIEndpoint, + callback: @escaping CallbackClosure) { + request(payload: payload, + endpoint: endpoint) { response in + if let value = response.successValue()?.decodeData(ofType: T.self) { + callback(.success(result: value)) + } else { + if let error = response.errorValue() { + callback(.failure(error: error)) + } else { + callback(.failure(error: SDKErrorFactory.unexpectedError())) + } + } + } + } + + func encodedRequest(endpoint: APIEndpoint, body: URLComponents?, callback: @escaping CallbackClosure) { + networkRequest = httpClient.sendRequest(endpoint: endpoint, + data: nil, + urlComponents: body, + completion: { response in + if let value = response.successValue()?.decodeData(ofType: T.self) { + callback(.success(result: value)) + } else { + if let error = response.errorValue() { + callback(.failure(error: error)) + } else { + callback(.failure(error: SDKErrorFactory.unexpectedError())) + } + } + }) + } + + func cancelNetworkRequest() { + networkRequest?.cancel() + } +} diff --git a/KarhooSDK/Network/Common/RequestSender/RequestSender.swift b/KarhooSDK/Network/Common/RequestSender/RequestSender.swift new file mode 100644 index 00000000..c6516cc4 --- /dev/null +++ b/KarhooSDK/Network/Common/RequestSender/RequestSender.swift @@ -0,0 +1,28 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol RequestSender { + func request(payload: KarhooCodableModel?, + endpoint: APIEndpoint, + callback: @escaping CallbackClosure) + + func requestAndDecode(payload: KarhooCodableModel?, + endpoint: APIEndpoint, + callback: @escaping CallbackClosure) + + func encodedRequest(endpoint: APIEndpoint, + body: URLComponents?, + callback: @escaping CallbackClosure) + + func cancelNetworkRequest() +} + +extension RequestSender { + func encodedRequest(endpoint: APIEndpoint, + body: URLComponents?, + callback: @escaping CallbackClosure) {} +} diff --git a/KarhooSDK/Network/Header/HeaderConstants.swift b/KarhooSDK/Network/Header/HeaderConstants.swift new file mode 100644 index 00000000..3e8c1eb7 --- /dev/null +++ b/KarhooSDK/Network/Header/HeaderConstants.swift @@ -0,0 +1,19 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct HeaderConstants { + static let authorization = "authorization" + static let identifier = "identifier" + static let referer = "referer" + static let bearer = "Bearer" + static let contentType = "Content-Type" + static let accept = "accept" + static let typeJSON = "application/json" + static let typeEncoded = "application/x-www-form-urlencoded" + static let correlationId = "correlation_id" + static let correlationIdPrefix = "IOS-" +} diff --git a/KarhooSDK/Network/Header/HeaderProvider.swift b/KarhooSDK/Network/Header/HeaderProvider.swift new file mode 100644 index 00000000..d83b031d --- /dev/null +++ b/KarhooSDK/Network/Header/HeaderProvider.swift @@ -0,0 +1,20 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol HeaderProvider { + + func combine(headers: inout HttpHeaders, with otherHeaders: HttpHeaders) -> HttpHeaders + + func headersWithAuthorization(headers: inout HttpHeaders, endpoint: APIEndpoint) -> HttpHeaders + + func headersWithCorrelationId(headers: inout HttpHeaders, endpoint: APIEndpoint) -> HttpHeaders + + func headersWithJSONContentType(headers: inout HttpHeaders) -> HttpHeaders + + func headersWithFormEncodedType(headers: inout HttpHeaders) -> HttpHeaders + +} diff --git a/KarhooSDK/Network/Header/KarhooHeaderProvider.swift b/KarhooSDK/Network/Header/KarhooHeaderProvider.swift new file mode 100644 index 00000000..4da0cc1e --- /dev/null +++ b/KarhooSDK/Network/Header/KarhooHeaderProvider.swift @@ -0,0 +1,77 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +class KarhooHeaderProvider: HeaderProvider { + + fileprivate let accessTokenProvider: AccessTokenProvider + fileprivate var quoteUUID: String? + + init(authTokenProvider: AccessTokenProvider = DefaultAccessTokenProvider.shared) { + self.accessTokenProvider = authTokenProvider + } + + func combine(headers: inout HttpHeaders, with otherHeaders: HttpHeaders) -> HttpHeaders { + headers.merge(otherHeaders) { (_, new) in new } + return headers + } + + func headersWithJSONContentType(headers: inout HttpHeaders) -> HttpHeaders { + headers[HeaderConstants.contentType] = HeaderConstants.typeJSON + return headers + } + + func headersWithFormEncodedType(headers: inout HttpHeaders) -> HttpHeaders { + headers[HeaderConstants.contentType] = HeaderConstants.typeEncoded + return headers + } + + func headersWithAuthorization(headers: inout HttpHeaders, endpoint: APIEndpoint) -> HttpHeaders { + switch Karhoo.configuration.authenticationMethod() { + case .guest(let settings): + headers[HeaderConstants.identifier] = settings.identifier + headers[HeaderConstants.referer] = settings.referer + return headers + default: break + } + + switch endpoint { + case .login, + .karhooUserTokenRefresh, + .register, + .passwordReset: + return headers + default: + if let token = accessTokenProvider.accessToken?.token { + headers[HeaderConstants.authorization] = "\(HeaderConstants.bearer) \(token)" + } + return headers + } + } + + func headersWithCorrelationId(headers: inout HttpHeaders, endpoint: APIEndpoint) -> HttpHeaders { + var uuid: String + switch endpoint { + case .quoteListId: + uuid = createQuoteUUID() + case .bookTrip: + uuid = bookingUUID() + default: + uuid = UUID().uuidString + } + headers[HeaderConstants.correlationId] = "\(HeaderConstants.correlationIdPrefix)\(uuid)" + return headers + } + + private func createQuoteUUID() -> String { + quoteUUID = UUID().uuidString + return bookingUUID() + } + + private func bookingUUID() -> String { + return quoteUUID ?? UUID().uuidString + } +} diff --git a/KarhooSDK/Network/HttpClient/HttpClient.swift b/KarhooSDK/Network/HttpClient/HttpClient.swift new file mode 100644 index 00000000..259b008c --- /dev/null +++ b/KarhooSDK/Network/HttpClient/HttpClient.swift @@ -0,0 +1,12 @@ +import Foundation + +public protocol NetworkRequest { + func cancel() +} + +protocol HttpClient { + func sendRequest(endpoint: APIEndpoint, + data: Data?, + urlComponents: URLComponents?, + completion: @escaping CallbackClosure) -> NetworkRequest? +} diff --git a/KarhooSDK/Network/HttpClient/JsonHttpClient/Json.swift b/KarhooSDK/Network/HttpClient/JsonHttpClient/Json.swift new file mode 100644 index 00000000..09adbe10 --- /dev/null +++ b/KarhooSDK/Network/HttpClient/JsonHttpClient/Json.swift @@ -0,0 +1,11 @@ +import Foundation + +public typealias Json = [String: Any] + +public func == (lhs: Json, rhs: Json ) -> Bool { + return NSDictionary(dictionary: lhs).isEqual(to: rhs) +} + +public enum JsonError: KarhooError { + case invalidJsonArguments +} diff --git a/KarhooSDK/Network/HttpClient/JsonHttpClient/JsonHttpClient.swift b/KarhooSDK/Network/HttpClient/JsonHttpClient/JsonHttpClient.swift new file mode 100644 index 00000000..46cc77fe --- /dev/null +++ b/KarhooSDK/Network/HttpClient/JsonHttpClient/JsonHttpClient.swift @@ -0,0 +1,164 @@ +import Foundation + +/** + * This class does not refresh JWT token automatically. + * Use TokenRefreshingJsonHttpClient outside of login/registration + */ +final class JsonHttpClient: HttpClient { + + private var urlSessionSender: URLSessionSender + private let requestBuilder = JsonHttpRequestBuilder() + private let headerProvider: HeaderProvider + private let analyticsService: AnalyticsService + + static let shared = JsonHttpClient() + + init(urlSessionSender: URLSessionSender = KarhooURLSessionSender.shared, + headerProvider: HeaderProvider = KarhooHeaderProvider(), + analyticsService: AnalyticsService = KarhooAnalyticsService()) { + self.urlSessionSender = urlSessionSender + self.headerProvider = headerProvider + self.analyticsService = analyticsService + } + + @discardableResult + func sendRequest(endpoint: APIEndpoint, + data: Data? = nil, + completion: @escaping CallbackClosure) -> NetworkRequest? { + + let headers: HttpHeaders = addRelativeHeaders(endpoint: endpoint) + + do { + let url = absoluteUrl(endpoint: endpoint) + var request: URLRequest + + request = try requestBuilder.request(method: endpoint.method, url: url, headers: headers, data: data) + + return urlSessionSender.send(request: request) { data, response, error in + self.handle(response: response, data: data, error: error, completion: completion) + } + } catch { + return nil + } + } + + @discardableResult + func sendRequest(endpoint: APIEndpoint, data: Data?, urlComponents: URLComponents? = nil, completion: @escaping CallbackClosure) -> NetworkRequest? { + let headers: HttpHeaders = addRelativeHeaders(endpoint: endpoint) + + do { + let url = absoluteUrl(endpoint: endpoint) + var request: URLRequest + + request = try requestBuilder.request(method: endpoint.method, + url: url, headers: headers, + data: data, + urlComponents: urlComponents) + + return urlSessionSender.send(request: request) { data, response, error in + self.handle(response: response, data: data, error: error, completion: completion) + } + } catch { + return nil + } + } + + private func handle(response: URLResponse?, data: Data?, error: Error?, + completion: CallbackClosure) { + let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 + let httpResponse = HttpResponse(code: statusCode, data: data ?? Data()) + + guard error == nil && + isValid(statusCode: statusCode) else { + handleError(requestUrl: response?.url?.absoluteString ?? "", + statusCode: statusCode, + response: httpResponse, + error: error, + completion: completion) + return + } + + completion(Result.success(result: httpResponse)) + } + + func handleError(requestUrl: String, + statusCode: Int, + response: HttpResponse, + error: Error?, + completion: CallbackClosure) { + + analyticsService.send(eventName: .requestFails, payload: error != nil ? [AnalyticsConstants.Keys.requestError.rawValue: error.debugDescription, + AnalyticsConstants.Keys.requestUrl.rawValue: requestUrl] : [AnalyticsConstants.Keys.requestUrl.rawValue: requestUrl]) + if let error = response.decodeError() { + if error.code.isEmpty { + completion(Result.failure(error: SDKErrorFactory.unexpectedError())) + } else { + completion(Result.failure(error: error)) + } + } else { + let httpError = HTTPError(statusCode: statusCode, error: error as NSError?) + completion(Result.failure(error: httpError)) + } + } + + private func isValid(statusCode: Int) -> Bool { + return statusCode <= HttpStatus.maxValueSuccessCode + } + + private func addRelativeHeaders(endpoint: APIEndpoint) -> HttpHeaders { + var headers = HttpHeaders() + + if endpoint.method == .post || endpoint.method == .put { + headers = headerProvider.headersWithJSONContentType(headers: &headers) + } + + switch endpoint { + case .authRevoke, + .authTokenExchange: + headers = headerProvider.headersWithFormEncodedType(headers: &headers) + default: + headers = headerProvider.headersWithAuthorization(headers: &headers, endpoint: endpoint) + headers = headerProvider.headersWithCorrelationId(headers: &headers, endpoint: endpoint) + } + return headers + } +} + +private extension JsonHttpClient { + + func absoluteUrl(endpoint: APIEndpoint) -> URL { + let environmentDetails = KarhooEnvironmentDetails(environment: Karhoo.configuration.environment()) + + switch Karhoo.configuration.authenticationMethod() { + case .guest: + guard let guestModeUrl = URL(string: environmentDetails.guestHost + endpoint.path) else { + fatalError(urlMalformedException) + } + return guestModeUrl + default: + break + } + + switch endpoint { + case .authRevoke, + .authTokenExchange, + .authUserInfo, + .authRefresh: + guard let authServiceUrl = URL(string: environmentDetails.authHost + endpoint.relativePath) else { + fatalError(urlMalformedException) + } + + return authServiceUrl + + default: + guard let url = URL(string: environmentDetails.host + endpoint.path) else { + fatalError(urlMalformedException) + } + return url + } + } + + var urlMalformedException: String { + return "KarhooSDK: Could not resolve host URL" + } +} diff --git a/KarhooSDK/Network/HttpClient/JsonHttpClient/JsonHttpRequestBuilder.swift b/KarhooSDK/Network/HttpClient/JsonHttpClient/JsonHttpRequestBuilder.swift new file mode 100644 index 00000000..0dad2521 --- /dev/null +++ b/KarhooSDK/Network/HttpClient/JsonHttpClient/JsonHttpRequestBuilder.swift @@ -0,0 +1,21 @@ +import Foundation + +/** + * Makes a valid HTTP request with JSON out of provided parameters + */ +final class JsonHttpRequestBuilder { + + func request(method: HttpMethod, url: URL, headers: HttpHeaders?, data: Data?, urlComponents: URLComponents? = nil) throws -> URLRequest { + var request = URLRequest(url: url) + request.httpMethod = method.rawValue + headers?.forEach { request.addValue($1, forHTTPHeaderField: $0) } + + if urlComponents == nil { + request.httpBody = data + } else { + request.httpBody = urlComponents?.query?.data(using: .utf8) + } + + return request + } +} diff --git a/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/Interactor/KarhooRefreshTokenInteractor.swift b/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/Interactor/KarhooRefreshTokenInteractor.swift new file mode 100644 index 00000000..8d6a99ae --- /dev/null +++ b/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/Interactor/KarhooRefreshTokenInteractor.swift @@ -0,0 +1,113 @@ +// +// RefreshTokenProvider.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooRefreshTokenInteractor: RefreshTokenInteractor { + + private let dataStore: UserDataStore + private let refreshTokenRequest: RequestSender + private var callback: ((Result) -> Void)? + + init(dataStore: UserDataStore = DefaultUserDataStore(), + refreshTokenRequest: RequestSender = KarhooRequestSender(httpClient: JsonHttpClient.shared)) { + self.dataStore = dataStore + self.refreshTokenRequest = refreshTokenRequest + } + + func tokenNeedsRefreshing() -> Bool { + guard let credentials = dataStore.getCurrentCredentials() else { + return false + } + return tokenNeedsRefreshing(credentials: credentials) + } + + func refreshToken(completion: @escaping (Result) -> Void) { + self.callback = completion + + guard tokenNeedsRefreshing() else { + completion(Result.success(result: false)) + return + } + + guard let refreshToken = dataStore.getCurrentCredentials()?.refreshToken else { + completion(Result.failure(error: RefreshTokenError.noRefreshToken)) + return + } + + switch Karhoo.configuration.authenticationMethod() { + case .guest: + completion(Result.success(result: false)) + case .karhooUser: + let refreshPayload = RefreshTokenRequestPayload(refreshToken: refreshToken) + refreshTokenRequest.requestAndDecode(payload: refreshPayload, + endpoint: .karhooUserTokenRefresh, + callback: { [weak self] (result: Result) in + self?.handleRefreshRequest(result: result) + }) + case .tokenExchange: + refreshTokenRequest.encodedRequest(endpoint: .authRefresh, + body: authRefreshUrlComponents(), + callback: { [weak self] (result: Result) in + self?.handleRefreshRequest(result: result) + }) + } + } + + private func authRefreshUrlComponents() -> URLComponents { + var urlComponents = URLComponents() + let tokenExchangeSettings = Karhoo.configuration.authenticationMethod().tokenExchangeSettings + + urlComponents.queryItems = [URLQueryItem(name: AuthHeaderKeys.clientId.rawValue, value: tokenExchangeSettings?.clientId ?? ""), + URLQueryItem(name: AuthHeaderKeys.refreshToken.rawValue, value: dataStore.getCurrentCredentials()?.refreshToken ?? ""), + URLQueryItem(name: AuthHeaderKeys.grantType.rawValue, value: AuthHeaderKeys.refreshToken.keyValue)] + + return urlComponents + } + + private func handleRefreshRequest(result: Result) { + if let token = result.successValue() { + guard self.tokenNeedsRefreshing() == true else { + callback?(Result.success(result: false)) + return + } + + self.saveToDataStore(token: token) + + } else if let error = result.errorValue() { + callback?(Result.failure(error: error)) + } + } + + private func saveToDataStore(token: AuthToken) { + let currentCredentials = dataStore.getCurrentCredentials() + let user = dataStore.getCurrentUser() + let currentRefreshToken = currentCredentials?.refreshToken ?? "" + let newCredentials = token.toCredentials(withRefreshToken: currentRefreshToken) + + if let user = user { + dataStore.setCurrentUser(user: user, credentials: newCredentials) + callback?(Result.success(result: true)) + } else { + callback?(Result.failure(error: RefreshTokenError.userAlreadyLoggedOut)) + } + } + + private func tokenNeedsRefreshing(credentials: Credentials) -> Bool { + guard let expiryDate = credentials.expiryDate else { + return true + } + + let timeToExpiration = expiryDate.timeIntervalSince1970 - Date().timeIntervalSince1970 + return timeToExpiration < Constants.MaxTimeIntervalToRefreshToken + } + + private struct Constants { + static let MaxTimeIntervalToRefreshToken = TimeInterval(30) // 30 sec + } +} diff --git a/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/Interactor/RefreshTokenInteractor.swift b/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/Interactor/RefreshTokenInteractor.swift new file mode 100644 index 00000000..a9158952 --- /dev/null +++ b/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/Interactor/RefreshTokenInteractor.swift @@ -0,0 +1,15 @@ +// +// RefreshTokenInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol RefreshTokenInteractor { + func tokenNeedsRefreshing() -> Bool + + func refreshToken(completion: @escaping (Result) -> Void) +} diff --git a/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/RefreshTokenError.swift b/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/RefreshTokenError.swift new file mode 100644 index 00000000..29fb2192 --- /dev/null +++ b/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/RefreshTokenError.swift @@ -0,0 +1,14 @@ +// +// RefreshTokenError.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +enum RefreshTokenError: KarhooError { + case noRefreshToken + case userAlreadyLoggedOut +} diff --git a/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/TokenRefreshingHttpClient.swift b/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/TokenRefreshingHttpClient.swift new file mode 100644 index 00000000..7e52e2c8 --- /dev/null +++ b/KarhooSDK/Network/HttpClient/TokenRefreshingHttpClient/TokenRefreshingHttpClient.swift @@ -0,0 +1,106 @@ +// +// TokenRefreshingHttpClient.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class TokenRefreshingHttpClient: HttpClient { + + private let httpClient: HttpClient + private let refreshTokenProvider: RefreshTokenInteractor + private let dataStore: UserDataStore + + static let shared = TokenRefreshingHttpClient() + + init(httpClient: HttpClient = JsonHttpClient.shared, + refreshTokenProvider: RefreshTokenInteractor = KarhooRefreshTokenInteractor(), + dataStore: UserDataStore = DefaultUserDataStore()) { + self.httpClient = httpClient + self.refreshTokenProvider = refreshTokenProvider + self.dataStore = dataStore + } + + @discardableResult + func sendRequest(endpoint: APIEndpoint, + data: Data? = nil, + urlComponents: URLComponents? = nil, + completion: @escaping CallbackClosure) -> NetworkRequest? { + + if refreshTokenProvider.tokenNeedsRefreshing() == true { + return refreshTokenChainWithRequest(endpoint: endpoint, + data: data, + completion: completion) + } else { + let completion: CallbackClosure = { [weak self] result in + + if result.errorValue()?.isUnauthorizedError() == true { + self?.refreshTokenChainWithRequest(endpoint: endpoint, + data: data, + completion: completion) + } else { + completion(result) + } + } + + return httpClient.sendRequest(endpoint: endpoint, + data: data, + urlComponents: nil, + completion: completion) + } + } + + private func logUserOut() { + dataStore.removeCurrentUserAndCredentials() + } + + @discardableResult + private func refreshTokenChainWithRequest(endpoint: APIEndpoint, + data: Data?, + completion: @escaping CallbackClosure) -> NetworkRequest? { + let requestWrapper = AsyncNetworkRequestWrapper() + + refreshTokenProvider.refreshToken { [weak self] result in + + guard requestWrapper.cancelled == false else { + return + } + + if let error = result.errorValue(), error.isConnectionError() == true { + completion(Result.failure(error: error)) + return + } + + if let error = result.errorValue() { + self?.logUserOut() + completion(Result.failure(error: error)) + return + } + + requestWrapper.realRequest = self?.httpClient.sendRequest(endpoint: endpoint, + data: data, + urlComponents: nil, + completion: { result in + completion(result) + }) + } + return requestWrapper + } +} + +class AsyncNetworkRequestWrapper: NetworkRequest { + private(set) var cancelled = false + fileprivate(set) var realRequest: NetworkRequest? + + var hasRequest: Bool { + return realRequest != nil + } + + func cancel() { + cancelled = true + realRequest?.cancel() + } +} diff --git a/KarhooSDK/Network/HttpConstants.swift b/KarhooSDK/Network/HttpConstants.swift new file mode 100644 index 00000000..cd8e2664 --- /dev/null +++ b/KarhooSDK/Network/HttpConstants.swift @@ -0,0 +1,48 @@ +import Foundation + +public typealias HttpHeaders = [String: String] +typealias HttpStatusCode = Int + +struct HttpResponse { + let code: HttpStatusCode + let data: Data + + init(code: HttpStatusCode, data: Data) { + self.code = code + self.data = data + } + + func decodeData(ofType type: T.Type) -> T? { + return decode(ofType: T.self) + } + + func decodeError() -> KarhooSDKError? { + return decode(ofType: KarhooSDKError.self) + } + + func decode(ofType type: T.Type) -> T? { + var decoded: T? + do { + decoded = try JSONDecoder().decode(type, from: data) + } catch let error { + print("Error decoding JSON: \(error) MODEL: \(type)") + } + return decoded + } +} + +enum HttpStatus: Int { + static let maxValueSuccessCode = 299 + + case success = 201 + case badRequest = 400 + case serverError = 500 +} + +enum HttpMethod: String { + case get = "GET" + case post = "POST" + case delete = "DELETE" + case put = "PUT" + case patch = "PATCH" +} diff --git a/KarhooSDK/Network/KarhooEnvironmentDetails.swift b/KarhooSDK/Network/KarhooEnvironmentDetails.swift new file mode 100644 index 00000000..bc73dc59 --- /dev/null +++ b/KarhooSDK/Network/KarhooEnvironmentDetails.swift @@ -0,0 +1,35 @@ +import Foundation + +public struct KarhooEnvironmentDetails { + public let host: String + public let guestHost: String + public let authHost: String + + public init(host: String, + authHost: String, + guestHost: String) { + self.host = host + self.authHost = authHost + self.guestHost = guestHost + } +} + +internal extension KarhooEnvironmentDetails { + + init(environment: KarhooEnvironment) { + switch environment { + case .custom(let environment): + self.host = environment.host + self.authHost = environment.authHost + self.guestHost = environment.guestHost + case .sandbox: + self.host = "https://rest.sandbox.karhoo.com" + self.authHost = "https://sso.sandbox.karhoo.com" + self.guestHost = "https://public-api.sandbox.karhoo.com" + case .production: + self.host = "https://rest.karhoo.com" + self.authHost = "https://sso.karhoo.com" + self.guestHost = "https://public-api.karhoo.com" + } + } +} diff --git a/KarhooSDK/Network/URLSessionSender/KarhooURLSessionSender.swift b/KarhooSDK/Network/URLSessionSender/KarhooURLSessionSender.swift new file mode 100644 index 00000000..d539d527 --- /dev/null +++ b/KarhooSDK/Network/URLSessionSender/KarhooURLSessionSender.swift @@ -0,0 +1,28 @@ +import Foundation + +public class KarhooURLSessionSender: URLSessionSender { + private var session: URLSession + + public static let shared = KarhooURLSessionSender() + + public init(session: URLSession = URLSession(configuration: URLSessionConfiguration.default)) { + self.session = session + } + + public var configuration: URLSessionConfiguration { + return session.configuration + } + + public func send(request: URLRequest, + completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> NetworkRequest { + let task = session.dataTask(with: request) { (data, response, error) in + DispatchQueue.main.async { + completion(data, response, error) + } + } + task.resume() + return task + } +} + +extension URLSessionDataTask: NetworkRequest { } diff --git a/KarhooSDK/Network/URLSessionSender/URLSessionSender.swift b/KarhooSDK/Network/URLSessionSender/URLSessionSender.swift new file mode 100644 index 00000000..b3760baf --- /dev/null +++ b/KarhooSDK/Network/URLSessionSender/URLSessionSender.swift @@ -0,0 +1,8 @@ +import Foundation + +/** + * Interface allowing us to quickly swap networking library if we need to + */ +public protocol URLSessionSender { + func send(request: URLRequest, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> NetworkRequest +} diff --git a/KarhooSDK/Service/Address/AddressService.swift b/KarhooSDK/Service/Address/AddressService.swift new file mode 100644 index 00000000..a051c09c --- /dev/null +++ b/KarhooSDK/Service/Address/AddressService.swift @@ -0,0 +1,18 @@ +// +// AddressService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol AddressService { + + func placeSearch(placeSearch: PlaceSearch) -> Call + + func locationInfo(locationInfoSearch: LocationInfoSearch) -> Call + + func reverseGeocode(position: Position) -> Call +} diff --git a/KarhooSDK/Service/Address/KarhooAddressService.swift b/KarhooSDK/Service/Address/KarhooAddressService.swift new file mode 100644 index 00000000..d08295f3 --- /dev/null +++ b/KarhooSDK/Service/Address/KarhooAddressService.swift @@ -0,0 +1,39 @@ +// +// KarhooAddressService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooAddressService: AddressService { + + private let placeSearchInteractor: PlaceSearchInteractor + private let locationInfoInteractor: LocationInfoInteractor + private let reverseGeocodeInteractor: ReverseGeocodeInteractor + + init(placeSearchInteractor: PlaceSearchInteractor = KarhooPlaceSearchInteractor(), + locationInfoInteractor: LocationInfoInteractor = KarhooLocationInfoInteractor(), + reverseGeocodeInteractor: ReverseGeocodeInteractor = KarhooReverseGeocodeInteractor()) { + self.placeSearchInteractor = placeSearchInteractor + self.locationInfoInteractor = locationInfoInteractor + self.reverseGeocodeInteractor = reverseGeocodeInteractor + } + + func placeSearch(placeSearch: PlaceSearch) -> Call { + placeSearchInteractor.set(placeSearch: placeSearch) + return Call(executable: placeSearchInteractor) + } + + func locationInfo(locationInfoSearch: LocationInfoSearch) -> Call { + locationInfoInteractor.set(locationInfoSearch: locationInfoSearch) + return Call(executable: locationInfoInteractor) + } + + func reverseGeocode(position: Position) -> Call { + reverseGeocodeInteractor.set(position: position) + return Call(executable: reverseGeocodeInteractor) + } +} diff --git a/KarhooSDK/Service/Address/LocationInfoInteractor/KarhooLocationInfoInteractor.swift b/KarhooSDK/Service/Address/LocationInfoInteractor/KarhooLocationInfoInteractor.swift new file mode 100644 index 00000000..c051df59 --- /dev/null +++ b/KarhooSDK/Service/Address/LocationInfoInteractor/KarhooLocationInfoInteractor.swift @@ -0,0 +1,36 @@ +// +// KarhooLocationInfoInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooLocationInfoInteractor: LocationInfoInteractor { + + private let requestSender: RequestSender + private var locationInfoSearch: LocationInfoSearch? + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.requestSender = requestSender + } + + func set(locationInfoSearch: LocationInfoSearch) { + self.locationInfoSearch = locationInfoSearch + } + + func execute(callback: @escaping CallbackClosure) { + guard let locationInfoSearch = self.locationInfoSearch else { + return + } + requestSender.requestAndDecode(payload: locationInfoSearch, + endpoint: .locationInfo, + callback: callback) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Address/LocationInfoInteractor/LocationInfoInteractor.swift b/KarhooSDK/Service/Address/LocationInfoInteractor/LocationInfoInteractor.swift new file mode 100644 index 00000000..140dc217 --- /dev/null +++ b/KarhooSDK/Service/Address/LocationInfoInteractor/LocationInfoInteractor.swift @@ -0,0 +1,10 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol LocationInfoInteractor: KarhooExecutable { + func set(locationInfoSearch: LocationInfoSearch) +} diff --git a/KarhooSDK/Service/Address/PlaceSearchInteractor/KarhooPlaceSearchInteractor.swift b/KarhooSDK/Service/Address/PlaceSearchInteractor/KarhooPlaceSearchInteractor.swift new file mode 100644 index 00000000..8d893d7e --- /dev/null +++ b/KarhooSDK/Service/Address/PlaceSearchInteractor/KarhooPlaceSearchInteractor.swift @@ -0,0 +1,37 @@ +// +// KarhooAddressSearchProvider.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooPlaceSearchInteractor: PlaceSearchInteractor { + + private let requestSender: RequestSender + private var placeSearch: PlaceSearch? + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.requestSender = requestSender + } + + func set(placeSearch: PlaceSearch) { + self.placeSearch = placeSearch + } + + func execute(callback: @escaping CallbackClosure) { + guard let placeSearch = self.placeSearch else { + return + } + + requestSender.requestAndDecode(payload: placeSearch, + endpoint: .placeSearch, + callback: callback) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Address/PlaceSearchInteractor/PlaceSearchInteractor.swift b/KarhooSDK/Service/Address/PlaceSearchInteractor/PlaceSearchInteractor.swift new file mode 100644 index 00000000..06817b9b --- /dev/null +++ b/KarhooSDK/Service/Address/PlaceSearchInteractor/PlaceSearchInteractor.swift @@ -0,0 +1,13 @@ +// +// PlaceSearchInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol PlaceSearchInteractor: KarhooExecutable { + func set(placeSearch: PlaceSearch) +} diff --git a/KarhooSDK/Service/Address/ReverseGeocodeInteractor/KarhooReverseGeocodeInteractor.swift b/KarhooSDK/Service/Address/ReverseGeocodeInteractor/KarhooReverseGeocodeInteractor.swift new file mode 100644 index 00000000..65e2e282 --- /dev/null +++ b/KarhooSDK/Service/Address/ReverseGeocodeInteractor/KarhooReverseGeocodeInteractor.swift @@ -0,0 +1,37 @@ +// +// KarhooReverseGeocodeInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooReverseGeocodeInteractor: ReverseGeocodeInteractor { + + private let requestSender: RequestSender + private var position: Position? + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.requestSender = requestSender + } + + func set(position: Position) { + self.position = position + } + + func execute(callback: @escaping CallbackClosure) { + guard let position = self.position else { + return + } + + requestSender.requestAndDecode(payload: nil, + endpoint: .reverseGeocode(position: position), + callback: callback) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Address/ReverseGeocodeInteractor/ReverseGeocodeInteractor.swift b/KarhooSDK/Service/Address/ReverseGeocodeInteractor/ReverseGeocodeInteractor.swift new file mode 100644 index 00000000..498b9b0c --- /dev/null +++ b/KarhooSDK/Service/Address/ReverseGeocodeInteractor/ReverseGeocodeInteractor.swift @@ -0,0 +1,13 @@ +// +// ReverseGeocodeInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol ReverseGeocodeInteractor: KarhooExecutable { + func set(position: Position) +} diff --git a/KarhooSDK/Service/Analytics/AnalyticsConstants.swift b/KarhooSDK/Service/Analytics/AnalyticsConstants.swift new file mode 100644 index 00000000..bcfe7cb1 --- /dev/null +++ b/KarhooSDK/Service/Analytics/AnalyticsConstants.swift @@ -0,0 +1,116 @@ +// +// AnalyticsConstants.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public enum AnalyticsConstants { + + public enum EventNames: String { + + case userLoggedIn = "user_logged_in" + case userLoggedOut = "user_logged_out" + case appOpened = "app_opened" + case appBackgrounded = "app_backgrounded" + case registrationStarted = "user_registration_started" + case registrationCompleted = "user_registration_completed" + case userTermsReviewed = "user_terms_reviewed" + case appClosed = "app_closed" + case userProfileSavePressed = "user_profile_save_pressed" + case userProfileEditPressed = "user_profile_edit_pressed" + case userProfileDiscardPressed = "user_profile_discard_pressed" + case userProfileUpdateSuccess = "user_profile_update_success" + case userProfleUpdateFailed = "user_profile_update_failed" + + case currentLocationPressed = "current_location_pressed" + case pickupAddressDisplayed = "pickup_address_displayed" + case pickupAddressSelected = "pickup_address_selected" + case destinationAddressDisplayed = "destination_address_displayed" + case destinationAddressSelected = "destination_address_selected" + case addressesSuggested = "addresses_suggested" + case bookingRequested = "booking_requested" + case pickupReverseGeocodeRequested = "pickup_reverse_geocode_requested" + case pickupReverseGeocodeResponded = "pickup_reverse_geocode_responded" + case preebookTimeSet = "preebook_time_set" + case userCardRegistered = "user_card_registered" + case userCardRegistrationFailed = "user_card_registration_failed" + case tripCancellationAttempted = "trip_cancellation_attempted_by_user" + case tripCancellationInitiatedByUser = "trip_cancellation_initiated_by_user" + case categorySelected = "vehicle_type_selected" + case availabilityListExpanded = "fleet_list_extended" + case fleetListSorted = "fleets_sorted" + case fleetListShown = "fleet_list_shown" + case locationServicesRejected = "location_services_rejected" + case prebookOpened = "prebook_picker_opened" + case prebookTimeSet = "prebook_time_set" + case stateChangeDisplayed = "state_change_displayed" + case derEtaDisplayed = "eta_displayed" + case pobEtaDisplayed = "deta_displayed" + case baseFareDialogViewed = "base_fare_dialog_viewed" + case destinationFieldPressed = "destination_field_pressed" + case userCalledDriver = "user_called_driver" + case rideSummaryExited = "ride_summary_exited" + case returnRideRequested = "return_ride_requested" + case changePaymentDetailsPressed = "chenge_payment_details_pressed" + case tripRatingSubmitted = "trip_rating_submitted" + case quoteRating = "quote_selection_rating" + case prePobRating = "pre_pob_experience_rating" + case pobExperienceRating = "pob_experience_rating" + case appExperienceRating = "app_experience_rating" + case additional_feedback = "additional_feedback" + case additional_feedback_submitted = "additional_feedback_submitted" + case ssoTokenRevoked = "sso_token_revoked" + case ssoUserLogIn = "sso_user_logged_in" + case requestFails = "request_error" + + public var description: String { + return rawValue + } + } + + public enum Keys: String { + + case userId = "user_id" + case locationLat = "user_location_latitude" + case locationLong = "user_location_longitude" + case locationAccuracy = "user_location_accuracy" + + case sendingApp = "sendingApp" + case permID = "perm_id" + case sessionID = "session_id" + case timestamp = "timestamp" + + case categorySelected = "category" + case tripState = "tripState" + case sortType = "sortType" + case qtaListId = "qta_list_id" + case quoteListId = "quote_list_id" + case prebookTimeSet = "prebook_time_set" + case etaDisplayed = "etaDisplayed" + case detaDisplayed = "detaDisplayed" + case countAddressOptions = "countAddressOptions" + case address = "address" + case requestError = "request_error" + case requestUrl = "request_url" + + case unkownBundleIdentifier = "unkown app" + + case batteryLife = "batteryLife" + case networkType = "networkType" + case appVersion = "appVersion" + case price = "price" + case priceCurrency = "priceCurrency" + case destinationLatitude = "destinationLatitude" + case destinationLongitude = "destinationLongitude" + case destinationAddress = "destinationAddress" + case isPrebook = "isPrebook" + + var description: String { + return rawValue + } + } +} diff --git a/KarhooSDK/Service/Analytics/AnalyticsPayloadBuilder.swift b/KarhooSDK/Service/Analytics/AnalyticsPayloadBuilder.swift new file mode 100644 index 00000000..a26a8ba9 --- /dev/null +++ b/KarhooSDK/Service/Analytics/AnalyticsPayloadBuilder.swift @@ -0,0 +1,95 @@ +// +// AnalyticsPayloadBuilder.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import CoreLocation + +final class DefaultAnalyticsPayloadBuilder { + + private var payload = [String: Any]() + private let timestampFormatter = TimestampFormatter() + private let uuidProvider = DeviceIdentifierProvider.shared + private let locationProvider: UserLocationProvider + private let batteryMonitor: BatteryMonitor + private let networkTypeProvider = NetworkConnectionTypeProvider() + + init(locationProvider: UserLocationProvider = KarhooUserLocationProvider(), + batteryMonitor: BatteryMonitor = BatteryMonitor()) { + self.locationProvider = locationProvider + self.batteryMonitor = batteryMonitor + } + + func defaultPayload(context: Context = CurrentContext()) -> [String: Any] { + payload = [:] + addBundleId(context: context) + addUserPayload() + addTimestamp() + addSessionIDs() + addBatteryLevel() + addNetworkType() + addAppVersion() + return payload + } + + private func addBundleId(context: Context) { + let unknownBundleId = AnalyticsConstants.Keys.unkownBundleIdentifier.description + let bundleId = context.getCurrentBundle().bundleIdentifier ?? unknownBundleId + payload[AnalyticsConstants.Keys.sendingApp.description] = bundleId + } + + private func addUserPayload() { + guard let user = KarhooUserService().getCurrentUser() else { + return + } + + payload[AnalyticsConstants.Keys.userId.description] = user.userId + addUserLocationPayload(location: locationProvider.getLastKnownLocation()) + } + + private func addUserLocationPayload(location: CLLocation?) { + guard let location = location else { + return + } + + payload[AnalyticsConstants.Keys.locationAccuracy.description] = location.horizontalAccuracy + payload[AnalyticsConstants.Keys.locationLat.description] = location.coordinate.latitude + payload[AnalyticsConstants.Keys.locationLong.description] = location.coordinate.longitude + } + + private func addSessionIDs() { + payload[AnalyticsConstants.Keys.sessionID.description] = uuidProvider.getSessionID() + payload[AnalyticsConstants.Keys.permID.description] = uuidProvider.getPermamentID() + } + + private func addTimestamp() { + payload[AnalyticsConstants.Keys.timestamp.description] = timestampFormatter.formattedDate(Date()) + } + + private func addBatteryLevel() { + payload[AnalyticsConstants.Keys.batteryLife.description] = batteryMonitor.batteryLevel + } + + private func addNetworkType() { + payload[AnalyticsConstants.Keys.networkType.description] = networkTypeProvider.connectionType() + } + + private func addAppVersion() { + payload[AnalyticsConstants.Keys.appVersion.description] = appVersion() + } + + private func appVersion() -> String { + guard let info = Bundle.main.infoDictionary else { + return "" + } + + let build = info["CFBundleVersion"] as? String + let version = info["CFBundleShortVersionString"] as? String + + return "\(version ?? "") (\(build ?? ""))" + } +} diff --git a/KarhooSDK/Service/Analytics/AnalyticsService.swift b/KarhooSDK/Service/Analytics/AnalyticsService.swift new file mode 100644 index 00000000..1a032449 --- /dev/null +++ b/KarhooSDK/Service/Analytics/AnalyticsService.swift @@ -0,0 +1,16 @@ +// +// AnalyticsService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol AnalyticsService { + + func send(eventName: AnalyticsConstants.EventNames, payload: [String: Any]) + + func send(eventName: AnalyticsConstants.EventNames) +} diff --git a/KarhooSDK/Service/Analytics/KarhooAnalyticsService.swift b/KarhooSDK/Service/Analytics/KarhooAnalyticsService.swift new file mode 100644 index 00000000..7d32f834 --- /dev/null +++ b/KarhooSDK/Service/Analytics/KarhooAnalyticsService.swift @@ -0,0 +1,69 @@ +// +// KarhooAnalyticsService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import CoreLocation + +final class KarhooAnalyticsService: AnalyticsService { + + private let providers: [AnalyticsProvider] + private let context: Context + private let payloadBuilder: DefaultAnalyticsPayloadBuilder + private let userDataStore: UserDataStore + + private let gdprSensitiveEvents: [AnalyticsConstants.EventNames] = [.appOpened] + + init(providers: [AnalyticsProvider]? = nil, + payloadBuilder: DefaultAnalyticsPayloadBuilder = DefaultAnalyticsPayloadBuilder(), + context: Context = CurrentContext(), + userDataStore: UserDataStore = DefaultUserDataStore()) { + if let providers = providers { + self.providers = providers + } else { + self.providers = KarhooAnalyticsService.defaultProviders(context: context) + } + + self.payloadBuilder = payloadBuilder + self.context = context + self.userDataStore = userDataStore + } + + class func defaultProviders(context: Context) -> [AnalyticsProvider] { + guard !context.isTestflightBuild() else { + return [LogAnalyticsProvider()] + } + return [Karhoo.configuration.analyticsProvider()] + } + + func send(eventName: AnalyticsConstants.EventNames) { + if eventIsGdprSensitive(event: eventName) { + return + } + + send(eventName: eventName, payload: [:]) + } + + func send(eventName: AnalyticsConstants.EventNames, payload: [String: Any]) { + if eventIsGdprSensitive(event: eventName) { + return + } + + var combinedPayload = getStandardPayload() + payload.forEach { (key, value) in + combinedPayload[key] = value } + providers.forEach { $0.trackEvent(name: eventName.description, payload: combinedPayload) } + } + + private func eventIsGdprSensitive(event: AnalyticsConstants.EventNames) -> Bool { + return gdprSensitiveEvents.contains(event) && userDataStore.getCurrentUser() == nil + } + + private func getStandardPayload() -> [String: Any] { + return payloadBuilder.defaultPayload(context: CurrentContext()) + } +} diff --git a/KarhooSDK/Service/Analytics/Providers/LogAnalyticsProvider.swift b/KarhooSDK/Service/Analytics/Providers/LogAnalyticsProvider.swift new file mode 100644 index 00000000..4e56bb0c --- /dev/null +++ b/KarhooSDK/Service/Analytics/Providers/LogAnalyticsProvider.swift @@ -0,0 +1,36 @@ +// +// LogAnalyticsProvider.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class LogAnalyticsProvider: AnalyticsProvider { + + private let newLine = "\n" + private let delimiter = "\n-----------------\n" + private let nameTitle = "Event: " + private let payloadDelimiter = ": " + + private let output: (String, CVarArg...) -> Void + + init(output: @escaping (String, CVarArg...) -> Void = NSLog) { + self.output = output + } + + func trackEvent(name: String) { + output(delimiter + nameTitle + name.description + delimiter) + } + + func trackEvent(name: String, payload: [String: Any]?) { + output(delimiter + nameTitle + name.description + newLine) + let payloadToUse = payload ?? [:] + for (key, value) in payloadToUse { + output(key + payloadDelimiter + "\(value)" + newLine) + } + output(delimiter) + } +} diff --git a/KarhooSDK/Service/Auth/AuthHeaderKeys.swift b/KarhooSDK/Service/Auth/AuthHeaderKeys.swift new file mode 100644 index 00000000..fe2e69cb --- /dev/null +++ b/KarhooSDK/Service/Auth/AuthHeaderKeys.swift @@ -0,0 +1,27 @@ +// +// AuthHeaderKeys.swift +// KarhooSDK +// +// Created by Tiziano Bruni on 03/03/2020. +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +enum AuthHeaderKeys: String { + case scope + case clientId = "client_id" + case token + case tokenTypeHint = "token_type_hint" + case refreshToken = "refresh_token" + case grantType = "grant_type" + + var keyValue: String { + switch self { + case .refreshToken: + return "refresh_token" + default: + return "" + } + } +} diff --git a/KarhooSDK/Service/Auth/AuthService.swift b/KarhooSDK/Service/Auth/AuthService.swift new file mode 100644 index 00000000..f3f3c9c1 --- /dev/null +++ b/KarhooSDK/Service/Auth/AuthService.swift @@ -0,0 +1,13 @@ +// +// SSOAuthService.swift +// KarhooSDK +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +public protocol AuthService { + func login(token: String) -> Call + func revoke() -> Call +} diff --git a/KarhooSDK/Service/Auth/KarhooAuthService.swift b/KarhooSDK/Service/Auth/KarhooAuthService.swift new file mode 100644 index 00000000..0263f489 --- /dev/null +++ b/KarhooSDK/Service/Auth/KarhooAuthService.swift @@ -0,0 +1,29 @@ +// +// KarhooSSOAuthService.swift +// KarhooSDK +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +final class KarhooAuthService: AuthService { + + private let authInteractor: AuthLoginInteractor + private let revokeInteractor: KarhooExecutable + + init(authInteractor: AuthLoginInteractor = KarhooAuthLoginInteractor(), + revokeInteractor: KarhooExecutable = KarhoooAuthRevokeInteractor()) { + self.revokeInteractor = revokeInteractor + self.authInteractor = authInteractor + } + + func login(token: String) -> Call { + authInteractor.set(token: token) + return Call(executable: authInteractor) + } + + func revoke() -> Call { + return Call(executable: revokeInteractor) + } +} diff --git a/KarhooSDK/Service/Auth/LoginInteractor/AuthLoginInteractor.swift b/KarhooSDK/Service/Auth/LoginInteractor/AuthLoginInteractor.swift new file mode 100644 index 00000000..57186660 --- /dev/null +++ b/KarhooSDK/Service/Auth/LoginInteractor/AuthLoginInteractor.swift @@ -0,0 +1,12 @@ +// +// SSOAuthLoginInteractor.swift +// KarhooSDK +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +public protocol AuthLoginInteractor: KarhooExecutable { + func set(token: String) +} diff --git a/KarhooSDK/Service/Auth/LoginInteractor/KarhooAuthLoginInteractor.swift b/KarhooSDK/Service/Auth/LoginInteractor/KarhooAuthLoginInteractor.swift new file mode 100644 index 00000000..ca4886be --- /dev/null +++ b/KarhooSDK/Service/Auth/LoginInteractor/KarhooAuthLoginInteractor.swift @@ -0,0 +1,80 @@ +// +// KarhooSSOAuthInteractor.swift +// KarhooSDK +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +final class KarhooAuthLoginInteractor: AuthLoginInteractor { + + private var token: String? + private let tokenExchangeRequestSender: RequestSender + private let userInfoSender: RequestSender + private let userDataStore: UserDataStore + private let analytics: AnalyticsService + + init(tokenExchangeRequestSender: RequestSender = KarhooRequestSender(httpClient: JsonHttpClient.shared), + userInfoSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared), + userDataStore: UserDataStore = DefaultUserDataStore(), + analytics: AnalyticsService = KarhooAnalyticsService()) { + self.tokenExchangeRequestSender = tokenExchangeRequestSender + self.userInfoSender = userInfoSender + self.userDataStore = userDataStore + self.analytics = analytics + } + + func cancel() { + tokenExchangeRequestSender.cancelNetworkRequest() + userInfoSender.cancelNetworkRequest() + } + + func set(token: String) { + self.token = token + } + + func execute(callback: @escaping CallbackClosure) { + if token == nil { return } + guard let userInfoCallback = callback as? CallbackClosure else { + return + } + + tokenExchangeRequestSender.encodedRequest(endpoint: .authTokenExchange, + body: authLoginHeaderComponents(), + callback: { [weak self] (result: Result) in + guard let authToken = result.successValue() else { + userInfoCallback(.failure(error: result.errorValue())) + return + } + self?.userDataStore.set(credentials: authToken.toCredentials()) + self?.getUserInfo(authToken.accessToken, callback: userInfoCallback) + }) + } + + private func getUserInfo(_ token: String, callback: @escaping CallbackClosure) { + userInfoSender.requestAndDecode(payload: nil, + endpoint: .authUserInfo) { [weak self](result: Result) in + switch result { + case .success(var user): + self?.userDataStore.updateUser(user: &user) + self?.analytics.send(eventName: .ssoUserLogIn) + callback(.success(result: user)) + case .failure(let error): + callback(.failure(error: error)) + } + } + } + + private func authLoginHeaderComponents() -> URLComponents { + var components = URLComponents() + let tokenExchangeSettings = Karhoo.configuration.authenticationMethod().tokenExchangeSettings + + components.queryItems = [URLQueryItem(name: AuthHeaderKeys.scope.rawValue, + value: tokenExchangeSettings?.scope ?? ""), + URLQueryItem(name: AuthHeaderKeys.clientId.rawValue, + value: tokenExchangeSettings?.clientId ?? ""), + URLQueryItem(name: AuthHeaderKeys.token.rawValue, value: token)] + return components + } +} diff --git a/KarhooSDK/Service/Auth/RevokeInteractor/KarhooAuthRevokeInteractor.swift b/KarhooSDK/Service/Auth/RevokeInteractor/KarhooAuthRevokeInteractor.swift new file mode 100644 index 00000000..e2f425b8 --- /dev/null +++ b/KarhooSDK/Service/Auth/RevokeInteractor/KarhooAuthRevokeInteractor.swift @@ -0,0 +1,55 @@ +// +// KarhoooSSOAuthRevokeInteractor.swift +// KarhooSDK +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation + +final class KarhoooAuthRevokeInteractor: KarhooExecutable { + + private let userDataStore: UserDataStore + private let revokeRequestSender: RequestSender + private let analytics: AnalyticsService + + init(userDataStore: UserDataStore = DefaultUserDataStore(), + revokeRequestSender: RequestSender = KarhooRequestSender(httpClient: JsonHttpClient.shared), + analytics: AnalyticsService = KarhooAnalyticsService()) { + self.userDataStore = userDataStore + self.revokeRequestSender = revokeRequestSender + self.analytics = analytics + } + + func execute(callback: @escaping CallbackClosure) { + guard let user = userDataStore.getCurrentUser() else { + callback(Result.failure(error: nil)) + return + } + + revokeRequestSender.encodedRequest(endpoint: .authRevoke, + body: requestComponents(), + callback: { [weak self] (result: Result) in + guard result.successValue(orErrorCallback: callback) != nil, + let resultValue = KarhooVoid() as? T else { return } + self?.analytics.send(eventName: .ssoTokenRevoked) + self?.userDataStore.removeCurrentUserAndCredentials() + callback(Result.success(result: resultValue)) + }) + } + + private func requestComponents() -> URLComponents { + var urlComponents = URLComponents() + let tokenExchangeSettings = Karhoo.configuration.authenticationMethod().tokenExchangeSettings + + urlComponents.queryItems = [URLQueryItem(name: AuthHeaderKeys.token.rawValue, value: userDataStore.getCurrentCredentials()?.refreshToken ?? ""), + URLQueryItem(name: AuthHeaderKeys.tokenTypeHint.rawValue, value: AuthHeaderKeys.refreshToken.keyValue), + URLQueryItem(name: AuthHeaderKeys.clientId.rawValue, value: tokenExchangeSettings?.clientId ?? "")] + + return urlComponents + } + + func cancel() { + revokeRequestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Availability/AvailabilityInteractor/AvailabilityInteractor.swift b/KarhooSDK/Service/Availability/AvailabilityInteractor/AvailabilityInteractor.swift new file mode 100644 index 00000000..9a5a5f35 --- /dev/null +++ b/KarhooSDK/Service/Availability/AvailabilityInteractor/AvailabilityInteractor.swift @@ -0,0 +1,11 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol AvailabilityInteractor: KarhooExecutable { + + func set(availabilitySearch: AvailabilitySearch) +} diff --git a/KarhooSDK/Service/Availability/AvailabilityInteractor/KarhooAvailabilityInteractor.swift b/KarhooSDK/Service/Availability/AvailabilityInteractor/KarhooAvailabilityInteractor.swift new file mode 100644 index 00000000..87d1a005 --- /dev/null +++ b/KarhooSDK/Service/Availability/AvailabilityInteractor/KarhooAvailabilityInteractor.swift @@ -0,0 +1,34 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooAvailabilityInteractor: AvailabilityInteractor { + + private let requestSender: RequestSender + private var availabilitySearch: AvailabilitySearch? + + init(request: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.requestSender = request + } + + func set(availabilitySearch: AvailabilitySearch) { + self.availabilitySearch = availabilitySearch + } + + func execute(callback: @escaping CallbackClosure) { + guard let availabilitySearch = self.availabilitySearch else { + return + } + + requestSender.requestAndDecode(payload: availabilitySearch, + endpoint: .availability, + callback: callback) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Availability/AvailabilityService.swift b/KarhooSDK/Service/Availability/AvailabilityService.swift new file mode 100644 index 00000000..58ca8c0a --- /dev/null +++ b/KarhooSDK/Service/Availability/AvailabilityService.swift @@ -0,0 +1,13 @@ +// +// AvailabilityService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol AvailabilityService { + func availability(availabilitySearch: AvailabilitySearch) -> Call +} diff --git a/KarhooSDK/Service/Availability/KarhooAvailabilityService.swift b/KarhooSDK/Service/Availability/KarhooAvailabilityService.swift new file mode 100644 index 00000000..0fffd7ba --- /dev/null +++ b/KarhooSDK/Service/Availability/KarhooAvailabilityService.swift @@ -0,0 +1,23 @@ +// +// KarhooAvailabilityService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooAvailabilityService: AvailabilityService { + + private let availabilityInteractor: AvailabilityInteractor + + init(availabilityInteractor: AvailabilityInteractor = KarhooAvailabilityInteractor()) { + self.availabilityInteractor = availabilityInteractor + } + + func availability(availabilitySearch: AvailabilitySearch) -> Call { + availabilityInteractor.set(availabilitySearch: availabilitySearch) + return Call(executable: availabilityInteractor) + } +} diff --git a/KarhooSDK/Service/Config/ConfigService.swift b/KarhooSDK/Service/Config/ConfigService.swift new file mode 100644 index 00000000..5b962b1c --- /dev/null +++ b/KarhooSDK/Service/Config/ConfigService.swift @@ -0,0 +1,13 @@ +// +// ConfigurationService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol ConfigService { + func uiConfig(uiConfigRequest: UIConfigRequest) -> Call +} diff --git a/KarhooSDK/Service/Config/KarhooConfigService.swift b/KarhooSDK/Service/Config/KarhooConfigService.swift new file mode 100644 index 00000000..4505130d --- /dev/null +++ b/KarhooSDK/Service/Config/KarhooConfigService.swift @@ -0,0 +1,23 @@ +// +// KarhooConfigService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooConfigService: ConfigService { + + private let uiConfigInteractor: UIConfigInteractor + + init(uiConfigInteractor: UIConfigInteractor = KarhooUIConfigInteractor()) { + self.uiConfigInteractor = uiConfigInteractor + } + + func uiConfig(uiConfigRequest: UIConfigRequest) -> Call { + uiConfigInteractor.set(uiConfigRequest: uiConfigRequest) + return Call(executable: uiConfigInteractor) + } +} diff --git a/KarhooSDK/Service/Config/Provider/KarhooUIConfigProvider.swift b/KarhooSDK/Service/Config/Provider/KarhooUIConfigProvider.swift new file mode 100644 index 00000000..2bcf2b60 --- /dev/null +++ b/KarhooSDK/Service/Config/Provider/KarhooUIConfigProvider.swift @@ -0,0 +1,22 @@ +// +// KarhooUIConfigProvider.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooUIConfigProvider: UIConfigProvider { + + func fetchConfig(uiConfigRequest: UIConfigRequest, + organisation: Organisation, + callback: CallbackClosure) { + if let config = UISettings.settings[organisation.id]?[uiConfigRequest.viewId ?? ""] { + callback(Result.success(result: config)) + } else { + callback(Result.failure(error: SDKErrorFactory.noConfigAvailableForView())) + } + } +} diff --git a/KarhooSDK/Service/Config/Provider/UIConfigProvider.swift b/KarhooSDK/Service/Config/Provider/UIConfigProvider.swift new file mode 100644 index 00000000..09d45ff5 --- /dev/null +++ b/KarhooSDK/Service/Config/Provider/UIConfigProvider.swift @@ -0,0 +1,15 @@ +// +// UIConfigProvider.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol UIConfigProvider { + func fetchConfig(uiConfigRequest: UIConfigRequest, + organisation: Organisation, + callback: @escaping CallbackClosure) +} diff --git a/KarhooSDK/Service/Config/Provider/UISettings.swift b/KarhooSDK/Service/Config/Provider/UISettings.swift new file mode 100644 index 00000000..7908436f --- /dev/null +++ b/KarhooSDK/Service/Config/Provider/UISettings.swift @@ -0,0 +1,22 @@ +// +// UISettings.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct UISettings { + + //defaultForKarhooUsers : a1013897-132a-456c-9be2-636979095ad9 + //portalUser: 089a666b-a6ce-4e75-8d7f-12d8f0208f1b + //b2c Karhoo org: 23661866-6554-46bf-977e-21430a3e1f22 + static var settings: [String: [String: UIConfig]] = ["a1013897-132a-456c-9be2-636979095ad9": + ["additionalFeedbackButton": UIConfig(hidden: false)], + "089a666b-a6ce-4e75-8d7f-12d8f0208f1b": + ["additionalFeedbackButton": UIConfig(hidden: true)], + "23661866-6554-46bf-977e-21430a3e1f22": ["additionalFeedbackButton": UIConfig(hidden: false)], + "5fc4f33b-2832-466e-9943-8728589ef727": ["additionalFeedbackButton": UIConfig(hidden: false)]] +} diff --git a/KarhooSDK/Service/Config/UIConfigInteractor/KarhooUIConfigInteractor.swift b/KarhooSDK/Service/Config/UIConfigInteractor/KarhooUIConfigInteractor.swift new file mode 100644 index 00000000..81f8ac96 --- /dev/null +++ b/KarhooSDK/Service/Config/UIConfigInteractor/KarhooUIConfigInteractor.swift @@ -0,0 +1,46 @@ +// +// KarhooUIConfigInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +class KarhooUIConfigInteractor: UIConfigInteractor { + + private let userDataStore: UserDataStore + private let uiConfigProvider: UIConfigProvider + private var uiConfigRequest: UIConfigRequest? + + init(userDataStore: UserDataStore = DefaultUserDataStore(), + uiConfigProvider: UIConfigProvider = KarhooUIConfigProvider()) { + self.userDataStore = userDataStore + self.uiConfigProvider = uiConfigProvider + } + + func set(uiConfigRequest: UIConfigRequest) { + self.uiConfigRequest = uiConfigRequest + } + + func execute(callback: @escaping CallbackClosure) { + guard let typedCallback = callback as? CallbackClosure else { + return + } + + guard let request = uiConfigRequest else { + typedCallback(.failure(error: SDKErrorFactory.unexpectedError())) + return + } + + guard let userOrg = userDataStore.getCurrentUser()?.organisations[0] else { + typedCallback(.failure(error: SDKErrorFactory.getLoginPermissionError())) + return + } + + uiConfigProvider.fetchConfig(uiConfigRequest: request, organisation: userOrg, callback: typedCallback) + } + + func cancel() {} +} diff --git a/KarhooSDK/Service/Config/UIConfigInteractor/UIConfigInteractor.swift b/KarhooSDK/Service/Config/UIConfigInteractor/UIConfigInteractor.swift new file mode 100644 index 00000000..55e273ee --- /dev/null +++ b/KarhooSDK/Service/Config/UIConfigInteractor/UIConfigInteractor.swift @@ -0,0 +1,13 @@ +// +// ConfigInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol UIConfigInteractor: KarhooExecutable { + func set(uiConfigRequest: UIConfigRequest) +} diff --git a/KarhooSDK/Service/DriverTracking/DriverTrackingService.swift b/KarhooSDK/Service/DriverTracking/DriverTrackingService.swift new file mode 100644 index 00000000..09a8f878 --- /dev/null +++ b/KarhooSDK/Service/DriverTracking/DriverTrackingService.swift @@ -0,0 +1,13 @@ +// +// DriverTrackingService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol DriverTrackingService { + func trackDriver(tripId: String) -> PollCall +} diff --git a/KarhooSDK/Service/DriverTracking/Interactor/KarhooDriverTrackingInteractor.swift b/KarhooSDK/Service/DriverTracking/Interactor/KarhooDriverTrackingInteractor.swift new file mode 100644 index 00000000..ead60f2f --- /dev/null +++ b/KarhooSDK/Service/DriverTracking/Interactor/KarhooDriverTrackingInteractor.swift @@ -0,0 +1,31 @@ +// +// KarhooDriverTrackingInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooDriverTrackingInteractor: KarhooExecutable { + + private let tripId: String + private let requestSender: RequestSender + + init(tripId: String, + requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.tripId = tripId + self.requestSender = requestSender + } + + func execute(callback: @escaping CallbackClosure) { + requestSender.requestAndDecode(payload: nil, + endpoint: .trackDriver(identifier: tripId), + callback: callback) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/DriverTracking/KarhooDriverTrackingService.swift b/KarhooSDK/Service/DriverTracking/KarhooDriverTrackingService.swift new file mode 100644 index 00000000..127138a5 --- /dev/null +++ b/KarhooSDK/Service/DriverTracking/KarhooDriverTrackingService.swift @@ -0,0 +1,23 @@ +// +// KarhooDriverTrackingService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooDriverTrackingService: DriverTrackingService { + + let trackDriverPollFactory: PollCallFactory + init(trackDriverPollFactory: PollCallFactory = KarhooPollCallFactory()) { + self.trackDriverPollFactory = trackDriverPollFactory + } + + func trackDriver(tripId: String) -> PollCall { + let interactor = KarhooDriverTrackingInteractor(tripId: tripId) + return trackDriverPollFactory.shared(identifier: "trackDriver\(tripId)", + executable: interactor) + } +} diff --git a/KarhooSDK/Service/Fares/FareService.swift b/KarhooSDK/Service/Fares/FareService.swift new file mode 100644 index 00000000..48b11e1e --- /dev/null +++ b/KarhooSDK/Service/Fares/FareService.swift @@ -0,0 +1,13 @@ +// +// FareService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol FareService { + func fareDetails(tripId: String) -> Call +} diff --git a/KarhooSDK/Service/Fares/FaresInteractor/FareInteractor.swift b/KarhooSDK/Service/Fares/FaresInteractor/FareInteractor.swift new file mode 100644 index 00000000..cb414021 --- /dev/null +++ b/KarhooSDK/Service/Fares/FaresInteractor/FareInteractor.swift @@ -0,0 +1,13 @@ +// +// FareInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol FareInteractor: KarhooExecutable { + func set(tripId: String) +} diff --git a/KarhooSDK/Service/Fares/FaresInteractor/KarhooFareInteractor.swift b/KarhooSDK/Service/Fares/FaresInteractor/KarhooFareInteractor.swift new file mode 100644 index 00000000..51340f91 --- /dev/null +++ b/KarhooSDK/Service/Fares/FaresInteractor/KarhooFareInteractor.swift @@ -0,0 +1,37 @@ +// +// KarhooFareInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooFareInteractor: FareInteractor { + + private var tripId: String? + private let requestSender: RequestSender + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.requestSender = requestSender + } + + func execute(callback: @escaping (Result) -> Void) where T: KarhooCodableModel { + guard let tripId = self.tripId else { + return + } + + requestSender.requestAndDecode(payload: nil, + endpoint: .getFareDetails(identifier: tripId), + callback: callback) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } + + func set(tripId: String) { + self.tripId = tripId + } +} diff --git a/KarhooSDK/Service/Fares/KarhooFareService.swift b/KarhooSDK/Service/Fares/KarhooFareService.swift new file mode 100644 index 00000000..9f259912 --- /dev/null +++ b/KarhooSDK/Service/Fares/KarhooFareService.swift @@ -0,0 +1,23 @@ +// +// KarhooFareService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooFareService: FareService { + + private let fareInteractor: FareInteractor + + init(fareInteractor: FareInteractor = KarhooFareInteractor()) { + self.fareInteractor = fareInteractor + } + + func fareDetails(tripId: String) -> Call { + fareInteractor.set(tripId: tripId) + return Call(executable: fareInteractor) + } +} diff --git a/KarhooSDK/Service/Payment/AddPaymentDetailsInteractor/AddPaymentDetailsInteractor.swift b/KarhooSDK/Service/Payment/AddPaymentDetailsInteractor/AddPaymentDetailsInteractor.swift new file mode 100644 index 00000000..7b59f758 --- /dev/null +++ b/KarhooSDK/Service/Payment/AddPaymentDetailsInteractor/AddPaymentDetailsInteractor.swift @@ -0,0 +1,13 @@ +// +// AddPaymentDetailsInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol AddPaymentDetailsInteractor: KarhooExecutable { + func set(addPaymentDetailsPayload: AddPaymentDetailsPayload) +} diff --git a/KarhooSDK/Service/Payment/AddPaymentDetailsInteractor/KarhooAddPaymentDetailsInteractor.swift b/KarhooSDK/Service/Payment/AddPaymentDetailsInteractor/KarhooAddPaymentDetailsInteractor.swift new file mode 100644 index 00000000..e03447dd --- /dev/null +++ b/KarhooSDK/Service/Payment/AddPaymentDetailsInteractor/KarhooAddPaymentDetailsInteractor.swift @@ -0,0 +1,49 @@ +// +// KarhooAddPaymentDetailsInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooAddPaymentDetailsInteractor: AddPaymentDetailsInteractor { + + private var addPaymentDetailsPayload: AddPaymentDetailsPayload? + private let requestSender: RequestSender + private let userDataStore: UserDataStore + private var executeCallback: CallbackClosure? + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared), + userDataStore: UserDataStore = DefaultUserDataStore()) { + self.requestSender = requestSender + self.userDataStore = userDataStore + } + + func set(addPaymentDetailsPayload: AddPaymentDetailsPayload) { + self.addPaymentDetailsPayload = addPaymentDetailsPayload + } + + func execute(callback: @escaping CallbackClosure) { + guard let payload = self.addPaymentDetailsPayload else { + return + } + self.executeCallback = callback as? CallbackClosure + + requestSender.requestAndDecode(payload: payload, + endpoint: APIEndpoint.addPaymentDetails, + callback: { [weak self] (result: Result) in + if let nonce = result.successValue() { + self?.userDataStore.updateCurrentUserNonce(nonce: nonce) + } + + self?.executeCallback?(result) + + }) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Payment/GetNonceInteractor/GetNonceInteractor.swift b/KarhooSDK/Service/Payment/GetNonceInteractor/GetNonceInteractor.swift new file mode 100644 index 00000000..c4cf720f --- /dev/null +++ b/KarhooSDK/Service/Payment/GetNonceInteractor/GetNonceInteractor.swift @@ -0,0 +1,13 @@ +// +// GetNonceInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol GetNonceInteractor: KarhooExecutable { + func set(nonceRequestPayload: NonceRequestPayload) +} diff --git a/KarhooSDK/Service/Payment/GetNonceInteractor/KarhooGetNonceInteractor.swift b/KarhooSDK/Service/Payment/GetNonceInteractor/KarhooGetNonceInteractor.swift new file mode 100644 index 00000000..7f0fd8e3 --- /dev/null +++ b/KarhooSDK/Service/Payment/GetNonceInteractor/KarhooGetNonceInteractor.swift @@ -0,0 +1,45 @@ +// +// KarhooGetNonceInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooGetNonceInteractor: GetNonceInteractor { + + private let getNonceRequest: RequestSender + private var nonceRequestPayload: NonceRequestPayload? + private let userDataStore: UserDataStore + private var getNonceCallback: CallbackClosure? + + init(request: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared), + userDataStore: UserDataStore = DefaultUserDataStore()) { + self.getNonceRequest = request + self.userDataStore = userDataStore + } + + func set(nonceRequestPayload: NonceRequestPayload) { + self.nonceRequestPayload = nonceRequestPayload + } + + func execute(callback: @escaping CallbackClosure) { + guard let payload = self.nonceRequestPayload else { + return + } + + self.getNonceCallback = callback as? CallbackClosure + getNonceRequest.requestAndDecode(payload: payload, + endpoint: APIEndpoint.getNonce, + callback: { [weak self] (result: Result) in + self?.userDataStore.updateCurrentUserNonce(nonce: result.successValue()) + self?.getNonceCallback?(result) + }) + } + + func cancel() { + getNonceRequest.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Payment/KarhooPaymentService.swift b/KarhooSDK/Service/Payment/KarhooPaymentService.swift new file mode 100644 index 00000000..0baaa26e --- /dev/null +++ b/KarhooSDK/Service/Payment/KarhooPaymentService.swift @@ -0,0 +1,41 @@ +// +// KarhooPaymentService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooPaymentService: PaymentService { + + private let paymentSDKTokenInteractor: PaymentSDKTokenInteractor + private let getNonceInteractor: GetNonceInteractor + private let addPaymentDetailsInteractor: AddPaymentDetailsInteractor + + init(tokenInteractor: PaymentSDKTokenInteractor = KarhooPaymentSDKTokenInteractor(), + getNonceInteractor: GetNonceInteractor = KarhooGetNonceInteractor(), + addPaymentDetailsInteractor: AddPaymentDetailsInteractor = KarhooAddPaymentDetailsInteractor()) { + self.paymentSDKTokenInteractor = tokenInteractor + self.getNonceInteractor = getNonceInteractor + self.addPaymentDetailsInteractor = addPaymentDetailsInteractor + } + + func initialisePaymentSDK(paymentSDKTokenPayload: PaymentSDKTokenPayload) -> Call { + paymentSDKTokenInteractor.set(payload: paymentSDKTokenPayload) + + return Call(executable: paymentSDKTokenInteractor) + } + + func addPaymentDetails(addPaymentDetailsPayload: AddPaymentDetailsPayload) -> Call { + addPaymentDetailsInteractor.set(addPaymentDetailsPayload: addPaymentDetailsPayload) + + return Call(executable: addPaymentDetailsInteractor) + } + + func getNonce(nonceRequestPayload: NonceRequestPayload) -> Call { + getNonceInteractor.set(nonceRequestPayload: nonceRequestPayload) + return Call(executable: getNonceInteractor) + } +} diff --git a/KarhooSDK/Service/Payment/PaymentSDKTokenInteractor/KarhooPaymentSDKTokenInteractor.swift b/KarhooSDK/Service/Payment/PaymentSDKTokenInteractor/KarhooPaymentSDKTokenInteractor.swift new file mode 100644 index 00000000..29149cf7 --- /dev/null +++ b/KarhooSDK/Service/Payment/PaymentSDKTokenInteractor/KarhooPaymentSDKTokenInteractor.swift @@ -0,0 +1,34 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooPaymentSDKTokenInteractor: PaymentSDKTokenInteractor { + + private let paymentSDKTokenRequest: RequestSender + private var requestPayload: PaymentSDKTokenPayload? + + init(request: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.paymentSDKTokenRequest = request + } + + func set(payload: PaymentSDKTokenPayload) { + self.requestPayload = payload + } + + func execute(callback: @escaping CallbackClosure) { + guard let payload = self.requestPayload else { + return + } + + paymentSDKTokenRequest.requestAndDecode(payload: nil, + endpoint: .paymentSDKToken(payload: payload), + callback: callback) + } + + func cancel() { + paymentSDKTokenRequest.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Payment/PaymentSDKTokenInteractor/PaymentSDKTokenInteractor.swift b/KarhooSDK/Service/Payment/PaymentSDKTokenInteractor/PaymentSDKTokenInteractor.swift new file mode 100644 index 00000000..f78fbf37 --- /dev/null +++ b/KarhooSDK/Service/Payment/PaymentSDKTokenInteractor/PaymentSDKTokenInteractor.swift @@ -0,0 +1,11 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol PaymentSDKTokenInteractor: KarhooExecutable { + + func set(payload: PaymentSDKTokenPayload) +} diff --git a/KarhooSDK/Service/Payment/PaymentService.swift b/KarhooSDK/Service/Payment/PaymentService.swift new file mode 100644 index 00000000..3e0b12fe --- /dev/null +++ b/KarhooSDK/Service/Payment/PaymentService.swift @@ -0,0 +1,18 @@ +// +// PaymentService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol PaymentService { + + func initialisePaymentSDK(paymentSDKTokenPayload: PaymentSDKTokenPayload) -> Call + + func getNonce(nonceRequestPayload: NonceRequestPayload) -> Call + + func addPaymentDetails(addPaymentDetailsPayload: AddPaymentDetailsPayload) -> Call +} diff --git a/KarhooSDK/Service/Quote/CategoryQuoteMapper.swift b/KarhooSDK/Service/Quote/CategoryQuoteMapper.swift new file mode 100644 index 00000000..b8f43b78 --- /dev/null +++ b/KarhooSDK/Service/Quote/CategoryQuoteMapper.swift @@ -0,0 +1,34 @@ +// +// CategoryQuoteMapper.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +struct CategoryQuoteMapper { + + func map(categories: Categories?, toQuotes quotes: [Quote]) -> [QuoteCategory] { + let categoryKeysForMapping = (categories?.categories ?? quotes.map { $0.categoryName }).uniqueArray() + + return categoryKeysForMapping.map { category in + let quotes = quotes.filter { $0.categoryName.lowercased() == category.lowercased() } + return QuoteCategory(name: category, + quotes: quotes) + } + } +} + +fileprivate extension Array where Element: Equatable { + func uniqueArray() -> [Element] { + var newArray: [Element] = [] + for item in self { + if newArray.contains(item) == false { + newArray.append(item) + } + } + return newArray + } +} diff --git a/KarhooSDK/Service/Quote/Interactor/KarhooQuoteInteractor.swift b/KarhooSDK/Service/Quote/Interactor/KarhooQuoteInteractor.swift new file mode 100644 index 00000000..dfa74bff --- /dev/null +++ b/KarhooSDK/Service/Quote/Interactor/KarhooQuoteInteractor.swift @@ -0,0 +1,157 @@ +// +// KarhooQuoteInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooQuoteInteractor: QuoteInteractor { + + private let availabilityRequest: RequestSender + private let quoteListIdRequest: RequestSender + private let quotesRequest: RequestSender + private var quoteSearch: QuoteSearch? + private var quoteListId: QuoteListId? + private var availableQuoteCategories: Categories? + private let filterRidesWithETA: Int = 20 + private let refreshQuoteListMinimumValidity: Int = 10 + + init(availabilityRequest: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared), + quoteListIdRequest: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared), + quotesRequest: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.availabilityRequest = availabilityRequest + self.quoteListIdRequest = quoteListIdRequest + self.quotesRequest = quotesRequest + } + + func set(quoteSearch: QuoteSearch) { + cancel() + self.quoteSearch = quoteSearch + } + + func execute(callback: @escaping CallbackClosure) { + guard let quotesCallback = callback as? CallbackClosure else { + return + } + + if let quoteListId = self.quoteListId { + makeQuotesRequest(quoteListId: quoteListId, callback: quotesCallback) + } else { + requestAndHandleAvailability(callback: quotesCallback) + } + } + + func cancel() { + availabilityRequest.cancelNetworkRequest() + quoteListIdRequest.cancelNetworkRequest() + quoteListId = nil + } + + private func requestAndHandleAvailability(callback: @escaping CallbackClosure) { + guard let payload = self.availabilitySearch else { + return + } + + availabilityRequest.requestAndDecode(payload: payload, + endpoint: .availability, + callback: { [weak self] (result: Result) in + self?.availableQuoteCategories = result.successValue() + self?.requestAndHandleQuoteListId(callback: callback) + }) + } + + private func requestAndHandleQuoteListId(callback: @escaping CallbackClosure) { + guard let requestPayload = self.availabilitySearch else { + return + } + + let quoteListIdRequestPayload = QuoteListIdRequestPayload(origin: requestPayload.originPlaceId, + destination: requestPayload.destinationPlaceId, + dateScheduled: requestPayload.dateScheduled) + + quoteListIdRequest.requestAndDecode(payload: quoteListIdRequestPayload, + endpoint: .quoteListId, + callback: { [weak self] (result: Result) in + if let quoteListId = result.successValue() { + self?.makeQuotesRequest(quoteListId: quoteListId, + callback: callback) + } else { + callback(.failure(error: result.errorValue())) + } + + }) + } + + private func makeQuotesRequest(quoteListId: QuoteListId, callback: @escaping CallbackClosure) { + self.quoteListId = quoteListId + + quotesRequest.requestAndDecode(payload: nil, + endpoint: .quotes(identifier: quoteListId.identifier), + callback: { [weak self] (result: Result) in + if let successValue = result.successValue() { + self?.handleSuccessfulQuoteRequest(quoteList: successValue, + callback: callback) + } else { + self?.handleFailedQuoteRequest(error: result.errorValue(), + callback: callback) + } + }) + } + + private func handleFailedQuoteRequest(error: KarhooError?, + callback: @escaping CallbackClosure) { + if error?.type != .couldNotGetEstimates { + callback(.failure(error: error)) + return + } + + cancelSearchAndRequestQuoteId(callback: callback) + } + + private func handleSuccessfulQuoteRequest(quoteList: QuoteList, callback: @escaping CallbackClosure) { + guard quoteList.validity > refreshQuoteListMinimumValidity else { + cancelSearchAndRequestQuoteId(callback: callback) + return + } + + let filteredQuotes = quoteList.quoteItems.filter { $0.qtaHighMinutes <= filterRidesWithETA } + let quoteCategories = CategoryQuoteMapper().map(categories: availableQuoteCategories, + toQuotes: filteredQuotes) + let quotes = Quotes(quoteListId: quoteList.listId, + quoteCategories: quoteCategories, + all: filteredQuotes) + + callback(.success(result: quotes)) + } + + private func cancelSearchAndRequestQuoteId(callback: @escaping CallbackClosure) { + cancel() + requestAndHandleQuoteListId(callback: callback) + } +} + +private extension KarhooQuoteInteractor { + + private var availabilitySearch: AvailabilitySearch? { + + guard let quoteSearch = self.quoteSearch else { + return nil + } + + var dateScheduled: String? + + if let quoteSearchDate = quoteSearch.dateScheduled { + let dateFormatter = KarhooNetworkDateFormatter(timeZone: quoteSearch.origin.timezone(), + formatType: .availability) + + dateScheduled = dateFormatter.toString(from: quoteSearchDate) + } + + return AvailabilitySearch(origin: quoteSearch.origin.placeId, + destination: quoteSearch.destination.placeId, + dateScheduled: dateScheduled) + } +} diff --git a/KarhooSDK/Service/Quote/Interactor/QuoteInteractor.swift b/KarhooSDK/Service/Quote/Interactor/QuoteInteractor.swift new file mode 100644 index 00000000..93d73d39 --- /dev/null +++ b/KarhooSDK/Service/Quote/Interactor/QuoteInteractor.swift @@ -0,0 +1,14 @@ +// +// QuoteInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol QuoteInteractor: KarhooExecutable { + + func set(quoteSearch: QuoteSearch) +} diff --git a/KarhooSDK/Service/Quote/KarhooQuoteService.swift b/KarhooSDK/Service/Quote/KarhooQuoteService.swift new file mode 100644 index 00000000..6be9dab9 --- /dev/null +++ b/KarhooSDK/Service/Quote/KarhooQuoteService.swift @@ -0,0 +1,24 @@ +// +// KarhooQuoteService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooQuoteService: QuoteService { + + private let quoteInteractor: QuoteInteractor + + init(quoteInteractor: QuoteInteractor = KarhooQuoteInteractor()) { + self.quoteInteractor = quoteInteractor + } + + func quotes(quoteSearch: QuoteSearch) -> PollCall { + quoteInteractor.set(quoteSearch: quoteSearch) + let pollExecutor = PollExecutor(executable: quoteInteractor) + return PollCall(pollExecutor: pollExecutor) + } +} diff --git a/KarhooSDK/Service/Quote/QuoteService.swift b/KarhooSDK/Service/Quote/QuoteService.swift new file mode 100644 index 00000000..d5345f22 --- /dev/null +++ b/KarhooSDK/Service/Quote/QuoteService.swift @@ -0,0 +1,13 @@ +// +// QuoteService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol QuoteService { + func quotes(quoteSearch: QuoteSearch) -> PollCall +} diff --git a/KarhooSDK/Service/Trip/BookingInteractor/BookingInteractor.swift b/KarhooSDK/Service/Trip/BookingInteractor/BookingInteractor.swift new file mode 100644 index 00000000..d456c99c --- /dev/null +++ b/KarhooSDK/Service/Trip/BookingInteractor/BookingInteractor.swift @@ -0,0 +1,13 @@ +// +// BookingInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol BookingInteractor: KarhooExecutable { + func set(tripBooking: TripBooking) +} diff --git a/KarhooSDK/Service/Trip/BookingInteractor/KarhooBookingInteractor.swift b/KarhooSDK/Service/Trip/BookingInteractor/KarhooBookingInteractor.swift new file mode 100644 index 00000000..2851a904 --- /dev/null +++ b/KarhooSDK/Service/Trip/BookingInteractor/KarhooBookingInteractor.swift @@ -0,0 +1,43 @@ +// +// KarhooBookingInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooBookingInteractor: BookingInteractor { + + private let bookingRequest: RequestSender + private var tripBooking: TripBooking? + + init(bookingRequest: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.bookingRequest = bookingRequest + } + + func set(tripBooking: TripBooking) { + self.tripBooking = tripBooking + } + + func execute(callback: @escaping CallbackClosure) { + guard let tripBooking = self.tripBooking else { + return + } + + var bookingEndpoint: APIEndpoint = .bookTrip + + if tripBooking.paymentNonce != nil { + bookingEndpoint = .bookTripWithNonce + } + + bookingRequest.requestAndDecode(payload: tripBooking, + endpoint: bookingEndpoint, + callback: callback) + } + + func cancel() { + bookingRequest.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Trip/CancelTripnteractor/CancelTripInteractor.swift b/KarhooSDK/Service/Trip/CancelTripnteractor/CancelTripInteractor.swift new file mode 100644 index 00000000..881bde2b --- /dev/null +++ b/KarhooSDK/Service/Trip/CancelTripnteractor/CancelTripInteractor.swift @@ -0,0 +1,13 @@ +// +// CancelTripInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol CancelTripInteractor: KarhooExecutable { + func set(tripCancellation: TripCancellation) +} diff --git a/KarhooSDK/Service/Trip/CancelTripnteractor/KarhooCancelTripInteractor.swift b/KarhooSDK/Service/Trip/CancelTripnteractor/KarhooCancelTripInteractor.swift new file mode 100644 index 00000000..1c28f4fc --- /dev/null +++ b/KarhooSDK/Service/Trip/CancelTripnteractor/KarhooCancelTripInteractor.swift @@ -0,0 +1,48 @@ +// +// KarhooCancelTripInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooCancelTripInteractor: CancelTripInteractor { + + private let requestSender: RequestSender + private let analyticsService: AnalyticsService + private var tripCancellation: TripCancellation? + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared), + analyticsService: AnalyticsService = KarhooAnalyticsService()) { + self.requestSender = requestSender + self.analyticsService = analyticsService + } + + func set(tripCancellation: TripCancellation) { + self.tripCancellation = tripCancellation + } + + func execute(callback: @escaping CallbackClosure) { + guard let tripCancellation = self.tripCancellation else { + return + } + + let payload = CancelTripRequestPayload(reason: tripCancellation.cancelReason) + + requestSender.request(payload: payload, + endpoint: .cancelTrip(identifier: tripCancellation.tripId), + callback: { result in + guard result.successValue(orErrorCallback: callback) != nil, + let resultValue = KarhooVoid() as? T else { return } + callback(Result.success(result: resultValue)) + }) + + analyticsService.send(eventName: .tripCancellationAttempted) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Trip/KarhooTripService.swift b/KarhooSDK/Service/Trip/KarhooTripService.swift new file mode 100644 index 00000000..ca623f28 --- /dev/null +++ b/KarhooSDK/Service/Trip/KarhooTripService.swift @@ -0,0 +1,60 @@ +// +// KarhooTripService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooTripService: TripService { + private let bookingInteractor: BookingInteractor + private let cancelTripInteractor: CancelTripInteractor + private let tripSearchInteractor: TripSearchInteractor + private let analytics: AnalyticsService + private let tripPollFactory: PollCallFactory + private let tripStatusPollFactory: PollCallFactory + + init(bookingInteractor: BookingInteractor = KarhooBookingInteractor(), + cancelTripInteractor: CancelTripInteractor = KarhooCancelTripInteractor(), + tripSearchInteractor: TripSearchInteractor = KarhooTripSearchInteractor(), + analytics: AnalyticsService = KarhooAnalyticsService(), + tripPollFactory: PollCallFactory = KarhooPollCallFactory(), + tripStatusPollFactory: PollCallFactory = KarhooPollCallFactory()) { + + self.bookingInteractor = bookingInteractor + self.cancelTripInteractor = cancelTripInteractor + self.analytics = analytics + self.tripSearchInteractor = tripSearchInteractor + self.tripPollFactory = tripPollFactory + self.tripStatusPollFactory = tripStatusPollFactory + } + + func book(tripBooking: TripBooking) -> Call { + bookingInteractor.set(tripBooking: tripBooking) + return Call(executable: bookingInteractor) + } + + func cancel(tripCancellation: TripCancellation) -> Call { + cancelTripInteractor.set(tripCancellation: tripCancellation) + return Call(executable: cancelTripInteractor) + } + + func search(tripSearch: TripSearch) -> Call<[TripInfo]> { + tripSearchInteractor.set(tripSearch: tripSearch) + return Call(executable: tripSearchInteractor) + } + + func trackTrip(tripId: String) -> PollCall { + let interactor = KarhooTripUpdateInteractor(tripId: tripId) + return tripPollFactory.shared(identifier: tripId, + executable: interactor) + } + + func status(tripId: String) -> PollCall { + let interactor = KarhooTripStatusInteractor(tripId: tripId) + return tripStatusPollFactory.shared(identifier: tripId, + executable: interactor) + } +} diff --git a/KarhooSDK/Service/Trip/TripSearchInteractor/KarhooTripSearchInteractor.swift b/KarhooSDK/Service/Trip/TripSearchInteractor/KarhooTripSearchInteractor.swift new file mode 100644 index 00000000..a01f9593 --- /dev/null +++ b/KarhooSDK/Service/Trip/TripSearchInteractor/KarhooTripSearchInteractor.swift @@ -0,0 +1,38 @@ +// +// KarhooTripSearchInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooTripSearchInteractor: TripSearchInteractor { + + private let requestSender: RequestSender + private var tripSearch: TripSearch? + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.requestSender = requestSender + } + + func set(tripSearch: TripSearch) { + self.tripSearch = tripSearch + } + + func execute(callback: @escaping CallbackClosure) { + guard let tripSearch = self.tripSearch else { return } + + requestSender.requestAndDecode(payload: tripSearch, + endpoint: .tripSearch) { (result: Result) in + guard let bookingList = result.successValue(orErrorCallback: callback), + let result = bookingList.trips as? T else { return } + callback(.success(result: result)) + } + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Trip/TripSearchInteractor/TripSearchInteractor.swift b/KarhooSDK/Service/Trip/TripSearchInteractor/TripSearchInteractor.swift new file mode 100644 index 00000000..9e2755a4 --- /dev/null +++ b/KarhooSDK/Service/Trip/TripSearchInteractor/TripSearchInteractor.swift @@ -0,0 +1,13 @@ +// +// TripsInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol TripSearchInteractor: KarhooExecutable { + func set(tripSearch: TripSearch) +} diff --git a/KarhooSDK/Service/Trip/TripService.swift b/KarhooSDK/Service/Trip/TripService.swift new file mode 100644 index 00000000..55bfad6b --- /dev/null +++ b/KarhooSDK/Service/Trip/TripService.swift @@ -0,0 +1,21 @@ +// +// TripService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol TripService { + func book(tripBooking: TripBooking) -> Call + + func cancel(tripCancellation: TripCancellation) -> Call + + func search(tripSearch: TripSearch) -> Call<[TripInfo]> + + func trackTrip(tripId: String) -> PollCall + + func status(tripId: String) -> PollCall +} diff --git a/KarhooSDK/Service/Trip/TripStatus/KarhooTripStatusInteractor.swift b/KarhooSDK/Service/Trip/TripStatus/KarhooTripStatusInteractor.swift new file mode 100644 index 00000000..3cac04a0 --- /dev/null +++ b/KarhooSDK/Service/Trip/TripStatus/KarhooTripStatusInteractor.swift @@ -0,0 +1,32 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooTripStatusInteractor: TripStatusInteractor { + private let tripId: String + private let requestSender: RequestSender + + init(tripId: String, + requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.tripId = tripId + self.requestSender = requestSender + } + + func execute(callback: @escaping CallbackClosure) { + requestSender.requestAndDecode(payload: nil, + endpoint: .tripStatus(identifier: tripId), + callback: { (result: Result) in + guard let status = result.successValue(orErrorCallback: callback), + let resultValue = status.status as? T else { return } + callback(.success(result: resultValue)) + + }) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Trip/TripStatus/TripStatusInteractor.swift b/KarhooSDK/Service/Trip/TripStatus/TripStatusInteractor.swift new file mode 100644 index 00000000..b2e8f0e9 --- /dev/null +++ b/KarhooSDK/Service/Trip/TripStatus/TripStatusInteractor.swift @@ -0,0 +1,8 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol TripStatusInteractor: KarhooExecutable {} diff --git a/KarhooSDK/Service/Trip/TripUpdate/KarhooTripUpdateInteractor.swift b/KarhooSDK/Service/Trip/TripUpdate/KarhooTripUpdateInteractor.swift new file mode 100644 index 00000000..d12872b3 --- /dev/null +++ b/KarhooSDK/Service/Trip/TripUpdate/KarhooTripUpdateInteractor.swift @@ -0,0 +1,31 @@ +// +// KarhooTripUpdateInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooTripUpdateInteractor: TripUpdateInteractor { + + private let tripId: String + private let requestSender: RequestSender + + init(tripId: String, + requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.tripId = tripId + self.requestSender = requestSender + } + + func execute(callback: @escaping CallbackClosure) { + requestSender.requestAndDecode(payload: nil, + endpoint: .trackTrip(identifier: tripId), + callback: callback) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/Trip/TripUpdate/TripUpdateInteractor.swift b/KarhooSDK/Service/Trip/TripUpdate/TripUpdateInteractor.swift new file mode 100644 index 00000000..ec564e27 --- /dev/null +++ b/KarhooSDK/Service/Trip/TripUpdate/TripUpdateInteractor.swift @@ -0,0 +1,11 @@ +// +// TripUpdateInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol TripUpdateInteractor: KarhooExecutable { } diff --git a/KarhooSDK/Service/User/KarhooUserService.swift b/KarhooSDK/Service/User/KarhooUserService.swift new file mode 100644 index 00000000..454e8fcd --- /dev/null +++ b/KarhooSDK/Service/User/KarhooUserService.swift @@ -0,0 +1,85 @@ +// +// KarhooUserService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooUserService: UserService { + + private let userDataStore: UserDataStore + private let loginInteractor: LoginInteractor + private let registerInteractor: RegisterInteractor + private let passwordResetInteractor: PasswordResetInteractor + private let logoutInteractor: KarhooExecutable + private let updateUserDetailsInteractor: UpdaterUserDetailsInteractor + + init(userDataStore: UserDataStore = DefaultUserDataStore(), + loginInteractor: LoginInteractor = KarhooLoginInteractor(), + registerInteractor: RegisterInteractor = KarhooRegisterInteractor(), + passwordResetInteractor: PasswordResetInteractor = KarhooPasswordResetInteractor(), + logoutInteractor: KarhooExecutable = KarhooLogoutInteractor(), + updateUserDetailsInteractor: UpdaterUserDetailsInteractor = KarhooUpdateUserDetailsInteractor()) { + self.userDataStore = userDataStore + self.loginInteractor = loginInteractor + self.registerInteractor = registerInteractor + self.passwordResetInteractor = passwordResetInteractor + self.logoutInteractor = logoutInteractor + self.updateUserDetailsInteractor = updateUserDetailsInteractor + } + + public func login(userLogin: UserLogin) -> Call { + authenticationMethodSanityCheck() + loginInteractor.set(userLogin: userLogin) + return Call(executable: loginInteractor) + } + + public func logout() -> Call { + authenticationMethodSanityCheck() + return Call(executable: logoutInteractor) + } + + public func getCurrentUser() -> UserInfo? { + return userDataStore.getCurrentUser() + } + + func register(userRegistration: UserRegistration) -> Call { + authenticationMethodSanityCheck() + registerInteractor.set(userRegistration: userRegistration) + return Call(executable: registerInteractor) + } + + public func passwordReset(email: String) -> Call { + authenticationMethodSanityCheck() + passwordResetInteractor.set(email: email) + return Call(executable: passwordResetInteractor) + } + + public func updateUserDetails(update: UserDetailsUpdateRequest) -> Call { + authenticationMethodSanityCheck() + updateUserDetailsInteractor.set(update: update) + return Call(executable: updateUserDetailsInteractor) + } + + public func add(observer: UserStateObserver) { + userDataStore.add(observer: observer) + } + + public func remove(observer: UserStateObserver) { + userDataStore.remove(observer: observer) + } + + private func authenticationMethodSanityCheck() { + let error = "The AuthenticationMethod set in KarhooSDKConfiguration does not support this operation" + + switch Karhoo.configuration.authenticationMethod() { + case .tokenExchange(_:), .guest(_:): + fatalError(error) + default: + return + } + } +} diff --git a/KarhooSDK/Service/User/LoginInteractor/KarhooLoginInteractor.swift b/KarhooSDK/Service/User/LoginInteractor/KarhooLoginInteractor.swift new file mode 100644 index 00000000..cd6626c7 --- /dev/null +++ b/KarhooSDK/Service/User/LoginInteractor/KarhooLoginInteractor.swift @@ -0,0 +1,131 @@ +// +// KarhooLoginInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooLoginInteractor: LoginInteractor { + + private var userLogin: UserLogin? + + private let analytics: AnalyticsService + private let loginRequestSender: RequestSender + private let profileRequestSender: RequestSender + private let userDataStore: UserDataStore + private let nonceRequestSender: RequestSender + + private let authorizedUserRoles = ["TRIP_ADMIN", "MOBILE_USER"] + + init(userDataStore: UserDataStore = DefaultUserDataStore(), + loginRequestSender: RequestSender = KarhooRequestSender(httpClient: JsonHttpClient.shared), + profileRequestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared), + analytics: AnalyticsService = KarhooAnalyticsService(), + nonceRequestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared)) { + self.analytics = analytics + self.userDataStore = userDataStore + self.loginRequestSender = loginRequestSender + self.profileRequestSender = profileRequestSender + self.nonceRequestSender = nonceRequestSender + } + + func set(userLogin: UserLogin) { + self.userLogin = userLogin + } + + func execute(callback: @escaping CallbackClosure) { + guard let userLogin = self.userLogin else { + return + } + + if let user = userDataStore.getCurrentUser(), + let result = user as? T { + if user.email == userLogin.username { + callback(Result.success(result: result)) + return + } + callback(Result.failure(error: SDKErrorFactory.userAlreadyLoggedIn())) + return + } + + loginRequestSender.requestAndDecode(payload: userLogin, + endpoint: .login, + callback: { [weak self] (result: Result) in + guard let token = result.successValue( + orErrorCallback: callback) else { return } + self?.gotToken(token: token, callback: callback) + }) + } + + func cancel() { + loginRequestSender.cancelNetworkRequest() + profileRequestSender.cancelNetworkRequest() + } + + private func gotToken(token: AuthToken, + callback: @escaping CallbackClosure) { + let credentials = token.toCredentials() + userDataStore.set(credentials: credentials) + sendProfileRequest(callback: callback, credentials: credentials) + } + + private func sendProfileRequest(callback: @escaping CallbackClosure, + credentials: Credentials?) { + profileRequestSender.requestAndDecode(payload: nil, + endpoint: .userProfile, + callback: {[weak self] (result: Result) in + guard let user = result.successValue(orErrorCallback: callback) + else { + return + } + self?.handleProfileRequest(user: user, + callback: callback, + credentials: credentials) + }) + } + + private func handleProfileRequest(user: UserInfo, + callback: @escaping CallbackClosure, + credentials: Credentials?) { + if userIsAuthorized(user: user) { + didLogin(user: user, callback: callback, credentials: credentials) + } else { + callback(.failure(error: SDKErrorFactory.getLoginPermissionError())) + } + } + + private func userIsAuthorized(user: UserInfo) -> Bool { + return user.organisations.flatMap { $0.roles }.contains(where: { authorizedUserRoles.contains($0) }) + } + + private func didLogin(user: UserInfo, + callback: CallbackClosure, + credentials: Credentials?) { + guard let credentials = credentials else { + callback(.failure(error: SDKErrorFactory.unexpectedError())) + return + } + + guard let result = user as? T else { + return + } + + analytics.send(eventName: .userLoggedIn) + userDataStore.setCurrentUser(user: user, credentials: credentials) + updateUserNonce(user: user) + callback(.success(result: result)) + } + + private func updateUserNonce(user: UserInfo) { + let payload = NonceRequestPayload(payer: Payer(user: user), + organisationId: user.organisations.first?.id ?? "") + + nonceRequestSender.requestAndDecode(payload: payload, + endpoint: .getNonce) { [weak self] (result: Result) in + self?.userDataStore.updateCurrentUserNonce(nonce: result.successValue()) + } + } +} diff --git a/KarhooSDK/Service/User/LoginInteractor/LoginInteractor.swift b/KarhooSDK/Service/User/LoginInteractor/LoginInteractor.swift new file mode 100644 index 00000000..a4266560 --- /dev/null +++ b/KarhooSDK/Service/User/LoginInteractor/LoginInteractor.swift @@ -0,0 +1,13 @@ +// +// LoginInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol LoginInteractor: KarhooExecutable { + func set(userLogin: UserLogin) +} diff --git a/KarhooSDK/Service/User/LogoutInteractor/KarhooLogoutInteractor.swift b/KarhooSDK/Service/User/LogoutInteractor/KarhooLogoutInteractor.swift new file mode 100644 index 00000000..4ff07735 --- /dev/null +++ b/KarhooSDK/Service/User/LogoutInteractor/KarhooLogoutInteractor.swift @@ -0,0 +1,38 @@ +// +// KarhooLogoutInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooLogoutInteractor: KarhooExecutable { + private let userDataStore: UserDataStore + private let analytics: AnalyticsService + + init(userDataStore: UserDataStore = DefaultUserDataStore(), + analytics: AnalyticsService = KarhooAnalyticsService()) { + self.userDataStore = userDataStore + self.analytics = analytics + } + + func execute(callback: @escaping CallbackClosure) { + guard let _ = userDataStore.getCurrentUser() else { + callback(Result.failure(error: nil)) + return + } + + guard let result = KarhooVoid() as? T else { + return + } + + analytics.send(eventName: AnalyticsConstants.EventNames.userLoggedOut) + + userDataStore.removeCurrentUserAndCredentials() + callback(Result.success(result: result)) + } + + func cancel() { } +} diff --git a/KarhooSDK/Service/User/PasswordResetInteractor/KarhooPasswordResetInteractor.swift b/KarhooSDK/Service/User/PasswordResetInteractor/KarhooPasswordResetInteractor.swift new file mode 100644 index 00000000..62cbc5f5 --- /dev/null +++ b/KarhooSDK/Service/User/PasswordResetInteractor/KarhooPasswordResetInteractor.swift @@ -0,0 +1,40 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooPasswordResetInteractor: PasswordResetInteractor { + private var email: String? + private let requestSender: RequestSender + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: JsonHttpClient.shared)) { + self.requestSender = requestSender + } + + func set(email: String) { + self.email = email + } + + func execute(callback: @escaping CallbackClosure) { + guard let email = self.email else { + return + } + + requestSender.request(payload: PasswordResetRequestPayload(email: email), + endpoint: .passwordReset, + callback: { result in + guard result.successValue(orErrorCallback: callback) != nil, + let success = KarhooVoid() as? T else { + return + } + + callback(.success(result: success)) + }) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/User/PasswordResetInteractor/PasswordResetInteractor.swift b/KarhooSDK/Service/User/PasswordResetInteractor/PasswordResetInteractor.swift new file mode 100644 index 00000000..b6bcf343 --- /dev/null +++ b/KarhooSDK/Service/User/PasswordResetInteractor/PasswordResetInteractor.swift @@ -0,0 +1,13 @@ +// +// PasswordResetInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol PasswordResetInteractor: KarhooExecutable { + func set(email: String) +} diff --git a/KarhooSDK/Service/User/RegisterInteractor/KarhooRegisterInteractor.swift b/KarhooSDK/Service/User/RegisterInteractor/KarhooRegisterInteractor.swift new file mode 100644 index 00000000..ae57a97c --- /dev/null +++ b/KarhooSDK/Service/User/RegisterInteractor/KarhooRegisterInteractor.swift @@ -0,0 +1,37 @@ +// +// KarhooRegisterInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooRegisterInteractor: RegisterInteractor { + + private let requestSender: RequestSender + private var userRegistration: UserRegistration? + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: JsonHttpClient.shared)) { + self.requestSender = requestSender + } + + func set(userRegistration: UserRegistration) { + self.userRegistration = userRegistration + } + + func execute(callback: @escaping CallbackClosure) { + guard let userRegistration = self.userRegistration else { + return + } + + requestSender.requestAndDecode(payload: userRegistration, + endpoint: .register, + callback: callback) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/User/RegisterInteractor/RegisterInteractor.swift b/KarhooSDK/Service/User/RegisterInteractor/RegisterInteractor.swift new file mode 100644 index 00000000..f23b0ad0 --- /dev/null +++ b/KarhooSDK/Service/User/RegisterInteractor/RegisterInteractor.swift @@ -0,0 +1,13 @@ +// +// RegisterInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol RegisterInteractor: KarhooExecutable { + func set(userRegistration: UserRegistration) +} diff --git a/KarhooSDK/Service/User/UpdateUserDetailsInteractor/KarhooUpdateUserDetailsInteractor.swift b/KarhooSDK/Service/User/UpdateUserDetailsInteractor/KarhooUpdateUserDetailsInteractor.swift new file mode 100644 index 00000000..348222be --- /dev/null +++ b/KarhooSDK/Service/User/UpdateUserDetailsInteractor/KarhooUpdateUserDetailsInteractor.swift @@ -0,0 +1,61 @@ +// +// KarhooUpdateUserDetailsInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class KarhooUpdateUserDetailsInteractor: UpdaterUserDetailsInteractor { + + private var userDetailsUpdate: UserDetailsUpdateRequest? + private let requestSender: RequestSender + private let userDataStore: UserDataStore + private let analyticsService: AnalyticsService + + init(requestSender: RequestSender = KarhooRequestSender(httpClient: TokenRefreshingHttpClient.shared), + userDataStore: UserDataStore = DefaultUserDataStore(), + analyticsService: AnalyticsService = Karhoo.getAnalyticsService()) { + self.requestSender = requestSender + self.userDataStore = userDataStore + self.analyticsService = analyticsService + } + + func set(update: UserDetailsUpdateRequest) { + self.userDetailsUpdate = update + } + + func execute(callback: @escaping CallbackClosure) { + guard let userUpdateDetails = self.userDetailsUpdate else { + return + } + + guard let userId = userDataStore.getCurrentUser()?.userId else { + callback(Result.failure(error: SDKErrorFactory.getLoginPermissionError())) + return + } + + requestSender.requestAndDecode(payload: userUpdateDetails, + endpoint: APIEndpoint.userProfileUpdate(identifier: userId), + callback: { [weak self] (result: Result) in + switch result { + case .success(var user): + self?.userDataStore.updateUser(user: &user) + self?.analyticsService.send(eventName: .userProfileUpdateSuccess) + guard let result = user as? T else { + return + } + callback(.success(result: result)) + case .failure(let error): + self?.analyticsService.send(eventName: .userProfleUpdateFailed) + callback(.failure(error: error)) + } + }) + } + + func cancel() { + requestSender.cancelNetworkRequest() + } +} diff --git a/KarhooSDK/Service/User/UpdateUserDetailsInteractor/UpdateUserDetailsInteractor.swift b/KarhooSDK/Service/User/UpdateUserDetailsInteractor/UpdateUserDetailsInteractor.swift new file mode 100644 index 00000000..623c483e --- /dev/null +++ b/KarhooSDK/Service/User/UpdateUserDetailsInteractor/UpdateUserDetailsInteractor.swift @@ -0,0 +1,13 @@ +// +// UpdateUserDetailsInteractor.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +protocol UpdaterUserDetailsInteractor: KarhooExecutable { + func set(update: UserDetailsUpdateRequest) +} diff --git a/KarhooSDK/Service/User/UserService.swift b/KarhooSDK/Service/User/UserService.swift new file mode 100644 index 00000000..788a34c9 --- /dev/null +++ b/KarhooSDK/Service/User/UserService.swift @@ -0,0 +1,26 @@ +// +// UserService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +public protocol UserStateObserver: class { + func userStateUpdated(user: UserInfo?) +} + +public protocol UserService { + + func login(userLogin: UserLogin) -> Call + func logout() -> Call + func getCurrentUser() -> UserInfo? + func passwordReset(email: String) -> Call + func updateUserDetails(update: UserDetailsUpdateRequest) -> Call + func register(userRegistration: UserRegistration) -> Call + func add(observer: UserStateObserver) + func remove(observer: UserStateObserver) + +} diff --git a/KarhooSDK1.0.1.framework.zip b/KarhooSDK1.0.1.framework.zip new file mode 100644 index 00000000..a36a7246 Binary files /dev/null and b/KarhooSDK1.0.1.framework.zip differ diff --git a/KarhooSDKIntegrationTests/Error/GeneralErrorSpec.swift b/KarhooSDKIntegrationTests/Error/GeneralErrorSpec.swift new file mode 100644 index 00000000..0c4f6c69 --- /dev/null +++ b/KarhooSDKIntegrationTests/Error/GeneralErrorSpec.swift @@ -0,0 +1,62 @@ +// +// GeneralErrorSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +import KarhooSDK + +final class GeneralErrorSpec: XCTestCase { + + // any service + private var tripService: TripService! + private let path = "/v1/bookings/123/cancel" + private var call: Call! + + override func setUp() { + super.setUp() + + self.tripService = Karhoo.getTripService() + self.call = tripService.cancel(tripCancellation: TripCancellation(tripId: "123", + cancelReason: .notNeededAnymore)) + } + + /** + * When: Executing a karhoo call + * And: The response returns an unparsable error + * Then: Error should be an unkown error type + */ + func testErrorResponseWithInvalidData() { + NetworkStub.responseWithInvalidData(path: path, statusCode: 400) + + let expectation = self.expectation(description: "calls the callback with an error") + + call.execute(callback: { result in + XCTAssertEqual(result.errorValue()?.type, .unknownError) + expectation.fulfill() + }) + + self.waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Executing a karhoo call + * And: No internet connection + * Then: Resulting error value should not be nil + */ + func testErrorResponseNoInternetConnection() { + NetworkStub.errorResponseNoNetworkConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with an error") + + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + expectation.fulfill() + }) + + self.waitForExpectations(timeout: 0.5, handler: .none) + } +} diff --git a/KarhooSDKIntegrationTests/Info.plist b/KarhooSDKIntegrationTests/Info.plist new file mode 100644 index 00000000..adea5b9d --- /dev/null +++ b/KarhooSDKIntegrationTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + NSPrincipalClass + KarhooSDKIntegrationTests.IntegrationTestSetup + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0.5 + CFBundleVersion + 12 + + diff --git a/KarhooSDKIntegrationTests/IntegrationTestSetup.swift b/KarhooSDKIntegrationTests/IntegrationTestSetup.swift new file mode 100644 index 00000000..905c4103 --- /dev/null +++ b/KarhooSDKIntegrationTests/IntegrationTestSetup.swift @@ -0,0 +1,17 @@ +// +// IntegrationTestSetup.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +class IntegrationTestSetup: NSObject { + + override init() { + Karhoo.set(configuration: MockSDKConfig()) + } +} diff --git a/KarhooSDKIntegrationTests/JSON/AddressService/LocationInfo.json b/KarhooSDKIntegrationTests/JSON/AddressService/LocationInfo.json new file mode 100644 index 00000000..586de8e1 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/AddressService/LocationInfo.json @@ -0,0 +1,28 @@ +{ + "position": {"latitude": 51.5166744, "longitude": -0.1769328}, + "place_id": "123", + "poi_type": "REGULATED", + "address": { + "display_address": "Paddington Station", + "line_1": "", + "line_2": "", + "building_number": "", + "street_name": "Praed St", + "city": "London", + "postal_code": "W2 1HQ", + "region": "Greater London", + "country_code": "UK" + }, + "time_zone": "UK", + "details": { + "iata": "iata", + "terminal": "terminal", + "type": "NOT_SET_DETAILS_TYPE", + }, + "meeting_point": { + "position": {"latitude": 51.5062894, "longitude": -0.0859324}, + "instructions": "I am near by", + "type": "CURB_SIDE", + }, + "instructions": "Waiting" +} diff --git a/KarhooSDKIntegrationTests/JSON/AddressService/Places.json b/KarhooSDKIntegrationTests/JSON/AddressService/Places.json new file mode 100644 index 00000000..5acb7155 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/AddressService/Places.json @@ -0,0 +1,14 @@ +{ + "locations": [ + { + "place_id": "ChIJ3QD47p1xdkgRytPI0DjT6SU", + "display_address": "Terminal 5, Longford, Hounslow, UK", + "type": "AIRPORT" + }, + { + "place_id": "12356344f35gergedr", + "display_address": "Johns house", + "type": "NOT_SET_DETAILS_TYPE" + } + ] +} diff --git a/KarhooSDKIntegrationTests/JSON/Auth/AuthExchangeToken.json b/KarhooSDKIntegrationTests/JSON/Auth/AuthExchangeToken.json new file mode 100644 index 00000000..4fe47126 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Auth/AuthExchangeToken.json @@ -0,0 +1,9 @@ +{ + "id_token": "asdhaskdjhaksdh1212", + "token_type": "bearer", + "access_token": "asdhkasjndkasn1o2j312i", + "refresh_token": "aksjdnaskdjn000-12312-asdasd", + "refresh_expires_in": 31622398, + "scope": "openid profile email phone", + "expires_in": 1798 +} diff --git a/KarhooSDKIntegrationTests/JSON/Auth/AuthUserInfo.json b/KarhooSDKIntegrationTests/JSON/Auth/AuthUserInfo.json new file mode 100644 index 00000000..0597ceec --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Auth/AuthUserInfo.json @@ -0,0 +1,26 @@ +{ + "sub":"zzzz-zzwww123-asda-xxx6", + "upstream":{ + "sub":"123123123-43434-1231-88" + }, + "primary_organisation_id":"12312312-adas-aasxas-adasd12312-3", + "last_name":"TestName", + "given_name":"TestLasName", + "client_id":"mobile-testClient", + "organisations":[ + { + "id":"12312-asdasd_ASDASD-asd", + "name":"DefaultOrgForTestUsers", + "roles":[ + "ROLE_1", + "ROLE_2" + ] + } + ], + "user_id":"23123210ADAS-ZZZZAA-Axx-1", + "scope":"test scope", + "phone_number":"+15005550006", + "family_name":"Family", + "first_name":"Fist_Name", + "email":"email@karhoo.com" +} diff --git a/KarhooSDKIntegrationTests/JSON/DriverTracking/DriverTracking.json b/KarhooSDKIntegrationTests/JSON/DriverTracking/DriverTracking.json new file mode 100644 index 00000000..c2a7a3ce --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/DriverTracking/DriverTracking.json @@ -0,0 +1,12 @@ +{ + "position": { + "latitude": 1, + "longitude": -1 + }, + "direction": { + "kph": 30, + "heading": 180 + }, + "origin_eta": 5, + "destination_eta": 10 +} diff --git a/KarhooSDKIntegrationTests/JSON/Empty.json b/KarhooSDKIntegrationTests/JSON/Empty.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Empty.json @@ -0,0 +1 @@ +{} diff --git a/KarhooSDKIntegrationTests/JSON/Error.json b/KarhooSDKIntegrationTests/JSON/Error.json new file mode 100644 index 00000000..c6557779 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Error.json @@ -0,0 +1,4 @@ +{ + "code": "1234", + "message": "Error message", +} diff --git a/KarhooSDKIntegrationTests/JSON/Fare/Fare.json b/KarhooSDKIntegrationTests/JSON/Fare/Fare.json new file mode 100644 index 00000000..75432b4c --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Fare/Fare.json @@ -0,0 +1,45 @@ +{ + "state": "PENDING", + "expected_final_time": "2016-04-16T16:06:05Z", + "expected_in": "360", + "breakdown": { + "base": { + "tax_percentage": 0, + "discount_percentage": 0, + "commission_percentage": 0, + "tax_amount": 0, + "list_price": 0, + "net_price": 0 + }, + "commission": { + "tax_percentage": 0, + "discount_percentage": 0, + "commission_percentage": 0, + "tax_amount": 0, + "list_price": 0, + "net_price": 0 + }, + "extras": [ + { + "tax_percentage": 0, + "discount_percentage": 0, + "commission_percentage": 0, + "tax_amount": 0, + "list_price": 0, + "net_price": 0 + } + ], + "expenses": [ + { + "tax_percentage": 0, + "discount_percentage": 0, + "commission_percentage": 0, + "tax_amount": 0, + "list_price": 0, + "net_price": 0 + } + ], + "total": 0, + "currency": "EUR" + } +} diff --git a/KarhooSDKIntegrationTests/JSON/InvalidData.json b/KarhooSDKIntegrationTests/JSON/InvalidData.json new file mode 100644 index 00000000..50c8760d --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/InvalidData.json @@ -0,0 +1,3 @@ +{ + "invalid": "data" +} diff --git a/KarhooSDKIntegrationTests/JSON/InvalidJson.json b/KarhooSDKIntegrationTests/JSON/InvalidJson.json new file mode 100644 index 00000000..7c9a8e00 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/InvalidJson.json @@ -0,0 +1,4 @@ +{ + "invalid": json + "hello": invalid +} diff --git a/KarhooSDKIntegrationTests/JSON/Payment/AddPaymentDetails/AddPaymentDetails.json b/KarhooSDKIntegrationTests/JSON/Payment/AddPaymentDetails/AddPaymentDetails.json new file mode 100644 index 00000000..a3ac7cb0 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Payment/AddPaymentDetails/AddPaymentDetails.json @@ -0,0 +1,5 @@ +{ + "nonce": "some_nonce", + "card_type": "Visa", + "last_four": "1111" +} diff --git a/KarhooSDKIntegrationTests/JSON/Payment/GetNonce/GetNonce.json b/KarhooSDKIntegrationTests/JSON/Payment/GetNonce/GetNonce.json new file mode 100644 index 00000000..a3ac7cb0 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Payment/GetNonce/GetNonce.json @@ -0,0 +1,5 @@ +{ + "nonce": "some_nonce", + "card_type": "Visa", + "last_four": "1111" +} diff --git a/KarhooSDKIntegrationTests/JSON/Payment/PaymentSDKToken/PaymentSDKToken.json b/KarhooSDKIntegrationTests/JSON/Payment/PaymentSDKToken/PaymentSDKToken.json new file mode 100644 index 00000000..986f5699 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Payment/PaymentSDKToken/PaymentSDKToken.json @@ -0,0 +1,3 @@ +{ + "token": "some_sdk_token" +} diff --git a/KarhooSDKIntegrationTests/JSON/Quote/Availability.json b/KarhooSDKIntegrationTests/JSON/Quote/Availability.json new file mode 100644 index 00000000..fac698a1 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Quote/Availability.json @@ -0,0 +1,166 @@ +{ + "availabilities": [{ + "availability_id": "Yzk5ZjRmMTAtNDQ1Yy00ZDc0LWIzY2ItNDliYmQyMjExMTQ5O3NhbG9vbg==", + "category_name": "Saloon", + "description": "", + "fleet_id": "c99f4f10-445c-4d74-b3cb-49bbd2211149", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/e47b52b-fdb7-4736-becb-5c64f8470a95.png", + "phone_number": "+447715424075", + "rating": 0, + "supplier_name": "Autocab_TEST", + "terms_conditions_url": "https://karhoo.com/fleettcs/c99f4f10-445c-4d74-b3cb-49bbd2211149", + "vehicle_class": "saloon" + }, { + "availability_id": "MjczODZlY2ItNTNhZi00NThkLWE2ODQtZDk3ZTQ4ZTBiN2EzO3NhbG9vbg==", + "category_name": "Saloon", + "description": "", + "fleet_id": "27386ecb-53af-458d-a684-d97e48e0b7a3", + "integrated_fleet": true, + "logo_url": "", + "phone_number": "+447760222330", + "rating": 0, + "supplier_name": "Magenta [Sandbox]", + "terms_conditions_url": "", + "vehicle_class": "saloon" + }, { + "availability_id": "MjczODZlY2ItNTNhZi00NThkLWE2ODQtZDk3ZTQ4ZTBiN2EzO2V4ZWN1dGl2ZQ==", + "category_name": "Exec", + "description": "", + "fleet_id": "27386ecb-53af-458d-a684-d97e48e0b7a3", + "integrated_fleet": true, + "logo_url": "", + "phone_number": "+447760222330", + "rating": 0, + "supplier_name": "Magenta [Sandbox]", + "terms_conditions_url": "", + "vehicle_class": "executive" + }, { + "availability_id": "NTIxMjNiZDktY2M5OC00YjhkLWE5OGEtMTIyNDQ2ZDY5ZTc5O2VsZWN0cmlj", + "category_name": "Electric", + "description": "", + "fleet_id": "52123bd9-cc98-4b8d-a98a-122446d69e79", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/52123bd9-cc98-4b8d-a98a-122446d69e79.png", + "phone_number": "+447904839920", + "rating": 0, + "supplier_name": "iCabbi [Sandbox]", + "terms_conditions_url": "https://cdn.karhoo.com/d/images/logos/52123bd9-cc98-4b8d-a98a-122446d69e79", + "vehicle_class": "electric" + }, { + "availability_id": "Y2M3NzVlZGEtOTUwZC00YTc3LWFhODMtMTcyZDQ4N2E0Y2JmO2V4ZWN1dGl2ZQ==", + "category_name": "Exec", + "description": "", + "fleet_id": "cc775eda-950d-4a77-aa83-172d487a4cbf", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/cc775eda-950d-4a77-aa83-172d487a4cbf.png", + "phone_number": "+447760222330", + "rating": 0, + "supplier_name": "Antelope [Sandbox]", + "terms_conditions_url": "https://karhoo.com/karhootcs/", + "vehicle_class": "executive" + }, { + "availability_id": "Y2M3NzVlZGEtOTUwZC00YTc3LWFhODMtMTcyZDQ4N2E0Y2JmO21vdG9yY3ljbGU=", + "category_name": "Moto", + "description": "", + "fleet_id": "cc775eda-950d-4a77-aa83-172d487a4cbf", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/cc775eda-950d-4a77-aa83-172d487a4cbf.png", + "phone_number": "+447760222330", + "rating": 0, + "supplier_name": "Antelope [Sandbox]", + "terms_conditions_url": "https://karhoo.com/karhootcs/", + "vehicle_class": "motorcycle" + }, { + "availability_id": "NDk2ZTQ0NTktZTcxMC00YWYyLTk3NzktMWI2M2Q1NGNmOGRlO3NhbG9vbg==", + "category_name": "Saloon", + "description": "", + "fleet_id": "496e4459-e710-4af2-9779-1b63d54cf8de", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/e47b52b-fdb7-4736-becb-5c64f8470a95.png", + "phone_number": "+447365298748585", + "rating": 0, + "supplier_name": "AutoCab", + "terms_conditions_url": "https://karhoo.com/fleettcs/496e4459-e710-4af2-9779-1b63d54cf8de", + "vehicle_class": "saloon" + }, { + "availability_id": "MjczODZlY2ItNTNhZi00NThkLWE2ODQtZDk3ZTQ4ZTBiN2EzO21wdg==", + "category_name": "MPV", + "description": "", + "fleet_id": "27386ecb-53af-458d-a684-d97e48e0b7a3", + "integrated_fleet": true, + "logo_url": "", + "phone_number": "+447760222330", + "rating": 0, + "supplier_name": "Magenta [Sandbox]", + "terms_conditions_url": "", + "vehicle_class": "mpv" + }, { + "availability_id": "NTIxMjNiZDktY2M5OC00YjhkLWE5OGEtMTIyNDQ2ZDY5ZTc5O3NhbG9vbg==", + "category_name": "Saloon", + "description": "", + "fleet_id": "52123bd9-cc98-4b8d-a98a-122446d69e79", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/52123bd9-cc98-4b8d-a98a-122446d69e79.png", + "phone_number": "+447904839920", + "rating": 0, + "supplier_name": "iCabbi [Sandbox]", + "terms_conditions_url": "https://cdn.karhoo.com/d/images/logos/52123bd9-cc98-4b8d-a98a-122446d69e79", + "vehicle_class": "saloon" + }, { + "availability_id": "NTIxMjNiZDktY2M5OC00YjhkLWE5OGEtMTIyNDQ2ZDY5ZTc5O21wdg==", + "category_name": "MPV", + "description": "", + "fleet_id": "52123bd9-cc98-4b8d-a98a-122446d69e79", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/52123bd9-cc98-4b8d-a98a-122446d69e79.png", + "phone_number": "+447904839920", + "rating": 0, + "supplier_name": "iCabbi [Sandbox]", + "terms_conditions_url": "https://cdn.karhoo.com/d/images/logos/52123bd9-cc98-4b8d-a98a-122446d69e79", + "vehicle_class": "mpv" + }, { + "availability_id": "Y2M3NzVlZGEtOTUwZC00YTc3LWFhODMtMTcyZDQ4N2E0Y2JmO2VsZWN0cmlj", + "category_name": "Electric", + "description": "", + "fleet_id": "cc775eda-950d-4a77-aa83-172d487a4cbf", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/cc775eda-950d-4a77-aa83-172d487a4cbf.png", + "phone_number": "+447760222330", + "rating": 0, + "supplier_name": "Antelope [Sandbox]", + "terms_conditions_url": "https://karhoo.com/karhootcs/", + "vehicle_class": "electric" + }, { + "availability_id": "Y2M3NzVlZGEtOTUwZC00YTc3LWFhODMtMTcyZDQ4N2E0Y2JmO3NhbG9vbg==", + "category_name": "Saloon", + "description": "", + "fleet_id": "cc775eda-950d-4a77-aa83-172d487a4cbf", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/cc775eda-950d-4a77-aa83-172d487a4cbf.png", + "phone_number": "+447760222330", + "rating": 0, + "supplier_name": "Antelope [Sandbox]", + "terms_conditions_url": "https://karhoo.com/karhootcs/", + "vehicle_class": "saloon" + }, { + "availability_id": "Y2M3NzVlZGEtOTUwZC00YTc3LWFhODMtMTcyZDQ4N2E0Y2JmO3RheGk=", + "category_name": "Taxi", + "description": "", + "fleet_id": "cc775eda-950d-4a77-aa83-172d487a4cbf", + "integrated_fleet": true, + "logo_url": "https://cdn.karhoo.com/d/images/logos/cc775eda-950d-4a77-aa83-172d487a4cbf.png", + "phone_number": "+447760222330", + "rating": 0, + "supplier_name": "Antelope [Sandbox]", + "terms_conditions_url": "https://karhoo.com/karhootcs/", + "vehicle_class": "taxi" + }], + "categories": [ + "Saloon", + "Taxi", + "MPV", + "Exec", + "Electric", + "Moto"] +} \ No newline at end of file diff --git a/KarhooSDKIntegrationTests/JSON/Quote/QuoteListId.json b/KarhooSDKIntegrationTests/JSON/Quote/QuoteListId.json new file mode 100644 index 00000000..90412ddc --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Quote/QuoteListId.json @@ -0,0 +1,4 @@ +{ + "id": "some-id", + "validity": 300 +} diff --git a/KarhooSDKIntegrationTests/JSON/Quote/Quotes.json b/KarhooSDKIntegrationTests/JSON/Quote/Quotes.json new file mode 100644 index 00000000..f1f787df --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Quote/Quotes.json @@ -0,0 +1,31 @@ +{ + "id": "4129e51a-bc10-11e8-a821-0a580a0414db", + "quote_items": [{ + "availability_id": "NTIxMjNiZDktY2M5OC00YjhkLWE5OGEtMTIyNDQ2ZDY5ZTc5O3NhbG9vbg==", + "category_name": "Saloon", + "pick_up_type": "MEET_AND_GREET", + "currency_code": "GBP", + "fleet_id": "someFleetId", + "fleet_name": "someFleetName", + "high_price": 779, + "low_price": 778, + "phone_number": "+123", + "qta_high_minutes": 2, + "qta_low_minutes": 1, + "quote_id": "someQuoteId", + "quote_type": "ESTIMATED", + "supplier_logo_url": "someLogoUrl", + "terms_conditions_url": "someTermsUrl", + "vehicle_attributes": { + "child_seat": false, + "electric": false, + "hybrid": false, + "luggage_capacity": 2, + "passenger_capacity": 4 + }, + "vehicle_class": "saloon", + "source": "MARKET" + }], + "status": "COMPLETED", + "validity": 221 +} diff --git a/KarhooSDKIntegrationTests/JSON/Trip/TripInfo.json b/KarhooSDKIntegrationTests/JSON/Trip/TripInfo.json new file mode 100644 index 00000000..aaeb664a --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Trip/TripInfo.json @@ -0,0 +1,130 @@ +{ + "id": "b6a5f9dc-9066-4252-9013-be85dfa563bc", + "passengers": { + "additional_passengers": 0, + "passenger_details": [ + { + "first_name": "John", + "last_name": "Smith", + "email": "john.smith@karhoo.com", + "phone_number": "+4412345678" + } + ], + "luggage": { + "total": 0 + } + }, + "status": "REQUESTED", + "origin": { + "display_address": "2 Eastborne Terrace", + "position": { + "latitude": 51.5086692, + "longitude": -0.1375291 + }, + "place_id": "EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", + "poi_type": "NOT_SET_POI_TYPE", + "timezone": "london/europe" + }, + "destination": { + "display_address": "2 Eastborne Terrace", + "position": { + "latitude": 51.5086692, + "longitude": -0.1375291 + }, + "place_id": "EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", + "poi_type": "NOT_SET_POI_TYPE", + "timezone": "london/europe" + }, + "date_scheduled": "2018-04-21T12:35:00Z", + "quote": { + "type": "FIXED", + "total": 3550, + "currency": "GBP", + "gratuity_percent": 15, + "breakdown": [ + { + "value": 1030, + "name": "Basic amount", + "description": "Basic amount before any taxes or extras" + } + ], + "vehicle_class": "Saloon", + "qta_high_minutes": 8, + "qta_low_minutes": 5, + "vehicle_attributes": { + "passenger_capacity": 3, + "luggage_capacity": 2, + "hybrid": false, + "electric": true, + "child_seat": true + } + }, + "fare": { + "total": 3550, + "currency": "GBP", + "gratuity_percent": 15, + "breakdown": [ + { + "value": 1030, + "name": "Basic amount", + "description": "Basic amount before any taxes or extras" + } + ] + }, + "external_trip_id": "string", + "display_trip_id": "A5TH-R27D", + "fleet_info": { + "fleet_id": "some fleet id", + "name": "some fleet name", + "logo_url": "some logo url", + "description": "some description", + "phone_number": "some phone number", + "terms_conditions_url": "some terms and conditions", + "email": "dispatch-co@karhoo.com" + }, + "vehicle": { + "vehicle_class": "MPV", + "description": "Renault Scenic (Black)", + "vehicle_license_plate": "123 XYZ", + "driver": { + "first_name": "Michael", + "last_name": "Higgins", + "phone_number": "+441111111111", + "photo_url": "https://karhoo.com/drivers/mydriver.png", + "license_number": "ZXZ151YTY" + }, + "attributes": { + "passenger_capacity": 3, + "luggage_capacity": 2, + "hybrid": false, + "electric": true, + "child_seat": true + } + }, + "partner_trip_id": "9975d6f2-75e0-463a-85f8-d6b17d2b0143", + "comments": "They are waiting by the green door at Fake Street 2a", + "flight_number": "BA1326", + "date_booked": "string", + "meeting_point": { + "position": { + "latitude": 51.5086692, + "longitude": -0.1375291 + }, + "type": "MEET_AND_GREET", + "instructions": "string", + "note": "string" + }, + "agent": { + "user_id": "string", + "user_name": "string", + "organisation_id": "string", + "organisation_name": "string" + }, + "cost_center_reference": "EMEA-sales", + "cancelled_by": { + "first_name": "string", + "last_name": "string", + "id": "string", + "email": "string" + } +} diff --git a/KarhooSDKIntegrationTests/JSON/Trip/TripSearch.json b/KarhooSDKIntegrationTests/JSON/Trip/TripSearch.json new file mode 100644 index 00000000..7d23358f --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Trip/TripSearch.json @@ -0,0 +1,134 @@ +{ + "bookings": [ + { + "id": "b6a5f9dc-9066-4252-9013-be85dfa563bc", + "passengers": { + "additional_passengers": 0, + "passenger_details": [ + { + "first_name": "John", + "last_name": "Smith", + "email": "john.smith@karhoo.com", + "phone_number": "+4412345678" + } + ], + "luggage": { + "total": 0 + } + }, + "status": "REQUESTED", + "origin": { + "display_address": "2 Eastborne Terrace", + "position": { + "latitude": 51.5086692, + "longitude": -0.1375291 + }, + "place_id": "EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", + "poi_type": "NOT_SET_POI_TYPE", + "timezone": "london/europe" + }, + "destination": { + "display_address": "2 Eastborne Terrace", + "position": { + "latitude": 51.5086692, + "longitude": -0.1375291 + }, + "place_id": "EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", + "poi_type": "NOT_SET_POI_TYPE", + "timezone": "london/europe" + }, + "date_scheduled": "2018-04-21T12:35:00Z", + "quote": { + "type": "FIXED", + "total": 3550, + "currency": "GBP", + "gratuity_percent": 15, + "breakdown": [ + { + "value": 1030, + "name": "Basic amount", + "description": "Basic amount before any taxes or extras" + } + ], + "vehicle_class": "Saloon", + "qta_high_minutes": 8, + "qta_low_minutes": 5, + "vehicle_attributes": { + "passenger_capacity": 3, + "luggage_capacity": 2, + "hybrid": false, + "electric": true, + "child_seat": true + } + }, + "fare": { + "total": 3550, + "currency": "GBP", + "gratuity_percent": 15, + "breakdown": [ + { + "value": 1030, + "name": "Basic amount", + "description": "Basic amount before any taxes or extras" + } + ] + }, + "external_trip_id": "string", + "display_trip_id": "A5TH-R27D", + "fleet_info": { + "fleet_id": "some fleet id", + "name": "some fleet name", + "logo_url": "some logo url", + "description": "some description", + "phone_number": "some phone number", + "terms_conditions_url": "some terms and conditions", + "email": "dispatch-co@karhoo.com" + }, + "vehicle": { + "vehicle_class": "MPV", + "description": "Renault Scenic (Black)", + "vehicle_license_plate": "123 XYZ", + "driver": { + "first_name": "Michael", + "last_name": "Higgins", + "phone_number": "+441111111111", + "photo_url": "https://karhoo.com/drivers/mydriver.png", + "license_number": "ZXZ151YTY" + }, + "attributes": { + "passenger_capacity": 3, + "luggage_capacity": 2, + "hybrid": false, + "electric": true, + "child_seat": true + } + }, + "partner_trip_id": "9975d6f2-75e0-463a-85f8-d6b17d2b0143", + "comments": "They are waiting by the green door at Fake Street 2a", + "flight_number": "BA1326", + "date_booked": "string", + "meeting_point": { + "position": { + "latitude": 51.5086692, + "longitude": -0.1375291 + }, + "type": "NOT_SET", + "instructions": "string", + "note": "string" + }, + "agent": { + "user_id": "string", + "user_name": "string", + "organisation_id": "string", + "organisation_name": "string" + }, + "cost_center_reference": "EMEA-sales", + "cancelled_by": { + "first_name": "string", + "last_name": "string", + "id": "string", + "email": "string" + } +} + ] +} \ No newline at end of file diff --git a/KarhooSDKIntegrationTests/JSON/Trip/TripStatus.json b/KarhooSDKIntegrationTests/JSON/Trip/TripStatus.json new file mode 100644 index 00000000..a8140e1b --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/Trip/TripStatus.json @@ -0,0 +1,8 @@ +{ + "status": "POB", + "position": { + "latitude": 1, + "longitude": -1 + }, + "timestamp": "2018-09-13T11:26:21Z" +} \ No newline at end of file diff --git a/KarhooSDKIntegrationTests/JSON/User/AuthToken.json b/KarhooSDKIntegrationTests/JSON/User/AuthToken.json new file mode 100644 index 00000000..d3d8b7ce --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/User/AuthToken.json @@ -0,0 +1,5 @@ +{ + "access_token": "eyJz93a...k4laUWw", + "expires_in": 86400, + "refresh_token": "sajkqoweio...iuoiuoqwe" +} \ No newline at end of file diff --git a/KarhooSDKIntegrationTests/JSON/User/AuthorisedUserInfo.json b/KarhooSDKIntegrationTests/JSON/User/AuthorisedUserInfo.json new file mode 100644 index 00000000..b09c42c2 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/User/AuthorisedUserInfo.json @@ -0,0 +1,18 @@ +{ + "user_id": "some_userId", + "email": "some@email.com", + "first_name": "Some", + "last_name": "SomeSome", + "phone_number": "12345678910", + "locale": "string", + "primary_organisation_id": "string", + "organisations": [ + { + "id": "a1013897-132a-456c-9be2-636979095ad9", + "name": "string", + "roles": [ + "TRIP_ADMIN" + ] + } + ] +} diff --git a/KarhooSDKIntegrationTests/JSON/User/UnauthorisedUserInfo.json b/KarhooSDKIntegrationTests/JSON/User/UnauthorisedUserInfo.json new file mode 100644 index 00000000..ba5bc73f --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/User/UnauthorisedUserInfo.json @@ -0,0 +1,18 @@ +{ + "user_id": "string", + "email": "string", + "first_name": "string", + "last_name": "string", + "phone_number": "string", + "locale": "string", + "primary_organisation_id": "string", + "organisations": [ + { + "id": "string", + "name": "string", + "roles": [ + "string" + ] + } + ] +} \ No newline at end of file diff --git a/KarhooSDKIntegrationTests/JSON/User/UpdateUserRequest.json b/KarhooSDKIntegrationTests/JSON/User/UpdateUserRequest.json new file mode 100644 index 00000000..45205117 --- /dev/null +++ b/KarhooSDKIntegrationTests/JSON/User/UpdateUserRequest.json @@ -0,0 +1,19 @@ +{ + "user_id": "string", + "email": "string", + "first_name": "string", + "last_name": "string", + "phone_number": "string", + "locale": "string", + "primary_organisation_id": "string", + "organisations": [ + { + "id": "string", + "name": "string", + "roles": [ + "string" + ] + } + ], + "avatar_url": "http://example.com" +} diff --git a/KarhooSDKIntegrationTests/Service/Address/LocationInfoMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Address/LocationInfoMethodSpec.swift new file mode 100644 index 00000000..556c35a7 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Address/LocationInfoMethodSpec.swift @@ -0,0 +1,142 @@ +// +// LocationInfoMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +import KarhooSDK + +final class LocationInfoMethodSpec: XCTestCase { + + private var addressService: AddressService! + private let path = "/v1/locations/place-details" + private var call: Call! + + override func setUp() { + super.setUp() + + self.addressService = Karhoo.getAddressService() + self.call = addressService.locationInfo(locationInfoSearch: LocationInfoSearch(placeId: "123", + sessionToken: "123")) + } + + /** + * When: Requesting location info + * And: The response returns location info json object + * Then: Result is success and returns LocationInfo object + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "LocationInfo.json", path: path) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { [weak self] result in + XCTAssertTrue(result.isSuccess()) + self?.assertLocationInfo(info: result.successValue()!) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Requesting location info + * And: The response returns an error + * Then: Result is error + */ + func testErrorResponse() { + NetworkStub.errorResponse(path: path, responseData: RawKarhooErrorFactory.buildError(code: "K2001")) + + let expectation = self.expectation(description: "calls the callback with error") + + call.execute(callback: { result in + let error = result.errorValue() + XCTAssertEqual(error?.type, .couldNotGetAddress) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Requesting location info + * And: The response returns empty json object + * Then: Result is success with empty LocationInfo object + */ + func testEmptyResponse() { + NetworkStub.emptySuccessResponse(path: path) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Requesting location info + * And: The response returns invalid json object + * Then: Result is error + */ + func testInvalidResponse() { + NetworkStub.responseWithInvalidJson(path: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with success") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Requesting location info + * And: The response returns time out error + * Then: Result is error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + private func assertLocationInfo(info: LocationInfo) { + XCTAssertEqual(51.5166744, info.position.latitude) + XCTAssertEqual(-0.1769328, info.position.longitude) + XCTAssertEqual("123", info.placeId) + XCTAssertEqual(.regulated, info.poiType) + let address = LocationInfoAddress(displayAddress: "Paddington Station", + lineOne: "", + lineTwo: "", + buildingNumber: "", + streetName: "Praed St", + city: "London", + postalCode: "W2 1HQ", + countryCode: "UK", + region: "Greater London") + XCTAssert(address.equals(info.address)) + XCTAssertEqual("UK", info.timeZoneIdentifier) + XCTAssertEqual("iata", info.details.iata) + XCTAssertEqual("terminal", info.details.terminal) + XCTAssertEqual(.notSetDetailsType, info.details.type) + XCTAssertEqual(51.5062894, info.meetingPoint.position.latitude) + XCTAssertEqual(-0.0859324, info.meetingPoint.position.longitude) + XCTAssertEqual("I am near by", info.meetingPoint.instructions) + XCTAssertEqual(.curbSide, info.meetingPoint.type) + XCTAssertEqual("Waiting", info.instructions) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Address/PlaceSearchMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Address/PlaceSearchMethodSpec.swift new file mode 100644 index 00000000..ea558f45 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Address/PlaceSearchMethodSpec.swift @@ -0,0 +1,133 @@ +// +// PlaceSearchMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class PlaceSearchMethodSpec: XCTestCase { + + private var addressService: AddressService! + private let path = "/v1/locations/address-autocomplete" + private var call: Call! + + override func setUp() { + super.setUp() + + addressService = Karhoo.getAddressService() + call = addressService.placeSearch(placeSearch: PlaceSearch(position: Position(latitude: 1, longitude: 1), + query: "123", + sessionToken: "1234")) + } + + /** + * Given: Searching for a place + * When: The response returns expected result + * Then: Result should be success with expected result + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "Places.json", path: path) + + let expectation = self.expectation(description: "Calls calback with success result") + + call.execute(callback: { result in + self.assertSuccess(result: result) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for a place + * When: The response returns error + * Then: Result should be a fail with expected error type + */ + func testErrorResponse() { + NetworkStub.errorResponse(path: path, responseData: RawKarhooErrorFactory.buildError(code: "K2002")) + + let expectation = self.expectation(description: "Calls calback with error result") + + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.couldNotAutocompleteAddress, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for a place + * And: The response returns empty json object + * Then: Result is an unknownError + */ + func testEmptyResponse() { + NetworkStub.emptySuccessResponse(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * Given: Searching for a place + * And: The response returns invalid json object + * Then: Result is an unknownError + */ + func testInvalidResponse() { + NetworkStub.responseWithInvalidJson(path: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Searching for a place + * And: The response returns time out error + * Then: Result is error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + private func assertSuccess(result: Result) { + XCTAssertTrue(result.isSuccess()) + + let firstPlace = result.successValue()!.places[0] + let secondPlace = result.successValue()!.places[1] + + XCTAssertEqual("ChIJ3QD47p1xdkgRytPI0DjT6SU", firstPlace.placeId) + XCTAssertEqual("Terminal 5, Longford, Hounslow, UK", firstPlace.displayAddress) + XCTAssertEqual(PoiDetailsType.airport, firstPlace.poiDetailsType) + + XCTAssertEqual("12356344f35gergedr", secondPlace.placeId) + XCTAssertEqual("Johns house", secondPlace.displayAddress) + XCTAssertEqual(PoiDetailsType.notSetDetailsType, secondPlace.poiDetailsType) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Address/ReverseGeocodeMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Address/ReverseGeocodeMethodSpec.swift new file mode 100644 index 00000000..16523410 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Address/ReverseGeocodeMethodSpec.swift @@ -0,0 +1,144 @@ +// +// ReverseGeocodeMethod.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +import KarhooSDK + +final class ReverseGeocodeMethodSpec: XCTestCase { + + private var addressService: AddressService! + private let path = "/v1/locations/reverse-geocode" + private var call: Call! + + override func setUp() { + super.setUp() + + self.addressService = Karhoo.getAddressService() + self.call = addressService.reverseGeocode(position: Position(latitude: 0, longitude: 0)) + } + + /** + * When: Requesting location info + * And: The response returns location info json object + * Then: Result is success and returns LocationInfo object + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "LocationInfo.json", path: path) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { [weak self] result in + XCTAssertTrue(result.isSuccess()) + self?.assertLocationInfo(info: result.successValue()!) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Requesting location info + * And: The response returns an error + * Then: Result is error + */ + func testErrorResponse() { + let expectedError = RawKarhooErrorFactory.buildError(code: "K2001") + + NetworkStub.errorResponse(path: path, responseData: expectedError) + + let expectation = self.expectation(description: "calls the callback with error") + + call.execute(callback: { result in + let error = result.errorValue() + XCTAssertEqual(.couldNotGetAddress, error?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Requesting location info + * And: The response returns empty json object + * Then: Result is success with empty LocationInfo object + */ + func testEmptyResponse() { + NetworkStub.emptySuccessResponse(path: path) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Requesting location info + * And: The response returns invalid json object + * Then: Result is error + */ + func testInvalidResponse() { + NetworkStub.responseWithInvalidJson(path: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Requesting location info + * And: The response returns time out error + * Then: Result is error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + private func assertLocationInfo(info: LocationInfo) { + XCTAssertEqual(51.5166744, info.position.latitude) + XCTAssertEqual(-0.1769328, info.position.longitude) + XCTAssertEqual("123", info.placeId) + XCTAssertEqual(.regulated, info.poiType) + let address = LocationInfoAddress(displayAddress: "Paddington Station", + lineOne: "", + lineTwo: "", + buildingNumber: "", + streetName: "Praed St", + city: "London", + postalCode: "W2 1HQ", + countryCode: "UK", + region: "Greater London") + XCTAssert(address.equals(info.address)) + XCTAssertEqual("UK", info.timeZoneIdentifier) + XCTAssertEqual("iata", info.details.iata) + XCTAssertEqual("terminal", info.details.terminal) + XCTAssertEqual(.notSetDetailsType, info.details.type) + XCTAssertEqual(51.5062894, info.meetingPoint.position.latitude) + XCTAssertEqual(-0.0859324, info.meetingPoint.position.longitude) + XCTAssertEqual("I am near by", info.meetingPoint.instructions) + XCTAssertEqual(.curbSide, info.meetingPoint.type) + XCTAssertEqual("Waiting", info.instructions) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Auth/AuthLoginMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Auth/AuthLoginMethodSpec.swift new file mode 100644 index 00000000..5358281d --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Auth/AuthLoginMethodSpec.swift @@ -0,0 +1,153 @@ +// +// AuthLoginMethodSpec.swift +// KarhooSDKIntegrationTests +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class AuthLoginMethodSpec: XCTestCase { + private var authService: AuthService! + private let exchangeTokenPath = "/karhoo/anonymous/token-exchange" + private let userInfoPath = "/oauth/v2/userinfo" + + private var call: Call! + + override func setUp() { + super.setUp() + authService = Karhoo.getAuthService() + + call = authService.login(token: "aasdasdadqdqwd") + } + + /** + * Given: Calling SSO auth login with client id = mobile-accor + * And: The access code is returned + * And: The access token is generated using the code + * Then: The userInfo is returned via the access token + */ + func testUserLoginSuccess() { + NetworkStub.successResponse(jsonFile: "AuthExchangeToken.json", path: exchangeTokenPath) + NetworkStub.successResponse(jsonFile: "AuthUserInfo.json", path: userInfoPath) + + let expectation = self.expectation(description: "Calls callback with expected result") + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling SSO auth login with client id = mobile-accor + * And: The access code is not returned + * Then: The user is not logged + */ + func testUserLoginAuthCodeFail() { + NetworkStub.errorResponse(path: exchangeTokenPath, responseData: RawKarhooErrorFactory.buildError(code: "K9999")) + + let expectation = self.expectation(description: "Calls callback with expected result") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling SSO auth login with client id = mobile-accor + * And: The access code is returned but with invalid data + * Then: The user is not logged + */ + func testUserLoginAuthCodeInvalid() { + NetworkStub.responseWithInvalidData(path: exchangeTokenPath, statusCode: 200) + + let expectation = self.expectation(description: "Calls callback with expected result") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling SSO auth login with client id = mobile-accor + * And: The access code is returned + * And: The access token request fails + * Then: The user is not logged + */ + func testUserLoginAuthTokenFail() { + NetworkStub.successResponse(jsonFile: "AuthExchangeToken.json", path: exchangeTokenPath) + NetworkStub.errorResponse(path: exchangeTokenPath, responseData: RawKarhooErrorFactory.buildError(code: "K9999")) + + let expectation = self.expectation(description: "Calls callback with expected result") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling SSO auth login with client id = mobile-accor + * And: The access code is returned + * And: The access token request response is invalid + * Then: The user is not logged + */ + func testUserLoginAuthTokenInvalid() { + NetworkStub.successResponse(jsonFile: "AuthExchangeToken.json", path: exchangeTokenPath) + NetworkStub.responseWithInvalidData(path: exchangeTokenPath, statusCode: 200) + + let expectation = self.expectation(description: "Calls callback with expected result") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling SSO auth login with client id = mobile-accor + * And: The access code is returned + * And: The access token request is successfull + * Then: The userInfo request fails + */ + func testUserLoginUserInfoFail() { + NetworkStub.successResponse(jsonFile: "AuthExchangeToken.json", path: exchangeTokenPath) + NetworkStub.errorResponse(path: userInfoPath, responseData: RawKarhooErrorFactory.buildError(code: "K9999")) + + let expectation = self.expectation(description: "Calls callback with expected result") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling SSO auth login with client id = mobile-accor + * And: The access code is returned + * And: The access token request is successfull + * Then: The userInfo return value is invalid + */ + func testUserLoginUserInfoInvalid() { + NetworkStub.successResponse(jsonFile: "AuthExchangeToken.json", path: exchangeTokenPath) + NetworkStub.responseWithInvalidData(path: userInfoPath, statusCode: 200) + + let expectation = self.expectation(description: "Calls callback with expected result") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Auth/RevokeMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Auth/RevokeMethodSpec.swift new file mode 100644 index 00000000..09ff0157 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Auth/RevokeMethodSpec.swift @@ -0,0 +1,55 @@ +// +// RevokeMethodSpec.swift +// KarhooSDKIntegrationTests +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class AuthRevokeMethodSpec: XCTestCase { + private var authService: AuthService! + private let revokePath = "/oauth/v2/revoke" + + private var call: Call! + + override func setUp() { + super.setUp() + authenticate() + authService = Karhoo.getAuthService() + + call = authService.revoke() + } + + private func authenticate() { + NetworkStub.successResponse(jsonFile: "AuthToken.json", path: "/v1/auth/token") + NetworkStub.successResponse(jsonFile: "AuthorisedUserInfo.json", path: "/v1/directory/users/me") + + let expectation = self.expectation(description: "calls the callback with success") + + Karhoo.getUserService().login(userLogin: UserLogin(username: "mock", + password: "mock")).execute(callback: { _ in + expectation.fulfill() + }) + waitForExpectations(timeout: 1) + } + + /** + * When: Calling Revoke + * Then: User should be removed + * And: Result propogated + */ + func testRevokeSuccess() { + NetworkStub.emptySuccessResponse(path: revokePath) + + let expectation = self.expectation(description: "Calls callback with expected result") + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + XCTAssertNil(Karhoo.getUserService().getCurrentUser()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Config/UIConfigMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Config/UIConfigMethodSpec.swift new file mode 100644 index 00000000..391aa82a --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Config/UIConfigMethodSpec.swift @@ -0,0 +1,78 @@ +// +// UIConfigMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class UIConfigMethod: XCTestCase { + + private var configService: ConfigService! + private var call: Call! + + override func setUp() { + super.setUp() + + authenticate() + self.configService = Karhoo.getConfigService() + } + + private func authenticate() { + NetworkStub.successResponse(jsonFile: "AuthToken.json", path: "/v1/auth/token") + NetworkStub.successResponse(jsonFile: "AuthorisedUserInfo.json", path: "/v1/directory/users/me") + + let expectation = self.expectation(description: "calls the callback with success") + + Karhoo.getUserService().login(userLogin: UserLogin(username: "mock", + password: "mock")).execute(callback: { _ in + expectation.fulfill() + }) + waitForExpectations(timeout: 1) + } + + /** + * When: Getting UIConfig for a configurable view + * Then: Expected config should return + */ + func testHappyPath() { + let payload = UIConfigRequest(viewId: "additionalFeedbackButton") + self.call = configService.uiConfig(uiConfigRequest: payload) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + XCTAssertFalse(result.successValue()!.hidden) + + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Getting UIConfig for a view that is not configured + * Then: Expected error should return + */ + func testNoConfig() { + let payload = UIConfigRequest(viewId: "some") + self.call = configService.uiConfig(uiConfigRequest: payload) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual("KSDK05", result.errorValue()?.code) + + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/DriverTracking/DriverTrackingMethodSpec.swift b/KarhooSDKIntegrationTests/Service/DriverTracking/DriverTrackingMethodSpec.swift new file mode 100644 index 00000000..ffee73d1 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/DriverTracking/DriverTrackingMethodSpec.swift @@ -0,0 +1,113 @@ +// +// DriverTrackingMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class DriverTrackingMethodSpec: XCTestCase { + + private var driverTrackingService: DriverTrackingService! + private let path = "/v1/bookings/some/track" + private var pollCall: PollCall! + + override func setUp() { + super.setUp() + + driverTrackingService = Karhoo.getDriverTrackingService() + + pollCall = driverTrackingService.trackDriver(tripId: "some") + } + + /** + * When: Getting a driver position + * And: The result succeeds + * Then: Expected result should be propogated + */ + func testExecuteDriverTrackingSuccess() { + NetworkStub.response(fromFile: "DriverTracking.json", forPath: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with success") + + pollCall.execute(callback: { result in + self.assertSuccess(result: result) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Getting a driver position + * And: The result fails + * Then: Expected error should be propogated + */ + func testExecuteDriverTrackingFails() { + let driverTrackingError = RawKarhooErrorFactory.buildError(code: "K4012") + + NetworkStub.errorResponse(path: path, responseData: driverTrackingError) + + let expectation = self.expectation(description: "Calls callback with K4012 error type") + + pollCall.execute(callback: { result in + XCTAssertEqual(.couldNotGetTripCouldNotFindSpecifiedTrip, result.errorValue()!.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Polling for a driver position + * When: The request succeeds + * And: Then the request fails + * Then: Two results should be propogated as expected + */ + func testDriverTrackingPolling() { + NetworkStub.response(fromFile: "DriverTracking.json", forPath: path, statusCode: 200) + + var driverTrackingResults: [Result] = [] + let expectation = self.expectation(description: "polling returns 2 times") + let observer = Observer { (result: Result) in + driverTrackingResults.append(result) + NetworkStub.errorResponse(path: self.path, + responseData: RawKarhooErrorFactory.buildError(code: "K4012")) + + if driverTrackingResults.count == 2 { + self.assertSuccess(result: driverTrackingResults[0]) + XCTAssertEqual(.couldNotGetTripCouldNotFindSpecifiedTrip, driverTrackingResults[1].errorValue()?.type) + expectation.fulfill() + } + } + pollCall.observable(pollTime: 0.1).subscribe(observer: observer) + waitForExpectations(timeout: 1) + } + + /** + * Given: Getting a driver position + * When: Request times out + * Then: Unknown error should be propogated + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "Unknown error propogated") + pollCall.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + private func assertSuccess(result: Result) { + XCTAssertEqual(Position(latitude: 1, longitude: -1), result.successValue()?.position) + XCTAssertEqual(5, result.successValue()?.originEta) + XCTAssertEqual(10, result.successValue()?.destinationEta) + } +} diff --git a/KarhooSDKIntegrationTests/Service/FareMethodSpec.swift b/KarhooSDKIntegrationTests/Service/FareMethodSpec.swift new file mode 100644 index 00000000..ac8edb00 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/FareMethodSpec.swift @@ -0,0 +1,135 @@ +// +// GetFareSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +//swiftlint:disable +//function_body_length + +import XCTest +@testable import KarhooSDK + +final class FareMethodSpec: XCTestCase { + + private var fareService: FareService! + private let tripId = "123" + private var path: String! + private var call: Call! + + override func setUp() { + super.setUp() + + fareService = Karhoo.getFareService() + path = "/v1/fares/trip/" + tripId + call = fareService.fareDetails(tripId: tripId) + } + + /** + * When: Requesting a fare for a specific trip + * And: The result succeeds + * Then: Expected result should be propogated + */ + func testGetFareSuccess() { + NetworkStub.response(fromFile: "Fare.json", forPath: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + self.assertSuccess(result: result) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + private func assertSuccess(result: Result) { + XCTAssert(result.isSuccess()) + guard let fare = result.successValue() else { + XCTFail("Missing success value") + return + } + + XCTAssertEqual(fare.state, "PENDING") + XCTAssertEqual(fare.expectedFinalTime, "2016-04-16T16:06:05Z") + XCTAssertEqual(fare.expectedIn, "360") + XCTAssertEqual(fare.breakdown.total, 0) + XCTAssertEqual(fare.breakdown.currency, "EUR") + } + + /** + * When: Requesting a fare + * And: The result fails + * Then: Expected error should be propogated + */ + func testGetFareFails() { + let trackTripError = RawKarhooErrorFactory.buildError(code: "K4001") + + NetworkStub.errorResponse(path: path, responseData: trackTripError) + + let expectation = self.expectation(description: "calls callback with error result") + + call.execute(callback: { result in + XCTAssertEqual(.couldNotBookTrip, result.errorValue()!.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Get a fare + * And: The response returns empty json object + * Then: Result is success with empty TripInfo object + */ + func testEmptyResponse() { + NetworkStub.emptySuccessResponse(path: path) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Get a fare + * And: The response returns invalid json object + * Then: Result is error + */ + func testInvalidResponse() { + NetworkStub.responseWithInvalidJson(path: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Get a fare + * And: The response returns time out error + * Then: Result is error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Payment/AddPaymentDetails/AddPaymentDetailsMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Payment/AddPaymentDetails/AddPaymentDetailsMethodSpec.swift new file mode 100644 index 00000000..e2ae0e29 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Payment/AddPaymentDetails/AddPaymentDetailsMethodSpec.swift @@ -0,0 +1,87 @@ +// +// AddPaymentDetailsSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class AddPaymentDetailsMethodSpec: XCTestCase { + + private var paymentService: PaymentService! + private let path = "/v2/payments/payment-methods/braintree/add-payment-details" + private var call: Call! + + override func setUp() { + super.setUp() + + self.paymentService = Karhoo.getPaymentService() + + let payload = AddPaymentDetailsPayload(nonce: "some", payer: Payer(), organisationId: "some+desiredOrg") + self.call = paymentService.addPaymentDetails(addPaymentDetailsPayload: payload) + } + + /** + * When: Adding payment details + * And: The response returns successful + * Then: Result is success + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "AddPaymentDetails.json", path: path) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + XCTAssertEqual(result.successValue()?.cardType, "Visa") + XCTAssertEqual(result.successValue()?.lastFour, "1111") + XCTAssertEqual(result.successValue()?.nonce, "some_nonce") + + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Adding payment details + * And: The response returns error K0003 + * Then: Resulting error value should be a couldNotReadAuthorisationError + */ + func testErrorResponse() { + let expectedError = RawKarhooErrorFactory.buildError(code: "K0003") + + NetworkStub.errorResponse(path: path, responseData: expectedError) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + let error = result.errorValue() + XCTAssertEqual(error?.type, .couldNotReadAuthorisationToken) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Adding payment details + * And: The response returns time out error + * Then: Result is error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Payment/GetNonceMethod/GetNonceMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Payment/GetNonceMethod/GetNonceMethodSpec.swift new file mode 100644 index 00000000..0edbb2d6 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Payment/GetNonceMethod/GetNonceMethodSpec.swift @@ -0,0 +1,85 @@ +// +// GetNonceMethod.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class GetNonceMethodSpec: XCTestCase { + + private var paymentService: PaymentService! + private let path = "/v2/payments/payment-methods/braintree/get-nonce" + private var call: Call! + + override func setUp() { + super.setUp() + + self.paymentService = Karhoo.getPaymentService() + let nonceRequestPayload = NonceRequestPayloadMock().set(payer: Payer()).set(organisationId: "some").build() + self.call = paymentService.getNonce(nonceRequestPayload: nonceRequestPayload) + } + + /** + * When: getting nonce + * And: The response returns a 200 and nonce + * Then: Result is success + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "GetNonce.json", path: path) + + let expectation = self.expectation(description: "calls the callback with expected response") + + call.execute(callback: { result in + XCTAssertEqual(result.successValue()?.nonce, "some_nonce") + XCTAssertEqual(result.successValue()?.cardType, "Visa") + XCTAssertEqual(result.successValue()?.lastFour, "1111") + + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Getting nonce + * And: The response returns error K0003 + * Then: Resulting error value should be a couldNotReadAuthorisationError + */ + func testErrorResponse() { + let expectedError = RawKarhooErrorFactory.buildError(code: "K0003") + + NetworkStub.errorResponse(path: path, responseData: expectedError) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + let error = result.errorValue() + XCTAssertEqual(error?.type, .couldNotReadAuthorisationToken) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Getting nonce + * And: The response returns time out error + * Then: Result is error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Payment/PaymentSDKToken/PaymentSDKTokenMethod.swift b/KarhooSDKIntegrationTests/Service/Payment/PaymentSDKToken/PaymentSDKTokenMethod.swift new file mode 100644 index 00000000..8eaa531d --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Payment/PaymentSDKToken/PaymentSDKTokenMethod.swift @@ -0,0 +1,116 @@ +// +// PaymentSDKTokenMethod.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class PaymentSDKTokenMethod: XCTestCase { + + private var paymentService: PaymentService! + private let path = "/v2/payments/payment-methods/braintree/client-tokens" + private var call: Call! + + override func setUp() { + super.setUp() + + paymentService = Karhoo.getPaymentService() + let mockPayload = PaymentSDKTokenPayload(organisationId: "some_org", currency: "GBP") + + call = paymentService.initialisePaymentSDK(paymentSDKTokenPayload: mockPayload) + } + + /** + * Given: Calling initialisePaymentSDK + * When: expected response is returned and a success + * Then: Expected result should be propogated + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "PaymentSDKToken.json", path: self.path) + + let expectation = self.expectation(description: "Callback called with succeess") + + call.execute(callback: { result in + XCTAssertEqual("some_sdk_token", result.successValue()?.token) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling initialisePaymentSDK + * When: error returned + * Then: Expected error should be propogated + */ + func testErrorResponse() { + NetworkStub.errorResponse(path: self.path, responseData: RawKarhooErrorFactory.buildError(code: "K0005")) + + let expectation = self.expectation(description: "Callback called with an error") + + call.execute(callback: { result in + XCTAssertEqual(.missingRequiredRoleForThisRequest, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling initialisePaymentSDK + * When: Response is a http success but contains an empty json + * Then: Expected error should be propogated + */ + func testEmptySuccessResponse() { + NetworkStub.emptySuccessResponse(path: self.path) + + let expectation = self.expectation(description: "callback called with an unexpected error") + + call.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling initialisePaymentSDK + * When: Response is a http success but contains an invalid json + * Then: Expected error should be propogated + */ + func testInvalidSuccessResponse() { + NetworkStub.emptySuccessResponse(path: self.path) + + let expectation = self.expectation(description: "callback called with an unexpected error") + + call.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Calling initialisePaymentSDK + * And: The response returns time out error + * Then: Result is error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Quote/QuoteSearchMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Quote/QuoteSearchMethodSpec.swift new file mode 100644 index 00000000..b11d9496 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Quote/QuoteSearchMethodSpec.swift @@ -0,0 +1,206 @@ +// +// QuoteSearchMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class QuoteSearchMethodSpec: XCTestCase { + + private let availabilityPath = "/v1/quotes/availability" + private let quoteListIdPath = "/v1/quotes" + private let quotesPath = "/v1/quotes/some-id" + private var quoteService: QuoteService! + private var pollCall: PollCall! + + override func setUp() { + super.setUp() + + let quoteSearch = QuoteSearch(origin: LocationInfoMock().set(placeId: "originPlaceId").build(), + destination: LocationInfoMock().set(placeId: "destinationPlaceId").build(), + dateScheduled: Date()) + quoteService = Karhoo.getQuoteService() + pollCall = quoteService.quotes(quoteSearch: quoteSearch) + } + + /** + * Given: Searching for quotes + * When: All requests succceed as expected + * Then: Success result should be propogated + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "Availability.json", path: availabilityPath) + NetworkStub.successResponse(jsonFile: "QuoteListId.json", path: quoteListIdPath) + NetworkStub.successResponse(jsonFile: "Quotes.json", path: quotesPath) + + let expectation = self.expectation(description: "Calls callback with success result") + pollCall.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + self.assertSuccess(quote: result.successValue()!.all[0]) + + XCTAssertEqual(6, result.successValue()?.quoteCategories.count) + XCTAssertEqual(1, result.successValue()?.quotes(for: "Saloon").count) + XCTAssertEqual(0, result.successValue()?.quotes(for: "Taxi").count) + XCTAssertEqual(0, result.successValue()?.quotes(for: "MPV").count) + XCTAssertEqual(0, result.successValue()?.quotes(for: "Exec").count) + XCTAssertEqual(0, result.successValue()?.quotes(for: "Electric").count) + XCTAssertEqual(0, result.successValue()?.quotes(for: "Moto").count) + + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for quotes + * When: Availability fails + * And: Quote list id and Quotes request succeeds + * Then: Success result should be propogated (quotes) + // categories will come from quotes in this scenario, not availability + */ + func testAvailabilityRequestErrorResponse() { + NetworkStub.errorResponse(path: availabilityPath, responseData: RawKarhooErrorFactory.buildError(code: "K5001")) + NetworkStub.successResponse(jsonFile: "QuoteListId.json", path: quoteListIdPath) + NetworkStub.successResponse(jsonFile: "Quotes.json", path: quotesPath) + + let expectation = self.expectation(description: "Calls callback with success result") + pollCall.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + self.assertSuccess(quote: result.successValue()!.all[0]) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for quotes + * When: QuotesListId fails (no availability) + * Then: Expected error should be propogated + */ + func testQuoteListIdRequestErrorResponse() { + NetworkStub.successResponse(jsonFile: "Availability.json", path: availabilityPath) + NetworkStub.errorResponse(path: quoteListIdPath, responseData: RawKarhooErrorFactory.buildError(code: "K3002")) + NetworkStub.successResponse(jsonFile: "Quotes.json", path: quotesPath) + + let expectation = self.expectation(description: "Calls callback with error result") + pollCall.execute(callback: { result in + XCTAssertEqual(.noAvailabilityInRequestedArea, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for quotes + * When: Quotes request fails + * Then: Expected error should be propogated + */ + func testQuotesRequestErrorResponse() { + NetworkStub.successResponse(jsonFile: "Availability.json", path: availabilityPath) + NetworkStub.successResponse(jsonFile: "QuoteListId.json", path: quoteListIdPath) + NetworkStub.errorResponse(path: quotesPath, responseData: RawKarhooErrorFactory.buildError(code: "K3003")) + + let expectation = self.expectation(description: "Calls callback with error result") + pollCall.execute(callback: { result in + XCTAssertEqual(.couldNotFindSpecifiedQuote, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for quotes + * When: Quotes request succeeds but returns empty json + * Then: Unknown error should be propogated + */ + func testQuoteSearchEmptySuccessResult() { + NetworkStub.successResponse(jsonFile: "Availability.json", path: availabilityPath) + NetworkStub.successResponse(jsonFile: "QuoteListId.json", path: quoteListIdPath) + NetworkStub.emptySuccessResponse(path: quotesPath) + + let expectation = self.expectation(description: "Calls callback with error result") + pollCall.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for quotes + * When: Quotes request times out + * Then: Unknown error should be propogated + */ + func testQuotesRequestTimeout() { + NetworkStub.successResponse(jsonFile: "Availability.json", path: availabilityPath) + NetworkStub.successResponse(jsonFile: "QuoteListId.json", path: quoteListIdPath) + NetworkStub.errorResponseTimeOutConnection(path: quotesPath) + + let expectation = self.expectation(description: "Calls callback with error result") + pollCall.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Polling for quotes + * When: The first execution succeeds + * And: The second execution fails + * Then: correct results should be propogated + */ + func testQuoteSearchPolling() { + NetworkStub.successResponse(jsonFile: "Availability.json", path: availabilityPath) + NetworkStub.successResponse(jsonFile: "QuoteListId.json", path: quoteListIdPath) + NetworkStub.successResponse(jsonFile: "Quotes.json", path: quotesPath) + + var quoteSearchResult: [Result] = [] + let expectation = self.expectation(description: "polling returns 2 times") + + let quoteSearchObserver = Observer { (result: Result) in + quoteSearchResult.append(result) + NetworkStub.clearStubs() + NetworkStub.errorResponse(path: self.quotesPath, + responseData: RawKarhooErrorFactory.buildError(code: "K3003")) + + if quoteSearchResult.count == 2 { + self.assertSuccess(quote: quoteSearchResult[0].successValue()!.all[0]) + XCTAssertEqual(.couldNotFindSpecifiedQuote, quoteSearchResult[1].errorValue()?.type) + expectation.fulfill() + } + } + + let quotesObservable = pollCall.observable(pollTime: 0.1) + quotesObservable.subscribe(observer: quoteSearchObserver) + + waitForExpectations(timeout: 1) + } + + private func assertSuccess(quote: Quote) { + XCTAssertEqual("someQuoteId", quote.quoteId) + XCTAssertEqual("Saloon", quote.categoryName) + XCTAssertEqual("someFleetId", quote.fleetId) + XCTAssertEqual("someFleetName", quote.fleetName) + XCTAssertEqual(1, quote.qtaLowMinutes) + XCTAssertEqual(2, quote.qtaHighMinutes) + XCTAssertEqual(.estimated, quote.quoteType) + XCTAssertEqual(7.78, quote.lowPrice) + XCTAssertEqual(7.79, quote.highPrice) + XCTAssertEqual("someTermsUrl", quote.termsConditionsUrl) + XCTAssertEqual("someLogoUrl", quote.supplierLogoUrl) + XCTAssertEqual("+123", quote.phoneNumber) + XCTAssertEqual(PickUpType.meetAndGreet, quote.pickUpType) + XCTAssertEqual(.market, quote.source) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Trip/BookTripMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Trip/BookTripMethodSpec.swift new file mode 100644 index 00000000..06e908ee --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Trip/BookTripMethodSpec.swift @@ -0,0 +1,192 @@ +// +// BookTripMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +//swiftlint:disable function_body_length + +/** booking-with-nonce request tests covered in BookingInteractorSpec + as responses between book trip and book trip with nonce are the same +**/ + +import XCTest +@testable import KarhooSDK + +final class BookTripMethodSpec: XCTestCase { + + private var tripService: TripService! + private let path = "/v1/bookings" + private var call: Call! + + override func setUp() { + super.setUp() + + tripService = Karhoo.getTripService() + + let userInfo = UserInfo(userId: "", + firstName: "", + lastName: "", + email: "", + mobileNumber: "", + organisations: [Organisation()], + locale: "") + + let passengers = Passengers(additionalPassengers: 0, passengerDetails: [PassengerDetails(user: userInfo)]) + let tripBooking = TripBooking(quoteId: "123", passengers: passengers) + call = tripService.book(tripBooking: tripBooking) + } + + /** + * When: Booking a trip + * And: The result succeeds + * Then: Expected result should be propogated + */ + func testBookTripSuccess() { + NetworkStub.response(fromFile: "TripInfo.json", forPath: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + self.assertSuccess(result: result) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Booking a trip + * And: The result fails + * Then: Expected error should be propogated + */ + func testBookTripFails() { + let trackTripError = RawKarhooErrorFactory.buildError(code: "K4001") + + NetworkStub.errorResponse(path: path, responseData: trackTripError) + + let expectation = self.expectation(description: "calls callback with error result") + + call.execute(callback: { result in + XCTAssertEqual(.couldNotBookTrip, result.errorValue()!.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Booking a trip + * And: The response returns empty json object + * Then: Result is success with empty TripInfo object + */ + func testEmptyResponse() { + NetworkStub.emptySuccessResponse(path: path) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Booking a trip + * And: The response returns invalid json object + * Then: Result is error + */ + func testInvalidResponse() { + NetworkStub.responseWithInvalidJson(path: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Booking a trip + * And: The response returns time out error + * Then: Result is error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + private func assertSuccess(result: Result) { + XCTAssert(result.isSuccess()) + guard let trip = result.successValue() else { + XCTFail("Missing success value") + return + } + XCTAssertEqual("b6a5f9dc-9066-4252-9013-be85dfa563bc", trip.tripId) + XCTAssertEqual(.requested, trip.state) + XCTAssertEqual("EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", trip.origin.placeId) + XCTAssertEqual("EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", trip.destination!.placeId) + XCTAssertEqual("2018-04-21 12:35:00 +0000", trip.dateScheduled?.description) + XCTAssertEqual(35.50, trip.tripQuote.denominateTotal) + XCTAssertEqual("A5TH-R27D", trip.displayId) + + let fleetInfo = trip.fleetInfo + XCTAssertEqual("some fleet id", fleetInfo.fleetId) + XCTAssertEqual("some fleet name", fleetInfo.name) + XCTAssertEqual("some logo url", fleetInfo.logoUrl) + XCTAssertEqual("some description", fleetInfo.description) + XCTAssertEqual("some phone number", fleetInfo.phoneNumber) + XCTAssertEqual("some terms and conditions", fleetInfo.termsConditionsUrl) + XCTAssertEqual("dispatch-co@karhoo.com", fleetInfo.email) + + let vehicle = trip.vehicle + XCTAssertEqual("MPV", vehicle.vehicleClass) + XCTAssertEqual("Renault Scenic (Black)", vehicle.description) + XCTAssertEqual("123 XYZ", vehicle.vehicleLicensePlate) + + let driver = trip.vehicle.driver + XCTAssertEqual("Michael", driver.firstName) + XCTAssertEqual("Higgins", driver.lastName) + XCTAssertEqual("+441111111111", driver.phoneNumber) + XCTAssertEqual("https://karhoo.com/drivers/mydriver.png", driver.photoUrl) + XCTAssertEqual("ZXZ151YTY", driver.licenseNumber) + + XCTAssertEqual("BA1326", trip.flightNumber) + + let meetingPoint = trip.meetingPoint! + XCTAssertEqual(51.5086692, meetingPoint.position.latitude) + XCTAssertEqual(-0.1375291, meetingPoint.position.longitude) + XCTAssertEqual(.meetAndGreet, meetingPoint.type) + XCTAssertEqual( "string", meetingPoint.instructions) + + let tripQuote = trip.tripQuote + XCTAssertEqual(.fixed, tripQuote.type) + XCTAssertEqual(3550, tripQuote.total) + XCTAssertEqual("GBP", tripQuote.currency) + XCTAssertEqual(15, tripQuote.gratuityPercent) + XCTAssertEqual("Saloon", tripQuote.vehicleClass) + XCTAssertEqual(5, tripQuote.qtaLowMinutes) + XCTAssertEqual(8, tripQuote.qtaHighMinutes) + + let vehicleAttributes = VehicleAttributes(childSeat: true, electric: true, + hybrid: false, luggageCapacity: 2, + passengerCapacity: 3) + XCTAssertEqual(vehicleAttributes, tripQuote.vehicleAttributes) + + } +} diff --git a/KarhooSDKIntegrationTests/Service/Trip/CancelTripMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Trip/CancelTripMethodSpec.swift new file mode 100644 index 00000000..38d2c65f --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Trip/CancelTripMethodSpec.swift @@ -0,0 +1,97 @@ +// +// CancelTripMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class CancelTripMethodSpec: XCTestCase { + + private var tripService: TripService! + private let path = "/v1/bookings/some/cancel" + private var call: Call! + + override func setUp() { + super.setUp() + + tripService = Karhoo.getTripService() + + let tripCancellation = TripCancellation(tripId: "some", + cancelReason: .notNeededAnymore) + + call = tripService.cancel(tripCancellation: tripCancellation) + } + + /** + * Given: Cancelling a trip + * When: Request succeeds + * Then: Success result should be propogated + */ + func testHappyPath() { + NetworkStub.emptySuccessResponse(path: path) + + let expectation = self.expectation(description: "Calls callback with success result") + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Cancelling a trip + * When: Request fails with error + * Then: error type should be propogated + */ + func testErrorResponse() { + NetworkStub.errorResponse(path: self.path, responseData: RawKarhooErrorFactory.buildError(code: "K4007")) + + let expectation = self.expectation(description: "Calls callback with error result") + call.execute(callback: { result in + XCTAssertEqual(.couldNotCancelTrip, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Cancelling a trip + * When: Request fails with error that is invalid + * Then: unkown error type should be propogated + */ + func testErrorInvalidResponse() { + NetworkStub.responseWithInvalidData(path: self.path, statusCode: 400) + + let expectation = self.expectation(description: "Calls callback with error result") + call.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Cancelling a trip + * When: Request times out + * Then: Unknown error should be propogated + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "Unknown error propogated") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Trip/TrackTripMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Trip/TrackTripMethodSpec.swift new file mode 100644 index 00000000..ad5e3350 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Trip/TrackTripMethodSpec.swift @@ -0,0 +1,167 @@ +// +// TrackTripMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +//swiftlint:disable function_body_length + +import XCTest +@testable import KarhooSDK + +final class TrackTripMethodSpec: XCTestCase { + + private var tripService: TripService! + private let path = "/v1/bookings/123" + private var pollCall: PollCall! + + override func setUp() { + super.setUp() + + tripService = Karhoo.getTripService() + + pollCall = tripService.trackTrip(tripId: "123") + } + + /** + * When: Getting a trip + * And: The result succeeds + * Then: Expected result should be propogated + */ + func testExecuteTrackTripSuccess() { + NetworkStub.response(fromFile: "TripInfo.json", forPath: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with success") + + pollCall.execute(callback: { result in + self.assertSuccess(result: result) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Getting a trip + * And: The result fails + * Then: Expected error should be propogated + */ + func testExecuteTrackTripFails() { + let trackTripError = RawKarhooErrorFactory.buildError(code: "K4011") + + NetworkStub.errorResponse(path: path, responseData: trackTripError) + + let expectation = self.expectation(description: "calls callback with error result") + + pollCall.execute(callback: { result in + XCTAssertEqual(.couldNotGetTrip, result.errorValue()!.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Getting a trip + * When: The request succeeds + * And: Then the request fails + * Then: Two results should be propogated as expected + */ + func testTrackTripPolling() { + NetworkStub.response(fromFile: "TripInfo.json", forPath: path, statusCode: 200) + + var trackTripResults: [Result] = [] + let expectation = self.expectation(description: "polling returns 2 times") + + let observer = Observer { (result: Result) in + trackTripResults.append(result) + NetworkStub.errorResponse(path: self.path, + responseData: RawKarhooErrorFactory.buildError(code: "K4012")) + + if trackTripResults.count == 2 { + self.assertSuccess(result: trackTripResults[0]) + XCTAssertEqual(.couldNotGetTripCouldNotFindSpecifiedTrip, trackTripResults[1].errorValue()?.type) + expectation.fulfill() + } + } + pollCall.observable(pollTime: 0.1).subscribe(observer: observer) + waitForExpectations(timeout: 1) + } + + /** + * Given: Getting a trip + * When: The request times out + * Then: Result is unknown error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + pollCall.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + private func assertSuccess(result: Result) { + XCTAssert(result.isSuccess()) + guard let trip = result.successValue() else { + XCTFail("Missing success value") + return + } + XCTAssertEqual("b6a5f9dc-9066-4252-9013-be85dfa563bc", trip.tripId) + XCTAssertEqual(.requested, trip.state) + XCTAssertEqual("EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", trip.origin.placeId) + XCTAssertEqual("EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", trip.destination!.placeId) + XCTAssertEqual("2018-04-21 12:35:00 +0000", trip.dateScheduled?.description) + XCTAssertEqual(35.50, trip.tripQuote.denominateTotal) + XCTAssertEqual("A5TH-R27D", trip.displayId) + + let fleetInfo = trip.fleetInfo + XCTAssertEqual("some fleet id", fleetInfo.fleetId) + XCTAssertEqual("some fleet name", fleetInfo.name) + XCTAssertEqual("some logo url", fleetInfo.logoUrl) + XCTAssertEqual("some description", fleetInfo.description) + XCTAssertEqual("some phone number", fleetInfo.phoneNumber) + XCTAssertEqual("some terms and conditions", fleetInfo.termsConditionsUrl) + XCTAssertEqual("dispatch-co@karhoo.com", fleetInfo.email) + + let vehicle = trip.vehicle + XCTAssertEqual("MPV", vehicle.vehicleClass) + XCTAssertEqual("Renault Scenic (Black)", vehicle.description) + XCTAssertEqual("123 XYZ", vehicle.vehicleLicensePlate) + + let driver = trip.vehicle.driver + XCTAssertEqual("Michael", driver.firstName) + XCTAssertEqual("Higgins", driver.lastName) + XCTAssertEqual("+441111111111", driver.phoneNumber) + XCTAssertEqual("https://karhoo.com/drivers/mydriver.png", driver.photoUrl) + XCTAssertEqual("ZXZ151YTY", driver.licenseNumber) + + XCTAssertEqual("BA1326", trip.flightNumber) + + let meetingPoint = trip.meetingPoint! + XCTAssertEqual(51.5086692, meetingPoint.position.latitude) + XCTAssertEqual(-0.1375291, meetingPoint.position.longitude) + XCTAssertEqual(.meetAndGreet, meetingPoint.type) + XCTAssertEqual( "string", meetingPoint.instructions) + + let tripQuote = trip.tripQuote + XCTAssertEqual(.fixed, tripQuote.type) + XCTAssertEqual(3550, tripQuote.total) + XCTAssertEqual("GBP", tripQuote.currency) + XCTAssertEqual(15, tripQuote.gratuityPercent) + XCTAssertEqual("Saloon", tripQuote.vehicleClass) + XCTAssertEqual(5, tripQuote.qtaLowMinutes) + XCTAssertEqual(8, tripQuote.qtaHighMinutes) + + let vehicleAttributes = VehicleAttributes(childSeat: true, electric: true, + hybrid: false, luggageCapacity: 2, + passengerCapacity: 3) + XCTAssertEqual(vehicleAttributes, tripQuote.vehicleAttributes) } +} diff --git a/KarhooSDKIntegrationTests/Service/Trip/TripSearchMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Trip/TripSearchMethodSpec.swift new file mode 100644 index 00000000..c332698e --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Trip/TripSearchMethodSpec.swift @@ -0,0 +1,192 @@ +// +// TripSearchMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +//swiftlint:disable function_body_length + +import XCTest +@testable import KarhooSDK + +final class TripSearchMethodSpec: XCTestCase { + + private let path = "/v1/bookings/search" + private var tripService: TripService! + private var call: Call<[TripInfo]>! + + override func setUp() { + super.setUp() + + tripService = Karhoo.getTripService() + + let tripSearch = TripSearch(tripStates: [.requested]) + call = tripService.search(tripSearch: tripSearch) + + } + + /** + * Given: Searching for trips + * When: Request returns successfully with expected result + * Then: Expected result should be propogated + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "TripSearch.json", path: path) + + let expectation = self.expectation(description: "Calls callback with success response") + + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + self.assertSuccess(trip: result.successValue()![0]) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for trips + * When: Request fails with a valid error + * Then: Expected error should be propogated + */ + func testErrorResponse() { + NetworkStub.errorResponse(path: path, + responseData: RawKarhooErrorFactory.buildError(code: "K4011")) + + let expectation = self.expectation(description: "Calls callback with expected error") + + call.execute(callback: { result in + XCTAssertEqual(.couldNotGetTrip, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for trips + * When: Request fails with an invalid error + * Then: Unknown error should be propogated + */ + func testErrorInvalidResponse() { + NetworkStub.responseWithInvalidData(path: path, statusCode: 400) + + let expectation = self.expectation(description: "calls callback with unknown error") + + call.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for trips + * When: Request succeeds with invalid result + * Then: Unknown error should be propogated + */ + func testSuccessInvalidResponse() { + NetworkStub.responseWithInvalidData(path: path, statusCode: 200) + + let expectation = self.expectation(description: "calls callback with unknown error") + + call.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for trips + * When: Request succeeds with empty json + * Then: Unknown error should be propogated + */ + func testSuccessEmptyResponse() { + NetworkStub.emptySuccessResponse(path: path) + + let expectation = self.expectation(description: "calls callback with empty TripInfoList") + + call.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Searching for trips + * When: Request times out + * Then: Unknown error should be propogated + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls callback with unknown error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + private func assertSuccess(trip: TripInfo) { + XCTAssertEqual("b6a5f9dc-9066-4252-9013-be85dfa563bc", trip.tripId) + XCTAssertEqual(.requested, trip.state) + XCTAssertEqual("EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", trip.origin.placeId) + XCTAssertEqual("EhpCcm93bmVsbCBTdCwgU2hlZmZpZWxkLCBVSw", trip.destination!.placeId) + XCTAssertEqual("2018-04-21 12:35:00 +0000", trip.dateScheduled?.description) + XCTAssertEqual(35.50, trip.tripQuote.denominateTotal) + XCTAssertEqual("A5TH-R27D", trip.displayId) + + let fleetInfo = trip.fleetInfo + XCTAssertEqual("some fleet id", fleetInfo.fleetId) + XCTAssertEqual("some fleet name", fleetInfo.name) + XCTAssertEqual("some logo url", fleetInfo.logoUrl) + XCTAssertEqual("some description", fleetInfo.description) + XCTAssertEqual("some phone number", fleetInfo.phoneNumber) + XCTAssertEqual("some terms and conditions", fleetInfo.termsConditionsUrl) + XCTAssertEqual("dispatch-co@karhoo.com", fleetInfo.email) + + let vehicle = trip.vehicle + XCTAssertEqual("MPV", vehicle.vehicleClass) + XCTAssertEqual("Renault Scenic (Black)", vehicle.description) + XCTAssertEqual("123 XYZ", vehicle.vehicleLicensePlate) + + let driver = trip.vehicle.driver + XCTAssertEqual("Michael", driver.firstName) + XCTAssertEqual("Higgins", driver.lastName) + XCTAssertEqual("+441111111111", driver.phoneNumber) + XCTAssertEqual("https://karhoo.com/drivers/mydriver.png", driver.photoUrl) + XCTAssertEqual("ZXZ151YTY", driver.licenseNumber) + + XCTAssertEqual("BA1326", trip.flightNumber) + + let meetingPoint = trip.meetingPoint! + XCTAssertEqual(51.5086692, meetingPoint.position.latitude) + XCTAssertEqual(-0.1375291, meetingPoint.position.longitude) + XCTAssertEqual(.notSet, meetingPoint.type) + XCTAssertEqual( "string", meetingPoint.instructions) + + let tripQuote = trip.tripQuote + XCTAssertEqual(.fixed, tripQuote.type) + XCTAssertEqual(3550, tripQuote.total) + XCTAssertEqual("GBP", tripQuote.currency) + XCTAssertEqual(15, tripQuote.gratuityPercent) + XCTAssertEqual("Saloon", tripQuote.vehicleClass) + XCTAssertEqual(5, tripQuote.qtaLowMinutes) + XCTAssertEqual(8, tripQuote.qtaHighMinutes) + + let vehicleAttributes = VehicleAttributes(childSeat: true, electric: true, + hybrid: false, luggageCapacity: 2, + passengerCapacity: 3) + XCTAssertEqual(vehicleAttributes, tripQuote.vehicleAttributes) + } +} diff --git a/KarhooSDKIntegrationTests/Service/Trip/TripStatusMethodSpec.swift b/KarhooSDKIntegrationTests/Service/Trip/TripStatusMethodSpec.swift new file mode 100644 index 00000000..810f471a --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/Trip/TripStatusMethodSpec.swift @@ -0,0 +1,139 @@ +// +// TripStatusMethod.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class TripStatusMethodSpec: XCTestCase { + + private var tripService: TripService! + private let path = "/v1/bookings/some/status" + private var pollCall: PollCall! + + override func setUp() { + super.setUp() + + tripService = Karhoo.getTripService() + pollCall = tripService.status(tripId: "some") + } + + /** + * When: Getting trip status + * And: The result succeeds + * Then: Expected result should be propogated + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "TripStatus.json", path: path) + let expectation = self.expectation(description: "Calls callback with success result") + + pollCall.execute(callback: { result in + XCTAssertEqual(.passengerOnBoard, result.successValue()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Getting trip status + * And: The result succeeds but the response is empty + * Then: Trip status should be unkown + */ + func testSuccessEmptyResponse() { + NetworkStub.emptySuccessResponse(path: path) + + let expectation = self.expectation(description: "Calls callback with unknown trip state") + + pollCall.execute(callback: { result in + XCTAssertEqual(.unknown, result.successValue()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Getting trip status + * And: The result succeeds but the response is invalid + * Then: Trip status should be unkown + */ + func testSuccessInvalidResponse() { + NetworkStub.responseWithInvalidData(path: path, statusCode: 200) + + let expectation = self.expectation(description: "Calls callback with unknown trip state") + + pollCall.execute(callback: { result in + XCTAssertEqual(.unknown, result.successValue()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Getting trip status + * And: The result fails with valid error + * Then: Expected error should be propogated + */ + func testErrorResponse() { + NetworkStub.errorResponse(path: path, responseData: RawKarhooErrorFactory.buildError(code: "K4011")) + + let expectation = self.expectation(description: "Calls callback with expected error") + + pollCall.execute(callback: { result in + XCTAssertEqual(.couldNotGetTrip, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Getting trip status + * And: The result fails with an invalid error + * Then: unknown error should be propogated + */ + func testErrorInvalidResponse() { + NetworkStub.responseWithInvalidData(path: path, statusCode: 400) + + let expectation = self.expectation(description: "Calls callback with unknown error") + + pollCall.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Polling for trip status + * When: The request succeeds + * And: Then the request fails + * Then: Two results should be propogated as expected + */ + func testTripStatusPolling() { + NetworkStub.successResponse(jsonFile: "TripStatus.json", path: path) + + var tripStatusResult: [Result] = [] + let expectation = self.expectation(description: "polling returns 2 times") + let observer = Observer { (result: Result) in + tripStatusResult.append(result) + NetworkStub.errorResponse(path: self.path, + responseData: RawKarhooErrorFactory.buildError(code: "K4012")) + + if tripStatusResult.count == 2 { + XCTAssertEqual(.passengerOnBoard, tripStatusResult[0].successValue()) + XCTAssertEqual(.couldNotGetTripCouldNotFindSpecifiedTrip, tripStatusResult[1].errorValue()?.type) + expectation.fulfill() + } + } + pollCall.observable(pollTime: 0.1).subscribe(observer: observer) + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/User/LoginMethodSpec.swift b/KarhooSDKIntegrationTests/Service/User/LoginMethodSpec.swift new file mode 100644 index 00000000..b28de64e --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/User/LoginMethodSpec.swift @@ -0,0 +1,172 @@ +// +// LoginMethodSpec.swift +// KarhooSDKIntegrationTests +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class LoginMethodSpec: XCTestCase { + + private var userService: UserService! + private let authPath = "/v1/auth/token" + private let profilePath = "/v1/directory/users/me" + private let oauthUserInfoPath = "/oauth/v2/userInfo" + + private var call: Call! + + override func setUp() { + super.setUp() + + userService = Karhoo.getUserService() + tearDown() + + let userLogin = UserLogin(username: "some", password: "some") + call = userService.login(userLogin: userLogin) + } + + override func tearDown() { + super.tearDown() + userService.logout().execute(callback: { _ in}) + } + + /** + * Given: Calling login + * When: Auth token requests succeeds + * And: profile request succeeds with an authorised user + * Then: Expected result should be propogated + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "AuthToken.json", path: self.authPath) + NetworkStub.successResponse(jsonFile: "AuthorisedUserInfo.json", path: self.profilePath) + + let expectation = self.expectation(description: "Calls callback with expected result") + call.execute(callback: { result in + XCTAssertEqual("some_userId", result.successValue()?.userId) + XCTAssertEqual("", result.successValue()?.externalId) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling login + * When: Auth token requests fails + * Then: Expected error should be propogated + */ + func testAuthTokenRequestFailsWithError() { + NetworkStub.errorResponse(path: self.authPath, responseData: RawKarhooErrorFactory.buildError(code: "K1011")) + + let expectation = self.expectation(description: "Calls callback with expected error") + call.execute(callback: { result in + XCTAssertEqual(.passwordInWrongFormat, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling login + * When: Auth token requests succeeds + * And: profile request fails + * Then: Expected error should be propogated + */ + func testAuthTokenSuccessProfileRequestFails() { + NetworkStub.successResponse(jsonFile: "AuthToken.json", path: self.authPath) + NetworkStub.errorResponse(path: self.profilePath, responseData: RawKarhooErrorFactory.buildError(code: "K1005")) + + let expectation = self.expectation(description: "Calls callback with expected error") + call.execute(callback: { result in + XCTAssertEqual(.couldNotGetUserDetails, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling login + * When: Auth token requests succeeds + * And: profile request succeeds + * And: User is unauthorised (missing TRIP_ADMIN role) + * Then: Missing permission error should be propogated + */ + func testUnauthorisedLogin() { + NetworkStub.successResponse(jsonFile: "AuthToken.json", path: self.authPath) + NetworkStub.successResponse(jsonFile: "UnauthorisedUserInfo.json", path: self.profilePath) + + let expectation = self.expectation(description: "Calls callback with expected error") + call.execute(callback: { result in + XCTAssertEqual(.missingUserPermission, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Calling login + * When: Auth token requests succeeds + * And: profile request succeeds but response is invalid + * Then: Unexpected error should be propogated + */ + func testInvalidProfileResponse() { + NetworkStub.successResponse(jsonFile: "AuthToken.json", path: self.authPath) + NetworkStub.responseWithInvalidData(path: self.profilePath, statusCode: 200) + + let expectation = self.expectation(description: "Calls callback with expected error") + call.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * When: Logging in + * And: The user is already logged in + * Then: User already logged in error should be propagated + */ + func testLoginWhenAlreadyLoggedIn() { + NetworkStub.successResponse(jsonFile: "AuthToken.json", path: self.authPath) + NetworkStub.successResponse(jsonFile: "AuthorisedUserInfo.json", path: self.profilePath) + + let initialLoginExpectation = self.expectation(description: "1st login attempt finished") + let secondLoginExpectation = self.expectation(description: "2nd login attempt finished") + + call.execute(callback: { _ in + initialLoginExpectation.fulfill() + + // login again + self.call.execute(callback: { result in + XCTAssertEqual(.userAlreadyLoggedIn, result.errorValue()?.type) + secondLoginExpectation.fulfill() + }) + }) + + wait(for: [initialLoginExpectation, secondLoginExpectation], timeout: 1) + } + + /** + * Given: Logging in + * When: The request times out + * Then: Result is unknown error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: authPath) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/User/PasswordResetMethodSpec.swift b/KarhooSDKIntegrationTests/Service/User/PasswordResetMethodSpec.swift new file mode 100644 index 00000000..f345f39d --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/User/PasswordResetMethodSpec.swift @@ -0,0 +1,94 @@ +// +// PasswordResetMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class PasswordResetMethodSpec: XCTestCase { + + private var userService: UserService! + private let path = "/v1/directory/users/password-reset" + private var call: Call! + + override func setUp() { + super.setUp() + + userService = Karhoo.getUserService() + + call = userService.passwordReset(email: "some@some.com") + } + + /** + * Given: Resetting password + * When: The request succeeds + * Then: success result should be propogated + */ + func testHappyPath() { + NetworkStub.emptySuccessResponse(path: self.path) + + let expectation = self.expectation(description: "Calls callback with success result") + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Resetting password + * When: The request fails + * Then: error result should be propogated + */ + func testErrorResponse() { + NetworkStub.errorResponse(path: self.path, responseData: RawKarhooErrorFactory.buildError(code: "K1006")) + + let expectation = self.expectation(description: "Calls callback with error result") + call.execute(callback: { result in + XCTAssertEqual(.couldNotGetUserDetails, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Resetting password + * When: The request fails and the error response is invalid + * Then: Unknown error should be propogated + */ + func testErrorInvalidResponse() { + NetworkStub.responseWithInvalidData(path: self.path, statusCode: 400) + + let expectation = self.expectation(description: "Calls callback with error") + call.execute(callback: { result in + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + * Given: Resetting password + * When: The request times out + * Then: Result is unknown error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Service/User/RegisterMethodSpec.swift b/KarhooSDKIntegrationTests/Service/User/RegisterMethodSpec.swift new file mode 100644 index 00000000..8d5168a4 --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/User/RegisterMethodSpec.swift @@ -0,0 +1,131 @@ +// +// RegisterMethodSpec.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class RegisterMethodSpec: XCTestCase { + + private var userService: UserService! + private let path = "/v1/directory/users" + private var call: Call! + + override func setUp() { + super.setUp() + + self.userService = Karhoo.getUserService() + let userRegistration = UserRegistration(firstName: "", lastName: "", email: "", + phoneNumber: "", locale: nil, password: "") + self.call = userService.register(userRegistration: userRegistration) + } + + /** + * When: Registering user + * And: The response returns location info json object + * Then: Result is success and returns LocationInfo object + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "AuthorisedUserInfo.json", path: path) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { [weak self] result in + XCTAssertTrue(result.isSuccess()) + self?.assertUserInfo(info: result.successValue()!) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Registering user + * And: The response returns an error + * Then: Result is error + */ + func testErrorResponse() { + let expectedError = RawKarhooErrorFactory.buildError(code: "K1001") + + NetworkStub.errorResponse(path: path, responseData: expectedError) + + let expectation = self.expectation(description: "calls the callback with error") + + call.execute(callback: { result in + let error = result.errorValue() + XCTAssertEqual(.couldNotRegister, error?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Registering user + * And: The response returns empty json object + * Then: Result is success with empty LocationInfo object + */ + func testEmptyResponse() { + NetworkStub.emptySuccessResponse(path: path) + + let expectation = self.expectation(description: "calls the callback with success") + + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Registering user + * And: The response returns invalid json object + * Then: Result is error + */ + func testInvalidResponse() { + NetworkStub.responseWithInvalidJson(path: path, statusCode: 200) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + /** + * When: Registering user + * And: The response returns time out error + * Then: Result is error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 0.5, handler: .none) + } + + private func assertUserInfo(info: UserInfo) { + XCTAssertEqual("some_userId", info.userId) + XCTAssertEqual("Some", info.firstName) + XCTAssertEqual("SomeSome", info.lastName) + XCTAssertEqual("some@email.com", info.email) + XCTAssertEqual("12345678910", info.mobileNumber) + XCTAssertEqual("string", info.locale) + XCTAssertEqual("", info.metadata) + } +} diff --git a/KarhooSDKIntegrationTests/Service/User/UserDetailsUpdateMethodSpec.swift b/KarhooSDKIntegrationTests/Service/User/UserDetailsUpdateMethodSpec.swift new file mode 100644 index 00000000..d0bff8aa --- /dev/null +++ b/KarhooSDKIntegrationTests/Service/User/UserDetailsUpdateMethodSpec.swift @@ -0,0 +1,110 @@ +// +// UserDetailsUpdateMethodSpec.swift +// KarhooSDKIntegrationTests +// +// Created by Edward Wilkins on 23/09/2019. +// Copyright © 2019 Flit Technologies Ltd. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class UserDetailsUpdateMethodSpec: XCTestCase { + + private var userService: UserService = Karhoo.getUserService() + private let path = "/v1/directory/users/some_userId" + private let authPath = "/v1/auth/token" + private let profilePath = "/v1/directory/users/me" + private var call: Call! + + override func setUp() { + super.setUp() + login() + let updateChanges = UserDetailsUpdateRequest(firstName: "BeLikeJeeves", + lastName: "Karhoolians", + phoneNumber: "7894 052433", + locale: nil, + avatarURL: nil) + + call = userService.updateUserDetails(update: updateChanges) + } + + private func login() { + NetworkStub.successResponse(jsonFile: "AuthToken.json", path: self.authPath) + NetworkStub.successResponse(jsonFile: "AuthorisedUserInfo.json", path: self.profilePath) + + let expectation = self.expectation(description: "login finishes") + userService.login(userLogin: UserLogin(username: "some", password: "some")).execute(callback: { _ in + expectation.fulfill() + }) + } + + /** + Given: Updating Profile + When: The request succeeds + Then: Success result should be propogated + */ + func testHappyPath() { + NetworkStub.successResponse(jsonFile: "UpdateUserRequest.json", path: path) + + let expectation = self.expectation(description: "Calls callback with success result") + call.execute(callback: { result in + XCTAssertTrue(result.isSuccess()) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + Given: Updating Profile + When: The request fails + Then: Failure result should be propogated + */ + func testErrorResponse() { + NetworkStub.errorResponse(path: path, responseData: RawKarhooErrorFactory.buildError(code: "K1006")) + + let expectation = self.expectation(description: "Calls callback with error result") + call.execute(callback: { result in + XCTAssertEqual(.couldNotGetUserDetails, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + Given: Updating Profile + When: The request fails and the error response is invalid + Then: Unknown Error should be propogated + */ + func testErrorInvalidResponse() { + NetworkStub.responseWithInvalidData(path: self.path, statusCode: 400) + + let expectation = self.expectation(description: "Calls callback with error") + call.execute(callback: { result in + XCTAssertEqual(.missingUserPermission, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } + + /** + Given: Updaitng Profile + When: The request times out + Then: Result is unknown error + */ + func testTimeOut() { + NetworkStub.errorResponseTimeOutConnection(path: path) + + let expectation = self.expectation(description: "calls the callback with error") + call.execute(callback: { result in + XCTAssertFalse(result.isSuccess()) + XCTAssertEqual(.unknownError, result.errorValue()?.type) + expectation.fulfill() + }) + + waitForExpectations(timeout: 1) + } +} diff --git a/KarhooSDKIntegrationTests/Utils/NetworkStubFactory.swift b/KarhooSDKIntegrationTests/Utils/NetworkStubFactory.swift new file mode 100644 index 00000000..e102ff92 --- /dev/null +++ b/KarhooSDKIntegrationTests/Utils/NetworkStubFactory.swift @@ -0,0 +1,76 @@ +// +// NetworkStub.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import OHHTTPStubs + +final class NetworkStub { + + static func emptySuccessResponse(path: String) { + stub(condition: isPath(path)) { _ in + let stubPath = OHPathForFile("Empty.json", NetworkStub.self) + return fixture(filePath: stubPath!, status: 200, headers: ["Content-Type": "application/json"]) + } + } + + static func successResponse(jsonFile: String, path: String) { + stub(condition: isPath(path)) { _ in + let stubPath = OHPathForFile(jsonFile, NetworkStub.self) + return fixture(filePath: stubPath!, status: 200, headers: ["Content-Type": "application/json"]) + } + } + + static func errorResponse(path: String, responseData: Data) { + stub(condition: isPath(path)) { _ in + return OHHTTPStubsResponse(data: responseData, + statusCode: 400, + headers: ["Content-Type": "application/json"]) + } + } + + static func responseWithInvalidData(path: String, statusCode: Int32) { + stub(condition: isPath(path)) { _ in + let stubPath = OHPathForFile("InvalidData.json", NetworkStub.self) + return fixture(filePath: stubPath!, status: statusCode, headers: ["Content-Type": "application/json"]) + } + } + + static func responseWithInvalidJson(path: String, statusCode: Int32) { + stub(condition: isPath(path)) { _ in + let stubPath = OHPathForFile("InvalidJson.json", NetworkStub.self) + return fixture(filePath: stubPath!, status: statusCode, headers: ["Content-Type": "application/json"]) + } + } + + static func errorResponseNoNetworkConnection(path: String) { + stub(condition: isPath(path)) { _ in + let notConnectedError = NSError(domain: NSURLErrorDomain, code: URLError.notConnectedToInternet.rawValue) + return OHHTTPStubsResponse(error: notConnectedError) + } + } + + static func errorResponseTimeOutConnection(path: String) { + stub(condition: isPath(path)) { _ in + let timeOutError = NSError(domain: NSURLErrorDomain, code: URLError.timedOut.rawValue) + return OHHTTPStubsResponse(error: timeOutError) + } + } + + static func response(fromFile fileName: String, + forPath path: String, + statusCode: Int32) { + stub(condition: isPath(path)) { _ in + let stubPath = OHPathForFile(fileName, NetworkStub.self) + return fixture(filePath: stubPath!, status: statusCode, headers: ["Content-Type": "application/json"]) + } + } + + static func clearStubs() { + OHHTTPStubs.removeAllStubs() + } +} diff --git a/KarhooSDKIntegrationTests/Utils/RawKarhooErrorFactory.swift b/KarhooSDKIntegrationTests/Utils/RawKarhooErrorFactory.swift new file mode 100644 index 00000000..5c0d565f --- /dev/null +++ b/KarhooSDKIntegrationTests/Utils/RawKarhooErrorFactory.swift @@ -0,0 +1,29 @@ +// +// RawKarhooErrorFactory.swift +// KarhooSDKIntegrationTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import KarhooSDK + +struct RawKarhooErrorFactory { + + static func buildError(code: String) -> Data { + return IntegrationTestError(code: code).encode()! + } +} + +private struct IntegrationTestError: KarhooError, KarhooCodableModel { + + let code: String + let message: String + + init(code: String, + message: String = "some") { + self.code = code + self.message = message + } +} diff --git a/KarhooSDKTests/Info.plist b/KarhooSDKTests/Info.plist new file mode 100644 index 00000000..3a46e35a --- /dev/null +++ b/KarhooSDKTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + NSPrincipalClass + KarhooSDKTests.UnitTestSetup + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0.5 + CFBundleVersion + 12 + + diff --git a/KarhooSDKTests/Mocks/Api/DataStore/MockUserDataStore.swift b/KarhooSDKTests/Mocks/Api/DataStore/MockUserDataStore.swift new file mode 100644 index 00000000..0a06f7b9 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/DataStore/MockUserDataStore.swift @@ -0,0 +1,69 @@ +// +// MockUserDataStore.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +class MockUserDataStore: UserDataStore { + + var userToReturn: UserInfo? + var storedUser: UserInfo? + + var credentialsToReturn: Credentials? + var storedCredentials: Credentials? + + func getCurrentUser() -> UserInfo? { + return userToReturn + } + + func getCurrentCredentials() -> Credentials? { + return credentialsToReturn + } + + private(set) var setCurrentUserCalled = false + func setCurrentUser(user: UserInfo, credentials: Credentials) { + setCurrentUserCalled = true + storedUser = user + storedCredentials = credentials + } + + var removeUserCalled = false + func removeCurrentUserAndCredentials() { + userToReturn = nil + removeUserCalled = true + } + + var addedObserver: UserStateObserver? + func add(observer: UserStateObserver) { + addedObserver = observer + } + + var removedObserver: UserStateObserver? + func remove(observer: UserStateObserver) { + removedObserver = observer + } + + var credentialsToSet: Credentials? + func set(credentials: Credentials) { + credentialsToSet = credentials + } + private(set) var updateCurrentNonce: Nonce? + private(set) var updateCurrentNonceCalled = false + func updateCurrentUserNonce(nonce: Nonce?) { + updateCurrentNonce = nonce + updateCurrentNonceCalled = true + } + + private(set) var updateUser: UserInfo? + private(set) var updateUserCalled = false + func updateUser(user: inout UserInfo) { + self.updateUser = user + updateUserCalled = true + } +} diff --git a/KarhooSDKTests/Mocks/Api/Observable/MockExecutable.swift b/KarhooSDKTests/Mocks/Api/Observable/MockExecutable.swift new file mode 100644 index 00000000..7d43f5ee --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Observable/MockExecutable.swift @@ -0,0 +1,31 @@ +// +// TestKarhooCall.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +class MockExecutable: KarhooExecutable { + + private var callback: CallbackClosure? + + var didExecute = false + func execute(callback: @escaping CallbackClosure) { + self.callback = callback as? (Result) -> Void + didExecute = true + } + + var cancelCalled = false + func cancel() { + cancelCalled = true + } + + func triggerExecution(result: Result) { + callback?(result) + } +} diff --git a/KarhooSDKTests/Mocks/Api/Observable/MockInteractor.swift b/KarhooSDKTests/Mocks/Api/Observable/MockInteractor.swift new file mode 100644 index 00000000..8c5ab683 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Observable/MockInteractor.swift @@ -0,0 +1,39 @@ +// +// MockInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +protocol MockInteractor: class, KarhooExecutable { + associatedtype ResponseType + var callbackSet: CallbackClosure? { get set } + func triggerSuccess(result: ResponseType) + func triggerFail(error: KarhooError) + var cancelCalled: Bool { get set } +} + +extension MockInteractor { + func execute(callback: @escaping CallbackClosure) { + callbackSet = { (response: Result) -> Void in + guard let response = response as? Result else { return } + callback(response) + } + } + + func triggerSuccess(result: ResponseType) { + callbackSet?(.success(result: result)) + } + + func triggerFail(error: KarhooError) { + callbackSet?(.failure(error: error)) + } + + func cancel() { + cancelCalled = true + } +} diff --git a/KarhooSDKTests/Mocks/Api/Observable/MockKarhooCodableModel.swift b/KarhooSDKTests/Mocks/Api/Observable/MockKarhooCodableModel.swift new file mode 100644 index 00000000..2ccdcbfe --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Observable/MockKarhooCodableModel.swift @@ -0,0 +1,20 @@ +// +// MockDataEncodable.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +struct MockKarhooCodableModel: KarhooCodableModel { + + let id: String + + init(id: String) { + self.id = id + } +} diff --git a/KarhooSDKTests/Mocks/Api/Observable/MockObserverBroadcaster.swift b/KarhooSDKTests/Mocks/Api/Observable/MockObserverBroadcaster.swift new file mode 100644 index 00000000..cc5416c0 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Observable/MockObserverBroadcaster.swift @@ -0,0 +1,38 @@ +// +// MockObserverBroadcaster.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockObserverBroadcaster: ObserverBroadcaster { + + var removeAllListenersCalled = false + override func removeAllListeners() { + super.removeAllListeners() + removeAllListenersCalled = true + } + + var addListenerCalled = false + override func add(listener: Observer) { + addListenerCalled = true + super.add(listener: listener) + } + + var removedListener: Observer? + override func remove(listener: Observer) { + super.remove(listener: listener) + removedListener = listener + } + + private(set) var broadcastedResult: Result? + override func broadcast(result: Result) { + super.broadcast(result: result) + broadcastedResult = result + } +} diff --git a/KarhooSDKTests/Mocks/Api/Observable/MockPollCall.swift b/KarhooSDKTests/Mocks/Api/Observable/MockPollCall.swift new file mode 100644 index 00000000..980d7e3f --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Observable/MockPollCall.swift @@ -0,0 +1,19 @@ +// +// MockKarhooPollCall.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +class MockKarhooPollCall: PollCall { + + let pollableExecutorSet: KarhooPollExecutor + override init(pollExecutor: KarhooPollExecutor) { + pollableExecutorSet = pollExecutor + super.init(pollExecutor: pollExecutor) + } +} diff --git a/KarhooSDKTests/Mocks/Api/Observable/MockPollCallFactory.swift b/KarhooSDKTests/Mocks/Api/Observable/MockPollCallFactory.swift new file mode 100644 index 00000000..dd363b44 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Observable/MockPollCallFactory.swift @@ -0,0 +1,22 @@ +// +// MockPollCallFactory.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +class MockPollCallFactory: PollCallFactory { + + var identifierSet: String? + var executableSet: KarhooExecutable? + func shared(identifier: String, + executable: KarhooExecutable) -> PollCall { + self.identifierSet = identifier + self.executableSet = executable + return MockKarhooPollCall(pollExecutor: MockPollExecutor()) + } +} diff --git a/KarhooSDKTests/Mocks/Api/Observable/MockPollExecutor.swift b/KarhooSDKTests/Mocks/Api/Observable/MockPollExecutor.swift new file mode 100644 index 00000000..04295b04 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Observable/MockPollExecutor.swift @@ -0,0 +1,36 @@ +// +// MockPollableExecutor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockPollExecutor: KarhooPollExecutor { + private var callback: CallbackClosure? + + var stopPollingCalled = false + func stopPolling() { + stopPollingCalled = true + } + + var executable: KarhooExecutable { + return MockExecutable() + } + + var startPollingCalled = false + var pollingStartedWithTime: TimeInterval? + func startPolling(pollTime: TimeInterval, callback: @escaping CallbackClosure) { + self.pollingStartedWithTime = pollTime + self.callback = callback as? (Result) -> Void + startPollingCalled = true + } + + func trigger(result: Result) { + callback?(result) + } +} diff --git a/KarhooSDKTests/Mocks/Api/Request/AvailabilitySearchMock.swift b/KarhooSDKTests/Mocks/Api/Request/AvailabilitySearchMock.swift new file mode 100644 index 00000000..38ed4998 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Request/AvailabilitySearchMock.swift @@ -0,0 +1,45 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class AvailabilitySearchMock { + + private var availabilitySearch: AvailabilitySearch + + init() { + self.availabilitySearch = AvailabilitySearch(origin: "", destination: "", dateScheduled: nil) + } + + func setOrigin(origin: String) -> AvailabilitySearchMock { + self.availabilitySearch = AvailabilitySearch( + origin: origin, + destination: availabilitySearch.destinationPlaceId, + dateScheduled: availabilitySearch.dateScheduled) + return self + } + + func setDestination(destination: String) -> AvailabilitySearchMock { + self.availabilitySearch = AvailabilitySearch( + origin: availabilitySearch.originPlaceId, + destination: destination, + dateScheduled: availabilitySearch.dateScheduled) + return self + } + + func setDateScheduled(dateScheduled: String) -> AvailabilitySearchMock { + self.availabilitySearch = AvailabilitySearch( + origin: availabilitySearch.originPlaceId, + destination: availabilitySearch.destinationPlaceId, + dateScheduled: dateScheduled) + return self + } + + func build() -> AvailabilitySearch { + return availabilitySearch + } +} diff --git a/KarhooSDKTests/Mocks/Api/Request/LoginRequestPayloadMock.swift b/KarhooSDKTests/Mocks/Api/Request/LoginRequestPayloadMock.swift new file mode 100644 index 00000000..f29e15c0 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Request/LoginRequestPayloadMock.swift @@ -0,0 +1,37 @@ +// +// LoginRequestPayloadMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +final class LoginRequestPayloadMock { + + private var loginRequest: UserLogin + + init() { + self.loginRequest = UserLogin(username: "", password: "") + } + + func set(username: String) -> LoginRequestPayloadMock { + self.loginRequest = UserLogin(username: username, + password: loginRequest.password) + + return self + } + + func set(password: String) -> LoginRequestPayloadMock { + self.loginRequest = UserLogin(username: loginRequest.username, + password: password) + + return self + } + + func build() -> UserLogin { + return self.loginRequest + } +} diff --git a/KarhooSDKTests/Mocks/Api/Request/NonceRequestPayload.swift b/KarhooSDKTests/Mocks/Api/Request/NonceRequestPayload.swift new file mode 100644 index 00000000..eaedff4c --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Request/NonceRequestPayload.swift @@ -0,0 +1,33 @@ +// +// GetNoncePayloadMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +final class NonceRequestPayloadMock { + + private var nonceRequestPayload: NonceRequestPayload + + init() { + self.nonceRequestPayload = NonceRequestPayload(payer: Payer(), organisationId: "") + } + + func set(payer: Payer) -> NonceRequestPayloadMock { + self.nonceRequestPayload = NonceRequestPayload(payer: payer, organisationId: nonceRequestPayload.organisationId) + return self + } + + func set(organisationId: String) -> NonceRequestPayloadMock { + self.nonceRequestPayload = NonceRequestPayload(payer: nonceRequestPayload.payer, organisationId: organisationId) + return self + } + + func build() -> NonceRequestPayload { + return self.nonceRequestPayload + } +} diff --git a/KarhooSDKTests/Mocks/Api/Request/PasswordResetRequestPayloadMock.swift b/KarhooSDKTests/Mocks/Api/Request/PasswordResetRequestPayloadMock.swift new file mode 100644 index 00000000..2896441a --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Request/PasswordResetRequestPayloadMock.swift @@ -0,0 +1,27 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class PasswordResetRequestPayloadMock { + + private var passwordResetRequestPayload: PasswordResetRequestPayload + + init() { + self.passwordResetRequestPayload = PasswordResetRequestPayload(email: "") + } + + func set(email: String) -> PasswordResetRequestPayloadMock { + self.passwordResetRequestPayload = PasswordResetRequestPayload(email: email) + + return self + } + + func build() -> PasswordResetRequestPayload { + return passwordResetRequestPayload + } +} diff --git a/KarhooSDKTests/Mocks/Api/Request/TripBookingMock.swift b/KarhooSDKTests/Mocks/Api/Request/TripBookingMock.swift new file mode 100644 index 00000000..d7ac1ea0 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Request/TripBookingMock.swift @@ -0,0 +1,44 @@ +// +// BookingRequestPayloadMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class TripBookingMock { + + private var tripBooking: TripBooking + + init() { + self.tripBooking = TripBooking(quoteId: "", + passengers: Passengers()) + } + + func set(quoteId: String) -> TripBookingMock { + self.tripBooking = TripBooking(quoteId: quoteId, + passengers: tripBooking.passengers) + return self + } + + func set(passengers: Passengers) -> TripBookingMock { + self.tripBooking = TripBooking(quoteId: tripBooking.quoteId, + passengers: passengers) + return self + } + + func set(flightNumber: String) -> TripBookingMock { + self.tripBooking = TripBooking(quoteId: tripBooking.quoteId, + passengers: tripBooking.passengers, + flightNumber: flightNumber) + return self + } + + func build() -> TripBooking { + return tripBooking + } +} diff --git a/KarhooSDKTests/Mocks/Api/Request/UserRegistrationMock.swift b/KarhooSDKTests/Mocks/Api/Request/UserRegistrationMock.swift new file mode 100644 index 00000000..a0343c82 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Request/UserRegistrationMock.swift @@ -0,0 +1,74 @@ +// +// UserRegistrationMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class UserRegistrationMock { + + private var userRegistration: UserRegistration + + init() { + userRegistration = UserRegistration(firstName: "", + lastName: "", + email: "", + phoneNumber: "", + locale: "", + password: "") + } + + func set(firstName: String) -> UserRegistrationMock { + create(firstName: firstName) + return self + } + + func set(lastName: String) -> UserRegistrationMock { + create(lastName: lastName) + return self + } + + func set(email: String) -> UserRegistrationMock { + create(email: email) + return self + } + + func set(phoneNumber: String) -> UserRegistrationMock { + create(phoneNumber: phoneNumber) + return self + } + + func set(locale: String) -> UserRegistrationMock { + create(locale: locale) + return self + } + + func set(password: String) -> UserRegistrationMock { + create(password: password) + return self + } + + private func create(firstName: String? = nil, + lastName: String? = nil, + email: String? = nil, + phoneNumber: String? = nil, + locale: String? = nil, + password: String? = nil) { + + userRegistration = UserRegistration(firstName: firstName ?? userRegistration.firstName, + lastName: lastName ?? userRegistration.lastName, + email: email ?? userRegistration.email, + phoneNumber: phoneNumber ?? userRegistration.phoneNumber, + locale: locale ?? userRegistration.locale, + password: password ?? userRegistration.password) + } + + func build() -> UserRegistration { + return userRegistration + } +} diff --git a/KarhooSDKTests/Mocks/Api/Request/UserUpdateMock.swift b/KarhooSDKTests/Mocks/Api/Request/UserUpdateMock.swift new file mode 100644 index 00000000..9706513f --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Request/UserUpdateMock.swift @@ -0,0 +1,65 @@ +// +// UserUpdateMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class UserUpdateMock { + + private var userUpdateDetails: UserDetailsUpdateRequest + + init() { + userUpdateDetails = UserDetailsUpdateRequest(firstName: "", + lastName: "", + phoneNumber: "", + locale: "", + avatarURL: "") + } + + func set(firstName: String) -> UserUpdateMock { + create(firstName: firstName) + return self + } + + func set(lastName: String) -> UserUpdateMock { + create(lastName: lastName) + return self + } + + func set(phoneNumber: String) -> UserUpdateMock { + create(phoneNumber: phoneNumber) + return self + } + + func set(locale: String?) -> UserUpdateMock { + create(locale: locale) + return self + } + + func set(avatarURL: String) -> UserUpdateMock { + create(avatarURL: avatarURL) + return self + } + + private func create(firstName: String? = nil, + lastName: String? = nil, + phoneNumber: String? = nil, + locale: String? = nil, + avatarURL: String? = nil) { + userUpdateDetails = UserDetailsUpdateRequest(firstName: firstName ?? userUpdateDetails.firstName, + lastName: lastName ?? userUpdateDetails.lastName, + phoneNumber: phoneNumber ?? userUpdateDetails.phoneNumber, + locale: locale ?? userUpdateDetails.locale, + avatarURL: avatarURL ?? userUpdateDetails.avatarURL) + } + + func build() -> UserDetailsUpdateRequest { + return userUpdateDetails + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/AuthTokenMock.swift b/KarhooSDKTests/Mocks/Api/Response/AuthTokenMock.swift new file mode 100644 index 00000000..649bc441 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/AuthTokenMock.swift @@ -0,0 +1,49 @@ +// +// AuthTokenMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class AuthTokenMock { + + private var authToken: AuthToken + + init() { + authToken = AuthToken(accessToken: "", expiresIn: 0, refreshToken: "", refreshExpiresIn: 0) + } + + func set(accessToken: String) -> AuthTokenMock { + create(accessToken: accessToken) + return self + } + + func set(expiresIn: Int) -> AuthTokenMock { + create(expiresIn: expiresIn) + return self + } + + func set(refreshToken: String) -> AuthTokenMock { + create(refreshToken: refreshToken) + return self + } + + func build() -> AuthToken { + return authToken + } + + private func create(accessToken: String? = nil, + expiresIn: Int? = nil, + refreshToken: String? = nil, + refreshExpiresIn: Int = 0) { + authToken = AuthToken(accessToken: accessToken ?? authToken.accessToken, + expiresIn: expiresIn ?? authToken.expiresIn, + refreshToken: refreshToken ?? authToken.refreshToken, + refreshExpiresIn: refreshExpiresIn) + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/CategoriesMock.swift b/KarhooSDKTests/Mocks/Api/Response/CategoriesMock.swift new file mode 100644 index 00000000..f4abd317 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/CategoriesMock.swift @@ -0,0 +1,25 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +class CategoriesMock { + private var categories: Categories + + init() { + self.categories = Categories(categories: []) + } + + func set(categories: [String]) -> CategoriesMock { + self.categories = Categories(categories: categories) + return self + } + + func build() -> Categories { + return categories + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/DriverTrackingInfoMock.swift b/KarhooSDKTests/Mocks/Api/Response/DriverTrackingInfoMock.swift new file mode 100644 index 00000000..8c6a72d0 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/DriverTrackingInfoMock.swift @@ -0,0 +1,46 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class DriverTrackingInfoMock { + + private var driverTrackingInfo: DriverTrackingInfo + + init() { + self.driverTrackingInfo = DriverTrackingInfo(position: Position(latitude: 0, longitude: 0), + originEta: 0, + destinationEta: 0) + } + + func setPosition(position: Position) -> DriverTrackingInfoMock { + create(position: position) + return self + } + + func setOriginEta(originEta: Int) -> DriverTrackingInfoMock { + create(originEta: originEta) + return self + } + + func setDestinationEta(destinationEta: Int) -> DriverTrackingInfoMock { + create(destinationEta: destinationEta) + return self + } + + func create(position: Position? = nil, + originEta: Int? = nil, + destinationEta: Int? = nil) { + driverTrackingInfo = DriverTrackingInfo(position: position ?? driverTrackingInfo.position, + originEta: originEta ?? driverTrackingInfo.originEta, + destinationEta: destinationEta ?? driverTrackingInfo.destinationEta) + } + + func build() -> DriverTrackingInfo { + return driverTrackingInfo + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/LocationInfoMock.swift b/KarhooSDKTests/Mocks/Api/Response/LocationInfoMock.swift new file mode 100644 index 00000000..a054c73a --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/LocationInfoMock.swift @@ -0,0 +1,82 @@ +// +// LocationInfoMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class LocationInfoMock { + + private var locationInfo: LocationInfo + + init() { + self.locationInfo = LocationInfo(placeId: "", timeZoneIdentifier: "") + } + + func set(placeId: String) -> LocationInfoMock { + create(placeId: placeId) + return self + } + + func set(timeZoneIdentifier: String) -> LocationInfoMock { + create(timeZoneIdentifier: timeZoneIdentifier) + return self + } + + func set(position: Position) -> LocationInfoMock { + create(position: position) + return self + } + + func set(poiType: PoiType) -> LocationInfoMock { + create(poiType: poiType) + return self + } + + func set(address: LocationInfoAddress) -> LocationInfoMock { + create(address: address) + return self + } + + func set(details: PoiDetails) -> LocationInfoMock { + create(details: details) + return self + } + + func set(meetingPoint: MeetingPoint) -> LocationInfoMock { + create(meetingPoint: meetingPoint) + return self + } + + func set(instructions: String) -> LocationInfoMock { + create(instructions: instructions) + return self + } + + private func create(placeId: String? = nil, + timeZoneIdentifier: String? = nil, + position: Position? = nil, + poiType: PoiType? = nil, + address: LocationInfoAddress? = nil, + details: PoiDetails? = nil, + meetingPoint: MeetingPoint? = nil, + instructions: String? = nil) { + locationInfo = LocationInfo(placeId: placeId ?? locationInfo.placeId, + timeZoneIdentifier: timeZoneIdentifier ?? locationInfo.timeZoneIdentifier, + position: position ?? locationInfo.position, + poiType: poiType ?? locationInfo.poiType, + address: address ?? locationInfo.address, + details: details ?? locationInfo.details, + meetingPoint: meetingPoint ?? locationInfo.meetingPoint, + instructions: instructions ?? locationInfo.instructions) + } + + func build() -> LocationInfo { + return locationInfo + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/OrganisationMock.swift b/KarhooSDKTests/Mocks/Api/Response/OrganisationMock.swift new file mode 100644 index 00000000..7cdf1db8 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/OrganisationMock.swift @@ -0,0 +1,49 @@ +// +// OrganisationMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +final class OrganisationMock { + + private var organisation: Organisation + + init() { + self.organisation = Organisation(id: "", + name: "", + roles: [""]) + } + + func set(id: String) -> OrganisationMock { + create(id: id) + return self + } + + func set(name: String) -> OrganisationMock { + create(name: name) + return self + } + + func set(roles: [String]) -> OrganisationMock { + create(roles: roles) + return self + } + + func build() -> Organisation { + return organisation + } + + private func create(id: String = "", + name: String = "", + roles: [String] = [""]) { + self.organisation = Organisation(id: id.isEmpty ? organisation.id : id, + name: name.isEmpty ? organisation.name : name, + roles: roles.isEmpty ? organisation.roles : roles) + + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/PaymentSDKTokenMock.swift b/KarhooSDKTests/Mocks/Api/Response/PaymentSDKTokenMock.swift new file mode 100644 index 00000000..4177dbbe --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/PaymentSDKTokenMock.swift @@ -0,0 +1,25 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class PaymentSDKTokenMock { + private var paymentSDKToken: PaymentSDKToken + + init() { + self.paymentSDKToken = PaymentSDKToken(token: "") + } + + func set(token: String) -> PaymentSDKTokenMock { + paymentSDKToken = PaymentSDKToken(token: token) + return self + } + + func build() -> PaymentSDKToken { + return paymentSDKToken + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/QuoteIdMock.swift b/KarhooSDKTests/Mocks/Api/Response/QuoteIdMock.swift new file mode 100644 index 00000000..bd8718e0 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/QuoteIdMock.swift @@ -0,0 +1,27 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +class QuoteIdMock { + + private var quoteId: QuoteListId + + init() { + self.quoteId = QuoteListId(identifier: "", validityTime: 0) + } + + func set(quoteId: String, validityTime: Int = 0) -> QuoteIdMock { + self.quoteId = QuoteListId(identifier: quoteId, + validityTime: validityTime) + return self + } + + func build() -> QuoteListId { + return quoteId + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/QuoteListMock.swift b/KarhooSDKTests/Mocks/Api/Response/QuoteListMock.swift new file mode 100644 index 00000000..44d439aa --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/QuoteListMock.swift @@ -0,0 +1,66 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class QuoteListMock { + + private var quoteList: QuoteList + + init() { + self.quoteList = QuoteList() + } + + func set(status: String) -> QuoteListMock { + createDataForQuoteList(status: status) + return self + } + + func set(quoteListId: String) -> QuoteListMock { + createDataForQuoteList(listId: quoteListId) + return self + } + + func set(validity: Int) -> QuoteListMock { + createDataForQuoteList(validity: validity) + return self + } + + func add(quoteItem: Quote) -> QuoteListMock { + createDataForQuoteList(quoteItem: quoteItem) + return self + } + + func build() -> QuoteList { + return quoteList + } + + private func createDataForQuoteList(quoteItem: Quote = Quote(), + status: String? = nil, + listId: String? = nil, + validity: Int? = nil) { + + let status = status ?? quoteList.status + let listId = listId ?? quoteList.listId + let validity = validity ?? quoteList.validity + let quoteListItems = getQuoteItemList(quoteItem: quoteItem) + + self.quoteList = QuoteList( + quoteItems: quoteListItems, + listId: listId, + status: status, + validity: validity) + } + + private func getQuoteItemList(quoteItem: Quote) -> [Quote] { + if !quoteItem.equals(Quote()) { + let updatedList = quoteList.quoteItems + [quoteItem] + return updatedList + } + return quoteList.quoteItems + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/QuoteMock.swift b/KarhooSDKTests/Mocks/Api/Response/QuoteMock.swift new file mode 100644 index 00000000..c6527f43 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/QuoteMock.swift @@ -0,0 +1,129 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class QuoteMock { + + private var quote: Quote + + init() { + self.quote = Quote() + } + + func build() -> Quote { + return quote + } + + func set(quoteId: String) -> QuoteMock { + create(quoteId: quoteId) + return self + } + + func set(fleetId: String) -> QuoteMock { + create(fleetId: fleetId) + return self + } + + func set(availabilityId: String) -> QuoteMock { + create(availabilityId: availabilityId) + return self + } + + func set(phoneNumber: String) -> QuoteMock { + create(phoneNumber: phoneNumber) + return self + } + + func set(fleetName: String) -> QuoteMock { + create(fleetName: fleetName) + return self + } + + func set(supplierLogoUrl: String) -> QuoteMock { + create(supplierLogoUrl: supplierLogoUrl) + return self + } + + func set(vehicleClass: String) -> QuoteMock { + create(vehicleClass: vehicleClass) + return self + } + + func set(quoteType: QuoteType) -> QuoteMock { + create(quoteType: quoteType) + return self + } + + func set(highPrice: Int) -> QuoteMock { + create(highPrice: highPrice) + return self + } + + func set(lowPrice: Int) -> QuoteMock { + create(lowPrice: lowPrice) + return self + } + + func set(currencyCode: String) -> QuoteMock { + create(currencyCode: currencyCode) + return self + } + + func set(qtaHighMinutes: Int) -> QuoteMock { + create(qtaHighMinutes: qtaHighMinutes) + return self + } + + func set(qtaLowMinutes: Int) -> QuoteMock { + create(qtaLowMinutes: qtaLowMinutes) + return self + } + + func set(termsAndConditionsUrl: String) -> QuoteMock { + create(termsConditionsUrl: termsAndConditionsUrl) + return self + } + + func set(categoryName: String) -> QuoteMock { + create(categoryName: categoryName) + return self + } + + private func create(quoteId: String? = nil, + fleetId: String? = nil, + availabilityId: String? = nil, + phoneNumber: String? = nil, + fleetName: String? = nil, + supplierLogoUrl: String? = nil, + vehicleClass: String? = nil, + quoteType: QuoteType? = nil, + highPrice: Int? = nil, + lowPrice: Int? = nil, + currencyCode: String? = nil, + qtaHighMinutes: Int? = nil, + qtaLowMinutes: Int? = nil, + termsConditionsUrl: String? = nil, + categoryName: String? = nil) { + self.quote = Quote( + quoteId: quoteId ?? quote.quoteId, + fleetId: fleetId ?? quote.fleetId, + availabilityId: availabilityId ?? quote.availabilityId, + fleetName: fleetName ?? quote.fleetName, + phoneNumber: phoneNumber ?? quote.phoneNumber, + supplierLogoUrl: supplierLogoUrl ?? quote.supplierLogoUrl, + vehicleClass: vehicleClass ?? quote.vehicleClass, + quoteType: quoteType ?? quote.quoteType, + highPrice: highPrice ?? Int(Double(quote.highPrice)/0.01), + lowPrice: lowPrice ?? Int(Double(quote.lowPrice)/0.01), + currencyCode: currencyCode ?? quote.currencyCode, + qtaHighMinutes: qtaHighMinutes ?? quote.qtaHighMinutes, + qtaLowMinutes: qtaLowMinutes ?? quote.qtaLowMinutes, + termsConditionsURL: termsConditionsUrl ?? quote.termsConditionsUrl, + categoryName: categoryName ?? quote.categoryName) + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/RefreshTokenMock.swift b/KarhooSDKTests/Mocks/Api/Response/RefreshTokenMock.swift new file mode 100644 index 00000000..a883ba4d --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/RefreshTokenMock.swift @@ -0,0 +1,44 @@ +// +// RefreshTokenMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +final class RefreshTokenMock { + + private var refreshToken: RefreshToken + + init() { + self.refreshToken = RefreshToken(accessToken: "", expiresIn: 0) + } + + func set(accessToken: String) -> RefreshTokenMock { + create(accessToken: accessToken) + return self + } + + func set(expiresIn: Int) -> RefreshTokenMock { + create(expiresIn: expiresIn) + return self + } + + func build() -> RefreshToken { + return refreshToken + } + + func getData() -> Data { + return refreshToken.encode() ?? Data() + } + + private func create(accessToken: String? = nil, + expiresIn: Int? = nil) { + self.refreshToken = RefreshToken(accessToken: accessToken ?? refreshToken.accessToken, + expiresIn: expiresIn ?? refreshToken.expiresIn) + } + +} diff --git a/KarhooSDKTests/Mocks/Api/Response/TripInfoMock.swift b/KarhooSDKTests/Mocks/Api/Response/TripInfoMock.swift new file mode 100644 index 00000000..1602c3ec --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/TripInfoMock.swift @@ -0,0 +1,111 @@ +// +// TripInfoMock.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class TripInfoMock { + + private var tripInfo: TripInfo + + init() { + self.tripInfo = TripInfo(tripId: "", + displayId: "", + origin: TripLocationDetails(), + destination: TripLocationDetails(), + dateScheduled: nil, + state: .unknown, + quote: TripQuote(), + vehicle: Vehicle(), + fleetInfo: FleetInfo(), + meetingPoint: nil) + } + + func set(tripId: String) -> TripInfoMock { + create(tripId: tripId) + return self + } + + func set(displayId: String) -> TripInfoMock { + create(displayId: displayId) + return self + } + + func set(origin: TripLocationDetails) -> TripInfoMock { + create(origin: origin) + return self + } + + func set(destination: TripLocationDetails) -> TripInfoMock { + create(destination: destination) + return self + } + + func set(dateScheduled: Date) -> TripInfoMock { + create(dateScheduled: dateScheduled) + return self + } + + func set(state: TripState) -> TripInfoMock { + create(state: state) + return self + } + + func set(quote: TripQuote) -> TripInfoMock { + create(quote: quote) + return self + } + + func set(vehicle: Vehicle) -> TripInfoMock { + create(vehicle: vehicle) + return self + } + + func set(driver: Driver) -> TripInfoMock { + create(driver: driver) + return self + } + + func set(fleetInfo: FleetInfo) -> TripInfoMock { + create(fleetInfo: fleetInfo) + return self + } + + func set(meethingPoint: MeetingPoint) -> TripInfoMock { + create(meetingPoint: meethingPoint) + return self + } + + func create(tripId: String? = nil, + displayId: String? = nil, + origin: TripLocationDetails? = nil, + destination: TripLocationDetails? = nil, + dateScheduled: Date? = nil, + state: TripState? = nil, + quote: TripQuote? = nil, + vehicle: Vehicle? = nil, + driver: Driver? = nil, + fleetInfo: FleetInfo? = nil, + meetingPoint: MeetingPoint? = nil) { + tripInfo = TripInfo(tripId: tripId ?? tripInfo.tripId, + displayId: displayId ?? tripInfo.displayId, + origin: origin ?? tripInfo.origin, + destination: destination ?? tripInfo.destination , + dateScheduled: dateScheduled ?? tripInfo.dateScheduled, + state: state ?? tripInfo.state, + quote: quote ?? tripInfo.tripQuote, + vehicle: vehicle ?? tripInfo.vehicle, + fleetInfo: fleetInfo ?? tripInfo.fleetInfo, + meetingPoint: meetingPoint ?? tripInfo.meetingPoint) + } + + func build() -> TripInfo { + return tripInfo + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/TripStatusMock.swift b/KarhooSDKTests/Mocks/Api/Response/TripStatusMock.swift new file mode 100644 index 00000000..feb14139 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/TripStatusMock.swift @@ -0,0 +1,29 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class TripStatusMock { + private var tripStatus: TripStatus + + init() { + self.tripStatus = TripStatus(status: .unknown) + } + + func set(state: TripState) -> TripStatusMock { + self.tripStatus = TripStatus(status: state) + return self + } + + func build() -> TripStatus { + return tripStatus + } + + func getTripStatus() -> TripStatus { + return tripStatus + } +} diff --git a/KarhooSDKTests/Mocks/Api/Response/UserInfoMock.swift b/KarhooSDKTests/Mocks/Api/Response/UserInfoMock.swift new file mode 100644 index 00000000..86972d09 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Response/UserInfoMock.swift @@ -0,0 +1,85 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class UserInfoMock { + + private var user: UserInfo + + init() { + self.user = UserInfo(userId: "", + firstName: "", + lastName: "", + email: "", + mobileNumber: "", + organisations: []) + } + + func set(userId: String) -> UserInfoMock { + create(userId: userId) + return self + } + + func set(firstName: String) -> UserInfoMock { + create(firstName: firstName) + return self + } + + func set(lastName: String) -> UserInfoMock { + create(lastName: lastName) + return self + } + + func set(email: String) -> UserInfoMock { + create(email: email) + return self + } + + func set(mobile: String) -> UserInfoMock { + create(mobile: mobile) + return self + } + + func set(organisation: [Organisation]) -> UserInfoMock { + create(organisations: organisation) + return self + } + + func set(nonce: Nonce?) -> UserInfoMock { + create(nonce: nonce) + return self + } + + func set(externalId: String) -> UserInfoMock { + create(externalId: externalId) + return self + } + + func build() -> UserInfo { + return user + } + + private func create(userId: String = "", + firstName: String = "", + lastName: String = "", + email: String = "", + mobile: String = "", + organisations: [Organisation] = [], + nonce: Nonce? = nil, + externalId: String = "") { + self.user = UserInfo(userId: userId.isEmpty ? user.userId : userId, + firstName: firstName.isEmpty ? user.firstName : firstName, + lastName: lastName.isEmpty ? user.lastName : lastName, + email: email.isEmpty ? user.email : email, + mobileNumber: mobile.isEmpty ? user.mobileNumber : user.mobileNumber, + organisations: organisations.isEmpty ? user.organisations : organisations, + externalId: externalId) + + self.user.nonce = nonce + } +} diff --git a/KarhooSDKTests/Mocks/Api/Util/MockAppStateNotifier.swift b/KarhooSDKTests/Mocks/Api/Util/MockAppStateNotifier.swift new file mode 100644 index 00000000..01995d99 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Util/MockAppStateNotifier.swift @@ -0,0 +1,29 @@ +// +// MockAppStateNotifier.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import KarhooSDK + +class MockAppStateNotifier: AppStateNotifierProtocol { + private(set) var listener: AppStateChangeDelegate? + + func register(listener: AppStateChangeDelegate) { + self.listener = listener + } + + func remove(listener: AppStateChangeDelegate) { + self.listener = nil + } + + func signalAppDidBecomeActive() { + listener?.appDidBecomeActive() + } + + func signalAppWillResignActive() { + listener?.appWillResignActive() + } +} diff --git a/KarhooSDKTests/Mocks/Api/Util/MockBroadcaster.swift b/KarhooSDKTests/Mocks/Api/Util/MockBroadcaster.swift new file mode 100644 index 00000000..f0deb518 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Util/MockBroadcaster.swift @@ -0,0 +1,34 @@ +// +// MockBroadcaster.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +class MockBroadcaster: Broadcaster { + var lastListenerAdded: AnyObject? + var lastListenerRemoved: AnyObject? + + override func add(listener: AnyObject) { + lastListenerAdded = listener + super.add(listener: listener) + } + + override func remove(listener: AnyObject) { + lastListenerRemoved = listener + super.remove(listener: listener) + } + + var hasListenersReturnValue: Bool? + override func hasListeners() -> Bool { + if hasListenersReturnValue == nil { + return super.hasListeners() + } + + return hasListenersReturnValue! + } +} diff --git a/KarhooSDKTests/Mocks/Api/Util/MockCLLocationManager.swift b/KarhooSDKTests/Mocks/Api/Util/MockCLLocationManager.swift new file mode 100644 index 00000000..c920cac9 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Util/MockCLLocationManager.swift @@ -0,0 +1,58 @@ +// +// MockCLLocationManager.swift +// Karhoo +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import CoreLocation + +class MockCLLocationManager: CLLocationManager { + + private var _delegate: CLLocationManagerDelegate? // swiftlint:disable:this weak_delegate + override var delegate: CLLocationManagerDelegate? { + set { + _delegate = newValue + } + + get { + return _delegate + } + } + + var mockedLocation: CLLocation? + override var location: CLLocation? { + return mockedLocation + } + + func triggerUpdate(location: CLLocation?) { + var locationList = [CLLocation]() + if let location = location { + locationList.append(location) + } + _delegate?.locationManager?(self, didUpdateLocations: locationList) + } + + var updateLocationStarted = false + override func startUpdatingLocation() { + updateLocationStarted = true + } + + static var authorizationStatusToReturn: CLAuthorizationStatus = .notDetermined + override class func authorizationStatus() -> CLAuthorizationStatus { + return authorizationStatusToReturn + } + + static var locationEnabledReturnValue: Bool = false + override class func locationServicesEnabled() -> Bool { + return locationEnabledReturnValue + } + + class func reset() { + locationEnabledReturnValue = false + authorizationStatusToReturn = .notDetermined + } + +} diff --git a/KarhooSDKTests/Mocks/Api/Util/MockTimingScheduler.swift b/KarhooSDKTests/Mocks/Api/Util/MockTimingScheduler.swift new file mode 100644 index 00000000..e05ba3f7 --- /dev/null +++ b/KarhooSDKTests/Mocks/Api/Util/MockTimingScheduler.swift @@ -0,0 +1,39 @@ +// +// MockTimingScheduler.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +final class MockTimingScheduler: TimingScheduler { + + private(set) var scheduled = false + private(set) var capturedBlock: (() -> Void)? + private(set) var capturedRepeats: Bool? + private(set) var timeIntervalSet: TimeInterval? + func fireAndSchedule(block: @escaping () -> Void, in timeInterval: TimeInterval, repeats: Bool) { + scheduled = true + capturedBlock = block + capturedRepeats = repeats + invalidateCalled = false + timeIntervalSet = timeInterval + } + + func fire() { + if capturedRepeats == false { + scheduled = false + } + + capturedBlock?() + } + + private(set) var invalidateCalled = false + func invalidate() { + scheduled = false + invalidateCalled = true + } +} diff --git a/KarhooSDKTests/Mocks/Error.swift b/KarhooSDKTests/Mocks/Error.swift new file mode 100644 index 00000000..a801da52 --- /dev/null +++ b/KarhooSDKTests/Mocks/Error.swift @@ -0,0 +1,22 @@ +// +// Errors.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +struct MockError: KarhooError, Equatable { + var code: String + var message: String + var userMessage: String + + static func == (lhs: MockError, rhs: MockError) -> Bool { + return lhs.code == rhs.code + && lhs.message == rhs.message + && lhs.userMessage == rhs.userMessage + } +} diff --git a/KarhooSDKTests/Mocks/MockUserDefaults.swift b/KarhooSDKTests/Mocks/MockUserDefaults.swift new file mode 100644 index 00000000..4ea0ce2f --- /dev/null +++ b/KarhooSDKTests/Mocks/MockUserDefaults.swift @@ -0,0 +1,23 @@ +// +// MockUserDefaults.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +final class MockUserDefaults: UserDefaults { + var synchronizeCalled = false + override func synchronize() -> Bool { + synchronizeCalled = true + return super.synchronize() + } + + var setForKeyCalled = false + override func set(_ value: Any?, forKey defaultName: String) { + super.set(value, forKey: defaultName) + setForKeyCalled = true + } +} diff --git a/KarhooSDKTests/Mocks/Networking/MockAccessTokenProvider.swift b/KarhooSDKTests/Mocks/Networking/MockAccessTokenProvider.swift new file mode 100644 index 00000000..9ac8555a --- /dev/null +++ b/KarhooSDKTests/Mocks/Networking/MockAccessTokenProvider.swift @@ -0,0 +1,16 @@ +// +// MockAccessTokenProvider.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import KarhooSDK + +final public class MockAccessTokenProvider: AccessTokenProvider { + public var accessToken: AccessToken? { + return AccessToken(token: "MockAuthToken") + } +} diff --git a/KarhooSDKTests/Mocks/Networking/MockHttpClient.swift b/KarhooSDKTests/Mocks/Networking/MockHttpClient.swift new file mode 100644 index 00000000..27d1afcf --- /dev/null +++ b/KarhooSDKTests/Mocks/Networking/MockHttpClient.swift @@ -0,0 +1,35 @@ +import Foundation + +@testable import KarhooSDK + +/** + * Unlike JSONHttpClient, MockHttpClient doesn't add any headers + * (in particular it doesn't add an Authentication header) + */ +class MockHttpClient: HttpClient { + + var networkRequestToReturn: NetworkRequest? + + func sendRequest(endpoint: APIEndpoint, + data: Data? = nil, + urlComponents: URLComponents? = nil, + completion: @escaping CallbackClosure) -> NetworkRequest? { + sendRequestsCount += 1 + lastRequestEndpoint = endpoint + lastRequestBody = data + lastCompletion = completion + return networkRequestToReturn + } + + private(set) var sendRequestsCount: Int = 0 + private(set) var lastRequestEndpoint: APIEndpoint? + var lastRequestMethod: HttpMethod? { + return lastRequestEndpoint?.method + } + var lastRequestPath: String? { + return lastRequestEndpoint?.path + } + private(set) var lastRequestBody: Data? + private(set) var lastCompletion: CallbackClosure? + +} diff --git a/KarhooSDKTests/Mocks/Networking/MockNetworkDateFormatter.swift b/KarhooSDKTests/Mocks/Networking/MockNetworkDateFormatter.swift new file mode 100644 index 00000000..8e73f34b --- /dev/null +++ b/KarhooSDKTests/Mocks/Networking/MockNetworkDateFormatter.swift @@ -0,0 +1,20 @@ +// +// MockNetworkDateFormatter.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockNetworkDateFormatter: NetworkDateFormatter { + var stringToReturn: String = TestUtil.getRandomString() + var dateToConvert: Date? + func toString(from date: Date) -> String { + dateToConvert = date + return stringToReturn + } +} diff --git a/KarhooSDKTests/Mocks/Networking/MockNetworkRequest.swift b/KarhooSDKTests/Mocks/Networking/MockNetworkRequest.swift new file mode 100644 index 00000000..8b2fd7d0 --- /dev/null +++ b/KarhooSDKTests/Mocks/Networking/MockNetworkRequest.swift @@ -0,0 +1,18 @@ +// +// MockNetworkRequest.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import KarhooSDK + +final class MockNetworkRequest: NetworkRequest { + + private(set) var cancelCalled = false + + func cancel() { + cancelCalled = true + } +} diff --git a/KarhooSDKTests/Mocks/Networking/MockRefreshTokenRequest.swift b/KarhooSDKTests/Mocks/Networking/MockRefreshTokenRequest.swift new file mode 100644 index 00000000..66f6baf9 --- /dev/null +++ b/KarhooSDKTests/Mocks/Networking/MockRefreshTokenRequest.swift @@ -0,0 +1,85 @@ +// +// MockRefreshTokenRequest.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +class MockRefreshTokenRequest: RequestSender { + + var payloadSet: KarhooCodableModel? + var endpointSet: APIEndpoint? + var requestCalled = false + var beforeResponse: (() -> Void)! + private var errorCallback: ((KarhooError) -> Void)? + private var valueCallback: ((Any) -> Void)? + + func request(payload: KarhooCodableModel?, + endpoint: APIEndpoint, + callback: @escaping CallbackClosure) { + fatalError("Not Implemented") + } + + func requestAndDecode(payload: KarhooCodableModel?, + endpoint: APIEndpoint, + callback: @escaping CallbackClosure) { + requestCalled = true + payloadSet = payload + endpointSet = endpoint + self.errorCallback = { error in + callback(.failure(error: error)) + } + self.valueCallback = { value in + guard let value = value as? T else { return } + callback(.success(result: value)) + } + } + + func encodedRequest(endpoint: APIEndpoint, + body: URLComponents?, + callback: @escaping CallbackClosure) { + requestCalled = true + endpointSet = endpoint + self.errorCallback = { error in + callback(.failure(error: error)) + } + self.valueCallback = { value in + guard let value = value as? T else { return } + callback(.success(result: value)) + } + } + + func cancelNetworkRequest() {} + + func success(response: KarhooCodableModel) { + beforeResponse?() + valueCallback?(response) + } + + func fail(error: KarhooError = TestUtil.getRandomError()) { + beforeResponse?() + errorCallback?(error) + } + + func assertRequestSend(endpoint: APIEndpoint, + method: HttpMethod? = nil, + path: String? = nil, + payload: KarhooCodableModel? = nil) { + XCTAssertTrue(requestCalled) + XCTAssertEqual(endpoint, endpointSet) + XCTAssertEqual(method ?? endpoint.method, endpointSet?.method) + XCTAssertEqual(path ?? endpoint.path, endpointSet?.path) + + // test payload only if it's set + if let payload = payload { + XCTAssertNotNil(payloadSet) + XCTAssertEqual(payload.encode(), payloadSet?.encode()) + } + } +} diff --git a/KarhooSDKTests/Mocks/Networking/MockRequestSender.swift b/KarhooSDKTests/Mocks/Networking/MockRequestSender.swift new file mode 100644 index 00000000..b743f61e --- /dev/null +++ b/KarhooSDKTests/Mocks/Networking/MockRequestSender.swift @@ -0,0 +1,122 @@ +// +// MockRequestSender.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +final class MockRequestSender: RequestSender { + + var payloadSet: KarhooCodableModel? + var endpointSet: APIEndpoint? + // regular request + var requestCallback: CallbackClosure? + var requestCalled = false + func request(payload: KarhooCodableModel?, + endpoint: APIEndpoint, + callback: @escaping CallbackClosure) { + requestCalled = true + payloadSet = payload + endpointSet = endpoint + requestCallback = callback + } + + var requestAndDecodeCalled = false + + // Using Any to avoid specifying class generics for each instance of TestRequestSender + private var errorCallback: ((KarhooError) -> Void)? + private var valueCallback: ((Any) -> Void)? + func requestAndDecode(payload: KarhooCodableModel?, + endpoint: APIEndpoint, + callback: @escaping CallbackClosure) { + self.requestAndDecodeCalled = true + self.payloadSet = payload + self.endpointSet = endpoint + + self.errorCallback = { error in + callback(.failure(error: error)) + } + self.valueCallback = { value in + guard let value = value as? T else { return } + callback(.success(result: value)) + } + } + + private var encodedRequestCallback: ((Any) -> Void)? + private var encodedRequestErrorCallback: ((KarhooError) -> Void)? + var encodedRequestCalled = false + var bodySet: URLComponents? + func encodedRequest(endpoint: APIEndpoint, body: URLComponents?, callback: @escaping CallbackClosure) { + self.bodySet = body + self.endpointSet = endpoint + + self.encodedRequestErrorCallback = { error in + callback(.failure(error: error)) + } + + self.encodedRequestCallback = { value in + guard let value = value as? T else { return } + callback(.success(result: value)) + } + } + + var cancelNetworkRequestCalled = false + func cancelNetworkRequest() { + cancelNetworkRequestCalled = true + } + + // MARK: Test functions + func triggerSuccess(response: Data) { + let success = Result.success(result: HttpResponse(code: 200, data: response)) + requestCallback?(success) + } + + func triggerSuccessWithDecoded(value: KarhooCodableModel) { + valueCallback?(value) + } + + func triggerFail(error: KarhooError = TestUtil.getRandomError()) { + requestCallback?(.failure(error: error)) + errorCallback?(error) + encodedRequestErrorCallback?(error) + } + + func triggerEncodedRequestSuccess(value: KarhooCodableModel) { + encodedRequestCallback?(value) + } + + func assertRequestSend(endpoint: APIEndpoint, + method: HttpMethod? = nil, + path: String? = nil, + payload: KarhooCodableModel? = nil) { + XCTAssertTrue(requestCalled) + XCTAssertEqual(endpoint, endpointSet) + XCTAssertEqual(method ?? endpoint.method, endpointSet?.method) + XCTAssertEqual(path ?? endpoint.path, endpointSet?.path) + + // test payload only if it's set + if let payload = payload { + XCTAssertNotNil(payloadSet) + XCTAssertEqual(payload.encode(), payloadSet?.encode()) + } + } + + func assertRequestSendAndDecoded(endpoint: APIEndpoint, + method: HttpMethod, + payload: KarhooCodableModel? = nil) { + XCTAssertTrue(requestAndDecodeCalled) + XCTAssertEqual(endpoint, endpointSet) + XCTAssertEqual(endpoint.method, endpointSet?.method) + + // test payload only if it's set + if let payload = payload { + XCTAssertNotNil(payloadSet) + XCTAssertEqual(payload.encode(), payloadSet?.encode()) + } + } +} diff --git a/KarhooSDKTests/Mocks/Networking/MockSDKConfig.swift b/KarhooSDKTests/Mocks/Networking/MockSDKConfig.swift new file mode 100644 index 00000000..2f4852af --- /dev/null +++ b/KarhooSDKTests/Mocks/Networking/MockSDKConfig.swift @@ -0,0 +1,28 @@ +// +// MockKarhooSDKConfiguration.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +@testable import KarhooSDK + +class MockSDKConfig: KarhooSDKConfiguration { + + let authMethod: AuthenticationMethod + static let tokenExchangeSettings = TokenExchangeSettings(clientId: "mock-client", scope: "1234") + + init(authMethod: AuthenticationMethod = .karhooUser) { + self.authMethod = authMethod + } + + func environment() -> KarhooEnvironment { + return .sandbox + } + + func authenticationMethod() -> AuthenticationMethod { + return authMethod + } + +} diff --git a/KarhooSDKTests/Mocks/Networking/MockURLSessionSender.swift b/KarhooSDKTests/Mocks/Networking/MockURLSessionSender.swift new file mode 100644 index 00000000..2d52b5fd --- /dev/null +++ b/KarhooSDKTests/Mocks/Networking/MockURLSessionSender.swift @@ -0,0 +1,15 @@ +import Foundation + +@testable import KarhooSDK + +public class MockURLSessionSender: URLSessionSender { + private(set) var lastRequest: URLRequest? + var mockResponse: (data: Data?, response: URLResponse?, error: Error?)? //swiftlint:disable:this large_tuple + + public func send(request: URLRequest, + completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> NetworkRequest { + lastRequest = request + completion(mockResponse?.0, mockResponse?.1, mockResponse?.2) + return MockNetworkRequest() + } +} diff --git a/KarhooSDKTests/Mocks/ObjectTestFactory.swift b/KarhooSDKTests/Mocks/ObjectTestFactory.swift new file mode 100644 index 00000000..3ac8c7a4 --- /dev/null +++ b/KarhooSDKTests/Mocks/ObjectTestFactory.swift @@ -0,0 +1,22 @@ +// +// ObjectTestFactory.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import CoreLocation + +@testable import KarhooSDK + +class ObjectTestFactory { + + class func getRandomCredentials(expiryDate: Date? = TestUtil.getRandomDate(), + withRefreshToken refreshToken: Bool = true) -> Credentials { + return Credentials(accessToken: TestUtil.getRandomString(), + expiryDate: expiryDate, + refreshToken: (refreshToken ? TestUtil.getRandomString() : nil)) + } +} diff --git a/KarhooSDKTests/Mocks/Service/Address/LocationInfo/MockLocationInfoInteractor.swift b/KarhooSDKTests/Mocks/Service/Address/LocationInfo/MockLocationInfoInteractor.swift new file mode 100644 index 00000000..ea68c277 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Address/LocationInfo/MockLocationInfoInteractor.swift @@ -0,0 +1,22 @@ +// +// MockLocationInfoInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockLocationInfoInteractor: LocationInfoInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var locationInfoSearchSet: LocationInfoSearch? + func set(locationInfoSearch: LocationInfoSearch) { + self.locationInfoSearchSet = locationInfoSearch + } +} diff --git a/KarhooSDKTests/Mocks/Service/Address/PlaceSearch/MockPlaceSearchInteractor.swift b/KarhooSDKTests/Mocks/Service/Address/PlaceSearch/MockPlaceSearchInteractor.swift new file mode 100644 index 00000000..f534f10b --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Address/PlaceSearch/MockPlaceSearchInteractor.swift @@ -0,0 +1,22 @@ +// +// MockPlaceSearchInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockPlaceSearchInteractor: PlaceSearchInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var placeSearchSet: PlaceSearch? + func set(placeSearch: PlaceSearch) { + self.placeSearchSet = placeSearch + } +} diff --git a/KarhooSDKTests/Mocks/Service/Address/ReverseGeocode/MockReverseGeocodeInteractor.swift b/KarhooSDKTests/Mocks/Service/Address/ReverseGeocode/MockReverseGeocodeInteractor.swift new file mode 100644 index 00000000..90c909e7 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Address/ReverseGeocode/MockReverseGeocodeInteractor.swift @@ -0,0 +1,22 @@ +// +// MockReverseGeocodeInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockReverseGeocodeInteractor: ReverseGeocodeInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var positionSet: Position? + func set(position: Position) { + self.positionSet = position + } +} diff --git a/KarhooSDKTests/Mocks/Service/Analytics/MockAnalyticsProvider.swift b/KarhooSDKTests/Mocks/Service/Analytics/MockAnalyticsProvider.swift new file mode 100644 index 00000000..6a61df47 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Analytics/MockAnalyticsProvider.swift @@ -0,0 +1,26 @@ +// +// MockAnalyticsProvider.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockAnalyticsProvider: AnalyticsProvider { + + var trackedName: String? + var trackedProperties: [String: Any]? + + func trackEvent(name: String) { + trackedName = name + } + + func trackEvent(name: String, payload: [String: Any]?) { + trackedName = name + trackedProperties = payload + } +} diff --git a/KarhooSDKTests/Mocks/Service/Analytics/MockAnalyticsService.swift b/KarhooSDKTests/Mocks/Service/Analytics/MockAnalyticsService.swift new file mode 100644 index 00000000..f91dde3c --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Analytics/MockAnalyticsService.swift @@ -0,0 +1,39 @@ +// +// MockAnalyticsService.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockAnalyticsService: AnalyticsService { + + var eventSent: AnalyticsConstants.EventNames? + var payloadSent: [String: Any]? + + func send(eventName: AnalyticsConstants.EventNames, payload: [String: Any]) { + eventSent = eventName + payloadSent = payload + } + + func send(eventName: AnalyticsConstants.EventNames) { + eventSent = eventName + } + + private(set) var bookingRequestedCalled = false + func bookingRequested(destination: LocationInfo, + dateScheduled: Date?, + quote: Quote?) { + bookingRequestedCalled = true + } + + private(set) var tripCancellationAttemptedCalled = false + func tripCancellationAttempted() { + tripCancellationAttemptedCalled = true + } + +} diff --git a/KarhooSDKTests/Mocks/Service/Availability/MockAvailabilityInteractor.swift b/KarhooSDKTests/Mocks/Service/Availability/MockAvailabilityInteractor.swift new file mode 100644 index 00000000..9693455f --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Availability/MockAvailabilityInteractor.swift @@ -0,0 +1,22 @@ +// +// MockAvailabilityInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockAvailabilityInteractor: AvailabilityInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var setAvailabilitySearch: AvailabilitySearch? + func set(availabilitySearch: AvailabilitySearch) { + setAvailabilitySearch = availabilitySearch + } +} diff --git a/KarhooSDKTests/Mocks/Service/Config/MockUIConfigInteractor.swift b/KarhooSDKTests/Mocks/Service/Config/MockUIConfigInteractor.swift new file mode 100644 index 00000000..ec6bbf67 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Config/MockUIConfigInteractor.swift @@ -0,0 +1,25 @@ +// +// MockUIConfigInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +final class MockUIConfigInteractor: UIConfigInteractor, MockInteractor { + + var cancelCalled: Bool = false + func cancel() { + cancelCalled = true + } + + var callbackSet: CallbackClosure? + + private(set) var uiConfigRequestSet: UIConfigRequest? + func set(uiConfigRequest: UIConfigRequest) { + self.uiConfigRequestSet = uiConfigRequest + } +} diff --git a/KarhooSDKTests/Mocks/Service/Config/MockUIConfigProvider.swift b/KarhooSDKTests/Mocks/Service/Config/MockUIConfigProvider.swift new file mode 100644 index 00000000..a8ec427c --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Config/MockUIConfigProvider.swift @@ -0,0 +1,33 @@ +// +// MockUIConfigProvider.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +final class MockUIConfigProvider: UIConfigProvider { + + private var configCallbackSet: CallbackClosure? + + private(set) var fetchConfigCalled = false + private(set) var organisationSet: Organisation? + private(set) var uiconfigRequestSet: UIConfigRequest? + + func fetchConfig(uiConfigRequest: UIConfigRequest, + organisation: Organisation, + callback: @escaping CallbackClosure) { + fetchConfigCalled = true + self.uiconfigRequestSet = uiConfigRequest + self.organisationSet = organisation + self.configCallbackSet = callback + } + + func triggerConfigCallbackResult(_ result: Result) { + configCallbackSet?(result) + } + +} diff --git a/KarhooSDKTests/Mocks/Service/Fare/MockFareInteractor.swift b/KarhooSDKTests/Mocks/Service/Fare/MockFareInteractor.swift new file mode 100644 index 00000000..4e5a3aa7 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Fare/MockFareInteractor.swift @@ -0,0 +1,21 @@ +// +// MockFareInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockFareInteractor: FareInteractor, MockInteractor { + var callbackSet: CallbackClosure? + var cancelCalled = false + var tripIdSet: String? + + func set(tripId: String) { + self.tripIdSet = tripId + } +} diff --git a/KarhooSDKTests/Mocks/Service/Payment/AddPaymentDetails/MockAddPaymentDetailsInteractor.swift b/KarhooSDKTests/Mocks/Service/Payment/AddPaymentDetails/MockAddPaymentDetailsInteractor.swift new file mode 100644 index 00000000..fe64441f --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Payment/AddPaymentDetails/MockAddPaymentDetailsInteractor.swift @@ -0,0 +1,20 @@ +// +// MockAddPaymentDetailsInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +@testable import KarhooSDK + +final class MockAddPaymentDetailsInteractor: MockInteractor, AddPaymentDetailsInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + private(set) var addPaymentDetailsPayloadSet: AddPaymentDetailsPayload? + func set(addPaymentDetailsPayload: AddPaymentDetailsPayload) { + self.addPaymentDetailsPayloadSet = addPaymentDetailsPayload + } +} diff --git a/KarhooSDKTests/Mocks/Service/Payment/GetNonce/MockGetNonceInteractor.swift b/KarhooSDKTests/Mocks/Service/Payment/GetNonce/MockGetNonceInteractor.swift new file mode 100644 index 00000000..27e37fb5 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Payment/GetNonce/MockGetNonceInteractor.swift @@ -0,0 +1,20 @@ +// +// MockGetNonceInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +@testable import KarhooSDK + +final class MockGetNonceInteractor: MockInteractor, GetNonceInteractor { + + var cancelCalled: Bool = false + var callbackSet: CallbackClosure? + + private(set) var nonceRequestPayloadSet: NonceRequestPayload? + func set(nonceRequestPayload: NonceRequestPayload) { + nonceRequestPayloadSet = nonceRequestPayload + } +} diff --git a/KarhooSDKTests/Mocks/Service/Payment/PaymentSDKToken/MockPaymentSDKTokenInteractor.swift b/KarhooSDKTests/Mocks/Service/Payment/PaymentSDKToken/MockPaymentSDKTokenInteractor.swift new file mode 100644 index 00000000..05181910 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Payment/PaymentSDKToken/MockPaymentSDKTokenInteractor.swift @@ -0,0 +1,19 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockPaymentSDKTokenInteractor: MockInteractor, PaymentSDKTokenInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + private(set) var payloadSet: PaymentSDKTokenPayload? + func set(payload: PaymentSDKTokenPayload) { + self.payloadSet = payload + } +} diff --git a/KarhooSDKTests/Mocks/Service/Quote/MockQuoteInteractor.swift b/KarhooSDKTests/Mocks/Service/Quote/MockQuoteInteractor.swift new file mode 100644 index 00000000..7f2b1fb9 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Quote/MockQuoteInteractor.swift @@ -0,0 +1,22 @@ +// +// MockQuoteInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockQuoteInteractor: QuoteInteractor, MockInteractor { + + var cancelCalled: Bool = false + var callbackSet: CallbackClosure? + + var quoteSearchSet: QuoteSearch? + func set(quoteSearch: QuoteSearch) { + self.quoteSearchSet = quoteSearch + } +} diff --git a/KarhooSDKTests/Mocks/Service/Trips/Booking/MockBookingInteractor.swift b/KarhooSDKTests/Mocks/Service/Trips/Booking/MockBookingInteractor.swift new file mode 100644 index 00000000..bd5fb158 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Trips/Booking/MockBookingInteractor.swift @@ -0,0 +1,22 @@ +// +// TestBookingInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockBookingInteractor: BookingInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var tripBookingSet: TripBooking? + func set(tripBooking: TripBooking) { + tripBookingSet = tripBooking + } +} diff --git a/KarhooSDKTests/Mocks/Service/Trips/CancelTrip/MockCancelTripInteractor.swift b/KarhooSDKTests/Mocks/Service/Trips/CancelTrip/MockCancelTripInteractor.swift new file mode 100644 index 00000000..f080c9dd --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Trips/CancelTrip/MockCancelTripInteractor.swift @@ -0,0 +1,22 @@ +// +// MockCancelTripInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockCancelTripInteractor: CancelTripInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var tripCancellationSet: TripCancellation? + func set(tripCancellation: TripCancellation) { + tripCancellationSet = tripCancellation + } +} diff --git a/KarhooSDKTests/Mocks/Service/Trips/TripSearch/MockTripSearchInteractor.swift b/KarhooSDKTests/Mocks/Service/Trips/TripSearch/MockTripSearchInteractor.swift new file mode 100644 index 00000000..e6816c27 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Trips/TripSearch/MockTripSearchInteractor.swift @@ -0,0 +1,22 @@ +// +// MockTripSearchInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockTripSearchInteractor: TripSearchInteractor, MockInteractor { + + var callbackSet: CallbackClosure<[TripInfo]>? + var cancelCalled = false + + var tripSearchSet: TripSearch? + func set(tripSearch: TripSearch) { + tripSearchSet = tripSearch + } +} diff --git a/KarhooSDKTests/Mocks/Service/Trips/TripStatusUpdate/MockTripStatusInteractor.swift b/KarhooSDKTests/Mocks/Service/Trips/TripStatusUpdate/MockTripStatusInteractor.swift new file mode 100644 index 00000000..053483cf --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Trips/TripStatusUpdate/MockTripStatusInteractor.swift @@ -0,0 +1,17 @@ +// +// MockTripStatusInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockTripStatusInteractor: TripStatusInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false +} diff --git a/KarhooSDKTests/Mocks/Service/Trips/TripUpdate/MockTripUpdateInteractor.swift b/KarhooSDKTests/Mocks/Service/Trips/TripUpdate/MockTripUpdateInteractor.swift new file mode 100644 index 00000000..294c1d09 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/Trips/TripUpdate/MockTripUpdateInteractor.swift @@ -0,0 +1,17 @@ +// +// MockTripUpdateInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockTripUpdateInteractor: TripUpdateInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false +} diff --git a/KarhooSDKTests/Mocks/Service/User/Login/MockLoginInteractor.swift b/KarhooSDKTests/Mocks/Service/User/Login/MockLoginInteractor.swift new file mode 100644 index 00000000..0a1b863c --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/User/Login/MockLoginInteractor.swift @@ -0,0 +1,22 @@ +// +// MockLoginInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockLoginInteractor: LoginInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var userLoginSet: UserLogin? + func set(userLogin: UserLogin) { + userLoginSet = userLogin + } +} diff --git a/KarhooSDKTests/Mocks/Service/User/Logout/MockLogoutInteractor.swift b/KarhooSDKTests/Mocks/Service/User/Logout/MockLogoutInteractor.swift new file mode 100644 index 00000000..2e0c7eb4 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/User/Logout/MockLogoutInteractor.swift @@ -0,0 +1,17 @@ +// +// MockLogoutInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockLogoutInteractor: KarhooExecutable, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false +} diff --git a/KarhooSDKTests/Mocks/Service/User/PasswordReset/MockPasswordResetInteractor.swift b/KarhooSDKTests/Mocks/Service/User/PasswordReset/MockPasswordResetInteractor.swift new file mode 100644 index 00000000..8cf9c2ba --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/User/PasswordReset/MockPasswordResetInteractor.swift @@ -0,0 +1,19 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockPasswordResetInteractor: PasswordResetInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var emailSet: String? + func set(email: String) { + emailSet = email + } +} diff --git a/KarhooSDKTests/Mocks/Service/User/Signup/MockRegisterInteractor.swift b/KarhooSDKTests/Mocks/Service/User/Signup/MockRegisterInteractor.swift new file mode 100644 index 00000000..0fa076f1 --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/User/Signup/MockRegisterInteractor.swift @@ -0,0 +1,22 @@ +// +// MockSignupInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockRegisterInteractor: RegisterInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var userRegistrationSet: UserRegistration? + func set(userRegistration: UserRegistration) { + userRegistrationSet = userRegistration + } +} diff --git a/KarhooSDKTests/Mocks/Service/User/UpdateUserDetails/MockUpdateUserDetailsInteractor.swift b/KarhooSDKTests/Mocks/Service/User/UpdateUserDetails/MockUpdateUserDetailsInteractor.swift new file mode 100644 index 00000000..d66e315a --- /dev/null +++ b/KarhooSDKTests/Mocks/Service/User/UpdateUserDetails/MockUpdateUserDetailsInteractor.swift @@ -0,0 +1,22 @@ +// +// UserDetailsUpdateInteractor.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockUpdateuserDetailsInteractor: UpdaterUserDetailsInteractor, MockInteractor { + + var callbackSet: CallbackClosure? + var cancelCalled = false + + var userUpdateSet: UserDetailsUpdateRequest? + func set(update: UserDetailsUpdateRequest) { + userUpdateSet = update + } +} diff --git a/KarhooSDKTests/Mocks/TestUtil.swift b/KarhooSDKTests/Mocks/TestUtil.swift new file mode 100644 index 00000000..b67300af --- /dev/null +++ b/KarhooSDKTests/Mocks/TestUtil.swift @@ -0,0 +1,68 @@ +// +// TestUtil.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import CoreLocation + +@testable import KarhooSDK + +class TestUtil { + + class func getRandomError() -> MockError { + return MockError(code: TestUtil.getRandomString(), + message: TestUtil.getRandomString(), + userMessage: TestUtil.getRandomString()) + } + + class func getUnauthenticatedError() -> HTTPError { + return HTTPError(statusCode: 401, errorType: .userAuthenticationRequired) + } + + class func getRandomLocation() -> CLLocation { + return CLLocation(latitude: getRandomCoordinateComponent(), + longitude: getRandomCoordinateComponent()) + } + + class func getRandomCoordinateComponent() -> CLLocationDegrees { + return CLLocationDegrees(arc4random_uniform(1000)/10) + } + + class func getRandomString(length: Int = 32) -> String { + let letters: NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + let noOfLetters = UInt32(letters.length) + + var randomString = "" + + for _ in 0 ..< length { + let rand = arc4random_uniform(noOfLetters) + var nextChar = letters.character(at: Int(rand)) + randomString += NSString(characters: &nextChar, length: 1) as String + } + + return randomString + } + + class func getRandomInt(lessThan: UInt32 = UInt32(INT_MAX)) -> Int { + return Int(arc4random_uniform(UInt32(lessThan))) + } + + class func getRandomDate(laterThan date: Date = Date()) -> Date { + let timeInterval = date.timeIntervalSince1970 + TimeInterval(arc4random_uniform(10000000)) + return Date(timeIntervalSince1970: timeInterval) + } + + class func datesEqual(_ first: Date?, _ second: Date?, precision: TimeInterval = 0.1) -> Bool { + guard let first = first, let second = second else { + return false + } + + let timeDifference = abs(first.timeIntervalSince1970 - second.timeIntervalSince1970) + let epsilon = precision + return (timeDifference < epsilon) + } +} diff --git a/KarhooSDKTests/TestCases/Api/ContextSpec.swift b/KarhooSDKTests/TestCases/Api/ContextSpec.swift new file mode 100644 index 00000000..00dea458 --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/ContextSpec.swift @@ -0,0 +1,40 @@ +// +// ContextSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class ContextSpec: XCTestCase { + + private var testObject: CurrentContext! + + override func setUp() { + super.setUp() + + testObject = CurrentContext() + } + + /** + * When: Getting the sdk bundle + * Then: The sdk bundle should be fetched + */ + func testSDKBundle() { + let bundle = testObject.getSdkBundle() + XCTAssert(bundle.bundleIdentifier?.hasSuffix("KarhooSDK") == true) + } + + /** + * When: Getting the current bundle + * Then: The main bundle should be fetched + */ + func testGetMainBundle() { + let bundle = testObject.getCurrentBundle() + XCTAssert(bundle == Bundle.main) + } +} diff --git a/KarhooSDKTests/TestCases/Api/DataStore/CredentialsSpec.swift b/KarhooSDKTests/TestCases/Api/DataStore/CredentialsSpec.swift new file mode 100644 index 00000000..cb60c49b --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/DataStore/CredentialsSpec.swift @@ -0,0 +1,54 @@ +// +// CredentialsSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class CredentialsSpec: XCTestCase { + + /** + * When: Creating Credentials object using convenience init + * Then: expectedDate should be correct + */ + func testCredentialsConvenienceInit() { + let expiresIn: TimeInterval = 1000 + + let accessToken = TestUtil.getRandomString() + let refreshToken = TestUtil.getRandomString() + + let credentials = Credentials(accessToken: accessToken, + expiresIn: expiresIn, + refreshToken: refreshToken) + + XCTAssert(credentials.accessToken == accessToken) + XCTAssert(credentials.refreshToken == refreshToken) + + let expectedExpiryDate = Date().addingTimeInterval(expiresIn) + XCTAssert(TestUtil.datesEqual(credentials.expiryDate!, expectedExpiryDate)) + } + + /** + * When: Converting from RefreshToken to Credentials + * Then: ExpiryDate should be set correctly + * And: Other fields should be set accordingly + */ + func testInitialiseFromRestRefreshTokenResponse() { + let accessToken = TestUtil.getRandomString() + let expiresIn = TestUtil.getRandomInt() + let refreshToken = TestUtil.getRandomString() + let refreshTokenModel = RefreshToken(accessToken: accessToken, expiresIn: expiresIn) + + let credentials = refreshTokenModel.toCredentials(withRefreshToken: refreshToken) + + XCTAssert(credentials.accessToken == accessToken) + XCTAssert(credentials.refreshToken == refreshToken) + + let expectedExpiryDate = Date().addingTimeInterval(TimeInterval(expiresIn)) + XCTAssert(TestUtil.datesEqual(credentials.expiryDate, expectedExpiryDate)) + } +} diff --git a/KarhooSDKTests/TestCases/Api/DataStore/UserDataStoreSpec.swift b/KarhooSDKTests/TestCases/Api/DataStore/UserDataStoreSpec.swift new file mode 100644 index 00000000..d64f5cc7 --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/DataStore/UserDataStoreSpec.swift @@ -0,0 +1,287 @@ +// +// UserDataStoreSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class UserDataStoreSpec: XCTestCase { + + private var mockCredentialsParser: MockCredentialsParser! + private var mockUserDefaults: MockUserDefaults! + private var mockObserver: MockObserver! + private var mockBroadcaster: MockBroadcaster! + + private var testObject: DefaultUserDataStore! + + override func setUp() { + super.setUp() + mockCredentialsParser = MockCredentialsParser() + mockUserDefaults = MockUserDefaults() + mockObserver = MockObserver() + mockBroadcaster = MockBroadcaster() + + testObject = DefaultUserDataStore(persistantStore: mockUserDefaults, + credentialsParser: mockCredentialsParser, + broadcaster: mockBroadcaster) + testObject.add(observer: mockObserver) + } + + override func tearDown() { + super.tearDown() + mockUserDefaults.removeObject(forKey: DefaultUserDataStore.currentUserKey) + _ = mockUserDefaults.synchronize() + } + + /** + * When: Adding an observer + * Then: The observer should be added to the associated broadcaster + */ + func testAddObserver() { + XCTAssert(mockBroadcaster.lastListenerAdded === mockObserver) + } + + /** + * When: Removing an observer + * Then: The observer should be removed to the associated broadcaster + */ + func testRemoveObserver() { + testObject.remove(observer: mockObserver) + + XCTAssert(mockBroadcaster.lastListenerRemoved === mockObserver) + } + + /** + * When: Setting credentials + * Then: Persistant store should be set + */ + func testSetCredentials() { + testObject.set(credentials: ObjectTestFactory.getRandomCredentials()) + XCTAssertTrue(mockUserDefaults.setForKeyCalled) + } + + /** + * Given: A stored user + * When: Getting the current user + * Then: The logged in user should be returned + */ + func testGetExistingUser() { + let storedUser = UserInfoMock().set(userId: "some").build() + mockUserDefaults.set(storedUser.encode()!, forKey: DefaultUserDataStore.currentUserKey) + + let user = testObject.getCurrentUser() + + XCTAssertEqual(user, storedUser) + } + + /** + * Given: No stored user + * When: Getting the current user + * Then: Nil should be returned + */ + func testGetNoUser() { + let user = testObject.getCurrentUser() + + XCTAssertNil(user) + } + + /** + * Given: No stored credentials + * When: Getting the current credentials + * Then: Nil should be returned + */ + func testGetNoCredentials() { + let credentials = testObject.getCurrentCredentials() + + XCTAssertNil(credentials) + } + + /** + * Given: A stored user + * When: Setting a new user + * Then: The stored user should be replaced by the new user + */ + func testSetUserWhenExistingUser() { + let newUser = UserInfoMock().set(userId: "some").build() + + let newCredentials = ObjectTestFactory.getRandomCredentials() + mockCredentialsParser.dataToReturn = ["credentials": "abc"] + + mockCredentialsParser.credentialsToReturn = newCredentials + testObject.setCurrentUser(user: newUser, credentials: newCredentials) + + XCTAssertEqual(newUser, testObject.getCurrentUser()) + + XCTAssert(mockObserver.userStateUpdateCalled) + XCTAssertEqual(newUser, mockObserver.userUpdatedTo) + } + + /** + * Given: No logged in user + * When: Setting a new user + * Then: The new user should be set + */ + func testSetUser() { + let newUser = UserInfoMock().set(userId: "some").build() + + let newCredentials = ObjectTestFactory.getRandomCredentials() + mockCredentialsParser.dataToReturn = ["some": "data"] + + testObject.setCurrentUser(user: newUser, credentials: newCredentials) + + XCTAssertEqual(newUser, testObject.getCurrentUser()) + + XCTAssert(mockUserDefaults.synchronizeCalled) + + XCTAssert(mockObserver.userStateUpdateCalled) + XCTAssertEqual(newUser, testObject.getCurrentUser()) + } + + /** + * Given: A current user exists + * When: Trying to remove the current user + * Then: The current user should be removed + */ + func testRemoveCurrentUser() { + mockUserDefaults.set(["test": "test"], forKey: DefaultUserDataStore.currentUserKey) + testObject.removeCurrentUserAndCredentials() + + XCTAssertNil(mockUserDefaults.value(forKey: DefaultUserDataStore.currentUserKey)) + XCTAssert(mockUserDefaults.synchronizeCalled) + + XCTAssert(mockObserver.userStateUpdateCalled) + XCTAssertNil(mockObserver.userUpdatedTo) + } + + /** + * Given: No current user exists + * When: Trying to remove the current user + * Then: Nothing should happen + */ + func testRemoveCurrentUserNoUser() { + testObject.removeCurrentUserAndCredentials() + + XCTAssertNil(mockUserDefaults.value(forKey: DefaultUserDataStore.currentUserKey)) + XCTAssert(mockUserDefaults.synchronizeCalled) + + XCTAssert(mockObserver.userStateUpdateCalled) + XCTAssertNil(mockObserver.userUpdatedTo) + } + + /** + * When: Updating user nonce + * Then: Nonce should be set on the user + * And: observer should be broadcasted + */ + func testUpdateNonce() { + let storedUser = UserInfoMock().set(userId: "some").build() + mockUserDefaults.set(storedUser.encode()!, forKey: DefaultUserDataStore.currentUserKey) + + let user = testObject.getCurrentUser() + + XCTAssertNil(user?.nonce) + + let newNonce = Nonce(nonce: "some", cardType: "Visa", lastFour: "1234") + testObject.updateCurrentUserNonce(nonce: newNonce) + + XCTAssertEqual(testObject.getCurrentUser()?.nonce?.nonce, "some") + XCTAssertEqual(mockObserver.userUpdatedTo?.nonce?.nonce, "some") + XCTAssertTrue(mockObserver.userStateUpdateCalled) + } + + /** + * When: Updating user nonce to nil + * Then: Nonce should be set to nil + * And: observer should be broadcasted + */ + func testUpdateNilNonce() { + let storedUser = UserInfoMock().set(userId: "some").build() + mockUserDefaults.set(storedUser.encode()!, forKey: DefaultUserDataStore.currentUserKey) + + let user = testObject.getCurrentUser() + + XCTAssertNil(user?.nonce) + + testObject.updateCurrentUserNonce(nonce: nil) + + XCTAssertNil(testObject.getCurrentUser()?.nonce?.nonce) + XCTAssertNil(mockObserver.userUpdatedTo?.nonce?.nonce) + XCTAssertTrue(mockObserver.userStateUpdateCalled) + } + + /** + * When: Updating the current user + * Then: New user should be persisted + * And: observer should be broadcasted with new user + */ + func testUpdateUserData() { + let existingUser = UserInfoMock().set(userId: "someExistingUser").build() + var newUser = UserInfoMock().set(userId: "someNewUser").build() + + mockUserDefaults.set(existingUser.encode()!, forKey: DefaultUserDataStore.currentUserKey) + + let currentUser = testObject.getCurrentUser() + XCTAssertEqual(currentUser?.userId, existingUser.userId) + + testObject.updateUser(user: &newUser) + + XCTAssertTrue(mockObserver.userStateUpdateCalled) + XCTAssertEqual(testObject.getCurrentUser()?.userId, newUser.userId) + } + + /** + * When: Updating the current user + * Then: New user should be persisted with current user nonce + * And: observer should be broadcasted with new user + */ + func testUpdateUserRetainsUserNonce() { + let currentUserData = UserInfoMock().set(userId: "nonceUser").set(nonce: Nonce(nonce: "some")).build() + var newUserUpdate = UserInfoMock().set(userId: "nonceUser").build() + + mockUserDefaults.set(currentUserData.encode()!, forKey: DefaultUserDataStore.currentUserKey) + + testObject.updateUser(user: &newUserUpdate) + XCTAssertTrue(mockObserver.userStateUpdateCalled) + + XCTAssertNotNil(testObject.getCurrentUser()?.nonce) + XCTAssertEqual(testObject.getCurrentUser()?.nonce, currentUserData.nonce) + } +} + +private class MockCredentialsParser: CredentialsParser { + var credentialsToReturn: Credentials? + var dataToDecode: [String: Any]? + + var credentialsToCode: Credentials? + var dataToReturn: [String: Any]? + + func from(dictionary: [String: Any]?) -> Credentials? { + dataToDecode = dictionary + return credentialsToReturn + } + + func from(credentials: Credentials?) -> [String: Any]? { + credentialsToCode = credentials + return dataToReturn + } +} + +private class MockObserver: UserStateObserver { + + var userStateUpdateCalled = false + var userUpdatedTo: UserInfo? + func userStateUpdated(user: UserInfo?) { + userStateUpdateCalled = true + userUpdatedTo = user + } + + func reset() { + userStateUpdateCalled = false + userUpdatedTo = nil + } +} diff --git a/KarhooSDKTests/TestCases/Api/KarhooSpec.swift b/KarhooSDKTests/TestCases/Api/KarhooSpec.swift new file mode 100644 index 00000000..34c7699c --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/KarhooSpec.swift @@ -0,0 +1,137 @@ +// +// KarhooSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class KarhooSpec: XCTestCase { + + /** + * When: Setting an SDKConfiguration implementation + * Then: Shared settings should be set + */ + func testSettingAConfiguration() { + Karhoo.set(configuration: MockSDKConfig()) + + let configSet = Karhoo.configuration + + if case KarhooEnvironment.sandbox = configSet!.environment() {} else { + XCTFail("Environment not configured") + } + } + + /** + * When: Getting a login service + * Then: It should return a KarhooUserService + */ + func testGetUserService() { + let userService = Karhoo.getUserService() + + XCTAssertNotNil(userService as? KarhooUserService) + } + + /** + * When: Getting an availability service + * Then: It should return a KarhooAvailabilityService + */ + func testGetAvailabilityService() { + let availabilityService = Karhoo.getAvailabilityService() + XCTAssertNotNil(availabilityService) + } + + /** + * When: Getting an address service + * Then: It should return an instance of KarhooAddressService + */ + func testGetAddressService() { + let addressService = Karhoo.getAddressService() + + XCTAssertNotNil(addressService as? KarhooAddressService) + } + + /** + * When: Getting an address service + * Then: It should return an instance of KarhooQuoteService + */ + func testGetQuoteService() { + let quoteService = Karhoo.getQuoteService() + + XCTAssertNotNil(quoteService as? KarhooQuoteService) + } + + /** + * When: Getting an analytics service + * Then: It should return an instance of KarhooAnalyticsService + */ + func testGetAnalyticsService() { + let analytics = Karhoo.getAnalyticsService() + + XCTAssertNotNil(analytics as? KarhooAnalyticsService) + } + + /** + * When: Getting a payment service + * Then: It should return an instance of KarhooPaymentService + */ + func testGetPaymentService() { + let paymentService = Karhoo.getPaymentService() + + XCTAssertNotNil(paymentService as? KarhooPaymentService) + } + + /** + * When: Getting a driver tracking service + * Then: It should return an instance of KarhooDriverTrackingService + */ + func testGetDriverTrackingService() { + let driverTrackingService = Karhoo.getDriverTrackingService() + + XCTAssertNotNil(driverTrackingService as? KarhooDriverTrackingService) + } + + /** + * When: Getting a booking status + * Then: It should return an instance of KarhooBookingStatus + */ + func testGetBroadcaster() { + let broadcaster = Karhoo.Utils.getBroadcaster(ofType: AnyObject.self) + + XCTAssertNotNil(broadcaster) + } + + /** + * When: Getting a fare service + * Then: It should return an instance of fare service + */ + func testGetFareService() { + let fareService = Karhoo.getFareService() + XCTAssertNotNil(fareService) + } + + /** + * When: Getting an auth service + * Then: It should return an instance of auth service + */ + func testGetAuthService() { + let authService = Karhoo.getAuthService() + XCTAssertNotNil(authService) + } + + /** + * Given: Trip service has been requested + * When: Asking for trip service again + * Then: Same object should be return (to ensure we don't have multiple trip/location broadcaster managers) + */ + func testMultipleTripService() { + let firstTripService = Karhoo.getTripService() as? KarhooTripService + let secondTripService = Karhoo.getTripService() as? KarhooTripService + + XCTAssert(firstTripService === secondTripService) + } +} diff --git a/KarhooSDKTests/TestCases/Api/MockContext.swift b/KarhooSDKTests/TestCases/Api/MockContext.swift new file mode 100644 index 00000000..b623b039 --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/MockContext.swift @@ -0,0 +1,27 @@ +// +// TestContext.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +@testable import KarhooSDK + +final class MockContext: Context { + var isTestflightBuildReturnValue = false + + func getSdkBundle() -> Bundle { + return Bundle.main + } + + func getCurrentBundle() -> Bundle { + return Bundle.main + } + + func isTestflightBuild() -> Bool { + return isTestflightBuildReturnValue + } +} diff --git a/KarhooSDKTests/TestCases/Api/Observable/KarhooCallSpec.swift b/KarhooSDKTests/TestCases/Api/Observable/KarhooCallSpec.swift new file mode 100644 index 00000000..5908d27e --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/Observable/KarhooCallSpec.swift @@ -0,0 +1,32 @@ +// +// KarhooCallSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooCallSpec: XCTestCase { + + private var testObject: Call! + private var mockExecutable = MockExecutable() + + override func setUp() { + super.setUp() + testObject = Call(executable: mockExecutable) + } + + /** + * When: Calling execute on a KarhooCall + * Then: KarhooCall should execute its executable + */ + func testKarhooCallExecutesExecutable() { + testObject.execute(callback: { _ in}) + XCTAssertTrue(mockExecutable.didExecute) + } +} diff --git a/KarhooSDKTests/TestCases/Api/Observable/KarhooObservableSpec.swift b/KarhooSDKTests/TestCases/Api/Observable/KarhooObservableSpec.swift new file mode 100644 index 00000000..cbb6df6f --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/Observable/KarhooObservableSpec.swift @@ -0,0 +1,75 @@ +// +// KarhooObservableSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooObservableSpec: XCTestCase { + + private var testObject: Observable! + private var mockPollExecutor = MockPollExecutor() + private var mockObserverBroadcaster = MockObserverBroadcaster() + + override func setUp() { + super.setUp() + + testObject = Observable(pollExecutor: mockPollExecutor, + pollTime: 5, + broadcaster: mockObserverBroadcaster) + } + + /** + * When: Calling observe for the first time + * Then: Observable should add passed callbackclosure to the closure broadcaster + * And: Observable should tell its pollable executor to start polling + */ + func testFirstObserve() { + let observer = Observer { _ in } + testObject.subscribe(observer: observer) + XCTAssertTrue(mockPollExecutor.startPollingCalled) + XCTAssertTrue(mockObserverBroadcaster.addListenerCalled) + } + + /** + * When: Calling observe for the second time + * Then: Observable should add passed callbackclosure to the closure broadcaster + * And: Observable should tell its pollable executor to start polling only once + */ + func testSecondObserve() { + let firstObserver = Observer { _ in } + testObject.subscribe(observer: firstObserver) + XCTAssertTrue(mockPollExecutor.startPollingCalled) + + mockPollExecutor.startPollingCalled = false + let secondObserver = Observer { _ in } + testObject.subscribe(observer: secondObserver) + XCTAssertFalse(mockPollExecutor.startPollingCalled) + + XCTAssertTrue(mockObserverBroadcaster.addListenerCalled) + } + + /** + * When: Pollable executor returns with a result + * Then: Broadcaster should broadcast result + */ + func testObservablePollReturnsResult() { + var observeResult: Result? + let observer = Observer { result in + observeResult = result + } + testObject.subscribe(observer: observer) + + mockPollExecutor.trigger(result: .success(result: MockKarhooCodableModel(id: "some"))) + + XCTAssertEqual("some", mockObserverBroadcaster.broadcastedResult?.successValue()?.id) + XCTAssertEqual("some", observeResult?.successValue()?.id) + } + +} diff --git a/KarhooSDKTests/TestCases/Api/Observable/KarhooPollCallSpec.swift b/KarhooSDKTests/TestCases/Api/Observable/KarhooPollCallSpec.swift new file mode 100644 index 00000000..fbb6c9d5 --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/Observable/KarhooPollCallSpec.swift @@ -0,0 +1,50 @@ +// +// KarhooPollCallSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooPollCallSpec: XCTestCase { + + private var testObject: PollCall! + private var mockPollExecutor: MockPollExecutor = MockPollExecutor() + + override func setUp() { + super.setUp() + + testObject = PollCall(pollExecutor: mockPollExecutor) + } + + /** + * When: getting multiple observables with the same pollTime + * Then: should return the same observables + */ + func testGetMultipleObservables() { + let first = testObject.observable() + let second = testObject.observable() + let third = testObject.observable() + + XCTAssert(first === second) + XCTAssert(first === third) + } + + /** + * When: getting multiple observables with the different pollTime + * Then: should return different observables + */ + func testGetMultipleObservablesWithDifferentPolltime() { + let first = testObject.observable(pollTime: 5) + let second = testObject.observable(pollTime: 5) + let third = testObject.observable(pollTime: 10) + + XCTAssert(first === second) + XCTAssertFalse(first === third) + } +} diff --git a/KarhooSDKTests/TestCases/Api/Observable/KarhooPollableExecutorSpec.swift b/KarhooSDKTests/TestCases/Api/Observable/KarhooPollableExecutorSpec.swift new file mode 100644 index 00000000..9f096493 --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/Observable/KarhooPollableExecutorSpec.swift @@ -0,0 +1,63 @@ +// +// KarhooPollableExecutorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooPollableExecutorSpec: XCTestCase { + + private var testObject: KarhooPollExecutor! + private var mockExecutable: MockExecutable! + private var mockTimingScheduler: MockTimingScheduler = MockTimingScheduler() + + override func setUp() { + super.setUp() + mockExecutable = MockExecutable() + testObject = PollExecutor(pollingScheduler: mockTimingScheduler, + executable: mockExecutable) + } + + /** + * When: Starting polling + * Then: pollingScheduler should fire in the set poll time repeating + */ + func testStartPolling() { + testObject.startPolling(pollTime: 5, callback: { (_: Result) -> Void in}) + XCTAssertEqual(5, mockTimingScheduler.timeIntervalSet) + } + + /** + * When: Timing scheduler fires and executes callback + * Then: Callback should be fired + */ + func testTimingSchedulerFires() { + var capturedResult: Result? + testObject.startPolling(pollTime: 5, callback: { (result: Result) -> Void in + capturedResult = result + }) + + mockTimingScheduler.fire() + mockExecutable.triggerExecution(result: .success(result: MockKarhooCodableModel(id: "some"))) + + XCTAssertEqual("some", capturedResult?.successValue()?.id) + } + + /** + * When: Stopping polling + * Then: Polling schedulr should be invalidated + * And: Excecutable should be cancelled + */ + func testStopPolling() { + testObject.stopPolling() + + XCTAssertTrue(mockTimingScheduler.invalidateCalled) + XCTAssertTrue(mockExecutable.cancelCalled) + } +} diff --git a/KarhooSDKTests/TestCases/Api/Parser/CredentiasParserSpec.swift b/KarhooSDKTests/TestCases/Api/Parser/CredentiasParserSpec.swift new file mode 100644 index 00000000..2e21b55b --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/Parser/CredentiasParserSpec.swift @@ -0,0 +1,92 @@ +// +// CredentiasParserSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class CredentiasParserSpec: XCTestCase { + + /** + * Given: A correctly formate dictionary + * When: Converting to SDK credentials + * Then: The corresponding SDK credentials should be produced + */ + func testConvertToCredentials() { + let accessToken = TestUtil.getRandomString() + let expiryDate = Date() + let refreshToken = TestUtil.getRandomString() + let dictionary = [CredentialsStoreKeys.accessToken.rawValue: accessToken, + CredentialsStoreKeys.expiryDate.rawValue: expiryDate, + CredentialsStoreKeys.refreshToken.rawValue: refreshToken] as [String: Any] + + let credentials = DefaultCredentialsParser().from(dictionary: dictionary) + + XCTAssert(credentials?.accessToken == accessToken) + XCTAssert(credentials?.expiryDate == expiryDate) + XCTAssert(credentials?.refreshToken == refreshToken) + } + + /** + * Given: A dictionary with no access token + * When: Converting to SDK credentials + * Then: The conversion should not produce anything + */ + func testConvertInvalidDictionaryToCredentials() { + let expiryDate = Date() + let refreshToken = "blue" + let dictionary = [CredentialsStoreKeys.expiryDate.rawValue: expiryDate, + CredentialsStoreKeys.refreshToken.rawValue: refreshToken] as [String: Any] + + let credentials = DefaultCredentialsParser().from(dictionary: dictionary) + + XCTAssertNil(credentials) + } + + /** + * Given: A nil dictionary + * When: Converting to SDK credentials + * Then: The conversion should not produce anything + */ + func testNilDictionaryToCredentials() { + let credentials = DefaultCredentialsParser().from(dictionary: nil) + + XCTAssertNil(credentials) + } + + /** + * Given: Credentials with access token set + * When: Converting to a dictionary + * Then: The corresponding dictionary should be produced + */ + func testConvertCredentialsToDictionary() { + let accessToken = TestUtil.getRandomString() + let expiryDate = Date() + let refreshToken = TestUtil.getRandomString() + let credentials = Credentials(accessToken: accessToken, + expiryDate: expiryDate, + refreshToken: refreshToken) + + let dictionary = DefaultCredentialsParser().from(credentials: credentials) + + XCTAssert(dictionary?[CredentialsStoreKeys.accessToken.rawValue] as? String == accessToken) + XCTAssert(dictionary?[CredentialsStoreKeys.expiryDate.rawValue] as? Date == expiryDate) + XCTAssert(dictionary?[CredentialsStoreKeys.refreshToken.rawValue] as? String == refreshToken) + } + + /** + * Given: Nil credentials + * When: Converting to a dictionary + * Then: The conversion should not produce anything + */ + func testNilCredentialsToDictionary() { + let dictionary = DefaultCredentialsParser().from(credentials: nil) + + XCTAssertNil(dictionary) + } +} diff --git a/KarhooSDKTests/TestCases/Api/Util/Broadcaster/BroadcasterSpec.swift b/KarhooSDKTests/TestCases/Api/Util/Broadcaster/BroadcasterSpec.swift new file mode 100755 index 00000000..83e0a94f --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/Util/Broadcaster/BroadcasterSpec.swift @@ -0,0 +1,138 @@ +// +// BroadcasterSpec.swift +// YMBroadcaster +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class BroadcasterSpec: XCTestCase { + + /** + * When Adding a listener + * Then The listener should be notified of the changes + */ + func testAddListener() { + let broadcaster = Broadcaster() + let listener = Listener() + broadcaster.add(listener: listener) + + broadcaster.broadcast { (listenerToInvoke) in + XCTAssert(listener === listenerToInvoke) + } + } + + /** + * When Removing a listener + * Then The listener should no longer be notified of the changes + */ + func testRemoveListener() { + let broadcaster = Broadcaster() + let listener = Listener() + broadcaster.add(listener: listener) + broadcaster.remove(listener: listener) + + broadcaster.broadcast { (_) in + XCTAssert(false) + } + } + + /** + * When Removing a listener which was never added + * Then Nothing should happen + */ + func testRemoveNonExistantListener() { + let broadcaster = Broadcaster() + let listener = Listener() + broadcaster.remove(listener: listener) + } + + /** + * When Broadcasting a message + * Then All the listeners should be notified + */ + func testBroadcast() { + let broadcaster = Broadcaster() + let listener1 = Listener() + let listener2 = Listener() + let listener3 = Listener() + let listeners = [listener1, listener2, listener3] + + broadcaster.add(listener: listener1) + broadcaster.add(listener: listener2) + broadcaster.add(listener: listener3) + + broadcaster.broadcast { (listenerToInvoke) in + XCTAssert(listeners.contains(where: { (listener: Listener) -> Bool in + return listener === listenerToInvoke + })) + } + } + + /** + * When A listener responds to a broadcast by removing itself + * Then All the listeners should be notified + * And The listener should be removed + */ + func testBroadcastWhileRemovingListener() { + let broadcaster = Broadcaster() + let listener1 = Listener() + let listener2 = Listener() + let listener3 = Listener() + + broadcaster.add(listener: listener1) + broadcaster.add(listener: listener2) + broadcaster.add(listener: listener3) + + broadcaster.broadcast { (listenerToInvoke) in + if listenerToInvoke === listener2 { + broadcaster.remove(listener: listener2) + } + } + + broadcaster.broadcast { (listenerToInvoke) in + XCTAssert(listenerToInvoke !== listener2) + } + } + + /** + * When A listener is deallocated without being removed + * Then The broadcast should still work + * And The listener should be removed + */ + func testListenerDeallocated() { + let broadcaster = Broadcaster() + let listener1 = Listener() + var listener2: Listener? = Listener() + let listener3 = Listener() + + broadcaster.add(listener: listener1) + broadcaster.add(listener: listener2!) + broadcaster.add(listener: listener3) + + let deinitExpectation = expectation(description: "Listener deallocated") + listener2?.deinitCallback = { + deinitExpectation.fulfill() + } + listener2 = nil + + broadcaster.broadcast { (listenerToInvoke) in + XCTAssert(listenerToInvoke !== listener2) + } + + waitForExpectations(timeout: 1, handler: nil) + + } +} + +class Listener { + var deinitCallback: (() -> Void)? + + deinit { + deinitCallback?() + } +} diff --git a/KarhooSDKTests/TestCases/Api/Util/Extension/PositionExtSpec.swift b/KarhooSDKTests/TestCases/Api/Util/Extension/PositionExtSpec.swift new file mode 100644 index 00000000..398f0dea --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/Util/Extension/PositionExtSpec.swift @@ -0,0 +1,63 @@ +// +// PositionExtSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +import KarhooSDK +import CoreLocation + +class PositionExtSpec: XCTestCase { + + /** + * When: Lat and long are 0 + * Then: isValid should return false + */ + func testInvalidLatLong() { + let invalidLatLong = Position(latitude: 0.0, longitude: 0.0) + XCTAssertFalse(invalidLatLong.isValid()) + } + + /** + * When: latitude is 0 but longitude is not 0 + * Then: isValid should return true + */ + func testZeroLongNonZeroLat() { + let validLatLong = Position(latitude: 0.0, longitude: 10) + XCTAssertTrue(validLatLong.isValid()) + } + + /** + * When: latitude is not 0 but longitude + * Then: isValid should return true + */ + func testZeroLatNonZeroLong() { + let validLatLong = Position(latitude: 10.0, longitude: 0) + XCTAssertTrue(validLatLong.isValid()) + } + + /** + * When: latitude is not 0 but longitude + * Then: isValid should return true + */ + func testValidLatLong() { + let validLatLong = Position(latitude: 15, longitude: 15) + XCTAssertTrue(validLatLong.isValid()) + } + + /** + * When: Converting a position to a CLLocation + * Then: The expected CLLocation should be returned + */ + func testToCLLocation() { + let position = Position(latitude: 10, longitude: 20) + + let convertedPosition = position.toCLLocation() + + XCTAssertEqual(10, convertedPosition.coordinate.latitude) + XCTAssertEqual(20, convertedPosition.coordinate.longitude) + } +} diff --git a/KarhooSDKTests/TestCases/Api/Util/ResultSpec.swift b/KarhooSDKTests/TestCases/Api/Util/ResultSpec.swift new file mode 100644 index 00000000..7ee16f82 --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/Util/ResultSpec.swift @@ -0,0 +1,96 @@ +// +// ResultSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class ResultSpec: XCTestCase { + + /** + * Given: A successfull result + * When: Checking if successfull + * Then: It should return true + */ + func testIsSuccess() { + let result: Result = .success(result: 3) + + XCTAssertTrue(result.isSuccess()) + } + + /** + * Given: A successfull result + * When: Getting the success value + * Then: It should return the associated value + */ + func testSuccessValue() { + let result: Result = .success(result: 5) + + XCTAssert(result.successValue() == 5) + } + + /** + * Given: A successfull result + * When: Getting the error value + * Then: It should return nil + */ + func testSuccessFailureValue() { + let result: Result = .success(result: 5) + + XCTAssertNil(result.errorValue()) + } + + /** + * Given: A failure result + * When: Checking if successfull + * Then: It should return false + */ + func testIsSuccessWhenFailure() { + let result: Result = .failure(error: nil) + + XCTAssertFalse(result.isSuccess()) + } + + /** + * Given: A failure result + * And: Error is set + * When: Getting the error + * Then: It should return the error + */ + func testValueWhenFailure() { + let expectedError = TestUtil.getRandomError() + let result: Result = .failure(error: expectedError) + + XCTAssertNotNil(result.errorValue()) + XCTAssert(expectedError.equals(result.errorValue()!)) + } + + /** + * Given: A failure result + * And: Error is not set + * When: Getting the error + * Then: It should return nil + */ + func testNilError() { + let result: Result = .failure(error: nil) + + XCTAssertNil(result.errorValue()) + } + + /** + * Given: A failure result + * When: Getting the associated success value + * Then: It should return nil + */ + func testSuccessValueWhenFailure() { + let error = TestUtil.getRandomError() + let result: Result = .failure(error: error) + + XCTAssertNil(result.successValue()) + } +} diff --git a/KarhooSDKTests/TestCases/Api/Util/WeakReferenceWrapperSpec.swift b/KarhooSDKTests/TestCases/Api/Util/WeakReferenceWrapperSpec.swift new file mode 100755 index 00000000..32979eb4 --- /dev/null +++ b/KarhooSDKTests/TestCases/Api/Util/WeakReferenceWrapperSpec.swift @@ -0,0 +1,61 @@ +// +// WeakReferenceWrapperSpec.swift +// YMBroadcaster +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class WeakReferenceWrapperSpec: XCTestCase { + + /** + * When Creating a wrapper + * Then The stored reference should be conained in the wrapper + */ + + func testReferenceAssignment() { + + let testObject = TestObject() + let weakReferenceWrapper = WeakReferenceWrapper(testObject) + let storedReference = weakReferenceWrapper.getReference() + + XCTAssertTrue(storedReference === testObject) + } + + /** + * When An object that has been wrapped removed from all other scopes + * Then The object should be deallocated + * And the wrapper should no longer contain a reference to it + */ + func testReferenceDeallocated() { + + var testObject: TestObject? = TestObject() + + let deinitExpectation = expectation(description: "Deallocation expectation") + testObject?.deinitCallback = { + deinitExpectation.fulfill() + } + + let weakReferenceWrapper = WeakReferenceWrapper(testObject!) + testObject = nil + + XCTAssertNotNil(weakReferenceWrapper) + let storedReference = weakReferenceWrapper.getReference() + XCTAssertNil(storedReference) + + waitForExpectations(timeout: 1, handler: nil) + } +} + +private class TestObject { + + var deinitCallback: (() -> Void)? + + deinit { + deinitCallback?() + } +} diff --git a/KarhooSDKTests/TestCases/Networking/DefaultAuthTokenSpec.swift b/KarhooSDKTests/TestCases/Networking/DefaultAuthTokenSpec.swift new file mode 100644 index 00000000..55810817 --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/DefaultAuthTokenSpec.swift @@ -0,0 +1,51 @@ +// +// DefaultAuthTokenSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +final class DefaultAuthTokenProviderSpec: XCTestCase { + + private var mockUserDataStore: MockUserDataStore! + private var testObject: DefaultAccessTokenProvider! + + override func setUp() { + super.setUp() + + mockUserDataStore = MockUserDataStore() + testObject = DefaultAccessTokenProvider(userStore: mockUserDataStore) + } + + /** + * Given: User credentials saved + * When: Requesting authorization token + * Then: Correct token value should be returned + */ + func testTokenProvider() { + let testToken = "Test token" + mockUserDataStore.credentialsToReturn = Credentials(accessToken: testToken, + expiryDate: nil, + refreshToken: nil) + + let capturedToken = testObject.accessToken + XCTAssert(capturedToken?.token == testToken) + } + + /** + * Given: User credentials NOT saved + * When: Requesting authorization token + * Then: Nil should be returned + */ + func testMissingToken() { + mockUserDataStore.credentialsToReturn = nil + + let capturedToken = testObject.accessToken + XCTAssertNil(capturedToken) + } +} diff --git a/KarhooSDKTests/TestCases/Networking/Headers/HeaderProviderSpec.swift b/KarhooSDKTests/TestCases/Networking/Headers/HeaderProviderSpec.swift new file mode 100644 index 00000000..87c8c77a --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/Headers/HeaderProviderSpec.swift @@ -0,0 +1,190 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class HeaderProviderSpec: XCTestCase { + + private var testAccessTokenProvider: AccessTokenProvider! + private var testHeaderProvider: HeaderProvider! + private var testAuthCredentials: Credentials! + + override func setUp() { + super.setUp() + + let testDataStore = MockUserDataStore() + testAuthCredentials = ObjectTestFactory.getRandomCredentials() + testDataStore.credentialsToReturn = testAuthCredentials + testAccessTokenProvider = DefaultAccessTokenProvider(userStore: testDataStore) + + testHeaderProvider = KarhooHeaderProvider(authTokenProvider: testAccessTokenProvider) + } + + /** + * Given: A user makes a login request + * When: They add the auth token + * Then: The auth token shouldn't be added as its not needed + */ + func testAddingAuthTokenToLoginEndpointDoesntAddToken() { + var headers = HttpHeaders() + headers = testHeaderProvider.headersWithAuthorization(headers: &headers, endpoint: .login) + + XCTAssertEqual(0, headers.count) + } + + /** + * Given: A user makes a refresh request + * When: They add the auth token + * Then: The auth token shouldn't be added as its not needed + */ + func testAddingAuthTokenToRefreshEndpointDoesntAddToken() { + var headers = HttpHeaders() + headers = testHeaderProvider.headersWithAuthorization(headers: &headers, endpoint: .karhooUserTokenRefresh) + + XCTAssertEqual(0, headers.count) + } + + /** + * Given: A user makes a register request + * When: They add the auth token + * Then: The auth token shouldn't be added as its not needed + */ + func testAddingAuthTokenToRegisterEndpointDoesntAddToken() { + var headers = HttpHeaders() + headers = testHeaderProvider.headersWithAuthorization(headers: &headers, endpoint: .register) + + XCTAssertEqual(0, headers.count) + } + + /** + * Given: A user makes a password reset request + * When: They add the auth token + * Then: The auth token shouldn't be added as its not needed + */ + func testAddingAuthTokenToPasswordResetRequestDoesntAddToken() { + var headers = HttpHeaders() + + headers = testHeaderProvider.headersWithAuthorization(headers: &headers, endpoint: .passwordReset) + + XCTAssertEqual(0, headers.count) + } + + /** + * Given: Custom http headers provided + * When: One of the headers is the authorization headers + * Then: Custom headers should be set + * And: Authorization header should be set accordingly + * And: Global custom http headers should be set correctly + */ + func testRequestWithAuthProvidedHeaders() { + var httpHeaders = ["foo": "bar"] + + httpHeaders = testHeaderProvider.headersWithAuthorization(headers: &httpHeaders, endpoint: .bookTrip) + httpHeaders = testHeaderProvider.headersWithJSONContentType(headers: &httpHeaders) + + XCTAssertEqual(httpHeaders["foo"], "bar") + XCTAssertEqual(httpHeaders["Content-Type"], "application/json") + XCTAssertEqual(httpHeaders["authorization"], "Bearer \(testAuthCredentials.accessToken)") + } + + /** + * Given: A set of headers with some values + * When: Adding a new header + * Then: The combine func should return a combination of two headers + */ + func testAddingTwoHeadersTogetherUsingCombineMergesTheTwoHeadersIntoOne() { + var httpHeadersOne = ["foo": "bar"] + let httpHeadersTwo = ["bar": "foo"] + + let finalHeaders = testHeaderProvider.combine(headers: &httpHeadersOne, with: httpHeadersTwo) + + XCTAssertEqual("bar", finalHeaders["foo"]) + XCTAssertEqual("foo", finalHeaders["bar"]) + } + + /** + * Given: A network request is made + * When: The payload type is json + * Then: The content type should be of the correct value + */ + func testAddingJsonContentTypeGetsAppendedToTheHeader() { + var httpHeader = ["foo": "bar"] + + let finalHeaders = testHeaderProvider.headersWithJSONContentType(headers: &httpHeader) + + XCTAssertEqual("bar", finalHeaders["foo"]) + XCTAssertEqual("application/json", finalHeaders["Content-Type"]) + } + + /** + * Given: A network request is made + * When: Adding a correlation id + * Then: the correlation id should be appended + */ + func testSettingCorrelationIdAppendsOntoHeaders() { + var httpHeader = ["foo": "bar"] + + let finalHeaders = testHeaderProvider.headersWithCorrelationId(headers: &httpHeader, + endpoint: .availability) + let correlationId: String = finalHeaders["correlation_id"] ?? "" + + let prefix = String(correlationId.prefix(HeaderConstants.correlationIdPrefix.count)) + let daRest = correlationId.replacingOccurrences(of: HeaderConstants.correlationIdPrefix, with: "") + + XCTAssertEqual("bar", finalHeaders["foo"]) + XCTAssertEqual(HeaderConstants.correlationIdPrefix, prefix) + XCTAssertNotEqual("", daRest) + } + + /** + * Given: A request is made to retrieve a correlation id for a quote + * When: Adding a header to the request + * Then: The quote correlation id should be saved= + */ + func testMakingABookingRequestUsesTheSameIdAsQuote() { + var headers = HttpHeaders() + let quoteHeaders = testHeaderProvider.headersWithCorrelationId(headers: &headers, + endpoint: .quoteListId) + let bookingHeaders = testHeaderProvider.headersWithCorrelationId(headers: &headers, + endpoint: .bookTrip) + + XCTAssertEqual(quoteHeaders, bookingHeaders) + } + + /** + * Given: A request has been made to quotes + * When: A second request is made to quotes + * Then: The Ids should be different + */ + func testMultipleQuoteRequestsGenerateDifferentIds() { + var headers = HttpHeaders() + let quoteHeadersOne = testHeaderProvider.headersWithCorrelationId(headers: &headers, + endpoint: .quoteListId) + let quoteHeadersTwo = testHeaderProvider.headersWithCorrelationId(headers: &headers, + endpoint: .quoteListId) + + XCTAssertNotEqual(quoteHeadersOne, quoteHeadersTwo) + } + + /** + * Given: A request has been made to booking + * When: There is no quote correlation id saved + * Then: A new id should be generated + */ + func testNoSavedQuoteCorrelationIdCreatesOneForBooking() { + var headers = HttpHeaders() + let bookingHeaders = testHeaderProvider.headersWithCorrelationId(headers: &headers, + endpoint: .bookTrip) + + let correlationId: String = bookingHeaders["correlation_id"] ?? "" + + let prefix = String(correlationId.prefix(HeaderConstants.correlationIdPrefix.count)) + let daRest = correlationId.replacingOccurrences(of: HeaderConstants.correlationIdPrefix, with: "") + + XCTAssertEqual(HeaderConstants.correlationIdPrefix, prefix) + XCTAssertNotEqual("", daRest) + } +} diff --git a/KarhooSDKTests/TestCases/Networking/HttpClient/JsonHttpClientSpec.swift b/KarhooSDKTests/TestCases/Networking/HttpClient/JsonHttpClientSpec.swift new file mode 100644 index 00000000..80f5bfa0 --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/HttpClient/JsonHttpClientSpec.swift @@ -0,0 +1,275 @@ +// +// JsonHttpClientSpec.swift +// KarhooSDKTests +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class JsonHttpClientSpec: XCTestCase { + + private var testObject: JsonHttpClient! + private var capturedResult: Result? + private var mockURLSessionSender: MockURLSessionSender! + private var testHeaderProvider: HeaderProvider! + private var testAccessTokenProvider: AccessTokenProvider! + private var testAuthCredentials: Credentials! + private var testDataStore: MockUserDataStore! + private var testAnalytics: MockAnalyticsService! + + override func setUp() { + super.setUp() + + mockURLSessionSender = MockURLSessionSender() + testDataStore = MockUserDataStore() + testAuthCredentials = ObjectTestFactory.getRandomCredentials() + testDataStore.credentialsToReturn = testAuthCredentials + testAccessTokenProvider = DefaultAccessTokenProvider(userStore: testDataStore) + testHeaderProvider = KarhooHeaderProvider(authTokenProvider: testAccessTokenProvider) + testAnalytics = MockAnalyticsService() + + testObject = JsonHttpClient(urlSessionSender: mockURLSessionSender, + headerProvider: testHeaderProvider, + analyticsService: testAnalytics) + } + + /** + * When: Sending a request + * Then: Request is send + */ + func testRequestSend() { + sendSampleRequest() + + XCTAssertTrue(mockURLSessionSender.lastRequest != nil) + } + + /** + * When: Request is send + * Then: Absolute API URL should be correct + */ + func testRelativeApiUrl() { + sendSampleRequest() + + let url = mockURLSessionSender.lastRequest?.url + XCTAssertEqual("https://rest.sandbox.karhoo.com/v1/demand/mockPath", url?.absoluteString ?? "") + } + + /** + * Given: Karhoo user authentication method is set up + * When: Request is sent + * Then: Base url should be set + */ + func testKarhooUserAuthenticationMethodBaseURL() { + sendSampleRequest(path: "") + + let baseUrl = mockURLSessionSender.lastRequest?.url?.host + XCTAssertEqual("rest.sandbox.karhoo.com", baseUrl) + } + + /** + * Given: Karhoo user authentication method is set up + * When: Request is sent + * Then: Base url should be set + */ + func testAuthServiceBaseURL() { + let tokenAuthMethod = AuthenticationMethod.tokenExchange(settings: MockSDKConfig.tokenExchangeSettings) + Karhoo.set(configuration: MockSDKConfig(authMethod: tokenAuthMethod)) + + let authEndpoints: [APIEndpoint] = [.authRevoke, .authRefresh, .authUserInfo, .authTokenExchange] + + authEndpoints.forEach { endpoint in + sendToEndpoint(endpoint: endpoint) + + let baseUrl = mockURLSessionSender.lastRequest?.url?.host + + XCTAssertEqual("sso.sandbox.karhoo.com", baseUrl) + } + } + + /** + * Given: Anonymous user authentication method is set up + * When: Request is sent + * Then: Base url should be set + * And: API key should be set + */ + func testAnonymousAuthBaseURLAndAuthorisationHeaders() { + let settings = GuestSettings(identifier: "123", referer: "ref", organisationId: "") + Karhoo.set(configuration: MockSDKConfig(authMethod: .guest(settings: settings))) + sendToEndpoint(endpoint: .locationInfo) + + let url = mockURLSessionSender.lastRequest?.url?.host + let httpHeaders = mockURLSessionSender.lastRequest?.allHTTPHeaderFields + + XCTAssertEqual(httpHeaders?[HeaderConstants.identifier], "123") + XCTAssertEqual(httpHeaders?["Referer"], "ref") + XCTAssertEqual("public-api.sandbox.karhoo.com", url) + } + + /** + * Given: Custom http headers provided + * When: Post request is send + * And: Authorization header should be set correctly + * And: Content-type header should be set correctly + * And: Request interceptor headers should be added + */ + func testAuthorisationRequest() { + Karhoo.set(configuration: MockSDKConfig(authMethod: .karhooUser)) + sendSampleRequest(method: .post) + let httpHeaders = mockURLSessionSender.lastRequest?.allHTTPHeaderFields + + XCTAssertEqual(httpHeaders?["Content-Type"], "application/json") + XCTAssertEqual(httpHeaders?["Authorization"], "Bearer \(testAuthCredentials.accessToken)") + } + + /** + * Given: Empty Authorization header provided + * When: Sending the request + * Then: Authorization header should remain empty + */ + func testRequestWithEmptyAuthHeader() { + testDataStore.credentialsToReturn = nil + + sendSampleRequest(method: .post) + + guard let httpHeaders = mockURLSessionSender.lastRequest?.allHTTPHeaderFields else { + XCTFail("Could not set headers") + return + } + let token = httpHeaders["Authorization"] + XCTAssertEqual(token, nil) + } + + /** + * Given: Request response is empty + * When: Request is send + * Then: Completion callback should receive success with empty response details + */ + func testEmptyResponse() { + var capturedResult: Result? + mockURLSessionSender.mockResponse = (nil, nil, nil) + + sendSampleRequest(completion: { result in + capturedResult = result + }) + + XCTAssertTrue(capturedResult?.isSuccess() == true) + let httpResponse: HttpResponse? = capturedResult?.successValue() + XCTAssertNotNil(httpResponse) + + XCTAssertTrue(httpResponse!.code == 0) + XCTAssertTrue(httpResponse!.data == Data()) + } + + /** + * Given: Request response is a valid json + * When: Request is send + * Then: Completion callback should receive success with correct response details + */ + func testJsonResponse() { + let mockData = try? JSONSerialization.data(withJSONObject: [ + "foo": "bar" + ]) + + mockURLSessionSender.mockResponse = (mockData, mockURLResponse(), nil) + + sendSampleRequest() + + XCTAssertTrue(capturedResult?.isSuccess() == true) + let httpResponse: HttpResponse? = capturedResult?.successValue() + XCTAssertNotNil(httpResponse) + + XCTAssertTrue(httpResponse!.code == 201) + XCTAssertTrue(httpResponse!.data == mockData) + } + + /** + * Given: Request response is an empty json + * When: Request is send + * Then: Completion callback should receive success with correct response details + */ + func testEmptyJsonResponse() { + mockURLSessionSender.mockResponse = (Data(), nil, nil) + + sendSampleRequest() + + XCTAssertTrue(capturedResult?.isSuccess() == true) + let httpResponse: HttpResponse? = capturedResult?.successValue() + XCTAssertNotNil(httpResponse) + + XCTAssertTrue(httpResponse!.code == 0) + XCTAssertTrue(httpResponse!.data == Data()) + } + + /** + * Given: Request response is an error + * When: Request is send + * Then: Completion callback should receive an error and no status code + */ + func testErrorResponse() { + let expectedError = NSError(domain: "test", code: 22, userInfo: [:]) + mockURLSessionSender.mockResponse = (nil, nil, expectedError) + + sendSampleRequest() + + XCTAssertTrue(capturedResult?.isSuccess() == false) + let httpResponse: HttpResponse? = capturedResult?.successValue() + XCTAssertNil(httpResponse) + XCTAssertNotNil(capturedResult!.errorValue()) + } + + /** + * Given: Request response status code is an error + * When: Request is send + * Then: Completion callback should receive an error + */ + func testErrorResponseCode() { + let errorCode = HttpStatus.badRequest.rawValue + let response = mockURLResponse(statusCode: errorCode) + mockURLSessionSender.mockResponse = (Data(), response, nil) + + sendSampleRequest() + + XCTAssertTrue(capturedResult?.isSuccess() == false) + let errorValue = capturedResult?.errorValue() as? HTTPError + XCTAssertEqual(errorCode, errorValue?.statusCode) + } + + /** + * Given: Request response is an error + * When: Request is send + * Then: An analytic event is sent to notify the error + */ + func testErrorResponseAnalyticSent() { + let expectedError = NSError(domain: "test", code: 22, userInfo: [:]) + mockURLSessionSender.mockResponse = (nil, nil, expectedError) + + sendSampleRequest() + + XCTAssertEqual(testAnalytics.eventSent, AnalyticsConstants.EventNames.requestFails) + } +} + +private extension JsonHttpClientSpec { + func sendSampleRequest(method: HttpMethod = .post, + path: String = "/v1/demand/mockPath", + completion: CallbackClosure? = nil) { + _ = testObject.sendRequest(endpoint: .custom(path: path, method: method), + completion: completion ?? self.saveResponse) + } + + func sendToEndpoint(endpoint: APIEndpoint, completion: CallbackClosure? = nil) { + _ = testObject.sendRequest(endpoint: endpoint, completion: completion ?? self.saveResponse) + } + func saveResponse(_ result: Result) { + capturedResult = result + } + + func mockURLResponse(statusCode: Int = 201) -> HTTPURLResponse { + return HTTPURLResponse(url: URL(string: "www.google.pl")!, + statusCode: statusCode, + httpVersion: "1.0", + headerFields: nil)! + } +} diff --git a/KarhooSDKTests/TestCases/Networking/HttpClient/JsonHttpRequestBuilderSpec.swift b/KarhooSDKTests/TestCases/Networking/HttpClient/JsonHttpRequestBuilderSpec.swift new file mode 100644 index 00000000..c133711d --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/HttpClient/JsonHttpRequestBuilderSpec.swift @@ -0,0 +1,57 @@ +import XCTest + +@testable import KarhooSDK + +final class JsonHttpRequestBuilderSpec: XCTestCase { + private var testObject: JsonHttpRequestBuilder! + + override func setUp() { + super.setUp() + + self.testObject = JsonHttpRequestBuilder() + } + + /** + * Given: Body and header provided + * When: Building a request + * Then: Result should be configured properly + */ + func testRequestWithBody() throws { + let request = try? self.testObject.request(method: .get, + url: URL(string: "www.google.pl")!, + headers: ["foo": "bar"], + data: try JSONEncoder().encode(["kar": "hoo"])) + + XCTAssertNotNil(request) + XCTAssertEqual(request!.httpMethod ?? "", "GET") + XCTAssertEqual(request?.url?.absoluteString, "www.google.pl") + XCTAssertEqual(request?.allHTTPHeaderFields?["foo"], "bar") + + let json = try? JSONSerialization.jsonObject(with: request?.httpBody ?? Data()) as? Json + XCTAssertTrue(json != nil) + XCTAssertTrue(json! == ["kar": "hoo"] as Json) + } + + /** + * Given: Body and header NOT provided + * When: Building a request + * Then: Result should be configured properly + */ + func testRequestWithoutBody() { + let url = URL(string: "www.google.pl")! + let request = try? self.testObject.request(method: .get, url: url, headers: nil, data: nil) + + XCTAssertNotNil(request) + XCTAssertEqual(request!.httpMethod ?? "", "GET") + XCTAssertEqual(request?.url?.absoluteString, "www.google.pl") + XCTAssertNil(request?.allHTTPHeaderFields?["foo"]) + XCTAssertNil(request?.allHTTPHeaderFields?["Content-Type"]) + + let json = try? JSONSerialization.jsonObject(with: request?.httpBody ?? Data()) + XCTAssertTrue(json == nil) + } + +} + +private class TestNonSerializable { +} diff --git a/KarhooSDKTests/TestCases/Networking/HttpClient/TokenRefreshingHttpClientSpec.swift b/KarhooSDKTests/TestCases/Networking/HttpClient/TokenRefreshingHttpClientSpec.swift new file mode 100644 index 00000000..415fa055 --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/HttpClient/TokenRefreshingHttpClientSpec.swift @@ -0,0 +1,295 @@ +// +// TokenRefreshingHttpClientSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class TokenRefreshingHttpClientSpec: XCTestCase { + + private var mockHttpClient: MockHttpClient! + private var testRefreshTokenProvider: TestRefreshTokenProvider! + private var mockUserDataStore: MockUserDataStore! + private var testObject: TokenRefreshingHttpClient! + + override func setUp() { + super.setUp() + + mockHttpClient = MockHttpClient() + testRefreshTokenProvider = TestRefreshTokenProvider() + mockUserDataStore = MockUserDataStore() + + testObject = TokenRefreshingHttpClient(httpClient: mockHttpClient, + refreshTokenProvider: testRefreshTokenProvider, + dataStore: mockUserDataStore) + } + + /** + * Given: Access token does NOT need refreshing + * When: Sending a request + * Then: Request should be send immediately + * And: Request send from httpClient should be returned + */ + func testTokenDoesNotNeedRefreshingSendRequest() { + testRefreshTokenProvider.tokenNeedsRefreshingValueToReturn = false + let mockNetworkRequest = MockNetworkRequest() + mockHttpClient.networkRequestToReturn = mockNetworkRequest + + var capturedResult: Result? + let capturedRequest = testObject.sendRequest(endpoint: .availability) { result in + capturedResult = result + } + + let mockResult = HttpResponse(code: 200, data: Data()) + mockHttpClient.lastCompletion?(Result.success(result: mockResult)) + + XCTAssert(mockHttpClient.sendRequestsCount == 1) + XCTAssert(capturedRequest! as? MockNetworkRequest === mockNetworkRequest) + XCTAssert(capturedResult?.isSuccess() == true) + } + + /** + * Given: Access token DOES need refreshing + * When: Sending a request + * Then: KarhooRefreshTokenInteractor should refresh token + * And: HttpClient should send a request + * And: AsyncNetworkRequestWrapper instance should be returned + */ + func testTokenDoesNeedRefreshing() { + testRefreshTokenProvider.tokenNeedsRefreshingValueToReturn = true + let mockNetworkRequest = MockNetworkRequest() + mockHttpClient.networkRequestToReturn = mockNetworkRequest + + let capturedRequest = testObject.sendRequest(endpoint: .availability) { _ in } + + XCTAssertNotNil(capturedRequest) + XCTAssertNotNil((capturedRequest as? AsyncNetworkRequestWrapper)?.hasRequest) + } + + /** + * Given: Access token DOES need refreshing + * And: Sending a request + * When: KarhooRefreshTokenInteractor token refresh succeeds + * Then: Http client call should be made + * And: Callback should be fired + */ + func testTokenDoesNeedRefreshingRefreshSuccess() { + testRefreshTokenProvider.tokenNeedsRefreshingValueToReturn = true + let mockNetworkRequest = MockNetworkRequest() + mockHttpClient.networkRequestToReturn = mockNetworkRequest + + var capturedResult: Result? + let capturedRequest = testObject.sendRequest(endpoint: .quoteListId) { result in + capturedResult = result + } + + testRefreshTokenProvider.capturedRefreshTokenCompletion?(Result.success(result: true)) + + let mockResult = HttpResponse(code: 200, data: Data()) + mockHttpClient.lastCompletion?(Result.success(result: mockResult)) + + XCTAssertNotNil(capturedRequest) + XCTAssert(mockHttpClient.sendRequestsCount == 1) + XCTAssert(capturedResult?.isSuccess() == true) + } + + /** + * Given: Access token DOES need refreshing + * And: Sending a request + * And: Returned NetworkRequest is cancelled + * And: KarhooRefreshTokenInteractor token refresh succeeds + * When: Refresh Token completes + * Then: Http client call should NOT be made + */ + func testTokenDoesNeedRefreshingRefreshSuccessRequestCancelled() { + testRefreshTokenProvider.tokenNeedsRefreshingValueToReturn = true + let mockNetworkRequest = MockNetworkRequest() + mockHttpClient.networkRequestToReturn = mockNetworkRequest + + let capturedRequest = testObject.sendRequest(endpoint: .bookTrip) { _ in } + + capturedRequest?.cancel() + testRefreshTokenProvider.capturedRefreshTokenCompletion?(Result.success(result: true)) + + XCTAssert(testRefreshTokenProvider.refreshTokenCalled) + XCTAssert(mockHttpClient.sendRequestsCount == 0) + } + + /** + * Given: Access token DOES need refreshing + * And: Sending a request + * And: Returned NetworkRequest is cancelled + * And: KarhooRefreshTokenInteractor token refresh succeeds + * And: Network request is made + * And: Returned network request gets cancelled + * When: Actual network request returns + * Then: Completion block should NOT be called + */ + func testTokenNeedsRefreshingSuccessNetworkRequestMadeAndCancelled() { + testRefreshTokenProvider.tokenNeedsRefreshingValueToReturn = true + let mockNetworkRequest = MockNetworkRequest() + mockHttpClient.networkRequestToReturn = mockNetworkRequest + + let capturedRequest = testObject.sendRequest(endpoint: .bookTrip) { _ in } + + testRefreshTokenProvider.capturedRefreshTokenCompletion?(Result.success(result: true)) + + capturedRequest?.cancel() + XCTAssert(mockNetworkRequest.cancelCalled) + } + + /** + * Given: Access token DOES need refreshing + * And: Sending a request + * And: NO network connection + * When: KarhooRefreshTokenInteractor token refresh failure + * Then: Http client call should NOT be made + * And: Failure Callback should be called with an error + */ + func testTokenDoesNeedRefreshingRefreshFailureNoNetwork() { + testRefreshTokenProvider.tokenNeedsRefreshingValueToReturn = true + let mockNetworkRequest = MockNetworkRequest() + mockHttpClient.networkRequestToReturn = mockNetworkRequest + + var capturedResult: Result? + testObject.sendRequest(endpoint: .bookTrip) { result in + capturedResult = result + } + + let noConnectionError = HTTPError(statusCode: 0, errorType: .notConnectedToInternet) + let errorResult = Result.failure(error: noConnectionError) + testRefreshTokenProvider.capturedRefreshTokenCompletion?(errorResult) + + XCTAssert(mockHttpClient.sendRequestsCount == 0) + XCTAssert(capturedResult?.isSuccess() == false) + XCTAssertEqual(noConnectionError, capturedResult?.errorValue() as? HTTPError) + } + + /** + * Given: Access token DOES need refreshing + * And: Sending a request + * And: Network connection is up + * When: KarhooRefreshTokenInteractor token refresh failure + * Then: Http client call should be made + * And: AsyncNetworkRequestWrapper instance should be returned + * And: Callback should be called with refresh token error + */ + func testTokenDoesNeedRefreshingRefreshFailureWithNetworkConnection() { + testRefreshTokenProvider.tokenNeedsRefreshingValueToReturn = true + let mockNetworkRequest = MockNetworkRequest() + mockHttpClient.networkRequestToReturn = mockNetworkRequest + + var capturedResult: Result? + let capturedRequest = testObject.sendRequest(endpoint: .bookTrip) { result in + capturedResult = result + } + + let tokenError = TestUtil.getRandomError() + let tokenRefreshErrorResult = Result.failure(error: tokenError) + testRefreshTokenProvider.capturedRefreshTokenCompletion?(tokenRefreshErrorResult) + + XCTAssertNotNil((capturedRequest as? AsyncNetworkRequestWrapper)?.hasRequest) + XCTAssert(capturedResult?.isSuccess() == false) + XCTAssert(tokenError.equals(capturedResult!.errorValue()!)) + } + + /** + * Given: Access token does NOT need refreshing + * And: Sending a network request + * And: The response is 401 unauthorized error code + * When: It tries to refresh token + * And: Token refresh is successful + * Then: It should retry making the request + * And: Request result callback should be executed + */ + func testSuccessfulTokenRefreshOn401() { + testRefreshTokenProvider.tokenNeedsRefreshingValueToReturn = false + mockHttpClient.networkRequestToReturn = MockNetworkRequest() + + var capturedResult: Result? + testObject.sendRequest(endpoint: .availability) { result in + capturedResult = result + } + + testRefreshTokenProvider.refreshTokenResult = Result.success(result: true) + XCTAssert(mockHttpClient.sendRequestsCount == 1) + + let unauthenticatedError = TestUtil.getUnauthenticatedError() + mockHttpClient.lastCompletion?(Result.failure(error: unauthenticatedError)) + + XCTAssert(mockHttpClient.sendRequestsCount == 2) + mockHttpClient.lastCompletion?(Result.success(result: HttpResponse(code: 200, data: Data()))) + + XCTAssert(testRefreshTokenProvider.tokenNeedsRefreshingCalled) + XCTAssert(testRefreshTokenProvider.refreshTokenCalled) + + XCTAssert(capturedResult?.isSuccess() == true) + } + + /** + * Given: Access token does NOT need refreshing + * And: Sending a network request + * And: The response is 401 unauthorized error code + * When: It tries to refresh token + * And: Token refresh fails + * Then: User should be logged out + * And: Request result callback should be executed + */ + func testTokenRefreshFailureOn401() { + mockUserDataStore.storedUser = UserInfoMock().set(userId: "some").build() + + testRefreshTokenProvider.tokenNeedsRefreshingValueToReturn = false + mockHttpClient.networkRequestToReturn = MockNetworkRequest() + + var capturedResult: Result? + testObject.sendRequest(endpoint: .availability) { result in + capturedResult = result + } + + testRefreshTokenProvider.refreshTokenResult = Result.failure(error: TestUtil.getRandomError()) + XCTAssert(mockHttpClient.sendRequestsCount == 1) + + let unauthenticatedError = TestUtil.getUnauthenticatedError() + mockHttpClient.lastCompletion?(Result.failure(error: unauthenticatedError)) + + XCTAssert(mockHttpClient.sendRequestsCount == 1) + let expectedError = TestUtil.getRandomError() + mockHttpClient.lastCompletion?(Result.failure(error: expectedError)) + + XCTAssert(testRefreshTokenProvider.tokenNeedsRefreshingCalled) + XCTAssert(testRefreshTokenProvider.refreshTokenCalled) + + XCTAssert(expectedError.equals(capturedResult!.errorValue()!)) + + XCTAssert(mockUserDataStore.removeUserCalled == true) + } +} + +private final class TestRefreshTokenProvider: RefreshTokenInteractor { + + private(set) var tokenNeedsRefreshingCalled = false + var tokenNeedsRefreshingValueToReturn = false + + func tokenNeedsRefreshing() -> Bool { + tokenNeedsRefreshingCalled = true + return tokenNeedsRefreshingValueToReturn + } + + private(set) var refreshTokenCalled = false + var refreshTokenResult: Result? + + var capturedRefreshTokenCompletion: ((Result) -> Void)? + + func refreshToken(completion: @escaping (Result) -> Void) { + refreshTokenCalled = true + capturedRefreshTokenCompletion = completion + + if let result = refreshTokenResult { + completion(result) + } + } +} diff --git a/KarhooSDKTests/TestCases/Networking/KarhooNetworkDateFormatterSpec.swift b/KarhooSDKTests/TestCases/Networking/KarhooNetworkDateFormatterSpec.swift new file mode 100644 index 00000000..8de1a163 --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/KarhooNetworkDateFormatterSpec.swift @@ -0,0 +1,72 @@ +// +// KarhooNetworkDateFormatterSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class KarhooNetworkDateFormatterSpec: XCTestCase { + + /** + * Given: Requesting availability (quotes) + * When: Date is UTC + * Then: The string should be correct in the correct (iso-8601) format (no seconds) + */ + func testConvertingDateForAvailabilityRequestUTC() { + let date = Date(timeIntervalSince1970: 150000) + let output = KarhooNetworkDateFormatter(formatType: .availability).toString(from: date) + + XCTAssertEqual("1970-01-02T17:40", output) + } + + /** + * Given: Requesting availability (quotes) + * When: Time zone is CET (Paris) + * Then: The string should be correct in the correct (iso-8601) format (no seconds) + */ + func testConvertingDateForAvailabilityRequestInParis() { + let dateRequired = Date(timeIntervalSince1970: 1494583933) // UTC (2017-05-12T10:12) + + let dateFormatter = KarhooNetworkDateFormatter(timeZone: TimeZone(abbreviation: "CET")!, + formatType: .availability) + let output = dateFormatter.toString(from: dateRequired) + + XCTAssertEqual("2017-05-12T12:12", output) + } + + /** + * Given: date_scheduled API response as input (returned in UTC) + * When: Time zone in UTC + * Then: expected Date should be set + * And: the expected date should be represented correctly as a string + */ + func testDateScheduledUTC() { + let input = "2018-04-21T12:35:00Z" + let dateFormatter = KarhooNetworkDateFormatter(formatType: .booking) + let output = dateFormatter.toDate(from: input) + XCTAssertEqual("2018-04-21 12:35:00 +0000", output?.description) + } + + /** + * Given: date_scheduled API response as input (returned in UTC) + * When: Time zone is CET + * Then: expected Date should be set + * And: the expected date should be represented correctly as a string + */ + func testDateScheduledParis() { + let input = "2018-04-21T12:35:00Z" + let stringToDateFormatter = KarhooNetworkDateFormatter(formatType: .booking) + let dateOutput = stringToDateFormatter.toDate(from: input) + + XCTAssertEqual("2018-04-21 12:35:00 +0000", dateOutput?.description) + + let dateToStringFormatter = KarhooNetworkDateFormatter(timeZone: TimeZone(abbreviation: "CET"), + formatType: .booking) + let formatterOutput = dateToStringFormatter.toString(from: dateOutput!) + XCTAssertEqual("2018-04-21T14:35:00GMT+2", formatterOutput) + } +} diff --git a/KarhooSDKTests/TestCases/Networking/KarhooRequestSenderSpec.swift b/KarhooSDKTests/TestCases/Networking/KarhooRequestSenderSpec.swift new file mode 100644 index 00000000..cf9579ca --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/KarhooRequestSenderSpec.swift @@ -0,0 +1,86 @@ +// +// KarhooRequestSenderSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooRequestSenderSpec: XCTestCase { + + private var testRequestSender: KarhooRequestSender! + private var mockHttpClient: MockHttpClient! + private var requestTesting: RequestTesting! + private var mockPayload: MockPayload! + + let endpoint = APIEndpoint.availability + + override func setUp() { + mockPayload = MockPayload(value: "some") + mockHttpClient = MockHttpClient() + requestTesting = RequestTesting(httpClient: mockHttpClient) + testRequestSender = KarhooRequestSender(httpClient: mockHttpClient) + + super.setUp() + } + + /** + * When: Sending a request + * Then: The correct request should be sent + */ + func testRequest() { + testRequestSender.request(payload: mockPayload, endpoint: endpoint, callback: { _ in}) + + XCTAssertEqual(1, mockHttpClient.sendRequestsCount) + requestTesting.assertRequestSend(endpoint: endpoint, body: mockPayload.encode()!) + } + + /** + * When: Request is successful + * Then: Correct callback should be propogated + */ + func testSuccessRequest() { + var requestCallback: Result? + + testRequestSender.request(payload: mockPayload, + endpoint: endpoint, + callback: { (response: Result) in + requestCallback = response + }) + + let response = HttpResponse(code: 200, data: mockPayload.encode()!) + let success = Result.success(result: response) + mockHttpClient.lastCompletion?(success) + + XCTAssertTrue(requestCallback!.isSuccess()) + XCTAssertEqual(requestCallback?.successValue()?.data, mockPayload.encode()!) + } + + /** + * When: Request is successful + * Then: Correct callback should be propogated + */ + func testRequestFails() { + var requestCallback: Result? + testRequestSender.request(payload: mockPayload, + endpoint: endpoint, + callback: { (response: Result) in + requestCallback = response + }) + + let responseError = TestUtil.getRandomError() + mockHttpClient.lastCompletion?(Result.failure(error: responseError)) + + XCTAssertFalse(requestCallback!.isSuccess()) + XCTAssertEqual(requestCallback?.errorValue()?.localizedDescription, responseError.localizedDescription) + } +} + +struct MockPayload: KarhooCodableModel { + var value: String +} diff --git a/KarhooSDKTests/TestCases/Networking/ReachabilityWrapperSpec.swift b/KarhooSDKTests/TestCases/Networking/ReachabilityWrapperSpec.swift new file mode 100644 index 00000000..b48fe939 --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/ReachabilityWrapperSpec.swift @@ -0,0 +1,238 @@ +// +// ReachabilityWrapperSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import Reachability +@testable import KarhooSDK + +class ReachabilityWrapperSpec: XCTestCase { + + private var mockReachability: MockReachability! + private var mockBroadcaster: MockBroadcaster! + private var testObject: ReachabilityWrapper! + + override func setUp() { + super.setUp() + + mockReachability = MockReachability() + mockBroadcaster = MockBroadcaster() + + testObject = ReachabilityWrapper(reachability: mockReachability, + broadcaster: mockBroadcaster) + } + + /** + * When: Adding a listener + * Then: The listener should be added to the underlying broadcaster + * And: The listener should be informed of the current reachability status + */ + func testAddListener() { + let listener = TestReachabilityListener() + mockReachability.reachabilityStatus = .cellular + + testObject.add(listener: listener) + + XCTAssert(mockBroadcaster.lastListenerAdded === listener) + XCTAssert(listener.lastReachabilityStatus == true) + } + + /** + * Given: There are no listeners + * When: Adding a listener + * Then: Reachability notifier should be started + */ + func testStartNotifier() { + let listener = TestReachabilityListener() + testObject.add(listener: listener) + + XCTAssertTrue(mockReachability.notifierRunning) + } + + /** + * Given: There's one reachability listener + * When: A new listener is added + * Then: Reachability notifier should keep running + */ + func testNotifiersMultipleListeners() { + let firstListener = TestReachabilityListener() + let secondListener = TestReachabilityListener() + + testObject.add(listener: firstListener) + testObject.add(listener: secondListener) + + XCTAssertTrue(mockReachability.notifierRunning) + } + + /** + * Given: There is one reachability listener + * When: Last listener removed + * Then: Reachability notifier should be stopped + */ + func testStopNotifier() { + let listener = TestReachabilityListener() + testObject.add(listener: listener) + testObject.remove(listener: listener) + + XCTAssertFalse(mockReachability.notifierRunning) + } + + /** + * When: Removing a listener + * Then: The listener should be removed from the underlying broadcaster + */ + func testRemoveListener() { + let listener = TestReachabilityListener() + + testObject.remove(listener: listener) + + XCTAssert(mockBroadcaster.lastListenerRemoved === listener) + } + + /** + * When: Checking if reachable + * Then: The corresponding bool to each status should be returned + */ + func testIsReachable() { + let testCases: [Reachability.Connection: Bool] = [.unavailable: false, + .wifi: true, + .cellular: true] + + for (status, expectedResult) in testCases { + mockReachability.reachabilityStatus = status + let result = testObject.isReachable() + + XCTAssertEqual(result, expectedResult) + } + } + + /** + * When: Reachability goes from reachable to non-reachable + * Then: The listeners should be informed + */ + func testReachabilityChangeReachableToNot() { + changeReachability(to: .cellular) + let listener = TestReachabilityListener() + testObject.add(listener: listener) + + let listener2 = TestReachabilityListener() + testObject.add(listener: listener2) + + changeReachability(to: .unavailable) + + XCTAssert(listener.lastReachabilityStatus == false) + XCTAssert(listener2.lastReachabilityStatus == false) + } + + /** + * When: Reachability goes from unreachabble to reachabble + * Then: The listeners should be informed + */ + func testReachabilityChangeUnReachableToReachable() { + let listener = TestReachabilityListener() + testObject.add(listener: listener) + + let listener2 = TestReachabilityListener() + testObject.add(listener: listener2) + + changeReachability(to: .wifi) + + XCTAssert(listener.lastReachabilityStatus == true) + XCTAssert(listener2.lastReachabilityStatus == true) + } + + /** + * When: Reachability goes from reachable to reachable + * Then: Nothing should happen (listeners should not be informed) + */ + func testReachabilityChangeReachableToReachable() { + changeReachability(to: .cellular) + + let listener = TestReachabilityListener() + testObject.add(listener: listener) + listener.lastReachabilityStatus = nil + + let listener2 = TestReachabilityListener() + testObject.add(listener: listener2) + listener2.lastReachabilityStatus = nil + + changeReachability(to: .wifi) + + XCTAssertNil(listener.lastReachabilityStatus) + XCTAssertNil(listener2.lastReachabilityStatus) + } + + /** + * When: Reachability goes from unreachabble to unreachabble + * Then: Nothing should happen (listeners should not be informed) + */ + func testReachabilityChangeUnReachableToUnReachable() { + changeReachability(to: .unavailable) + + let listener = TestReachabilityListener() + testObject.add(listener: listener) + listener.lastReachabilityStatus = nil + + let listener2 = TestReachabilityListener() + testObject.add(listener: listener2) + listener2.lastReachabilityStatus = nil + + changeReachability(to: .unavailable) + + XCTAssertNil(listener.lastReachabilityStatus) + XCTAssertNil(listener2.lastReachabilityStatus) + } + + private func changeReachability(to status: Reachability.Connection) { + mockReachability.triggerReachabilityChange(to: status) + + let exp = expectation(description: "Waiting for callback") + DispatchQueue.main.async { + exp.fulfill() + } + wait(for: [exp], timeout: 1) + } +} + +private class MockReachability: ReachabilityProtocol { + var reachabilityStatus: Reachability.Connection = .unavailable + var whenReachable: Reachability.NetworkReachable? + var whenUnreachable: Reachability.NetworkUnreachable? + private(set) var notifierRunning = false + + var currentReachabilityStatus: Reachability.Connection { + return reachabilityStatus + } + + func triggerReachabilityChange(to status: Reachability.Connection) { + reachabilityStatus = status + let reachabilityObject = try? Reachability() /** this object is ignored, but is + required as whenReachable/whenUnreachable callbacks parameter */ + + if status == .unavailable { + whenUnreachable?(reachabilityObject!) + } else { + whenReachable?(reachabilityObject!) + } + } + + func startNotifier() throws { + notifierRunning = true + } + + func stopNotifier() { + notifierRunning = false + } +} + +private class TestReachabilityListener: ReachabilityListener { + var lastReachabilityStatus: Bool? + func reachabilityChanged(isReachable: Bool) { + lastReachabilityStatus = isReachable + } +} diff --git a/KarhooSDKTests/TestCases/Networking/RequestTesting.swift b/KarhooSDKTests/TestCases/Networking/RequestTesting.swift new file mode 100644 index 00000000..4a2a3c1c --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/RequestTesting.swift @@ -0,0 +1,30 @@ +// +// RequestTesting.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest +@testable import KarhooSDK + +final class RequestTesting { + private let httpClient: MockHttpClient + + init(httpClient: MockHttpClient) { + self.httpClient = httpClient + } + + func assertRequestSend(endpoint: APIEndpoint, headers: HttpHeaders? = nil, body: Data) { + XCTAssertTrue(httpClient.sendRequestsCount > 0) + XCTAssertEqual(httpClient.lastRequestMethod, endpoint.method) + XCTAssertEqual(httpClient.lastRequestPath, endpoint.path) + XCTAssertEqual(httpClient.lastRequestEndpoint, endpoint) + guard let setBody = httpClient.lastRequestBody else { + return + } + XCTAssertEqual(setBody, body) + } +} diff --git a/KarhooSDKTests/TestCases/Networking/TokenRefresh/KarhooRefreshTokenInteractorSpec.swift b/KarhooSDKTests/TestCases/Networking/TokenRefresh/KarhooRefreshTokenInteractorSpec.swift new file mode 100644 index 00000000..65d909b6 --- /dev/null +++ b/KarhooSDKTests/TestCases/Networking/TokenRefresh/KarhooRefreshTokenInteractorSpec.swift @@ -0,0 +1,317 @@ +// +// KarhooRefreshTokenInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class KarhooRefreshTokenInteractorSpec: XCTestCase { + private var testObject: KarhooRefreshTokenInteractor! + private var mockUserDataStore: MockUserDataStore! + private var mockRequestSender: MockRefreshTokenRequest! + + override func setUp() { + super.setUp() + Karhoo.set(configuration: MockSDKConfig()) + + mockUserDataStore = MockUserDataStore() + mockRequestSender = MockRefreshTokenRequest() + + testObject = KarhooRefreshTokenInteractor(dataStore: mockUserDataStore, + refreshTokenRequest: mockRequestSender) + } + + /** + * When: Requesting token + * Then: Request should fire with expected path / method + */ + func testRefreshRequest() { + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheNearFuture()) + mockUserDataStore.credentialsToReturn = credentials + + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + testObject.refreshToken(completion: { _ in}) + + mockRequestSender.assertRequestSend(endpoint: .karhooUserTokenRefresh, method: .post) + } + + /** + * When: Requesting token (Auth settings) + * Then: Request should fire with expected path / method + */ + func testAuthRefreshRequest() { + Karhoo.set(configuration: MockSDKConfig(authMethod: .tokenExchange(settings: MockSDKConfig.tokenExchangeSettings))) + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheNearFuture()) + mockUserDataStore.credentialsToReturn = credentials + + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + testObject.refreshToken(completion: { _ in}) + + mockRequestSender.assertRequestSend(endpoint: .authRefresh, method: .post) + } + + /** + * When: Using Guest mode + * Then: Token refresh required should be false + */ + func testGuestAuthenticationMode() { + let settings = GuestSettings(identifier: "123", referer: "ref", organisationId: "") + Karhoo.set(configuration: MockSDKConfig(authMethod: .guest(settings: settings))) + + testObject.refreshToken(completion: { _ in}) + + XCTAssertFalse(testObject.tokenNeedsRefreshing()) + } + + /** + * Given: Credentials authentication token expirationDate is far in the future + * When: tokenNeedsRefreshing method called + * Then: It should return false + */ + func testTokenDoesntNeedRefreshing() { + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheFarFuture()) + mockUserDataStore.credentialsToReturn = credentials + + XCTAssertFalse(testObject.tokenNeedsRefreshing()) + } + + /** + * Given: Credentials authentication token expirationDate is near in the future + * When: tokenNeedsRefreshing method called + * Then: It should return true + */ + func testTokenDoesNeedRefreshingDateFarFuture() { + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheNearFuture()) + mockUserDataStore.credentialsToReturn = credentials + + XCTAssertTrue(testObject.tokenNeedsRefreshing()) + } + + /** + * Given: Credentials authentication token expirationDate is in the past + * When: tokenNeedsRefreshing method called + * Then: It should return true + */ + func testTokenDoesNeedRefreshingPastDate() { + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInThePast()) + mockUserDataStore.credentialsToReturn = credentials + + XCTAssertTrue(testObject.tokenNeedsRefreshing()) + } + + /** + * Given: Credentials authentication token expirationDate is far in the future + * When: refreshToken method called + * Then: Network request to refresh token is NOT made + * And: Completion block is called with result success(false) + */ + func testNoRefreshNeeded() { + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheFarFuture()) + mockUserDataStore.credentialsToReturn = credentials + + var capturedResult: Result? + testObject.refreshToken { result in + capturedResult = result + } + + XCTAssertFalse(mockRequestSender.requestCalled) + XCTAssertTrue(capturedResult!.isSuccess()) + XCTAssertFalse(capturedResult!.successValue()!) + } + + /** + * Given: Credentials authentication token expirationDate is nil + * When: tokenNeedsRefreshing method called + * Then: It should return true + */ + func testNilExpiryDateRefreshCheck() { + let credentials = Credentials(accessToken: "123", + expiryDate: nil, + refreshToken: "123") + mockUserDataStore.credentialsToReturn = credentials + + XCTAssertTrue(testObject.tokenNeedsRefreshing()) + } + + /** + * Given: Refresh token not saved in user data store + * And: Token expiration date in near future + * When: refreshToken method called + * Then: Callback should be fired with an error + */ + func testMissingRefreshToken() { + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheNearFuture(), + withRefreshToken: false) + mockUserDataStore.credentialsToReturn = credentials + + var capturedResult: Result? + testObject.refreshToken { result in + capturedResult = result + } + + let returnedError = capturedResult?.errorValue() as? RefreshTokenError + XCTAssertNotNil(returnedError) + XCTAssertEqual(.noRefreshToken, returnedError) + } + + /** + * Given: Refresh token expirationDate is near + * And: refreshToken method called + * And: Network request is made to refresh token + * When: Network request to refresh token returns + * Then: New credentials should be saved in user data store + */ + func testRefreshSuccess() { + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheNearFuture()) + mockUserDataStore.credentialsToReturn = credentials + + let accessTokenAfterRefresh = TestUtil.getRandomString() + let successResponse = AuthTokenMock().set(accessToken: accessTokenAfterRefresh).set(expiresIn: 10000) + + var capturedResult: Result? + testObject.refreshToken { result in + capturedResult = result + } + + mockRequestSender.success(response: successResponse.build()) + + XCTAssertTrue(mockRequestSender.requestCalled) + XCTAssertTrue(capturedResult!.isSuccess()) + XCTAssertTrue(capturedResult!.successValue()!) + XCTAssertEqual(mockUserDataStore.storedCredentials?.accessToken, accessTokenAfterRefresh) + } + + /** + * Given: Refresh token expirationDate is near + * And: refreshToken method called + * And: Network request is made to refresh token + * When: Network request to refresh token fails + * Then: New credentials should NOT be saved in user data store + * And: Callback should return httpClient error + */ + func testRefreshTokenError() { + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheNearFuture()) + mockUserDataStore.credentialsToReturn = credentials + + let expectedError = TestUtil.getRandomError() + + var capturedResult: Result? + testObject.refreshToken { result in + capturedResult = result + } + + mockRequestSender.fail(error: expectedError) + + XCTAssertTrue(mockRequestSender.requestCalled) + XCTAssertFalse(mockUserDataStore.setCurrentUserCalled) + XCTAssert(expectedError.equals(capturedResult!.errorValue()!)) + + } + + /** + * Given: Refresh token expirationDate is near + * And: User credentials stored in user data store + * And: refreshToken method called + * And: Network request is made to refresh token + * When: Network request to refresh token succeeds + * And: User object is no longer stored in a data store + * Then: New credentials should NOT be saved in user data store + * And: Callback should return userAlreadyLoggedOut error + */ + func testRefreshTokenErrorUserJustLoggedOut() { + + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheNearFuture()) + mockUserDataStore.credentialsToReturn = credentials + + let accessTokenAfterRefresh = TestUtil.getRandomString() + + let requestPayload = AuthTokenMock().set(accessToken: accessTokenAfterRefresh).set(expiresIn: 10000) + + mockRequestSender.beforeResponse = { [weak self] in + self?.mockUserDataStore.userToReturn = nil + } + + var capturedResult: Result? + testObject.refreshToken { result in + capturedResult = result + } + + mockRequestSender.success(response: requestPayload.build()) + XCTAssertTrue(mockRequestSender.requestCalled) + XCTAssert(mockUserDataStore.setCurrentUserCalled == false) + let expectedError = RefreshTokenError.userAlreadyLoggedOut + let capturedError = capturedResult?.errorValue() as? RefreshTokenError + XCTAssertNotNil(capturedError) + XCTAssertEqual(expectedError, capturedError) + } + + /** + * Given: Refresh token expirationDate is near + * And: User credentials stored in user data store + * And: refreshToken method called + * And: Network request is made to refresh token + * When: Network request to refresh token succeeds + * And: Access token no longer needs refreshing + * (it's been refreshed by another request in a meantime) + * Then: Refresh token should NOT be saved to a data store + * And: Callback should return false + */ + func testTokenAlreadyRefreshed() { + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: dateInTheNearFuture()) + mockUserDataStore.credentialsToReturn = credentials + + let accessTokenAfterRefresh = TestUtil.getRandomString() + let requestPayload = AuthTokenMock().set(accessToken: accessTokenAfterRefresh).set(expiresIn: 10000) + + mockRequestSender.beforeResponse = { [weak self] in + let credentials = ObjectTestFactory.getRandomCredentials(expiryDate: self?.dateInTheFarFuture()) + self?.mockUserDataStore.credentialsToReturn = credentials + } + + var capturedResult: Result? + testObject.refreshToken { result in + capturedResult = result + } + + mockRequestSender.success(response: requestPayload.build()) + + XCTAssertFalse(mockUserDataStore.setCurrentUserCalled) + XCTAssertFalse(capturedResult!.successValue()!) + } + + private func dateInTheFarFuture() -> Date { + return TestUtil.getRandomDate(laterThan: Date().addingTimeInterval(3600)) + } + + private func dateInTheNearFuture() -> Date { + let shortInterval = TimeInterval(Double(TestUtil.getRandomInt(lessThan: 100)) * 0.01) + return Date().addingTimeInterval(shortInterval) + } + + private func dateInThePast() -> Date { + return Date().addingTimeInterval(-400) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Address/KarhooAddressServiceSpec.swift b/KarhooSDKTests/TestCases/Service/Address/KarhooAddressServiceSpec.swift new file mode 100644 index 00000000..cb360904 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Address/KarhooAddressServiceSpec.swift @@ -0,0 +1,166 @@ +// +// KarhooAddressServiceSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +import CoreLocation + +@testable import KarhooSDK + +class KarhooAddressServiceSpec: XCTestCase { + + private var mockPlaceSearchInteractor: MockPlaceSearchInteractor! + private var mockLocationInfoInteractor: MockLocationInfoInteractor! + private var mockReverseGeocodeInteractor: MockReverseGeocodeInteractor! + private var testObject: KarhooAddressService! + private let mockPlaceSearch: PlaceSearch = PlaceSearch(position: Position(latitude: 51, + longitude: 20), + query: "search", + sessionToken: "1234") + private let testPostion = Position(latitude: 10, longitude: 10) + + override func setUp() { + super.setUp() + + mockPlaceSearchInteractor = MockPlaceSearchInteractor() + mockLocationInfoInteractor = MockLocationInfoInteractor() + mockReverseGeocodeInteractor = MockReverseGeocodeInteractor() + + testObject = KarhooAddressService(placeSearchInteractor: mockPlaceSearchInteractor, + locationInfoInteractor: mockLocationInfoInteractor, + reverseGeocodeInteractor: mockReverseGeocodeInteractor) + } + + /** + * Given: Searching for an address + * When: The interactor succeeds + * Then: Result should be propogated + * And: Expected request should be set + */ + func testAddressSearchSuccess() { + let expectedResult = Places(places: [Place(placeId: "expectedPlaceId", + displayAddress: "some")]) + + var capturedResult: Result? + testObject.placeSearch(placeSearch: mockPlaceSearch).execute(callback: { capturedResult = $0 }) + + mockPlaceSearchInteractor.triggerSuccess(result: expectedResult) + + XCTAssertEqual(capturedResult!.successValue()!.places[0].encode(), + expectedResult.places[0].encode()) + XCTAssertTrue(capturedResult!.isSuccess()) + XCTAssertNil(capturedResult?.errorValue()) + XCTAssertEqual(mockPlaceSearch, mockPlaceSearchInteractor.placeSearchSet!) + } + + /** + * Given: Searching for an address + * When: The interactor fails + * Then: Result should be propogated + * And: Expected request should be set + */ + func testAddressSearchFails() { + let expectedError = TestUtil.getRandomError() + + var capturedResult: Result? + testObject.placeSearch(placeSearch: mockPlaceSearch).execute(callback: { capturedResult = $0 }) + + mockPlaceSearchInteractor.triggerFail(error: expectedError) + + XCTAssertNil(capturedResult?.successValue()) + XCTAssertFalse(capturedResult!.isSuccess()) + XCTAssert(expectedError.equals(capturedResult!.errorValue()!)) + XCTAssertEqual(mockPlaceSearch, mockPlaceSearchInteractor.placeSearchSet!) + } + + /** + * Given: Reverse geocoding a CLLocation + * When: The interactor succeeds + * Then: The expected LocationDetails result should be propogated + * And: The right location should be set on the interactor + */ + func testReverseGeocodeSucceeds() { + let testPosition = Position(latitude: 14, longitude: 10) + let expectedResult = LocationInfoMock().set(placeId: "some").build() + + var capturedResult: Result? + testObject.reverseGeocode(position: testPosition).execute(callback: { capturedResult = $0 }) + + mockReverseGeocodeInteractor.triggerSuccess(result: expectedResult) + + XCTAssertTrue(capturedResult!.isSuccess()) + XCTAssertNil(capturedResult?.errorValue()) + XCTAssertEqual(capturedResult!.successValue()?.encode(), expectedResult.encode()) + XCTAssertEqual(testPosition, mockReverseGeocodeInteractor.positionSet) + } + + /** + * Given: Reverse geocoding a CLLocation + * When: The interactor fails + * Then: The expected error result should be propogated + * And: The right location should be set on the interactor + */ + func testReverseGeocodeFails() { + let expectedError = TestUtil.getRandomError() + + var capturedResult: Result? + testObject.reverseGeocode(position: testPostion).execute(callback: { capturedResult = $0 }) + + mockReverseGeocodeInteractor.triggerFail(error: expectedError) + + XCTAssertFalse(capturedResult!.isSuccess()) + XCTAssertNil(capturedResult?.successValue()) + XCTAssert(expectedError.equals(capturedResult!.errorValue()!)) + XCTAssertEqual(mockReverseGeocodeInteractor.positionSet, testPostion) + } + + /** + * Given: Getting LocationInfo for a placeId + * When: The Interactor succeeds + * Then: The expected LocationInfo result should be propogated + * And: The right placeId should be set on the Interactor + */ + func testGetLocationInfoSucceeds() { + let expectedResult = LocationInfoMock().set(placeId: "some").build() + + var capturedResult: Result? + testObject.locationInfo(locationInfoSearch: LocationInfoSearch(placeId: "123", + sessionToken: "1234")) + .execute(callback: { capturedResult = $0 }) + + mockLocationInfoInteractor.triggerSuccess(result: expectedResult) + + XCTAssertTrue(capturedResult!.isSuccess()) + XCTAssertNil(capturedResult?.errorValue()) + XCTAssertEqual(capturedResult!.successValue()?.encode(), expectedResult.encode()) + XCTAssertEqual(mockLocationInfoInteractor.locationInfoSearchSet?.placeId, "123") + XCTAssertEqual(mockLocationInfoInteractor.locationInfoSearchSet?.sessionToken, "1234") + + } + + /** + * Given: Getting LocationInfo for a place id + * When: The interactor fails + * Then: The expected error result should be propogated + * And: The right place id should be set on the interactor + */ + func testGetLocationInfoFails() { + let expectedError = TestUtil.getRandomError() + + var capturedResponse: Result? + testObject.locationInfo(locationInfoSearch: LocationInfoSearch(placeId: "123", + sessionToken: "1234")) + .execute(callback: { capturedResponse = $0 }) + mockLocationInfoInteractor.triggerFail(error: expectedError) + + XCTAssertFalse(capturedResponse!.isSuccess()) + XCTAssertNil(capturedResponse?.successValue()) + XCTAssert(expectedError.equals(capturedResponse!.errorValue()!)) + XCTAssertEqual(mockLocationInfoInteractor.locationInfoSearchSet?.placeId, "123") + XCTAssertEqual(mockLocationInfoInteractor.locationInfoSearchSet?.sessionToken, "1234") + } +} diff --git a/KarhooSDKTests/TestCases/Service/Address/LocationInfo/KarhooLocationInfoInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Address/LocationInfo/KarhooLocationInfoInteractorSpec.swift new file mode 100644 index 00000000..0f505564 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Address/LocationInfo/KarhooLocationInfoInteractorSpec.swift @@ -0,0 +1,74 @@ +// +// KarhooLocationInfoInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class KarhooLocationInfoInteractorSpec: XCTestCase { + + private var mockLocationInfoRequestSender: MockRequestSender! + private var testObject: KarhooLocationInfoInteractor! + + override func setUp() { + super.setUp() + mockLocationInfoRequestSender = MockRequestSender() + testObject = KarhooLocationInfoInteractor(requestSender: mockLocationInfoRequestSender) + testObject.set(locationInfoSearch: LocationInfoSearch(placeId: "123", sessionToken: "12354")) + } + + /** + * When: A request is made for location details + * Then: The correct request should be sent + */ + func testRequestFormat() { + let request = LocationInfoSearch(placeId: "some_placeId", + sessionToken: "some_token") + testObject.set(locationInfoSearch: request) + + testObject.execute(callback: { (_: Result) in}) + + mockLocationInfoRequestSender.assertRequestSendAndDecoded(endpoint: .locationInfo, + method: .get, + payload: request) + } + + /** + * When: Making a successful request + * Then: Expected result should be propogated + */ + func testRequestSuccess() { + let expectedResponse = LocationInfoMock().set(placeId: "some").set(timeZoneIdentifier: "some").build() + + var capturedResponse: Result? + testObject.execute(callback: { capturedResponse = $0 }) + + mockLocationInfoRequestSender.triggerSuccessWithDecoded(value: expectedResponse) + + XCTAssertTrue(capturedResponse!.isSuccess()) + XCTAssertNil(capturedResponse?.errorValue()) + XCTAssertEqual(expectedResponse.encode(), capturedResponse!.successValue()!.encode()) + } + + /** + * When: Request fails + * Then: Error should be propogated + */ + func testRequestFails() { + let expectedError = TestUtil.getRandomError() + + var capturedResponse: Result? + testObject.execute(callback: { capturedResponse = $0 }) + + mockLocationInfoRequestSender.triggerFail(error: expectedError) + + XCTAssertFalse(capturedResponse!.isSuccess()) + XCTAssertNil(capturedResponse?.successValue()) + XCTAssert(expectedError.equals(capturedResponse!.errorValue()!)) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Address/Parser/KarhooAddressParserSpec.swift b/KarhooSDKTests/TestCases/Service/Address/Parser/KarhooAddressParserSpec.swift new file mode 100644 index 00000000..ece09c58 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Address/Parser/KarhooAddressParserSpec.swift @@ -0,0 +1,141 @@ +// +// KarhooAddressParserSpec.swift +// KarhooSDK +// +// Created by Yaser on 2017-06-06. +// Copyright © 2017 Flit Technologies Ltd. All rights reserved. +// + +import XCTest +import CoreLocation + +@testable import KarhooSDK + +//swiftlint:disable force_try + +class KarhooAddressParserSpec: XCTestCase { + + /** + * When: Parsing an address to a dictionary + * Then: The corresponding dictionary should be produced + */ + func testToDictionary() { + let address = ObjectTestFactory.getRandomAddress() + + let dictionary = KarhooAddressParser().from(address: address) + + XCTAssert(compare(dictionary: dictionary, address: address)) + } + + /** + * When: Parsing a valid dictionary to an Address + * Then: The corresponding address should be produced + */ + func testToAddress() { + let dictionary = createValidDictionary() + + let address = try! KarhooAddressParser().from(dictionary: dictionary) + + XCTAssert(compare(dictionary: dictionary, address: address)) + } + + /** + * When: Parsing a dictionary with optional values missing to an Address + * Then: The corresponding address should be produced + */ + func testToAddressConversionWithMissingOptionals() { + let dictionary = createValidDictionary(rawData: nil) + + let address = try! KarhooAddressParser().from(dictionary: dictionary) + + XCTAssert(compare(dictionary: dictionary, address: address)) + } + + /** + * When: Parsing an dictionary missing mandatory address fields + * Then: An error should be thrown + */ + func testInvalidToAddress() { + let keysToRemove = [AddressParserKeys.placeId.rawValue, + AddressParserKeys.lineOne.rawValue, + AddressParserKeys.latitude.rawValue, + AddressParserKeys.longitude.rawValue] + + let testObject = KarhooAddressParser() + + keysToRemove.forEach { (key: String) in + var dictionary = createValidDictionary() + dictionary.removeValue(forKey: key) + + var errorThrown: ParserError? + do { + _ = try testObject.from(dictionary: dictionary) + } catch let error { + errorThrown = error as? ParserError + } + + XCTAssert(errorThrown! == .infoMissing(key)) + } + } + + /** + * When: Parsing a dictionary containing the wrong types + * Then: An error should be thrown + */ + func testInvalidTypedToAddress() { + let keysToAlter = [AddressParserKeys.placeId.rawValue, + AddressParserKeys.lineOne.rawValue, + AddressParserKeys.latitude.rawValue, + AddressParserKeys.longitude.rawValue] + + let testObject = KarhooAddressParser() + + keysToAlter.forEach { (key: String) in + var dictionary = createValidDictionary() + dictionary[key] = NSObject() + + var errorThrown: ParserError? + do { + _ = try testObject.from(dictionary: dictionary) + } catch let error { + errorThrown = error as? ParserError + } + + XCTAssert(errorThrown! == .infoMissing(key)) + } + } + + private func compare(dictionary: [String: Any?], address: Address) -> Bool { + var isSame = true + + isSame = isSame && dictionary[AddressParserKeys.placeId.rawValue] as? String == address.placeId + isSame = isSame && dictionary[AddressParserKeys.displayAddress.rawValue] as? String == address.displayAddress + isSame = isSame && dictionary[AddressParserKeys.lineOne.rawValue] as? String == address.lineOne + + let lat = dictionary[AddressParserKeys.latitude.rawValue] as? CLLocationDegrees + isSame = isSame && lat == address.location.coordinate.latitude + + let lon = dictionary[AddressParserKeys.longitude.rawValue] as? CLLocationDegrees + isSame = isSame && lon == address.location.coordinate.longitude + + return isSame + } + + private func createValidDictionary(rawData: Data? = Data()) -> [String: Any] { + var dictionary = [String: Any]() + + dictionary[AddressParserKeys.placeId.rawValue] = TestUtil.getRandomString() + dictionary[AddressParserKeys.displayAddress.rawValue] = TestUtil.getRandomString() + dictionary[AddressParserKeys.lineOne.rawValue] = TestUtil.getRandomString() + dictionary[AddressParserKeys.lineTwo.rawValue] = TestUtil.getRandomString() + + let coordinate = TestUtil.getRandomLocation().coordinate + dictionary[AddressParserKeys.latitude.rawValue] = coordinate.latitude + dictionary[AddressParserKeys.longitude.rawValue] = coordinate.longitude + dictionary[AddressParserKeys.rawData.rawValue] = rawData + + return dictionary + } +} + +//swiftlint:enable force_try diff --git a/KarhooSDKTests/TestCases/Service/Address/PlaceSearch/KarhooPlaceSearchInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Address/PlaceSearch/KarhooPlaceSearchInteractorSpec.swift new file mode 100644 index 00000000..06253007 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Address/PlaceSearch/KarhooPlaceSearchInteractorSpec.swift @@ -0,0 +1,78 @@ +// +// KarhooPlaceSearchInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class KarhooPlaceSearchInteractorSpec: XCTestCase { + + private var mockPlaceSearchRequest: MockRequestSender! + private var testObject: KarhooPlaceSearchInteractor! + + let testPlaceSearch: PlaceSearch = PlaceSearch(position: Position(latitude: 51, + longitude: 20), + query: "search", + sessionToken: "19182391827") + override func setUp() { + super.setUp() + + mockPlaceSearchRequest = MockRequestSender() + testObject = KarhooPlaceSearchInteractor(requestSender: mockPlaceSearchRequest) + testObject.set(placeSearch: testPlaceSearch) + } + + /** + * When: Searching for a place + * Then: The correct request should be formed + */ + func testRequestFormat() { + testObject.execute(callback: { (_: Result) in}) + mockPlaceSearchRequest.assertRequestSendAndDecoded(endpoint: .placeSearch, + method: .get, + payload: testPlaceSearch) + } + + /** + * Given: Request succeeds + * When: Searching for a place + * Then: The correct result should be propogated + */ + func testRequestSuccess() { + let expectedResponse = Places(places: [Place(placeId: "some", + displayAddress: "display")]) + + var expectedResult: Result? + testObject.execute(callback: { expectedResult = $0}) + + mockPlaceSearchRequest.triggerSuccessWithDecoded(value: expectedResponse) + + XCTAssertEqual(expectedResponse.places[0].encode(), expectedResult!.successValue()!.places[0].encode()!) + XCTAssertTrue(expectedResult!.isSuccess()) + XCTAssertNil(expectedResult!.errorValue()) + } + + /** + * Given: Request succeeds + * When: Searching for a place + * Then: The correct result should be propogated + */ + func testRequestFails() { + let expectedError = TestUtil.getRandomError() + + var expectedResult: Result? + + testObject.execute(callback: { expectedResult = $0}) + + mockPlaceSearchRequest.triggerFail(error: expectedError) + + XCTAssertNil(expectedResult?.successValue()) + XCTAssertFalse(expectedResult!.isSuccess()) + XCTAssert(expectedError.equals(expectedResult!.errorValue()!)) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Address/ReverseGeocode/KarhooReverseGeocodeProviderSpec.swift b/KarhooSDKTests/TestCases/Service/Address/ReverseGeocode/KarhooReverseGeocodeProviderSpec.swift new file mode 100644 index 00000000..170e63ff --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Address/ReverseGeocode/KarhooReverseGeocodeProviderSpec.swift @@ -0,0 +1,72 @@ +// +// KarhooReverseGeocodeProviderSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +import CoreLocation + +@testable import KarhooSDK + +class KarhooReverseGeocodeInteractorSpec: XCTestCase { + + private var mockReverseGeocodeRequest: MockRequestSender! + private var testObject: KarhooReverseGeocodeInteractor! + private let testPosition: Position = Position(latitude: 51.51479, + longitude: -0.1444379) + + override func setUp() { + super.setUp() + mockReverseGeocodeRequest = MockRequestSender() + testObject = KarhooReverseGeocodeInteractor(requestSender: mockReverseGeocodeRequest) + testObject.set(position: testPosition) + } + + /** + * When: sending request + * Then: Expected payload, path and should be set + */ + func testRequestFormat() { + testObject.execute(callback: { (_: Result) in}) + + mockReverseGeocodeRequest.assertRequestSendAndDecoded(endpoint: .reverseGeocode(position: testPosition), + method: .get, + payload: nil) + } + + /** + * When: Request succeeds + * Then: expected callback should be propogated + */ + func testRequestSucceeds() { + let expectedResponse = LocationInfoMock().set(placeId: "some").set(timeZoneIdentifier: "Europe/London") + + var capturedResponse: Result? + testObject.execute(callback: { capturedResponse = $0 }) + + mockReverseGeocodeRequest.triggerSuccessWithDecoded(value: expectedResponse.build()) + + XCTAssertEqual(capturedResponse!.successValue()!.placeId, expectedResponse.build().placeId) + XCTAssertTrue(capturedResponse!.isSuccess()) + XCTAssertNil(capturedResponse?.errorValue()) + } + + /** + * When: Sign up request fais + * Then: expected callback should be propogated + */ + func testRequestFails() { + let expectedError = TestUtil.getRandomError() + + var capturedResponse: Result? + testObject.execute(callback: { capturedResponse = $0 }) + + mockReverseGeocodeRequest.triggerFail(error: expectedError) + + XCTAssertFalse(capturedResponse!.isSuccess()) + XCTAssert(expectedError.equals(capturedResponse!.errorValue()!)) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Analytics/AnalyticsServiceSpec.swift b/KarhooSDKTests/TestCases/Service/Analytics/AnalyticsServiceSpec.swift new file mode 100644 index 00000000..920241a2 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Analytics/AnalyticsServiceSpec.swift @@ -0,0 +1,129 @@ +// +// AnalyticsServiceSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +final class AnalyticsServiceSpec: XCTestCase { + + private var mockContext: MockContext! + private var mockUserDataStore: MockUserDataStore! + private var testObject: KarhooAnalyticsService! + private var mockProvider: MockAnalyticsProvider! + + override func setUp() { + super.setUp() + + let mockUser: UserInfo = UserInfoMock() + .set(userId: "123") + .set(email: "some") + .build() + + mockUserDataStore = MockUserDataStore() + mockContext = MockContext() + mockProvider = MockAnalyticsProvider() + + mockUserDataStore.userToReturn = mockUser + + testObject = KarhooAnalyticsService(providers: [mockProvider], + context: mockContext, + userDataStore: mockUserDataStore) + } + + /** + * Given: A testflight build + * When: Getting the default providers + * Then: The correct providers should be fetched + */ + func testDefaultProvidersTestflight() { + mockContext.isTestflightBuildReturnValue = true + let providers = KarhooAnalyticsService.defaultProviders(context: mockContext) + + XCTAssertEqual(1, providers.count) + XCTAssertNotNil(providers[0] as? LogAnalyticsProvider) + } + + /** + * Given: A regular build + * When: Getting the default providers + * Then: The correct providers should be fetched + */ + func testDefaultProviders() { + mockContext.isTestflightBuildReturnValue = false + let providers = KarhooAnalyticsService.defaultProviders(context: mockContext) + + XCTAssertGreaterThan(providers.count, 0) + } + + /** + * Given: A set of preferred providers + * When: Constructing an analytics service + * Then: The construction with the custom providers should work + */ + func testCustomProviders() { + let firstProvider = MockAnalyticsProvider() + let secondProvider = MockAnalyticsProvider() + + let analytics = KarhooAnalyticsService(providers: [firstProvider, + secondProvider], + context: mockContext) + analytics.send(eventName: .userLoggedIn) + + XCTAssertEqual(AnalyticsConstants.EventNames.userLoggedIn.description, firstProvider.trackedName) + XCTAssertEqual(AnalyticsConstants.EventNames.userLoggedIn.description, secondProvider.trackedName) + } + + /** + * Given: Tracking a GDPR sensitive event + * When: The user IS NOT logged in + * Then: The event should not be tracked + */ + func testTrackingGdprEventWhenNotLoggedIn() { + mockUserDataStore.userToReturn = nil + + testObject.send(eventName: .appOpened, payload: [:]) + + XCTAssertNil(mockProvider.trackedName) + } + + /** + * Given: Tracking a GDPR sensitive event + * When: The user IS logged in + * Then: The event should be tracked + */ + func testTrackingGDPRSensitiveEventUserIsLoggedIn() { + testObject.send(eventName: .appOpened, payload: [:]) + + XCTAssertEqual(AnalyticsConstants.EventNames.appOpened.description, mockProvider.trackedName) + } + + /** + * When: Tracking an event that is NOT GDPR sensitive + * Then: The event should be tracked + */ + func testTrackingNonGDPRSensitiveEvent() { + testObject.send(eventName: .userCalledDriver, payload: [:]) + + XCTAssertEqual(AnalyticsConstants.EventNames.userCalledDriver.description, mockProvider.trackedName) + } + + private func checkGenericParameters(payload: [String: Any]?) -> Bool { + var returnValue = true + + if payload?[AnalyticsConstants.Keys.sendingApp.description] == nil { + returnValue = false + } + + if let user = mockUserDataStore.getCurrentUser() { + returnValue = returnValue && payload?[AnalyticsConstants.Keys.userId.description] as? String == user.userId + } + + return returnValue + } +} diff --git a/KarhooSDKTests/TestCases/Service/Analytics/Providers/LogAnalyticsProviderSpec.swift b/KarhooSDKTests/TestCases/Service/Analytics/Providers/LogAnalyticsProviderSpec.swift new file mode 100644 index 00000000..714fc6a6 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Analytics/Providers/LogAnalyticsProviderSpec.swift @@ -0,0 +1,71 @@ +// +// LogAnalyticsProviderSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class LogAnalyticsProviderSpec: XCTestCase { + + private var testLogger: TestLogger! + private var testObject: LogAnalyticsProvider! + private let testEvent: String = AnalyticsConstants.EventNames.bookingRequested.description + override func setUp() { + super.setUp() + + testLogger = TestLogger() + testObject = LogAnalyticsProvider(output: testLogger.log) + } + + /** + * When: Logging an event + * Then: The corresponding string should be logged + */ + func testLogEvent() { + testObject.trackEvent(name: testEvent) + + XCTAssert(testLogger.stringToOutput.contains(testEvent)) + } + + /** + * When: Logging an event with payload + * Then: The corresponding string should be logged + */ + func testLogEventWithPayload() { + let testPayload = ["TestString": "abc", "TestInt": 2] as [String: Any] + + testObject.trackEvent(name: testEvent, payload: testPayload) + + XCTAssert(testLogger.stringToOutput.contains(testEvent.description)) + + XCTAssert(testLogger.stringToOutput.contains("TestString")) + XCTAssert(testLogger.stringToOutput.contains("abc")) + + XCTAssert(testLogger.stringToOutput.contains("TestInt")) + XCTAssert(testLogger.stringToOutput.contains("2")) + } + + /** + * When: Logging an event with empty payload + * Then: The corresponding string should be logged + */ + func testLogEventWithEmptyPayload() { + testObject.trackEvent(name: testEvent, payload: nil) + + XCTAssert(testLogger.stringToOutput.contains(testEvent.description)) + } +} + +private class TestLogger { + + var stringToOutput: String = "" + + func log(string: String, args: CVarArg...) { + stringToOutput += string + } +} diff --git a/KarhooSDKTests/TestCases/Service/Analytics/TimestampFormatterSpec.swift b/KarhooSDKTests/TestCases/Service/Analytics/TimestampFormatterSpec.swift new file mode 100644 index 00000000..faba1dce --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Analytics/TimestampFormatterSpec.swift @@ -0,0 +1,38 @@ +// +// TimestampFormatterSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +import KarhooSDK + +final class TimestampFormatterSpec: XCTestCase { + + private var testObject: TimestampFormatter! + + override func setUp() { + super.setUp() + + testObject = TimestampFormatter() + } + + /** + * When: Formatting a date + * Then: Result should be in a correct format + */ + func testFormatDate() { + let date = Date(timeIntervalSince1970: 0) + let result = testObject.formattedDate(date) + + let dateFormatter = DateFormatter() + dateFormatter.timeZone = TimeZone.current + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + + let expectedOutput = dateFormatter.string(from: date) + + XCTAssertEqual(expectedOutput, result) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Auth/KarhooAuthLoginInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Auth/KarhooAuthLoginInteractorSpec.swift new file mode 100644 index 00000000..a1ee6843 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Auth/KarhooAuthLoginInteractorSpec.swift @@ -0,0 +1,97 @@ +// +// KarhooAuthLoginInteractorSpec.swift +// KarhooSDKTests +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation +import XCTest +@testable import KarhooSDK + +final class KarhooAuthLoginInteractorSpec: XCTestCase { + + private var testObject: KarhooAuthLoginInteractor! + private var mocktokenExchangeRequest: MockRequestSender! + private var mockUserRequest: MockRequestSender! + private var mockUserDataStore: MockUserDataStore! + private var mockAnalytics: MockAnalyticsService! + + override func setUp() { + super.setUp() + + mocktokenExchangeRequest = MockRequestSender() + mockAnalytics = MockAnalyticsService() + mockUserRequest = MockRequestSender() + mockUserDataStore = MockUserDataStore() + mockAnalytics = MockAnalyticsService() + + testObject = KarhooAuthLoginInteractor(tokenExchangeRequestSender: mocktokenExchangeRequest, + userInfoSender: mockUserRequest, + userDataStore: mockUserDataStore, + analytics: mockAnalytics) + testObject.set(token: "13123123123123") + } + + /** + * When: Cancelling revoke + * Then: Login request should be cancelled + */ + func testCancel() { + testObject.cancel() + XCTAssertTrue(mocktokenExchangeRequest.cancelNetworkRequestCalled) + XCTAssertTrue(mockUserRequest.cancelNetworkRequestCalled) + } + + /** + * Given: Exchange token + * When: Login and user info request succeeds + * Then: expected result should propagate + */ + func testLoginHappyPath() { + var result: Result? + + testObject.execute(callback: { result = $0 }) + mocktokenExchangeRequest.triggerEncodedRequestSuccess(value: AuthTokenMock().set(accessToken: "123").build()) + mockUserRequest.triggerSuccessWithDecoded(value: UserInfoMock().set(firstName: "Bob").build()) + + XCTAssertEqual("123", mockUserDataStore.credentialsToSet?.accessToken) + XCTAssertEqual("Bob", mockUserDataStore.updateUser?.firstName) + XCTAssertEqual(mockAnalytics.eventSent, .ssoUserLogIn) + XCTAssertNotNil(result!.successValue()) + } + + /** + * Given: Exchange token + * When: Login fails + * Then: User profile request should not be sent + * And: Error should be propogated + * And: No credentials or user should be set + */ + func testTokenRequestFails() { + var result: Result? + let expectedError = TestUtil.getRandomError() + testObject.execute(callback: { result = $0 }) + mocktokenExchangeRequest.triggerFail(error: expectedError) + + XCTAssertFalse(mockUserRequest.requestAndDecodeCalled) + XCTAssertNil(mockUserDataStore.credentialsToSet) + XCTAssertNotNil(result!.errorValue()) + } + + /** + * Given: Exchange token successful + * When: User profile request fails + * Then: + */ + func testUserRequestFails() { + var result: Result? + let expectedError = TestUtil.getRandomError() + testObject.execute(callback: { result = $0 }) + mocktokenExchangeRequest.triggerEncodedRequestSuccess(value: AuthTokenMock().set(accessToken: "123").build()) + mockUserRequest.triggerFail(error: expectedError) + + XCTAssertNotNil(result!.errorValue()) + XCTAssertNil(mockAnalytics.eventSent) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Auth/KarhooAuthRevokeInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Auth/KarhooAuthRevokeInteractorSpec.swift new file mode 100644 index 00000000..ec92fb1d --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Auth/KarhooAuthRevokeInteractorSpec.swift @@ -0,0 +1,55 @@ +// +// KarhooAuthRevokeInteractorSpec.swift +// KarhooSDKTests +// +// Copyright © 2020 Flit Technologies Ltd. All rights reserved. +// + +import Foundation +import XCTest +@testable import KarhooSDK + +final class KarhoooAuthRevokeInteractorSpec: XCTestCase { + + private var testObject: KarhoooAuthRevokeInteractor! + private var mockRevokeRequest: MockRequestSender! + private var mockUserDataStore: MockUserDataStore! + private var mockAnalytics: MockAnalyticsService! + + override func setUp() { + super.setUp() + mockAnalytics = MockAnalyticsService() + mockRevokeRequest = MockRequestSender() + mockUserDataStore = MockUserDataStore() + + testObject = KarhoooAuthRevokeInteractor(userDataStore: mockUserDataStore, + revokeRequestSender: mockRevokeRequest, + analytics: mockAnalytics) + } + + /** + * When: Cancelling revoke + * Then: Revoke request should be cancelled + */ + func testCancel() { + testObject.cancel() + XCTAssertTrue(mockRevokeRequest.cancelNetworkRequestCalled) + } + + /** + * Given: Revoke succeeds + * Then: Callback should be success + */ + func testRevokeSuccess() { + var result: Result? + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + testObject.execute(callback: { result = $0 }) + mockRevokeRequest.triggerEncodedRequestSuccess(value: KarhooVoid()) + + XCTAssertTrue(mockUserDataStore.removeUserCalled) + XCTAssertEqual(mockAnalytics.eventSent, .ssoTokenRevoked) + XCTAssertNotNil(result!.successValue()) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Availability/KarhooAvailabilityInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Availability/KarhooAvailabilityInteractorSpec.swift new file mode 100644 index 00000000..d205ee7f --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Availability/KarhooAvailabilityInteractorSpec.swift @@ -0,0 +1,73 @@ +// +// KarhooAvailabilityInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooAvailabilityInteractorSpec: XCTestCase { + + private var mockAvailabilityRequest: MockRequestSender! + + private var testObject: KarhooAvailabilityInteractor! + private let mockSearch = AvailabilitySearch(origin: "origin", + destination: "destination") + + override func setUp() { + super.setUp() + + mockAvailabilityRequest = MockRequestSender() + testObject = KarhooAvailabilityInteractor(request: mockAvailabilityRequest) + testObject.set(availabilitySearch: mockSearch) + } + + /** + * When: Getting availability + * Then: Expected method, path and payload should be set + */ + func testRequestFormat() { + testObject.execute(callback: { (_: Result) in }) + + mockAvailabilityRequest.assertRequestSendAndDecoded(endpoint: .availability, + method: .post, + payload: mockSearch) + } + + /** + * When: Request succeeds + * Then: Callback should succeed with expexcted result + */ + func testRequestSucceeds() { + let expectedResponse = CategoriesMock().set(categories: ["first", "second", "third"]) + + var result: Result? + testObject.execute(callback: { result = $0}) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: expectedResponse.build()) + + XCTAssertTrue(result!.isSuccess()) + XCTAssertTrue(expectedResponse.build().equals(result!.successValue()!)) + } + + /** + * When: Request fails + * Then: Callback should fail with expexcted error + */ + func testRequestFails() { + let error = TestUtil.getRandomError() + + var result: Result? + testObject.execute(callback: { result = $0 }) + + mockAvailabilityRequest.triggerFail(error: error) + + XCTAssertFalse(result!.isSuccess()) + XCTAssertTrue(error.equals(result?.errorValue()!)) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Availability/KarhooAvailabilityServiceSpec.swift b/KarhooSDKTests/TestCases/Service/Availability/KarhooAvailabilityServiceSpec.swift new file mode 100644 index 00000000..862f60a1 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Availability/KarhooAvailabilityServiceSpec.swift @@ -0,0 +1,71 @@ +// +// KarhooAvailabilityServiceSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class KarhooAvailabilityServiceSpec: XCTestCase { + + private var mockAvailabilityInteractor: MockAvailabilityInteractor! + private var testObject: KarhooAvailabilityService! + private let mockSearch = AvailabilitySearch(origin: "origin", destination: "destination", dateScheduled: "date") + + override func setUp() { + super.setUp() + + mockAvailabilityInteractor = MockAvailabilityInteractor() + testObject = KarhooAvailabilityService(availabilityInteractor: mockAvailabilityInteractor) + } + + /** + * When: Getting availability + * Then: Availability should be called and callback set + */ + func testAvailabilityRequest() { + testObject.availability(availabilitySearch: mockSearch).execute(callback: { _ in}) + + XCTAssertTrue(mockSearch.equals(mockAvailabilityInteractor.setAvailabilitySearch!)) + } + + /** + * When: Availability request is successful + * Then: Callback should be successful + * And: Callback should contain expected result + */ + func testAvailabilityRequestSucceed() { + var callbackResult: Result? + let karhooCall = testObject.availability(availabilitySearch: mockSearch) + + karhooCall.execute(callback: { callbackResult = $0 }) + + let categories = Categories(categories: ["first", "second"]) + mockAvailabilityInteractor.triggerSuccess(result: categories) + + XCTAssertTrue(callbackResult!.isSuccess()) + XCTAssertNil(callbackResult?.errorValue()) + XCTAssertEqual(categories, callbackResult?.successValue()) + } + + /** + * When: Request fails + * Then: Callback should fail + */ + func testAvailabilityRequestFails() { + var callbackResult: Result? + let karhooCall = testObject.availability(availabilitySearch: mockSearch) + + karhooCall.execute(callback: { callbackResult = $0 }) + + let expectedError = TestUtil.getRandomError() + mockAvailabilityInteractor.triggerFail(error: expectedError) + + XCTAssertFalse(callbackResult!.isSuccess()) + XCTAssertNil(callbackResult?.successValue()) + XCTAssert(expectedError.equals(callbackResult!.errorValue()!)) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Config/KarhooConfigServiceSpec.swift b/KarhooSDKTests/TestCases/Service/Config/KarhooConfigServiceSpec.swift new file mode 100644 index 00000000..103d6936 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Config/KarhooConfigServiceSpec.swift @@ -0,0 +1,45 @@ +// +// KarhooConfigServiceSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest +@testable import KarhooSDK + +final class KarhooConfigServiceSpec: XCTestCase { + + private var testObject: ConfigService = KarhooConfigService() + private var mockUIConfigInteractor = MockUIConfigInteractor() + + override func setUp() { + super.setUp() + + testObject = KarhooConfigService(uiConfigInteractor: mockUIConfigInteractor) + } + + /** + * When: Getting config service + * Then: Config service should be returned + */ + func testSuccess() { + testObject.uiConfig(uiConfigRequest: UIConfigRequest(viewId: "some")).execute(callback: { result in + XCTAssertTrue(result.successValue()!.hidden) + }) + + mockUIConfigInteractor.triggerSuccess(result: UIConfig(hidden: true)) + XCTAssertEqual("some", mockUIConfigInteractor.uiConfigRequestSet?.viewId) + } + + func testFail() { + testObject.uiConfig(uiConfigRequest: UIConfigRequest(viewId: "some")).execute(callback: { result in + XCTAssertEqual("KSDK05", result.errorValue()?.code) + }) + + mockUIConfigInteractor.triggerFail(error: SDKErrorFactory.noConfigAvailableForView()) + XCTAssertEqual("some", mockUIConfigInteractor.uiConfigRequestSet?.viewId) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Config/KarhooUIConfigInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Config/KarhooUIConfigInteractorSpec.swift new file mode 100644 index 00000000..3c534b1f --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Config/KarhooUIConfigInteractorSpec.swift @@ -0,0 +1,88 @@ +// +// KarhooUIConfigInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest +@testable import KarhooSDK + +final class KarhooUIConfigInteractorSpec: XCTestCase { + + private var testObject: UIConfigInteractor = KarhooUIConfigInteractor() + private var mockUIConfigProvider = MockUIConfigProvider() + private var mockUserDataStore = MockUserDataStore() + private let mockUser = UserInfoMock().set(userId: "some").set(organisation: [Organisation(id: "orgId", + name: "orgName", + roles: [])]) + override func setUp() { + super.setUp() + + testObject = KarhooUIConfigInteractor(userDataStore: mockUserDataStore, + uiConfigProvider: mockUIConfigProvider) + } + + /** + * When: no config request is set + * Then: Callback should be unexpected error + * And: Provider should not be called + */ + func testConfigRequestNotSet() { + testObject.execute(callback: { (result: Result) in + XCTAssert(result.errorValue()?.code == "KSDK01") + }) + + XCTAssertFalse(mockUIConfigProvider.fetchConfigCalled) + } + + /** + * When: no user in the data store + * Then: Callback should be user error + * And: Provider should not be called + */ + func testNoUserAvailable() { + mockUserDataStore.userToReturn = nil + testObject.set(uiConfigRequest: UIConfigRequest(viewId: "some")) + testObject.execute(callback: { (result: Result) in + XCTAssert(result.errorValue()?.code == "KSDK02") + }) + + XCTAssertFalse(mockUIConfigProvider.fetchConfigCalled) + } + + /** + * When: Provider returns config + * Then: Callback should be success + */ + func testProviderSucceeds() { + testObject.set(uiConfigRequest: UIConfigRequest(viewId: "someViewId")) + mockUserDataStore.userToReturn = mockUser.build() + + testObject.execute(callback: { (result: Result) in + XCTAssertTrue(result.successValue()!.hidden) + }) + + mockUIConfigProvider.triggerConfigCallbackResult(.success(result: UIConfig(hidden: true))) + XCTAssertEqual(mockUser.build().organisations[0].name, mockUIConfigProvider.organisationSet?.name) + XCTAssertEqual("someViewId", mockUIConfigProvider.uiconfigRequestSet?.viewId) + } + + /** + * When: no config request is set + * Then: Callback should be unexpected error + */ + func testProviderFails() { + testObject.set(uiConfigRequest: UIConfigRequest(viewId: "someViewId")) + mockUserDataStore.userToReturn = mockUser.build() + + testObject.execute(callback: { (result: Result) in + XCTAssertEqual("KSDK05", result.errorValue()?.code) + }) + + mockUIConfigProvider.triggerConfigCallbackResult(.failure(error: SDKErrorFactory.noConfigAvailableForView())) + + } +} diff --git a/KarhooSDKTests/TestCases/Service/Config/Provider/KarhooUIConfigProviderSpec.swift b/KarhooSDKTests/TestCases/Service/Config/Provider/KarhooUIConfigProviderSpec.swift new file mode 100644 index 00000000..ca134c94 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Config/Provider/KarhooUIConfigProviderSpec.swift @@ -0,0 +1,48 @@ +// +// KarhooUIConfigProviderSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest +@testable import KarhooSDK + +final class KarhooUIConfigProviderSpec: XCTestCase { + + private var testObject = KarhooUIConfigProvider() + + override func setUp() { + super.setUp() + } + + /** + * When: User is in DefaultOrgForKarhoo + * Then: UIConfig model returned should not be hidden + */ + func testConfigForDefaultKarhooUsers() { + let request = UIConfigRequest(viewId: "additionalFeedbackButton") + let organisation = OrganisationMock().set(id: "a1013897-132a-456c-9be2-636979095ad9").build() + testObject.fetchConfig(uiConfigRequest: request, + organisation: organisation, + callback: { result in + XCTAssertFalse(result.successValue()!.hidden) + }) + } + + /** + * When: No config is found for a view + * Then: Expected error should return + */ + func testNoConfigFound() { + let request = UIConfigRequest(viewId: "view that don't exist") + let organisation = OrganisationMock().set(id: "a1013897-132a-456c-9be2-636979095ad9").build() + testObject.fetchConfig(uiConfigRequest: request, + organisation: organisation, + callback: { result in + XCTAssertTrue(result.errorValue()!.equals(SDKErrorFactory.noConfigAvailableForView())) + }) + } +} diff --git a/KarhooSDKTests/TestCases/Service/DriverTracking/KarhooDriverTrackingInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/DriverTracking/KarhooDriverTrackingInteractorSpec.swift new file mode 100644 index 00000000..53889be7 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/DriverTracking/KarhooDriverTrackingInteractorSpec.swift @@ -0,0 +1,73 @@ +// +// KarhooDriverTrackingInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooDriverTrackingInteractorSpec: XCTestCase { + + private var testObject: KarhooDriverTrackingInteractor! + private let mockTripID = "Some" + private var mockDriverTrackingRequest: MockRequestSender! + + override func setUp() { + super.setUp() + mockDriverTrackingRequest = MockRequestSender() + testObject = KarhooDriverTrackingInteractor(tripId: mockTripID, requestSender: mockDriverTrackingRequest) + } + + /** + * When: Adding payment method + * Then: Expected method, path and payload should be set + */ + func testRequestFormat() { + testObject.execute(callback: { response in + _ = response as Result + }) + + mockDriverTrackingRequest.assertRequestSendAndDecoded(endpoint: .trackDriver(identifier: mockTripID), + method: .get, + payload: nil) + } + + /** + * Given: Tracking a driver (trip id) + * When: request succeeds + * Then: Callback should be success + */ + func testTrackDriverSuccess() { + var response: Result? + testObject.execute(callback: { result in + response = result + }) + + let driverTrackingInfo = DriverTrackingInfoMock().setOriginEta(originEta: 5).build() + mockDriverTrackingRequest.triggerSuccessWithDecoded(value: driverTrackingInfo) + + XCTAssertEqual(5, response!.successValue()!.originEta) + } + + /** + * Given: Tracking a driver (trip id) + * When: request fails + * Then: Callback should contain expected error + */ + func testTrackDriverFails() { + var response: Result? + testObject.execute(callback: { result in + response = result + }) + + let expectedError = TestUtil.getRandomError() + mockDriverTrackingRequest.triggerFail(error: expectedError) + + XCTAssert(expectedError.equals(response!.errorValue())) + } +} diff --git a/KarhooSDKTests/TestCases/Service/DriverTracking/KarhooDriverTrackingServiceSpec.swift b/KarhooSDKTests/TestCases/Service/DriverTracking/KarhooDriverTrackingServiceSpec.swift new file mode 100644 index 00000000..ef5cc65a --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/DriverTracking/KarhooDriverTrackingServiceSpec.swift @@ -0,0 +1,33 @@ +// +// KarhooDriverTrackingServiceSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation + +import XCTest + +@testable import KarhooSDK + +final class KarhooDriverTrackingServiceSpec: XCTestCase { + + private var testObject: KarhooDriverTrackingService! + + override func setUp() { + super.setUp() + + testObject = KarhooDriverTrackingService() + } + + /** + * When: Tracking a driver + * Then: KarhooPollableCall should be returned + */ + func testTrackDriver() { + let pollCall = testObject.trackDriver(tripId: "some") + XCTAssertNotNil(pollCall) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Fares/KarhooFareInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Fares/KarhooFareInteractorSpec.swift new file mode 100644 index 00000000..9d750a28 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Fares/KarhooFareInteractorSpec.swift @@ -0,0 +1,64 @@ +// +// FareInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +final class KarhooFareInteractorSpec: XCTestCase { + + private var testObject: KarhooFareInteractor! + let tripId = "some_tripId" + private var mockFareRequest: MockRequestSender! + + override func setUp() { + super.setUp() + + mockFareRequest = MockRequestSender() + testObject = KarhooFareInteractor(requestSender: mockFareRequest) + testObject.set(tripId: tripId) + } + + /** + Given: Fare succeeds + Then: Callback should be success + */ + func testFareSuccess() { + + let expectedResponse = Fare(state: "some string", breakdown: FareComponent(total: 20, currency: "GBP")) + var expectedResult: Result? + + testObject.execute(callback: { expectedResult = $0}) + + mockFareRequest.triggerSuccessWithDecoded(value: expectedResponse) + + XCTAssertEqual(expectedResponse.encode(), expectedResult!.successValue()!.encode()!) + XCTAssertEqual(expectedResult?.successValue()!.breakdown.currency, "GBP") + XCTAssertEqual(expectedResult?.successValue()!.breakdown.total, 20) + XCTAssertTrue(expectedResult!.isSuccess()) + XCTAssertNil(expectedResult!.errorValue()) + } + + /** + Given: Fare fails + Then: Callback should contain expected error + */ + func testFareFails() { + let expectedError = TestUtil.getRandomError() + + var expectedResult: Result? + + testObject.execute(callback: { expectedResult = $0}) + + mockFareRequest.triggerFail(error: expectedError) + + XCTAssertNil(expectedResult?.successValue()) + XCTAssertFalse(expectedResult!.isSuccess()) + XCTAssert(expectedError.equals(expectedResult!.errorValue()!)) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Fares/KarhooFareServiceSpec.swift b/KarhooSDKTests/TestCases/Service/Fares/KarhooFareServiceSpec.swift new file mode 100644 index 00000000..c7a07d5d --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Fares/KarhooFareServiceSpec.swift @@ -0,0 +1,34 @@ +// +// KarhooFareServiceSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +final class KarhooFareServiceSpec: XCTestCase { + + private var mockFareInteractor: MockFareInteractor! + private var testObject: FareService! + + override func setUp() { + super.setUp() + + mockFareInteractor = MockFareInteractor() + mockFareInteractor.cancelCalled = true + testObject = KarhooFareService(fareInteractor: mockFareInteractor) + } + + /** + Given: Getting a Fare + Then: Fare should be returned + */ + func testFare() { + let call = testObject.fareDetails(tripId: "some") + XCTAssertNotNil(call) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Location/UserLocationProviderSpec.swift b/KarhooSDKTests/TestCases/Service/Location/UserLocationProviderSpec.swift new file mode 100644 index 00000000..80eaf7a8 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Location/UserLocationProviderSpec.swift @@ -0,0 +1,92 @@ +// +// UserLocationProviderSpec.swift +// Karhoo +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +import CoreLocation +@testable import KarhooSDK + +final class UserLocationProviderSpec: XCTestCase { + + private var mockLocationService: MockCLLocationManager! + private var testObject: KarhooUserLocationProvider! + + override func setUp() { + super.setUp() + + mockLocationService = MockCLLocationManager() + testObject = KarhooUserLocationProvider(locationManager: mockLocationService) + } + + /** + * When Initialized + * Then The location provider should be correctly configured + */ + func testSetup() { + XCTAssert(mockLocationService.desiredAccuracy == kCLLocationAccuracyBest) + XCTAssert(mockLocationService.activityType == .fitness) + XCTAssert(mockLocationService.delegate === testObject) + XCTAssert(mockLocationService.updateLocationStarted) + } + + /** + * When Getting the last know position + * Then The last known position of the location manager should be returned + */ + func testGetLastPosition() { + let location = CLLocation(latitude: 0.1, longitude: 0.1) + mockLocationService.mockedLocation = location + let lastKnown = testObject.getLastKnownLocation() + + XCTAssert(lastKnown == location) + } + + /** + * Given A callback has been set + * When A new location is received + * Then The location should be passed to the callback + */ + func testCallback() { + var receivedLocation: CLLocation? + testObject.set { (location: CLLocation) in + receivedLocation = location + } + + let location = CLLocation(latitude: 0.2, longitude: 0.2) + mockLocationService.triggerUpdate(location: location) + + XCTAssert(receivedLocation == location) + } + + /** + * Given A callback has been set + * When An empty list of places is received + * Then The location should not be passed to the callback + */ + func testCallbackNoLocations() { + var callbackCalled = false + testObject.set { (_: CLLocation) in + callbackCalled = true + } + + mockLocationService.triggerUpdate(location: nil) + + XCTAssert(callbackCalled == false) + } + + /** + * Given A callback has not been set + * When A new location is received + * Then Nothing should happen + */ + func testNoCallback() { + let location = CLLocation(latitude: 0.2, longitude: 0.2) + mockLocationService.triggerUpdate(location: location) + + // Just make sure nothing crashes + } +} diff --git a/KarhooSDKTests/TestCases/Service/Payment/AddPaymentDetailsSpec/KarhooAddPaymentDetailsSpec.swift b/KarhooSDKTests/TestCases/Service/Payment/AddPaymentDetailsSpec/KarhooAddPaymentDetailsSpec.swift new file mode 100644 index 00000000..a966301a --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Payment/AddPaymentDetailsSpec/KarhooAddPaymentDetailsSpec.swift @@ -0,0 +1,90 @@ +// +// KarhooAddPaymentDetailsSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class KarhooAddPaymentDetailsSpec: XCTestCase { + + private var testObject: KarhooAddPaymentDetailsInteractor! + private var mockRequestSender: MockRequestSender! + private var mockRequest: AddPaymentDetailsPayload! + private var mockUserDataStore: MockUserDataStore! + + override func setUp() { + super.setUp() + mockUserDataStore = MockUserDataStore() + + mockRequest = AddPaymentDetailsPayload(nonce: "n", + payer: Payer(), + organisationId: "elephant") + mockRequestSender = MockRequestSender() + testObject = KarhooAddPaymentDetailsInteractor(requestSender: mockRequestSender, + userDataStore: mockUserDataStore) + testObject.set(addPaymentDetailsPayload: mockRequest) + } + + /** + * When: Adding payment details + * Then: Expected method, path and payload should be set + */ + func testRequestFormat() { + testObject.execute(callback: { ( _: Result) in}) + mockRequestSender.assertRequestSendAndDecoded(endpoint: .addPaymentDetails, + method: .post, + payload: mockRequest) + } + + /** + * When: Cancelling request + * Then: request should cancel + */ + func testCancelRequest() { + testObject.cancel() + XCTAssertTrue(mockRequestSender.cancelNetworkRequestCalled) + } + + /** + * Given: Adding payment details + * When: Add payment details request succeeds + * Then: Callback should be success + * And: Nonce should be persisted to user data store + */ + func testAddPaymentDetailsSuccess() { + var callbackResult: Result? + testObject.execute(callback: { response in + callbackResult = response + }) + + let response = Nonce(nonce: "some+nonce") + mockRequestSender.triggerSuccessWithDecoded(value: response) + + XCTAssertEqual("some+nonce", callbackResult!.successValue()?.nonce) + XCTAssertEqual("some+nonce", mockUserDataStore.updateCurrentNonce?.nonce) + } + + /** + * Given: Adding payment details + * When: Add payment method request fails + * Then: Callback should contain expected error + * And: Nonce should NOT be persisted to user data store + */ + func testAddPaymentDetailsFail() { + let expectedError = TestUtil.getRandomError() + + var callbackResult: Result? + testObject.execute(callback: { response in + callbackResult = response + }) + + mockRequestSender.triggerFail(error: expectedError) + XCTAssertTrue(expectedError.equals(callbackResult!.errorValue()!)) + XCTAssertFalse(mockUserDataStore.updateCurrentNonceCalled) + XCTAssertNil(mockUserDataStore.updateCurrentNonce) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Payment/GetNonceMethod/KarhooGetNonceInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Payment/GetNonceMethod/KarhooGetNonceInteractorSpec.swift new file mode 100644 index 00000000..b3751207 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Payment/GetNonceMethod/KarhooGetNonceInteractorSpec.swift @@ -0,0 +1,93 @@ +// +// KarhooGetNonceInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +final class KarhooGetNonceInteractorSpec: XCTestCase { + + private var mockGetNonceRequest: MockRequestSender! + private var testObject: KarhooGetNonceInteractor! + private var mockNonceRequestPayload: NonceRequestPayload! + private var mockUserDataStore = MockUserDataStore() + + override func setUp() { + super.setUp() + mockGetNonceRequest = MockRequestSender() + testObject = KarhooGetNonceInteractor(request: mockGetNonceRequest, + userDataStore: mockUserDataStore) + + let mockPayer = Payer(id: "some_id", + firstName: "some_first", + lastName: "some_last", + email: "some_email") + + mockNonceRequestPayload = NonceRequestPayloadMock() + .set(payer: mockPayer) + .set(organisationId: "some_desiredOrg") + .build() + + testObject.set(nonceRequestPayload: mockNonceRequestPayload) + } + + /** + * When: Getting nonce + * Then: Expected method, path and payload should be set + */ + func testRequestFormat() { + testObject.execute(callback: { ( _: Result) in}) + mockGetNonceRequest.assertRequestSendAndDecoded(endpoint: .getNonce, + method: .post, + payload: mockNonceRequestPayload) + } + + /** + * When: Cancelling request + * Then: request should cancel + */ + func testCancelRequest() { + testObject.cancel() + XCTAssertTrue(mockGetNonceRequest.cancelNetworkRequestCalled) + } + + /** + * Given: Getting nonce + * When: method request succeeds + * Then: Callback should be success + * And: Nonce should be saved to the user data store + */ + func testGetNonceSuccess() { + var callbackResult: Result? + testObject.execute(callback: { response in + callbackResult = response + }) + + mockGetNonceRequest.triggerSuccessWithDecoded(value: Nonce(nonce: "some_nonce")) + XCTAssertEqual(callbackResult!.successValue()?.nonce, "some_nonce") + XCTAssertEqual(mockUserDataStore.updateCurrentNonce?.nonce, "some_nonce") + } + + /** + * Given: Getting nocne + * When: method request fails + * Then: Callback should contain expected error + * And: User should be updated with a new nonce + */ + func testGetNonceFail() { + let expectedError = TestUtil.getRandomError() + + var callbackResult: Result? + testObject.execute(callback: { response in + callbackResult = response + }) + + mockGetNonceRequest.triggerFail(error: expectedError) + XCTAssertTrue(expectedError.equals(callbackResult?.errorValue()!)) + XCTAssertTrue(mockUserDataStore.updateCurrentNonceCalled) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Payment/KarhooPaymentServiceSpec.swift b/KarhooSDKTests/TestCases/Service/Payment/KarhooPaymentServiceSpec.swift new file mode 100644 index 00000000..d44e471f --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Payment/KarhooPaymentServiceSpec.swift @@ -0,0 +1,155 @@ +// +// KarhooPaymentServiceSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +class KarhooPaymentServiceSpec: XCTestCase { + + private var mockPaymentSDKTokenInteractor: MockPaymentSDKTokenInteractor! + private var mockGetNonceInteractor: MockGetNonceInteractor! + private var mockAddPaymentDetailsInteractor: MockAddPaymentDetailsInteractor! + private var testObject: KarhooPaymentService! + private let mockRequestPayload: PaymentSDKTokenPayload = PaymentSDKTokenPayload(organisationId: "some", + currency: "gbp") + override func setUp() { + super.setUp() + + mockPaymentSDKTokenInteractor = MockPaymentSDKTokenInteractor() + mockGetNonceInteractor = MockGetNonceInteractor() + mockAddPaymentDetailsInteractor = MockAddPaymentDetailsInteractor() + + testObject = KarhooPaymentService(tokenInteractor: mockPaymentSDKTokenInteractor, + getNonceInteractor: mockGetNonceInteractor, + addPaymentDetailsInteractor: mockAddPaymentDetailsInteractor) + } + + /** + * When: paymentSDKTokenInteractor succeeds + * Then: succeess value should be propogated through callback + */ + func testPaymentSDKTokenInteractorSucceeds() { + let karhooCall = testObject.initialisePaymentSDK(paymentSDKTokenPayload: mockRequestPayload) + + let mockResponse = PaymentSDKToken(token: "this is the return token") + + var executeResult: Result? + karhooCall.execute(callback: { result in + executeResult = result + }) + + mockPaymentSDKTokenInteractor.triggerSuccess(result: mockResponse) + XCTAssertTrue(executeResult!.isSuccess()) + XCTAssertEqual(mockResponse.token, executeResult?.successValue()?.token) + } + + /** + * When: Client token fetch fails + * Then: An error should be sent through the callback + */ + func testTokenFetchFails() { + let karhooCall = testObject.initialisePaymentSDK(paymentSDKTokenPayload: mockRequestPayload) + + var executeResult: Result? + karhooCall.execute(callback: { result in + executeResult = result + }) + + let error = TestUtil.getRandomError() + mockPaymentSDKTokenInteractor.triggerFail(error: error) + + XCTAssert(error.equals(executeResult!.errorValue()!)) + XCTAssertNil(executeResult?.successValue()?.token) + } + + /** + * Given: Get nonce is called + * When: getNonceInteractor succeeds + * Then: succeess value should be propogated through callback + */ + func testGetNonceSuccess() { + let mockPayload = NonceRequestPayloadMock().set(payer: Payer()).set(organisationId: "some").build() + let karhooCall = testObject.getNonce(nonceRequestPayload: mockPayload) + + let mockNonceResponse = Nonce(nonce: "some_nonce") + + var executeResult: Result? + karhooCall.execute(callback: { result in + executeResult = result + }) + + mockGetNonceInteractor.triggerSuccess(result: mockNonceResponse) + + XCTAssertTrue(executeResult!.isSuccess()) + XCTAssertEqual(mockNonceResponse.nonce, executeResult?.successValue()?.nonce) + } + + /** + * Given: Get nonce is called + * When: getNonceInteractor fails + * Then: An error should be sent through the callback + */ + func testGetNonceFail() { + let mockPayload = NonceRequestPayloadMock().set(payer: Payer()).set(organisationId: "some").build() + let karhooCall = testObject.getNonce(nonceRequestPayload: mockPayload) + + var executeResult: Result? + karhooCall.execute(callback: { result in + executeResult = result + }) + + let error = TestUtil.getRandomError() + mockGetNonceInteractor.triggerFail(error: error) + + XCTAssert(error.equals(executeResult!.errorValue()!)) + XCTAssertNil(executeResult?.successValue()?.nonce) + } + + /** + * When: Adding payment details succeeds + * Then: A success response should be sent through the callback + */ + func testAddPaymentDetailsSuccessful() { + let mockPayload = AddPaymentDetailsPayload(nonce: "some_nonce", + payer: Payer(), + organisationId: "some_orh") + let karhooCall = testObject.addPaymentDetails(addPaymentDetailsPayload: mockPayload) + + var executeResult: Result? + karhooCall.execute(callback: { result in + executeResult = result + }) + + mockAddPaymentDetailsInteractor.triggerSuccess(result: Nonce(nonce: "some+token")) + + XCTAssertEqual("some+token", executeResult?.successValue()?.nonce) + } + + /** + * When: Adding payment details fails + * Then: An error should be sent through the callback + */ + func testAddPaymentDetailsFail() { + let mockPayload = AddPaymentDetailsPayload(nonce: "some_nonce", + payer: Payer(), + organisationId: "some_orh") + + let karhooCall = testObject.addPaymentDetails(addPaymentDetailsPayload: mockPayload) + + var executeResult: Result? + karhooCall.execute(callback: { result in + executeResult = result + }) + + let error = TestUtil.getRandomError() + mockAddPaymentDetailsInteractor.triggerFail(error: error) + + XCTAssert(error.equals(executeResult!.errorValue()!)) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Payment/PaymentSDKToken/KarhooPaymentSDKTokenInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Payment/PaymentSDKToken/KarhooPaymentSDKTokenInteractorSpec.swift new file mode 100644 index 00000000..c099597e --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Payment/PaymentSDKToken/KarhooPaymentSDKTokenInteractorSpec.swift @@ -0,0 +1,83 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooPaymentSDKTokenInteractorSpec: XCTestCase { + + private var mockPaymentSDKTokenRequest: MockRequestSender! + private var testObject: KarhooPaymentSDKTokenInteractor! + private let mockPayload = PaymentSDKTokenPayload(organisationId: "some_org", currency: "GBP") + + override func setUp() { + super.setUp() + + mockPaymentSDKTokenRequest = MockRequestSender() + testObject = KarhooPaymentSDKTokenInteractor(request: mockPaymentSDKTokenRequest) + testObject.set(payload: mockPayload) + } + + /** + * When: Getting token + * Then: Expected method, path and payload should be set + */ + func testRequestFormat() { + testObject.execute(callback: { result in + _ = result as Result + }) + + mockPaymentSDKTokenRequest.assertRequestSendAndDecoded(endpoint: .paymentSDKToken(payload: mockPayload), + method: .post, + payload: nil) + } + + /** + * When: Cancelling request + * Then: Request should cancel + */ + func testCancelRequest() { + testObject.cancel() + XCTAssertTrue(mockPaymentSDKTokenRequest.cancelNetworkRequestCalled) + } + + /** + * Given: Getting token + * When: sdk token request succeeds + * Then: Callback should contain expected result + */ + func testSuccessfulTokenFetch() { + let successResult = PaymentSDKTokenMock().set(token: "some_token") + + var callbackResult: Result? + testObject.execute(callback: { response in + callbackResult = response + }) + + mockPaymentSDKTokenRequest.triggerSuccessWithDecoded(value: successResult.build()) + + XCTAssertEqual("some_token", callbackResult!.successValue()!.token) + } + + /** + * Given: Getting token + * When: sdk token request fails + * Then: Callback should contain expected result + */ + func testTokenFetchFails() { + let expectedError = TestUtil.getRandomError() + + var callbackResult: Result? + testObject.execute(callback: { result in + callbackResult = result + }) + + mockPaymentSDKTokenRequest.triggerFail(error: expectedError) + + XCTAssertTrue(expectedError.equals(callbackResult!.errorValue()!)) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Quote/CategoryQuoteMapperSpec.swift b/KarhooSDKTests/TestCases/Service/Quote/CategoryQuoteMapperSpec.swift new file mode 100644 index 00000000..45eb0b31 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Quote/CategoryQuoteMapperSpec.swift @@ -0,0 +1,111 @@ +// +// CategoryQuoteMapperSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class CategoryQuoteMapperSpec: XCTestCase { + + private var testObject: CategoryQuoteMapper! + + override func setUp() { + super.setUp() + + testObject = CategoryQuoteMapper() + } + + /** + * When: Passinng in an array of categories + * And: An array of quotes with exact matching categories + * Then: The expected dictionary should be outputted + */ + func testMatchingCategoriesAndQuotesMapCorrectly() { + let mockCategories = CategoriesMock().set(categories: ["foo", "bar", "fizz"]).build() + + var mockQuotes: [Quote] = [] + mockQuotes.append(QuoteMock().set(quoteId: "fooQuote").set(categoryName: "foo").build()) + mockQuotes.append(QuoteMock().set(quoteId: "barQuote").set(categoryName: "bar").build()) + mockQuotes.append(QuoteMock().set(quoteId: "fizzQuote").set(categoryName: "fizz").build()) + + let expectedOutput = testObject.map(categories: mockCategories, toQuotes: mockQuotes) + + let fooQuotes = expectedOutput.first(where: { $0.categoryName == "foo"})?.quotes + let barQuotes = expectedOutput.first(where: { $0.categoryName == "bar"})?.quotes + let fizzQuotes = expectedOutput.first(where: { $0.categoryName == "fizz"})?.quotes + + XCTAssertEqual(mockQuotes[0].quoteId, fooQuotes?[0].quoteId) + XCTAssertEqual(mockQuotes[1].quoteId, barQuotes?[0].quoteId) + XCTAssertEqual(mockQuotes[2].quoteId, fizzQuotes?[0].quoteId) + } + + /** + * When: Passinng in an array of categories + * And: An array of quotes that has one mismatched category + * Then: The expected dictionary should be outputted + * And: The mismatched category quotes should be nil + */ + func testMismatchedCategoryNameMapsCorrectly() { + let mockCategories = CategoriesMock().set(categories: ["foo", "bar", "rogue-category"]).build() + + var mockQuotes: [Quote] = [] + mockQuotes.append(QuoteMock().set(quoteId: "fooQuote").set(categoryName: "foo").build()) + mockQuotes.append(QuoteMock().set(quoteId: "barQuote").set(categoryName: "bar").build()) + mockQuotes.append(QuoteMock().set(quoteId: "barQuote").set(categoryName: "bar").build()) + + let expectedOutput = testObject.map(categories: mockCategories, toQuotes: mockQuotes) + + let fooQuotes = expectedOutput.first(where: { $0.categoryName == "foo"})?.quotes + let barQuotes = expectedOutput.first(where: { $0.categoryName == "bar"})?.quotes + let rogueCategoryQuotes = expectedOutput.first(where: { $0.categoryName == "rogue-category"})?.quotes + + XCTAssertEqual(1, fooQuotes?.count) + XCTAssertEqual(2, barQuotes?.count) + XCTAssertTrue(rogueCategoryQuotes!.isEmpty) + } + + /** + * When: Passing an array of categories + * And: There is a duplicate category + * Then: There should only be 2 categories outputted (removes duplicate foo) + */ + func testDuplicateCategoryHandling() { + let mockCategories = CategoriesMock().set(categories: ["foo", "bar", "foo"]).build() + + var mockQuotes: [Quote] = [] + mockQuotes.append(QuoteMock().set(quoteId: "fooQuote").set(categoryName: "foo").build()) + mockQuotes.append(QuoteMock().set(quoteId: "barQuote").set(categoryName: "bar").build()) + mockQuotes.append(QuoteMock().set(quoteId: "barQuote").set(categoryName: "bar").build()) + + let expectedOutput = testObject.map(categories: mockCategories, toQuotes: mockQuotes) + + XCTAssertEqual(2, expectedOutput.count) + } + + /** + * When: Passing nil categories + * Then: Quotes should be categorized by their category names only + */ + func testNilCategoriesMapsValidQuotesCorrectly() { + var mockQuotes: [Quote] = [] + mockQuotes.append(QuoteMock().set(quoteId: "fooQuote").set(categoryName: "foo").build()) + mockQuotes.append(QuoteMock().set(quoteId: "barQuote").set(categoryName: "bar").build()) + mockQuotes.append(QuoteMock().set(quoteId: "fizzQuote").set(categoryName: "fizz").build()) + + let expectedOutput = testObject.map(categories: nil, toQuotes: mockQuotes) + + let fooQuotes = expectedOutput.first(where: { $0.categoryName == "foo"})?.quotes + let barQuotes = expectedOutput.first(where: { $0.categoryName == "bar"})?.quotes + let fizzQuotes = expectedOutput.first(where: { $0.categoryName == "fizz"})?.quotes + + XCTAssertEqual(mockQuotes[0].quoteId, fooQuotes![0].quoteId) + XCTAssertEqual(mockQuotes[1].quoteId, barQuotes![0].quoteId) + XCTAssertEqual(mockQuotes[2].quoteId, fizzQuotes![0].quoteId) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Quote/Interactor/KarhooQuoteInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Quote/Interactor/KarhooQuoteInteractorSpec.swift new file mode 100644 index 00000000..087b98da --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Quote/Interactor/KarhooQuoteInteractorSpec.swift @@ -0,0 +1,387 @@ +// +// KarhooQuoteInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarooQuoteInteractorSpec: XCTestCase { + + private var testObject: KarhooQuoteInteractor! + private var mockAvailabilityRequest: MockRequestSender! + private var mockQuoteListIdRequest: MockRequestSender! + private var mockQuotesRequest: MockRequestSender! + + private lazy var mockQuoteSearch: QuoteSearch = { + return QuoteSearch(origin: LocationInfoMock().set(placeId: "originPlaceId").build(), + destination: LocationInfoMock().set(placeId: "destinationPlaceId").build(), + dateScheduled: Date()) + }() + + private lazy var mockCategories: Categories = { + return CategoriesMock().set(categories: ["foo"]).build() + }() + + override func setUp() { + super.setUp() + + mockAvailabilityRequest = MockRequestSender() + mockQuoteListIdRequest = MockRequestSender() + mockQuotesRequest = MockRequestSender() + + testObject = KarhooQuoteInteractor(availabilityRequest: mockAvailabilityRequest, + quoteListIdRequest: mockQuoteListIdRequest, + quotesRequest: mockQuotesRequest) + + testObject.set(quoteSearch: mockQuoteSearch) + } + + /** + * When: Calling execute + * And: No QuoteSearch is set on the interactor + * And: No requests should be made + */ + func testNoQuoteSearchSet() { + let noQuoteSearchSetInteractor = KarhooQuoteInteractor(availabilityRequest: mockAvailabilityRequest, + quoteListIdRequest: mockQuoteListIdRequest, + quotesRequest: mockQuotesRequest) + + noQuoteSearchSetInteractor.execute(callback: { (_:Result) in }) + + XCTAssertFalse(mockAvailabilityRequest.requestAndDecodeCalled) + XCTAssertFalse(mockQuoteListIdRequest.requestAndDecodeCalled) + XCTAssertFalse(mockQuotesRequest.requestAndDecodeCalled) + } + + /** + * Given: Searching for Quotes with a dateScheduled + * When: The timezone is new york + * Then: The expected pickuptime string, localised in the correct format + * Should be propogated to the availabilityRequest + */ + func testDateScheduledFormatInAvailabilityRequest() { + let testDate = Date(timeIntervalSince1970: 1605195000) // UTC: 11/12/2020-15:30 + + let testQuoteSearch = QuoteSearch(origin: LocationInfoMock().set(timeZoneIdentifier: "America/New_York") + .set(placeId: "origin_id") + .build(), + destination: LocationInfoMock().set(placeId: "destination_id") + .build(), + dateScheduled: testDate) + + testObject.set(quoteSearch: testQuoteSearch) + testObject.execute(callback: { (_: Result) in }) + + let availabilityRequestPayload = mockAvailabilityRequest.payloadSet as? AvailabilitySearch + + XCTAssertEqual("2020-11-12T10:30", availabilityRequestPayload?.dateScheduled) + } + + /** + * When: Cancelling Quote Search + * Then: Availability and QuoteListId request should be cancelled + * And: QuotePoller should be stopped + */ + func testCancelQuoteSearch() { + testObject.cancel() + + XCTAssertTrue(mockAvailabilityRequest.cancelNetworkRequestCalled) + XCTAssertTrue(mockQuoteListIdRequest.cancelNetworkRequestCalled) + } + + /** + * Given: Calling execute + * When: the interactor does not hold a quote list id + * Then: request for availability should be made + * And: Quote request should not be called + */ + func testNoQuoteListId() { + testObject.execute(callback: { (_: Result) in }) + + var dateScheduled: String? + if let quoteSearchDate = mockQuoteSearch.dateScheduled { + let dateFormatter = KarhooNetworkDateFormatter(timeZone: mockQuoteSearch.origin.timezone(), + formatType: .availability) + + dateScheduled = dateFormatter.toString(from: quoteSearchDate) + } + + let expectedPayload = AvailabilitySearch(origin: mockQuoteSearch.origin.placeId, + destination: mockQuoteSearch.destination.placeId, + dateScheduled: dateScheduled) + + mockAvailabilityRequest.assertRequestSendAndDecoded(endpoint: .availability, + method: .get, + payload: expectedPayload) + + XCTAssertFalse(mockQuotesRequest.requestAndDecodeCalled) + } + + /** + * Given: Calling execute + * When: The interactor does not hold a quote list id + * And: the request for availability succeeds + * Then: quote list id request should be made + */ + func testRequestAndHandleAvailabilityRequestSuccess() { + testObject.execute(callback: { (_: Result) in }) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: mockCategories) + + let availabilityRequestPayload = mockAvailabilityRequest.payloadSet as? AvailabilitySearch + + let expectedPayload = QuoteListIdRequestPayload(origin: mockQuoteSearch.origin.placeId, + destination: mockQuoteSearch.destination.placeId, + dateScheduled: availabilityRequestPayload?.dateScheduled) + + mockQuoteListIdRequest.assertRequestSendAndDecoded(endpoint: .quoteListId, + method: .post, + payload: expectedPayload) + } + + /** + * Given: Calling execute + * When: The interactor does not hold a quote list id + * And: the request for availability fails + * Then: quote list id request should be made + */ + func testRequestAndHandleAvailabilityRequestFails() { + testObject.execute(callback: { (_: Result) in }) + + mockAvailabilityRequest.triggerFail(error: TestUtil.getRandomError()) + let availabilityRequestPayload = mockAvailabilityRequest.payloadSet as? AvailabilitySearch + + let expectedPayload = QuoteListIdRequestPayload(origin: mockQuoteSearch.origin.placeId, + destination: mockQuoteSearch.destination.placeId, + dateScheduled: availabilityRequestPayload?.dateScheduled) + + mockQuoteListIdRequest.assertRequestSendAndDecoded(endpoint: .quoteListId, + method: .post, + payload: expectedPayload) + } + + /** + * Given: Calling execute + * When: The interactor does not hold a quote list id + * And: the request for availability completes + * And: quote list id request fails + * Then: Callback should be propogated + */ + func testQuoteListIdRequestFails() { + let expectedError = TestUtil.getRandomError() + var result: Result? + testObject.execute(callback: { result = $0 }) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: mockCategories) + mockQuoteListIdRequest.triggerFail(error: expectedError) + + XCTAssert(expectedError.equals(result!.errorValue()!)) + } + + /** + * Given: Calling execute + * When: The interactor does not hold a quote list id + * And: the request for availability completes + * And: quote list id request succeeds + * Then: quote request should be made with expected search + */ + func testQuoteListIdRequestSucceeds() { + testObject.execute(callback: { (_: Result) in }) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: mockCategories) + mockQuoteListIdRequest.triggerSuccessWithDecoded(value: QuoteListId(identifier: "some", validityTime: 100)) + + mockQuotesRequest.assertRequestSendAndDecoded(endpoint: .quotes(identifier: "some"), + method: .post, + payload: nil) + } + + /** + * Given: Calling execute + * When: The interactor does not hold a quote list id + * And: the request for availability completes + * And: quote list id request succeeds + * And: quote request succeeds + * Then: callback should propogate expected value + */ + func testQuotesRequestSucceeds() { + var result: Result? + testObject.execute(callback: { result = $0 }) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: mockCategories) + mockQuoteListIdRequest.triggerSuccessWithDecoded(value: QuoteListId(identifier: "some", validityTime: 100)) + + let expectedQuote = QuoteMock().set(quoteId: "foo").set(categoryName: "foo").build() + + let expectedResult = QuoteListMock().set(quoteListId: "some_id") + .set(status: "some_status") + .set(validity: 60) + .add(quoteItem: expectedQuote).build() + + mockQuotesRequest.triggerSuccessWithDecoded(value: expectedResult) + + XCTAssertEqual(expectedQuote.quoteId, result!.successValue()!.quotes(for: "foo")[0].quoteId) + } + + /** + * Given: Calling execute + * When: The interactor does not hold a quote list id + * And: the request for availability completes + * And: quote list id request succeeds + * And: quote request fails + * Then: quote list id request should be made again + */ + func testQuotesRequestFails() { + let expectedError = TestUtil.getRandomError() + + var result: Result? + testObject.execute(callback: { result = $0 }) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: mockCategories) + mockQuoteListIdRequest.triggerSuccessWithDecoded(value: QuoteListId(identifier: "some", validityTime: 100)) + + mockQuotesRequest.triggerFail(error: expectedError) + + XCTAssert(expectedError.equals(result!.errorValue())) + } + + /** + * Given: Calling execute + * When: The interactor does not hold a quote list id + * And: the request for availability completes + * And: quote list id request succeeds + * And: quote request fails due to K3001 + * Then: quote list id request should be made again + */ + func testQuotesRequestFailsCouldNotGetEstimate() { + let expectedError = MockError(code: "K3001", message: "Could not get estimates", userMessage: "some") + + testObject.execute(callback: { (_: Result) in }) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: mockCategories) + mockQuoteListIdRequest.triggerSuccessWithDecoded(value: QuoteListId(identifier: "some", validityTime: 100)) + + mockQuoteListIdRequest.requestAndDecodeCalled = false + + mockQuotesRequest.triggerFail(error: expectedError) + + XCTAssertTrue(mockAvailabilityRequest.cancelNetworkRequestCalled) + XCTAssertTrue(mockQuoteListIdRequest.cancelNetworkRequestCalled) + XCTAssertTrue(mockQuoteListIdRequest.requestAndDecodeCalled) + } + + /** + * Given: Calling execute + * When: the interactor holds a quote list id + * Then: request for availability should not be made + * Then: request for quote list id should not be made + * And: Quote request should be made + * And: Result should propogate in the callback + */ + func testQuoteListIdAlreadyPresent() { + testObject.execute(callback: { (_: Result) in }) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: mockCategories) + mockQuoteListIdRequest.triggerSuccessWithDecoded(value: QuoteListId(identifier: "some", validityTime: 100)) + + XCTAssertTrue(mockAvailabilityRequest.requestAndDecodeCalled) + XCTAssertTrue(mockQuoteListIdRequest.requestAndDecodeCalled) + XCTAssertTrue(mockQuotesRequest.requestAndDecodeCalled) + + // reset + mockAvailabilityRequest.requestAndDecodeCalled = false + mockQuoteListIdRequest.requestAndDecodeCalled = false + mockQuotesRequest.requestAndDecodeCalled = false + + var result: Result? + testObject.execute(callback: { result = $0 }) + + XCTAssertFalse(mockAvailabilityRequest.requestAndDecodeCalled) + XCTAssertFalse(mockQuoteListIdRequest.requestAndDecodeCalled) + + XCTAssertTrue(mockQuotesRequest.requestAndDecodeCalled) + + let expectedQuote = QuoteMock().set(quoteId: "foo").set(categoryName: "foo").build() + + let expectedResult = QuoteListMock().set(quoteListId: "some_id") + .set(status: "some_status") + .set(validity: 60) + .add(quoteItem: expectedQuote).build() + + mockQuotesRequest.triggerSuccessWithDecoded(value: expectedResult) + + XCTAssertEqual(expectedQuote.quoteId, result!.successValue()!.quotes(for: "foo")[0].quoteId) + } + + /** + * Given: Calling execute + * When: The interactor does not hold a quote list id + * And: the request for availability completes + * And: quote list id request succeeds + * And: quote request succeeds but contains a quote with an ETA greater than 20 mins + * Then: callback should propogate empty quotes list + */ + func testQuoteWithGreaterThan20MinEtaIsFiltered() { + var result: Result? + testObject.execute(callback: { result = $0 }) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: mockCategories) + mockQuoteListIdRequest.triggerSuccessWithDecoded(value: QuoteListId(identifier: "some", validityTime: 100)) + + let expectedQuote = QuoteMock().set(quoteId: "foo").set(categoryName: "foo").set(qtaHighMinutes: 21).build() + + let expectedResult = QuoteListMock().set(quoteListId: "some_id") + .set(status: "some_status") + .set(validity: 60) + .add(quoteItem: expectedQuote).build() + + mockQuotesRequest.triggerSuccessWithDecoded(value: expectedResult) + + XCTAssertEqual(0, result?.successValue()?.all.count) + } + + /** + * Given: Calling execute + * And: the request for availability completes + * And: quote list id request succeeds but will expire within 10 seconds + * Then: callback should not be called and request new QuoteListId + * When: quote list returns QuoteList with validity above 10 + * Then: callback should be called + */ + func testValidityTimeRefreshesQuoteList() { + var result: Result? + testObject.execute(callback: { result = $0 }) + + mockAvailabilityRequest.triggerSuccessWithDecoded(value: mockCategories) + mockQuoteListIdRequest.triggerSuccessWithDecoded(value: QuoteListId(identifier: "some", validityTime: 5)) + + let expectedQuote = QuoteMock().set(quoteId: "foo").set(categoryName: "foo").build() + + let quotesWithExpiringValidity = QuoteListMock().set(quoteListId: "some_id") + .set(status: "some_status") + .set(validity: 5) + .add(quoteItem: expectedQuote).build() + + mockQuotesRequest.triggerSuccessWithDecoded(value: quotesWithExpiringValidity) + + XCTAssertNil(result) + + mockQuoteListIdRequest.triggerSuccessWithDecoded(value: QuoteListId(identifier: "some", validityTime: 5)) + + let validQuoteList = QuoteListMock().set(quoteListId: "some_id") + .set(status: "some_status") + .set(validity: 300) + .add(quoteItem: expectedQuote).build() + + mockQuotesRequest.triggerSuccessWithDecoded(value: validQuoteList) + + XCTAssertNotNil(result) + XCTAssertTrue(result!.isSuccess()) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Quote/KarhooQuoteServiceSpec.swift b/KarhooSDKTests/TestCases/Service/Quote/KarhooQuoteServiceSpec.swift new file mode 100644 index 00000000..50515726 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Quote/KarhooQuoteServiceSpec.swift @@ -0,0 +1,70 @@ +// +// KarhooQuoteServiceSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooQuoteServiceSpec: XCTestCase { + + private var testobject: KarhooQuoteService! + private var mockQuoteInteractor: MockQuoteInteractor! + + private let mockQuoteSearch: QuoteSearch = QuoteSearch(origin: LocationInfoMock() + .set(placeId: "originPlaceId") + .build(), + destination: LocationInfoMock() + .set(placeId: "destinationPlaceId") + .build(), + dateScheduled: Date()) + + static let mockQuote = QuoteMock().set(quoteId: "success-quote").set(categoryName: "foo").build() + let mockQuotesResult = Quotes(quoteListId: "some", + quoteCategories: [QuoteCategory(name: "foo", quotes: [mockQuote])], + all: [mockQuote]) + + override func setUp() { + super.setUp() + + mockQuoteInteractor = MockQuoteInteractor() + + testobject = KarhooQuoteService(quoteInteractor: mockQuoteInteractor) + } + + /** + * When: Quote search succeeds + * Then: callback should be executed with expected value + */ + func testQuoteSearchSucces() { + let pollCall = testobject.quotes(quoteSearch: mockQuoteSearch) + + var result: Result? + pollCall.execute(callback: { result = $0 }) + + mockQuoteInteractor.triggerSuccess(result: mockQuotesResult) + + XCTAssertEqual("success-quote", result?.successValue()?.quotes(for: "foo")[0].quoteId) + } + + /** + * When: Quote search fails + * Then: callback should be executed with expected value + */ + func testQuoteSearchFails() { + let pollCall = testobject.quotes(quoteSearch: mockQuoteSearch) + + var result: Result? + pollCall.execute(callback: { result = $0 }) + + let expectedError = TestUtil.getRandomError() + mockQuoteInteractor.triggerFail(error: expectedError) + + XCTAssert(expectedError.equals(result?.errorValue())) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Trips/BookingInteractor/KarhooBookingInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Trips/BookingInteractor/KarhooBookingInteractorSpec.swift new file mode 100644 index 00000000..c65a3404 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Trips/BookingInteractor/KarhooBookingInteractorSpec.swift @@ -0,0 +1,114 @@ +// +// KarhooBookingInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest +@testable import KarhooSDK + +class KarhooBookingInteractorSpec: XCTestCase { + + var mockBookingRequest: MockRequestSender! + var mockUserDataStore: MockUserDataStore! + var mockAnalytics: MockAnalyticsService! + + var testObject: KarhooBookingInteractor! + private let passengers = Passengers(additionalPassengers: 0, + passengerDetails: [PassengerDetails(user: UserInfoMock() + .set(email: "someEmail") + .set(firstName: "someFirstName") + .set(lastName: "someLastName") + .set(mobile: "someMobile") + .build())]) + + override func setUp() { + super.setUp() + + mockBookingRequest = MockRequestSender() + mockUserDataStore = MockUserDataStore() + mockAnalytics = MockAnalyticsService() + + testObject = KarhooBookingInteractor(bookingRequest: mockBookingRequest) + } + + /** + * When: Booking a trip with no payment nonce + * Then: The correct request and format should be sent + */ + func testRequestFormatNoPaymentNonce() { + let tripBooking = TripBooking(quoteId: "some", + passengers: passengers, + flightNumber: "312") + + testObject.set(tripBooking: tripBooking) + testObject.execute(callback: { (_:Result) in }) + + mockBookingRequest.assertRequestSendAndDecoded(endpoint: APIEndpoint.bookTrip, + method: .post, + payload: tripBooking) + } + + /** + * When: Booking a trip WITH a payment nonce + * Then: The correct request and format should be sent + */ + func testRequestFormatWithPaymentNonce() { + var tripBooking = TripBooking(quoteId: "some", + passengers: passengers, + flightNumber: "312") + + tripBooking.paymentNonce = "some_nonce" + + testObject.set(tripBooking: tripBooking) + testObject.execute(callback: { (_:Result) in }) + + mockBookingRequest.assertRequestSendAndDecoded(endpoint: APIEndpoint.bookTripWithNonce, + method: .post, + payload: tripBooking) + } + + /** + * Given: Booking a trip + * When: Request succeeds + * Then: Expected callback should be propogated + */ + func testRequestSuccess() { + let tripBooking = TripBooking(quoteId: "some", + passengers: passengers, + flightNumber: "312") + var capturedCallback: Result? + + testObject.set(tripBooking: tripBooking) + testObject.execute(callback: { capturedCallback = $0 }) + + mockBookingRequest.triggerSuccessWithDecoded(value: TripInfoMock().set(tripId: "some").build()) + + XCTAssertEqual("some", capturedCallback!.successValue()?.tripId) + XCTAssertTrue(capturedCallback!.isSuccess()) + XCTAssertNil(capturedCallback?.errorValue()) + } + + /** + * Given: Booking a trip + * When: Request fails + * Then: Expected callback should be propogated + */ + func testRequestFails() { + let tripBooking = TripBooking(quoteId: "some", + passengers: passengers, + flightNumber: "312") + + var capturedCallback: Result? + + testObject.set(tripBooking: tripBooking) + testObject.execute(callback: { capturedCallback = $0 }) + + let error = TestUtil.getRandomError() + mockBookingRequest.triggerFail(error: error) + XCTAssert(error.equals(capturedCallback!.errorValue())) + XCTAssertFalse(capturedCallback!.isSuccess()) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Trips/CancelTripInteractor/KarhooCancelTripInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Trips/CancelTripInteractor/KarhooCancelTripInteractorSpec.swift new file mode 100644 index 00000000..d86bb308 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Trips/CancelTripInteractor/KarhooCancelTripInteractorSpec.swift @@ -0,0 +1,80 @@ +// +// KarhooCancelTripInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooCancelTripInteractorSpec: XCTestCase { + + private var testObject: KarhooCancelTripInteractor! + private var mockCancelTripRequestSender: MockRequestSender! + private var mockAnalyticsService: MockAnalyticsService! + + private let tripCancellationMock = TripCancellation(tripId: "some_tripId", + cancelReason: .notNeededAnymore) + override func setUp() { + super.setUp() + mockCancelTripRequestSender = MockRequestSender() + mockAnalyticsService = MockAnalyticsService() + + testObject = KarhooCancelTripInteractor(requestSender: mockCancelTripRequestSender, + analyticsService: mockAnalyticsService) + testObject.set(tripCancellation: tripCancellationMock) + } + + /** + * When: Making request + * Then: Correct payload, method, path and request should be sent + */ + func testRequestFormat() { + testObject.execute(callback: { (_:Result) in }) + + let endpoint = APIEndpoint.cancelTrip(identifier: tripCancellationMock.tripId) + + let testPayload = CancelTripRequestPayload(reason: tripCancellationMock.cancelReason) + + mockCancelTripRequestSender.assertRequestSend(endpoint: endpoint, + method: .post, + payload: testPayload) + } + + /** + * When: Cancel trip request is successful + * Then: Callback should be successful + * And: Analytics should call tripCancellationAttempt + */ + func testRequestSucceeds() { + var capturedCallback: Result? + testObject.execute(callback: { capturedCallback = $0 }) + + mockCancelTripRequestSender.triggerSuccess(response: KarhooVoid().encode()!) + + XCTAssertTrue(mockAnalyticsService.tripCancellationAttemptedCalled) + XCTAssertTrue(capturedCallback!.isSuccess()) + } + + /** + * When: Cancel trip request fails + * Then: Callback should fail + * And: Analytics should call tripCancellationAttempt + */ + func testRequestFails() { + let expectedError = TestUtil.getRandomError() + + var capturedCallback: Result? + testObject.execute(callback: { capturedCallback = $0 }) + + mockCancelTripRequestSender.triggerFail(error: expectedError) + + XCTAssertTrue(mockAnalyticsService.tripCancellationAttemptedCalled) + XCTAssertFalse(capturedCallback!.isSuccess()) + XCTAssert(expectedError.equals(capturedCallback!.errorValue())) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Trips/KarhooTripServiceSpec.swift b/KarhooSDKTests/TestCases/Service/Trips/KarhooTripServiceSpec.swift new file mode 100644 index 00000000..2b2b116c --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Trips/KarhooTripServiceSpec.swift @@ -0,0 +1,168 @@ +// +// KarhooTripServiceSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +final class KarhooTripServiceSpec: XCTestCase { + + private var testObject: KarhooTripService! + private var mockTripBookingInteractor: MockBookingInteractor! + private var mockCancelTripInteractor: MockCancelTripInteractor! + private var mockTripSearchInteractor: MockTripSearchInteractor! + + private var mocktripPollFactory: MockPollCallFactory! + private var mocktripStatusPollFactory: MockPollCallFactory! + private var mockAnalytics: MockAnalyticsService! + private let mockPassengers = Passengers(additionalPassengers: 0, + passengerDetails: [PassengerDetails(user: UserInfoMock().build())]) + + private let tripCancellationMock = TripCancellation(tripId: "some_tripId", + cancelReason: .notNeededAnymore) + override func setUp() { + super.setUp() + + mockTripBookingInteractor = MockBookingInteractor() + mockCancelTripInteractor = MockCancelTripInteractor() + mockTripSearchInteractor = MockTripSearchInteractor() + mocktripPollFactory = MockPollCallFactory() + mocktripStatusPollFactory = MockPollCallFactory() + mockAnalytics = MockAnalyticsService() + + testObject = KarhooTripService(bookingInteractor: mockTripBookingInteractor, + cancelTripInteractor: mockCancelTripInteractor, + tripSearchInteractor: mockTripSearchInteractor, + analytics: mockAnalytics, + tripPollFactory: mocktripPollFactory, + tripStatusPollFactory: mocktripStatusPollFactory) + } + + /** + * Given: Request succeeded + * When: Booking a trip + * Then: TripBooker captured parameters are correct + * And: Correct trip is returned in callback + */ + func testBookingTripRequestSuccess() { + let tripBooking = TripBooking(quoteId: "some", passengers: mockPassengers) + let tripBooked = TripInfoMock().set(tripId: "some").build() + + var capturedCallback: Result? + testObject.book(tripBooking: tripBooking).execute(callback: { capturedCallback = $0 }) + + mockTripBookingInteractor.triggerSuccess(result: tripBooked) + + XCTAssertEqual(mockTripBookingInteractor.tripBookingSet!, tripBooking) + XCTAssertEqual(tripBooked.tripId, capturedCallback!.successValue()!.tripId) + } + + /** + * Given: Request failed + * When: Booking a trip + * Then: Error should be propagated through callback + */ + func testBookingTripRequestFailure() { + let tripBooking = TripBooking(quoteId: "some", passengers: mockPassengers) + let expectedError = TestUtil.getRandomError() + + var capturedCallback: Result? + testObject.book(tripBooking: tripBooking).execute(callback: { capturedCallback = $0 }) + + mockTripBookingInteractor.triggerFail(error: expectedError) + + XCTAssert(expectedError.equals(capturedCallback?.errorValue())) + } + + /** + * Given: Request succeeded + * When: Getting trips + * Then: List of trips should be returned in callback + */ + func testGetTripsSuccess() { + let tripList = [TripInfoMock().set(tripId: "some").build()] + + var capturedCallback: Result<[TripInfo]>? + testObject.search(tripSearch: TripSearch()).execute(callback: { capturedCallback = $0 }) + + mockTripSearchInteractor.triggerSuccess(result: tripList) + + XCTAssertEqual(tripList[0].tripId, capturedCallback!.successValue()![0].tripId) + } + + /** + * Given: request fails + * When: Getting trips + * Then: Error should be propogated through callback + */ + func testGetTripsFail() { + let expectedError = TestUtil.getRandomError() + + var capturedCallback: Result<[TripInfo]>? + testObject.search(tripSearch: TripSearch()).execute(callback: { capturedCallback = $0 }) + + mockTripSearchInteractor.triggerFail(error: expectedError) + + XCTAssert(expectedError.equals(capturedCallback?.errorValue())) + } + + /** + * When: Cancelling a trip + * Then: expected Callback should be received + */ + func testCancelTripSuccess() { + + var capturedCallback: Result? + testObject.cancel(tripCancellation: tripCancellationMock).execute(callback: { capturedCallback = $0 }) + + mockCancelTripInteractor.triggerSuccess(result: KarhooVoid()) + + XCTAssertTrue(capturedCallback!.isSuccess()) + XCTAssertNil(capturedCallback!.errorValue()) + } + + /** + * When: Cancelling a trip + * Then: expected Callback should be received + */ + func testCancelTripFails() { + let expectedError = TestUtil.getRandomError() + + var capturedCallback: Result? + testObject.cancel(tripCancellation: tripCancellationMock).execute(callback: { capturedCallback = $0 }) + + mockCancelTripInteractor.triggerFail(error: expectedError) + + XCTAssertFalse(capturedCallback!.isSuccess()) + XCTAssert(expectedError.equals(capturedCallback!.errorValue())) + } + + /** + * When: adding a track trip observer + * Then: trackTripPollFactory should be called + */ + func testTrackTrip() { + let expectedTripId = "12345" + _ = testObject.trackTrip(tripId: expectedTripId) + + XCTAssertNotNil(mocktripPollFactory.executableSet) + XCTAssertEqual(expectedTripId, mocktripPollFactory.identifierSet) + } + + /** + * When: adding a track trip status observer + * Then: trackTripPollFactory should be called + */ + func testTrackTripStatus() { + let expectedTripId = "12345" + _ = testObject.status(tripId: expectedTripId) + + XCTAssertNotNil(mocktripStatusPollFactory.executableSet) + XCTAssertEqual(expectedTripId, mocktripStatusPollFactory.identifierSet) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Trips/TripSearchInteractor/KarhooTripsListInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Trips/TripSearchInteractor/KarhooTripsListInteractorSpec.swift new file mode 100644 index 00000000..0553804c --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Trips/TripSearchInteractor/KarhooTripsListInteractorSpec.swift @@ -0,0 +1,79 @@ +// +// KarhooTripsListInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooTripsListInteractorSpec: XCTestCase { + + private var mockTripSearchRequest: MockRequestSender! + private var testObject: KarhooTripSearchInteractor! + + override func setUp() { + super.setUp() + mockTripSearchRequest = MockRequestSender() + testObject = KarhooTripSearchInteractor(requestSender: mockTripSearchRequest) + } + + /** + * When: Making a request + * Then: Expected method, path and payload should be set + */ + func testRequestFormat() { + let tripStates: [TripState] = [.requested, + .confirmed, + .driverEnRoute, + .arrived, + .passengerOnBoard] + + let testPayload = TripSearch(tripStates: tripStates) + testObject.set(tripSearch: testPayload) + testObject.execute(callback: {(_: Result<[TripInfo]>) in }) + + mockTripSearchRequest.assertRequestSendAndDecoded(endpoint: APIEndpoint.tripSearch, + method: .post, + payload: testPayload) + } + + /** + * When: The request succeeds + * Then: Callback should contain expected result + */ + func testRequestSuccess() { + var capturedCallback: Result<[TripInfo]>? + + testObject.set(tripSearch: TripSearch()) + testObject.execute(callback: { capturedCallback = $0 }) + + let mockResponse = BookingSearch(trips: [TripInfoMock().set(tripId: "123").build(), + TripInfoMock().set(tripId: "456").build()]) + mockTripSearchRequest.triggerSuccessWithDecoded(value: mockResponse) + + XCTAssertEqual("123", capturedCallback?.successValue()![0].tripId) + XCTAssertEqual("456", capturedCallback?.successValue()![1].tripId) + } + + /** + * When: Request fails + * Then: Error should be in callback + */ + func testRequestFails() { + let error = TestUtil.getRandomError() + var capturedCallback: Result<[TripInfo]>? + + testObject.set(tripSearch: TripSearch()) + testObject.execute(callback: { capturedCallback = $0 }) + + mockTripSearchRequest.triggerFail(error: error) + + XCTAssertNotNil(capturedCallback?.errorValue()) + XCTAssert(error.equals(capturedCallback!.errorValue())) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Trips/TripStatusUpdate/KarhooTripStatusInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/Trips/TripStatusUpdate/KarhooTripStatusInteractorSpec.swift new file mode 100644 index 00000000..da2508cf --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Trips/TripStatusUpdate/KarhooTripStatusInteractorSpec.swift @@ -0,0 +1,78 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooTripStatusInteractorSpec: XCTestCase { + + private var mockTripStatusRequest: MockRequestSender! + private var testObject: KarhooTripStatusInteractor! + + let tripId = "TRIP_ID" + + override func setUp() { + super.setUp() + + mockTripStatusRequest = MockRequestSender() + testObject = KarhooTripStatusInteractor(tripId: tripId, + requestSender: mockTripStatusRequest) + } + + /** + * When: Making a request to get trip + * Then: Expected method, path and payload should be set + */ + + func testRequestFormat() { + testObject.execute(callback: { (_: Result) in }) + mockTripStatusRequest.assertRequestSendAndDecoded(endpoint: .tripStatus(identifier: tripId), + method: .get) + XCTAssertNil(mockTripStatusRequest.payloadSet?.encode()) + } + + /** + * Given: Requesting status for a trip + * When: The request succeeds + * Then: Callback should be a success with a trip state result + */ + func testRequestSuccess() { + let successResult = TripStatusMock().set(state: .confirmed).build() + + var capturedCallback: Result? + testObject.execute(callback: { capturedCallback = $0 }) + + mockTripStatusRequest.triggerSuccessWithDecoded(value: successResult) + + XCTAssertEqual(.confirmed, capturedCallback!.successValue()!) + } + + /** + * Given: Requesting status for a trip + * When: The request fails + * Then: Callback should be a fail with expected error + */ + func testRequestFailure() { + let expectedError = TestUtil.getRandomError() + + var capturedCallback: Result? + testObject.execute(callback: { capturedCallback = $0 }) + + mockTripStatusRequest.triggerFail(error: expectedError) + + XCTAssert(expectedError.equals(capturedCallback!.errorValue())) + } + + /** + * When: Cancelling the request for trip status + * Then: TripStatusRequest should cancel + */ + func testCancelRequest() { + testObject.cancel() + XCTAssertTrue(mockTripStatusRequest.cancelNetworkRequestCalled) + } +} diff --git a/KarhooSDKTests/TestCases/Service/Trips/TripUpdate/KarhooTripUpdateInteractortSpec.swift b/KarhooSDKTests/TestCases/Service/Trips/TripUpdate/KarhooTripUpdateInteractortSpec.swift new file mode 100644 index 00000000..ad1032b6 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/Trips/TripUpdate/KarhooTripUpdateInteractortSpec.swift @@ -0,0 +1,77 @@ +// +// KarhooTripUpdateInteractortSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +class KarhooTripUpdateInteractortSpec: XCTestCase { + + private var mockTripUpdateRequest: MockRequestSender! + private var testObject: KarhooTripUpdateInteractor! + let requestedTripId = "123" + let tripId = "TRIP_ID" + + override func setUp() { + super.setUp() + mockTripUpdateRequest = MockRequestSender() + testObject = KarhooTripUpdateInteractor(tripId: tripId, + requestSender: mockTripUpdateRequest) + } + + /** + * When: Making a request to get trip + * Then: Expected method, path and payload should be set + */ + + func testRequestFormat() { + testObject.execute(callback: { (_: Result) in }) + mockTripUpdateRequest.assertRequestSendAndDecoded(endpoint: .trackTrip(identifier: tripId), + method: .get) + } + + /** + * When: Request returns a full trip as expected + * Then: Callback should contain said trip + */ + func testRequestSuccess() { + + var capturedCallback: Result? + testObject.execute(callback: { capturedCallback = $0 }) + + let mockResponse = TripInfoMock().set(tripId: "ABC123").build() + mockTripUpdateRequest.triggerSuccessWithDecoded(value: mockResponse) + + XCTAssertEqual(capturedCallback!.successValue()!.tripId, "ABC123") + } + + /** + * When: Request fails + * Then: Callback should contain http response error + */ + func testRequestFailure() { + var capturedCallback: Result? + testObject.execute(callback: { capturedCallback = $0 }) + + let error = HTTPError(statusCode: 402, errorType: .badServerResponse) + mockTripUpdateRequest.triggerFail(error: error) + + XCTAssertEqual(error, capturedCallback!.errorValue() as? HTTPError) + } + + /** + * When: Canceling a request + * Then: Cancel network was called + */ + + func testCancelNetworkRequest() { + testObject.cancel() + XCTAssert(mockTripUpdateRequest.cancelNetworkRequestCalled) + } +} diff --git a/KarhooSDKTests/TestCases/Service/User/KarhooUserServiceSpec.swift b/KarhooSDKTests/TestCases/Service/User/KarhooUserServiceSpec.swift new file mode 100644 index 00000000..3b9cddd5 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/User/KarhooUserServiceSpec.swift @@ -0,0 +1,253 @@ +// +// KarhooUserServiceSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +final class KarhooUserServiceSpec: XCTestCase { + + private var mockUserDataStore: MockUserDataStore! + private var mockRegisterInteractor: MockRegisterInteractor! + private var mockLogoutInteractor: MockLogoutInteractor! + private var mockPasswordResetInteractor: MockPasswordResetInteractor! + private var mockLoginInteractor: MockLoginInteractor! + private var mockUpdateUserDetailsInteractor: MockUpdateuserDetailsInteractor! + + private var testObject: UserService! + + override func setUp() { + super.setUp() + + mockUserDataStore = MockUserDataStore() + mockRegisterInteractor = MockRegisterInteractor() + mockLogoutInteractor = MockLogoutInteractor() + mockPasswordResetInteractor = MockPasswordResetInteractor() + mockLoginInteractor = MockLoginInteractor() + mockUpdateUserDetailsInteractor = MockUpdateuserDetailsInteractor() + + testObject = KarhooUserService(userDataStore: mockUserDataStore, + loginInteractor: mockLoginInteractor, + registerInteractor: mockRegisterInteractor, + passwordResetInteractor: mockPasswordResetInteractor, + logoutInteractor: mockLogoutInteractor, + updateUserDetailsInteractor: mockUpdateUserDetailsInteractor) + } + + /** + * Given: A email and password + * When: Trying to log in + * Then: It should request a login from the login data accessor + */ + func testLogin() { + let userLogin = UserLogin(username: "name", password: "password") + testObject.login(userLogin: userLogin).execute(callback: { _ in}) + + XCTAssertEqual(userLogin.encode(), mockLoginInteractor.userLoginSet?.encode()) + } + + /** + * Given: A login attempt has been made + * When: The login attempt succeeds + * Then: It should pass the information along to the caller + */ + func testLoginSuccessful() { + var returnedUser: UserInfo? + let userLogin = UserLogin(username: "some", password: "some") + testObject.login(userLogin: userLogin).execute(callback: { (result: Result) in + returnedUser = result.successValue() + }) + + let user = UserInfoMock().set(userId: "some").build() + mockLoginInteractor.triggerSuccess(result: user) + + XCTAssertEqual(user.encode(), returnedUser?.encode()) + } + + /** + * Given: A login attempt has been made + * When: The login attempt fails + * Then: It should pass the error along to the caller + */ + func testLoginFailure() { + let expectedError = TestUtil.getRandomError() + + let userLogin = UserLogin(username: "some", password: "some") + var result: Result? + testObject.login(userLogin: userLogin) + .execute(callback: { result = $0 }) + + mockLoginInteractor.triggerFail(error: expectedError) + XCTAssert(expectedError.equals(result!.errorValue())) + } + + /** + * Given: A logout attempt has been made + * When: The logout attempt succeeds + * Then: It should pass the information along to the caller + */ + func testLogoutSuccessResponse() { + var result: Result? + testObject.logout().execute(callback: { result = $0 }) + + mockLogoutInteractor.triggerSuccess(result: KarhooVoid()) + + XCTAssertTrue(result?.isSuccess() == true) + } + + /** + * Given: A logout attempt has been made + * When: The logout attempt fails + * Then: It should pass the error along to the caller + */ + func testLogoutErrorResponse() { + var result: Result? + testObject.logout().execute(callback: { result = $0 }) + + let expectedError = TestUtil.getRandomError() + mockLogoutInteractor.triggerFail(error: expectedError) + + XCTAssertFalse(result!.isSuccess()) + XCTAssert(expectedError.equals(result!.errorValue())) + } + + /** + * Given: No logged in user + * When: Getting the current user + * Then: It should pass nil to the caller + */ + func testNoLoggedInUser() { + let user = testObject.getCurrentUser() + XCTAssertNil(user) + } + + /** + * Given: A logged in user + * When: Getting the current user + * Then: It should pass the user to the caller + */ + func testLoggedInUser() { + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + let returnedUser = testObject.getCurrentUser() + XCTAssertEqual(user, returnedUser) + } + + /** + * When: Attempting to create a user + * Then: The sign up service should be called with the correct arguments + */ + func testRegister() { + let userRegistration = UserRegistrationMock().build() + let successMock = UserInfoMock().set(userId: "some").build() + + var registerResult: Result? + testObject.register(userRegistration: userRegistration) + .execute(callback: { registerResult = $0 }) + + XCTAssertEqual(userRegistration.encode(), mockRegisterInteractor.userRegistrationSet?.encode()) + + mockRegisterInteractor.triggerSuccess(result: successMock) + + XCTAssertEqual(successMock, registerResult?.successValue()) + } + + /** + * Given: Resetting password + * When: PasswordResetInteractor succeeds + * Then: Expected callback should be propagated + */ + func testPasswordResetSuccess() { + var passwordResult: Result? + testObject.passwordReset(email: "some") + .execute(callback: { passwordResult = $0 }) + + mockPasswordResetInteractor.triggerSuccess(result: KarhooVoid()) + XCTAssertTrue(passwordResult!.isSuccess()) + } + + /** + * Given: Resetting password + * When: PasswordResetInteractor fails + * Then: Expected callback should be propagated + */ + func testPasswordResetFails() { + let testError = TestUtil.getRandomError() + + var result: Result? + testObject.passwordReset(email: "some") + .execute(callback: { result = $0 }) + + mockPasswordResetInteractor.triggerFail(error: testError) + + XCTAssertFalse(result!.isSuccess()) + XCTAssert(testError.equals(result!.errorValue())) + } + + /** + * Given: Updating Profile + * When: UpdateUserDetailsInteractor succeeds + * Then: The updated details should be sent + */ + func testUpdateUserDetailsSuccess() { + let request = UserUpdateMock().set(firstName: "FirstName").build() + let response = UserInfoMock().set(firstName: "Response").build() + var updateResult: Result? + + testObject.updateUserDetails(update: request) + .execute(callback: { updateResult = $0 }) + + mockUpdateUserDetailsInteractor.triggerSuccess(result: response) + XCTAssertTrue(updateResult!.isSuccess()) + XCTAssertEqual(updateResult?.successValue()?.firstName, "Response") + } + + /** + * Given: Updating Profile + * When: UpdateUserDetailsInteractor fails + * Then: Error should be sent + */ + func testUpdateUserDetailsFails() { + let request = UserUpdateMock().set(firstName: "FirstName").build() + let expectedError = TestUtil.getRandomError() + var updateResult: Result? + + testObject.updateUserDetails(update: request) + .execute(callback: { updateResult = $0 }) + + mockUpdateUserDetailsInteractor.triggerFail(error: expectedError) + XCTAssertFalse(updateResult!.isSuccess()) + } + + /** + * When: Adding a observer + * Then: The observer should be added to the data store + */ + func testAddingObserver() { + let observer = TestObserver() + testObject.add(observer: observer) + + XCTAssert(mockUserDataStore.addedObserver === observer) + } + + /** + * When: Removing a observer + * Then: The observer should be removed from the data store + */ + func testRemovingObserver() { + let observer = TestObserver() + testObject.remove(observer: observer) + + XCTAssert(mockUserDataStore.removedObserver === observer) + } +} + +private class TestObserver: UserStateObserver { + func userStateUpdated(user: UserInfo?) {} +} diff --git a/KarhooSDKTests/TestCases/Service/User/Logout/KarhooLogoutInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/User/Logout/KarhooLogoutInteractorSpec.swift new file mode 100644 index 00000000..90d0e026 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/User/Logout/KarhooLogoutInteractorSpec.swift @@ -0,0 +1,73 @@ +// +// KarhooLogoutInteractorSpec.swift +// KarhooSDK +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import XCTest + +@testable import KarhooSDK + +final class KarhooLogoutInteractorSpec: XCTestCase { + + private var mockAnalytics: MockAnalyticsService! + private var mockUserDataStore: MockUserDataStore! + private var testObject: KarhooLogoutInteractor! + + override func setUp() { + super.setUp() + + mockUserDataStore = MockUserDataStore() + mockAnalytics = MockAnalyticsService() + + testObject = KarhooLogoutInteractor(userDataStore: mockUserDataStore, + analytics: mockAnalytics) + } + + /** + * Given: The user is logged in + * When: Trying to log out + * Then: The users credentials should be removed + * And: Logout analytics event should be fired + * And: Logout callback should be called + */ + func testUserLoggedInLogOut() { + var result: Result? + let user = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = user + + testObject.execute(callback: { result = $0 }) + + XCTAssertTrue(mockUserDataStore.removeUserCalled) + XCTAssertEqual(mockAnalytics.eventSent, AnalyticsConstants.EventNames.userLoggedOut) + XCTAssertTrue(result!.isSuccess()) + } + + /** + * Given: User is NOT logged in + * When: Trying to log out + * Then: The logout should fail + * And: Logout anlaytics event should NOT be fired + */ + func testUserLoggedOutLogOut() { + var result: Result? + testObject.execute(callback: { result = $0 }) + + XCTAssertFalse(mockUserDataStore.removeUserCalled) + XCTAssertFalse(result!.isSuccess()) + } + + /** + * Given: No user logged in + * When: Logging out + * Then: An error should be returned + */ + func testNoUser() { + var result: Result? + testObject.execute(callback: { result = $0 }) + + XCTAssert(result?.isSuccess() == false) + } +} diff --git a/KarhooSDKTests/TestCases/Service/User/New Group/KarhooLoginInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/User/New Group/KarhooLoginInteractorSpec.swift new file mode 100644 index 00000000..ea5e3499 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/User/New Group/KarhooLoginInteractorSpec.swift @@ -0,0 +1,292 @@ +// +// KarhooLoginInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +class KarhooLoginInteractorSpec: XCTestCase { + private var mockLoginRequestSender: MockRequestSender! + private var mockProfileRequestSender: MockRequestSender! + private var mockAnalytics: MockAnalyticsService! + private var mockUserDataStore: MockUserDataStore! + private var mockGetNonceRequestSender: MockRequestSender! + private var testObject: KarhooLoginInteractor! + + override func setUp() { + super.setUp() + + mockAnalytics = MockAnalyticsService() + mockLoginRequestSender = MockRequestSender() + mockProfileRequestSender = MockRequestSender() + mockUserDataStore = MockUserDataStore() + mockGetNonceRequestSender = MockRequestSender() + + testObject = KarhooLoginInteractor(userDataStore: mockUserDataStore, + loginRequestSender: mockLoginRequestSender, + profileRequestSender: mockProfileRequestSender, + analytics: mockAnalytics, + nonceRequestSender: mockGetNonceRequestSender) + } + + /** + * When: Cancelling login + * Then: login request and profile requests should be cancelled + */ + func testCancel() { + testObject.cancel() + XCTAssertTrue(mockLoginRequestSender.cancelNetworkRequestCalled) + XCTAssertTrue(mockProfileRequestSender.cancelNetworkRequestCalled) + } + + /** + * Given: The user which we attempt to log in is already logged in + * When: Logging in + * Then: The currently logged in user should be returned + */ + func testLoginExistingUser() { + let existingUser = UserInfoMock().set(userId: "some").build() + let userLogin = UserLogin(username: existingUser.email, password: "some") + + mockUserDataStore.userToReturn = existingUser + + var result: Result? + testObject.set(userLogin: userLogin) + testObject.execute(callback: { result = $0 }) + + XCTAssertEqual(existingUser, result?.successValue()) + } + + /** + * Given: Another user than the one we attempt to log in is already + * logged in + * When: Logging in + * Then: The caller should get an error that there is another user + * currently logged in + */ + func testLoginOtherUser() { + let existingUser = UserInfoMock().set(userId: "some").build() + mockUserDataStore.userToReturn = existingUser + + var returnedError: Error? + let userLogin = UserLogin(username: "some other email", password: "some") + testObject.set(userLogin: userLogin) + testObject.execute(callback: { (result: Result) in + returnedError = result.errorValue() + }) + + XCTAssertNotNil(returnedError) + } + + /** + * Given: No currently logged in user + * When: Logging in + * Then: A login request should be sent + */ + func testNoLoggedInUser() { + let userLogin = UserLogin(username: "email@email.com", password: "password") + + testObject.set(userLogin: userLogin) + testObject.execute(callback: { (_: Result) in }) + + mockLoginRequestSender.assertRequestSendAndDecoded(endpoint: .login, + method: .post, + payload: userLogin) + } + + /** + * Given: A sent login request + * When: The request succeeds + * Then: The profile request succeeds + * And: The user HAS the trip admin role + * Then: Credentials should be saved to datas store + * And: The user should be stored + * And: Analytics should fire with the user + */ + func testRoleAuthorised() { + [Organisation(id: "some", name: "some", roles: ["MOBILE_USER", "USER"]), + Organisation(id: "some", name: "some", roles: ["MOBILE_USER", "TRIP_ADMIN", "USER"]), + Organisation(id: "some", name: "some", roles: ["TRIP_ADMIN", "USER"])].forEach({ org in + loginAndProfileAssertSuccess(organisations: [org]) + }) + } + + private func loginAndProfileAssertSuccess(organisations: [Organisation]) { + var returnedUser: UserInfo? + + let userLogin = UserLogin(username: "email", password: "password") + + testObject.set(userLogin: userLogin) + testObject.execute(callback: { (result: Result) in + returnedUser = result.successValue() + }) + + let loginResult = AuthTokenMock().set(accessToken: "some_token") + .set(refreshToken: "some_refresh_token") + .set(expiresIn: 60) + .build() + + mockLoginRequestSender.triggerSuccessWithDecoded(value: loginResult) + + let userMock = UserInfoMock().set(userId: "123").set(organisation: organisations) + + mockProfileRequestSender.triggerSuccessWithDecoded(value: userMock.build()) + + XCTAssertEqual(userMock.build(), returnedUser) + XCTAssertEqual(mockUserDataStore.credentialsToSet?.accessToken, "some_token") + XCTAssertEqual(userMock.build(), mockUserDataStore.storedUser) + + XCTAssertEqual(mockUserDataStore.credentialsToSet?.accessToken, "some_token") + } + + /** + * Given: A sent login request + * When: The request succeeds + * Then: The profile request succeeds + * And: The user doesn't have the trip admin role + * Then: Credentials should be set + * And: logged in analytics event should not be fired + * And: Callbackshould be a fail with an unauthorised error + */ + func testUnauthorisedRoleLoginAttempt() { + + let userLogin = UserLogin(username: "email", password: "password") + + var result: Result? + testObject.set(userLogin: userLogin) + testObject.execute(callback: { result = $0 }) + + let loginResult = AuthTokenMock().set(accessToken: "some_token") + .set(refreshToken: "some_refresh_token") + .set(expiresIn: 60) + .build() + + mockLoginRequestSender.triggerSuccessWithDecoded(value: loginResult) + + let unauthorizedOrganisationRole = Organisation(id: "some", name: "some", roles: ["USER"]) + + let userMock = UserInfoMock().set(userId: "123").set(organisation: [unauthorizedOrganisationRole]) + + mockProfileRequestSender.triggerSuccessWithDecoded(value: userMock.build()) + + XCTAssertEqual(mockUserDataStore.credentialsToSet?.accessToken, "some_token") + XCTAssertNil(mockUserDataStore.storedUser) + XCTAssertEqual(.missingUserPermission, result?.errorValue()?.type) + XCTAssertNil(result?.successValue()) + XCTAssert(result?.isSuccess() == false) + } + + /** + * Given: A sent login request + * When: The request succeeds + * Then: Credentials should be set + * And: The profile request fails + * Then: callback should fail + * And: analytics shouldn't be fired + */ + func testSuccessfulLoginAndProfileFetchFail() { + let expectedError = TestUtil.getRandomError() + + let userLogin = UserLogin(username: "email", password: "password") + var result: Result? + testObject.set(userLogin: userLogin) + testObject.execute(callback: { result = $0 }) + + let loginResult = AuthTokenMock().set(accessToken: "some_token") + .set(refreshToken: "some_refresh_token") + .set(expiresIn: 60) + .build() + + mockLoginRequestSender.triggerSuccessWithDecoded(value: loginResult) + + XCTAssertEqual(mockUserDataStore.credentialsToSet?.accessToken, "some_token") + + mockProfileRequestSender.triggerFail(error: expectedError) + + XCTAssert(expectedError.equals(result!.errorValue())) + } + + /** + * Given: A sent login request + * When: The request fails + * Then: The caller should be informed + * And: Profile fetch should NOT be made + * And: No credentials should be saved + */ + func testFailedLogin() { + let expectedError = TestUtil.getRandomError() + let userLogin = UserLogin(username: "email", password: "password") + + var result: Result? + testObject.set(userLogin: userLogin) + testObject.execute(callback: { result = $0 }) + + mockLoginRequestSender.triggerFail(error: expectedError) + + XCTAssertNil(mockUserDataStore.credentialsToSet) + XCTAssertFalse(mockProfileRequestSender.requestCalled) + XCTAssert(expectedError.equals(result!.errorValue())) + } + + /** + * Given: Login successful + * When: Nonce succeeds + * Then: User should be updated + */ + func testGetNonceSuccessAfterLogin() { + let successNonce = Nonce(nonce: "some_nonce", + cardType: "Visa", + lastFour: "1234") + + triggerSuccessfulLoginAndProfileFetch() + + XCTAssertTrue(mockGetNonceRequestSender.requestAndDecodeCalled) + + mockGetNonceRequestSender.triggerSuccessWithDecoded(value: successNonce) + + XCTAssertTrue(mockUserDataStore.updateCurrentNonceCalled) + XCTAssertEqual("some_nonce", successNonce.nonce) + } + + /** + * Given: Login successful + * When: Nonce fails + * Then: User should be updated + */ + func testGetNonceFailsAfterSuccessfulLogin() { + triggerSuccessfulLoginAndProfileFetch() + + XCTAssertTrue(mockGetNonceRequestSender.requestAndDecodeCalled) + + mockGetNonceRequestSender.triggerFail(error: TestUtil.getRandomError()) + + XCTAssertTrue(mockUserDataStore.updateCurrentNonceCalled) + XCTAssertNil(mockUserDataStore.updateCurrentNonce) + } + + private func triggerSuccessfulLoginAndProfileFetch() { + let userLogin = UserLogin(username: "email", password: "password") + + testObject.set(userLogin: userLogin) + testObject.execute(callback: { (_: Result) in }) + + let loginResult = AuthTokenMock().set(accessToken: "some_token") + .set(refreshToken: "some_refresh_token") + .set(expiresIn: 60) + .build() + + mockLoginRequestSender.triggerSuccessWithDecoded(value: loginResult) + + let authorizedOrganisationRole = Organisation(id: "some", name: "some", roles: ["TRIP_ADMIN"]) + + let userMock = UserInfoMock().set(userId: "123").set(organisation: [authorizedOrganisationRole]) + + mockProfileRequestSender.triggerSuccessWithDecoded(value: userMock.build()) + } +} diff --git a/KarhooSDKTests/TestCases/Service/User/PasswordReset/KarhooPasswordResetInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/User/PasswordReset/KarhooPasswordResetInteractorSpec.swift new file mode 100644 index 00000000..82ade190 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/User/PasswordReset/KarhooPasswordResetInteractorSpec.swift @@ -0,0 +1,69 @@ +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooPasswordResetInteractorSpec: XCTestCase { + + private var mockPasswordResetRequest: MockRequestSender! + private var testObject: KarhooPasswordResetInteractor! + + override func setUp() { + super.setUp() + + mockPasswordResetRequest = MockRequestSender() + testObject = KarhooPasswordResetInteractor(requestSender: mockPasswordResetRequest) + } + + /** + * When: Making a password reset request + * Then: Expected method, path and payload should be set + */ + func testRequestFormat() { + let testPayload = PasswordResetRequestPayloadMock().set(email: "some_email").build() + + testObject.set(email: "some_email") + testObject.execute(callback: { (_: Result) in }) + + mockPasswordResetRequest.assertRequestSend(endpoint: .passwordReset, + payload: testPayload) + } + + /** + * Given: Resetting password + * When: Resetting password request succeeds + * Then: Callback should be success + */ + func testPasswordResetRequestSucceeds() { + var result: Result? + testObject.set(email: "some_email") + testObject.execute(callback: { result = $0 }) + + mockPasswordResetRequest.triggerSuccess(response: KarhooVoid().encode()!) + + XCTAssert(result!.isSuccess()) + } + + /** + * Given: Resetting password + * When: Resetting password request fails + * Then: Callback should be a fail + */ + func testPasswordResetRequestFails() { + let expectedError = TestUtil.getRandomError() + + var result: Result? + testObject.set(email: "some_email") + testObject.execute(callback: { result = $0 }) + + mockPasswordResetRequest.triggerFail(error: expectedError) + + XCTAssert(expectedError.equals(result!.errorValue())) + XCTAssertFalse(result!.isSuccess()) + } +} diff --git a/KarhooSDKTests/TestCases/Service/User/Register/KarhooRegisterInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/User/Register/KarhooRegisterInteractorSpec.swift new file mode 100644 index 00000000..bd4798e1 --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/User/Register/KarhooRegisterInteractorSpec.swift @@ -0,0 +1,79 @@ +// +// KarhooRegisterInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooRegisterInteractorSpec: XCTestCase { + + private var testObject: KarhooRegisterInteractor! + private var mockUserRegisterRequest: MockRequestSender! + + private var testUserRegistration: UserRegistration { + return UserRegistration(firstName: "first", + lastName: "last", + email: "email", + phoneNumber: "phone", + locale: "locale", + password: "password") + } + + override func setUp() { + super.setUp() + mockUserRegisterRequest = MockRequestSender() + testObject = KarhooRegisterInteractor(requestSender: mockUserRegisterRequest) + } + + /** + * When: sending request + * Then: Expected payload and path should be set + */ + func testRequestFormat() { + testObject.set(userRegistration: testUserRegistration) + testObject.execute(callback: { (_: Result) -> Void in}) + + mockUserRegisterRequest.assertRequestSendAndDecoded(endpoint: .register, + method: .post, + payload: testUserRegistration) + } + + /** + * When: Sign up request succeeds + * Then: expected callback should be propogated + */ + func testRequestSucceeds() { + let expectedResult = UserInfoMock().set(userId: "some").build() + + var result: Result? + testObject.set(userRegistration: testUserRegistration) + testObject.execute(callback: { result = $0}) + + mockUserRegisterRequest.triggerSuccessWithDecoded(value: expectedResult) + XCTAssertTrue(result!.isSuccess()) + XCTAssertEqual(expectedResult, result?.successValue()) + } + + /** + * When: Sign up request fais + * Then: expected callback should be propogated + */ + func testRequestFails() { + let expectedError = TestUtil.getRandomError() + + var result: Result? + testObject.set(userRegistration: testUserRegistration) + testObject.execute(callback: { result = $0}) + + mockUserRegisterRequest.triggerFail(error: expectedError) + + XCTAssertFalse(result!.isSuccess()) + XCTAssert(expectedError.equals(result!.errorValue())) + } +} diff --git a/KarhooSDKTests/TestCases/Service/User/Update/KarhooUpdateUserDetailsInteractorSpec.swift b/KarhooSDKTests/TestCases/Service/User/Update/KarhooUpdateUserDetailsInteractorSpec.swift new file mode 100644 index 00000000..1388c07c --- /dev/null +++ b/KarhooSDKTests/TestCases/Service/User/Update/KarhooUpdateUserDetailsInteractorSpec.swift @@ -0,0 +1,88 @@ +// +// KarhooUserUpdateProfileInteractorSpec.swift +// KarhooSDKTests +// +// +// Copyright © 2020 Karhoo. All rights reserved. +// + +import Foundation +import XCTest + +@testable import KarhooSDK + +final class KarhooUpdateUserDetailsInteractorSpec: XCTestCase { + + private var testObject: KarhooUpdateUserDetailsInteractor! + private var mockUpdateSender: MockRequestSender! + private var mockUserDataStore = MockUserDataStore() + private var mockAnalytics = MockAnalyticsService() + + private var testUserProfileUpdate: UserDetailsUpdateRequest { + return UserDetailsUpdateRequest(firstName: "FirstName", + lastName: "Karhoo", + phoneNumber: "0w0", + locale: "locale", + avatarURL: "avatar") + } + + override func setUp() { + super.setUp() + mockUpdateSender = MockRequestSender() + mockUserDataStore.userToReturn = UserInfoMock().set(userId: "some").build() + testObject = KarhooUpdateUserDetailsInteractor(requestSender: mockUpdateSender, + userDataStore: mockUserDataStore, + analyticsService: mockAnalytics) + } + + /** + * When: sending request + * Then: Expected payload and path should be set + */ + func testUpdateFormat() { + testObject.set(update: testUserProfileUpdate) + testObject.execute(callback: { (_: Result) -> Void in}) + mockUpdateSender.assertRequestSendAndDecoded(endpoint: .userProfileUpdate(identifier: "some"), + method: .put, + payload: testUserProfileUpdate) + } + + /** + * When: Update request succeeds + * Then: expected callback should be propogated + * And: New user should be persisted + */ + func testUpdateSucceeds() { + let expectedResult = UserInfoMock().set(userId: "some").build() + + var result: Result? + testObject.set(update: testUserProfileUpdate) + testObject.execute(callback: { result = $0}) + + mockUpdateSender.triggerSuccessWithDecoded(value: expectedResult) + XCTAssertTrue(result!.isSuccess()) + XCTAssertEqual(expectedResult, result?.successValue()) + XCTAssertTrue(mockUserDataStore.updateUserCalled) + XCTAssertEqual(mockUserDataStore.updateUser?.userId, expectedResult.userId) + XCTAssertEqual(mockAnalytics.eventSent, AnalyticsConstants.EventNames.userProfileUpdateSuccess) + } + + /** + * When: update request fails + * Then: expected callback should be propogated + * And: user data store should not update any user + */ + func testUdpateFails() { + let expectedError = TestUtil.getRandomError() + + var result: Result? + testObject.set(update: testUserProfileUpdate) + testObject.execute(callback: { result = $0}) + + mockUpdateSender.triggerFail(error: expectedError) + + XCTAssertFalse(result!.isSuccess()) + XCTAssertFalse(mockUserDataStore.updateUserCalled) + XCTAssert(expectedError.equals(result!.errorValue())) + } +} diff --git a/KarhooSDKTests/UnitTestSetup.swift b/KarhooSDKTests/UnitTestSetup.swift new file mode 100644 index 00000000..e5429de9 --- /dev/null +++ b/KarhooSDKTests/UnitTestSetup.swift @@ -0,0 +1,17 @@ +// +// UnitTestSetup.swift +// KarhooSDKTests +// +// +// Copyright @ 2020 Karhoo. All rights reserved. +// + +import Foundation +@testable import KarhooSDK + +final class UnitTestSetup: NSObject { + + override init() { + Karhoo.set(configuration: MockSDKConfig()) + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8bbc7d55 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2020, Flit Technologies Ltd. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Podfile b/Podfile new file mode 100644 index 00000000..93a8f172 --- /dev/null +++ b/Podfile @@ -0,0 +1,38 @@ +# Karhoo private pods +# source 'git@github.com:karhoo/KarhooPods.git' + +# Standard cocoapods specs source +# source 'https://github.com/CocoaPods/Specs.git' + +platform :ios, '10.0' +use_frameworks! + +def sdkPods + pod 'SwiftLint' + pod 'KeychainSwift', '12.0.0' + pod 'ReachabilitySwift', '5.0.0' +end + +target 'KarhooSDK' do + inhibit_all_warnings! + sdkPods +end + +target 'Client' do + sdkPods +end + +target 'KarhooSDKTests' do +end + +target 'KarhooSDKIntegrationTests' do + pod 'OHHTTPStubs/Swift', '8.0.0' +end + +post_install do |installer_representation| + installer_representation.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO' + end + end +end diff --git a/Podfile.lock b/Podfile.lock new file mode 100644 index 00000000..efe52817 --- /dev/null +++ b/Podfile.lock @@ -0,0 +1,40 @@ +PODS: + - KeychainSwift (12.0.0) + - OHHTTPStubs/Core (8.0.0) + - OHHTTPStubs/Default (8.0.0): + - OHHTTPStubs/Core + - OHHTTPStubs/JSON + - OHHTTPStubs/NSURLSession + - OHHTTPStubs/OHPathHelpers + - OHHTTPStubs/JSON (8.0.0): + - OHHTTPStubs/Core + - OHHTTPStubs/NSURLSession (8.0.0): + - OHHTTPStubs/Core + - OHHTTPStubs/OHPathHelpers (8.0.0) + - OHHTTPStubs/Swift (8.0.0): + - OHHTTPStubs/Default + - ReachabilitySwift (5.0.0) + - SwiftLint (0.39.1) + +DEPENDENCIES: + - KeychainSwift (= 12.0.0) + - OHHTTPStubs/Swift (= 8.0.0) + - ReachabilitySwift (= 5.0.0) + - SwiftLint + +SPEC REPOS: + trunk: + - KeychainSwift + - OHHTTPStubs + - ReachabilitySwift + - SwiftLint + +SPEC CHECKSUMS: + KeychainSwift: d5e776578587ee5958ce36601df22f168f65138a + OHHTTPStubs: 9cbce6364bec557cc3439aa6bb7514670d780881 + ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + SwiftLint: 55e96a4a4d537d4a3156859fc1c54bd24851a046 + +PODFILE CHECKSUM: df7b63524fd630a103081ecdd10c67d496dce5eb + +COCOAPODS: 1.8.4 diff --git a/README.md b/README.md index 40e95189..63bb1bd1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,20 @@ -# karhoo-ios-sdk -iOS Network SDK for Karhoo +# Karhoo-iOS-SDK + +![](https://github.com/karhoo/Karhoo-iOS-SDK/workflows/Develop%20Branch%20CI/badge.svg) + +KarhooSDK is a framework for integrating with the KarhooAPI: https://developer.karhoo.com + +To setup the project, simply clone, pod install and run the unit tests to verify everything works. + +# SDK Architecture overview: + +The SDK is split into Services. Services such as Trip, DriverTracking, Availability, User, Payments etc. These services in turn depend on Interactors, these Interactors depend on a request. Simply calling a function in a service will trigger the interactor to call the request. The request uses our HttpClient to make the network call. + +Call and PollCall objects are returned from the KarhooSDK. PollCalls return observables which can be used to poll the endpoint, and Calls have an execute function which fires the request. + +Models are encoded/decoded using Decodable structs. These are located in Api/DataObjects/Request /Response groups. + +There is a unit test target that tests individual classes work as expected, and there is an integration test target that uses OHHTPStubs to test the sdk works as expected end to end. (from the network layer to a service) + +![](docs/assets/network_sdk.png) + diff --git a/build-framework.sh b/build-framework.sh new file mode 100644 index 00000000..8bf12286 --- /dev/null +++ b/build-framework.sh @@ -0,0 +1,92 @@ + + +#! /bin/sh -e +# This script demonstrates archive and create action on frameworks and libraries +# +# @author Boris Bielik + +# Release dir path +OUTPUT_DIR_PATH=$1 + +if [[ -z $1 ]]; then + echo "Output dir was not set. try to run ./scripts/create_xcframeworks.sh Products" + exit 1; +fi + +function archivePathSimulator { + local DIR=${OUTPUT_DIR_PATH}/archives/"${1}-SIMULATOR" + echo "${DIR}" +} + +function archivePathDevice { + local DIR=${OUTPUT_DIR_PATH}/archives/"${1}-DEVICE" + echo "${DIR}" +} + +# Archive takes 3 params +# +# 1st == SCHEME +# 2nd == destination +# 3rd == archivePath +function archive { + echo "▸ Starts archiving the scheme: ${1} for destination: ${2};\n▸ Archive path: ${3}.xcarchive" + xcodebuild archive \ + -workspace KarhooSDK.xcworkspace \ + -scheme ${1} \ + -destination "${2}" \ + -archivePath "${3}" \ + SKIP_INSTALL=NO \ + BUILD_LIBRARY_FOR_DISTRIBUTION=YES | xcpretty +} + +# Builds archive for iOS simulator & device +function buildArchive { + SCHEME=${1} + + archive $SCHEME "generic/platform=iOS Simulator" $(archivePathSimulator $SCHEME) + archive $SCHEME "generic/platform=iOS" $(archivePathDevice $SCHEME) +} + +# Creates xc framework +function createXCFramework { + FRAMEWORK_ARCHIVE_PATH_POSTFIX=".xcarchive/Products/Library/Frameworks" + FRAMEWORK_SIMULATOR_DIR="$(archivePathSimulator $1)${FRAMEWORK_ARCHIVE_PATH_POSTFIX}" + FRAMEWORK_DEVICE_DIR="$(archivePathDevice $1)${FRAMEWORK_ARCHIVE_PATH_POSTFIX}" + + xcodebuild -create-xcframework \ + -framework ${FRAMEWORK_SIMULATOR_DIR}/${1}.framework \ + -framework ${FRAMEWORK_DEVICE_DIR}/${1}.framework \ + -output ${OUTPUT_DIR_PATH}/xcframeworks/${1}.xcframework +} + +### Static Libraries cant be turned into frameworks +function createXCFrameworkForStaticLibrary { + + LIBRARY_ARCHIVE_PATH_POSTFIX=".xcarchive/Products/usr/local/lib" + LIBRARY_SIMULATOR_DIR="$(archivePathSimulator $1)${LIBRARY_ARCHIVE_PATH_POSTFIX}" + LIBRARY_DEVICE_DIR="$(archivePathDevice $1)${LIBRARY_ARCHIVE_PATH_POSTFIX}" + + xcodebuild -create-xcframework \ + -library ${LIBRARY_SIMULATOR_DIR}/libStaticLibrary.a \ + -library ${LIBRARY_DEVICE_DIR}/libStaticLibrary.a \ + -output ${OUTPUT_DIR_PATH}/xcframeworks/${1}.xcframework +} + +#### Static Library #### +LIBRARY=KarhooSDK + +echo "▸ Archive $LIBRARY" +buildArchive ${LIBRARY} + +echo "▸ Create $FRAMEWORK.xcframework" +createXCFrameworkForStaticLibrary ${LIBRARY} + +#### Dynamic Framework #### + +DYNAMIC_FRAMEWORK=KarhooSDK + +echo "▸ Archive $DYNAMIC_FRAMEWORK" +buildArchive ${DYNAMIC_FRAMEWORK} + +echo "▸ Create $DYNAMIC_FRAMEWORK.xcframework" +createXCFramework ${DYNAMIC_FRAMEWORK} diff --git a/docs/assets/network_sdk.png b/docs/assets/network_sdk.png new file mode 100644 index 00000000..5f8aa8f4 Binary files /dev/null and b/docs/assets/network_sdk.png differ diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 00000000..18030630 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,6 @@ +# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app +# apple_id("[[APPLE_ID]]") # Your Apple email address + + +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 00000000..7e3510ee --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,60 @@ +FASTLANE_HIDE_CHANGELOG = true +FASTLANE_SKIP_UPDATE_CHECK = true + +default_platform(:ios) + + +platform :ios do + before_all do + ENV["SLACK_URL"] = "https://hooks.slack.com/services/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" + end + + desc "Danger PR Validation" + lane :DangerPRValidation do + danger( + danger_id: "PR_VALIDATION", + dangerfile: "./DangerfilePRMetaValidation" + ) + end + + desc "Danger publish test results" + lane :DangerPostCI do + danger( + danger_id: "POST_CI", + ) + end + + desc "Description of what the lane does" + lane :unit_tests_integration_tests do + scan( + workspace: "KarhooSDK.xcworkspace", + scheme: "KarhooSDK", + code_coverage: true, + clean: true, + slack_message: "Unit/Integration Tests for *Karhoo iOS SDK*", + output_types: "junit", + output_files: "NetworkSDKTests.xml" + ) + end + + error do |lane, exception| + slack( + message: exception.message, + success: false + ) + end + + desc "Xcov Report" + lane :XcovReport do + xcov( + workspace: "KarhooSDK.xcworkspace", + scheme: "KarhooSDK", + output_directory: "fastlane/test_output", + ignore_file_path: "./*view.swift", + slack_url: "https://hooks.slack.com/services/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", + slack_channel: "sdktestscoverage", + slack_message: "KarhooSDK test coverage report:" + ) + end + +end diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 00000000..7bcde3e3 --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,44 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew cask install fastlane` + +# Available Actions +## iOS +### ios DangerPRValidation +``` +fastlane ios DangerPRValidation +``` +Danger PR Validation +### ios DangerPostCI +``` +fastlane ios DangerPostCI +``` +Danger publish test results +### ios unit_tests_integration_tests +``` +fastlane ios unit_tests_integration_tests +``` +Description of what the lane does +### ios XcovReport +``` +fastlane ios XcovReport +``` +Xcov Report + +---- + +This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/release-framework.sh b/release-framework.sh new file mode 100644 index 00000000..b756a3d7 --- /dev/null +++ b/release-framework.sh @@ -0,0 +1,21 @@ +echo "Ok lets go- pakaging up $1" + +#set plist version +xcrun agvtool new-marketing-version $1 + +#incrememnt build number +xcrun agvtool next-version -all + +#install pods +pod install + +sh build-framework.sh ./build-output + +sh bintray-upload.sh $1 + +#remove framework artifact +rm -R build-output +echo "You're welcome :)" + +git add . +git commit -m "Build $1" diff --git a/run-tests.sh b/run-tests.sh new file mode 100644 index 00000000..d203cdca --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +cd KarhooSDK + +pod install + +cd ../ + +fastlane ios unit_tests_integration_tests