AggregationIntegrationTests.swift 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. /*
  2. * Copyright 2023 Google LLC
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import FirebaseFirestore
  17. import FirebaseFirestoreSwift
  18. import Foundation
  19. // TODO(sum/avg) remove `sumAvgIsPublic` from the directive below to enable these tests when sum/avg
  20. // is public
  21. #if sumAvgIsPublic && swift(>=5.5.2)
  22. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  23. class AggregationIntegrationTests: FSTIntegrationTestCase {
  24. func testCount() async throws {
  25. let collection = collectionRef()
  26. try await collection.addDocument(data: [:])
  27. let snapshot = try await collection.count.getAggregation(source: .server)
  28. XCTAssertEqual(snapshot.count, 1)
  29. }
  30. func testCanRunAggregateQuery() async throws {
  31. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  32. try XCTSkipIf(
  33. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  34. "only tested against emulator"
  35. )
  36. let collection = collectionRef()
  37. try await collection.addDocument(data: ["author": "authorA",
  38. "title": "titleA",
  39. "pages": 100,
  40. "height": 24.5,
  41. "weight": 24.1,
  42. "foo": 1,
  43. "bar": 2,
  44. "baz": 3])
  45. try await collection.addDocument(data: ["author": "authorB",
  46. "title": "titleB",
  47. "pages": 50,
  48. "height": 25.5,
  49. "weight": 75.5,
  50. "foo": 1,
  51. "bar": 2,
  52. "baz": 3])
  53. let snapshot = try await collection.aggregate([
  54. AggregateField.count(),
  55. AggregateField.sum("pages"),
  56. AggregateField.sum("weight"),
  57. AggregateField.average("pages"),
  58. AggregateField.average("weight"),
  59. ]).getAggregation(source: .server)
  60. // Count
  61. XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 2)
  62. // Sum
  63. XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150)
  64. XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Double, 150)
  65. XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Int64, 150)
  66. XCTAssertEqual(snapshot.get(AggregateField.sum("weight")) as? NSNumber, 99.6)
  67. XCTAssertEqual(snapshot.get(AggregateField.sum("weight")) as? Double, 99.6)
  68. // Average
  69. XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0)
  70. XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Double, 75.0)
  71. XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Int64, 75)
  72. XCTAssertEqual(snapshot.get(AggregateField.average("weight")) as? NSNumber, 49.8)
  73. XCTAssertEqual(snapshot.get(AggregateField.average("weight")) as? Double, 49.8)
  74. }
  75. func testCannotPerformMoreThanMaxAggregations() async throws {
  76. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  77. try XCTSkipIf(
  78. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  79. "only tested against emulator"
  80. )
  81. let collection = collectionRef()
  82. try await collection.addDocument(data: ["author": "authorA",
  83. "title": "titleA",
  84. "pages": 100,
  85. "height": 24.5,
  86. "weight": 24.1,
  87. "foo": 1,
  88. "bar": 2,
  89. "baz": 3])
  90. // Max is 5, we're attempting 6. I also like to live dangerously.
  91. do {
  92. let snapshot = try await collection.aggregate([
  93. AggregateField.count(),
  94. AggregateField.sum("pages"),
  95. AggregateField.sum("weight"),
  96. AggregateField.average("pages"),
  97. AggregateField.average("weight"),
  98. AggregateField.average("foo"),
  99. ]).getAggregation(source: .server)
  100. XCTFail("Error expected.")
  101. } catch let error as NSError {
  102. XCTAssertNotNil(error)
  103. XCTAssertTrue(error.localizedDescription.contains("maximum number of aggregations"))
  104. }
  105. }
  106. func testPerformsAggregationsWhenNaNExistsForSomeFieldValues() async throws {
  107. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  108. try XCTSkipIf(
  109. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  110. "only tested against emulator"
  111. )
  112. let collection = collectionRef()
  113. try await collection.addDocument(data: ["author": "authorA",
  114. "title": "titleA",
  115. "pages": 100,
  116. "year": 1980,
  117. "rating": 4])
  118. try await collection.addDocument(data: ["author": "authorB",
  119. "title": "titleB",
  120. "pages": 50,
  121. "year": 2020,
  122. "rating": Double.nan])
  123. let snapshot = try await collection.aggregate([
  124. AggregateField.sum("pages"),
  125. AggregateField.sum("rating"),
  126. AggregateField.average("pages"),
  127. AggregateField.average("rating"),
  128. ]).getAggregation(source: .server)
  129. // Sum
  130. XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150)
  131. XCTAssertTrue((snapshot.get(AggregateField.sum("rating")) as? Double)?.isNaN ?? false)
  132. // Average
  133. XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0)
  134. XCTAssertTrue((snapshot.get(AggregateField.average("rating")) as? Double)?.isNaN ?? false)
  135. }
  136. func testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation() async throws {
  137. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  138. try XCTSkipIf(
  139. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  140. "only tested against emulator"
  141. )
  142. let collection = collectionRef()
  143. try await collection.addDocument(data: [:])
  144. let snapshot = try await collection.aggregate([AggregateField.average("foo")])
  145. .getAggregation(source: .server)
  146. XCTAssertTrue(FSTNSExceptionUtil.testForException({
  147. snapshot.count
  148. }, reasonContains: "'count()' was not requested in the aggregation query"))
  149. XCTAssertTrue(FSTNSExceptionUtil.testForException({
  150. snapshot.get(AggregateField.sum("foo"))
  151. }, reasonContains: "'sum(foo)' was not requested in the aggregation query"))
  152. XCTAssertTrue(FSTNSExceptionUtil.testForException({
  153. snapshot.get(AggregateField.average("bar"))
  154. }, reasonContains: "'avg(bar)' was not requested in the aggregation query"))
  155. }
  156. func testPerformsAggregationsOnNestedMapValues() async throws {
  157. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  158. try XCTSkipIf(
  159. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  160. "only tested against emulator"
  161. )
  162. let collection = collectionRef()
  163. try await collection.addDocument(data: ["metadata": [
  164. "pages": 100,
  165. "rating": [
  166. "critic": 2,
  167. "user": 5,
  168. ],
  169. ]])
  170. try await collection.addDocument(data: ["metadata": [
  171. "pages": 50,
  172. "rating": [
  173. "critic": 4,
  174. "user": 4,
  175. ],
  176. ]])
  177. let snapshot = try await collection.aggregate([
  178. AggregateField.count(),
  179. AggregateField.sum("metadata.pages"),
  180. AggregateField.sum(FieldPath(["metadata", "rating", "user"])),
  181. AggregateField.average("metadata.pages"),
  182. AggregateField.average(FieldPath(["metadata", "rating", "critic"])),
  183. ]).getAggregation(source: .server)
  184. // Count
  185. XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 2)
  186. // Sum
  187. XCTAssertEqual(
  188. snapshot.get(AggregateField.sum(FieldPath(["metadata", "pages"]))) as? NSNumber,
  189. 150
  190. )
  191. XCTAssertEqual(snapshot.get(AggregateField.sum("metadata.pages")) as? NSNumber, 150)
  192. XCTAssertEqual(snapshot.get(AggregateField.sum("metadata.rating.user")) as? NSNumber, 9)
  193. // Average
  194. XCTAssertEqual(
  195. snapshot.get(AggregateField.average(FieldPath(["metadata", "pages"]))) as? Double,
  196. 75.0
  197. )
  198. XCTAssertEqual(
  199. snapshot.get(AggregateField.average("metadata.rating.critic")) as? Double,
  200. 3.0
  201. )
  202. }
  203. func testSumOverflow() async throws {
  204. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  205. try XCTSkipIf(
  206. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  207. "only tested against emulator"
  208. )
  209. let collection = collectionRef()
  210. try await collection.addDocument(data: [
  211. "longOverflow": Int64.max,
  212. "accumulationOverflow": Int64.max,
  213. "positiveInfinity": Double.greatestFiniteMagnitude,
  214. "negativeInfinity": -Double.greatestFiniteMagnitude,
  215. ])
  216. try await collection.addDocument(data: [
  217. "longOverflow": Int64.max,
  218. "accumulationOverflow": 1,
  219. "positiveInfinity": Double.greatestFiniteMagnitude,
  220. "negativeInfinity": -Double.greatestFiniteMagnitude,
  221. ])
  222. try await collection.addDocument(data: [
  223. "longOverflow": Int64.max,
  224. "accumulationOverflow": -101,
  225. "positiveInfinity": Double.greatestFiniteMagnitude,
  226. "negativeInfinity": -Double.greatestFiniteMagnitude,
  227. ])
  228. let snapshot = try await collection.aggregate([
  229. AggregateField.sum("longOverflow"),
  230. AggregateField.sum("accumulationOverflow"),
  231. AggregateField.sum("positiveInfinity"),
  232. AggregateField.sum("negativeInfinity"),
  233. ]).getAggregation(source: .server)
  234. // Sum
  235. XCTAssertEqual(
  236. snapshot.get(AggregateField.sum("longOverflow")) as? Double,
  237. Double(Int64.max) + Double(Int64.max) + Double(Int64.max)
  238. )
  239. XCTAssertEqual(
  240. snapshot.get(AggregateField.sum("accumulationOverflow")) as? Int64,
  241. Int64.max - 100
  242. )
  243. XCTAssertEqual(
  244. snapshot.get(AggregateField.sum("positiveInfinity")) as? Double,
  245. Double.infinity
  246. )
  247. XCTAssertEqual(
  248. snapshot.get(AggregateField.sum("negativeInfinity")) as? Double,
  249. -Double.infinity
  250. )
  251. }
  252. func testAverageOverflow() async throws {
  253. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  254. try XCTSkipIf(
  255. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  256. "only tested against emulator"
  257. )
  258. let collection = collectionRef()
  259. try await collection.addDocument(data: [
  260. "longOverflow": Int64.max,
  261. "doubleOverflow": Double.greatestFiniteMagnitude,
  262. "negativeInfinity": -Double.greatestFiniteMagnitude,
  263. ])
  264. try await collection.addDocument(data: [
  265. "longOverflow": Int64.max,
  266. "doubleOverflow": Double.greatestFiniteMagnitude,
  267. "negativeInfinity": -Double.greatestFiniteMagnitude,
  268. ])
  269. try await collection.addDocument(data: [
  270. "longOverflow": Int64.max,
  271. "doubleOverflow": Double.greatestFiniteMagnitude,
  272. "negativeInfinity": -Double.greatestFiniteMagnitude,
  273. ])
  274. let snapshot = try await collection.aggregate([
  275. AggregateField.average("longOverflow"),
  276. AggregateField.average("doubleOverflow"),
  277. AggregateField.average("negativeInfinity"),
  278. ]).getAggregation(source: .server)
  279. // Average
  280. XCTAssertEqual(
  281. snapshot.get(AggregateField.average("longOverflow")) as? Double,
  282. Double(Int64.max)
  283. )
  284. XCTAssertEqual(
  285. snapshot.get(AggregateField.average("doubleOverflow")) as? Double,
  286. Double.infinity
  287. )
  288. XCTAssertEqual(
  289. snapshot.get(AggregateField.average("negativeInfinity")) as? Double,
  290. -Double.infinity
  291. )
  292. }
  293. func testAverageUnderflow() async throws {
  294. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  295. try XCTSkipIf(
  296. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  297. "only tested against emulator"
  298. )
  299. let collection = collectionRef()
  300. try await collection.addDocument(data: ["underflowSmall": Double.leastNonzeroMagnitude])
  301. try await collection.addDocument(data: ["underflowSmall": 0])
  302. let snapshot = try await collection.aggregate([AggregateField.average("underflowSmall")])
  303. .getAggregation(source: .server)
  304. // Average
  305. XCTAssertEqual(snapshot.get(AggregateField.average("underflowSmall")) as? Double, 0.0)
  306. }
  307. func testPerformsAggregateOverResultSetOfZeroDocuments() async throws {
  308. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  309. try XCTSkipIf(
  310. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  311. "only tested against emulator"
  312. )
  313. let collection = collectionRef()
  314. try await collection.addDocument(data: ["pages": 100])
  315. try await collection.addDocument(data: ["pages": 50])
  316. let snapshot = try await collection.whereField("pages", isGreaterThan: 200)
  317. .aggregate([AggregateField.count(), AggregateField.sum("pages"),
  318. AggregateField.average("pages")]).getAggregation(source: .server)
  319. // Count
  320. XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 0)
  321. // Sum
  322. XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 0)
  323. // Average
  324. // TODO: (sum/avg) this design is bad, will require and API update
  325. XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNull, NSNull())
  326. }
  327. func testPerformsAggregateOverResultSetOfZeroFields() async throws {
  328. // TODO(sum/avg) remove the check below when sum and avg are supported in production
  329. try XCTSkipIf(
  330. !FSTIntegrationTestCase.isRunningAgainstEmulator(),
  331. "only tested against emulator"
  332. )
  333. let collection = collectionRef()
  334. try await collection.addDocument(data: ["pages": 100])
  335. try await collection.addDocument(data: ["pages": 50])
  336. let snapshot = try await collection
  337. .aggregate([AggregateField.count(), AggregateField.sum("notInMyDocs"),
  338. AggregateField.average("notInMyDocs")]).getAggregation(source: .server)
  339. // Count - 0 because aggregation is performed on documents matching the query AND documents
  340. // that have all aggregated fields
  341. XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 0)
  342. // Sum
  343. XCTAssertEqual(snapshot.get(AggregateField.sum("notInMyDocs")) as? NSNumber, 0)
  344. // Average
  345. // TODO: (sum/avg) this design is bad, will require and API update
  346. XCTAssertEqual(snapshot.get(AggregateField.average("notInMyDocs")) as? NSNull, NSNull())
  347. }
  348. }
  349. #endif