FunctionsErrorTests.swift 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. // Copyright 2024 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. @testable import FirebaseFunctions
  15. import XCTest
  16. final class FunctionsErrorTests: XCTestCase {
  17. func testInitWithCode() {
  18. let error = FunctionsError(.permissionDenied)
  19. let nsError = error as NSError
  20. XCTAssertEqual(nsError.domain, "com.firebase.functions")
  21. XCTAssertEqual(nsError.code, 7)
  22. XCTAssertEqual(nsError.localizedDescription, "PERMISSION DENIED")
  23. XCTAssertEqual(nsError.userInfo.count, 1)
  24. }
  25. func testInitWithCodeAndUserInfo() {
  26. let error = FunctionsError(.unimplemented, userInfo: ["TEST_Key": "TEST_Value"])
  27. let nsError = error as NSError
  28. XCTAssertEqual(nsError.domain, "com.firebase.functions")
  29. XCTAssertEqual(nsError.code, 12)
  30. XCTAssertEqual(
  31. nsError.localizedDescription,
  32. "The operation couldn’t be completed. (com.firebase.functions error 12.)"
  33. )
  34. XCTAssertEqual(nsError.userInfo.count, 1)
  35. XCTAssertEqual(nsError.userInfo["TEST_Key"] as? String, "TEST_Value")
  36. }
  37. func testInitWithOKStatusCodeAndNoErrorBody() {
  38. // The error should be `nil`.
  39. let error = FunctionsError(
  40. httpStatusCode: 200,
  41. region: "my-region",
  42. url: URL(string: "https://example.com/fake_func")!,
  43. body: nil,
  44. serializer: FunctionsSerializer()
  45. )
  46. XCTAssertNil(error)
  47. }
  48. func testInitWithErrorStatusCodeAndNoErrorBody() {
  49. // The error should be inferred from the HTTP status code.
  50. let error = FunctionsError(
  51. httpStatusCode: 429,
  52. region: "my-region",
  53. url: URL(string: "https://example.com/fake_func")!,
  54. body: nil,
  55. serializer: FunctionsSerializer()
  56. )
  57. guard let error else { return XCTFail("Unexpected `nil` value") }
  58. let nsError = error as NSError
  59. XCTAssertEqual(nsError.domain, "com.firebase.functions")
  60. XCTAssertEqual(nsError.code, 8)
  61. XCTAssertEqual(nsError.localizedDescription, "RESOURCE EXHAUSTED")
  62. XCTAssertEqual(nsError.userInfo.count, 3)
  63. }
  64. func testInitWithOKStatusCodeAndIncompleteErrorBody() {
  65. // The status code in the error body takes precedence over the HTTP status code.
  66. let responseData = #"{ "error": { "status": "OUT_OF_RANGE" } }"#.data(using: .utf8)!
  67. let error = FunctionsError(
  68. httpStatusCode: 200,
  69. region: "my-region",
  70. url: URL(string: "https://example.com/fake_func")!,
  71. body: responseData,
  72. serializer: FunctionsSerializer()
  73. )
  74. guard let error else { return XCTFail("Unexpected `nil` value") }
  75. let nsError = error as NSError
  76. XCTAssertEqual(nsError.domain, "com.firebase.functions")
  77. XCTAssertEqual(nsError.code, 11)
  78. XCTAssertEqual(nsError.localizedDescription, "OUT OF RANGE")
  79. XCTAssertEqual(nsError.userInfo.count, 3)
  80. }
  81. func testInitWithErrorStatusCodeAndErrorBody() {
  82. // The status code in the error body takes precedence over the HTTP status code.
  83. let responseData =
  84. #"{ "error": { "status": "OUT_OF_RANGE", "message": "TEST_ErrorMessage", "details": 123 } }"#
  85. .data(using: .utf8)!
  86. let error = FunctionsError(
  87. httpStatusCode: 499,
  88. region: "my-region",
  89. url: URL(string: "https://example.com/fake_func")!,
  90. body: responseData,
  91. serializer: FunctionsSerializer()
  92. )
  93. guard let error else { return XCTFail("Unexpected `nil` value") }
  94. let nsError = error as NSError
  95. XCTAssertEqual(nsError.domain, "com.firebase.functions")
  96. XCTAssertEqual(nsError.code, 11)
  97. XCTAssertEqual(nsError.localizedDescription, "TEST_ErrorMessage")
  98. XCTAssertEqual(nsError.userInfo.count, 4)
  99. XCTAssertEqual(nsError.userInfo["details"] as? Int, 123)
  100. }
  101. func testInitWithErrorStatusCodeAndOKErrorBody() {
  102. // When the status code in the error body is `OK`, error should be `nil` regardless of the HTTP
  103. // status code.
  104. let responseData =
  105. #"{ "error": { "status": "OK", "message": "TEST_ErrorMessage", "details": 123 } }"#
  106. .data(using: .utf8)!
  107. let error = FunctionsError(
  108. httpStatusCode: 401,
  109. region: "my-region",
  110. url: URL(string: "https://example.com/fake_func")!,
  111. body: responseData,
  112. serializer: FunctionsSerializer()
  113. )
  114. XCTAssertNil(error)
  115. }
  116. func testInitWithErrorStatusCodeAndIncompleteErrorBody() {
  117. // The error name is not in the body; it should be inferred from the HTTP status code.
  118. let responseData = #"{ "error": { "message": "TEST_ErrorMessage", "details": null } }"#
  119. .data(using: .utf8)!
  120. let error = FunctionsError(
  121. httpStatusCode: 403,
  122. region: "my-region",
  123. url: URL(string: "https://example.com/fake_func")!,
  124. body: responseData,
  125. serializer: FunctionsSerializer()
  126. )
  127. guard let error else { return XCTFail("Unexpected `nil` value") }
  128. let nsError = error as NSError
  129. XCTAssertEqual(nsError.domain, "com.firebase.functions")
  130. XCTAssertEqual(nsError.code, 7) // `permissionDenied`, inferred from the HTTP status code
  131. XCTAssertEqual(nsError.localizedDescription, "TEST_ErrorMessage")
  132. XCTAssertEqual(nsError.userInfo.count, 4)
  133. XCTAssertEqual(nsError.userInfo["details"] as? NSNull, NSNull())
  134. }
  135. func testInitWithErrorStatusCodeAndInvalidErrorBody() {
  136. // An unsupported status code in the error body should result in the rest of the body ignored.
  137. let responseData =
  138. #"{ "error": { "status": "TEST_UNKNOWN_ERROR", "message": "TEST_ErrorMessage", "details": 123 } }"#
  139. .data(using: .utf8)!
  140. let error = FunctionsError(
  141. httpStatusCode: 503,
  142. region: "my-region",
  143. url: URL(string: "https://example.com/fake_func")!,
  144. body: responseData,
  145. serializer: FunctionsSerializer()
  146. )
  147. guard let error else { return XCTFail("Unexpected `nil` value") }
  148. let nsError = error as NSError
  149. XCTAssertEqual(nsError.domain, "com.firebase.functions")
  150. // Currently, `internal` is used as the fallback error code. Is this correct?
  151. // Seems like we could get more information from the HTTP status code in such cases.
  152. XCTAssertEqual(nsError.code, 13)
  153. XCTAssertEqual(nsError.localizedDescription, "INTERNAL")
  154. XCTAssertEqual(nsError.userInfo.count, 1)
  155. }
  156. }