FunctionsSerializerTests.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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. class FunctionsSerializerTests: XCTestCase {
  24. private var serializer: FunctionsSerializer!
  25. override func setUp() {
  26. super.setUp()
  27. serializer = FunctionsSerializer()
  28. }
  29. func testEncodeNull() throws {
  30. let null = NSNull()
  31. XCTAssertEqual(try serializer.encode(null) as? NSNull, null)
  32. }
  33. func testDecodeNull() throws {
  34. let null = NSNull()
  35. XCTAssertEqual(try serializer.decode(null) as? NSNull, null)
  36. }
  37. func testEncodeInt32() throws {
  38. let one = NSNumber(value: 1 as Int32)
  39. XCTAssertEqual(one, try serializer.encode(one) as? NSNumber)
  40. }
  41. func testEncodeInt() throws {
  42. let one = NSNumber(1)
  43. let dict = try XCTUnwrap(serializer.encode(one) as? NSDictionary)
  44. XCTAssertEqual("type.googleapis.com/google.protobuf.Int64Value", dict["@type"] as? String)
  45. XCTAssertEqual("1", dict["value"] as? String)
  46. }
  47. func testDecodeInt32() throws {
  48. let one = NSNumber(value: 1 as Int32)
  49. XCTAssertEqual(one, try serializer.decode(one) as? NSNumber)
  50. }
  51. func testDecodeInt() throws {
  52. let one = NSNumber(1)
  53. XCTAssertEqual(one, try serializer.decode(one) as? NSNumber)
  54. }
  55. func testDecodeIntFromDictionary() throws {
  56. let dictOne = ["@type": "type.googleapis.com/google.protobuf.Int64Value",
  57. "value": "1"]
  58. XCTAssertEqual(NSNumber(1), try serializer.decode(dictOne) as? NSNumber)
  59. }
  60. func testEncodeLong() throws {
  61. let lowLong = NSNumber(-9_223_372_036_854_775_800)
  62. let dict = try XCTUnwrap(serializer.encode(lowLong) as? NSDictionary)
  63. XCTAssertEqual("type.googleapis.com/google.protobuf.Int64Value", dict["@type"] as? String)
  64. XCTAssertEqual("-9223372036854775800", dict["value"] as? String)
  65. }
  66. func testDecodeLong() throws {
  67. let lowLong = NSNumber(-9_223_372_036_854_775_800)
  68. XCTAssertEqual(lowLong, try serializer.decode(lowLong) as? NSNumber)
  69. }
  70. func testDecodeLongFromDictionary() throws {
  71. let dictLowLong = ["@type": "type.googleapis.com/google.protobuf.Int64Value",
  72. "value": "-9223372036854775800"]
  73. let decoded = try serializer.decode(dictLowLong) as? NSNumber
  74. XCTAssertEqual(NSNumber(-9_223_372_036_854_775_800), decoded)
  75. // A naive implementation might convert a number to a double and think that's close enough.
  76. // We need to make sure it's a long long for accuracy.
  77. XCTAssertEqual(decoded?.objCType[0], CChar("q".utf8.first!))
  78. }
  79. func testDecodeInvalidLong() throws {
  80. let typeString = "type.googleapis.com/google.protobuf.Int64Value"
  81. let badVal = "-9223372036854775800 and some other junk"
  82. let dictLowLong = ["@type": typeString, "value": badVal]
  83. do {
  84. _ = try serializer.decode(dictLowLong) as? NSNumber
  85. } catch let FunctionsSerializer.Error.failedToParseWrappedNumber(value, type) {
  86. XCTAssertEqual(value, badVal)
  87. XCTAssertEqual(type, typeString)
  88. return
  89. }
  90. XCTFail()
  91. }
  92. func testEncodeUnsignedLong() throws {
  93. let typeString = "type.googleapis.com/google.protobuf.UInt64Value"
  94. let highULong = NSNumber(value: 18_446_744_073_709_551_607 as UInt64)
  95. let expected = ["@type": typeString, "value": "18446744073709551607"]
  96. let encoded = try serializer.encode(highULong) as? [String: String]
  97. XCTAssertEqual(encoded, expected)
  98. }
  99. func testDecodeUnsignedLong() throws {
  100. let highULong = NSNumber(value: 18_446_744_073_709_551_607 as UInt64)
  101. XCTAssertEqual(highULong, try serializer.decode(highULong) as? NSNumber)
  102. }
  103. func testDecodeUnsignedLongFromDictionary() throws {
  104. let typeString = "type.googleapis.com/google.protobuf.UInt64Value"
  105. let highULong = NSNumber(value: 18_446_744_073_709_551_607 as UInt64)
  106. let coded = ["@type": typeString, "value": "18446744073709551607"]
  107. let decoded = try serializer.decode(coded) as? NSNumber
  108. XCTAssertEqual(highULong, decoded)
  109. // A naive implementation might convert a number to a double and think that's close enough.
  110. // We need to make sure it's an unsigned long long for accuracy.
  111. XCTAssertEqual(decoded?.objCType[0], CChar("Q".utf8.first!))
  112. }
  113. func testDecodeUnsignedLongFromDictionaryOverflow() throws {
  114. let typeString = "type.googleapis.com/google.protobuf.UInt64Value"
  115. let tooHighVal = "18446744073709551616"
  116. let coded = ["@type": typeString, "value": tooHighVal]
  117. do {
  118. _ = try serializer.decode(coded) as? NSNumber
  119. } catch let FunctionsSerializer.Error.failedToParseWrappedNumber(value, type) {
  120. XCTAssertEqual(value, tooHighVal)
  121. XCTAssertEqual(type, typeString)
  122. return
  123. }
  124. XCTFail()
  125. }
  126. func testEncodeDouble() throws {
  127. let myDouble = NSNumber(value: 1.2 as Double)
  128. XCTAssertEqual(myDouble, try serializer.encode(myDouble) as? NSNumber)
  129. }
  130. func testDecodeDouble() throws {
  131. let myDouble = NSNumber(value: 1.2 as Double)
  132. XCTAssertEqual(myDouble, try serializer.decode(myDouble) as? NSNumber)
  133. }
  134. func testEncodeBool() throws {
  135. XCTAssertEqual(true, try serializer.encode(true) as? NSNumber)
  136. }
  137. func testDecodeBool() throws {
  138. XCTAssertEqual(true, try serializer.decode(true) as? NSNumber)
  139. }
  140. func testEncodeString() throws {
  141. XCTAssertEqual("hello", try serializer.encode("hello") as? String)
  142. }
  143. func testDecodeString() throws {
  144. XCTAssertEqual("good-bye", try serializer.decode("good-bye") as? String)
  145. }
  146. // TODO: Should we add support for Array as well as NSArray?
  147. func testEncodeSimpleArray() throws {
  148. let input = [1 as Int32, 2 as Int32] as NSArray
  149. XCTAssertEqual(input, try serializer.encode(input) as? NSArray)
  150. }
  151. func testEncodeArray() throws {
  152. let input = [
  153. 1 as Int32,
  154. "two",
  155. [3 as Int32, ["@type": "type.googleapis.com/google.protobuf.Int64Value",
  156. "value": "9876543210"]] as [Any],
  157. ] as NSArray
  158. XCTAssertEqual(input, try serializer.encode(input) as? NSArray)
  159. }
  160. func testEncodeArrayWithInvalidElements() {
  161. let input = ["TEST", CustomObject()] as NSArray
  162. try assert(serializer.encode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  163. }
  164. func testDecodeArray() throws {
  165. let input = [
  166. 1 as Int64,
  167. "two",
  168. [3 as Int32, ["@type": "type.googleapis.com/google.protobuf.Int64Value",
  169. "value": "9876543210"]] as [Any],
  170. ] as NSArray
  171. let expected = [1 as Int64, "two", [3 as Int32, 9_876_543_210 as Int64] as [Any]] as NSArray
  172. XCTAssertEqual(expected, try serializer.decode(input) as? NSArray)
  173. }
  174. func testDecodeArrayWithInvalidElements() {
  175. let input = ["TEST", CustomObject()] as NSArray
  176. try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  177. }
  178. func testEncodeDictionary() throws {
  179. let input = [
  180. "foo": 1 as Int32,
  181. "bar": "hello",
  182. "baz": [3 as Int32, 9_876_543_210 as Int64] as [Any],
  183. ] as NSDictionary
  184. let expected = [
  185. "foo": 1,
  186. "bar": "hello",
  187. "baz": [3, ["@type": "type.googleapis.com/google.protobuf.Int64Value",
  188. "value": "9876543210"]] as [Any],
  189. ] as NSDictionary
  190. XCTAssertEqual(expected, try serializer.encode(input) as? NSDictionary)
  191. }
  192. func testEncodeDictionaryWithInvalidElements() {
  193. let input = ["TEST_CustomObj": CustomObject()] as NSDictionary
  194. try assert(serializer.encode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  195. }
  196. func testEncodeDictionaryWithInvalidNestedDictionary() {
  197. let input =
  198. ["TEST_NestedDict": ["TEST_CustomObj": CustomObject()] as NSDictionary] as NSDictionary
  199. try assert(serializer.encode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  200. }
  201. func testDecodeDictionary() throws {
  202. let input = ["foo": 1, "bar": "hello", "baz": [3, 9_876_543_210]] as NSDictionary
  203. let expected = ["foo": 1, "bar": "hello", "baz": [3, 9_876_543_210]] as NSDictionary
  204. XCTAssertEqual(expected, try serializer.decode(input) as? NSDictionary)
  205. }
  206. func testDecodeDictionaryWithInvalidElements() {
  207. let input = ["TEST_CustomObj": CustomObject()] as NSDictionary
  208. try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  209. }
  210. func testDecodeDictionaryWithInvalidNestedDictionary() {
  211. let input =
  212. ["TEST_NestedDict": ["TEST_CustomObj": CustomObject()] as NSDictionary] as NSDictionary
  213. try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  214. }
  215. func testEncodeUnknownType() {
  216. let input = ["@type": "unknown", "value": "whatever"] as NSDictionary
  217. XCTAssertEqual(input, try serializer.encode(input) as? NSDictionary)
  218. }
  219. func testDecodeUnknownType() {
  220. let input = ["@type": "unknown", "value": "whatever"] as NSDictionary
  221. XCTAssertEqual(input, try serializer.decode(input) as? NSDictionary)
  222. }
  223. func testDecodeUnknownTypeWithoutValue() {
  224. let input = ["@type": "unknown"] as NSDictionary
  225. XCTAssertEqual(input, try serializer.decode(input) as? NSDictionary)
  226. }
  227. func testEncodeUnsupportedType() {
  228. let input = CustomObject()
  229. try assert(serializer.encode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  230. }
  231. func testDecodeUnsupportedType() {
  232. let input = CustomObject()
  233. try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  234. }
  235. // If the object can be decoded as a wrapped number, all other properties are ignored:
  236. func testDecodeValidWrappedNumberWithUnsupportedExtra() throws {
  237. let input = [
  238. "@type": "type.googleapis.com/google.protobuf.Int64Value",
  239. "value": "1234567890",
  240. "extra": CustomObject(),
  241. ] as NSDictionary
  242. XCTAssertEqual(NSNumber(1_234_567_890), try serializer.decode(input) as? NSNumber)
  243. }
  244. // If the object is not a valid wrapped number, it’s processed as a generic array:
  245. func testDecodeWrappedNumberWithUnsupportedValue() throws {
  246. let input = [
  247. "@type": "type.googleapis.com/google.protobuf.Int64Value",
  248. "value": CustomObject(),
  249. ] as NSDictionary
  250. try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  251. }
  252. // If the object is not a valid wrapped number, it’s processed as a generic array:
  253. func testDecodeInvalidWrappedNumberWithUnsupportedExtra() throws {
  254. let input = [
  255. "@type": "CUSTOM_TYPE",
  256. "value": "1234567890",
  257. "extra": CustomObject(),
  258. ] as NSDictionary
  259. try assert(serializer.decode(input), throwsUnsupportedTypeErrorWithName: "CustomObject")
  260. }
  261. }
  262. // MARK: - Utilities
  263. extension FunctionsSerializerTests {
  264. private func assert<T>(_ expression: @autoclosure () throws -> T,
  265. throwsUnsupportedTypeErrorWithName expectedTypeName: String,
  266. line: UInt = #line) {
  267. XCTAssertThrowsError(try expression(), line: line) { error in
  268. guard case let .unsupportedType(typeName: typeName) = error as? FunctionsSerializer
  269. .Error else {
  270. return XCTFail("Unexpected error: \(error)", line: line)
  271. }
  272. XCTAssertEqual(typeName, expectedTypeName, line: line)
  273. }
  274. }
  275. }
  276. /// Used to represent a type that cannot be encoded or decoded.
  277. private class CustomObject {
  278. let id = 123
  279. }