IntegrationTests.swift 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332
  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 FirebaseAuthInterop
  16. @testable import FirebaseFunctions
  17. import FirebaseMessagingInterop
  18. import XCTest
  19. /// This file was initialized as a direct port of
  20. /// `FirebaseFunctionsSwift/Tests/IntegrationTests.swift`
  21. /// which itself was ported from the Objective-C
  22. /// `FirebaseFunctions/Tests/Integration/FIRIntegrationTests.m`
  23. ///
  24. /// The tests require the emulator to be running with `FirebaseFunctions/Backend/start.sh
  25. /// synchronous`
  26. /// The Firebase Functions called in the tests are implemented in
  27. /// `FirebaseFunctions/Backend/index.js`.
  28. struct DataTestRequest: Encodable {
  29. var bool: Bool
  30. var int: Int32
  31. var long: Int64
  32. var string: String
  33. var array: [Int32]
  34. // NOTE: Auto-synthesized Encodable conformance uses 'encodeIfPresent' to
  35. // encode Optional values. To encode Optional.none as null you either need
  36. // to write a manual encodable conformance or use a helper like the
  37. // propertyWrapper here:
  38. @NullEncodable var null: Bool?
  39. }
  40. @propertyWrapper
  41. struct NullEncodable<T>: Encodable where T: Encodable {
  42. var wrappedValue: T?
  43. init(wrappedValue: T?) {
  44. self.wrappedValue = wrappedValue
  45. }
  46. func encode(to encoder: Encoder) throws {
  47. var container = encoder.singleValueContainer()
  48. switch wrappedValue {
  49. case let .some(value): try container.encode(value)
  50. case .none: try container.encodeNil()
  51. }
  52. }
  53. }
  54. struct DataTestResponse: Decodable, Equatable {
  55. var message: String
  56. var long: Int64
  57. var code: Int32
  58. }
  59. /// - Important: These tests require the emulator. Run `./FirebaseFunctions/Backend/start.sh`
  60. class IntegrationTests: XCTestCase {
  61. let functions = Functions(projectID: "functions-integration-test",
  62. region: "us-central1",
  63. customDomain: nil,
  64. auth: nil,
  65. messaging: MessagingTokenProvider(),
  66. appCheck: nil)
  67. override func setUp() {
  68. super.setUp()
  69. functions.useEmulator(withHost: "localhost", port: 5005)
  70. }
  71. func emulatorURL(_ funcName: String) -> URL {
  72. return URL(string: "http://localhost:5005/functions-integration-test/us-central1/\(funcName)")!
  73. }
  74. @MainActor func testData() {
  75. let data = DataTestRequest(
  76. bool: true,
  77. int: 2,
  78. long: 9_876_543_210,
  79. string: "four",
  80. array: [5, 6],
  81. null: nil
  82. )
  83. let byName = functions.httpsCallable("dataTest",
  84. requestAs: DataTestRequest.self,
  85. responseAs: DataTestResponse.self)
  86. let byURL = functions.httpsCallable(emulatorURL("dataTest"),
  87. requestAs: DataTestRequest.self,
  88. responseAs: DataTestResponse.self)
  89. for function in [byName, byURL] {
  90. let expectation = expectation(description: #function)
  91. function.call(data) { result in
  92. do {
  93. let response = try result.get()
  94. let expected = DataTestResponse(
  95. message: "stub response",
  96. long: 420,
  97. code: 42
  98. )
  99. XCTAssertEqual(response, expected)
  100. } catch {
  101. XCTFail("Failed to unwrap the function result: \(error)")
  102. }
  103. expectation.fulfill()
  104. }
  105. waitForExpectations(timeout: 5)
  106. }
  107. }
  108. func testDataAsync() async throws {
  109. let data = DataTestRequest(
  110. bool: true,
  111. int: 2,
  112. long: 9_876_543_210,
  113. string: "four",
  114. array: [5, 6],
  115. null: nil
  116. )
  117. let byName = functions.httpsCallable("dataTest",
  118. requestAs: DataTestRequest.self,
  119. responseAs: DataTestResponse.self)
  120. let byUrl = functions.httpsCallable(emulatorURL("dataTest"),
  121. requestAs: DataTestRequest.self,
  122. responseAs: DataTestResponse.self)
  123. for function in [byName, byUrl] {
  124. let response = try await function.call(data)
  125. let expected = DataTestResponse(
  126. message: "stub response",
  127. long: 420,
  128. code: 42
  129. )
  130. XCTAssertEqual(response, expected)
  131. }
  132. }
  133. @MainActor func testScalar() {
  134. let byName = functions.httpsCallable(
  135. "scalarTest",
  136. requestAs: Int16.self,
  137. responseAs: Int.self
  138. )
  139. let byURL = functions.httpsCallable(
  140. emulatorURL("scalarTest"),
  141. requestAs: Int16.self,
  142. responseAs: Int.self
  143. )
  144. for function in [byName, byURL] {
  145. let expectation = expectation(description: #function)
  146. function.call(17) { result in
  147. do {
  148. let response = try result.get()
  149. XCTAssertEqual(response, 76)
  150. } catch {
  151. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  152. }
  153. expectation.fulfill()
  154. }
  155. waitForExpectations(timeout: 5)
  156. }
  157. }
  158. func testScalarAsync() async throws {
  159. let byName = functions.httpsCallable(
  160. "scalarTest",
  161. requestAs: Int16.self,
  162. responseAs: Int.self
  163. )
  164. let byURL = functions.httpsCallable(
  165. emulatorURL("scalarTest"),
  166. requestAs: Int16.self,
  167. responseAs: Int.self
  168. )
  169. for function in [byName, byURL] {
  170. let result = try await function.call(17)
  171. XCTAssertEqual(result, 76)
  172. }
  173. }
  174. func testScalarAsyncAlternateSignature() async throws {
  175. let byName: Callable<Int16, Int> = functions.httpsCallable("scalarTest")
  176. let byURL: Callable<Int16, Int> = functions.httpsCallable(emulatorURL("scalarTest"))
  177. for function in [byName, byURL] {
  178. let result = try await function.call(17)
  179. XCTAssertEqual(result, 76)
  180. }
  181. }
  182. @MainActor func testToken() {
  183. // Recreate functions with a token.
  184. let functions = Functions(
  185. projectID: "functions-integration-test",
  186. region: "us-central1",
  187. customDomain: nil,
  188. auth: AuthTokenProvider(token: "token"),
  189. messaging: MessagingTokenProvider(),
  190. appCheck: nil
  191. )
  192. functions.useEmulator(withHost: "localhost", port: 5005)
  193. let byName = functions.httpsCallable(
  194. "tokenTest",
  195. requestAs: [String: Int].self,
  196. responseAs: [String: Int].self
  197. )
  198. let byURL = functions.httpsCallable(
  199. emulatorURL("tokenTest"),
  200. requestAs: [String: Int].self,
  201. responseAs: [String: Int].self
  202. )
  203. for function in [byName, byURL] {
  204. let expectation = expectation(description: #function)
  205. XCTAssertNotNil(function)
  206. function.call([:]) { result in
  207. do {
  208. let data = try result.get()
  209. XCTAssertEqual(data, [:])
  210. } catch {
  211. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  212. }
  213. expectation.fulfill()
  214. }
  215. waitForExpectations(timeout: 5)
  216. }
  217. }
  218. func testTokenAsync() async throws {
  219. // Recreate functions with a token.
  220. let functions = Functions(
  221. projectID: "functions-integration-test",
  222. region: "us-central1",
  223. customDomain: nil,
  224. auth: AuthTokenProvider(token: "token"),
  225. messaging: MessagingTokenProvider(),
  226. appCheck: nil
  227. )
  228. functions.useEmulator(withHost: "localhost", port: 5005)
  229. let byName = functions.httpsCallable(
  230. "tokenTest",
  231. requestAs: [String: Int].self,
  232. responseAs: [String: Int].self
  233. )
  234. let byURL = functions.httpsCallable(
  235. emulatorURL("tokenTest"),
  236. requestAs: [String: Int].self,
  237. responseAs: [String: Int].self
  238. )
  239. for function in [byName, byURL] {
  240. let data = try await function.call([:])
  241. XCTAssertEqual(data, [:])
  242. }
  243. }
  244. @MainActor func testFCMToken() {
  245. let byName = functions.httpsCallable(
  246. "FCMTokenTest",
  247. requestAs: [String: Int].self,
  248. responseAs: [String: Int].self
  249. )
  250. let byURL = functions.httpsCallable(
  251. emulatorURL("FCMTokenTest"),
  252. requestAs: [String: Int].self,
  253. responseAs: [String: Int].self
  254. )
  255. for function in [byName, byURL] {
  256. let expectation = expectation(description: #function)
  257. function.call([:]) { result in
  258. do {
  259. let data = try result.get()
  260. XCTAssertEqual(data, [:])
  261. } catch {
  262. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  263. }
  264. expectation.fulfill()
  265. }
  266. waitForExpectations(timeout: 5)
  267. }
  268. }
  269. func testFCMTokenAsync() async throws {
  270. let byName = functions.httpsCallable(
  271. "FCMTokenTest",
  272. requestAs: [String: Int].self,
  273. responseAs: [String: Int].self
  274. )
  275. let byURL = functions.httpsCallable(
  276. emulatorURL("FCMTokenTest"),
  277. requestAs: [String: Int].self,
  278. responseAs: [String: Int].self
  279. )
  280. for function in [byName, byURL] {
  281. let data = try await function.call([:])
  282. XCTAssertEqual(data, [:])
  283. }
  284. }
  285. @MainActor func testNull() {
  286. let byName = functions.httpsCallable(
  287. "nullTest",
  288. requestAs: Int?.self,
  289. responseAs: Int?.self
  290. )
  291. let byURL = functions.httpsCallable(
  292. emulatorURL("nullTest"),
  293. requestAs: Int?.self,
  294. responseAs: Int?.self
  295. )
  296. for function in [byName, byURL] {
  297. let expectation = expectation(description: #function)
  298. function.call(nil) { result in
  299. do {
  300. let data = try result.get()
  301. XCTAssertEqual(data, nil)
  302. } catch {
  303. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  304. }
  305. expectation.fulfill()
  306. }
  307. waitForExpectations(timeout: 5)
  308. }
  309. }
  310. func testNullAsync() async throws {
  311. let byName = functions.httpsCallable(
  312. "nullTest",
  313. requestAs: Int?.self,
  314. responseAs: Int?.self
  315. )
  316. let byURL = functions.httpsCallable(
  317. emulatorURL("nullTest"),
  318. requestAs: Int?.self,
  319. responseAs: Int?.self
  320. )
  321. for function in [byName, byURL] {
  322. let data = try await function.call(nil)
  323. XCTAssertEqual(data, nil)
  324. }
  325. }
  326. @MainActor func testMissingResult() {
  327. let byName = functions.httpsCallable(
  328. "missingResultTest",
  329. requestAs: Int?.self,
  330. responseAs: Int?.self
  331. )
  332. let byURL = functions.httpsCallable(
  333. emulatorURL("missingResultTest"),
  334. requestAs: Int?.self,
  335. responseAs: Int?.self
  336. )
  337. for function in [byName, byURL] {
  338. let expectation = expectation(description: #function)
  339. function.call(nil) { result in
  340. do {
  341. _ = try result.get()
  342. } catch {
  343. let error = error as NSError
  344. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  345. XCTAssertEqual("Response is missing data field.", error.localizedDescription)
  346. expectation.fulfill()
  347. return
  348. }
  349. XCTFail("Failed to throw error for missing result")
  350. }
  351. waitForExpectations(timeout: 5)
  352. }
  353. }
  354. func testMissingResultAsync() async {
  355. let byName = functions.httpsCallable(
  356. "missingResultTest",
  357. requestAs: Int?.self,
  358. responseAs: Int?.self
  359. )
  360. let byURL = functions.httpsCallable(
  361. emulatorURL("missingResultTest"),
  362. requestAs: Int?.self,
  363. responseAs: Int?.self
  364. )
  365. for function in [byName, byURL] {
  366. do {
  367. _ = try await function.call(nil)
  368. XCTFail("Failed to throw error for missing result")
  369. } catch {
  370. let error = error as NSError
  371. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  372. XCTAssertEqual("Response is missing data field.", error.localizedDescription)
  373. }
  374. }
  375. }
  376. @MainActor func testUnhandledError() {
  377. let byName = functions.httpsCallable(
  378. "unhandledErrorTest",
  379. requestAs: [Int].self,
  380. responseAs: Int.self
  381. )
  382. let byURL = functions.httpsCallable(
  383. emulatorURL("unhandledErrorTest"),
  384. requestAs: [Int].self,
  385. responseAs: Int.self
  386. )
  387. for function in [byName, byURL] {
  388. let expectation = expectation(description: #function)
  389. function.call([]) { result in
  390. do {
  391. _ = try result.get()
  392. } catch {
  393. let error = error as NSError
  394. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  395. XCTAssertEqual("INTERNAL", error.localizedDescription)
  396. expectation.fulfill()
  397. return
  398. }
  399. XCTFail("Failed to throw error for missing result")
  400. }
  401. XCTAssert(true)
  402. waitForExpectations(timeout: 5)
  403. }
  404. }
  405. func testUnhandledErrorAsync() async {
  406. let byName = functions.httpsCallable(
  407. "unhandledErrorTest",
  408. requestAs: [Int].self,
  409. responseAs: Int.self
  410. )
  411. let byURL = functions.httpsCallable(
  412. "unhandledErrorTest",
  413. requestAs: [Int].self,
  414. responseAs: Int.self
  415. )
  416. for function in [byName, byURL] {
  417. do {
  418. _ = try await function.call([])
  419. XCTFail("Failed to throw error for missing result")
  420. } catch {
  421. let error = error as NSError
  422. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  423. XCTAssertEqual("INTERNAL", error.localizedDescription)
  424. }
  425. }
  426. }
  427. @MainActor func testUnknownError() {
  428. let byName = functions.httpsCallable(
  429. "unknownErrorTest",
  430. requestAs: [Int].self,
  431. responseAs: Int.self
  432. )
  433. let byURL = functions.httpsCallable(
  434. emulatorURL("unknownErrorTest"),
  435. requestAs: [Int].self,
  436. responseAs: Int.self
  437. )
  438. for function in [byName, byURL] {
  439. let expectation = expectation(description: #function)
  440. function.call([]) { result in
  441. do {
  442. _ = try result.get()
  443. } catch {
  444. let error = error as NSError
  445. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  446. XCTAssertEqual("INTERNAL", error.localizedDescription)
  447. expectation.fulfill()
  448. return
  449. }
  450. XCTFail("Failed to throw error for missing result")
  451. }
  452. }
  453. waitForExpectations(timeout: 5)
  454. }
  455. func testUnknownErrorAsync() async {
  456. let byName = functions.httpsCallable(
  457. "unknownErrorTest",
  458. requestAs: [Int].self,
  459. responseAs: Int.self
  460. )
  461. let byURL = functions.httpsCallable(
  462. emulatorURL("unknownErrorTest"),
  463. requestAs: [Int].self,
  464. responseAs: Int.self
  465. )
  466. for function in [byName, byURL] {
  467. do {
  468. _ = try await function.call([])
  469. XCTAssertFalse(true, "Failed to throw error for missing result")
  470. } catch {
  471. let error = error as NSError
  472. XCTAssertEqual(FunctionsErrorCode.internal.rawValue, error.code)
  473. XCTAssertEqual("INTERNAL", error.localizedDescription)
  474. }
  475. }
  476. }
  477. @MainActor func testExplicitError() {
  478. let byName = functions.httpsCallable(
  479. "explicitErrorTest",
  480. requestAs: [Int].self,
  481. responseAs: Int.self
  482. )
  483. let byURL = functions.httpsCallable(
  484. "explicitErrorTest",
  485. requestAs: [Int].self,
  486. responseAs: Int.self
  487. )
  488. for function in [byName, byURL] {
  489. let expectation = expectation(description: #function)
  490. function.call([]) { result in
  491. do {
  492. _ = try result.get()
  493. } catch {
  494. let error = error as NSError
  495. XCTAssertEqual(FunctionsErrorCode.outOfRange.rawValue, error.code)
  496. XCTAssertEqual("explicit nope", error.localizedDescription)
  497. XCTAssertEqual(["start": 10 as Int32, "end": 20 as Int32, "long": 30],
  498. error.userInfo["details"] as? [String: Int32])
  499. expectation.fulfill()
  500. return
  501. }
  502. XCTFail("Failed to throw error for missing result")
  503. }
  504. waitForExpectations(timeout: 5)
  505. }
  506. }
  507. func testExplicitErrorAsync() async {
  508. let byName = functions.httpsCallable(
  509. "explicitErrorTest",
  510. requestAs: [Int].self,
  511. responseAs: Int.self
  512. )
  513. let byURL = functions.httpsCallable(
  514. emulatorURL("explicitErrorTest"),
  515. requestAs: [Int].self,
  516. responseAs: Int.self
  517. )
  518. for function in [byName, byURL] {
  519. do {
  520. _ = try await function.call([])
  521. XCTAssertFalse(true, "Failed to throw error for missing result")
  522. } catch {
  523. let error = error as NSError
  524. XCTAssertEqual(FunctionsErrorCode.outOfRange.rawValue, error.code)
  525. XCTAssertEqual("explicit nope", error.localizedDescription)
  526. XCTAssertEqual(["start": 10 as Int32, "end": 20 as Int32, "long": 30],
  527. error.userInfo["details"] as? [String: Int32])
  528. }
  529. }
  530. }
  531. @MainActor func testHttpError() {
  532. let byName = functions.httpsCallable(
  533. "httpErrorTest",
  534. requestAs: [Int].self,
  535. responseAs: Int.self
  536. )
  537. let byURL = functions.httpsCallable(
  538. emulatorURL("httpErrorTest"),
  539. requestAs: [Int].self,
  540. responseAs: Int.self
  541. )
  542. for function in [byName, byURL] {
  543. let expectation = expectation(description: #function)
  544. XCTAssertNotNil(function)
  545. function.call([]) { result in
  546. do {
  547. _ = try result.get()
  548. } catch {
  549. let error = error as NSError
  550. XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code)
  551. expectation.fulfill()
  552. return
  553. }
  554. XCTFail("Failed to throw error for missing result")
  555. }
  556. waitForExpectations(timeout: 5)
  557. }
  558. }
  559. func testHttpErrorAsync() async {
  560. let byName = functions.httpsCallable(
  561. "httpErrorTest",
  562. requestAs: [Int].self,
  563. responseAs: Int.self
  564. )
  565. let byURL = functions.httpsCallable(
  566. emulatorURL("httpErrorTest"),
  567. requestAs: [Int].self,
  568. responseAs: Int.self
  569. )
  570. for function in [byName, byURL] {
  571. do {
  572. _ = try await function.call([])
  573. XCTAssertFalse(true, "Failed to throw error for missing result")
  574. } catch {
  575. let error = error as NSError
  576. XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code)
  577. }
  578. }
  579. }
  580. @MainActor func testThrowError() {
  581. let byName = functions.httpsCallable(
  582. "throwTest",
  583. requestAs: [Int].self,
  584. responseAs: Int.self
  585. )
  586. let byURL = functions.httpsCallable(
  587. emulatorURL("throwTest"),
  588. requestAs: [Int].self,
  589. responseAs: Int.self
  590. )
  591. for function in [byName, byURL] {
  592. let expectation = expectation(description: #function)
  593. XCTAssertNotNil(function)
  594. function.call([]) { result in
  595. do {
  596. _ = try result.get()
  597. } catch {
  598. let error = error as NSError
  599. XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code)
  600. XCTAssertEqual(error.localizedDescription, "Invalid test requested.")
  601. expectation.fulfill()
  602. return
  603. }
  604. XCTFail("Failed to throw error for missing result")
  605. }
  606. waitForExpectations(timeout: 5)
  607. }
  608. }
  609. func testThrowErrorAsync() async {
  610. let byName = functions.httpsCallable(
  611. "throwTest",
  612. requestAs: [Int].self,
  613. responseAs: Int.self
  614. )
  615. let byURL = functions.httpsCallable(
  616. emulatorURL("throwTest"),
  617. requestAs: [Int].self,
  618. responseAs: Int.self
  619. )
  620. for function in [byName, byURL] {
  621. do {
  622. _ = try await function.call([])
  623. XCTAssertFalse(true, "Failed to throw error for missing result")
  624. } catch {
  625. let error = error as NSError
  626. XCTAssertEqual(FunctionsErrorCode.invalidArgument.rawValue, error.code)
  627. XCTAssertEqual(error.localizedDescription, "Invalid test requested.")
  628. }
  629. }
  630. }
  631. @MainActor func testTimeout() {
  632. let byName = functions.httpsCallable(
  633. "timeoutTest",
  634. requestAs: [Int].self,
  635. responseAs: Int.self
  636. )
  637. let byURL = functions.httpsCallable(
  638. emulatorURL("timeoutTest"),
  639. requestAs: [Int].self,
  640. responseAs: Int.self
  641. )
  642. for var function in [byName, byURL] {
  643. let expectation = expectation(description: #function)
  644. function.timeoutInterval = 0.05
  645. function.call([]) { result in
  646. do {
  647. _ = try result.get()
  648. } catch {
  649. let error = error as NSError
  650. XCTAssertEqual(FunctionsErrorCode.deadlineExceeded.rawValue, error.code)
  651. XCTAssertEqual("DEADLINE EXCEEDED", error.localizedDescription)
  652. XCTAssertNil(error.userInfo["details"])
  653. expectation.fulfill()
  654. return
  655. }
  656. XCTFail("Failed to throw error for missing result")
  657. }
  658. waitForExpectations(timeout: 5)
  659. }
  660. }
  661. func testTimeoutAsync() async {
  662. var byName = functions.httpsCallable(
  663. "timeoutTest",
  664. requestAs: [Int].self,
  665. responseAs: Int.self
  666. )
  667. byName.timeoutInterval = 0.05
  668. var byURL = functions.httpsCallable(
  669. emulatorURL("timeoutTest"),
  670. requestAs: [Int].self,
  671. responseAs: Int.self
  672. )
  673. byURL.timeoutInterval = 0.05
  674. for function in [byName, byURL] {
  675. do {
  676. _ = try await function.call([])
  677. XCTAssertFalse(true, "Failed to throw error for missing result")
  678. } catch {
  679. let error = error as NSError
  680. XCTAssertEqual(FunctionsErrorCode.deadlineExceeded.rawValue, error.code)
  681. XCTAssertEqual("DEADLINE EXCEEDED", error.localizedDescription)
  682. XCTAssertNil(error.userInfo["details"])
  683. }
  684. }
  685. }
  686. @MainActor func testCallAsFunction() {
  687. let data = DataTestRequest(
  688. bool: true,
  689. int: 2,
  690. long: 9_876_543_210,
  691. string: "four",
  692. array: [5, 6],
  693. null: nil
  694. )
  695. let byName = functions.httpsCallable("dataTest",
  696. requestAs: DataTestRequest.self,
  697. responseAs: DataTestResponse.self)
  698. let byURL = functions.httpsCallable(emulatorURL("dataTest"),
  699. requestAs: DataTestRequest.self,
  700. responseAs: DataTestResponse.self)
  701. for function in [byName, byURL] {
  702. let expectation = expectation(description: #function)
  703. function(data) { result in
  704. do {
  705. let response = try result.get()
  706. let expected = DataTestResponse(
  707. message: "stub response",
  708. long: 420,
  709. code: 42
  710. )
  711. XCTAssertEqual(response, expected)
  712. expectation.fulfill()
  713. } catch {
  714. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  715. }
  716. }
  717. waitForExpectations(timeout: 5)
  718. }
  719. }
  720. func testCallAsFunctionAsync() async throws {
  721. let data = DataTestRequest(
  722. bool: true,
  723. int: 2,
  724. long: 9_876_543_210,
  725. string: "four",
  726. array: [5, 6],
  727. null: nil
  728. )
  729. let byName = functions.httpsCallable("dataTest",
  730. requestAs: DataTestRequest.self,
  731. responseAs: DataTestResponse.self)
  732. let byURL = functions.httpsCallable(emulatorURL("dataTest"),
  733. requestAs: DataTestRequest.self,
  734. responseAs: DataTestResponse.self)
  735. for function in [byName, byURL] {
  736. let response = try await function(data)
  737. let expected = DataTestResponse(
  738. message: "stub response",
  739. long: 420,
  740. code: 42
  741. )
  742. XCTAssertEqual(response, expected)
  743. }
  744. }
  745. @MainActor func testInferredTypes() {
  746. let data = DataTestRequest(
  747. bool: true,
  748. int: 2,
  749. long: 9_876_543_210,
  750. string: "four",
  751. array: [5, 6],
  752. null: nil
  753. )
  754. let byName: Callable<DataTestRequest, DataTestResponse> = functions.httpsCallable("dataTest")
  755. let byURL: Callable<DataTestRequest, DataTestResponse> = functions
  756. .httpsCallable(emulatorURL("dataTest"))
  757. for function in [byName, byURL] {
  758. let expectation = expectation(description: #function)
  759. function(data) { result in
  760. do {
  761. let response = try result.get()
  762. let expected = DataTestResponse(
  763. message: "stub response",
  764. long: 420,
  765. code: 42
  766. )
  767. XCTAssertEqual(response, expected)
  768. expectation.fulfill()
  769. } catch {
  770. XCTAssert(false, "Failed to unwrap the function result: \(error)")
  771. }
  772. }
  773. waitForExpectations(timeout: 5)
  774. }
  775. }
  776. func testInferredTyesAsync() async throws {
  777. let data = DataTestRequest(
  778. bool: true,
  779. int: 2,
  780. long: 9_876_543_210,
  781. string: "four",
  782. array: [5, 6],
  783. null: nil
  784. )
  785. let byName: Callable<DataTestRequest, DataTestResponse> = functions
  786. .httpsCallable("dataTest")
  787. let byURL: Callable<DataTestRequest, DataTestResponse> = functions
  788. .httpsCallable(emulatorURL("dataTest"))
  789. for function in [byName, byURL] {
  790. let response = try await function(data)
  791. let expected = DataTestResponse(
  792. message: "stub response",
  793. long: 420,
  794. code: 42
  795. )
  796. XCTAssertEqual(response, expected)
  797. }
  798. }
  799. @MainActor func testFunctionsReturnsOnMainThread() {
  800. let expectation = expectation(description: #function)
  801. functions.httpsCallable(
  802. "scalarTest",
  803. requestAs: Int16.self,
  804. responseAs: Int.self
  805. ).call(17) { result in
  806. guard case .success = result else {
  807. return XCTFail("Unexpected failure.")
  808. }
  809. XCTAssert(Thread.isMainThread)
  810. expectation.fulfill()
  811. }
  812. waitForExpectations(timeout: 5)
  813. }
  814. @MainActor func testFunctionsThrowsOnMainThread() {
  815. let expectation = expectation(description: #function)
  816. functions.httpsCallable(
  817. "httpErrorTest",
  818. requestAs: [Int].self,
  819. responseAs: Int.self
  820. ).call([]) { result in
  821. guard case .failure = result else {
  822. return XCTFail("Unexpected failure.")
  823. }
  824. XCTAssert(Thread.isMainThread)
  825. expectation.fulfill()
  826. }
  827. waitForExpectations(timeout: 5)
  828. }
  829. }
  830. // MARK: - Streaming
  831. /// A convenience type used to represent that a callable function does not
  832. /// accept parameters.
  833. ///
  834. /// This can be used as the generic `Request` parameter to ``Callable`` to
  835. /// indicate the callable function does not accept parameters.
  836. private struct EmptyRequest: Encodable, Sendable {}
  837. @available(macOS 12.0, iOS 15.0, watchOS 8.0, tvOS 15.0, *)
  838. extension IntegrationTests {
  839. func testStream_NoArgs() async throws {
  840. // 1. Custom `EmptyRequest` struct is passed as a placeholder generic arg.
  841. let callable: Callable<EmptyRequest, String> = functions.httpsCallable("genStream")
  842. // 2. No request data is passed when creating stream.
  843. let stream = try callable.stream()
  844. var streamContents: [String] = []
  845. for try await response in stream {
  846. streamContents.append(response)
  847. }
  848. XCTAssertEqual(
  849. streamContents,
  850. ["hello", "world", "this", "is", "cool"]
  851. )
  852. }
  853. @available(macOS 14.0, iOS 17.0, tvOS 17.0, watchOS 10.0, *)
  854. func testStream_NoArgs_UeeNever() async throws {
  855. let callable: Callable<Never, String> = functions.httpsCallable("genStream")
  856. let stream = try callable.stream()
  857. var streamContents: [String] = []
  858. for try await response in stream {
  859. streamContents.append(response)
  860. }
  861. XCTAssertEqual(
  862. streamContents,
  863. ["hello", "world", "this", "is", "cool"]
  864. )
  865. }
  866. func testStream_SimpleStreamResponse() async throws {
  867. let callable: Callable<EmptyRequest, StreamResponse<String, String>> = functions
  868. .httpsCallable("genStream")
  869. let stream = try callable.stream()
  870. var streamContents: [String] = []
  871. for try await response in stream {
  872. switch response {
  873. case let .message(message):
  874. streamContents.append(message)
  875. case let .result(result):
  876. streamContents.append(result)
  877. }
  878. }
  879. XCTAssertEqual(
  880. streamContents,
  881. ["hello", "world", "this", "is", "cool", "hello world this is cool"]
  882. )
  883. }
  884. func testStream_CodableString() async throws {
  885. let byName: Callable<EmptyRequest, String> = functions.httpsCallable("genStream")
  886. let stream = try byName.stream()
  887. let result: [String] = try await stream.reduce([]) { $0 + [$1] }
  888. XCTAssertEqual(result, ["hello", "world", "this", "is", "cool"])
  889. }
  890. private struct Location: Codable, Equatable {
  891. let name: String
  892. }
  893. private struct WeatherForecast: Decodable, Equatable {
  894. enum Conditions: String, Decodable {
  895. case sunny
  896. case rainy
  897. case snowy
  898. }
  899. let location: Location
  900. let temperature: Int
  901. let conditions: Conditions
  902. }
  903. private struct WeatherForecastReport: Decodable, Equatable {
  904. let forecasts: [WeatherForecast]
  905. }
  906. func testStream_CodableObject() async throws {
  907. let callable: Callable<[Location], WeatherForecast> = functions
  908. .httpsCallable("genStreamWeather")
  909. let stream = try callable.stream([
  910. Location(name: "Toronto"),
  911. Location(name: "London"),
  912. Location(name: "Dubai"),
  913. ])
  914. let result: [WeatherForecast] = try await stream.reduce([]) { $0 + [$1] }
  915. XCTAssertEqual(
  916. result,
  917. [
  918. WeatherForecast(location: Location(name: "Toronto"), temperature: 25, conditions: .snowy),
  919. WeatherForecast(location: Location(name: "London"), temperature: 50, conditions: .rainy),
  920. WeatherForecast(location: Location(name: "Dubai"), temperature: 75, conditions: .sunny),
  921. ]
  922. )
  923. }
  924. func testStream_ResponseMessageDecodingFailure() async throws {
  925. let callable: Callable<[Location], StreamResponse<WeatherForecast, WeatherForecastReport>> =
  926. functions
  927. .httpsCallable("genStreamWeatherError")
  928. let stream = try callable.stream([Location(name: "Toronto")])
  929. do {
  930. for try await _ in stream {
  931. XCTFail("Expected error to be thrown from stream.")
  932. }
  933. } catch let error as FunctionsError where error.code == .dataLoss {
  934. XCTAssertNotNil(error.errorUserInfo[NSUnderlyingErrorKey] as? DecodingError)
  935. }
  936. }
  937. func testStream_ResponseResultDecodingFailure() async throws {
  938. let callable: Callable<[Location], StreamResponse<WeatherForecast, String>> = functions
  939. .httpsCallable("genStreamWeather")
  940. let stream = try callable.stream([Location(name: "Toronto")])
  941. do {
  942. for try await response in stream {
  943. if case .result = response {
  944. XCTFail("Expected error to be thrown from stream.")
  945. }
  946. }
  947. } catch let error as FunctionsError where error.code == .dataLoss {
  948. XCTAssertNotNil(error.errorUserInfo[NSUnderlyingErrorKey] as? DecodingError)
  949. }
  950. }
  951. func testStream_ComplexStreamResponse() async throws {
  952. let callable: Callable<[Location], StreamResponse<WeatherForecast, WeatherForecastReport>> =
  953. functions
  954. .httpsCallable("genStreamWeather")
  955. let stream = try callable.stream([
  956. Location(name: "Toronto"),
  957. Location(name: "London"),
  958. Location(name: "Dubai"),
  959. ])
  960. var streamContents: [WeatherForecast] = []
  961. var streamResult: WeatherForecastReport?
  962. for try await response in stream {
  963. switch response {
  964. case let .message(message):
  965. streamContents.append(message)
  966. case let .result(result):
  967. streamResult = result
  968. }
  969. }
  970. XCTAssertEqual(
  971. streamContents,
  972. [
  973. WeatherForecast(location: Location(name: "Toronto"), temperature: 25, conditions: .snowy),
  974. WeatherForecast(location: Location(name: "London"), temperature: 50, conditions: .rainy),
  975. WeatherForecast(location: Location(name: "Dubai"), temperature: 75, conditions: .sunny),
  976. ]
  977. )
  978. try XCTAssertEqual(
  979. XCTUnwrap(streamResult), WeatherForecastReport(forecasts: streamContents)
  980. )
  981. }
  982. func testStream_ComplexStreamResponse_Functional() async throws {
  983. let callable: Callable<[Location], StreamResponse<WeatherForecast, WeatherForecastReport>> =
  984. functions
  985. .httpsCallable("genStreamWeather")
  986. let stream = try callable.stream([
  987. Location(name: "Toronto"),
  988. Location(name: "London"),
  989. Location(name: "Dubai"),
  990. ])
  991. let result: (accumulatedMessages: [WeatherForecast], result: WeatherForecastReport?) =
  992. try await stream.reduce(([], nil)) { partialResult, streamResponse in
  993. switch streamResponse {
  994. case let .message(message):
  995. (partialResult.accumulatedMessages + [message], partialResult.result)
  996. case let .result(result):
  997. (partialResult.accumulatedMessages, result)
  998. }
  999. }
  1000. XCTAssertEqual(
  1001. result.accumulatedMessages,
  1002. [
  1003. WeatherForecast(location: Location(name: "Toronto"), temperature: 25, conditions: .snowy),
  1004. WeatherForecast(location: Location(name: "London"), temperature: 50, conditions: .rainy),
  1005. WeatherForecast(location: Location(name: "Dubai"), temperature: 75, conditions: .sunny),
  1006. ]
  1007. )
  1008. try XCTAssertEqual(
  1009. XCTUnwrap(result.result), WeatherForecastReport(forecasts: result.accumulatedMessages)
  1010. )
  1011. }
  1012. // Concurrency rules prevent easily testing this feature.
  1013. #if swift(<6)
  1014. func testStream_Canceled() async throws {
  1015. let task = Task.detached { [self] in
  1016. let callable: Callable<EmptyRequest, String> = functions.httpsCallable("genStream")
  1017. let stream = try callable.stream()
  1018. // Since we cancel the call we are expecting an empty array.
  1019. return try await stream.reduce([]) { $0 + [$1] } as [String]
  1020. }
  1021. // We cancel the task and we expect a null response even if the stream was initiated.
  1022. task.cancel()
  1023. let respone = try await task.value
  1024. XCTAssertEqual(respone, [])
  1025. }
  1026. #endif
  1027. func testStream_NonexistentFunction() async throws {
  1028. let callable: Callable<EmptyRequest, String> = functions.httpsCallable(
  1029. "nonexistentFunction"
  1030. )
  1031. let stream = try callable.stream()
  1032. do {
  1033. for try await _ in stream {
  1034. XCTFail("Expected error to be thrown from stream.")
  1035. }
  1036. } catch let error as FunctionsError where error.code == .notFound {
  1037. XCTAssertEqual(error.localizedDescription, "NOT FOUND")
  1038. }
  1039. }
  1040. func testStream_StreamError() async throws {
  1041. let callable: Callable<EmptyRequest, String> = functions.httpsCallable("genStreamError")
  1042. let stream = try callable.stream()
  1043. do {
  1044. for try await _ in stream {
  1045. XCTFail("Expected error to be thrown from stream.")
  1046. }
  1047. } catch let error as FunctionsError where error.code == .internal {
  1048. XCTAssertEqual(error.localizedDescription, "INTERNAL")
  1049. }
  1050. }
  1051. func testStream_RequestEncodingFailure() async throws {
  1052. struct Foo: Encodable {
  1053. enum CodingKeys: CodingKey {}
  1054. func encode(to encoder: any Encoder) throws {
  1055. throw EncodingError
  1056. .invalidValue("", EncodingError.Context(codingPath: [], debugDescription: ""))
  1057. }
  1058. }
  1059. let callable: Callable<Foo, String> = functions
  1060. .httpsCallable("genStream")
  1061. do {
  1062. _ = try callable.stream(Foo())
  1063. } catch let error as FunctionsError where error.code == .invalidArgument {
  1064. _ = try XCTUnwrap(error.errorUserInfo[NSUnderlyingErrorKey] as? EncodingError)
  1065. }
  1066. }
  1067. /// This tests an edge case to assert that if a custom `Response` is used
  1068. /// that matches the decoding logic of `StreamResponse`, the custom
  1069. /// `Response` does not decode successfully.
  1070. func testStream_ResultIsOnlyExposedInStreamResponse() async throws {
  1071. // The implementation is copied from `StreamResponse`. The only difference is the do-catch is
  1072. // removed from the decoding initializer.
  1073. enum MyStreamResponse<Message: Decodable & Sendable, Result: Decodable & Sendable>: Decodable,
  1074. Sendable {
  1075. /// The message yielded by the callable function.
  1076. case message(Message)
  1077. /// The final result returned by the callable function.
  1078. case result(Result)
  1079. private enum CodingKeys: String, CodingKey {
  1080. case message
  1081. case result
  1082. }
  1083. public init(from decoder: any Decoder) throws {
  1084. let container = try decoder
  1085. .container(keyedBy: Self<Message, Result>.CodingKeys.self)
  1086. var allKeys = ArraySlice(container.allKeys)
  1087. guard let onlyKey = allKeys.popFirst(), allKeys.isEmpty else {
  1088. throw DecodingError
  1089. .typeMismatch(
  1090. Self<Message,
  1091. Result>.self,
  1092. DecodingError.Context(
  1093. codingPath: container.codingPath,
  1094. debugDescription: "Invalid number of keys found, expected one.",
  1095. underlyingError: nil
  1096. )
  1097. )
  1098. }
  1099. switch onlyKey {
  1100. case .message:
  1101. self = try Self
  1102. .message(container.decode(Message.self, forKey: .message))
  1103. case .result:
  1104. self = try Self
  1105. .result(container.decode(Result.self, forKey: .result))
  1106. }
  1107. }
  1108. }
  1109. let callable: Callable<[Location], MyStreamResponse<WeatherForecast, WeatherForecastReport>> =
  1110. functions
  1111. .httpsCallable("genStreamWeather")
  1112. let stream = try callable.stream([Location(name: "Toronto")])
  1113. do {
  1114. for try await _ in stream {
  1115. XCTFail("Expected error to be thrown from stream.")
  1116. }
  1117. } catch let error as FunctionsError where error.code == .dataLoss {
  1118. XCTAssertNotNil(error.errorUserInfo[NSUnderlyingErrorKey] as? DecodingError)
  1119. }
  1120. }
  1121. func testStream_ForNonStreamingCF3() async throws {
  1122. let callable: Callable<Int16, Int> = functions.httpsCallable("scalarTest")
  1123. let stream = try callable.stream(17)
  1124. do {
  1125. for try await _ in stream {
  1126. XCTFail("Expected error to be thrown from stream.")
  1127. }
  1128. } catch let error as FunctionsError where error.code == .dataLoss {
  1129. XCTAssertEqual(error.localizedDescription, "Unexpected format for streamed response.")
  1130. }
  1131. }
  1132. func testStream_EmptyStream() async throws {
  1133. let callable: Callable<EmptyRequest, String> = functions.httpsCallable("genStreamEmpty")
  1134. var streamContents: [String] = []
  1135. for try await response in try callable.stream() {
  1136. streamContents.append(response)
  1137. }
  1138. XCTAssertEqual(streamContents, [])
  1139. }
  1140. func testStream_ResultOnly() async throws {
  1141. let callable: Callable<EmptyRequest, String> = functions.httpsCallable("genStreamResultOnly")
  1142. let stream = try callable.stream()
  1143. for try await _ in stream {
  1144. // The stream should not yield anything, so this should not be reached.
  1145. XCTFail("Stream should not yield any messages")
  1146. }
  1147. // Because StreamResponse was not used, the result is not accessible,
  1148. // but the message should not throw.
  1149. }
  1150. func testStream_ResultOnly_StreamResponse() async throws {
  1151. struct EmptyResponse: Decodable, Sendable {}
  1152. let callable: Callable<EmptyRequest, StreamResponse<EmptyResponse, String>> = functions
  1153. .httpsCallable(
  1154. "genStreamResultOnly"
  1155. )
  1156. let stream = try callable.stream()
  1157. var streamResult = ""
  1158. for try await response in stream {
  1159. switch response {
  1160. case .message:
  1161. XCTFail("Stream should not yield any messages")
  1162. case let .result(result):
  1163. streamResult = result
  1164. }
  1165. }
  1166. // The hardcoded string matches the CF3's return value.
  1167. XCTAssertEqual(streamResult, "Only a result")
  1168. }
  1169. func testStream_UnexpectedType() async throws {
  1170. // This function yields strings, not integers.
  1171. let callable: Callable<EmptyRequest, Int> = functions.httpsCallable("genStream")
  1172. let stream = try callable.stream()
  1173. do {
  1174. for try await _ in stream {
  1175. XCTFail("Expected error to be thrown from stream.")
  1176. }
  1177. } catch let error as FunctionsError where error.code == .dataLoss {
  1178. XCTAssertNotNil(error.errorUserInfo[NSUnderlyingErrorKey] as? DecodingError)
  1179. }
  1180. }
  1181. func testStream_Timeout() async throws {
  1182. var callable: Callable<EmptyRequest, String> = functions.httpsCallable("timeoutTest")
  1183. // Set a short timeout
  1184. callable.timeoutInterval = 0.01 // 10 milliseconds
  1185. let stream = try callable.stream()
  1186. do {
  1187. for try await _ in stream {
  1188. XCTFail("Expected error to be thrown from stream.")
  1189. }
  1190. } catch let error as FunctionsError where error.code == .unavailable {
  1191. // This should be a timeout error.
  1192. XCTAssertEqual(
  1193. error.localizedDescription,
  1194. "The operation couldn’t be completed. (com.firebase.functions error 14.)"
  1195. )
  1196. XCTAssertNotNil(error.errorUserInfo[NSUnderlyingErrorKey] as? URLError)
  1197. }
  1198. }
  1199. func testStream_LargeData() async throws {
  1200. func generateLargeString() -> String {
  1201. var largeString = ""
  1202. for _ in 0 ..< 10000 {
  1203. largeString += "A"
  1204. }
  1205. return largeString
  1206. }
  1207. let callable: Callable<EmptyRequest, String> = functions.httpsCallable("genStreamLargeData")
  1208. let stream = try callable.stream()
  1209. var concatenatedData = ""
  1210. for try await response in stream {
  1211. concatenatedData += response
  1212. }
  1213. // Assert that the concatenated data matches the expected large data.
  1214. XCTAssertEqual(concatenatedData, generateLargeString())
  1215. }
  1216. }
  1217. // MARK: - Helpers
  1218. private class AuthTokenProvider: AuthInterop {
  1219. func getUserID() -> String? {
  1220. return "fake user"
  1221. }
  1222. let token: String
  1223. init(token: String) {
  1224. self.token = token
  1225. }
  1226. func getToken(forcingRefresh: Bool, completion: (String?, Error?) -> Void) {
  1227. completion(token, nil)
  1228. }
  1229. }
  1230. private class MessagingTokenProvider: NSObject, MessagingInterop {
  1231. var fcmToken: String? { return "fakeFCMToken" }
  1232. }