IntegrationTests.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. import FirebaseFunctions
  16. import FirebaseFunctionsSwift
  17. import FirebaseFunctionsTestingSupport
  18. import XCTest
  19. /// This file was intitialized as a direct port of the Objective C
  20. /// FirebaseFunctions/Tests/Integration/FIRIntegrationTests.m
  21. ///
  22. /// The tests require the emulator to be running with `FirebaseFunctions/Backend/start.sh synchronous`
  23. /// The Firebase Functions called in the tests are implemented in `FirebaseFunctions/Backend/index.js`.
  24. struct DataTestRequest: Encodable {
  25. var bool: Bool
  26. var int: Int32
  27. var long: Int64
  28. var string: String
  29. var array: [Int32]
  30. // NOTE: Auto-synthesized Encodable conformance uses 'encodeIfPresent' to
  31. // encode Optional values. To encode Optional.none as null you either need
  32. // to write a manual encodable conformance or use a helper like the
  33. // propertyWrapper here:
  34. @NullEncodable var null: Bool?
  35. }
  36. @propertyWrapper
  37. struct NullEncodable<T>: Encodable where T: Encodable {
  38. var wrappedValue: T?
  39. init(wrappedValue: T?) {
  40. self.wrappedValue = wrappedValue
  41. }
  42. func encode(to encoder: Encoder) throws {
  43. var container = encoder.singleValueContainer()
  44. switch wrappedValue {
  45. case let .some(value): try container.encode(value)
  46. case .none: try container.encodeNil()
  47. }
  48. }
  49. }
  50. struct DataTestResponse: Decodable, Equatable {
  51. var message: String
  52. var long: Int64
  53. var code: Int32
  54. }
  55. class IntegrationTests: XCTestCase {
  56. let functions = FunctionsFake(
  57. projectID: "functions-integration-test",
  58. region: "us-central1",
  59. customDomain: nil,
  60. withToken: nil
  61. )
  62. let projectID = "functions-swift-integration-test"
  63. override func setUp() {
  64. super.setUp()
  65. functions.useLocalhost()
  66. }
  67. func testData() throws {
  68. let expectation = expectation(description: #function)
  69. let data = DataTestRequest(
  70. bool: true,
  71. int: 2,
  72. long: 9_876_543_210,
  73. string: "four",
  74. array: [5, 6],
  75. null: nil
  76. )
  77. let function = functions.httpsCallable("dataTest",
  78. requestType: DataTestRequest.self,
  79. responseType: DataTestResponse.self)
  80. try function.call(data) { result in
  81. do {
  82. let response = try result.get()
  83. let expected = DataTestResponse(
  84. message: "stub response",
  85. long: 420,
  86. code: 42
  87. )
  88. XCTAssertEqual(response, expected)
  89. expectation.fulfill()
  90. } catch {
  91. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  92. }
  93. }
  94. waitForExpectations(timeout: 5)
  95. }
  96. #if compiler(>=5.5) && canImport(_Concurrency)
  97. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  98. func testDataAsync() async throws {
  99. let data = DataTestRequest(
  100. bool: true,
  101. int: 2,
  102. long: 9_876_543_210,
  103. string: "four",
  104. array: [5, 6],
  105. null: nil
  106. )
  107. let function = functions.httpsCallable("dataTest",
  108. requestType: DataTestRequest.self,
  109. responseType: DataTestResponse.self)
  110. let response = try await function.call(data)
  111. let expected = DataTestResponse(
  112. message: "stub response",
  113. long: 420,
  114. code: 42
  115. )
  116. XCTAssertEqual(response, expected)
  117. }
  118. #endif
  119. func testScalar() throws {
  120. let expectation = expectation(description: #function)
  121. let function = functions.httpsCallable(
  122. "scalarTest",
  123. requestType: Int16.self,
  124. responseType: Int.self
  125. )
  126. try function.call(17) { result in
  127. do {
  128. let response = try result.get()
  129. XCTAssertEqual(response, 76)
  130. expectation.fulfill()
  131. } catch {
  132. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  133. }
  134. }
  135. waitForExpectations(timeout: 5)
  136. }
  137. #if compiler(>=5.5) && canImport(_Concurrency)
  138. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  139. func testScalarAsync() async throws {
  140. let function = functions.httpsCallable(
  141. "scalarTest",
  142. requestType: Int16.self,
  143. responseType: Int.self
  144. )
  145. let result = try await function.call(17)
  146. XCTAssertEqual(result, 76)
  147. }
  148. #endif
  149. func testToken() throws {
  150. // Recreate functions with a token.
  151. let functions = FunctionsFake(
  152. projectID: "functions-integration-test",
  153. region: "us-central1",
  154. customDomain: nil,
  155. withToken: "token"
  156. )
  157. functions.useLocalhost()
  158. let expectation = expectation(description: #function)
  159. let function = functions.httpsCallable(
  160. "FCMTokenTest",
  161. requestType: [String: Int].self,
  162. responseType: [String: Int].self
  163. )
  164. XCTAssertNotNil(function)
  165. try function.call([:]) { result in
  166. do {
  167. let data = try result.get()
  168. XCTAssertEqual(data, [:])
  169. expectation.fulfill()
  170. } catch {
  171. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  172. }
  173. }
  174. waitForExpectations(timeout: 5)
  175. }
  176. #if compiler(>=5.5) && canImport(_Concurrency)
  177. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  178. func testTokenAsync() async throws {
  179. // Recreate functions with a token.
  180. let functions = FunctionsFake(
  181. projectID: "functions-integration-test",
  182. region: "us-central1",
  183. customDomain: nil,
  184. withToken: "token"
  185. )
  186. functions.useLocalhost()
  187. let function = functions.httpsCallable(
  188. "FCMTokenTest",
  189. requestType: [String: Int].self,
  190. responseType: [String: Int].self
  191. )
  192. let data = try await function.call([:])
  193. XCTAssertEqual(data, [:])
  194. }
  195. #endif
  196. func testFCMToken() throws {
  197. let expectation = expectation(description: #function)
  198. let function = functions.httpsCallable(
  199. "FCMTokenTest",
  200. requestType: [String: Int].self,
  201. responseType: [String: Int].self
  202. )
  203. try function.call([:]) { result in
  204. do {
  205. let data = try result.get()
  206. XCTAssertEqual(data, [:])
  207. expectation.fulfill()
  208. } catch {
  209. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  210. }
  211. }
  212. waitForExpectations(timeout: 5)
  213. }
  214. #if compiler(>=5.5) && canImport(_Concurrency)
  215. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  216. func testFCMTokenAsync() async throws {
  217. let function = functions.httpsCallable(
  218. "FCMTokenTest",
  219. requestType: [String: Int].self,
  220. responseType: [String: Int].self
  221. )
  222. let data = try await function.call([:])
  223. XCTAssertEqual(data, [:])
  224. }
  225. #endif
  226. func testNull() throws {
  227. let expectation = expectation(description: #function)
  228. let function = functions.httpsCallable(
  229. "nullTest",
  230. requestType: Int?.self,
  231. responseType: Int?.self
  232. )
  233. try function.call(nil) { result in
  234. do {
  235. let data = try result.get()
  236. XCTAssertEqual(data, nil)
  237. expectation.fulfill()
  238. } catch {
  239. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  240. }
  241. }
  242. waitForExpectations(timeout: 5)
  243. }
  244. #if compiler(>=5.5) && canImport(_Concurrency)
  245. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  246. func testNullAsync() async throws {
  247. let function = functions.httpsCallable(
  248. "nullTest",
  249. requestType: Int?.self,
  250. responseType: Int?.self
  251. )
  252. let data = try await function.call(nil)
  253. XCTAssertEqual(data, nil)
  254. }
  255. #endif
  256. // No parameters to call should be the same as passing nil.
  257. // If no parameters are required, then the non-typed API
  258. // is more appropriate since it specifically avoids defining
  259. // type.
  260. // func testParameterless() {
  261. // }
  262. //
  263. //
  264. func testMissingResult() throws {
  265. let expectation = expectation(description: #function)
  266. let function = functions.httpsCallable(
  267. "missingResultTest",
  268. requestType: Int?.self,
  269. responseType: Int?.self
  270. )
  271. try function.call(nil) { result in
  272. do {
  273. _ = try result.get()
  274. } catch {
  275. let error = error as NSError
  276. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  277. XCTAssertEqual("Response is missing data field.", error.localizedDescription)
  278. expectation.fulfill()
  279. }
  280. }
  281. waitForExpectations(timeout: 5)
  282. }
  283. #if compiler(>=5.5) && canImport(_Concurrency)
  284. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  285. func testMissingResultAsync() async {
  286. let function = functions.httpsCallable(
  287. "missingResultTest",
  288. requestType: Int?.self,
  289. responseType: Int?.self
  290. )
  291. do {
  292. _ = try await function.call(nil)
  293. XCTFail("Failed to throw error for missing result")
  294. } catch {
  295. let error = error as NSError
  296. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  297. XCTAssertEqual("Response is missing data field.", error.localizedDescription)
  298. }
  299. }
  300. #endif
  301. func testUnhandledError() throws {
  302. let expectation = expectation(description: #function)
  303. let function = functions.httpsCallable(
  304. "unhandledErrorTest",
  305. requestType: [Int].self,
  306. responseType: Int.self
  307. )
  308. try function.call([]) { result in
  309. do {
  310. _ = try result.get()
  311. } catch {
  312. let error = error as NSError
  313. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  314. XCTAssertEqual("INTERNAL", error.localizedDescription)
  315. expectation.fulfill()
  316. }
  317. }
  318. XCTAssert(true)
  319. waitForExpectations(timeout: 5)
  320. }
  321. #if compiler(>=5.5) && canImport(_Concurrency)
  322. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  323. func testUnhandledErrorAsync() async {
  324. let function = functions.httpsCallable(
  325. "unhandledErrorTest",
  326. requestType: [Int].self,
  327. responseType: Int.self
  328. )
  329. do {
  330. _ = try await function.call([])
  331. XCTFail("Failed to throw error for missing result")
  332. } catch {
  333. let error = error as NSError
  334. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  335. XCTAssertEqual("INTERNAL", error.localizedDescription)
  336. }
  337. }
  338. #endif
  339. func testUnknownError() throws {
  340. let expectation = expectation(description: #function)
  341. let function = functions.httpsCallable(
  342. "unknownErrorTest",
  343. requestType: [Int].self,
  344. responseType: Int.self
  345. )
  346. try function.call([]) { result in
  347. do {
  348. _ = try result.get()
  349. } catch {
  350. let error = error as NSError
  351. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  352. XCTAssertEqual("INTERNAL", error.localizedDescription)
  353. expectation.fulfill()
  354. }
  355. }
  356. waitForExpectations(timeout: 5)
  357. }
  358. #if compiler(>=5.5) && canImport(_Concurrency)
  359. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  360. func testUnknownErrorAsync() async {
  361. let function = functions.httpsCallable(
  362. "unknownErrorTest",
  363. requestType: [Int].self,
  364. responseType: Int.self
  365. )
  366. do {
  367. _ = try await function.call([])
  368. XCTAssertFalse(true, "Failed to throw error for missing result")
  369. } catch {
  370. let error = error as NSError
  371. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  372. XCTAssertEqual("INTERNAL", error.localizedDescription)
  373. }
  374. }
  375. #endif
  376. func testExplicitError() throws {
  377. let expectation = expectation(description: #function)
  378. let function = functions.httpsCallable(
  379. "explicitErrorTest",
  380. requestType: [Int].self,
  381. responseType: Int.self
  382. )
  383. try function.call([]) { result in
  384. do {
  385. _ = try result.get()
  386. } catch {
  387. let error = error as NSError
  388. XCTAssertEqual(FunctionsErrorCode.outOfRange.rawValue, error.code)
  389. XCTAssertEqual("explicit nope", error.localizedDescription)
  390. XCTAssertEqual(["start": 10 as Int32, "end": 20 as Int32, "long": 30],
  391. error.userInfo[FunctionsErrorDetailsKey] as! [String: Int32])
  392. expectation.fulfill()
  393. }
  394. }
  395. waitForExpectations(timeout: 5)
  396. }
  397. #if compiler(>=5.5) && canImport(_Concurrency)
  398. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  399. func testExplicitErrorAsync() async {
  400. let function = functions.httpsCallable(
  401. "explicitErrorTest",
  402. requestType: [Int].self,
  403. responseType: Int.self
  404. )
  405. do {
  406. _ = try await function.call([])
  407. XCTAssertFalse(true, "Failed to throw error for missing result")
  408. } catch {
  409. let error = error as NSError
  410. XCTAssertEqual(FunctionsErrorCode.outOfRange.rawValue, error.code)
  411. XCTAssertEqual("explicit nope", error.localizedDescription)
  412. XCTAssertEqual(["start": 10 as Int32, "end": 20 as Int32, "long": 30],
  413. error.userInfo[FunctionsErrorDetailsKey] as! [String: Int32])
  414. }
  415. }
  416. #endif
  417. func testHttpError() throws {
  418. let expectation = expectation(description: #function)
  419. let function = functions.httpsCallable(
  420. "httpErrorTest",
  421. requestType: [Int].self,
  422. responseType: Int.self
  423. )
  424. XCTAssertNotNil(function)
  425. try function.call([]) { result in
  426. do {
  427. _ = try result.get()
  428. } catch {
  429. let error = error as NSError
  430. XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code)
  431. expectation.fulfill()
  432. }
  433. }
  434. waitForExpectations(timeout: 5)
  435. }
  436. #if compiler(>=5.5) && canImport(_Concurrency)
  437. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  438. func testHttpErrorAsync() async {
  439. let function = functions.httpsCallable(
  440. "httpErrorTest",
  441. requestType: [Int].self,
  442. responseType: Int.self
  443. )
  444. do {
  445. _ = try await function.call([])
  446. XCTAssertFalse(true, "Failed to throw error for missing result")
  447. } catch {
  448. let error = error as NSError
  449. XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code)
  450. }
  451. }
  452. #endif
  453. func testTimeout() throws {
  454. let expectation = expectation(description: #function)
  455. var function = functions.httpsCallable(
  456. "timeoutTest",
  457. requestType: [Int].self,
  458. responseType: Int.self
  459. )
  460. function.timeoutInterval = 0.05
  461. try function.call([]) { result in
  462. do {
  463. _ = try result.get()
  464. } catch {
  465. let error = error as NSError
  466. XCTAssertEqual(FunctionsErrorCode.deadlineExceeded.rawValue, error.code)
  467. XCTAssertEqual("DEADLINE EXCEEDED", error.localizedDescription)
  468. XCTAssertNil(error.userInfo[FunctionsErrorDetailsKey])
  469. expectation.fulfill()
  470. }
  471. }
  472. waitForExpectations(timeout: 5)
  473. }
  474. #if compiler(>=5.5) && canImport(_Concurrency)
  475. @available(iOS 15, tvOS 15, macOS 12, watchOS 8, *)
  476. func testTimeoutAsync() async {
  477. var function = functions.httpsCallable(
  478. "timeoutTest",
  479. requestType: [Int].self,
  480. responseType: Int.self
  481. )
  482. function.timeoutInterval = 0.05
  483. do {
  484. _ = try await function.call([])
  485. XCTAssertFalse(true, "Failed to throw error for missing result")
  486. } catch {
  487. let error = error as NSError
  488. XCTAssertEqual(FunctionsErrorCode.deadlineExceeded.rawValue, error.code)
  489. XCTAssertEqual("DEADLINE EXCEEDED", error.localizedDescription)
  490. XCTAssertNil(error.userInfo[FunctionsErrorDetailsKey])
  491. }
  492. }
  493. #endif
  494. }