├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── Pyrobase.podspec
├── Pyrobase.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcshareddata
│ └── xcschemes
│ └── Pyrobase.xcscheme
├── Pyrobase
├── Info.plist
├── PlistReader.swift
├── PyroAuth.swift
├── PyroAuthInfo.plist
├── PyroAuthResult.swift
├── PyroEventSource.swift
├── PyroEventSourceMessage.swift
├── PyroEventSourceParser.swift
├── PyroEventSourceSessionProvider.swift
├── PyroTransaction.swift
├── PyroTransactionParameter.swift
├── PyroTransactionResult.swift
├── PyroTransactionTemporaryPath.swift
├── Pyrobase.h
├── Pyrobase.swift
├── Request.swift
├── RequestMethod.swift
├── RequestOperation.swift
├── RequestPath.swift
├── RequestResponse.swift
└── RequestResult.swift
├── PyrobaseTests
├── AuthRequestMock.swift
├── CalendarExtensionTest.swift
├── Info.plist
├── JSONRequestOperationTest.swift
├── JSONSerializationMock.swift
├── PlistReaderContentMock.swift
├── PlistReaderResourceMock.swift
├── PlistReaderSample.plist
├── PlistReaderTest.swift
├── PlistSerializationMock.swift
├── PyroAuthContentTest.swift
├── PyroAuthMock.swift
├── PyroAuthTest.swift
├── PyroAuthTokenContentTest.swift
├── PyroEventSourceMessageTest.swift
├── PyroEventSourceParserTest.swift
├── PyroEventSourceSessionProviderMock.swift
├── PyroEventSourceSessionProviderTest.swift
├── PyroEventSourceTest.swift
├── PyroTransactionElapsedTimeMock.swift
├── PyroTransactionMock.swift
├── PyroTransactionTemporaryPathMock.swift
├── PyroTransactionTemporaryPathTest.swift
├── PyroTransactionTest.swift
├── PyrobaseTest.swift
├── RequestErrorTest.swift
├── RequestMethodTest.swift
├── RequestMock.swift
├── RequestOperationMock.swift
├── RequestPathTest.swift
├── RequestResponseTest.swift
├── RequestTest.swift
├── URLSessionDataDelegateMock.swift
└── URLSessionMock.swift
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # From https://github.com/github/gitignore/blob/master/Swift.gitignore
2 |
3 | GoogleService-Info.plist
4 | *.gpx
5 |
6 | # Xcode
7 | #
8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
9 |
10 | # macOS
11 | .DS_Store
12 |
13 | ## Build generated
14 | build/
15 | DerivedData/
16 |
17 | ## Various settings
18 | *.pbxuser
19 | !default.pbxuser
20 | *.mode1v3
21 | !default.mode1v3
22 | *.mode2v3
23 | !default.mode2v3
24 | *.perspectivev3
25 | !default.perspectivev3
26 | xcuserdata/
27 |
28 | ## Other
29 | *.moved-aside
30 | *.xcuserstate
31 |
32 | ## Obj-C/Swift specific
33 | *.hmap
34 | *.ipa
35 | *.dSYM.zip
36 | *.dSYM
37 |
38 | ## Playgrounds
39 | timeline.xctimeline
40 | playground.xcworkspace
41 | *.playground
42 |
43 | # Swift Package Manager
44 | #
45 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
46 | # Packages/
47 | .build/
48 |
49 | # CocoaPods
50 | #
51 | # We recommend against adding the Pods directory to your .gitignore. However
52 | # you should judge for yourself, the pros and cons are mentioned at:
53 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
54 |
55 | Pods/
56 |
57 | # Carthage
58 | #
59 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
60 |
61 | Carthage/Checkouts
62 | Carthage/Build
63 |
64 | # fastlane
65 | #
66 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
67 | # screenshots whenever they are needed.
68 | # For more information about the recommended setup visit:
69 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
70 |
71 | fastlane/report.xml
72 | fastlane/Preview.html
73 | fastlane/screenshots
74 | fastlane/test_output
75 |
76 |
77 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Build 1.0.1
4 | - Improved usability of errors
5 | - Added code and message property in `RequestError`
6 | - `RequestError` implemented `Equatable` protocol
7 | - `RequestError` is equal if code and message are the same
8 | - Defined static message for `invalidURL`, `unparseableJSON`, `noURLResponse`, `nullJSON`, and `unknown` cases
9 | - Defined code for `RequestError` cases
10 |
11 | ## Version 1.0
12 | - Implemented simple Firebase REST API
13 | - User can do authentication
14 | - User can issue GET, POST, PUT, PATCH requests
15 | - User can stream paths
16 | - User can run transactions
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 mownier
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Pyrobase.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'Pyrobase'
3 | s.version = '1.1'
4 | s.summary = 'An iOS lightweight wrapper for Firebase REST API'
5 | s.platform = :ios, '9.0'
6 | s.license = { :type => 'MIT', :file => 'LICENSE' }
7 | s.homepage = 'https://github.com/mownier/pyrobase'
8 | s.author = { 'Mounir Ybanez' => 'rinuom91@gmail.com' }
9 | s.source = { :git => 'https://github.com/mownier/pyrobase.git', :tag => s.version.to_s }
10 | s.source_files = 'Pyrobase/*.swift'
11 | s.requires_arc = true
12 | end
13 |
--------------------------------------------------------------------------------
/Pyrobase.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Pyrobase.xcodeproj/xcshareddata/xcschemes/Pyrobase.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
35 |
41 |
42 |
43 |
45 |
46 |
47 |
48 |
49 |
50 |
56 |
57 |
58 |
59 |
60 |
61 |
72 |
73 |
79 |
80 |
81 |
82 |
86 |
87 |
88 |
89 |
90 |
91 |
97 |
98 |
104 |
105 |
106 |
107 |
109 |
110 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/Pyrobase/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.1.0
19 | CFBundleVersion
20 | 1.1.0
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Pyrobase/PlistReader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlistReader.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 09/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | protocol PlistReaderContentProtocol {
10 |
11 | func extract(at path: String) -> Data?
12 | }
13 |
14 | protocol PlistReaderResourceProtocol {
15 |
16 | func path(for name: String) -> String?
17 | }
18 |
19 | enum PlistReaderError: Error {
20 |
21 | case notFound
22 | case noContent
23 | case unreadable
24 | }
25 |
26 | class PlistReader {
27 |
28 | internal(set) var data: Any!
29 | internal(set) var name: String
30 |
31 | init(name: String, resource: PlistReaderResourceProtocol, content: PlistReaderContentProtocol, format: PropertyListSerialization.PropertyListFormat, serialization: PropertyListSerialization.Type) throws {
32 | self.name = name
33 |
34 | guard let path = resource.path(for: name) else {
35 | throw PlistReaderError.notFound
36 | }
37 |
38 | guard let xml = content.extract(at: path) else {
39 | throw PlistReaderError.noContent
40 | }
41 |
42 | var plistFormat = format
43 |
44 | guard let data = try? serialization.propertyList(from: xml, options: [], format: &plistFormat) else {
45 | throw PlistReaderError.unreadable
46 | }
47 |
48 | self.data = data
49 | }
50 | }
51 |
52 | extension PlistReader {
53 |
54 | class func create(name: String, bundle: Bundle = .main, fileManager: FileManager = .default) -> PlistReader? {
55 | let format = PropertyListSerialization.PropertyListFormat.xml
56 | let serialization = PropertyListSerialization.self
57 | let reader = try? PlistReader(name: name, resource: bundle, content: fileManager, format: format, serialization: serialization)
58 | return reader
59 | }
60 | }
61 |
62 | extension Bundle: PlistReaderResourceProtocol {
63 |
64 | func path(for name: String) -> String? {
65 | return path(forResource: name, ofType: "plist")
66 | }
67 | }
68 |
69 | extension FileManager: PlistReaderContentProtocol {
70 |
71 | func extract(at path: String) -> Data? {
72 | return contents(atPath: path)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Pyrobase/PyroAuth.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroAuth.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 05/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public class PyroAuth {
10 |
11 | internal var key: String
12 | internal var request: RequestProtocol
13 | internal var signInPath: String
14 | internal var registerPath: String
15 | internal var refreshPath: String
16 | internal var confirmationCodePath: String
17 |
18 | public class func create(key: String, bundleIdentifier: String = "com.ner.Pyrobase", plistName: String = "PyroAuthInfo", request: RequestProtocol = Request.create() ) -> PyroAuth? {
19 | guard let bundle = Bundle(identifier: bundleIdentifier) else {
20 | return nil
21 | }
22 |
23 | guard let reader = PlistReader.create(name: plistName, bundle: bundle) else {
24 | return nil
25 | }
26 |
27 | var registerPath: String = ""
28 | var signInPath: String = ""
29 | var refreshPath: String = ""
30 | var confirmationCodePath: String = ""
31 |
32 | if let readerInfo = reader.data as? [AnyHashable: Any] {
33 | registerPath = (readerInfo["register_path"] as? String) ?? ""
34 | signInPath = (readerInfo["sign_in_path"] as? String) ?? ""
35 | refreshPath = (readerInfo["refresh_path"] as? String) ?? ""
36 | confirmationCodePath = (readerInfo["confirmation_code_path"] as? String) ?? ""
37 | }
38 |
39 | let auth = PyroAuth(key: key, request: request, signInPath: signInPath, registerPath: registerPath, refreshPath: refreshPath, confirmationCodePath: confirmationCodePath)
40 | return auth
41 | }
42 |
43 | public init(key: String, request: RequestProtocol, signInPath: String, registerPath: String, refreshPath: String, confirmationCodePath: String) {
44 | self.key = key
45 | self.request = request
46 | self.signInPath = signInPath
47 | self.registerPath = registerPath
48 | self.refreshPath = refreshPath
49 | self.confirmationCodePath = confirmationCodePath
50 | }
51 |
52 | public func register(email: String, password: String, completion: @escaping (PyroAuthResult) -> Void) {
53 | let data: [AnyHashable: Any] = ["email": email, "password": password, "returnSecureToken": true]
54 | let path = "\(registerPath)?key=\(key)"
55 | request.write(path: path, method: .post, data: data) { result in
56 | self.handleRequestResult(result, completion: completion)
57 | }
58 | }
59 |
60 | public func signIn(email: String, password: String, completion: @escaping (PyroAuthResult) -> Void) {
61 | let data: [AnyHashable: Any] = ["email": email, "password": password, "returnSecureToken": true]
62 | let path = "\(signInPath)?key=\(key)"
63 | request.write(path: path, method: .post, data: data) { result in
64 | self.handleRequestResult(result, completion: completion)
65 | }
66 | }
67 |
68 | public func refresh(token: String, completion: @escaping (PyroAuthResult) -> Void) {
69 | let data: [AnyHashable: Any] = ["grant_type": "refresh_token", "refresh_token": token]
70 | let path = "\(refreshPath)?key=\(key)"
71 | request.write(path: path, method: .post, data: data) { result in
72 | switch result {
73 | case .succeeded(let info):
74 | guard let resultInfo = info as? [AnyHashable: Any] else {
75 | completion(.failed(PyroAuthError.unexpectedContent))
76 | return
77 | }
78 |
79 | guard let accessToken = resultInfo["access_token"] as? String,
80 | let refreshToken = resultInfo["refresh_token"] as? String,
81 | let expiration = resultInfo["expires_in"] as? String else {
82 | return completion(.failed(PyroAuthError.incompleteContent))
83 | }
84 |
85 | var content = PyroAuthTokenContent()
86 | content.accessToken = accessToken
87 | content.refreshToken = refreshToken
88 | content.expiration = expiration
89 |
90 | completion(.succeeded(content))
91 |
92 | case .failed(let info):
93 | completion(.failed(info))
94 | }
95 | }
96 | }
97 |
98 | public func sendPasswordReset(email: String, completion: @escaping (PyroAuthResult) -> Void) {
99 | let data: [AnyHashable: Any] = ["requestType": "PASSWORD_RESET", "email": email]
100 | let path = "\(confirmationCodePath)?key=\(key)"
101 | request.write(path: path, method: .post, data: data) { result in
102 | switch result {
103 | case .succeeded:
104 | completion(.succeeded(true))
105 |
106 | case .failed(let info):
107 | completion(.failed(info))
108 | }
109 | }
110 | }
111 |
112 | internal func handleRequestResult(_ result: RequestResult, completion: @escaping (PyroAuthResult) -> Void) {
113 | switch result {
114 | case .succeeded(let info):
115 | guard let resultInfo = info as? [AnyHashable: Any] else {
116 | completion(.failed(PyroAuthError.unexpectedContent))
117 | return
118 | }
119 |
120 | guard let userId = resultInfo["localId"] as? String,
121 | let email = resultInfo["email"] as? String,
122 | let accessToken = resultInfo["idToken"] as? String,
123 | let refreshToken = resultInfo["refreshToken"] as? String,
124 | let expiration = resultInfo["expiresIn"] as? String else {
125 | return completion(.failed(PyroAuthError.incompleteContent))
126 | }
127 |
128 | var content = PyroAuthContent()
129 | content.userId = userId
130 | content.email = email
131 | content.accessToken = accessToken
132 | content.refreshToken = refreshToken
133 | content.expiration = expiration
134 |
135 | completion(.succeeded(content))
136 |
137 | case .failed(let info):
138 | completion(.failed(info))
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/Pyrobase/PyroAuthInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | confirmation_code_path
6 | https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode
7 | refresh_path
8 | https://securetoken.googleapis.com/v1/token
9 | register_path
10 | https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser
11 | sign_in_path
12 | https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Pyrobase/PyroAuthResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroAuthResult.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 10/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public enum PyroAuthResult {
10 |
11 | case failed(Error)
12 | case succeeded(T)
13 | }
14 |
15 | public enum PyroAuthError: Error {
16 |
17 | case unexpectedContent
18 | case incompleteContent
19 | }
20 |
21 | public struct PyroAuthContent {
22 |
23 | internal(set) public var userId: String
24 | internal(set) public var accessToken: String
25 | internal(set) public var email: String
26 | internal(set) public var refreshToken: String
27 | internal(set) public var expiration: String
28 |
29 | public init() {
30 | self.userId = ""
31 | self.accessToken = ""
32 | self.email = ""
33 | self.refreshToken = ""
34 | self.expiration = ""
35 | }
36 | }
37 |
38 | public struct PyroAuthTokenContent {
39 |
40 | internal(set) public var accessToken: String
41 | internal(set) public var expiration: String
42 | internal(set) public var refreshToken: String
43 |
44 | public init() {
45 | self.accessToken = ""
46 | self.expiration = ""
47 | self.refreshToken = ""
48 | }
49 | }
50 |
51 | extension PyroAuthContent: CustomStringConvertible {
52 |
53 | public var description: String {
54 | return "userId: \(userId)\naccessToken: \(accessToken)\nemail: \(email)\nrefreshToken: \(refreshToken)\nexpiration: \(expiration)"
55 | }
56 | }
57 |
58 | extension PyroAuthTokenContent: CustomStringConvertible {
59 |
60 | public var description: String {
61 | return "accessToken: \(accessToken)\nexpiration: \(expiration)\nrefreshToken: \(refreshToken)"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Pyrobase/PyroEventSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroEventSource.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 20/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public enum PyroEventSourceError: Error {
10 |
11 | case notClosed
12 | case notOpen
13 | case forcedClose
14 | }
15 |
16 | public enum PyroEventSourceState {
17 |
18 | case connecting
19 | case open
20 | case closed
21 | }
22 |
23 | public protocol PyroEventSourceCallback: class {
24 |
25 | func pyroEventSourceOnConnecting(_ eventSource: PyroEventSource)
26 | func pyroEventSourceOnOpen(_ eventSource: PyroEventSource)
27 | func pyroEventSourceOnClosed(_ eventSource: PyroEventSource)
28 | func pyroEventSource(_ eventSource: PyroEventSource, didReceiveMessage message: PyroEventSourceMessage)
29 | func pyroEventSource(_ eventSource: PyroEventSource, didReceiveError error: Error)
30 | }
31 |
32 | public class PyroEventSource: NSObject {
33 |
34 | internal var path: RequestPathProtocol
35 | internal var response: RequestResponseProtocol
36 | internal var sessionProvider: PyroEventSourceSessionProviderProtocol
37 | internal var parser: PyroEventSourceParserProtocol
38 | internal var session: URLSession!
39 | internal(set) public var lastEventID: String
40 | internal(set) public var state: PyroEventSourceState {
41 | didSet {
42 | switch state {
43 | case .open: callback?.pyroEventSourceOnOpen(self)
44 | case .closed: callback?.pyroEventSourceOnClosed(self)
45 | case .connecting: callback?.pyroEventSourceOnConnecting(self)
46 | }
47 | }
48 | }
49 |
50 | weak public var callback: PyroEventSourceCallback?
51 |
52 | public class func create(baseURL: String, accessToken: String, lastEventID: String = "") -> PyroEventSource {
53 | let provider = PyroEventSourceSessionProvider.create()
54 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
55 | let parser = PyroEventSourceParser()
56 | let response = RequestResponse()
57 | let eventSource = PyroEventSource(path: path, response: response, sessionProvider: provider, parser: parser, lastEventID: lastEventID)
58 | return eventSource
59 | }
60 |
61 | public init(path: RequestPathProtocol, response: RequestResponseProtocol, sessionProvider: PyroEventSourceSessionProvider, parser: PyroEventSourceParserProtocol, lastEventID: String) {
62 | self.path = path
63 | self.response = response
64 | self.sessionProvider = sessionProvider
65 | self.lastEventID = lastEventID
66 | self.state = .closed
67 | self.parser = parser
68 | }
69 |
70 | public func close() {
71 | session?.invalidateAndCancel()
72 | session = nil
73 | state = .closed
74 | }
75 |
76 | public func stream(_ relativePath: String) {
77 | guard state == .closed else {
78 | callback?.pyroEventSource(self, didReceiveError: PyroEventSourceError.notClosed)
79 | return
80 | }
81 |
82 | guard let url = URL(string: path.build(relativePath)) else {
83 | callback?.pyroEventSource(self, didReceiveError: RequestError.invalidURL)
84 | return
85 | }
86 |
87 | session = sessionProvider.createSession(for: self, lastEventID: lastEventID)
88 | state = .connecting
89 | session.dataTask(with: url).resume()
90 | }
91 |
92 | internal func isForcedClose(_ response: HTTPURLResponse?) -> Bool {
93 | return response != nil && response!.statusCode == 204
94 | }
95 | }
96 |
97 | extension PyroEventSource: URLSessionDataDelegate {
98 |
99 | public func urlSession(_ session: URLSession, dataTask task: URLSessionDataTask, didReceive data: Data) {
100 | guard state == .open else {
101 | callback?.pyroEventSource(self, didReceiveError: PyroEventSourceError.notOpen)
102 | return
103 | }
104 |
105 | guard !isForcedClose(task.response as? HTTPURLResponse) else {
106 | close()
107 | callback?.pyroEventSource(self, didReceiveError: PyroEventSourceError.forcedClose)
108 | return
109 | }
110 |
111 | if let responseError = response.isErroneous(task.response as? HTTPURLResponse, data: data) {
112 | close()
113 | callback?.pyroEventSource(self, didReceiveError: responseError)
114 |
115 | } else {
116 | let message = parser.parse(data)
117 | callback?.pyroEventSource(self, didReceiveMessage: message)
118 | lastEventID = message.id
119 | }
120 | }
121 |
122 | public func urlSession(_ session: URLSession, dataTask task: URLSessionDataTask, didReceive httpResponse: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
123 | if let responseError = response.isErroneous(httpResponse as? HTTPURLResponse, data: nil) {
124 | close()
125 | callback?.pyroEventSource(self, didReceiveError: responseError)
126 |
127 | } else {
128 | state = .open
129 | completionHandler(.allow)
130 | }
131 | }
132 |
133 | public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
134 | close()
135 |
136 | guard !isForcedClose(task.response as? HTTPURLResponse) else {
137 | callback?.pyroEventSource(self, didReceiveError: PyroEventSourceError.forcedClose)
138 | return
139 | }
140 |
141 | if let responseError = response.isErroneous(task.response as? HTTPURLResponse, data: nil) {
142 | callback?.pyroEventSource(self, didReceiveError: responseError)
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/Pyrobase/PyroEventSourceMessage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroEventSourceMessage.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 22/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public struct PyroEventSourceMessage {
10 |
11 | public var id: String
12 | public var event: String
13 | public var data: String
14 |
15 | public init() {
16 | self.id = ""
17 | self.event = ""
18 | self.data = ""
19 | }
20 | }
21 |
22 | extension PyroEventSourceMessage: CustomStringConvertible {
23 |
24 | public var description: String {
25 | return "\nid: \(id)\nevent: \(event)\ndata: \(data)\n"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Pyrobase/PyroEventSourceParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroEventSourceParser.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 22/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public protocol PyroEventSourceParserProtocol {
10 |
11 | func parse(_ data: Data) -> PyroEventSourceMessage
12 | }
13 |
14 | public class PyroEventSourceParser: PyroEventSourceParserProtocol {
15 |
16 | public func parse(_ data: Data) -> PyroEventSourceMessage {
17 | var message = PyroEventSourceMessage()
18 |
19 | guard let string = String(data: data, encoding: .utf8) else {
20 | return message
21 | }
22 |
23 | for event in string.components(separatedBy: CharacterSet.newlines) {
24 | guard !event.isEmpty && !event.hasPrefix(":") && !event.contains("retry:") else {
25 | continue
26 | }
27 |
28 | for line in event.components(separatedBy: CharacterSet.newlines) {
29 | guard let colonRange = line.range(of: ":") else {
30 | continue
31 | }
32 |
33 | let keyRange = Range(uncheckedBounds: (line.startIndex, colonRange.lowerBound))
34 | let key = String(line[keyRange]).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
35 |
36 | let valueRange = Range(uncheckedBounds: (line.index(after: colonRange.lowerBound), line.endIndex))
37 | let value = String(line[valueRange]).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
38 |
39 | switch key.lowercased() {
40 | case "id":
41 | message.id = value
42 |
43 | case "event":
44 | message.event = value
45 |
46 | case "data":
47 | message.data = value
48 |
49 | default: break
50 | }
51 | }
52 | }
53 |
54 | return message
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Pyrobase/PyroEventSourceSessionProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroEventSourceSessionProvider.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 22/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public protocol PyroEventSourceSessionProviderProtocol {
10 |
11 | var queue: OperationQueue { get }
12 | var headers: [String: String] { set get }
13 |
14 | func createSession(for delegate: URLSessionDataDelegate, lastEventID: String) -> URLSession
15 | }
16 |
17 | public class PyroEventSourceSessionProvider: PyroEventSourceSessionProviderProtocol {
18 |
19 | public var queue: OperationQueue
20 | public var headers: [String: String]
21 |
22 | public class func create() -> PyroEventSourceSessionProvider {
23 | let queue = OperationQueue()
24 | let headers = [
25 | "Accept": "text/event-stream"
26 | ]
27 | return PyroEventSourceSessionProvider(queue: queue, headers: headers)
28 | }
29 |
30 | public init(queue: OperationQueue, headers: [String: String]) {
31 | self.queue = queue
32 | self.headers = headers
33 | }
34 |
35 | public func createSession(for delegate: URLSessionDataDelegate, lastEventID: String) -> URLSession {
36 | let config = URLSessionConfiguration.default
37 | config.timeoutIntervalForRequest = TimeInterval(INT_MAX)
38 | config.timeoutIntervalForResource = TimeInterval(INT_MAX)
39 | config.httpAdditionalHeaders = headers
40 | if !lastEventID.isEmpty {
41 | config.httpAdditionalHeaders!["Last-Event-Id"] = lastEventID
42 | }
43 | return URLSession(configuration: config, delegate: delegate, delegateQueue: queue)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Pyrobase/PyroTransaction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroTransaction.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 15/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public class PyroTransaction {
10 |
11 | internal var path: RequestPathProtocol
12 | internal var request: RequestProtocol
13 | internal var tempPath: PyroTransactionTemporaryPathProtocol
14 |
15 | public var baseURL: String {
16 | return path.baseURL
17 | }
18 |
19 | public class func create(baseURL: String, accessToken: String) -> PyroTransaction {
20 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
21 | let request = Request.create()
22 | let tempPath = PyroTransactionTemporaryPath.create()
23 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
24 | return transaction
25 | }
26 |
27 | public init(request: RequestProtocol, path: RequestPathProtocol, tempPath: PyroTransactionTemporaryPathProtocol) {
28 | self.request = request
29 | self.path = path
30 | self.tempPath = tempPath
31 | }
32 |
33 | public func run(parentPath: String, childKey: String, mutator: @escaping (Any) -> Any, completion: @escaping (PyroTransactionResult) -> Void) {
34 | let param = Parameter(parentPath: parentPath, childKey: childKey, mutator: mutator, completion: completion)
35 |
36 | readTransaction(param: param)
37 | }
38 |
39 | internal func readTransaction(param: Parameter) {
40 | let readPath = path.build("\(tempPath.key)/\(param.parentPath)/\(param.childKey)")
41 |
42 | request.read(path: readPath, query: [:]) { result in
43 | switch result {
44 | case .failed(let info):
45 | guard let errorInfo = info as? RequestError, errorInfo == .nullJSON else {
46 | param.completion(.failed(info))
47 | return
48 | }
49 |
50 | self.writeTransaction(param: param)
51 |
52 | case .succeeded(let info):
53 | guard let string = info as? String, let timestamp = Double(string) else {
54 | param.completion(.failed(PyroTransactionError.invalidExpirationTimestamp))
55 | return
56 | }
57 |
58 | if self.tempPath.isTransactionDateExpired(timestamp, now: Date()) {
59 | self.writeTransaction(param: param)
60 |
61 | } else {
62 | param.completion(.failed(PyroTransactionError.activeTransactionNotDone))
63 | }
64 | }
65 | }
66 | }
67 |
68 | internal func writeTransaction(param: Parameter) {
69 | let info = ["\(param.parentPath)/\(param.childKey)": [".sv": "timestamp"]]
70 | let writePath = path.build(tempPath.key)
71 |
72 | request.write(path: writePath, method: .patch, data: info) { result in
73 | switch result {
74 | case .failed(let info):
75 | param.completion(.failed(info))
76 |
77 | case .succeeded:
78 | self.readChild(param: param)
79 | }
80 | }
81 | }
82 |
83 | internal func readChild(param: Parameter) {
84 | let readPath = path.build("\(param.parentPath)/\(param.childKey)")
85 |
86 | request.read(path: readPath, query: [:]) { result in
87 | switch result {
88 | case .failed(let info):
89 | self.deleteTransaction(param: param) { _ in
90 | param.completion(.failed(info))
91 | }
92 |
93 | case .succeeded(let info):
94 | self.writeChild(param: param, info: info)
95 | }
96 | }
97 | }
98 |
99 | internal func writeChild(param: Parameter, info: Any) {
100 | let newInfo = param.mutator(info)
101 | let writePath = path.build(param.parentPath)
102 | let data = [param.childKey: newInfo]
103 |
104 | request.write(path: writePath, method: .patch, data: data) { result in
105 | let completion: (RequestResult) -> Void
106 |
107 | switch result {
108 | case .failed(let info):
109 | completion = { _ in param.completion(.failed(info)) }
110 |
111 | case .succeeded(let info):
112 | completion = { _ in param.completion(.succeeded(info)) }
113 | }
114 |
115 | self.deleteTransaction(param: param, completion: completion)
116 | }
117 | }
118 |
119 | internal func deleteTransaction(param: Parameter, completion: @escaping (RequestResult) -> Void) {
120 | let deletePath = path.build("\(tempPath.key)/\(param.parentPath)")
121 |
122 | request.delete(path: deletePath) { result in
123 | completion(result)
124 | }
125 | }
126 | }
127 |
128 |
--------------------------------------------------------------------------------
/Pyrobase/PyroTransactionParameter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroTransactionParameter.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 16/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | struct Parameter {
10 |
11 | let parentPath: String
12 | let childKey: String
13 | let mutator: (Any) -> Any
14 | let completion: (PyroTransactionResult) -> Void
15 |
16 | init(parentPath: String, childKey: String, mutator: @escaping (Any) -> Any, completion: @escaping (PyroTransactionResult) -> Void) {
17 | self.parentPath = parentPath
18 | self.childKey = childKey
19 | self.mutator = mutator
20 | self.completion = completion
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Pyrobase/PyroTransactionResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroTransactionResult.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 19/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public enum PyroTransactionResult {
10 |
11 | case failed(Error)
12 | case succeeded(Any)
13 | }
14 |
15 | public enum PyroTransactionError: Error {
16 |
17 | case invalidExpirationTimestamp
18 | case activeTransactionNotDone
19 | }
20 |
--------------------------------------------------------------------------------
/Pyrobase/PyroTransactionTemporaryPath.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroTransactionTemporaryPath.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 19/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public protocol PyroTransactionTemporaryPathProtocol {
10 |
11 | var key: String { get }
12 | var expiration: UInt { get }
13 |
14 | func isTransactionDateExpired(_ timestamp: Double, now: Date) -> Bool
15 | }
16 |
17 | public class PyroTransactionTemporaryPath: PyroTransactionTemporaryPathProtocol {
18 |
19 | internal var elapsedTime: PyroTransactionElapsedTimeProtocol
20 |
21 | internal(set) public var key: String
22 | internal(set) public var expiration: UInt
23 |
24 | public class func create() -> PyroTransactionTemporaryPath {
25 | let key: String = "pyrobase_transactions"
26 | let expiration: UInt = 30
27 | let elapsedTime: Calendar = .current
28 | return PyroTransactionTemporaryPath(key: key, expiration: expiration, elapsedTime: elapsedTime)
29 | }
30 |
31 | public init(key: String, expiration: UInt, elapsedTime: PyroTransactionElapsedTimeProtocol) {
32 | self.key = key
33 | self.expiration = expiration
34 | self.elapsedTime = elapsedTime
35 | }
36 |
37 | public func isTransactionDateExpired(_ timestamp: Double, now: Date) -> Bool {
38 | let transactionDate = Date(timeIntervalSince1970: timestamp / 1000)
39 |
40 | guard let seconds: Int = elapsedTime.seconds(from: transactionDate, to: now) else {
41 | return false
42 | }
43 |
44 | return seconds >= Int(expiration)
45 | }
46 | }
47 |
48 | public protocol PyroTransactionElapsedTimeProtocol {
49 |
50 | func seconds(from: Date, to: Date) -> Int?
51 | }
52 |
53 | extension Calendar: PyroTransactionElapsedTimeProtocol {
54 |
55 | public func seconds(from transactionDate: Date, to now: Date) -> Int? {
56 | return dateComponents([.second], from: now, to: transactionDate).second
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Pyrobase/Pyrobase.h:
--------------------------------------------------------------------------------
1 | //
2 | // Pyrobase.h
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 01/05/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Pyrobase.
12 | FOUNDATION_EXPORT double PyrobaseVersionNumber;
13 |
14 | //! Project version string for Pyrobase.
15 | FOUNDATION_EXPORT const unsigned char PyrobaseVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Pyrobase/Pyrobase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Pyrobase.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 01/05/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public class Pyrobase {
10 |
11 | internal var path: RequestPathProtocol
12 | internal var request: RequestProtocol
13 |
14 | public var baseURL: String {
15 | return path.baseURL
16 | }
17 |
18 | public init(request: RequestProtocol, path: RequestPathProtocol) {
19 | self.request = request
20 | self.path = path
21 | }
22 |
23 | public func get(path relativePath: String, query: [AnyHashable: Any], completion: @escaping (RequestResult) -> Void) {
24 | request.read(path: path.build(relativePath), query: query) { result in
25 | completion(result)
26 | }
27 | }
28 |
29 | public func put(path relativePath: String, value: [AnyHashable: Any], completion: @escaping (RequestResult) -> Void) {
30 | request.write(path: path.build(relativePath), method: .put, data: value) { result in
31 | completion(result)
32 | }
33 | }
34 |
35 | public func post(path relativePath: String, value: [AnyHashable: Any], completion: @escaping (RequestResult) -> Void) {
36 | request.write(path: path.build(relativePath), method: .post, data: value) { result in
37 | completion(result)
38 | }
39 | }
40 |
41 | public func patch(path relativePath: String, value: [AnyHashable: Any], completion: @escaping (RequestResult) -> Void) {
42 | request.write(path: path.build(relativePath), method: .patch, data: value) { result in
43 | completion(result)
44 | }
45 | }
46 |
47 | public func delete(path relativePath: String, completion: @escaping (RequestResult) -> Void) {
48 | request.delete(path: path.build(relativePath), completion: completion)
49 | }
50 | }
51 |
52 | extension Pyrobase {
53 |
54 | public class func create(baseURL: String, accessToken: String) -> Pyrobase {
55 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
56 | let request = Request.create()
57 | let pyrobase = Pyrobase(request: request, path: path)
58 | return pyrobase
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Pyrobase/Request.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Request.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 02/05/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public protocol RequestProtocol {
10 |
11 | func read(path: String, query: [AnyHashable: Any], completion: @escaping (RequestResult) -> Void)
12 | func write(path: String, method: RequestMethod, data: [AnyHashable: Any], completion: @escaping (RequestResult) -> Void)
13 | func delete(path: String, completion: @escaping (RequestResult) -> Void)
14 | }
15 |
16 | public class Request: RequestProtocol {
17 |
18 | internal var session: URLSession
19 | internal var operation: RequestOperation
20 | internal var response: RequestResponseProtocol
21 |
22 | public init(session: URLSession, operation: RequestOperation, response: RequestResponseProtocol) {
23 | self.session = session
24 | self.operation = operation
25 | self.response = response
26 | }
27 |
28 | public func read(path: String, query: [AnyHashable: Any], completion: @escaping (RequestResult) -> Void) {
29 | request(path, .get, query, completion)
30 | }
31 |
32 | public func write(path: String, method: RequestMethod, data: [AnyHashable: Any], completion: @escaping (RequestResult) -> Void) {
33 | request(path, method, data, completion)
34 | }
35 |
36 | public func delete(path: String, completion: @escaping (RequestResult) -> Void) {
37 | request(path, .delete, [:], completion)
38 | }
39 |
40 | internal func request(_ path: String, _ method: RequestMethod, _ data: [AnyHashable: Any], _ completion: @escaping (RequestResult) -> Void) {
41 | guard let url = buildURL(path, method, data) else {
42 | completion(.failed(RequestError.invalidURL))
43 | return
44 | }
45 |
46 | let request = operation.build(url: url, method: method, data: data)
47 | let task = session.dataTask(with: request) { data, response, error in
48 | guard error == nil else {
49 | completion(.failed(error!))
50 | return
51 | }
52 |
53 | guard response != nil else {
54 | completion(.failed(RequestError.noURLResponse))
55 | return
56 | }
57 |
58 | let error = self.response.isErroneous(response as? HTTPURLResponse, data: data)
59 |
60 | guard error == nil else {
61 | completion(.failed(error!))
62 | return
63 | }
64 |
65 | guard data != nil else {
66 | completion(.succeeded([:]))
67 | return
68 | }
69 |
70 | let result = self.operation.parse(data: data!)
71 |
72 | switch result {
73 | case .error(let info):
74 | completion(.failed(info))
75 |
76 | case .okay(let info):
77 | guard let okayInfo = info as? String, okayInfo.lowercased() == "null", method != .delete else {
78 | completion(.succeeded(info))
79 | return
80 | }
81 |
82 | completion(.failed(RequestError.nullJSON))
83 | }
84 | }
85 | task.resume()
86 | }
87 |
88 | internal func buildURL(_ path: String, _ method: RequestMethod, _ data: [AnyHashable: Any]) -> URL? {
89 | switch method {
90 | case .get where !data.isEmpty:
91 | guard !path.isEmpty, var components = URLComponents(string: path) else {
92 | return nil
93 | }
94 |
95 | var queryItems = [URLQueryItem]()
96 |
97 | for (key, value) in data {
98 | let item = URLQueryItem(name: "\(key)", value: "\(value)")
99 | queryItems.insert(item, at: 0)
100 | }
101 |
102 | if components.queryItems != nil {
103 | components.queryItems!.append(contentsOf: queryItems)
104 |
105 | } else {
106 | components.queryItems = queryItems
107 | }
108 |
109 | return components.url
110 |
111 | default:
112 | return URL(string: path)
113 | }
114 | }
115 | }
116 |
117 | extension Request {
118 |
119 | public class func create() -> Request {
120 | let session = URLSession.shared
121 | let operation = JSONRequestOperation.create()
122 | let response = RequestResponse()
123 | let request = Request(session: session, operation: operation, response: response)
124 | return request
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Pyrobase/RequestMethod.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestMethod.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 05/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public enum RequestMethod {
10 |
11 | case get
12 | case put
13 | case patch
14 | case post
15 | case delete
16 | }
17 |
18 | extension RequestMethod: CustomStringConvertible {
19 |
20 | public var description: String {
21 | switch self {
22 | case .get: return "GET"
23 | case .put: return "PUT"
24 | case .patch: return "PATCH"
25 | case .post: return "POST"
26 | case .delete: return "DELETE"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Pyrobase/RequestOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestOperation.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 05/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public protocol RequestOperation {
10 |
11 | func build(url: URL, method: RequestMethod, data: [AnyHashable: Any]) -> URLRequest
12 | func parse(data: Data) -> RequestOperationResult
13 | }
14 |
15 | public enum RequestOperationResult {
16 |
17 | case error(Error)
18 | case okay(Any)
19 | }
20 |
21 | public class JSONRequestOperation: RequestOperation {
22 |
23 | internal var serialization: JSONSerialization.Type
24 |
25 | public init(serialization: JSONSerialization.Type) {
26 | self.serialization = serialization
27 | }
28 |
29 | public func build(url: URL, method: RequestMethod, data: [AnyHashable: Any]) -> URLRequest {
30 | var request = URLRequest(url: url)
31 | request.addValue("application/json", forHTTPHeaderField: "Accept")
32 | request.addValue("application/json", forHTTPHeaderField: "Content-Type")
33 | request.httpMethod = "\(method)"
34 |
35 | if !data.isEmpty && method != .get {
36 | request.httpBody = try? serialization.data(withJSONObject: data, options: [])
37 | }
38 |
39 | return request
40 | }
41 |
42 | public func parse(data: Data) -> RequestOperationResult {
43 | if serialization.isValidJSONObject(data) {
44 | guard let jsonObject = try? serialization.jsonObject(with: data, options: []) else {
45 | return .error(RequestError.unparseableJSON)
46 | }
47 |
48 | return .okay(jsonObject)
49 | }
50 |
51 | guard let resultString = String(data: data, encoding: .utf8),
52 | let resultStringData = resultString.data(using: .utf8) else {
53 | return .error(RequestError.unparseableJSON)
54 | }
55 |
56 | guard let jsonObject = try? serialization.jsonObject(with: resultStringData, options: []) else {
57 | return .okay(resultString)
58 | }
59 |
60 | return .okay(jsonObject)
61 | }
62 | }
63 |
64 | extension JSONRequestOperation {
65 |
66 | public class func create() -> JSONRequestOperation {
67 | let serialization = JSONSerialization.self
68 | let operation = JSONRequestOperation(serialization: serialization)
69 | return operation
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Pyrobase/RequestPath.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestPath.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 02/05/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public protocol RequestPathProtocol {
10 |
11 | var baseURL: String { get }
12 |
13 | func build(_ path: String) -> String
14 | }
15 |
16 | public class RequestPath: RequestPathProtocol {
17 |
18 | internal var accessToken: String
19 |
20 | public var baseURL: String
21 |
22 | public init(baseURL: String, accessToken: String) {
23 | self.baseURL = baseURL
24 | self.accessToken = accessToken
25 | }
26 |
27 | public func build(_ path: String) -> String {
28 | guard !baseURL.isEmpty else {
29 | return ""
30 | }
31 |
32 | return "\(baseURL)/\(path).json?auth=\(accessToken)"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Pyrobase/RequestResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestResponse.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 22/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public protocol RequestResponseProtocol {
10 |
11 | func isErroneous(_ response: HTTPURLResponse?, data: Data?) -> Error?
12 | }
13 |
14 | public class RequestResponse: RequestResponseProtocol {
15 |
16 | private(set) public var serializer: JSONSerialization.Type
17 |
18 | public init(serializer: JSONSerialization.Type = JSONSerialization.self) {
19 | self.serializer = serializer
20 | }
21 |
22 | public func isErroneous(_ response: HTTPURLResponse?, data: Data?) -> Error? {
23 | guard response != nil else {
24 | return nil
25 | }
26 |
27 | switch response!.statusCode {
28 | case 400: return RequestError.badRequest(errorMessage(data))
29 | case 401: return RequestError.unauthorized(errorMessage(data))
30 | case 403: return RequestError.forbidden(errorMessage(data))
31 | case 404: return RequestError.notFound(errorMessage(data))
32 | case 500: return RequestError.internalServiceError(errorMessage(data))
33 | case 503: return RequestError.serviceUnavailable(errorMessage(data))
34 | default: return nil
35 | }
36 | }
37 |
38 | func errorMessage(_ data: Data?) -> String {
39 | guard data != nil else {
40 | return ""
41 | }
42 |
43 | let info = (try? serializer.jsonObject(with: data!, options: [])) as? [AnyHashable: Any]
44 | return (info?["error"] as? String) ?? ""
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Pyrobase/RequestResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestResult.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 05/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | public enum RequestResult {
10 |
11 | case failed(Error)
12 | case succeeded(Any)
13 | }
14 |
15 | public enum RequestError: Error {
16 |
17 | case invalidURL
18 | case unparseableJSON
19 | case noURLResponse
20 | case nullJSON
21 | case unknown
22 |
23 | case badRequest(String)
24 | case unauthorized(String)
25 | case forbidden(String)
26 | case notFound(String)
27 | case internalServiceError(String)
28 | case serviceUnavailable(String)
29 |
30 | public var code: Int {
31 | switch self {
32 | case .invalidURL: return -9000
33 | case .unparseableJSON: return -9001
34 | case .noURLResponse: return -9002
35 | case .nullJSON: return -9003
36 | case .unknown: return -9004
37 | case .badRequest: return -9005
38 | case .unauthorized: return -9006
39 | case .forbidden: return -9007
40 | case .notFound: return -9008
41 | case .internalServiceError: return -9009
42 | case .serviceUnavailable: return -9010
43 | }
44 | }
45 |
46 | public var message: String {
47 | switch self {
48 | case .invalidURL: return "URL is invalid"
49 | case .unparseableJSON: return "Can not parse JSON"
50 | case .noURLResponse: return "No URL response"
51 | case .nullJSON: return "JSON is null"
52 | case .unknown: return "Unknown error encountered"
53 |
54 | case .badRequest(let message),
55 | .unauthorized(let message),
56 | .forbidden(let message),
57 | .notFound(let message),
58 | .internalServiceError(let message),
59 | .serviceUnavailable(let message):
60 | return message
61 | }
62 | }
63 | }
64 |
65 | extension RequestError: Equatable {
66 |
67 | public static func ==(lhs: RequestError, rhs: RequestError) -> Bool {
68 | return lhs.code == rhs.code && lhs.message == rhs.message
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/PyrobaseTests/AuthRequestMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthRequestMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 09/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import Pyrobase
10 |
11 | class AuthRequestMock: RequestProtocol {
12 |
13 | var writeURLPath: String = ""
14 | var writeMethod: RequestMethod = .post
15 | var writeData: [AnyHashable: Any] = [:]
16 |
17 | func read(path: String, query: [AnyHashable : Any], completion: @escaping (RequestResult) -> Void) {
18 |
19 | }
20 |
21 | func write(path: String, method: RequestMethod, data: [AnyHashable : Any], completion: @escaping (RequestResult) -> Void) {
22 | writeURLPath = path
23 | writeMethod = method
24 | writeData = data
25 | completion(.succeeded(true))
26 | }
27 |
28 | func delete(path: String, completion: @escaping (RequestResult) -> Void) {
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/PyrobaseTests/CalendarExtensionTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CalendarExtensionTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 20/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class CalendarExtensionTest: XCTestCase {
13 |
14 | func testElapsedSecondsComputation() {
15 | let now = Date()
16 | let calendar = Calendar.current
17 |
18 | var expectedSeconds = 2
19 | var components = DateComponents()
20 | components.second = expectedSeconds
21 | var fromDate = Calendar.current.date(byAdding: components, to: now)!
22 | var seconds = calendar.seconds(from: fromDate, to: now)
23 | XCTAssertEqual(expectedSeconds, seconds)
24 |
25 | expectedSeconds = -4
26 | components.second = expectedSeconds
27 | fromDate = Calendar.current.date(byAdding: components, to: now)!
28 | seconds = calendar.seconds(from: fromDate, to: now)
29 | XCTAssertEqual(expectedSeconds, seconds)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/PyrobaseTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/PyrobaseTests/JSONRequestOperationTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JSONRequestOperationTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 07/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class JSONRequestOperationTest: XCTestCase {
13 |
14 | func testBuildForGetMethod() {
15 | let operation = JSONRequestOperation.create()
16 | let url = URL(string: "https://foo.firebaseio.com/.json?accessToken=12345")!
17 | let request = operation.build(url: url, method: .get, data: [:])
18 | let headers = request.allHTTPHeaderFields!
19 |
20 | XCTAssertEqual(headers.count, 2)
21 | XCTAssertEqual(headers["Accept"], "application/json")
22 | XCTAssertEqual(headers["Content-Type"], "application/json")
23 | XCTAssertEqual(request.httpMethod, "GET")
24 | XCTAssertNil(request.httpBody)
25 | }
26 |
27 | func testBuildForPostMethod() {
28 | let operation = JSONRequestOperation.create()
29 | let url = URL(string: "https://foo.firebaseio.com/.json?accessToken=12345")!
30 | var request = operation.build(url: url, method: .post, data: [:])
31 | let headers = request.allHTTPHeaderFields!
32 |
33 | XCTAssertEqual(headers.count, 2)
34 | XCTAssertEqual(headers["Accept"], "application/json")
35 | XCTAssertEqual(headers["Content-Type"], "application/json")
36 | XCTAssertEqual(request.httpMethod, "POST")
37 | XCTAssertNil(request.httpBody)
38 |
39 | request = operation.build(url: url, method: .post, data: ["email": "me@me.com"])
40 | XCTAssertNotNil(request.httpBody)
41 | }
42 |
43 | func testBuildForPutMethod() {
44 | let operation = JSONRequestOperation.create()
45 | let url = URL(string: "https://foo.firebaseio.com/.json?accessToken=12345")!
46 | var request = operation.build(url: url, method: .put, data: [:])
47 | let headers = request.allHTTPHeaderFields!
48 |
49 | XCTAssertEqual(headers.count, 2)
50 | XCTAssertEqual(headers["Accept"], "application/json")
51 | XCTAssertEqual(headers["Content-Type"], "application/json")
52 | XCTAssertEqual(request.httpMethod, "PUT")
53 | XCTAssertNil(request.httpBody)
54 |
55 | request = operation.build(url: url, method: .put, data: ["email": "me@me.com"])
56 | XCTAssertNotNil(request.httpBody)
57 | }
58 |
59 | func testBuildForPatchMethod() {
60 | let operation = JSONRequestOperation.create()
61 | let url = URL(string: "https://foo.firebaseio.com/.json?accessToken=12345")!
62 | var request = operation.build(url: url, method: .patch, data: [:])
63 | let headers = request.allHTTPHeaderFields!
64 |
65 | XCTAssertEqual(headers.count, 2)
66 | XCTAssertEqual(headers["Accept"], "application/json")
67 | XCTAssertEqual(headers["Content-Type"], "application/json")
68 | XCTAssertEqual(request.httpMethod, "PATCH")
69 | XCTAssertNil(request.httpBody)
70 |
71 | request = operation.build(url: url, method: .patch, data: ["email": "me@me.com"])
72 | XCTAssertNotNil(request.httpBody)
73 | }
74 |
75 | func testParse() {
76 | let operation = JSONRequestOperation.create()
77 | let param: [AnyHashable: Any] = ["email": "me@me.com"]
78 | let data = try? JSONSerialization.data(withJSONObject: param, options: [])
79 | let result = operation.parse(data: data!)
80 |
81 | switch result {
82 | case .error:
83 | XCTFail()
84 |
85 | case .okay(let info):
86 | XCTAssertTrue(info is [AnyHashable: Any])
87 | let resultInfo = info as! [AnyHashable: Any]
88 | XCTAssertEqual(resultInfo.count, 1)
89 | XCTAssertEqual(param["email"] as! String, resultInfo["email"] as! String)
90 | }
91 | }
92 |
93 | func testParseWithValidJSONObjectButHavingNilConvertedResult() {
94 | let mock = JSONSerializationMock.self
95 | mock.isValid = true
96 | mock.shouldThrowErrorOnJSONObjectConversion = true
97 | let operation = JSONRequestOperation(serialization: mock)
98 | let param: [AnyHashable: Any] = ["email": "me@me.com"]
99 | let data = try? JSONSerialization.data(withJSONObject: param, options: [])
100 | let result = operation.parse(data: data!)
101 |
102 | switch result {
103 | case .okay: XCTFail()
104 | case .error: break
105 | }
106 | }
107 |
108 | func testParseWithValidJSONObjectAndHavingNonNilConvertedResult() {
109 | let mock = JSONSerializationMock.self
110 | mock.isValid = true
111 | mock.shouldThrowErrorOnJSONObjectConversion = false
112 | mock.expectedJSONObject = ["message": "wee"]
113 | let operation = JSONRequestOperation(serialization: mock)
114 | let param: [AnyHashable: Any] = ["email": "me@me.com"]
115 | let data = try? JSONSerialization.data(withJSONObject: param, options: [])
116 | let result = operation.parse(data: data!)
117 |
118 | switch result {
119 | case .error: XCTFail()
120 | case .okay: break
121 | }
122 | }
123 |
124 | func testParseWithNonValidJSONObjectButNotUTF8Encoded() {
125 | let operation = JSONRequestOperation.create()
126 | let string = "Jeprox"
127 | let data = string.data(using: .utf32)
128 | let result = operation.parse(data: data!)
129 |
130 | switch result {
131 | case .okay:
132 | XCTFail()
133 |
134 | case .error(let info):
135 | XCTAssertTrue(info is RequestError)
136 | let errorInfo = info as! RequestError
137 | XCTAssertTrue(errorInfo == RequestError.unparseableJSON)
138 | }
139 | }
140 |
141 | func testCreate() {
142 | let operation = JSONRequestOperation.create()
143 | XCTAssertTrue(operation.serialization == JSONSerialization.self)
144 | }
145 |
146 | func testBuildForGETWithData() {
147 | let operation = JSONRequestOperation.create()
148 | let url = URL(string: "https://foo.firebaseio.com")!
149 | let request = operation.build(url: url, method: .get, data: ["data": 1])
150 | XCTAssertNil(request.httpBody)
151 | }
152 | }
153 |
154 |
155 |
--------------------------------------------------------------------------------
/PyrobaseTests/JSONSerializationMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JSONSerializationMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 07/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class JSONSerializationMock: JSONSerialization {
12 |
13 | enum MockError: Error {
14 |
15 | case throwError1
16 | }
17 |
18 | static var isValid: Bool = true
19 | static var shouldThrowErrorOnJSONObjectConversion: Bool = true
20 | static var expectedJSONObject: Any = ""
21 |
22 | override class func isValidJSONObject(_ obj: Any) -> Bool {
23 | return true
24 | }
25 |
26 | override class func jsonObject(with data: Data, options opt: JSONSerialization.ReadingOptions = []) throws -> Any {
27 | guard shouldThrowErrorOnJSONObjectConversion else {
28 | return expectedJSONObject
29 | }
30 |
31 | throw MockError.throwError1
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/PyrobaseTests/PlistReaderContentMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlistReaderContentMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 09/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | @testable import Pyrobase
10 |
11 | class PlistReaderContentMock: PlistReaderContentProtocol {
12 |
13 | var expectedData: Data?
14 |
15 | func extract(at path: String) -> Data? {
16 | return expectedData
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/PyrobaseTests/PlistReaderResourceMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BundleMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 09/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | @testable import Pyrobase
10 |
11 | class PlistReaderResourceMock: PlistReaderResourceProtocol {
12 |
13 | var expectedPath: String?
14 |
15 | func path(for name: String) -> String? {
16 | return expectedPath
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/PyrobaseTests/PlistReaderSample.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | name
6 | Nina
7 |
8 |
9 |
--------------------------------------------------------------------------------
/PyrobaseTests/PlistReaderTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlistReaderTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 09/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PlistReaderTest: XCTestCase {
13 |
14 | func testInitWithErrorNotFound() {
15 | let resource = PlistReaderResourceMock()
16 | let content = PlistReaderContentMock()
17 | let format = PropertyListSerialization.PropertyListFormat.xml
18 | let serialization = PropertyListSerialization.self
19 | let isSucceeded: Bool
20 |
21 | resource.expectedPath = nil
22 |
23 | do {
24 | let _ = try PlistReader(name: "", resource: resource, content: content, format: format, serialization: serialization)
25 | isSucceeded = false
26 |
27 | } catch PlistReaderError.notFound {
28 | isSucceeded = true
29 |
30 | } catch {
31 | isSucceeded = false
32 | }
33 |
34 | XCTAssertTrue(isSucceeded)
35 | }
36 |
37 | func testInitWithErrorNoContent() {
38 | let resource = PlistReaderResourceMock()
39 | let content = PlistReaderContentMock()
40 | let format = PropertyListSerialization.PropertyListFormat.xml
41 | let serialization = PropertyListSerialization.self
42 | let isSucceeded: Bool
43 |
44 | resource.expectedPath = "path"
45 | content.expectedData = nil
46 |
47 | do {
48 | let _ = try PlistReader(name: "", resource: resource, content: content, format: format, serialization: serialization)
49 | isSucceeded = false
50 |
51 | } catch PlistReaderError.noContent {
52 | isSucceeded = true
53 |
54 | } catch {
55 | isSucceeded = false
56 | }
57 |
58 | XCTAssertTrue(isSucceeded)
59 | }
60 |
61 | func testInitWithErrorUnreadable() {
62 | let resource = PlistReaderResourceMock()
63 | let content = PlistReaderContentMock()
64 | let format = PropertyListSerialization.PropertyListFormat.xml
65 | let serialization = PlistSerializationMock.self
66 | let isSucceeded: Bool
67 |
68 | resource.expectedPath = "path"
69 | content.expectedData = Data(bytes: [1,2,3])
70 | serialization.expectedInfo = nil
71 |
72 | do {
73 | let _ = try PlistReader(name: "", resource: resource, content: content, format: format, serialization: serialization)
74 | isSucceeded = false
75 |
76 | } catch PlistReaderError.unreadable {
77 | isSucceeded = true
78 |
79 | } catch {
80 | isSucceeded = false
81 | }
82 |
83 | XCTAssertTrue(isSucceeded)
84 | }
85 |
86 | func testInitWithNoErrors() {
87 | let resource = PlistReaderResourceMock()
88 | let content = PlistReaderContentMock()
89 | let format = PropertyListSerialization.PropertyListFormat.xml
90 | let serialization = PlistSerializationMock.self
91 | let isSucceeded: Bool
92 |
93 | resource.expectedPath = "path"
94 | content.expectedData = Data(bytes: [1,2,3])
95 | serialization.expectedInfo = ["name": "Nina"]
96 |
97 | do {
98 | let reader = try PlistReader(name: "", resource: resource, content: content, format: format, serialization: serialization)
99 | XCTAssertNotNil(reader.data)
100 | XCTAssertTrue(reader.data is [String: String])
101 | let readerInfo = reader.data as! [String: String]
102 | XCTAssertEqual(readerInfo.count, 1)
103 | XCTAssertEqual(readerInfo["name"], "Nina")
104 | isSucceeded = true
105 |
106 | } catch {
107 | isSucceeded = false
108 | }
109 |
110 | XCTAssertTrue(isSucceeded)
111 | }
112 |
113 | func testCreate() {
114 | let bundle = Bundle(for: type(of: self))
115 | var reader = PlistReader.create(name: "PlistReaderSample", bundle: bundle)
116 | XCTAssertNotNil(reader)
117 |
118 | reader = PlistReader.create(name: "PlistReader-NonExisting")
119 | XCTAssertNil(reader)
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/PyrobaseTests/PlistSerializationMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlistSerializationMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 09/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class PlistSerializationMock: PropertyListSerialization {
12 |
13 | enum MockError: Error {
14 |
15 | case nilExpectedInfo
16 | }
17 |
18 | static var expectedInfo: Any?
19 |
20 | override class func propertyList(from data: Data, options opt: PropertyListSerialization.ReadOptions = [], format: UnsafeMutablePointer?) throws -> Any {
21 | guard expectedInfo != nil else {
22 | throw MockError.nilExpectedInfo
23 | }
24 |
25 | return expectedInfo!
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroAuthContentTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroAuthContentTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 13/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyroAuthContentTest: XCTestCase {
13 |
14 | func testDescription() {
15 | let accessToken = "accessToken"
16 | let expiration = "3600"
17 | let refreshToken = "refreshToken"
18 | let userId = "me12345"
19 | let email = "me@me.com"
20 |
21 | var content = PyroAuthContent()
22 | content.accessToken = accessToken
23 | content.expiration = expiration
24 | content.refreshToken = refreshToken
25 | content.userId = userId
26 | content.email = email
27 |
28 | let expectedDescription = "userId: \(userId)\naccessToken: \(accessToken)\nemail: \(email)\nrefreshToken: \(refreshToken)\nexpiration: \(expiration)"
29 | XCTAssertEqual(content.description, expectedDescription)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroAuthMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroAuthMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 14/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | @testable import Pyrobase
10 |
11 | class PyroAuthMock: PyroAuth {
12 |
13 | static var defaultRequest: RequestProtocol = Request.create()
14 | static var defaultBundleIdentifier: String = ""
15 | static var defaultPlistName: String = ""
16 |
17 | override class func create(key: String, bundleIdentifier: String = "com.ner.Pyrobase", plistName: String = "PyroAuthInfo", request: RequestProtocol = Request.create() ) -> PyroAuth? {
18 | self.defaultRequest = request
19 | self.defaultBundleIdentifier = bundleIdentifier
20 | self.defaultPlistName = plistName
21 |
22 | return super.create(key: key, bundleIdentifier: bundleIdentifier, plistName: plistName, request: request)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroAuthTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroAuthTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 09/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyroAuthTest: XCTestCase {
13 |
14 | func testCreate() {
15 | let key: String = "api_key"
16 |
17 | var bundle: Bundle = .main
18 | var bundleIdentifier: String = bundle.bundleIdentifier ?? ""
19 | var auth = PyroAuth.create(key: key, bundleIdentifier: bundleIdentifier)
20 | XCTAssertNil(auth) // This is nil because Bundle.main's path is different when testing
21 |
22 | auth = PyroAuth.create(key: key)
23 | XCTAssertNotNil(auth)
24 | XCTAssertEqual(auth!.registerPath, "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser")
25 | XCTAssertEqual(auth!.signInPath, "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword")
26 | XCTAssertEqual(auth!.refreshPath, "https://securetoken.googleapis.com/v1/token")
27 | XCTAssertEqual(auth!.confirmationCodePath, "https://www.googleapis.com/identitytoolkit/v3/relyingparty/getOobConfirmationCode")
28 |
29 | bundle = Bundle(for: type(of: self))
30 | bundleIdentifier = bundle.bundleIdentifier ?? ""
31 | auth = PyroAuth.create(key: key, bundleIdentifier: bundleIdentifier, plistName: "PlistReaderSample")
32 | XCTAssertTrue(auth!.signInPath.isEmpty)
33 | XCTAssertTrue(auth!.registerPath.isEmpty)
34 | XCTAssertTrue(auth!.refreshPath.isEmpty)
35 | XCTAssertTrue(auth!.confirmationCodePath.isEmpty)
36 |
37 | auth = PyroAuth.create(key: key, bundleIdentifier: bundleIdentifier, plistName: "Plist-NonExisting")
38 | XCTAssertNil(auth)
39 |
40 | auth = PyroAuthMock.create(key: key)
41 | XCTAssertNotNil(auth)
42 | XCTAssertTrue(PyroAuthMock.defaultRequest is Request)
43 | XCTAssertEqual(PyroAuthMock.defaultPlistName, "PyroAuthInfo")
44 | XCTAssertEqual(PyroAuthMock.defaultBundleIdentifier, "com.ner.Pyrobase")
45 | }
46 |
47 | func testRegisterBeforeRequestIsTriggered() {
48 | let apiKey = "api_key"
49 | let email: String = "me@me.com"
50 | let password: String = "12345"
51 |
52 | let request = AuthRequestMock()
53 | let auth = PyroAuth.create(key: apiKey, request: request)!
54 |
55 | let expectation1 = expectation(description: "testRegister")
56 |
57 | auth.register(email: email, password: password) { _ in
58 | let expectedWriteData: [AnyHashable: Any] = [
59 | "email": email,
60 | "password": password,
61 | "returnSecureToken": true
62 | ]
63 |
64 | let expectedWriteURLPath: String = "\(auth.registerPath)?key=\(apiKey)"
65 | let expectedMethod: RequestMethod = .post
66 |
67 | XCTAssertEqual(expectedWriteData.count, request.writeData.count)
68 | XCTAssertEqual(expectedWriteData["email"] as! String, request.writeData["email"] as! String)
69 | XCTAssertEqual(expectedWriteData["password"] as! String, request.writeData["password"] as! String)
70 | XCTAssertEqual(expectedWriteData["returnSecureToken"] as! Bool, request.writeData["returnSecureToken"] as! Bool)
71 | XCTAssertEqual(expectedWriteURLPath, request.writeURLPath)
72 | XCTAssertTrue(expectedMethod == request.writeMethod)
73 |
74 | expectation1.fulfill()
75 | }
76 |
77 | waitForExpectations(timeout: 1)
78 | }
79 |
80 | func testRegisterWithNoError() {
81 | let apiKey = "api_key"
82 | let email: String = "me@me.com"
83 | let password: String = "12345"
84 |
85 | let session = URLSessionMock()
86 | let operation = JSONRequestOperation.create()
87 | let response = RequestResponse()
88 | let request = Request(session: session, operation: operation, response: response)
89 | let auth = PyroAuth.create(key: apiKey, request: request)!
90 |
91 | let taskResult = URLSessionDataTaskMock.Result()
92 | let expectation1 = expectation(description: "testRegister")
93 | let expectedContent = [
94 | "email": email,
95 | "idToken": "poiuy1820ghjfk",
96 | "localId": "userId12345",
97 | "refreshToken": "qwert8907zxcv",
98 | "expiresIn": "3600"
99 | ]
100 |
101 | taskResult.response = URLResponse()
102 | taskResult.data = try? JSONSerialization.data(withJSONObject: expectedContent, options: [])
103 | session.expectedDataTaskResult = taskResult
104 |
105 | auth.register(email: email, password: password) { result in
106 | switch result {
107 | case .failed:
108 | XCTFail()
109 |
110 | case .succeeded(let content):
111 | XCTAssertFalse(content.userId.isEmpty)
112 | XCTAssertFalse(content.email.isEmpty)
113 | XCTAssertFalse(content.accessToken.isEmpty)
114 | XCTAssertFalse(content.refreshToken.isEmpty)
115 | XCTAssertFalse(content.expiration.isEmpty)
116 |
117 | XCTAssertEqual(content.userId, expectedContent["localId"])
118 | XCTAssertEqual(content.email, expectedContent["email"])
119 | XCTAssertEqual(content.accessToken, expectedContent["idToken"])
120 | XCTAssertEqual(content.refreshToken, expectedContent["refreshToken"])
121 | XCTAssertEqual(content.expiration, expectedContent["expiresIn"])
122 | }
123 | expectation1.fulfill()
124 | }
125 |
126 | waitForExpectations(timeout: 1)
127 | }
128 |
129 | func testRegisterWithUnexpectedContentError() {
130 | let apiKey = "api_key"
131 | let email: String = "me@me.com"
132 | let password: String = "12345"
133 |
134 | let session = URLSessionMock()
135 | let operation = JSONRequestOperation.create()
136 | let response = RequestResponse()
137 | let request = Request(session: session, operation: operation, response: response)
138 | let auth = PyroAuth.create(key: apiKey, request: request)!
139 |
140 | let taskResult = URLSessionDataTaskMock.Result()
141 | let expectation1 = expectation(description: "testRegister")
142 |
143 | taskResult.response = URLResponse()
144 | taskResult.data = "success".data(using: .utf8)
145 | session.expectedDataTaskResult = taskResult
146 |
147 | auth.register(email: email, password: password) { result in
148 | switch result {
149 | case .succeeded:
150 | XCTFail()
151 |
152 | case .failed(let info):
153 | XCTAssertTrue(info is PyroAuthError)
154 | let errorInfo = info as! PyroAuthError
155 | XCTAssertTrue(errorInfo == PyroAuthError.unexpectedContent)
156 | }
157 | expectation1.fulfill()
158 | }
159 |
160 | waitForExpectations(timeout: 1)
161 | }
162 |
163 | func testRegisterWithIncompleteContentError() {
164 | let apiKey = "api_key"
165 | let email: String = "me@me.com"
166 | let password: String = "12345"
167 |
168 | let session = URLSessionMock()
169 | let operation = JSONRequestOperation.create()
170 | let response = RequestResponse()
171 | let request = Request(session: session, operation: operation, response: response)
172 | let auth = PyroAuth.create(key: apiKey, request: request)!
173 |
174 | let taskResult = URLSessionDataTaskMock.Result()
175 | let expectation1 = expectation(description: "testRegister")
176 | let expectedContent = [
177 | "email": email,
178 | "idToken": "poiuy1820ghjfk",
179 | ]
180 |
181 | taskResult.response = URLResponse()
182 | taskResult.data = try? JSONSerialization.data(withJSONObject: expectedContent, options: [])
183 | session.expectedDataTaskResult = taskResult
184 |
185 | auth.register(email: email, password: password) { result in
186 | switch result {
187 | case .succeeded:
188 | XCTFail()
189 |
190 | case .failed(let info):
191 | XCTAssertTrue(info is PyroAuthError)
192 | let errorInfo = info as! PyroAuthError
193 | XCTAssertTrue(errorInfo == PyroAuthError.incompleteContent)
194 | }
195 | expectation1.fulfill()
196 | }
197 |
198 | waitForExpectations(timeout: 1)
199 | }
200 |
201 | func testRegisterWithCustomError() {
202 | let apiKey = "api_key"
203 | let email: String = "me@me.com"
204 | let password: String = "12345"
205 |
206 | let session = URLSessionMock()
207 | let operation = JSONRequestOperation.create()
208 | let response = RequestResponse()
209 | let request = Request(session: session, operation: operation, response: response)
210 | let auth = PyroAuth.create(key: apiKey, request: request)!
211 |
212 | let taskResult = URLSessionDataTaskMock.Result()
213 | let expectation1 = expectation(description: "testRegister")
214 |
215 | taskResult.error = URLSessionDataTaskMock.TaskMockError.mockError1
216 | session.expectedDataTaskResult = taskResult
217 |
218 | auth.register(email: email, password: password) { result in
219 | switch result {
220 | case .succeeded:
221 | XCTFail()
222 |
223 | case .failed(let info):
224 | XCTAssertTrue(info is URLSessionDataTaskMock.TaskMockError)
225 | let errorInfo = info as! URLSessionDataTaskMock.TaskMockError
226 | XCTAssertTrue(errorInfo == URLSessionDataTaskMock.TaskMockError.mockError1)
227 | }
228 | expectation1.fulfill()
229 | }
230 |
231 | waitForExpectations(timeout: 1)
232 | }
233 |
234 | func testSignInBeforeRequestIsTriggered() {
235 | let apiKey = "api_key"
236 | let email: String = "me@me.com"
237 | let password: String = "12345"
238 |
239 | let request = AuthRequestMock()
240 | let auth = PyroAuth.create(key: apiKey, request: request)!
241 |
242 | let expectation1 = expectation(description: "testSignIn")
243 |
244 | auth.signIn(email: email, password: password) { _ in
245 | let expectedWriteData: [AnyHashable: Any] = [
246 | "email": email,
247 | "password": password,
248 | "returnSecureToken": true
249 | ]
250 |
251 | let expectedWriteURLPath: String = "\(auth.signInPath)?key=\(apiKey)"
252 | let expectedMethod: RequestMethod = .post
253 |
254 | XCTAssertEqual(expectedWriteData.count, request.writeData.count)
255 | XCTAssertEqual(expectedWriteData["email"] as! String, request.writeData["email"] as! String)
256 | XCTAssertEqual(expectedWriteData["password"] as! String, request.writeData["password"] as! String)
257 | XCTAssertEqual(expectedWriteData["returnSecureToken"] as! Bool, request.writeData["returnSecureToken"] as! Bool)
258 | XCTAssertEqual(expectedWriteURLPath, request.writeURLPath)
259 | XCTAssertTrue(expectedMethod == request.writeMethod)
260 |
261 | expectation1.fulfill()
262 | }
263 |
264 | waitForExpectations(timeout: 1)
265 | }
266 |
267 | func testSignInWithNoError() {
268 | let apiKey = "api_key"
269 | let email: String = "me@me.com"
270 | let password: String = "12345"
271 |
272 | let session = URLSessionMock()
273 | let operation = JSONRequestOperation.create()
274 | let response = RequestResponse()
275 | let request = Request(session: session, operation: operation, response: response)
276 | let auth = PyroAuth.create(key: apiKey, request: request)!
277 |
278 | let taskResult = URLSessionDataTaskMock.Result()
279 | let expectation1 = expectation(description: "testSignIn")
280 | let expectedContent = [
281 | "email": email,
282 | "idToken": "poiuy1820ghjfk",
283 | "localId": "userId12345",
284 | "refreshToken": "qwert8907zxcv",
285 | "expiresIn": "3600"
286 | ]
287 |
288 | taskResult.response = URLResponse()
289 | taskResult.data = try? JSONSerialization.data(withJSONObject: expectedContent, options: [])
290 | session.expectedDataTaskResult = taskResult
291 |
292 | auth.signIn(email: email, password: password) { result in
293 | switch result {
294 | case .failed:
295 | XCTFail()
296 |
297 | case .succeeded(let content):
298 | XCTAssertFalse(content.userId.isEmpty)
299 | XCTAssertFalse(content.email.isEmpty)
300 | XCTAssertFalse(content.accessToken.isEmpty)
301 | XCTAssertFalse(content.refreshToken.isEmpty)
302 | XCTAssertFalse(content.expiration.isEmpty)
303 |
304 | XCTAssertEqual(content.userId, expectedContent["localId"])
305 | XCTAssertEqual(content.email, expectedContent["email"])
306 | XCTAssertEqual(content.accessToken, expectedContent["idToken"])
307 | XCTAssertEqual(content.refreshToken, expectedContent["refreshToken"])
308 | XCTAssertEqual(content.expiration, expectedContent["expiresIn"])
309 | }
310 | expectation1.fulfill()
311 | }
312 |
313 | waitForExpectations(timeout: 1)
314 | }
315 |
316 | func testSignInWithUnexpectedContentError() {
317 | let apiKey = "api_key"
318 | let email: String = "me@me.com"
319 | let password: String = "12345"
320 |
321 | let session = URLSessionMock()
322 | let operation = JSONRequestOperation.create()
323 | let response = RequestResponse()
324 | let request = Request(session: session, operation: operation, response: response)
325 | let auth = PyroAuth.create(key: apiKey, request: request)!
326 |
327 | let taskResult = URLSessionDataTaskMock.Result()
328 | let expectation1 = expectation(description: "testSignIn")
329 |
330 | taskResult.response = URLResponse()
331 | taskResult.data = "success".data(using: .utf8)
332 | session.expectedDataTaskResult = taskResult
333 |
334 | auth.signIn(email: email, password: password) { result in
335 | switch result {
336 | case .succeeded:
337 | XCTFail()
338 |
339 | case .failed(let info):
340 | XCTAssertTrue(info is PyroAuthError)
341 | let errorInfo = info as! PyroAuthError
342 | XCTAssertTrue(errorInfo == PyroAuthError.unexpectedContent)
343 | }
344 | expectation1.fulfill()
345 | }
346 |
347 | waitForExpectations(timeout: 1)
348 | }
349 |
350 | func testSignInWithIncompleteContentError() {
351 | let apiKey = "api_key"
352 | let email: String = "me@me.com"
353 | let password: String = "12345"
354 |
355 | let session = URLSessionMock()
356 | let operation = JSONRequestOperation.create()
357 | let response = RequestResponse()
358 | let request = Request(session: session, operation: operation, response: response)
359 | let auth = PyroAuth.create(key: apiKey, request: request)!
360 |
361 | let taskResult = URLSessionDataTaskMock.Result()
362 | let expectation1 = expectation(description: "testSignIn")
363 | let expectedContent = [
364 | "email": email,
365 | "idToken": "poiuy1820ghjfk",
366 | ]
367 |
368 | taskResult.response = URLResponse()
369 | taskResult.data = try? JSONSerialization.data(withJSONObject: expectedContent, options: [])
370 | session.expectedDataTaskResult = taskResult
371 |
372 | auth.signIn(email: email, password: password) { result in
373 | switch result {
374 | case .succeeded:
375 | XCTFail()
376 |
377 | case .failed(let info):
378 | XCTAssertTrue(info is PyroAuthError)
379 | let errorInfo = info as! PyroAuthError
380 | XCTAssertTrue(errorInfo == PyroAuthError.incompleteContent)
381 | }
382 | expectation1.fulfill()
383 | }
384 |
385 | waitForExpectations(timeout: 1)
386 | }
387 |
388 | func testSignInWithCustomError() {
389 | let apiKey = "api_key"
390 | let email: String = "me@me.com"
391 | let password: String = "12345"
392 |
393 | let session = URLSessionMock()
394 | let operation = JSONRequestOperation.create()
395 | let response = RequestResponse()
396 | let request = Request(session: session, operation: operation, response: response)
397 | let auth = PyroAuth.create(key: apiKey, request: request)!
398 |
399 | let taskResult = URLSessionDataTaskMock.Result()
400 | let expectation1 = expectation(description: "testSignIn")
401 |
402 | taskResult.error = URLSessionDataTaskMock.TaskMockError.mockError1
403 | session.expectedDataTaskResult = taskResult
404 |
405 | auth.signIn(email: email, password: password) { result in
406 | switch result {
407 | case .succeeded:
408 | XCTFail()
409 |
410 | case .failed(let info):
411 | XCTAssertTrue(info is URLSessionDataTaskMock.TaskMockError)
412 | let errorInfo = info as! URLSessionDataTaskMock.TaskMockError
413 | XCTAssertTrue(errorInfo == URLSessionDataTaskMock.TaskMockError.mockError1)
414 | }
415 | expectation1.fulfill()
416 | }
417 |
418 | waitForExpectations(timeout: 1)
419 | }
420 |
421 | func testRefreshBeforeRequestIsTriggered() {
422 | let apiKey = "api_key"
423 | let refreshToken: String = "refresh_token"
424 |
425 | let request = AuthRequestMock()
426 | let auth = PyroAuth.create(key: apiKey, request: request)!
427 |
428 | let expectation1 = expectation(description: "testRefresh")
429 |
430 | auth.refresh(token: refreshToken) { _ in
431 | let expectedWriteData: [AnyHashable: Any] = [
432 | "grant_type": "refresh_token",
433 | "refresh_token": refreshToken
434 | ]
435 |
436 | let expectedWriteURLPath: String = "\(auth.refreshPath)?key=\(apiKey)"
437 | let expectedMethod: RequestMethod = .post
438 |
439 | XCTAssertEqual(expectedWriteData.count, request.writeData.count)
440 | XCTAssertEqual(expectedWriteData["grant_type"] as! String, request.writeData["grant_type"] as! String)
441 | XCTAssertEqual(expectedWriteData["refresh_token"] as! String, request.writeData["refresh_token"] as! String)
442 | XCTAssertEqual(expectedWriteURLPath, request.writeURLPath)
443 | XCTAssertTrue(expectedMethod == request.writeMethod)
444 |
445 | expectation1.fulfill()
446 | }
447 |
448 | waitForExpectations(timeout: 1)
449 | }
450 |
451 | func testRefreshWithNoError() {
452 | let apiKey = "api_key"
453 | let refreshToken = "refresh_token"
454 |
455 | let session = URLSessionMock()
456 | let operation = JSONRequestOperation.create()
457 | let response = RequestResponse()
458 | let request = Request(session: session, operation: operation, response: response)
459 | let auth = PyroAuth.create(key: apiKey, request: request)!
460 |
461 | let taskResult = URLSessionDataTaskMock.Result()
462 | let expectation1 = expectation(description: "testRefresh")
463 | let expectedContent = [
464 | "access_token": "poiuy1820ghjfk",
465 | "refresh_token": "qwert8907zxcv",
466 | "expires_in": "3600"
467 | ]
468 |
469 | taskResult.response = URLResponse()
470 | taskResult.data = try? JSONSerialization.data(withJSONObject: expectedContent, options: [])
471 | session.expectedDataTaskResult = taskResult
472 |
473 | auth.refresh(token: refreshToken) { result in
474 | switch result {
475 | case .failed:
476 | XCTFail()
477 |
478 | case .succeeded(let content):
479 | XCTAssertFalse(content.accessToken.isEmpty)
480 | XCTAssertFalse(content.refreshToken.isEmpty)
481 | XCTAssertFalse(content.expiration.isEmpty)
482 |
483 | XCTAssertEqual(content.accessToken, expectedContent["access_token"])
484 | XCTAssertEqual(content.refreshToken, expectedContent["refresh_token"])
485 | XCTAssertEqual(content.expiration, expectedContent["expires_in"])
486 | }
487 | expectation1.fulfill()
488 | }
489 |
490 | waitForExpectations(timeout: 1)
491 | }
492 |
493 | func testRefreshWithUnexpectedContentError() {
494 | let apiKey = "api_key"
495 | let refreshToken = "refresh_token"
496 |
497 | let session = URLSessionMock()
498 | let operation = JSONRequestOperation.create()
499 | let response = RequestResponse()
500 | let request = Request(session: session, operation: operation, response: response)
501 | let auth = PyroAuth.create(key: apiKey, request: request)!
502 |
503 | let taskResult = URLSessionDataTaskMock.Result()
504 | let expectation1 = expectation(description: "testRefresh")
505 |
506 | taskResult.response = URLResponse()
507 | taskResult.data = "success".data(using: .utf8)
508 | session.expectedDataTaskResult = taskResult
509 |
510 | auth.refresh(token: refreshToken) { result in
511 | switch result {
512 | case .succeeded:
513 | XCTFail()
514 |
515 | case .failed(let info):
516 | XCTAssertTrue(info is PyroAuthError)
517 | let errorInfo = info as! PyroAuthError
518 | XCTAssertTrue(errorInfo == PyroAuthError.unexpectedContent)
519 | }
520 | expectation1.fulfill()
521 | }
522 |
523 | waitForExpectations(timeout: 1)
524 | }
525 |
526 | func testRefreshWithIncompleteContentError() {
527 | let apiKey = "api_key"
528 | let refreshToken = "refresh_token"
529 |
530 | let session = URLSessionMock()
531 | let operation = JSONRequestOperation.create()
532 | let response = RequestResponse()
533 | let request = Request(session: session, operation: operation, response: response)
534 | let auth = PyroAuth.create(key: apiKey, request: request)!
535 |
536 | let taskResult = URLSessionDataTaskMock.Result()
537 | let expectation1 = expectation(description: "testRefresh")
538 | let expectedContent = [
539 | "access_token": "poiuy1820ghjfk",
540 | ]
541 |
542 | taskResult.response = URLResponse()
543 | taskResult.data = try? JSONSerialization.data(withJSONObject: expectedContent, options: [])
544 | session.expectedDataTaskResult = taskResult
545 |
546 | auth.refresh(token: refreshToken) { result in
547 | switch result {
548 | case .succeeded:
549 | XCTFail()
550 |
551 | case .failed(let info):
552 | XCTAssertTrue(info is PyroAuthError)
553 | let errorInfo = info as! PyroAuthError
554 | XCTAssertTrue(errorInfo == PyroAuthError.incompleteContent)
555 | }
556 | expectation1.fulfill()
557 | }
558 |
559 | waitForExpectations(timeout: 1)
560 | }
561 |
562 | func testRefreshWithCustomError() {
563 | let apiKey = "api_key"
564 | let refreshToken = "refresh_token"
565 |
566 | let session = URLSessionMock()
567 | let operation = JSONRequestOperation.create()
568 | let response = RequestResponse()
569 | let request = Request(session: session, operation: operation, response: response)
570 | let auth = PyroAuth.create(key: apiKey, request: request)!
571 |
572 | let taskResult = URLSessionDataTaskMock.Result()
573 | let expectation1 = expectation(description: "testRefresh")
574 |
575 | taskResult.error = URLSessionDataTaskMock.TaskMockError.mockError1
576 | session.expectedDataTaskResult = taskResult
577 |
578 | auth.refresh(token: refreshToken) { result in
579 | switch result {
580 | case .succeeded:
581 | XCTFail()
582 |
583 | case .failed(let info):
584 | XCTAssertTrue(info is URLSessionDataTaskMock.TaskMockError)
585 | let errorInfo = info as! URLSessionDataTaskMock.TaskMockError
586 | XCTAssertTrue(errorInfo == URLSessionDataTaskMock.TaskMockError.mockError1)
587 | }
588 | expectation1.fulfill()
589 | }
590 |
591 | waitForExpectations(timeout: 1)
592 | }
593 |
594 | func testSendPasswordResetBeforeRequestIsTriggered() {
595 | let apiKey = "api_key"
596 | let email: String = "me@me.com"
597 |
598 | let request = AuthRequestMock()
599 | let auth = PyroAuth.create(key: apiKey, request: request)!
600 |
601 | let expectation1 = expectation(description: "testSendPasswordReset")
602 |
603 | auth.sendPasswordReset(email: email) { _ in
604 | let expectedWriteData: [AnyHashable: Any] = [
605 | "email": email,
606 | "requestType": "PASSWORD_RESET"
607 | ]
608 |
609 | let expectedWriteURLPath: String = "\(auth.confirmationCodePath)?key=\(apiKey)"
610 | let expectedMethod: RequestMethod = .post
611 |
612 | XCTAssertEqual(expectedWriteData.count, request.writeData.count)
613 | XCTAssertEqual(expectedWriteData["email"] as! String, request.writeData["email"] as! String)
614 | XCTAssertEqual(expectedWriteData["requestType"] as! String, request.writeData["requestType"] as! String)
615 | XCTAssertEqual(expectedWriteURLPath, request.writeURLPath)
616 | XCTAssertTrue(expectedMethod == request.writeMethod)
617 |
618 | expectation1.fulfill()
619 | }
620 |
621 | waitForExpectations(timeout: 1)
622 | }
623 |
624 | func testSendPasswordResetWithNoError() {
625 | let apiKey = "api_key"
626 | let email = "me@me.com"
627 |
628 | let session = URLSessionMock()
629 | let operation = JSONRequestOperation.create()
630 | let response = RequestResponse()
631 | let request = Request(session: session, operation: operation, response: response)
632 | let auth = PyroAuth.create(key: apiKey, request: request)!
633 |
634 | let taskResult = URLSessionDataTaskMock.Result()
635 | let expectation1 = expectation(description: "testSendPasswordReset")
636 |
637 | taskResult.response = URLResponse()
638 | taskResult.data = Data(bytes: [1,2,3])
639 | session.expectedDataTaskResult = taskResult
640 |
641 | auth.sendPasswordReset(email: email) { result in
642 | switch result {
643 | case .failed:
644 | XCTFail()
645 |
646 | case .succeeded(let isSuccess):
647 | XCTAssertTrue(isSuccess)
648 | }
649 | expectation1.fulfill()
650 | }
651 |
652 | waitForExpectations(timeout: 1)
653 | }
654 |
655 | func testSendPasswordResetWithCustomError() {
656 | let apiKey = "api_key"
657 | let email = "me@me.com"
658 |
659 | let session = URLSessionMock()
660 | let operation = JSONRequestOperation.create()
661 | let response = RequestResponse()
662 | let request = Request(session: session, operation: operation, response: response)
663 | let auth = PyroAuth.create(key: apiKey, request: request)!
664 |
665 | let taskResult = URLSessionDataTaskMock.Result()
666 | let expectation1 = expectation(description: "testSendPasswordReset")
667 |
668 | taskResult.error = URLSessionDataTaskMock.TaskMockError.mockError1
669 | session.expectedDataTaskResult = taskResult
670 |
671 | auth.sendPasswordReset(email: email) { result in
672 | switch result {
673 | case .succeeded:
674 | XCTFail()
675 |
676 | case .failed(let info):
677 | XCTAssertTrue(info is URLSessionDataTaskMock.TaskMockError)
678 | let errorInfo = info as! URLSessionDataTaskMock.TaskMockError
679 | XCTAssertTrue(errorInfo == URLSessionDataTaskMock.TaskMockError.mockError1)
680 | }
681 | expectation1.fulfill()
682 | }
683 |
684 | waitForExpectations(timeout: 1)
685 | }
686 |
687 | }
688 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroAuthTokenContentTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroAuthTokenContentTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 13/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyroAuthTokenContentTest: XCTestCase {
13 |
14 | func testDescription() {
15 | let accessToken = "accessToken"
16 | let expiration = "3600"
17 | let refreshToken = "refreshToken"
18 |
19 | var content = PyroAuthTokenContent()
20 | content.accessToken = accessToken
21 | content.expiration = expiration
22 | content.refreshToken = refreshToken
23 |
24 | let expectedDescription = "accessToken: \(accessToken)\nexpiration: \(expiration)\nrefreshToken: \(refreshToken)"
25 | XCTAssertEqual(content.description, expectedDescription)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroEventSourceMessageTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroEventSourceMessageTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 23/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyroEventSourceMessageTest: XCTestCase {
13 |
14 | func testDescription() {
15 | var message = PyroEventSourceMessage()
16 | message.id = "1"
17 | message.event = "put"
18 | message.data = "hello world"
19 | XCTAssertEqual(message.description, "\nid: 1\nevent: put\ndata: hello world\n")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroEventSourceParserTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroEventSourceParserTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 23/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyroEventSourceParserTest: XCTestCase {
13 |
14 | func testParseWithDataNotUTF8Encoded() {
15 | let parser = PyroEventSourceParser()
16 | let data = "id: 1\nevent: put\ndata: hello world\n\n".data(using: .utf32)!
17 | let message = parser.parse(data)
18 | XCTAssertTrue(message.id.isEmpty)
19 | XCTAssertTrue(message.event.isEmpty)
20 | XCTAssertTrue(message.data.isEmpty)
21 | }
22 |
23 | func testParseWithEmptyEventString() {
24 | let parser = PyroEventSourceParser()
25 | let data = "\n\n\n\n".data(using: .utf8)!
26 | let message = parser.parse(data)
27 | XCTAssertTrue(message.id.isEmpty)
28 | XCTAssertTrue(message.event.isEmpty)
29 | XCTAssertTrue(message.data.isEmpty)
30 | }
31 |
32 | func testParseWithColonAsPrefix() {
33 | let parser = PyroEventSourceParser()
34 | let data = ":\n: wee\n: toinks\n\n".data(using: .utf8)!
35 | let message = parser.parse(data)
36 | XCTAssertTrue(message.id.isEmpty)
37 | XCTAssertTrue(message.event.isEmpty)
38 | XCTAssertTrue(message.data.isEmpty)
39 | }
40 |
41 | func testParseWithRetryString() {
42 | let parser = PyroEventSourceParser()
43 | var data = "retry: 3600\n\n".data(using: .utf8)!
44 | var message = parser.parse(data)
45 | XCTAssertTrue(message.id.isEmpty)
46 | XCTAssertTrue(message.event.isEmpty)
47 | XCTAssertTrue(message.data.isEmpty)
48 |
49 | data = "id: retry:\n\n".data(using: .utf8)!
50 | message = parser.parse(data)
51 | XCTAssertTrue(message.id.isEmpty)
52 | XCTAssertTrue(message.event.isEmpty)
53 | XCTAssertTrue(message.data.isEmpty)
54 | }
55 |
56 | func testParseWithNoColon() {
57 | let parser = PyroEventSourceParser()
58 | let data = "id\n\n".data(using: .utf8)!
59 | let message = parser.parse(data)
60 | XCTAssertTrue(message.id.isEmpty)
61 | XCTAssertTrue(message.event.isEmpty)
62 | XCTAssertTrue(message.data.isEmpty)
63 | }
64 |
65 | func testParseWithValidData() {
66 | let parser = PyroEventSourceParser()
67 | var data = "id: 1\nevent: put\ndata: hello world\n\n".data(using: .utf8)!
68 | var message = parser.parse(data)
69 | XCTAssertEqual(message.id, "1")
70 | XCTAssertEqual(message.event, "put")
71 | XCTAssertEqual(message.data, "hello world")
72 |
73 | data = "foo: 1\nbar: put\nwee: hello world\n\n".data(using: .utf8)!
74 | message = parser.parse(data)
75 | XCTAssertTrue(message.id.isEmpty)
76 | XCTAssertTrue(message.event.isEmpty)
77 | XCTAssertTrue(message.data.isEmpty)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroEventSourceSessionProviderMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroEventSourceSessionProviderMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 23/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | @testable import Pyrobase
10 |
11 | class PyroEventSourceSessionProviderMock: PyroEventSourceSessionProviderProtocol {
12 |
13 | var queue: OperationQueue = OperationQueue()
14 | var headers: [String: String] = [:]
15 |
16 | func createSession(for delegate: URLSessionDataDelegate, lastEventID: String) -> URLSession {
17 | let session = URLSessionMock()
18 | session.shouldExecuteTaskResume = false
19 | return session
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroEventSourceSessionProviderTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroEventSourceSessionProviderTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 23/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyroEventSourceSessionProviderTest: XCTestCase {
13 |
14 | func testCreate() {
15 | let provider = PyroEventSourceSessionProvider.create()
16 | XCTAssertEqual(provider.headers.count, 1)
17 | let accept = provider.headers["Accept"]
18 | XCTAssertNotNil(accept)
19 | XCTAssertEqual(accept!, "text/event-stream")
20 | }
21 |
22 | func testCreateSession() {
23 | let delegate = URLSessionDataDelegateMock()
24 | let provider = PyroEventSourceSessionProvider.create()
25 | var session = provider.createSession(for: delegate, lastEventID: "")
26 | XCTAssertNotNil(session.configuration.httpAdditionalHeaders)
27 | XCTAssertEqual(session.configuration.httpAdditionalHeaders as! [String: String], provider.headers)
28 | XCTAssertEqual(session.configuration.timeoutIntervalForRequest, TimeInterval(INT_MAX))
29 | XCTAssertEqual(session.configuration.timeoutIntervalForResource, TimeInterval(INT_MAX))
30 | XCTAssertTrue(session.delegate is URLSessionDataDelegateMock)
31 |
32 | XCTAssertNil(session.configuration.httpAdditionalHeaders!["Last-Event-Id"])
33 |
34 | let lastEventId = "12345"
35 | session = provider.createSession(for: delegate, lastEventID: lastEventId)
36 | XCTAssertNotNil(session.configuration.httpAdditionalHeaders!["Last-Event-Id"])
37 | XCTAssertEqual(session.configuration.httpAdditionalHeaders!["Last-Event-Id"] as! String, lastEventId)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroEventSourceTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroEventSourceTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 21/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyroEventSourceTest: XCTestCase {
13 |
14 | var eventSource: PyroEventSource!
15 |
16 | var isCalledOpenCallback: Bool = false
17 | var isCalledClosedCallback: Bool = false
18 | var isCalledConnectingCallback: Bool = false
19 |
20 | var expectedPyroEventSourceError: PyroEventSourceError?
21 | var expectedRequestError: RequestError?
22 | var expectedMessage: PyroEventSourceMessage?
23 |
24 | override func setUp() {
25 | isCalledOpenCallback = false
26 | isCalledClosedCallback = false
27 | isCalledConnectingCallback = false
28 | }
29 |
30 | func testCreate() {
31 | let baseURL = "https://foo.firebaseio.com"
32 | let accessToken = "accessToken"
33 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
34 |
35 | let path = eventSource.path as! RequestPath
36 |
37 | XCTAssertEqual(path.baseURL, baseURL)
38 | XCTAssertEqual(path.accessToken, accessToken)
39 |
40 | XCTAssertNil(eventSource.callback)
41 | XCTAssertNil(eventSource.session)
42 | XCTAssertTrue(eventSource.lastEventID.isEmpty)
43 |
44 | XCTAssertTrue(eventSource.parser is PyroEventSourceParser)
45 | XCTAssertTrue(eventSource.response is RequestResponse)
46 | XCTAssertTrue(eventSource.state == .closed)
47 |
48 | let lastEventID = "abcde12345qwert"
49 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken, lastEventID: lastEventID)
50 | XCTAssertEqual(eventSource.lastEventID, lastEventID)
51 | }
52 |
53 | func testClose() {
54 | let baseURL = "https://foo.firebaseio.com"
55 | let accessToken = "accessToken"
56 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
57 |
58 | eventSource.close()
59 |
60 | XCTAssertFalse(isCalledClosedCallback)
61 | XCTAssertNil(eventSource.session)
62 | XCTAssertTrue(eventSource.state == .closed)
63 |
64 | eventSource.callback = self
65 | eventSource.close()
66 | XCTAssertTrue(isCalledClosedCallback)
67 | }
68 |
69 | func testStateDidSetObserver() {
70 | let baseURL = "https://foo.firebaseio.com"
71 | let accessToken = "accessToken"
72 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
73 | eventSource.callback = self
74 | eventSource.state = .open
75 | eventSource.state = .closed
76 | eventSource.state = .connecting
77 |
78 | XCTAssertTrue(isCalledOpenCallback)
79 | XCTAssertTrue(isCalledClosedCallback)
80 | XCTAssertTrue(isCalledConnectingCallback)
81 | }
82 |
83 | func testIsForcedClose() {
84 | let baseURL = "https://foo.firebaseio.com"
85 | let accessToken = "accessToken"
86 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
87 | var httpResponse = HTTPURLResponse(url: URL(string: baseURL)!, statusCode: 204, httpVersion: nil, headerFields: nil)
88 | var isForcedClose = eventSource.isForcedClose(httpResponse)
89 | XCTAssertTrue(isForcedClose)
90 |
91 | httpResponse = HTTPURLResponse(url: URL(string: baseURL)!, statusCode: 200, httpVersion: nil, headerFields: nil)
92 | isForcedClose = eventSource.isForcedClose(httpResponse)
93 | XCTAssertFalse(isForcedClose)
94 | }
95 |
96 | func testStreamWithNotClosedError() {
97 | let baseURL = "https://foo.firebaseio.com"
98 | let accessToken = "accessToken"
99 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
100 | eventSource.callback = self
101 | expectedPyroEventSourceError = PyroEventSourceError.notClosed
102 |
103 | eventSource.state = .open
104 | eventSource.stream("users/abcde12345qwert")
105 |
106 | eventSource.state = .connecting
107 | eventSource.stream("users/abcde12345qwert")
108 | }
109 |
110 | func testStreamWithInvalidURLError() {
111 | let accessToken = "accessToken"
112 | eventSource = PyroEventSource.create(baseURL: "", accessToken: accessToken)
113 | eventSource.callback = self
114 | expectedRequestError = RequestError.invalidURL
115 |
116 | eventSource.stream("users/abcde12345qwert")
117 | eventSource.stream("")
118 | }
119 |
120 | func testStreamWithConnectingState() {
121 | let baseURL = "https://foo.firebaseio.com"
122 | let accessToken = "accessToken"
123 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
124 | eventSource.callback = self
125 | eventSource.stream("users/abcde12345qwert")
126 | XCTAssertTrue(eventSource.state == .connecting)
127 | XCTAssertNotNil(eventSource.session)
128 | }
129 |
130 | func testURLSessionDidReceiveDataWithNotOpenError() {
131 | let baseURL = "https://foo.firebaseio.com"
132 | let accessToken = "accessToken"
133 | let session = URLSession()
134 | let httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 200, httpVersion: nil, headerFields: nil)
135 | let task = URLSessionDataTaskMock(httpResponse: httpResponse)
136 | let data = Data(bytes: [0,1,1,2,3,5,8])
137 | expectedPyroEventSourceError = PyroEventSourceError.notOpen
138 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
139 | eventSource.callback = self
140 | eventSource.urlSession(session, dataTask: task, didReceive: data)
141 | }
142 |
143 | func testURLSessionDidReceiveDataWithForcedCloseError() {
144 | let baseURL = "https://foo.firebaseio.com"
145 | let accessToken = "accessToken"
146 | let session = URLSession()
147 | let httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 204, httpVersion: nil, headerFields: nil)
148 | let task = URLSessionDataTaskMock(httpResponse: httpResponse)
149 | let data = Data(bytes: [0,1,1,2,3,5,8])
150 | expectedPyroEventSourceError = PyroEventSourceError.forcedClose
151 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
152 | eventSource.state = .open
153 | eventSource.callback = self
154 | eventSource.urlSession(session, dataTask: task, didReceive: data)
155 | }
156 |
157 | func testURLSessionDidReceiveDataWithErroneousHTTPURLResponse() {
158 | let baseURL = "https://foo.firebaseio.com"
159 | let accessToken = "accessToken"
160 | let session = URLSession()
161 | let httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 500, httpVersion: nil, headerFields: nil)
162 | let task = URLSessionDataTaskMock(httpResponse: httpResponse)
163 | let data = Data(bytes: [0,1,1,2,3,5,8])
164 | expectedRequestError = RequestError.internalServiceError("")
165 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
166 | eventSource.state = .open
167 | eventSource.callback = self
168 | eventSource.urlSession(session, dataTask: task, didReceive: data)
169 | }
170 |
171 | func testURLSessionDidReceiveDataShouldCallOnReceiveMessage() {
172 | let baseURL = "https://foo.firebaseio.com"
173 | let accessToken = "accessToken"
174 | let session = URLSession()
175 | let httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 200, httpVersion: nil, headerFields: nil)
176 | let task = URLSessionDataTaskMock(httpResponse: httpResponse)
177 | let string = "id: 1\nevent: put\ndata: hello world\n\n"
178 | let data = string.data(using: .utf8)!
179 | expectedMessage = PyroEventSourceMessage()
180 | expectedMessage?.id = "1"
181 | expectedMessage?.event = "put"
182 | expectedMessage?.data = "hello world"
183 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
184 | eventSource.state = .open
185 | eventSource.callback = self
186 | eventSource.urlSession(session, dataTask: task, didReceive: data)
187 | XCTAssertEqual(eventSource.lastEventID, expectedMessage!.id)
188 | }
189 |
190 | func testURLSessionDidReceiveRepsonseWithErroneousHTTPURLResponse() {
191 | let baseURL = "https://foo.firebaseio.com"
192 | let accessToken = "accessToken"
193 | let session = URLSession()
194 | let httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 500, httpVersion: nil, headerFields: nil)
195 | let task = URLSessionDataTaskMock(httpResponse: httpResponse)
196 | let completion: (URLSession.ResponseDisposition) -> Void = { _ in }
197 | expectedRequestError = RequestError.internalServiceError("")
198 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
199 | eventSource.state = .open
200 | eventSource.callback = self
201 | eventSource.urlSession(session, dataTask: task, didReceive: httpResponse!, completionHandler: completion)
202 | }
203 |
204 | func testURLSessionDidReceiveResponseWithOpenState() {
205 | let baseURL = "https://foo.firebaseio.com"
206 | let accessToken = "accessToken"
207 | let session = URLSession()
208 | let httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 200, httpVersion: nil, headerFields: nil)
209 | let task = URLSessionDataTaskMock(httpResponse: httpResponse)
210 | let expectation1 = expectation(description: "testURLSessionDidReceiveResponse")
211 | let completion: (URLSession.ResponseDisposition) -> Void = { disposition in
212 | XCTAssertTrue(disposition == .allow)
213 | XCTAssertTrue(self.eventSource.state == .open)
214 | expectation1.fulfill()
215 | }
216 | expectedRequestError = RequestError.internalServiceError("")
217 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
218 | eventSource.state = .open
219 | eventSource.callback = self
220 | eventSource.urlSession(session, dataTask: task, didReceive: httpResponse!, completionHandler: completion)
221 | waitForExpectations(timeout: 10)
222 | }
223 |
224 | func testURLSessionDidCompleteWithForcedCloseError() {
225 | let baseURL = "https://foo.firebaseio.com"
226 | let accessToken = "accessToken"
227 | let session = URLSession()
228 | let httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 204, httpVersion: nil, headerFields: nil)
229 | let task = URLSessionDataTaskMock(httpResponse: httpResponse)
230 | expectedPyroEventSourceError = PyroEventSourceError.forcedClose
231 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
232 | eventSource.state = .open
233 | eventSource.callback = self
234 | eventSource.urlSession(session, task: task, didCompleteWithError: nil)
235 | }
236 |
237 | func testURLSessionDidCompleteWithNilError() {
238 | let baseURL = "https://foo.firebaseio.com"
239 | let accessToken = "accessToken"
240 | let session = URLSession()
241 | let httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 200, httpVersion: nil, headerFields: nil)
242 | let task = URLSessionDataTaskMock(httpResponse: httpResponse)
243 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
244 | eventSource.state = .open
245 | eventSource.callback = self
246 | eventSource.urlSession(session, task: task, didCompleteWithError: nil)
247 | }
248 |
249 | func testURLSessionDidCompleteWithErroneousHTTPURLResponse() {
250 | let baseURL = "https://foo.firebaseio.com"
251 | let accessToken = "accessToken"
252 | let session = URLSession()
253 | let httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 500, httpVersion: nil, headerFields: nil)
254 | let task = URLSessionDataTaskMock(httpResponse: httpResponse)
255 | expectedRequestError = RequestError.internalServiceError("")
256 | eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
257 | eventSource.state = .open
258 | eventSource.callback = self
259 | eventSource.urlSession(session, task: task, didCompleteWithError: nil)
260 | }
261 | }
262 |
263 | extension PyroEventSourceTest: PyroEventSourceCallback {
264 |
265 | func pyroEventSource(_ eventSource: PyroEventSource, didReceiveError error: Error) {
266 | XCTAssertTrue(eventSource == self.eventSource)
267 |
268 | if expectedPyroEventSourceError != nil {
269 | XCTAssertTrue(error is PyroEventSourceError)
270 | let eventSourceError = error as! PyroEventSourceError
271 | XCTAssertTrue(expectedPyroEventSourceError! == eventSourceError)
272 |
273 | switch eventSourceError {
274 | case .forcedClose:
275 | XCTAssertTrue(eventSource.state == .closed)
276 | XCTAssertNil(eventSource.session)
277 | default:
278 | break
279 | }
280 |
281 | } else if expectedRequestError != nil {
282 | XCTAssertTrue(error is RequestError)
283 | let requestError = error as! RequestError
284 | XCTAssertTrue(expectedRequestError! == requestError)
285 | XCTAssertTrue(eventSource.state == .closed)
286 | XCTAssertNil(eventSource.session)
287 | }
288 | }
289 |
290 | func pyroEventSource(_ eventSource: PyroEventSource, didReceiveMessage message: PyroEventSourceMessage) {
291 | XCTAssertTrue(eventSource == self.eventSource)
292 |
293 | if expectedMessage != nil {
294 | XCTAssertEqual(expectedMessage!.id, message.id)
295 | XCTAssertEqual(expectedMessage!.event, message.event)
296 | XCTAssertEqual(expectedMessage!.data, message.data)
297 | }
298 | }
299 |
300 | func pyroEventSourceOnOpen(_ eventSource: PyroEventSource) {
301 | XCTAssertTrue(eventSource == self.eventSource)
302 | XCTAssertTrue(eventSource.state == .open)
303 |
304 | isCalledOpenCallback = true
305 | }
306 |
307 | func pyroEventSourceOnClosed(_ eventSource: PyroEventSource) {
308 | XCTAssertTrue(eventSource == self.eventSource)
309 | XCTAssertTrue(eventSource.state == .closed)
310 |
311 | isCalledClosedCallback = true
312 | }
313 |
314 | func pyroEventSourceOnConnecting(_ eventSource: PyroEventSource) {
315 | XCTAssertTrue(eventSource == self.eventSource)
316 | XCTAssertTrue(eventSource.state == .connecting)
317 |
318 | isCalledConnectingCallback = true
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroTransactionElapsedTimeMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroTransactionElapsedTimeMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 20/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | @testable import Pyrobase
10 |
11 | class PyroTransactionElapsedTimeMock: PyroTransactionElapsedTimeProtocol {
12 |
13 | var startDate: Date = Date()
14 | var nowDate: Date = Date()
15 |
16 | var expectedSeconds: Int? = 0
17 |
18 | func seconds(from transactionDate: Date, to now: Date) -> Int? {
19 | startDate = transactionDate
20 | nowDate = now
21 | return expectedSeconds
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroTransactionMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroTransactionMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 20/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | @testable import Pyrobase
10 |
11 | class PyroTransactionMock: PyroTransaction {
12 |
13 | var shouldProceedWriteTransaction: Bool = true
14 | var shouldProceedReadChild: Bool = true
15 | var shouldProceedWriteChild: Bool = true
16 | var isDeleted: Bool = false
17 |
18 | var expectedSucceededInfo: Any?
19 |
20 | override func writeTransaction(param: Parameter) {
21 | guard !shouldProceedWriteTransaction else {
22 | super.writeTransaction(param: param)
23 | return
24 | }
25 |
26 | invokeCompletion(param)
27 | }
28 |
29 | override func readChild(param: Parameter) {
30 | guard !shouldProceedReadChild else {
31 | super.readChild(param: param)
32 | return
33 | }
34 |
35 | invokeCompletion(param)
36 | }
37 |
38 | override func writeChild(param: Parameter, info: Any) {
39 | guard !shouldProceedWriteChild else {
40 | super.writeChild(param: param, info: info)
41 | return
42 | }
43 |
44 | invokeCompletion(param)
45 | }
46 |
47 | override func deleteTransaction(param: Parameter, completion: @escaping (RequestResult) -> Void) {
48 | isDeleted = true
49 | completion(.succeeded(true))
50 | }
51 |
52 | func invokeCompletion(_ param: Parameter) {
53 | guard expectedSucceededInfo != nil else {
54 | param.completion(.succeeded(true))
55 | return
56 | }
57 |
58 | param.completion(.succeeded(expectedSucceededInfo!))
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroTransactionTemporaryPathMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroTransactionTemporaryPathMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 19/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | @testable import Pyrobase
10 |
11 | class PyroTransactionTemporaryPathMock: PyroTransactionTemporaryPathProtocol {
12 |
13 | var key: String = ""
14 | var expiration: UInt = 0
15 | var isExpired: Bool = false
16 |
17 | func isTransactionDateExpired(_ timestamp: Double = 0, now: Date) -> Bool {
18 | return isExpired
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroTransactionTemporaryPathTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroTransactionTemporaryPathTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 20/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyroTransactionTemporaryPathTest: XCTestCase {
13 |
14 | func testCreate() {
15 | let tempPath = PyroTransactionTemporaryPath.create()
16 | XCTAssertEqual(tempPath.expiration, 30)
17 | XCTAssertEqual(tempPath.key, "pyrobase_transactions")
18 | XCTAssertTrue(tempPath.elapsedTime is Calendar)
19 | XCTAssertEqual(tempPath.elapsedTime as! Calendar, Calendar.current)
20 | }
21 |
22 | func testIsTransactionDateExpiredWithCorrectTimestamp() {
23 | let elapsedTime = PyroTransactionElapsedTimeMock()
24 | let expiration: UInt = 10
25 | let key: String = "key"
26 | let now = Date()
27 | let tempPath = PyroTransactionTemporaryPath(key: key, expiration: expiration, elapsedTime: elapsedTime)
28 | let _ = tempPath.isTransactionDateExpired(1000, now: now)
29 | XCTAssertEqual(elapsedTime.startDate.timeIntervalSince1970, 1)
30 | }
31 |
32 | func testIsTransactionDateExpiredShouldReturnTrue() {
33 | let elapsedTime = PyroTransactionElapsedTimeMock()
34 | let expiration: UInt = 10
35 | let key: String = "key"
36 | let now = Date()
37 |
38 | var tempPath = PyroTransactionTemporaryPath(key: key, expiration: expiration, elapsedTime: elapsedTime)
39 |
40 | elapsedTime.expectedSeconds = 10
41 | var isExpired = tempPath.isTransactionDateExpired(1000, now: now)
42 | XCTAssertTrue(isExpired)
43 |
44 | elapsedTime.expectedSeconds = 11
45 | isExpired = tempPath.isTransactionDateExpired(1000, now: now)
46 | XCTAssertTrue(isExpired)
47 |
48 | tempPath = PyroTransactionTemporaryPath.create()
49 |
50 | var components = DateComponents()
51 | components.second = Int(tempPath.expiration)
52 | var futureDate = Calendar.current.date(byAdding: components, to: now)!
53 | isExpired = tempPath.isTransactionDateExpired(futureDate.timeIntervalSince1970 * 1000, now: now)
54 | XCTAssertTrue(isExpired)
55 |
56 | components.second = Int(tempPath.expiration + 1)
57 | futureDate = Calendar.current.date(byAdding: components, to: now)!
58 | isExpired = tempPath.isTransactionDateExpired(futureDate.timeIntervalSince1970 * 1000, now: now)
59 | XCTAssertTrue(isExpired)
60 | }
61 |
62 | func testIsTransactionDateExpiredShouldReturnFalse() {
63 | let elapsedTime = PyroTransactionElapsedTimeMock()
64 | let expiration: UInt = 10
65 | let key: String = "key"
66 | let now = Date()
67 |
68 | var tempPath = PyroTransactionTemporaryPath(key: key, expiration: expiration, elapsedTime: elapsedTime)
69 |
70 | elapsedTime.expectedSeconds = 9
71 | var isExpired = tempPath.isTransactionDateExpired(1000, now: now)
72 | XCTAssertFalse(isExpired)
73 |
74 | elapsedTime.expectedSeconds = 0
75 | isExpired = tempPath.isTransactionDateExpired(1000, now: now)
76 | XCTAssertFalse(isExpired)
77 |
78 | tempPath = PyroTransactionTemporaryPath.create()
79 |
80 | var components = DateComponents()
81 | components.second = Int(tempPath.expiration) - 1
82 | var futureDate = Calendar.current.date(byAdding: components, to: now)!
83 | isExpired = tempPath.isTransactionDateExpired(futureDate.timeIntervalSince1970 * 1000, now: now)
84 | XCTAssertFalse(isExpired)
85 |
86 | components.second = 0
87 | futureDate = Calendar.current.date(byAdding: components, to: now)!
88 | isExpired = tempPath.isTransactionDateExpired(futureDate.timeIntervalSince1970 * 1000, now: now)
89 | XCTAssertFalse(isExpired)
90 | }
91 |
92 | func testIsTransactionDateExpiredWithNilElapsedSeconds() {
93 | let elapsedTime = PyroTransactionElapsedTimeMock()
94 | let expiration: UInt = 10
95 | let key: String = "key"
96 | let now = Date()
97 | let tempPath = PyroTransactionTemporaryPath(key: key, expiration: expiration, elapsedTime: elapsedTime)
98 |
99 | elapsedTime.expectedSeconds = nil
100 | let isExpired = tempPath.isTransactionDateExpired(1000, now: now)
101 | XCTAssertFalse(isExpired)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyroTransactionTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyroTransactionTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 15/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyroTransactionTest: XCTestCase {
13 |
14 | func testCreate() {
15 | let baseURL = "https://foo.firebaseio.com"
16 | let accessToken = "accessToken"
17 | let transaction = PyroTransaction.create(baseURL: baseURL, accessToken: accessToken)
18 | XCTAssertTrue(transaction.tempPath is PyroTransactionTemporaryPath)
19 | XCTAssertEqual(transaction.tempPath.key, "pyrobase_transactions")
20 | XCTAssertEqual(transaction.tempPath.expiration, 30)
21 | XCTAssertEqual(transaction.baseURL, baseURL)
22 | XCTAssertTrue(transaction.path is RequestPath)
23 | XCTAssertTrue(transaction.request is Request)
24 | XCTAssertEqual((transaction.path as! RequestPath).accessToken, accessToken)
25 | XCTAssertEqual((transaction.path as! RequestPath).baseURL, baseURL)
26 | }
27 |
28 | func testReadTransactionWithCorrectPath() {
29 | let baseURL = "https://foo.firebaseio.com"
30 | let accessToken = "accessToken"
31 | let request = RequestMock()
32 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
33 | let tempPath = PyroTransactionTemporaryPath.create()
34 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
35 | let parentPath = "posts/abcde12345qwert"
36 | let childKey = "likes_count"
37 | let mutator: (Any) -> Any = { info in
38 | let count = info as! Int
39 | return count + 1
40 | }
41 | let expectation1 = expectation(description: "testReadTransaction")
42 |
43 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { _ in
44 | XCTAssertEqual(request.urlPath, "\(baseURL)/\(tempPath.key)/\(parentPath)/\(childKey).json?auth=\(accessToken)")
45 | expectation1.fulfill()
46 | }
47 |
48 | waitForExpectations(timeout: 10)
49 | }
50 |
51 | func testReadTransactionWithCustomError() {
52 | let baseURL = "https://foo.firebaseio.com"
53 | let accessToken = "accessToken"
54 | let request = RequestMock()
55 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
56 | let tempPath = PyroTransactionTemporaryPath.create()
57 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
58 | let parentPath = "posts/abcde12345qwert"
59 | let childKey = "likes_count"
60 | let mutator: (Any) -> Any = { info in
61 | let count = info as! Int
62 | return count + 1
63 | }
64 | let expectation1 = expectation(description: "testReadTransaction")
65 |
66 | request.expectedErrors.append(RequestError.invalidURL)
67 |
68 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
69 | switch result {
70 | case .succeeded:
71 | XCTFail()
72 |
73 | case .failed(let info):
74 | XCTAssertTrue(info is RequestError)
75 | let errorInfo = info as! RequestError
76 | XCTAssertTrue(errorInfo == .invalidURL)
77 | }
78 | expectation1.fulfill()
79 | }
80 |
81 | waitForExpectations(timeout: 10)
82 | }
83 |
84 | func testReadTransactionWithInvalidExpirationTimestampError() {
85 | let baseURL = "https://foo.firebaseio.com"
86 | let accessToken = "accessToken"
87 | let request = RequestMock()
88 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
89 | let tempPath = PyroTransactionTemporaryPathMock()
90 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
91 | let parentPath = "posts/abcde12345qwert"
92 | let childKey = "likes_count"
93 | let mutator: (Any) -> Any = { info in
94 | let count = info as! Int
95 | return count + 1
96 | }
97 | let expectation1 = expectation(description: "testReadTransaction")
98 |
99 | request.expectedData.append("abcde12345qwert")
100 |
101 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
102 | switch result {
103 | case .succeeded:
104 | XCTFail()
105 |
106 | case .failed(let info):
107 | XCTAssertTrue(info is PyroTransactionError)
108 | let errorInfo = info as! PyroTransactionError
109 | XCTAssertTrue(errorInfo == .invalidExpirationTimestamp)
110 | }
111 | expectation1.fulfill()
112 | }
113 |
114 | waitForExpectations(timeout: 10)
115 | }
116 |
117 | func testReadTransactionWithActiveTransactionNotDoneError() {
118 | let baseURL = "https://foo.firebaseio.com"
119 | let accessToken = "accessToken"
120 | let request = RequestMock()
121 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
122 | let tempPath = PyroTransactionTemporaryPathMock()
123 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
124 | let parentPath = "posts/abcde12345qwert"
125 | let childKey = "likes_count"
126 | let mutator: (Any) -> Any = { info in
127 | let count = info as! Int
128 | return count + 1
129 | }
130 | let expectation1 = expectation(description: "testReadTransaction")
131 |
132 | request.expectedData.append("1234567890")
133 |
134 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
135 | switch result {
136 | case .succeeded:
137 | XCTFail()
138 |
139 | case .failed(let info):
140 | XCTAssertTrue(info is PyroTransactionError)
141 | let errorInfo = info as! PyroTransactionError
142 | XCTAssertTrue(errorInfo == .activeTransactionNotDone)
143 | }
144 | expectation1.fulfill()
145 | }
146 |
147 | waitForExpectations(timeout: 10)
148 | }
149 |
150 | func testWriteTransactionWithCorrectRequestInfo() {
151 | let baseURL = "https://foo.firebaseio.com"
152 | let accessToken = "accessToken"
153 | let request = RequestMock()
154 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
155 | let tempPath = PyroTransactionTemporaryPath.create()
156 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
157 | let parentPath = "posts/abcde12345qwert"
158 | let childKey = "likes_count"
159 | let mutator: (Any) -> Any = { info in
160 | let count = info as! Int
161 | return count + 1
162 | }
163 | let expectation1 = expectation(description: "testWriteTransaction")
164 |
165 | request.expectedErrors.append(RequestError.nullJSON)
166 | request.expectedErrors.append(RequestError.invalidURL)
167 |
168 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { _ in
169 | XCTAssertEqual(request.urlPath, "\(baseURL)/\(tempPath.key).json?auth=\(accessToken)")
170 | XCTAssertEqual(request.writeData.count, 1)
171 |
172 | let key = "\(parentPath)/\(childKey)"
173 | XCTAssertNotNil(request.writeData[key])
174 |
175 | let value = request.writeData[key] as! [String: String]
176 | XCTAssertNotNil(value[".sv"])
177 | XCTAssertEqual(value[".sv"], "timestamp")
178 |
179 | XCTAssertTrue(request.requestMethod == .patch)
180 | expectation1.fulfill()
181 | }
182 |
183 | waitForExpectations(timeout: 10)
184 | }
185 |
186 | func testWriteTransactionWithCustomError() {
187 | let baseURL = "https://foo.firebaseio.com"
188 | let accessToken = "accessToken"
189 | let request = RequestMock()
190 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
191 | let tempPath = PyroTransactionTemporaryPath.create()
192 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
193 | let parentPath = "posts/abcde12345qwert"
194 | let childKey = "likes_count"
195 | let mutator: (Any) -> Any = { info in
196 | let count = info as! Int
197 | return count + 1
198 | }
199 | let expectation1 = expectation(description: "testWriteTransaction")
200 |
201 | request.expectedErrors.append(RequestError.nullJSON)
202 | request.expectedErrors.append(RequestError.invalidURL)
203 |
204 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
205 | switch result {
206 | case .succeeded:
207 | XCTFail()
208 |
209 | case .failed(let info):
210 | XCTAssertTrue(info is RequestError)
211 | let errorInfo = info as! RequestError
212 | XCTAssertTrue(errorInfo == .invalidURL)
213 | }
214 |
215 | expectation1.fulfill()
216 | }
217 |
218 | waitForExpectations(timeout: 10)
219 | }
220 |
221 | func testWriteTransactionShouldBeCalledWhenTransactionDateExpiredOnRead() {
222 | let baseURL = "https://foo.firebaseio.com"
223 | let accessToken = "accessToken"
224 | let request = RequestMock()
225 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
226 | let tempPath = PyroTransactionTemporaryPathMock()
227 | let transaction = PyroTransactionMock(request: request, path: path, tempPath: tempPath)
228 | let parentPath = "posts/abcde12345qwert"
229 | let childKey = "likes_count"
230 | let mutator: (Any) -> Any = { info in
231 | let count = info as! Int
232 | return count + 1
233 | }
234 | let expectation1 = expectation(description: "testReadTransaction")
235 |
236 | tempPath.isExpired = true
237 | request.expectedData.append("1234567890")
238 | transaction.shouldProceedWriteTransaction = false
239 |
240 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
241 | expectation1.fulfill()
242 | }
243 |
244 | waitForExpectations(timeout: 10)
245 | }
246 |
247 | func testReadChildWithCorrectRequestInfo() {
248 | let baseURL = "https://foo.firebaseio.com"
249 | let accessToken = "accessToken"
250 | let request = RequestMock()
251 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
252 | let tempPath = PyroTransactionTemporaryPath.create()
253 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
254 | let parentPath = "posts/abcde12345qwert"
255 | let childKey = "likes_count"
256 | let mutator: (Any) -> Any = { info in
257 | let count = info as! Int
258 | return count + 1
259 | }
260 | let expectation1 = expectation(description: "testReadChild")
261 |
262 | request.shouldURLPathBeReplaced = false
263 | request.shouldRequestMethodBeReplaced = false
264 | request.expectedErrors.append(RequestError.nullJSON)
265 | request.expectedErrors.append(nil)
266 | request.expectedErrors.append(RequestError.invalidURL)
267 | request.expectedData.append(true)
268 |
269 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { _ in
270 | XCTAssertEqual(request.urlPath, "\(baseURL)/\(parentPath)/\(childKey).json?auth=\(accessToken)")
271 | XCTAssertTrue(request.requestMethod == .get)
272 | expectation1.fulfill()
273 | }
274 |
275 | waitForExpectations(timeout: 10)
276 | }
277 |
278 | func testReadChildWithCustomError() {
279 | let baseURL = "https://foo.firebaseio.com"
280 | let accessToken = "accessToken"
281 | let request = RequestMock()
282 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
283 | let tempPath = PyroTransactionTemporaryPath.create()
284 | let transaction = PyroTransactionMock(request: request, path: path, tempPath: tempPath)
285 | let parentPath = "posts/abcde12345qwert"
286 | let childKey = "likes_count"
287 | let mutator: (Any) -> Any = { info in
288 | let count = info as! Int
289 | return count + 1
290 | }
291 | let expectation1 = expectation(description: "testReadChild")
292 |
293 | request.expectedErrors.append(RequestError.nullJSON)
294 | request.expectedErrors.append(nil)
295 | request.expectedErrors.append(RequestError.invalidURL)
296 | request.expectedData.append(true)
297 |
298 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
299 | switch result {
300 | case .succeeded:
301 | XCTFail()
302 |
303 | case .failed(let info):
304 | XCTAssertTrue(info is RequestError)
305 | let errorInfo = info as! RequestError
306 | XCTAssertTrue(errorInfo == .invalidURL)
307 | }
308 |
309 | XCTAssertTrue(transaction.isDeleted)
310 | expectation1.fulfill()
311 | }
312 |
313 | waitForExpectations(timeout: 10)
314 | }
315 |
316 | func testWriteChildShouldBeCalledWhenSucceededReadChild() {
317 | let baseURL = "https://foo.firebaseio.com"
318 | let accessToken = "accessToken"
319 | let request = RequestMock()
320 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
321 | let tempPath = PyroTransactionTemporaryPathMock()
322 | let transaction = PyroTransactionMock(request: request, path: path, tempPath: tempPath)
323 | let parentPath = "posts/abcde12345qwert"
324 | let childKey = "likes_count"
325 | let mutator: (Any) -> Any = { info in
326 | let count = info as! Int
327 | return count + 1
328 | }
329 | let expectation1 = expectation(description: "testWriteChild")
330 |
331 | request.expectedErrors.append(RequestError.nullJSON)
332 | request.expectedErrors.append(nil)
333 | request.expectedErrors.append(nil)
334 | request.expectedData.append(true)
335 | request.expectedData.append(true)
336 |
337 | transaction.shouldProceedWriteChild = false
338 |
339 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
340 | expectation1.fulfill()
341 | }
342 |
343 | waitForExpectations(timeout: 10)
344 | }
345 |
346 | func testWriteChildWithCorrectRequestInfo() {
347 | let baseURL = "https://foo.firebaseio.com"
348 | let accessToken = "accessToken"
349 | let request = RequestMock()
350 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
351 | let tempPath = PyroTransactionTemporaryPath.create()
352 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
353 | let parentPath = "posts/abcde12345qwert"
354 | let childKey = "likes_count"
355 | let mutator: (Any) -> Any = { info in
356 | let count = info as! Int
357 | return count + 1
358 | }
359 | let expectation1 = expectation(description: "testReadChild")
360 |
361 | request.shouldURLPathBeReplaced = false
362 | request.shouldRequestMethodBeReplaced = false
363 | request.expectedErrors.append(RequestError.nullJSON)
364 | request.expectedErrors.append(nil)
365 | request.expectedErrors.append(nil)
366 | request.expectedErrors.append(RequestError.invalidURL)
367 | request.expectedData.append(true)
368 | request.expectedData.append(1)
369 |
370 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { _ in
371 | XCTAssertEqual(request.urlPath, "\(baseURL)/\(parentPath).json?auth=\(accessToken)")
372 | XCTAssertEqual(request.writeData.count, 1)
373 |
374 | let key = "\(childKey)"
375 | let expectedData: Int = mutator(1) as! Int
376 |
377 | XCTAssertNotNil(request.writeData[key])
378 | XCTAssertEqual(request.writeData[key] as! Int, expectedData)
379 | XCTAssertTrue(request.requestMethod == .patch)
380 | expectation1.fulfill()
381 | }
382 |
383 | waitForExpectations(timeout: 10)
384 | }
385 |
386 | func testWriteChildWithCustomError() {
387 | let baseURL = "https://foo.firebaseio.com"
388 | let accessToken = "accessToken"
389 | let request = RequestMock()
390 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
391 | let tempPath = PyroTransactionTemporaryPath.create()
392 | let transaction = PyroTransactionMock(request: request, path: path, tempPath: tempPath)
393 | let parentPath = "posts/abcde12345qwert"
394 | let childKey = "likes_count"
395 | let mutator: (Any) -> Any = { info in
396 | let count = info as! Int
397 | return count + 1
398 | }
399 | let expectation1 = expectation(description: "testReadChild")
400 |
401 | request.expectedErrors.append(RequestError.nullJSON)
402 | request.expectedErrors.append(nil)
403 | request.expectedErrors.append(nil)
404 | request.expectedErrors.append(RequestError.invalidURL)
405 | request.expectedData.append(true)
406 | request.expectedData.append(1)
407 |
408 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
409 | switch result {
410 | case .succeeded:
411 | XCTFail()
412 |
413 | case .failed(let info):
414 | XCTAssertTrue(info is RequestError)
415 | let errorInfo = info as! RequestError
416 | XCTAssertTrue(errorInfo == .invalidURL)
417 | }
418 |
419 | XCTAssertTrue(transaction.isDeleted)
420 | expectation1.fulfill()
421 | }
422 |
423 | waitForExpectations(timeout: 10)
424 | }
425 |
426 | func testWriteChildWithSucceededResult() {
427 | let baseURL = "https://foo.firebaseio.com"
428 | let accessToken = "accessToken"
429 | let request = RequestMock()
430 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
431 | let tempPath = PyroTransactionTemporaryPath.create()
432 | let transaction = PyroTransactionMock(request: request, path: path, tempPath: tempPath)
433 | let parentPath = "posts/abcde12345qwert"
434 | let childKey = "likes_count"
435 | let mutator: (Any) -> Any = { info in
436 | let count = info as! Int
437 | return count + 1
438 | }
439 | let expectation1 = expectation(description: "testReadChild")
440 |
441 | request.expectedErrors.append(RequestError.nullJSON)
442 | request.expectedErrors.append(nil)
443 | request.expectedErrors.append(nil)
444 | request.expectedErrors.append(nil)
445 | request.expectedData.append(true)
446 | request.expectedData.append(1)
447 | request.expectedData.append(["likes_count": mutator(1)])
448 |
449 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
450 | switch result {
451 | case .failed:
452 | XCTFail()
453 |
454 | case .succeeded(let info):
455 | XCTAssertTrue(info is [AnyHashable: Any])
456 | let resultInfo = info as! [AnyHashable: Any]
457 | let likesCount = resultInfo["likes_count"]
458 | XCTAssertTrue(likesCount is Int)
459 | XCTAssertEqual(likesCount as! Int, 2)
460 | }
461 |
462 | XCTAssertTrue(transaction.isDeleted)
463 | expectation1.fulfill()
464 | }
465 |
466 | waitForExpectations(timeout: 10)
467 | }
468 |
469 | func testDeleteTransactionWithCorrectRequestInfo() {
470 | let baseURL = "https://foo.firebaseio.com"
471 | let accessToken = "accessToken"
472 | let request = RequestMock()
473 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
474 | let tempPath = PyroTransactionTemporaryPath.create()
475 | let transaction = PyroTransaction(request: request, path: path, tempPath: tempPath)
476 | let parentPath = "posts/abcde12345qwert"
477 | let childKey = "likes_count"
478 | let mutator: (Any) -> Any = { info in
479 | let count = info as! Int
480 | return count + 1
481 | }
482 | let expectation1 = expectation(description: "testDeleteTransaction")
483 |
484 | request.shouldURLPathBeReplaced = true
485 | request.shouldRequestMethodBeReplaced = true
486 | request.expectedErrors.append(RequestError.nullJSON)
487 | request.expectedErrors.append(nil)
488 | request.expectedErrors.append(nil)
489 | request.expectedErrors.append(RequestError.invalidURL)
490 | request.expectedData.append(true)
491 | request.expectedData.append(1)
492 | request.expectedData.append(true)
493 |
494 | transaction.run(parentPath: parentPath, childKey: childKey, mutator: mutator) { result in
495 | XCTAssertEqual(request.urlPath, "\(baseURL)/\(tempPath.key)/\(parentPath).json?auth=\(accessToken)")
496 | XCTAssertTrue(request.requestMethod == .delete)
497 | expectation1.fulfill()
498 | }
499 |
500 | waitForExpectations(timeout: 10)
501 | }
502 | }
503 |
--------------------------------------------------------------------------------
/PyrobaseTests/PyrobaseTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PyrobaseTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 01/05/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class PyrobaseTest: XCTestCase {
13 |
14 | func testInitialization() {
15 | let baseURL = "https://foo.firebaseio.com"
16 | let accessToken = "accessToken"
17 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
18 | let request = Request.create()
19 | let pyrobase = Pyrobase(request: request, path: path)
20 |
21 | XCTAssertNotNil(pyrobase.path)
22 | XCTAssertNotNil(pyrobase.request)
23 | }
24 |
25 | func testCreate() {
26 | let baseURL = "https://foo.firebaseio.com"
27 | let accessToken = "accessToken"
28 | let pyrobase = Pyrobase.create(baseURL: baseURL, accessToken: accessToken)
29 |
30 | XCTAssertNotNil(pyrobase.path)
31 | XCTAssertTrue(pyrobase.path is RequestPath)
32 | }
33 |
34 | func testGet() {
35 | let baseURL = "https://foo.firebaseio.com"
36 | let accessToken = "accessToken"
37 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
38 | let request = RequestMock()
39 | let pyrobase = Pyrobase(request: request, path: path)
40 |
41 | let expectation1 = expectation(description: "testGet")
42 | pyrobase.get(path: "name", query: [:]) { result in
43 | switch result {
44 | case .failed:
45 | XCTFail()
46 |
47 | case .succeeded(let data):
48 | XCTAssertTrue(data is String)
49 | XCTAssertEqual(data as! String, "Luche")
50 | }
51 | expectation1.fulfill()
52 | }
53 |
54 | waitForExpectations(timeout: 1)
55 | }
56 |
57 | func testPut() {
58 | let baseURL = "https://foo.firebaseio.com"
59 | let accessToken = "accessToken"
60 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
61 | let request = RequestMock()
62 | let pyrobase = Pyrobase(request: request, path: path)
63 | let expectedValue = ["last_name": "Valdez"]
64 | let expectation1 = expectation(description: "testPut")
65 |
66 | pyrobase.put(path: "name", value: expectedValue) { result in
67 | switch result {
68 | case .failed:
69 | XCTFail()
70 |
71 | case .succeeded(let data):
72 | XCTAssertTrue(data is [String: String])
73 | let resultInfo = data as! [String: String]
74 | XCTAssertEqual(resultInfo.count, expectedValue.count)
75 | XCTAssertEqual(resultInfo["last_name"], expectedValue["last_name"])
76 |
77 | let absolutePath = path.build("name")
78 | let content = request.content[absolutePath]
79 | XCTAssertTrue(content is [String: String])
80 | let contentInfo = content as! [String: String]
81 | XCTAssertEqual(contentInfo.count, expectedValue.count)
82 | XCTAssertEqual(contentInfo["last_name"], expectedValue["last_name"])
83 | }
84 | expectation1.fulfill()
85 | }
86 |
87 | waitForExpectations(timeout: 1)
88 | }
89 |
90 | func testPost() {
91 | let baseURL = "https://foo.firebaseio.com"
92 | let accessToken = "accessToken"
93 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
94 | let request = RequestMock()
95 | let pyrobase = Pyrobase(request: request, path: path)
96 | let expectedValue = ["message": "Hello world!"]
97 | let expectation1 = expectation(description: "testPost")
98 |
99 | pyrobase.post(path: "messages", value: expectedValue) { result in
100 | switch result {
101 | case .failed:
102 | XCTFail()
103 |
104 | case .succeeded(let data):
105 | XCTAssertTrue(data is [String: String])
106 | let resultInfo = data as! [String: String]
107 | XCTAssertEqual(resultInfo.count, 1)
108 | XCTAssertNotNil(resultInfo["name"])
109 |
110 | let content = request.content[resultInfo["name"]!]
111 | XCTAssertTrue(content is [String: String])
112 | let contentInfo = content as! [String: String]
113 | XCTAssertEqual(contentInfo.count, expectedValue.count)
114 | XCTAssertEqual(contentInfo["message"], expectedValue["message"])
115 | }
116 | expectation1.fulfill()
117 | }
118 |
119 | waitForExpectations(timeout: 1)
120 | }
121 |
122 | func testPatch() {
123 | let baseURL = "https://foo.firebaseio.com"
124 | let accessToken = "accessToken"
125 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
126 | let request = RequestMock()
127 | let pyrobase = Pyrobase(request: request, path: path)
128 | let expectedValue = ["date": "June 8, 2017"]
129 | let expectation1 = expectation(description: "testPost")
130 |
131 | pyrobase.patch(path: "messages/abcde12345qwert", value: expectedValue) { result in
132 | switch result {
133 | case .failed:
134 | XCTFail()
135 |
136 | case .succeeded(let data):
137 | XCTAssertTrue(data is [String: String])
138 | let resultInfo = data as! [String: String]
139 | XCTAssertEqual(resultInfo.count, expectedValue.count)
140 | XCTAssertEqual(resultInfo["date"], expectedValue["date"])
141 |
142 | let absolutePath = path.build("messages/abcde12345qwert")
143 | let content = request.content[absolutePath]
144 | XCTAssertTrue(content is [String: String])
145 | let contentInfo = content as! [String: String]
146 | XCTAssertEqual(contentInfo.count, 2)
147 | XCTAssertEqual(contentInfo["message"], "Hello World!")
148 | XCTAssertEqual(contentInfo["date"], expectedValue["date"])
149 | }
150 | expectation1.fulfill()
151 | }
152 |
153 | waitForExpectations(timeout: 1)
154 | }
155 |
156 | func testDelete() {
157 | let baseURL = "https://foo.firebaseio.com"
158 | let accessToken = "accessToken"
159 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
160 | let request = RequestMock()
161 | let pyrobase = Pyrobase(request: request, path: path)
162 | let expectation1 = expectation(description: "testDelete")
163 |
164 | pyrobase.delete(path: "name") { result in
165 | switch result {
166 | case .failed:
167 | XCTFail()
168 |
169 | case .succeeded(let info):
170 | XCTAssertTrue(info is String)
171 | let resultInfo = info as! String
172 | XCTAssertEqual(resultInfo.lowercased(), "null")
173 |
174 | let absolutePath = path.build("name")
175 | XCTAssertNil(request.content[absolutePath])
176 | }
177 | expectation1.fulfill()
178 | }
179 |
180 | waitForExpectations(timeout: 1)
181 | }
182 |
183 | func testGetBaseURL() {
184 | let baseURL = "https://foo.firebaseio.com"
185 | let accessToken = "accessToken"
186 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
187 | let request = RequestMock()
188 | let pyrobase = Pyrobase(request: request, path: path)
189 |
190 | XCTAssertEqual(pyrobase.baseURL, baseURL)
191 | }
192 |
193 | func testGetWithQuery() {
194 | let baseURL = "https://foo.firebaseio.com"
195 | let accessToken = "accessToken"
196 | let path = RequestPath(baseURL: baseURL, accessToken: accessToken)
197 | let session = URLSessionMock()
198 | let operation = JSONRequestOperation.create()
199 | let response = RequestResponse()
200 | let request = Request(session: session, operation: operation, response: response)
201 | let pyrobase = Pyrobase(request: request, path: path)
202 | let query: [AnyHashable: Any] = ["orderBy": "\"$key\"", "limitToFirst": 1]
203 | let expectation1 = expectation(description: "testGet")
204 | pyrobase.get(path: "name", query: query) { result in
205 | switch result {
206 | case .failed:
207 | XCTFail()
208 |
209 | case .succeeded:
210 | break
211 | }
212 | expectation1.fulfill()
213 | }
214 |
215 | waitForExpectations(timeout: 1)
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/PyrobaseTests/RequestErrorTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestErrorTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 26/07/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class RequestErrorTest: XCTestCase {
13 |
14 | func testCode() {
15 | XCTAssertEqual(RequestError.invalidURL.code, -9000)
16 | XCTAssertEqual(RequestError.unparseableJSON.code, -9001)
17 | XCTAssertEqual(RequestError.noURLResponse.code, -9002)
18 | XCTAssertEqual(RequestError.nullJSON.code, -9003)
19 | XCTAssertEqual(RequestError.unknown.code, -9004)
20 |
21 | XCTAssertEqual(RequestError.badRequest("").code, -9005)
22 | XCTAssertEqual(RequestError.unauthorized("").code, -9006)
23 | XCTAssertEqual(RequestError.forbidden("").code, -9007)
24 | XCTAssertEqual(RequestError.notFound("").code, -9008)
25 | XCTAssertEqual(RequestError.internalServiceError("").code, -9009)
26 | XCTAssertEqual(RequestError.serviceUnavailable("").code, -9010)
27 | }
28 |
29 | func testMessage() {
30 | XCTAssertEqual(RequestError.invalidURL.message, "URL is invalid")
31 | XCTAssertEqual(RequestError.unparseableJSON.message, "Can not parse JSON")
32 | XCTAssertEqual(RequestError.noURLResponse.message, "No URL response")
33 | XCTAssertEqual(RequestError.nullJSON.message, "JSON is null")
34 | XCTAssertEqual(RequestError.unknown.message, "Unknown error encountered")
35 |
36 | XCTAssertEqual(RequestError.badRequest("Bad request").message, "Bad request")
37 | XCTAssertEqual(RequestError.unauthorized("Unauthorized").message, "Unauthorized")
38 | XCTAssertEqual(RequestError.forbidden("Forbidden").message, "Forbidden")
39 | XCTAssertEqual(RequestError.notFound("Not Found").message, "Not Found")
40 | XCTAssertEqual(RequestError.internalServiceError("Internal Service Error").message, "Internal Service Error")
41 | XCTAssertEqual(RequestError.serviceUnavailable("Service Unavailable").message, "Service Unavailable")
42 | }
43 |
44 | func testEquality() {
45 | XCTAssertEqual(RequestError.invalidURL, RequestError.invalidURL)
46 | XCTAssertNotEqual(RequestError.invalidURL, RequestError.noURLResponse)
47 | XCTAssertNotEqual(RequestError.invalidURL, RequestError.badRequest("Bad request"))
48 | XCTAssertEqual(RequestError.badRequest("Bad request"), RequestError.badRequest("Bad request"))
49 | XCTAssertNotEqual(RequestError.badRequest("Bad request"), RequestError.badRequest("Worse request"))
50 | XCTAssertNotEqual(RequestError.badRequest("Bad request"), RequestError.unauthorized("Unauthorized"))
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/PyrobaseTests/RequestMethodTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestMethodTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 07/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class RequestMethodTest: XCTestCase {
13 |
14 | func testPrintedDescription() {
15 | var method: RequestMethod = .get
16 | XCTAssertEqual("\(method)", "GET")
17 |
18 | method = .post
19 | XCTAssertEqual("\(method)", "POST")
20 |
21 | method = .put
22 | XCTAssertEqual("\(method)", "PUT")
23 |
24 | method = .patch
25 | XCTAssertEqual("\(method)", "PATCH")
26 |
27 | method = .delete
28 | XCTAssertEqual("\(method)", "DELETE")
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/PyrobaseTests/RequestMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 02/05/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import Pyrobase
10 |
11 | class RequestMock: RequestProtocol {
12 |
13 | var content: [AnyHashable: Any] = [
14 | "https://foo.firebaseio.com/name.json?auth=accessToken": "Luche",
15 | "https://foo.firebaseio.com/messages/abcde12345qwert.json?auth=accessToken": ["message": "Hello World!"]
16 | ]
17 |
18 | var urlPath: String = ""
19 | var expectedErrors: [Error?] = []
20 | var expectedData: [Any] = []
21 | var writeData: [AnyHashable : Any] = [:]
22 | var requestMethod: RequestMethod = .get
23 | var shouldURLPathBeReplaced: Bool = true
24 | var shouldRequestMethodBeReplaced: Bool = true
25 |
26 | func read(path: String, query: [AnyHashable: Any], completion: @escaping (RequestResult) -> Void) {
27 | urlPath = path
28 | requestMethod = .get
29 |
30 | if !expectedErrors.isEmpty, let error = expectedErrors.removeFirst() {
31 | completion(.failed(error))
32 |
33 | } else {
34 | if !expectedData.isEmpty {
35 | completion(.succeeded(expectedData.removeFirst()))
36 |
37 | } else {
38 | if let item = content[path] {
39 | completion(.succeeded(item))
40 |
41 | } else {
42 | completion(.succeeded("OK"))
43 | }
44 | }
45 | }
46 | }
47 |
48 | func write(path: String, method: RequestMethod, data: [AnyHashable : Any], completion: @escaping (RequestResult) -> Void) {
49 | urlPath = path
50 | writeData = data
51 | requestMethod = method
52 |
53 | switch method {
54 | case .put:
55 | content[path] = data
56 | completion(.succeeded(data))
57 |
58 | case .post:
59 | let newId = "abcde12345qwert"
60 | content[newId] = data
61 | completion(.succeeded(["name": newId]))
62 |
63 | case .patch:
64 | if !expectedErrors.isEmpty, let error = expectedErrors.removeFirst() {
65 | completion(.failed(error))
66 |
67 | } else {
68 | if !expectedData.isEmpty {
69 | completion(.succeeded(expectedData.removeFirst()))
70 |
71 | } else {
72 | var contentInfo = content[path] as! [AnyHashable: Any]
73 | for (key, value) in data {
74 | contentInfo[key] = value
75 | }
76 | content[path] = contentInfo
77 | completion(.succeeded(data))
78 | }
79 | }
80 |
81 | default:
82 | break
83 | }
84 | }
85 |
86 | func delete(path: String, completion: @escaping (RequestResult) -> Void) {
87 | if shouldURLPathBeReplaced {
88 | urlPath = path
89 | }
90 |
91 | if shouldRequestMethodBeReplaced {
92 | requestMethod = .delete
93 | }
94 |
95 | content.removeValue(forKey: path)
96 | completion(.succeeded("null"))
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/PyrobaseTests/RequestOperationMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestOperationMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 08/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import Pyrobase
10 |
11 | class RequestOperationMock: RequestOperation {
12 |
13 | enum MockError: Error {
14 |
15 | case failedToParse
16 | }
17 |
18 | func build(url: URL, method: RequestMethod, data: [AnyHashable : Any]) -> URLRequest {
19 | return URLRequest(url: url)
20 | }
21 |
22 | func parse(data: Data) -> RequestOperationResult {
23 | return .error(MockError.failedToParse)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/PyrobaseTests/RequestPathTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestPathTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 02/05/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class RequestPathTest: XCTestCase {
13 |
14 | func testInitialization() {
15 | let accessToken = "accessToken"
16 | let baseURL = "https://foo.firebaseio.com"
17 | let requestPath = RequestPath(baseURL: baseURL, accessToken: accessToken)
18 | XCTAssertNotNil(requestPath.baseURL)
19 | XCTAssertNotNil(requestPath.accessToken)
20 | XCTAssertEqual(requestPath.baseURL, baseURL)
21 | XCTAssertEqual(requestPath.accessToken, accessToken)
22 | }
23 |
24 | func testBuild() {
25 | let accessToken = "accessToken"
26 | let baseURL = "https://foo.firebaseio.com"
27 | let relativePath = "foo"
28 | let requestPath = RequestPath(baseURL: baseURL, accessToken: accessToken)
29 | let requestURL = "\(baseURL)/\(relativePath).json?auth=\(accessToken)"
30 | XCTAssertEqual(requestURL, requestPath.build(relativePath))
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/PyrobaseTests/RequestResponseTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestResponseTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 23/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class RequestResponseTest: XCTestCase {
13 |
14 | func testIsErroneous() {
15 | let response = RequestResponse()
16 |
17 | var httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 400, httpVersion: nil, headerFields: nil)!
18 | var error = response.isErroneous(httpResponse, data: nil)
19 | XCTAssertNotNil(error)
20 | XCTAssertTrue(error as! RequestError == .badRequest(""))
21 |
22 | httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 401, httpVersion: nil, headerFields: nil)!
23 | error = response.isErroneous(httpResponse, data: nil)
24 | XCTAssertNotNil(error)
25 | XCTAssertTrue(error as! RequestError == .unauthorized(""))
26 |
27 | httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 403, httpVersion: nil, headerFields: nil)!
28 | error = response.isErroneous(httpResponse, data: nil)
29 | XCTAssertNotNil(error)
30 | XCTAssertTrue(error as! RequestError == .forbidden(""))
31 |
32 | httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 404, httpVersion: nil, headerFields: nil)!
33 | error = response.isErroneous(httpResponse, data: nil)
34 | XCTAssertNotNil(error)
35 | XCTAssertTrue(error as! RequestError == .notFound(""))
36 |
37 | httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 500, httpVersion: nil, headerFields: nil)!
38 | error = response.isErroneous(httpResponse, data: nil)
39 | XCTAssertNotNil(error)
40 | XCTAssertTrue(error as! RequestError == .internalServiceError(""))
41 |
42 | httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 503, httpVersion: nil, headerFields: nil)!
43 | error = response.isErroneous(httpResponse, data: nil)
44 | XCTAssertNotNil(error)
45 | XCTAssertTrue(error as! RequestError == .serviceUnavailable(""))
46 |
47 | httpResponse = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 200, httpVersion: nil, headerFields: nil)!
48 | error = response.isErroneous(httpResponse, data: nil)
49 | XCTAssertNil(error)
50 | XCTAssertNil(response.isErroneous(nil, data: nil))
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/PyrobaseTests/RequestTest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestTest.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 02/05/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Pyrobase
11 |
12 | class RequestTest: XCTestCase {
13 |
14 | func testRead() {
15 | let session = URLSessionMock()
16 | let operation = JSONRequestOperation.create()
17 | let response = RequestResponse()
18 | let request = Request(session: session, operation: operation, response: response)
19 | let expectation1 = expectation(description: "testRead")
20 | request.read(path: "https://foo.firebaseio.com/users/12345/name.json?access_token=accessToken", query: [:]) { result in
21 | switch result {
22 | case .failed:
23 | XCTFail()
24 |
25 | case .succeeded(let data):
26 | XCTAssertTrue(data is String)
27 | XCTAssertEqual(data as! String, "Luche")
28 | }
29 | expectation1.fulfill()
30 | }
31 | waitForExpectations(timeout: 1)
32 | }
33 |
34 | func testReadWithInt() {
35 | let session = URLSessionMock()
36 | let operation = JSONRequestOperation.create()
37 | let response = RequestResponse()
38 | let request = Request(session: session, operation: operation, response: response)
39 | let expectation1 = expectation(description: "testRead")
40 | request.read(path: "https://foo.firebaseio.com/users/12345/int.json?access_token=accessToken", query: [:]) { result in
41 | switch result {
42 | case .failed:
43 | XCTFail()
44 |
45 | case .succeeded(let data):
46 | XCTAssertTrue(data is String)
47 | let number = Int(data as! String)
48 | XCTAssertNotNil(number)
49 | XCTAssertEqual(number, 101)
50 | }
51 | expectation1.fulfill()
52 | }
53 | waitForExpectations(timeout: 1)
54 | }
55 |
56 | func testReadWithDouble() {
57 | let session = URLSessionMock()
58 | let operation = JSONRequestOperation.create()
59 | let response = RequestResponse()
60 | let request = Request(session: session, operation: operation, response: response)
61 | let expectation1 = expectation(description: "testRead")
62 | request.read(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", query: [:]) { result in
63 | switch result {
64 | case .failed:
65 | XCTFail()
66 |
67 | case .succeeded(let data):
68 | XCTAssertTrue(data is String)
69 | let number = Double(data as! String)
70 | XCTAssertNotNil(number)
71 | XCTAssertEqual(number, 101.12345)
72 | }
73 | expectation1.fulfill()
74 | }
75 | waitForExpectations(timeout: 1)
76 | }
77 |
78 | func testReadWithInvalidURL() {
79 | let request = Request.create()
80 | let expectation1 = expectation(description: "testRead")
81 | request.read(path: "", query: [:]) { result in
82 | switch result {
83 | case .succeeded:
84 | XCTFail()
85 |
86 | case .failed(let error):
87 | XCTAssertTrue(error is RequestError)
88 | let errorInfo = error as! RequestError
89 | XCTAssertTrue(errorInfo == RequestError.invalidURL)
90 | }
91 | expectation1.fulfill()
92 | }
93 | waitForExpectations(timeout: 1)
94 | }
95 |
96 | func testReadWithError() {
97 | let taskResult = URLSessionDataTaskMock.Result()
98 | let session = URLSessionMock()
99 | let operation = JSONRequestOperation.create()
100 | let response = RequestResponse()
101 | let request = Request(session: session, operation: operation, response: response)
102 | let expectation1 = expectation(description: "testRead")
103 |
104 | taskResult.error = URLSessionDataTaskMock.TaskMockError.mockError1
105 | session.expectedDataTaskResult = taskResult
106 |
107 | request.read(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", query: [:]) { result in
108 | switch result {
109 | case .succeeded:
110 | XCTFail()
111 |
112 | case .failed(let error):
113 | XCTAssertTrue(error is URLSessionDataTaskMock.TaskMockError)
114 | let errorInfo = error as! URLSessionDataTaskMock.TaskMockError
115 | XCTAssertTrue(errorInfo == URLSessionDataTaskMock.TaskMockError.mockError1)
116 | }
117 | expectation1.fulfill()
118 | }
119 | waitForExpectations(timeout: 1)
120 | }
121 |
122 | func testReadWithNoURLResponse() {
123 | let taskResult = URLSessionDataTaskMock.Result()
124 | let session = URLSessionMock()
125 | let operation = JSONRequestOperation.create()
126 | let response = RequestResponse()
127 | let request = Request(session: session, operation: operation, response: response)
128 | let expectation1 = expectation(description: "testRead")
129 |
130 | taskResult.response = nil
131 | session.expectedDataTaskResult = taskResult
132 |
133 | request.read(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", query: [:]) { result in
134 | switch result {
135 | case .succeeded:
136 | XCTFail()
137 |
138 | case .failed(let error):
139 | XCTAssertTrue(error is RequestError)
140 | let errorInfo = error as! RequestError
141 | XCTAssertTrue(errorInfo == RequestError.noURLResponse)
142 | }
143 | expectation1.fulfill()
144 | }
145 | waitForExpectations(timeout: 1)
146 | }
147 |
148 | func testReadWithNilData() {
149 | let taskResult = URLSessionDataTaskMock.Result()
150 | let session = URLSessionMock()
151 | let operation = JSONRequestOperation.create()
152 | let response = RequestResponse()
153 | let request = Request(session: session, operation: operation, response: response)
154 | let expectation1 = expectation(description: "testRead")
155 |
156 | taskResult.response = HTTPURLResponse()
157 | session.expectedDataTaskResult = taskResult
158 |
159 | request.read(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", query: [:]) { result in
160 | switch result {
161 | case .failed:
162 | XCTFail()
163 |
164 | case .succeeded(let info):
165 | XCTAssertTrue(info is [AnyHashable: Any])
166 | let resultInfo = info as! [AnyHashable: Any]
167 | XCTAssertTrue(resultInfo.isEmpty)
168 | }
169 | expectation1.fulfill()
170 | }
171 | waitForExpectations(timeout: 1)
172 | }
173 |
174 | func testReadWithErrorParsingData() {
175 | let taskResult = URLSessionDataTaskMock.Result()
176 | let session = URLSessionMock()
177 | let operation = RequestOperationMock()
178 | let response = RequestResponse()
179 | let request = Request(session: session, operation: operation, response: response)
180 | let expectation1 = expectation(description: "testRead")
181 |
182 | taskResult.response = HTTPURLResponse()
183 | taskResult.data = Data(bytes: [1,2,3])
184 | session.expectedDataTaskResult = taskResult
185 |
186 | request.read(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", query: [:]) { result in
187 | switch result {
188 | case .succeeded:
189 | XCTFail()
190 |
191 | case .failed(let info):
192 | XCTAssertTrue(info is RequestOperationMock.MockError)
193 | let errorInfo = info as! RequestOperationMock.MockError
194 | XCTAssertTrue(errorInfo == RequestOperationMock.MockError.failedToParse)
195 | }
196 | expectation1.fulfill()
197 | }
198 | waitForExpectations(timeout: 1)
199 | }
200 |
201 | func testReadWithJSONNull() {
202 | let taskResult = URLSessionDataTaskMock.Result()
203 | let session = URLSessionMock()
204 | let operation = JSONRequestOperation.create()
205 | let response = RequestResponse()
206 | let request = Request(session: session, operation: operation, response: response)
207 | let expectation1 = expectation(description: "testRead")
208 |
209 | taskResult.response = HTTPURLResponse()
210 | taskResult.data = "null".data(using: .utf8)
211 | session.expectedDataTaskResult = taskResult
212 |
213 | request.read(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", query: [:]) { result in
214 | switch result {
215 | case .succeeded:
216 | XCTFail()
217 |
218 | case .failed(let info):
219 | XCTAssertTrue(info is RequestError)
220 | let resultInfo = info as! RequestError
221 | XCTAssertTrue(resultInfo == RequestError.nullJSON)
222 | }
223 | expectation1.fulfill()
224 | }
225 | waitForExpectations(timeout: 1)
226 | }
227 |
228 | func testWriteWithInvalidURL() {
229 | let request = Request.create()
230 | let expectation1 = expectation(description: "testWrite")
231 | request.write(path: "", method: .post, data: [:]) { result in
232 | switch result {
233 | case .succeeded:
234 | XCTFail()
235 |
236 | case .failed(let error):
237 | XCTAssertTrue(error is RequestError)
238 | let errorInfo = error as! RequestError
239 | XCTAssertTrue(errorInfo == RequestError.invalidURL)
240 | }
241 | expectation1.fulfill()
242 | }
243 | waitForExpectations(timeout: 1)
244 | }
245 |
246 | func testWriteWithError() {
247 | let taskResult = URLSessionDataTaskMock.Result()
248 | let session = URLSessionMock()
249 | let operation = JSONRequestOperation.create()
250 | let response = RequestResponse()
251 | let request = Request(session: session, operation: operation, response: response)
252 | let expectation1 = expectation(description: "testWrite")
253 |
254 | taskResult.error = URLSessionDataTaskMock.TaskMockError.mockError1
255 | session.expectedDataTaskResult = taskResult
256 |
257 | request.write(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", method: .post, data: [:]) { result in
258 | switch result {
259 | case .succeeded:
260 | XCTFail()
261 |
262 | case .failed(let error):
263 | XCTAssertTrue(error is URLSessionDataTaskMock.TaskMockError)
264 | let errorInfo = error as! URLSessionDataTaskMock.TaskMockError
265 | XCTAssertTrue(errorInfo == URLSessionDataTaskMock.TaskMockError.mockError1)
266 | }
267 | expectation1.fulfill()
268 | }
269 | waitForExpectations(timeout: 1)
270 | }
271 |
272 | func testWriteWithNoURLResponse() {
273 | let taskResult = URLSessionDataTaskMock.Result()
274 | let session = URLSessionMock()
275 | let operation = JSONRequestOperation.create()
276 | let response = RequestResponse()
277 | let request = Request(session: session, operation: operation, response: response)
278 | let expectation1 = expectation(description: "testWrite")
279 |
280 | taskResult.response = nil
281 | session.expectedDataTaskResult = taskResult
282 |
283 | request.write(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", method: .post, data: [:]) { result in
284 | switch result {
285 | case .succeeded:
286 | XCTFail()
287 |
288 | case .failed(let error):
289 | XCTAssertTrue(error is RequestError)
290 | let errorInfo = error as! RequestError
291 | XCTAssertTrue(errorInfo == RequestError.noURLResponse)
292 | }
293 | expectation1.fulfill()
294 | }
295 | waitForExpectations(timeout: 1)
296 | }
297 |
298 | func testWriteWithNilData() {
299 | let taskResult = URLSessionDataTaskMock.Result()
300 | let session = URLSessionMock()
301 | let operation = JSONRequestOperation.create()
302 | let response = RequestResponse()
303 | let request = Request(session: session, operation: operation, response: response)
304 | let expectation1 = expectation(description: "testWrite")
305 |
306 | taskResult.response = HTTPURLResponse()
307 | session.expectedDataTaskResult = taskResult
308 |
309 | request.write(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", method: .post, data: [:]) { result in
310 | switch result {
311 | case .failed:
312 | XCTFail()
313 |
314 | case .succeeded(let info):
315 | XCTAssertTrue(info is [AnyHashable: Any])
316 | let resultInfo = info as! [AnyHashable: Any]
317 | XCTAssertTrue(resultInfo.isEmpty)
318 | }
319 | expectation1.fulfill()
320 | }
321 | waitForExpectations(timeout: 1)
322 | }
323 |
324 | func testWriteWithErrorParsingData() {
325 | let taskResult = URLSessionDataTaskMock.Result()
326 | let session = URLSessionMock()
327 | let operation = RequestOperationMock()
328 | let response = RequestResponse()
329 | let request = Request(session: session, operation: operation, response: response)
330 | let expectation1 = expectation(description: "testWrite")
331 |
332 | taskResult.response = HTTPURLResponse()
333 | taskResult.data = Data(bytes: [1,2,3])
334 | session.expectedDataTaskResult = taskResult
335 |
336 | request.write(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", method: .post, data: [:]) { result in
337 | switch result {
338 | case .succeeded:
339 | XCTFail()
340 |
341 | case .failed(let info):
342 | XCTAssertTrue(info is RequestOperationMock.MockError)
343 | let errorInfo = info as! RequestOperationMock.MockError
344 | XCTAssertTrue(errorInfo == RequestOperationMock.MockError.failedToParse)
345 | }
346 | expectation1.fulfill()
347 | }
348 | waitForExpectations(timeout: 1)
349 | }
350 |
351 | func testDeleteWithInvalidURL() {
352 | let request = Request.create()
353 | let expectation1 = expectation(description: "testDelete")
354 | request.delete(path: "") { result in
355 | switch result {
356 | case .succeeded:
357 | XCTFail()
358 |
359 | case .failed(let error):
360 | XCTAssertTrue(error is RequestError)
361 | let errorInfo = error as! RequestError
362 | XCTAssertTrue(errorInfo == RequestError.invalidURL)
363 | }
364 | expectation1.fulfill()
365 | }
366 | waitForExpectations(timeout: 1)
367 | }
368 |
369 | func testDeleteWithError() {
370 | let taskResult = URLSessionDataTaskMock.Result()
371 | let session = URLSessionMock()
372 | let operation = JSONRequestOperation.create()
373 | let response = RequestResponse()
374 | let request = Request(session: session, operation: operation, response: response)
375 | let expectation1 = expectation(description: "testDelete")
376 |
377 | taskResult.error = URLSessionDataTaskMock.TaskMockError.mockError1
378 | session.expectedDataTaskResult = taskResult
379 |
380 | request.delete(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken") { result in
381 | switch result {
382 | case .succeeded:
383 | XCTFail()
384 |
385 | case .failed(let error):
386 | XCTAssertTrue(error is URLSessionDataTaskMock.TaskMockError)
387 | let errorInfo = error as! URLSessionDataTaskMock.TaskMockError
388 | XCTAssertTrue(errorInfo == URLSessionDataTaskMock.TaskMockError.mockError1)
389 | }
390 | expectation1.fulfill()
391 | }
392 | waitForExpectations(timeout: 1)
393 | }
394 |
395 | func testDeleteWithNoURLResponse() {
396 | let taskResult = URLSessionDataTaskMock.Result()
397 | let session = URLSessionMock()
398 | let operation = JSONRequestOperation.create()
399 | let response = RequestResponse()
400 | let request = Request(session: session, operation: operation, response: response)
401 | let expectation1 = expectation(description: "testDelete")
402 |
403 | taskResult.response = nil
404 | session.expectedDataTaskResult = taskResult
405 |
406 | request.delete(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken") { result in
407 | switch result {
408 | case .succeeded:
409 | XCTFail()
410 |
411 | case .failed(let error):
412 | XCTAssertTrue(error is RequestError)
413 | let errorInfo = error as! RequestError
414 | XCTAssertTrue(errorInfo == RequestError.noURLResponse)
415 | }
416 | expectation1.fulfill()
417 | }
418 | waitForExpectations(timeout: 1)
419 | }
420 |
421 | func testDeleteNilData() {
422 | let taskResult = URLSessionDataTaskMock.Result()
423 | let session = URLSessionMock()
424 | let operation = JSONRequestOperation.create()
425 | let response = RequestResponse()
426 | let request = Request(session: session, operation: operation, response: response)
427 | let expectation1 = expectation(description: "testDelete")
428 |
429 | taskResult.response = HTTPURLResponse()
430 | session.expectedDataTaskResult = taskResult
431 |
432 | request.delete(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken") { result in
433 | switch result {
434 | case .failed:
435 | XCTFail()
436 |
437 | case .succeeded(let info):
438 | XCTAssertTrue(info is [AnyHashable: Any])
439 | let resultInfo = info as! [AnyHashable: Any]
440 | XCTAssertTrue(resultInfo.isEmpty)
441 | }
442 | expectation1.fulfill()
443 | }
444 | waitForExpectations(timeout: 1)
445 | }
446 |
447 | func testDeleteWithErrorParsingData() {
448 | let taskResult = URLSessionDataTaskMock.Result()
449 | let session = URLSessionMock()
450 | let operation = RequestOperationMock()
451 | let response = RequestResponse()
452 | let request = Request(session: session, operation: operation, response: response)
453 | let expectation1 = expectation(description: "testDelete")
454 |
455 | taskResult.response = HTTPURLResponse()
456 | taskResult.data = Data(bytes: [1,2,3])
457 | session.expectedDataTaskResult = taskResult
458 |
459 | request.delete(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken") { result in
460 | switch result {
461 | case .succeeded:
462 | XCTFail()
463 |
464 | case .failed(let info):
465 | XCTAssertTrue(info is RequestOperationMock.MockError)
466 | let errorInfo = info as! RequestOperationMock.MockError
467 | XCTAssertTrue(errorInfo == RequestOperationMock.MockError.failedToParse)
468 | }
469 | expectation1.fulfill()
470 | }
471 | waitForExpectations(timeout: 1)
472 | }
473 |
474 | func testDeleteWithJSONNull() {
475 | let taskResult = URLSessionDataTaskMock.Result()
476 | let session = URLSessionMock()
477 | let operation = JSONRequestOperation.create()
478 | let response = RequestResponse()
479 | let request = Request(session: session, operation: operation, response: response)
480 | let expectation1 = expectation(description: "testDelete")
481 |
482 | taskResult.response = HTTPURLResponse()
483 | taskResult.data = "null".data(using: .utf8)
484 | session.expectedDataTaskResult = taskResult
485 |
486 | request.delete(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken") { result in
487 | switch result {
488 | case .failed:
489 | XCTFail()
490 |
491 | case .succeeded(let info):
492 | XCTAssertTrue(info is String)
493 | let resultInfo = info as! String
494 | XCTAssertEqual(resultInfo.lowercased(), "null")
495 | }
496 | expectation1.fulfill()
497 | }
498 | waitForExpectations(timeout: 1)
499 | }
500 |
501 | func testWriteHavingPOSTWithJSONNull() {
502 | let taskResult = URLSessionDataTaskMock.Result()
503 | let session = URLSessionMock()
504 | let operation = JSONRequestOperation.create()
505 | let response = RequestResponse()
506 | let request = Request(session: session, operation: operation, response: response)
507 | let expectation1 = expectation(description: "testWritePOST")
508 |
509 | taskResult.response = HTTPURLResponse()
510 | taskResult.data = "null".data(using: .utf8)
511 | session.expectedDataTaskResult = taskResult
512 |
513 | request.write(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", method: .post, data: [:]) { result in
514 | switch result {
515 | case .succeeded:
516 | XCTFail()
517 |
518 | case .failed(let info):
519 | XCTAssertTrue(info is RequestError)
520 | let resultInfo = info as! RequestError
521 | XCTAssertTrue(resultInfo == RequestError.nullJSON)
522 | }
523 | expectation1.fulfill()
524 | }
525 | waitForExpectations(timeout: 1)
526 | }
527 |
528 | func testWriteHavingPUTWithJSONNull() {
529 | let taskResult = URLSessionDataTaskMock.Result()
530 | let session = URLSessionMock()
531 | let operation = JSONRequestOperation.create()
532 | let response = RequestResponse()
533 | let request = Request(session: session, operation: operation, response: response)
534 | let expectation1 = expectation(description: "testWritePUT")
535 |
536 | taskResult.response = HTTPURLResponse()
537 | taskResult.data = "null".data(using: .utf8)
538 | session.expectedDataTaskResult = taskResult
539 |
540 | request.write(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", method: .put, data: [:]) { result in
541 | switch result {
542 | case .succeeded:
543 | XCTFail()
544 |
545 | case .failed(let info):
546 | XCTAssertTrue(info is RequestError)
547 | let resultInfo = info as! RequestError
548 | XCTAssertTrue(resultInfo == RequestError.nullJSON)
549 | }
550 | expectation1.fulfill()
551 | }
552 | waitForExpectations(timeout: 1)
553 | }
554 |
555 | func testWriteHavingPATCHWithJSONNull() {
556 | let taskResult = URLSessionDataTaskMock.Result()
557 | let session = URLSessionMock()
558 | let operation = JSONRequestOperation.create()
559 | let response = RequestResponse()
560 | let request = Request(session: session, operation: operation, response: response)
561 | let expectation1 = expectation(description: "testWritePATCH")
562 |
563 | taskResult.response = HTTPURLResponse()
564 | taskResult.data = "null".data(using: .utf8)
565 | session.expectedDataTaskResult = taskResult
566 |
567 | request.write(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", method: .patch, data: [:]) { result in
568 | switch result {
569 | case .succeeded:
570 | XCTFail()
571 |
572 | case .failed(let info):
573 | XCTAssertTrue(info is RequestError)
574 | let resultInfo = info as! RequestError
575 | XCTAssertTrue(resultInfo == RequestError.nullJSON)
576 | }
577 | expectation1.fulfill()
578 | }
579 | waitForExpectations(timeout: 1)
580 | }
581 |
582 | func testReadShouldReturnErroneous() {
583 | let taskResult = URLSessionDataTaskMock.Result()
584 | let session = URLSessionMock()
585 | let operation = JSONRequestOperation.create()
586 | let response = RequestResponse()
587 | let request = Request(session: session, operation: operation, response: response)
588 | let expectation1 = expectation(description: "testRead")
589 |
590 | taskResult.response = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 400, httpVersion: nil, headerFields: nil)
591 | session.expectedDataTaskResult = taskResult
592 |
593 | request.read(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", query: [:]) { result in
594 | switch result {
595 | case .succeeded:
596 | XCTFail()
597 |
598 | case .failed(let info):
599 | XCTAssertTrue(info is RequestError)
600 | let resultInfo = info as! RequestError
601 | XCTAssertTrue(resultInfo == .badRequest(""))
602 | }
603 | expectation1.fulfill()
604 | }
605 | waitForExpectations(timeout: 1)
606 | }
607 |
608 | func testWriteShouldReturnErroneous() {
609 | let taskResult = URLSessionDataTaskMock.Result()
610 | let session = URLSessionMock()
611 | let operation = RequestOperationMock()
612 | let response = RequestResponse()
613 | let request = Request(session: session, operation: operation, response: response)
614 | let expectation1 = expectation(description: "testWrite")
615 |
616 | taskResult.response = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 500, httpVersion: nil, headerFields: nil)
617 | session.expectedDataTaskResult = taskResult
618 |
619 | request.write(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken", method: .post, data: [:]) { result in
620 | switch result {
621 | case .succeeded:
622 | XCTFail()
623 |
624 | case .failed(let info):
625 | XCTAssertTrue(info is RequestError)
626 | let errorInfo = info as! RequestError
627 | XCTAssertTrue(errorInfo == .internalServiceError(""))
628 | }
629 | expectation1.fulfill()
630 | }
631 | waitForExpectations(timeout: 1)
632 | }
633 |
634 | func testDeleteShouldReturnErroneous() {
635 | let taskResult = URLSessionDataTaskMock.Result()
636 | let session = URLSessionMock()
637 | let operation = RequestOperationMock()
638 | let response = RequestResponse()
639 | let request = Request(session: session, operation: operation, response: response)
640 | let expectation1 = expectation(description: "testDelete")
641 |
642 | taskResult.response = HTTPURLResponse(url: URL(string: "https://sampleio.com")!, statusCode: 404, httpVersion: nil, headerFields: nil)
643 | session.expectedDataTaskResult = taskResult
644 |
645 | request.delete(path: "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken") { result in
646 | switch result {
647 | case .succeeded:
648 | XCTFail()
649 |
650 | case .failed(let info):
651 | XCTAssertTrue(info is RequestError)
652 | let errorInfo = info as! RequestError
653 | XCTAssertTrue(errorInfo == .notFound(""))
654 | }
655 | expectation1.fulfill()
656 | }
657 | waitForExpectations(timeout: 1)
658 | }
659 |
660 | func testBuildURLforGETWithNonEmptyData() {
661 | let request = Request.create()
662 | var path = "https://foo.firebaseio.com/posts"
663 | var url = request.buildURL(path, .get, ["orderBy": "\"$key\"", "limitToFirst": 1])
664 | XCTAssertNotNil(url)
665 | XCTAssertEqual(url!.absoluteString, "\(path)?orderBy=%22$key%22&limitToFirst=1")
666 |
667 | let accessToken = "accessToken"
668 | path = "https://foo.firebaseio.com/posts.json?auth=\(accessToken)"
669 | url = request.buildURL(path, .get, ["orderBy": "\"$key\"", "limitToFirst": 1])
670 | XCTAssertNotNil(url)
671 | XCTAssertEqual(url!.absoluteString, "\(path)&orderBy=%22$key%22&limitToFirst=1")
672 |
673 | url = request.buildURL("", .get, ["orderBy": "\"$key\"", "limitToFirst": 1])
674 | XCTAssertNil(url)
675 | }
676 | }
677 |
--------------------------------------------------------------------------------
/PyrobaseTests/URLSessionDataDelegateMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLSessionDataDelegateMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 23/06/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class URLSessionDataDelegateMock: NSObject, URLSessionDataDelegate {
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/PyrobaseTests/URLSessionMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URLSessionMock.swift
3 | // Pyrobase
4 | //
5 | // Created by Mounir Ybanez on 02/05/2017.
6 | // Copyright © 2017 Ner. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class URLSessionMock: URLSession {
12 |
13 | var content = [
14 | "https://foo.firebaseio.com/users/12345/name.json?access_token=accessToken": "Luche",
15 | "https://foo.firebaseio.com/users/12345/int.json?access_token=accessToken": "101",
16 | "https://foo.firebaseio.com/users/12345/double.json?access_token=accessToken": "101.12345"
17 | ]
18 |
19 | var expectedDataTaskResult: URLSessionDataTaskMock.Result?
20 | var shouldExecuteTaskResume: Bool = true
21 |
22 | override func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
23 | let url = request.url!
24 | let task = URLSessionDataTaskMock(handler: completionHandler)
25 | task.handler = completionHandler
26 |
27 | if expectedDataTaskResult != nil {
28 | task.result = expectedDataTaskResult!
29 |
30 | } else {
31 | task.result.data = content[url.absoluteString]?.data(using: .utf8)
32 | task.result.response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil)
33 | task.result.error = nil
34 | }
35 |
36 | return task
37 | }
38 |
39 | override func dataTask(with url: URL) -> URLSessionDataTask {
40 | let task = URLSessionDataTaskMock(httpResponse: nil)
41 | task.shouldExecuteResume = shouldExecuteTaskResume
42 | return task
43 | }
44 | }
45 |
46 | class URLSessionDataTaskMock: URLSessionDataTask {
47 |
48 | enum TaskMockError: Error {
49 |
50 | case mockError1
51 | }
52 |
53 | class Result {
54 |
55 | var data: Data?
56 | var response: URLResponse?
57 | var error: Error?
58 | }
59 |
60 | var handler: (Data?, URLResponse?, Error?) -> Void
61 | var result: Result
62 | var httpResponse: HTTPURLResponse?
63 | var shouldExecuteResume: Bool = true
64 |
65 | override var response: URLResponse? {
66 | return httpResponse
67 | }
68 |
69 | init(handler: @escaping (Data?, URLResponse?, Error?) -> Void) {
70 | self.handler = handler
71 | self.result = Result()
72 | }
73 |
74 | convenience init(httpResponse: HTTPURLResponse?) {
75 | let handler: (Data?, URLResponse?, Error?) -> Void = { _, _, _ in }
76 | self.init(handler: handler)
77 | self.httpResponse = httpResponse
78 | }
79 |
80 | override func resume() {
81 | guard shouldExecuteResume else {
82 | return
83 | }
84 |
85 | handler(result.data, result.response, result.error)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pyrobase
2 | An iOS lightweight wrapper for Firebase REST API. For more Firebase's details, see it [here.](https://firebase.google.com/docs/reference/rest/database/) It is written in Swift and has 100% code coverage.
3 |
4 | ## Installation
5 |
6 | ### Cocoapods
7 | ```shell
8 | $ pod repo update
9 | $ pod init
10 | $ vim Podfile
11 | // Add `pod Pyrobase` in your podfile
12 | $ pod install
13 | ```
14 |
15 | ### Carthage
16 | ```shell
17 | $ brew install carthage
18 | $ vim Cartfile
19 | // Add `github "mownier/Pyrobase" ~> 1.0` in your cartfile
20 | $ carthage update
21 | ```
22 |
23 | ## Usage
24 |
25 | ### Authentication
26 |
27 | Make sure to copy `PyroAuthInfo.plist`. And keep in mind the bundle identifier where the said file is added.
28 |
29 | #### Initialization
30 | ```swift
31 | let apiKey = "yourFirebaseAPIKey"
32 | let bundleIdentifier = "com.my.app"
33 | let auth = PyroAuth.create(key: apiKey, bundleIdentifier: bundleIdentifier)
34 | // The variable 'auth' is nil if you provide an invalid bundle identifier.
35 | // Otherwise, you are good to go.
36 | // NOTE: If you build this project as framework, you can opt out
37 | // providing the bundle identifier. The default value
38 | // is the project's bundle identifier
39 | ```
40 |
41 | #### Sign In
42 | ```swift
43 | auth.signIn(email: email, password: password) { result in
44 | switch result {
45 | case .failed(let error):
46 | print(error)
47 | // Do some stuff
48 |
49 | case .succeeded(let data):
50 | print(data)
51 | // Do some stuff
52 | // 'data' is PyroAuthContent
53 | }
54 | }
55 | ```
56 |
57 | #### Register
58 | ```swift
59 | auth.register(email: email, password: password) { result in
60 | switch result {
61 | case .failed(let error):
62 | print(error)
63 | // Do some stuff
64 |
65 | case .succeeded(let data):
66 | print(data)
67 | // Do some stuff
68 | // 'data' is PyroAuthContent
69 | }
70 | }
71 | ```
72 |
73 | #### Refresh Token
74 | ```swift
75 | auth.refresh(token: "refreshToken") { result in
76 | switch result {
77 | case .failed(let error):
78 | print(error)
79 | // Do some stuff
80 |
81 | case .succeeded(let data):
82 | print(data)
83 | // Do some stuff
84 | // 'data' is PyroAuthTokenContent
85 | }
86 | }
87 | ```
88 |
89 | #### Send Password Reset
90 | ```swift
91 | auth.sendPasswordReset(email: "me@me.com") { result in
92 | switch result {
93 | case .failed(let error):
94 | print(error)
95 | // Do some stuff
96 |
97 | case .succeeded(let data):
98 | print(data)
99 | // Do some stuff
100 | // 'data' is Bool
101 | }
102 | }
103 | ```
104 |
105 | ### REST
106 |
107 | #### Initialization
108 | ```swift
109 | let baseURL = "https://foo.firebaseio.com"
110 | let accessToken = "accessToken"
111 | let pyrobase = Pyrobase.create(baseURL: baseURL, accessToken: accessToken)
112 | ```
113 |
114 | #### GET Request
115 | ```swift
116 | pyrobase.get(path: "users/abcde12345wert", query: [:]) { result in
117 | switch result {
118 | case .failed(let error):
119 | print(error)
120 | // Do some stuff
121 |
122 | case .succeeded(let data):
123 | print(data)
124 | // Do some stuff
125 | }
126 | }
127 | ```
128 |
129 | #### POST Request
130 | ```swift
131 | pyrobase.post(path: "messages", value: ["message": "hello world", "user_id": "abcde12345qwert"]) { result in
132 | switch result {
133 | case .failed(let error):
134 | print(error)
135 | // Do some stuff
136 |
137 | case .succeeded(let data):
138 | print(data)
139 | // Do some stuff
140 | }
141 | }
142 | ```
143 |
144 | #### PUT Request
145 | ```swift
146 | pyrobase.put(path: "users/abcde12345wert", value: ["first_name": "Juan", "last_name": "Dela Cruz"]) { result in
147 | switch result {
148 | case .failed(let error):
149 | print(error)
150 | // Do some stuff
151 |
152 | case .succeeded(let data):
153 | print(data)
154 | // Do some stuff
155 | }
156 | }
157 | ```
158 |
159 | #### PATCH Request
160 | ```swift
161 | pyrobase.patch(path: "users/abcde12345wert", value: ["first_name": "Jose"]) { result in
162 | switch result {
163 | case .failed(let error):
164 | print(error)
165 | // Do some stuff
166 |
167 | case .succeeded(let data):
168 | print(data)
169 | // Do some stuff
170 | }
171 | }
172 | ```
173 |
174 | ### Transaction
175 |
176 | #### Initialization
177 | ```swift
178 | let baseURL = "https://foo.firebaseio.com"
179 | let accessToken = "accessToken"
180 | let transaction = PyroTransaction.create(baseURL: baseURL, accessToken: accessToken)
181 | ```
182 | #### Run
183 | ```swift
184 | transaction.run(
185 | parentPath: "posts/yuiop98765nbcwe",
186 | childKey: "likes_count",
187 | mutator: { data in
188 | let likesCount = data as! Int
189 | return likesCount + 1
190 | }) { result in
191 | switch result {
192 | case .failed(let error):
193 | print(error)
194 | // Do some stuff
195 |
196 | case .succeeded(let data):
197 | print(data)
198 | // Do some stuff
199 | }
200 | }
201 | ```
202 |
203 |
204 | ### Event Source
205 |
206 | #### Callback
207 | ```swift
208 | class StreamCallback: PyroEventSourceCallback {
209 |
210 | func pyroEventSource(_ eventSource: PyroEventSource, didReceiveError error: Error) {
211 | // Do some stuff
212 | }
213 |
214 | func pyroEventSource(_ eventSource: PyroEventSource, didReceiveMessage message: PyroEventSourceMessage) {
215 | // Do some stuff
216 | }
217 |
218 | func pyroEventSourceOnOpen(_ eventSource: PyroEventSource) {
219 | // Do some stuff
220 | }
221 |
222 | func pyroEventSourceOnClosed(_ eventSource: PyroEventSource) {
223 | // Do some stuff
224 | }
225 |
226 | func pyroEventSourceOnConnecting(_ eventSource: PyroEventSource) {
227 | // Do some stuff
228 | }
229 | }
230 | ```
231 |
232 | #### Initialization
233 | ```swift
234 | let callback = StreamCallback()
235 | let baseURL = "https://foo.firebaseio.com"
236 | let accessToken = "accessToken"
237 | let eventSource = PyroEventSource.create(baseURL: baseURL, accessToken: accessToken)
238 | eventSource.callback = callback
239 | ```
240 |
241 | #### Stream
242 | ```swift
243 | eventSource.stream("chat/rooms/hdjye53910kwdop")
244 | ```
245 |
246 | #### Close
247 | ```swift
248 | eventSource.close()
249 | ```
250 |
251 | ### Query Parameters
252 | Always keep in mind of adding `.indexOn` in your rules for the path you want to query. You may receive a `badRequest` error if you don't set it.
253 |
254 | ```swift
255 | let query = ["orderBy": "\"$key\"", "limitToFirst": 1]
256 | pyrobase.get("posts", query: query) { result in
257 | switch result {
258 | case .failed(let error):
259 | print(error)
260 | // Do some stuff
261 |
262 | case .succeeded(let data):
263 | print(data)
264 | // Do some stuff
265 | }
266 | }
267 | ```
268 |
269 |
270 | ### License
271 |
272 | MIT License
--------------------------------------------------------------------------------