FunctionsTests.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. // Copyright 2022 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 FirebaseCore
  16. @testable import FirebaseFunctions
  17. #if COCOAPODS
  18. import GTMSessionFetcher
  19. #else
  20. import GTMSessionFetcherCore
  21. #endif
  22. import XCTest
  23. import SharedTestUtilities
  24. class FunctionsTests: XCTestCase {
  25. var functions: Functions?
  26. var functionsCustomDomain: Functions?
  27. let fetcherService = GTMSessionFetcherService()
  28. let appCheckFake = FIRAppCheckFake()
  29. override func setUp() {
  30. super.setUp()
  31. functions = Functions(
  32. projectID: "my-project",
  33. region: "my-region",
  34. customDomain: nil,
  35. auth: nil,
  36. messaging: nil,
  37. appCheck: appCheckFake,
  38. fetcherService: fetcherService
  39. )
  40. functionsCustomDomain = Functions(projectID: "my-project", region: "my-region",
  41. customDomain: "https://mydomain.com", auth: nil,
  42. messaging: nil, appCheck: nil,
  43. fetcherService: fetcherService)
  44. }
  45. override func tearDown() {
  46. functions = nil
  47. functionsCustomDomain = nil
  48. super.tearDown()
  49. }
  50. func testFunctionsInstanceIsStablePerApp() throws {
  51. let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
  52. gcmSenderID: "00000000000000000-00000000000-000000000")
  53. options.projectID = "myProjectID"
  54. FirebaseApp.configure(options: options)
  55. var functions1 = Functions.functions()
  56. var functions2 = Functions.functions(app: FirebaseApp.app()!)
  57. XCTAssertEqual(functions1, functions2)
  58. FirebaseApp.configure(name: "test", options: options)
  59. let app2 = try XCTUnwrap(FirebaseApp.app(name: "test"))
  60. functions2 = Functions.functions(app: app2, region: "us-central2")
  61. XCTAssertNotEqual(functions1, functions2)
  62. functions1 = Functions.functions(app: app2, region: "us-central2")
  63. XCTAssertEqual(functions1, functions2)
  64. functions1 = Functions.functions(customDomain: "test_domain")
  65. functions2 = Functions.functions(region: "us-central1")
  66. XCTAssertNotEqual(functions1, functions2)
  67. functions2 = Functions.functions(app: FirebaseApp.app()!, customDomain: "test_domain")
  68. XCTAssertEqual(functions1, functions2)
  69. }
  70. func testURLWithName() throws {
  71. let url = try XCTUnwrap(functions?.urlWithName("my-endpoint"))
  72. XCTAssertEqual(url, "https://my-region-my-project.cloudfunctions.net/my-endpoint")
  73. }
  74. func testRegionWithEmulator() throws {
  75. functionsCustomDomain?.useEmulator(withHost: "localhost", port: 5005)
  76. let url = try XCTUnwrap(functionsCustomDomain?.urlWithName("my-endpoint"))
  77. XCTAssertEqual(url, "http://localhost:5005/my-project/my-region/my-endpoint")
  78. }
  79. func testRegionWithEmulatorWithScheme() throws {
  80. functionsCustomDomain?.useEmulator(withHost: "http://localhost", port: 5005)
  81. let url = try XCTUnwrap(functionsCustomDomain?.urlWithName("my-endpoint"))
  82. XCTAssertEqual(url, "http://localhost:5005/my-project/my-region/my-endpoint")
  83. }
  84. func testCustomDomain() throws {
  85. let url = try XCTUnwrap(functionsCustomDomain?.urlWithName("my-endpoint"))
  86. XCTAssertEqual(url, "https://mydomain.com/my-endpoint")
  87. }
  88. func testSetEmulatorSettings() throws {
  89. functions?.useEmulator(withHost: "localhost", port: 1000)
  90. XCTAssertEqual("http://localhost:1000", functions?.emulatorOrigin)
  91. }
  92. /// Test that Functions instances get deallocated.
  93. func testFunctionsLifecycle() throws {
  94. weak var weakApp: FirebaseApp?
  95. weak var weakFunctions: Functions?
  96. try autoreleasepool {
  97. let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000",
  98. gcmSenderID: "00000000000000000-00000000000-000000000")
  99. options.projectID = "myProjectID"
  100. let app1 = FirebaseApp(instanceWithName: "transitory app", options: options)
  101. weakApp = try XCTUnwrap(app1)
  102. let functions = Functions(app: app1, region: "transitory-region", customDomain: nil)
  103. weakFunctions = functions
  104. XCTAssertNotNil(weakFunctions)
  105. }
  106. XCTAssertNil(weakApp)
  107. XCTAssertNil(weakFunctions)
  108. }
  109. // MARK: - App Check Integration
  110. func testCallFunctionWhenUsingLimitedUseAppCheckTokenThenTokenSuccess() {
  111. // Given
  112. // Stub returns of two different kinds of App Check tokens. Only the
  113. // limited use token should be present in Functions's request header.
  114. appCheckFake.tokenResult = FIRAppCheckTokenResultFake(token: "shared_valid_token", error: nil)
  115. appCheckFake.limitedUseTokenResult = FIRAppCheckTokenResultFake(
  116. token: "limited_use_valid_token",
  117. error: nil
  118. )
  119. let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
  120. fetcherService.testBlock = { fetcherToTest, testResponse in
  121. let appCheckTokenHeader = fetcherToTest.request?
  122. .value(forHTTPHeaderField: "X-Firebase-AppCheck")
  123. // Assert that header contains limited use token.
  124. XCTAssertEqual(appCheckTokenHeader, "limited_use_valid_token")
  125. testResponse(nil, "{\"data\":\"May the force be with you!\"}".data(using: .utf8), nil)
  126. httpRequestExpectation.fulfill()
  127. }
  128. // When
  129. let options = HTTPSCallableOptions(requireLimitedUseAppCheckTokens: true)
  130. // Then
  131. let completionExpectation = expectation(description: "completionExpectation")
  132. functions?
  133. .httpsCallable("fake_func", options: options)
  134. .call { result, error in
  135. guard let result = result else {
  136. return XCTFail("Unexpected error: \(error!).")
  137. }
  138. XCTAssertEqual(result.data as! String, "May the force be with you!")
  139. completionExpectation.fulfill()
  140. }
  141. waitForExpectations(timeout: 1.5)
  142. }
  143. func testCallFunctionWhenLimitedUseAppCheckTokenDisabledThenCallWithoutToken() {
  144. // Given
  145. let limitedUseDummyToken = "limited use dummy token"
  146. appCheckFake.limitedUseTokenResult = FIRAppCheckTokenResultFake(
  147. token: limitedUseDummyToken,
  148. error: NSError(domain: #function, code: -1)
  149. )
  150. let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
  151. fetcherService.testBlock = { fetcherToTest, testResponse in
  152. // Assert that header does not contain an AppCheck token.
  153. fetcherToTest.request?.allHTTPHeaderFields?.forEach { key, value in
  154. if key == "X-Firebase-AppCheck" {
  155. XCTAssertNotEqual(value, limitedUseDummyToken)
  156. }
  157. }
  158. testResponse(nil, "{\"data\":\"May the force be with you!\"}".data(using: .utf8), nil)
  159. httpRequestExpectation.fulfill()
  160. }
  161. // When
  162. let options = HTTPSCallableOptions(requireLimitedUseAppCheckTokens: false)
  163. // Then
  164. let completionExpectation = expectation(description: "completionExpectation")
  165. functions?
  166. .httpsCallable("fake_func", options: options)
  167. .call { result, error in
  168. guard let result = result else {
  169. return XCTFail("Unexpected error: \(error!).")
  170. }
  171. XCTAssertEqual(result.data as! String, "May the force be with you!")
  172. completionExpectation.fulfill()
  173. }
  174. waitForExpectations(timeout: 1.5)
  175. }
  176. func testCallFunctionWhenLimitedUseAppCheckTokenCannotBeGeneratedThenCallWithoutToken() {
  177. // Given
  178. appCheckFake.limitedUseTokenResult = FIRAppCheckTokenResultFake(
  179. token: "dummy token",
  180. error: NSError(domain: #function, code: -1)
  181. )
  182. let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
  183. fetcherService.testBlock = { fetcherToTest, testResponse in
  184. // Assert that header does not contain an AppCheck token.
  185. fetcherToTest.request?.allHTTPHeaderFields?.forEach { key, _ in
  186. XCTAssertNotEqual(key, "X-Firebase-AppCheck")
  187. }
  188. testResponse(nil, "{\"data\":\"May the force be with you!\"}".data(using: .utf8), nil)
  189. httpRequestExpectation.fulfill()
  190. }
  191. // When
  192. let options = HTTPSCallableOptions(requireLimitedUseAppCheckTokens: true)
  193. // Then
  194. let completionExpectation = expectation(description: "completionExpectation")
  195. functions?
  196. .httpsCallable("fake_func", options: options)
  197. .call { result, error in
  198. guard let result = result else {
  199. return XCTFail("Unexpected error: \(error!).")
  200. }
  201. XCTAssertEqual(result.data as! String, "May the force be with you!")
  202. completionExpectation.fulfill()
  203. }
  204. waitForExpectations(timeout: 1.5)
  205. }
  206. func testCallFunctionWhenAppCheckIsInstalledAndFACTokenSuccess() {
  207. // Stub returns of two different kinds of App Check tokens. Only the
  208. // shared use token should be present in Functions's request header.
  209. appCheckFake.tokenResult = FIRAppCheckTokenResultFake(token: "shared_valid_token", error: nil)
  210. appCheckFake.limitedUseTokenResult = FIRAppCheckTokenResultFake(
  211. token: "limited_use_valid_token",
  212. error: nil
  213. )
  214. let networkError = NSError(
  215. domain: "testCallFunctionWhenAppCheckIsInstalled",
  216. code: -1,
  217. userInfo: nil
  218. )
  219. let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
  220. fetcherService.testBlock = { fetcherToTest, testResponse in
  221. let appCheckTokenHeader = fetcherToTest.request?
  222. .value(forHTTPHeaderField: "X-Firebase-AppCheck")
  223. XCTAssertEqual(appCheckTokenHeader, "shared_valid_token")
  224. testResponse(nil, nil, networkError)
  225. httpRequestExpectation.fulfill()
  226. }
  227. let completionExpectation = expectation(description: "completionExpectation")
  228. functions?
  229. .httpsCallable("fake_func")
  230. .call { result, error in
  231. guard let error = error else {
  232. return XCTFail("Unexpected success: \(result!).")
  233. }
  234. XCTAssertEqual(error as NSError, networkError)
  235. completionExpectation.fulfill()
  236. }
  237. waitForExpectations(timeout: 1.5)
  238. }
  239. func testCallFunctionWhenAppCheckIsNotInstalled() {
  240. let networkError = NSError(
  241. domain: "testCallFunctionWhenAppCheckIsInstalled",
  242. code: -1,
  243. userInfo: nil
  244. )
  245. let httpRequestExpectation = expectation(description: "HTTPRequestExpectation")
  246. fetcherService.testBlock = { fetcherToTest, testResponse in
  247. let appCheckTokenHeader = fetcherToTest.request?
  248. .value(forHTTPHeaderField: "X-Firebase-AppCheck")
  249. XCTAssertNil(appCheckTokenHeader)
  250. testResponse(nil, nil, networkError)
  251. httpRequestExpectation.fulfill()
  252. }
  253. let completionExpectation = expectation(description: "completionExpectation")
  254. functionsCustomDomain?.callFunction(
  255. name: "fake_func",
  256. withObject: nil,
  257. options: nil,
  258. timeout: 10
  259. ) { result in
  260. switch result {
  261. case .success:
  262. XCTFail("Unexpected success from functions?.callFunction")
  263. case let .failure(error as NSError):
  264. XCTAssertEqual(error, networkError)
  265. }
  266. completionExpectation.fulfill()
  267. }
  268. waitForExpectations(timeout: 1.5)
  269. }
  270. }