IntegrationTests.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680
  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. @testable import FirebaseFunctions
  16. import FirebaseAuthInterop
  17. import FirebaseMessagingInterop
  18. import XCTest
  19. /// This file was intitialized as a direct port of `FirebaseFunctionsSwift/Tests/IntegrationTests.swift`
  20. /// which itself was ported from the Objective C `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 = Functions(projectID: "functions-integration-test",
  57. region: "us-central1",
  58. customDomain: nil,
  59. auth: nil,
  60. messaging: MessagingTokenProvider(),
  61. appCheck: nil)
  62. let projectID = "functions-swift-integration-test"
  63. override func setUp() {
  64. super.setUp()
  65. functions.useEmulator(withHost: "localhost", port: 5005)
  66. }
  67. func testData() {
  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. requestAs: DataTestRequest.self,
  79. responseAs: DataTestResponse.self)
  80. 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. } catch {
  90. XCTFail("Failed to unwrap the function result: \(error)")
  91. }
  92. expectation.fulfill()
  93. }
  94. waitForExpectations(timeout: 5)
  95. }
  96. #if compiler(>=5.5.2) && canImport(_Concurrency)
  97. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  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. requestAs: DataTestRequest.self,
  109. responseAs: 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() {
  120. let expectation = expectation(description: #function)
  121. let function = functions.httpsCallable(
  122. "scalarTest",
  123. requestAs: Int16.self,
  124. responseAs: Int.self
  125. )
  126. function.call(17) { result in
  127. do {
  128. let response = try result.get()
  129. XCTAssertEqual(response, 76)
  130. } catch {
  131. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  132. }
  133. expectation.fulfill()
  134. }
  135. waitForExpectations(timeout: 5)
  136. }
  137. #if compiler(>=5.5.2) && canImport(_Concurrency)
  138. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  139. func testScalarAsync() async throws {
  140. let function = functions.httpsCallable(
  141. "scalarTest",
  142. requestAs: Int16.self,
  143. responseAs: Int.self
  144. )
  145. let result = try await function.call(17)
  146. XCTAssertEqual(result, 76)
  147. }
  148. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  149. func testScalarAsyncAlternateSignature() async throws {
  150. let function: Callable<Int16, Int> = functions.httpsCallable("scalarTest")
  151. let result = try await function.call(17)
  152. XCTAssertEqual(result, 76)
  153. }
  154. #endif
  155. func testToken() {
  156. // Recreate functions with a token.
  157. let functions = Functions(
  158. projectID: "functions-integration-test",
  159. region: "us-central1",
  160. customDomain: nil,
  161. auth: AuthTokenProvider(token: "token"),
  162. messaging: MessagingTokenProvider(),
  163. appCheck: nil
  164. )
  165. functions.useEmulator(withHost: "localhost", port: 5005)
  166. let expectation = expectation(description: #function)
  167. let function = functions.httpsCallable(
  168. "tokenTest",
  169. requestAs: [String: Int].self,
  170. responseAs: [String: Int].self
  171. )
  172. XCTAssertNotNil(function)
  173. function.call([:]) { result in
  174. do {
  175. let data = try result.get()
  176. XCTAssertEqual(data, [:])
  177. } catch {
  178. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  179. }
  180. expectation.fulfill()
  181. }
  182. waitForExpectations(timeout: 5)
  183. }
  184. #if compiler(>=5.5.2) && canImport(_Concurrency)
  185. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  186. func testTokenAsync() async throws {
  187. // Recreate functions with a token.
  188. let functions = Functions(
  189. projectID: "functions-integration-test",
  190. region: "us-central1",
  191. customDomain: nil,
  192. auth: AuthTokenProvider(token: "token"),
  193. messaging: MessagingTokenProvider(),
  194. appCheck: nil
  195. )
  196. functions.useEmulator(withHost: "localhost", port: 5005)
  197. let function = functions.httpsCallable(
  198. "tokenTest",
  199. requestAs: [String: Int].self,
  200. responseAs: [String: Int].self
  201. )
  202. let data = try await function.call([:])
  203. XCTAssertEqual(data, [:])
  204. }
  205. #endif
  206. func testFCMToken() {
  207. let expectation = expectation(description: #function)
  208. let function = functions.httpsCallable(
  209. "FCMTokenTest",
  210. requestAs: [String: Int].self,
  211. responseAs: [String: Int].self
  212. )
  213. function.call([:]) { result in
  214. do {
  215. let data = try result.get()
  216. XCTAssertEqual(data, [:])
  217. } catch {
  218. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  219. }
  220. expectation.fulfill()
  221. }
  222. waitForExpectations(timeout: 5)
  223. }
  224. #if compiler(>=5.5.2) && canImport(_Concurrency)
  225. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  226. func testFCMTokenAsync() async throws {
  227. let function = functions.httpsCallable(
  228. "FCMTokenTest",
  229. requestAs: [String: Int].self,
  230. responseAs: [String: Int].self
  231. )
  232. let data = try await function.call([:])
  233. XCTAssertEqual(data, [:])
  234. }
  235. #endif
  236. func testNull() {
  237. let expectation = expectation(description: #function)
  238. let function = functions.httpsCallable(
  239. "nullTest",
  240. requestAs: Int?.self,
  241. responseAs: Int?.self
  242. )
  243. function.call(nil) { result in
  244. do {
  245. let data = try result.get()
  246. XCTAssertEqual(data, nil)
  247. } catch {
  248. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  249. }
  250. expectation.fulfill()
  251. }
  252. waitForExpectations(timeout: 5)
  253. }
  254. #if compiler(>=5.5.2) && canImport(_Concurrency)
  255. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  256. func testNullAsync() async throws {
  257. let function = functions.httpsCallable(
  258. "nullTest",
  259. requestAs: Int?.self,
  260. responseAs: Int?.self
  261. )
  262. let data = try await function.call(nil)
  263. XCTAssertEqual(data, nil)
  264. }
  265. #endif
  266. func testMissingResult() {
  267. let expectation = expectation(description: #function)
  268. let function = functions.httpsCallable(
  269. "missingResultTest",
  270. requestAs: Int?.self,
  271. responseAs: Int?.self
  272. )
  273. function.call(nil) { result in
  274. do {
  275. _ = try result.get()
  276. } catch {
  277. let error = error as NSError
  278. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  279. XCTAssertEqual("Response is missing data field.", error.localizedDescription)
  280. expectation.fulfill()
  281. return
  282. }
  283. XCTFail("Failed to throw error for missing result")
  284. }
  285. waitForExpectations(timeout: 5)
  286. }
  287. #if compiler(>=5.5.2) && canImport(_Concurrency)
  288. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  289. func testMissingResultAsync() async {
  290. let function = functions.httpsCallable(
  291. "missingResultTest",
  292. requestAs: Int?.self,
  293. responseAs: Int?.self
  294. )
  295. do {
  296. _ = try await function.call(nil)
  297. XCTFail("Failed to throw error for missing result")
  298. } catch {
  299. let error = error as NSError
  300. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  301. XCTAssertEqual("Response is missing data field.", error.localizedDescription)
  302. }
  303. }
  304. #endif
  305. func testUnhandledError() {
  306. let expectation = expectation(description: #function)
  307. let function = functions.httpsCallable(
  308. "unhandledErrorTest",
  309. requestAs: [Int].self,
  310. responseAs: Int.self
  311. )
  312. function.call([]) { result in
  313. do {
  314. _ = try result.get()
  315. } catch {
  316. let error = error as NSError
  317. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  318. XCTAssertEqual("INTERNAL", error.localizedDescription)
  319. expectation.fulfill()
  320. return
  321. }
  322. XCTFail("Failed to throw error for missing result")
  323. }
  324. XCTAssert(true)
  325. waitForExpectations(timeout: 5)
  326. }
  327. #if compiler(>=5.5.2) && canImport(_Concurrency)
  328. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  329. func testUnhandledErrorAsync() async {
  330. let function = functions.httpsCallable(
  331. "unhandledErrorTest",
  332. requestAs: [Int].self,
  333. responseAs: Int.self
  334. )
  335. do {
  336. _ = try await function.call([])
  337. XCTFail("Failed to throw error for missing result")
  338. } catch {
  339. let error = error as NSError
  340. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  341. XCTAssertEqual("INTERNAL", error.localizedDescription)
  342. }
  343. }
  344. #endif
  345. func testUnknownError() {
  346. let expectation = expectation(description: #function)
  347. let function = functions.httpsCallable(
  348. "unknownErrorTest",
  349. requestAs: [Int].self,
  350. responseAs: Int.self
  351. )
  352. function.call([]) { result in
  353. do {
  354. _ = try result.get()
  355. } catch {
  356. let error = error as NSError
  357. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  358. XCTAssertEqual("INTERNAL", error.localizedDescription)
  359. expectation.fulfill()
  360. return
  361. }
  362. XCTFail("Failed to throw error for missing result")
  363. }
  364. waitForExpectations(timeout: 5)
  365. }
  366. #if compiler(>=5.5.2) && canImport(_Concurrency)
  367. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  368. func testUnknownErrorAsync() async {
  369. let function = functions.httpsCallable(
  370. "unknownErrorTest",
  371. requestAs: [Int].self,
  372. responseAs: Int.self
  373. )
  374. do {
  375. _ = try await function.call([])
  376. XCTAssertFalse(true, "Failed to throw error for missing result")
  377. } catch {
  378. let error = error as NSError
  379. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  380. XCTAssertEqual("INTERNAL", error.localizedDescription)
  381. }
  382. }
  383. #endif
  384. func testExplicitError() {
  385. let expectation = expectation(description: #function)
  386. let function = functions.httpsCallable(
  387. "explicitErrorTest",
  388. requestAs: [Int].self,
  389. responseAs: Int.self
  390. )
  391. function.call([]) { result in
  392. do {
  393. _ = try result.get()
  394. } catch {
  395. let error = error as NSError
  396. XCTAssertEqual(FunctionsErrorCode.outOfRange.rawValue, error.code)
  397. XCTAssertEqual("explicit nope", error.localizedDescription)
  398. XCTAssertEqual(["start": 10 as Int32, "end": 20 as Int32, "long": 30],
  399. error.userInfo["details"] as! [String: Int32])
  400. expectation.fulfill()
  401. return
  402. }
  403. XCTFail("Failed to throw error for missing result")
  404. }
  405. waitForExpectations(timeout: 5)
  406. }
  407. #if compiler(>=5.5.2) && canImport(_Concurrency)
  408. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  409. func testExplicitErrorAsync() async {
  410. let function = functions.httpsCallable(
  411. "explicitErrorTest",
  412. requestAs: [Int].self,
  413. responseAs: Int.self
  414. )
  415. do {
  416. _ = try await function.call([])
  417. XCTAssertFalse(true, "Failed to throw error for missing result")
  418. } catch {
  419. let error = error as NSError
  420. XCTAssertEqual(FunctionsErrorCode.outOfRange.rawValue, error.code)
  421. XCTAssertEqual("explicit nope", error.localizedDescription)
  422. XCTAssertEqual(["start": 10 as Int32, "end": 20 as Int32, "long": 30],
  423. error.userInfo["details"] as! [String: Int32])
  424. }
  425. }
  426. #endif
  427. func testHttpError() {
  428. let expectation = expectation(description: #function)
  429. let function = functions.httpsCallable(
  430. "httpErrorTest",
  431. requestAs: [Int].self,
  432. responseAs: Int.self
  433. )
  434. XCTAssertNotNil(function)
  435. function.call([]) { result in
  436. do {
  437. _ = try result.get()
  438. } catch {
  439. let error = error as NSError
  440. XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code)
  441. expectation.fulfill()
  442. return
  443. }
  444. XCTFail("Failed to throw error for missing result")
  445. }
  446. waitForExpectations(timeout: 5)
  447. }
  448. #if compiler(>=5.5.2) && canImport(_Concurrency)
  449. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  450. func testHttpErrorAsync() async {
  451. let function = functions.httpsCallable(
  452. "httpErrorTest",
  453. requestAs: [Int].self,
  454. responseAs: Int.self
  455. )
  456. do {
  457. _ = try await function.call([])
  458. XCTAssertFalse(true, "Failed to throw error for missing result")
  459. } catch {
  460. let error = error as NSError
  461. XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code)
  462. }
  463. }
  464. #endif
  465. func testTimeout() {
  466. let expectation = expectation(description: #function)
  467. var function = functions.httpsCallable(
  468. "timeoutTest",
  469. requestAs: [Int].self,
  470. responseAs: Int.self
  471. )
  472. function.timeoutInterval = 0.05
  473. function.call([]) { result in
  474. do {
  475. _ = try result.get()
  476. } catch {
  477. let error = error as NSError
  478. XCTAssertEqual(FunctionsErrorCode.deadlineExceeded.rawValue, error.code)
  479. XCTAssertEqual("DEADLINE EXCEEDED", error.localizedDescription)
  480. XCTAssertNil(error.userInfo["details"])
  481. expectation.fulfill()
  482. return
  483. }
  484. XCTFail("Failed to throw error for missing result")
  485. }
  486. waitForExpectations(timeout: 5)
  487. }
  488. #if compiler(>=5.5.2) && canImport(_Concurrency)
  489. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  490. func testTimeoutAsync() async {
  491. var function = functions.httpsCallable(
  492. "timeoutTest",
  493. requestAs: [Int].self,
  494. responseAs: Int.self
  495. )
  496. function.timeoutInterval = 0.05
  497. do {
  498. _ = try await function.call([])
  499. XCTAssertFalse(true, "Failed to throw error for missing result")
  500. } catch {
  501. let error = error as NSError
  502. XCTAssertEqual(FunctionsErrorCode.deadlineExceeded.rawValue, error.code)
  503. XCTAssertEqual("DEADLINE EXCEEDED", error.localizedDescription)
  504. XCTAssertNil(error.userInfo["details"])
  505. }
  506. }
  507. #endif
  508. func testCallAsFunction() {
  509. let expectation = expectation(description: #function)
  510. let data = DataTestRequest(
  511. bool: true,
  512. int: 2,
  513. long: 9_876_543_210,
  514. string: "four",
  515. array: [5, 6],
  516. null: nil
  517. )
  518. let function = functions.httpsCallable("dataTest",
  519. requestAs: DataTestRequest.self,
  520. responseAs: DataTestResponse.self)
  521. function(data) { result in
  522. do {
  523. let response = try result.get()
  524. let expected = DataTestResponse(
  525. message: "stub response",
  526. long: 420,
  527. code: 42
  528. )
  529. XCTAssertEqual(response, expected)
  530. expectation.fulfill()
  531. } catch {
  532. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  533. }
  534. }
  535. waitForExpectations(timeout: 5)
  536. }
  537. #if compiler(>=5.5.2) && canImport(_Concurrency)
  538. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  539. func testCallAsFunctionAsync() async throws {
  540. let data = DataTestRequest(
  541. bool: true,
  542. int: 2,
  543. long: 9_876_543_210,
  544. string: "four",
  545. array: [5, 6],
  546. null: nil
  547. )
  548. let function = functions.httpsCallable("dataTest",
  549. requestAs: DataTestRequest.self,
  550. responseAs: DataTestResponse.self)
  551. let response = try await function(data)
  552. let expected = DataTestResponse(
  553. message: "stub response",
  554. long: 420,
  555. code: 42
  556. )
  557. XCTAssertEqual(response, expected)
  558. }
  559. #endif
  560. func testInferredTypes() {
  561. let expectation = expectation(description: #function)
  562. let data = DataTestRequest(
  563. bool: true,
  564. int: 2,
  565. long: 9_876_543_210,
  566. string: "four",
  567. array: [5, 6],
  568. null: nil
  569. )
  570. let function: Callable<DataTestRequest, DataTestResponse> = functions.httpsCallable("dataTest")
  571. function(data) { result in
  572. do {
  573. let response = try result.get()
  574. let expected = DataTestResponse(
  575. message: "stub response",
  576. long: 420,
  577. code: 42
  578. )
  579. XCTAssertEqual(response, expected)
  580. expectation.fulfill()
  581. } catch {
  582. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  583. }
  584. }
  585. waitForExpectations(timeout: 5)
  586. }
  587. #if compiler(>=5.5.2) && canImport(_Concurrency)
  588. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  589. func testInferredTyesAsync() async throws {
  590. let data = DataTestRequest(
  591. bool: true,
  592. int: 2,
  593. long: 9_876_543_210,
  594. string: "four",
  595. array: [5, 6],
  596. null: nil
  597. )
  598. let function: Callable<DataTestRequest, DataTestResponse> = functions
  599. .httpsCallable("dataTest")
  600. let response = try await function(data)
  601. let expected = DataTestResponse(
  602. message: "stub response",
  603. long: 420,
  604. code: 42
  605. )
  606. XCTAssertEqual(response, expected)
  607. }
  608. #endif
  609. }
  610. private class AuthTokenProvider: AuthInterop {
  611. func getUserID() -> String? {
  612. return "fake user"
  613. }
  614. let token: String
  615. init(token: String) {
  616. self.token = token
  617. }
  618. func getToken(forcingRefresh: Bool, completion: (String?, Error?) -> Void) {
  619. completion(token, nil)
  620. }
  621. }
  622. private class MessagingTokenProvider: NSObject, MessagingInterop {
  623. var fcmToken: String? { return "fakeFCMToken" }
  624. }