HeartbeatControllerTests.swift 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  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 XCTest
  15. @testable import FirebaseCoreInternal
  16. import HeartbeatLoggingTestUtils
  17. class HeartbeatControllerTests: XCTestCase {
  18. // 2021-11-01 @ 00:00:00 (EST)
  19. let date = Date(timeIntervalSince1970: 1_635_739_200)
  20. func testFlush_WhenEmpty_ReturnsEmptyPayload() throws {
  21. // Given
  22. let controller = HeartbeatController(storage: HeartbeatStorageFake())
  23. // Then
  24. assertHeartbeatControllerFlushesEmptyPayload(controller)
  25. }
  26. func testLogAndFlush() throws {
  27. // Given
  28. let controller = HeartbeatController(
  29. storage: HeartbeatStorageFake(),
  30. dateProvider: { self.date }
  31. )
  32. assertHeartbeatControllerFlushesEmptyPayload(controller)
  33. // When
  34. controller.log("dummy_agent")
  35. let heartbeatPayload = controller.flush()
  36. // Then
  37. try HeartbeatLoggingTestUtils.assertEqualPayloadStrings(
  38. heartbeatPayload.headerValue(),
  39. """
  40. {
  41. "version": 2,
  42. "heartbeats": [
  43. {
  44. "agent": "dummy_agent",
  45. "dates": ["2021-11-01"]
  46. }
  47. ]
  48. }
  49. """
  50. )
  51. assertHeartbeatControllerFlushesEmptyPayload(controller)
  52. }
  53. func testLogAtEndOfTimePeriodAndAcceptAtStartOfNextOne() throws {
  54. // Given
  55. var testDate = date
  56. let controller = HeartbeatController(
  57. storage: HeartbeatStorageFake(),
  58. dateProvider: { testDate }
  59. )
  60. assertHeartbeatControllerFlushesEmptyPayload(controller)
  61. // When
  62. // - Clock time 2021-11-01 @ 00:00:00 (EST)
  63. controller.log("dummy_agent")
  64. // - Advance to 2021-11-01 @ 23:59:59 (EST)
  65. testDate.addTimeInterval(60 * 60 * 24 - 1)
  66. controller.log("dummy_agent")
  67. // - Advance to 2021-11-02 @ 00:00:00 (EST)
  68. testDate.addTimeInterval(1)
  69. controller.log("dummy_agent")
  70. // Then
  71. let heartbeatPayload = controller.flush()
  72. try HeartbeatLoggingTestUtils.assertEqualPayloadStrings(
  73. heartbeatPayload.headerValue(),
  74. """
  75. {
  76. "version": 2,
  77. "heartbeats": [
  78. {
  79. "agent": "dummy_agent",
  80. "dates": [
  81. "2021-11-01",
  82. "2021-11-02"
  83. ]
  84. }
  85. ]
  86. }
  87. """
  88. )
  89. assertHeartbeatControllerFlushesEmptyPayload(controller)
  90. }
  91. func testDoNotLogMoreThanOnceInACalendarDay() throws {
  92. // Given
  93. let controller = HeartbeatController(
  94. storage: HeartbeatStorageFake(),
  95. dateProvider: { self.date }
  96. )
  97. // When
  98. controller.log("dummy_agent")
  99. controller.log("dummy_agent")
  100. // Then
  101. let heartbeatPayload = controller.flush()
  102. try HeartbeatLoggingTestUtils.assertEqualPayloadStrings(
  103. heartbeatPayload.headerValue(),
  104. """
  105. {
  106. "version": 2,
  107. "heartbeats": [
  108. {
  109. "agent": "dummy_agent",
  110. "dates": ["2021-11-01"]
  111. }
  112. ]
  113. }
  114. """
  115. )
  116. }
  117. func testDoNotLogMoreThanOnceInACalendarDay_AfterFlushing() throws {
  118. // Given
  119. let controller = HeartbeatController(
  120. storage: HeartbeatStorageFake(),
  121. dateProvider: { self.date }
  122. )
  123. // When
  124. controller.log("dummy_agent")
  125. let heartbeatPayload = controller.flush()
  126. controller.log("dummy_agent")
  127. // Then
  128. try HeartbeatLoggingTestUtils.assertEqualPayloadStrings(
  129. heartbeatPayload.headerValue(),
  130. """
  131. {
  132. "version": 2,
  133. "heartbeats": [
  134. {
  135. "agent": "dummy_agent",
  136. "dates": ["2021-11-01"]
  137. }
  138. ]
  139. }
  140. """
  141. )
  142. // Below assertion asserts that duplicate was not logged.
  143. assertHeartbeatControllerFlushesEmptyPayload(controller)
  144. }
  145. func testHeartbeatDatesAreStandardizedForUTC() throws {
  146. // Given
  147. let newYorkDate = try XCTUnwrap(
  148. DateComponents(
  149. calendar: .current,
  150. timeZone: TimeZone(identifier: "America/New_York"),
  151. year: 2021,
  152. month: 11,
  153. day: 01,
  154. hour: 23
  155. ).date // 2021-11-01 @ 11 PM (EST)
  156. )
  157. let heartbeatController = HeartbeatController(
  158. storage: HeartbeatStorageFake(),
  159. dateProvider: { newYorkDate }
  160. )
  161. // When
  162. heartbeatController.log("dummy_agent")
  163. let payload = heartbeatController.flush()
  164. // Then
  165. // Note below how the date was intepreted as UTC - 2021-11-02.
  166. try HeartbeatLoggingTestUtils.assertEqualPayloadStrings(
  167. payload.headerValue(),
  168. """
  169. {
  170. "version": 2,
  171. "heartbeats": [
  172. {
  173. "agent": "dummy_agent",
  174. "dates": ["2021-11-02"]
  175. }
  176. ]
  177. }
  178. """
  179. )
  180. }
  181. func testDoNotLogMoreThanOnceInACalendarDay_WhenTravelingAcrossTimeZones() throws {
  182. // Given
  183. let newYorkDate = try XCTUnwrap(
  184. DateComponents(
  185. calendar: .current,
  186. timeZone: TimeZone(identifier: "America/New_York"),
  187. year: 2021,
  188. month: 11,
  189. day: 01,
  190. hour: 23
  191. ).date // 2021-11-01 @ 11 PM (New York time zone)
  192. )
  193. let tokyoDate = try XCTUnwrap(
  194. DateComponents(
  195. calendar: .current,
  196. timeZone: TimeZone(identifier: "Asia/Tokyo"),
  197. year: 2021,
  198. month: 11,
  199. day: 02,
  200. hour: 23
  201. ).date // 2021-11-02 @ 11 PM (Tokyo time zone)
  202. )
  203. var testDate = newYorkDate
  204. let heartbeatController = HeartbeatController(
  205. storage: HeartbeatStorageFake(),
  206. dateProvider: { testDate }
  207. )
  208. // When
  209. heartbeatController.log("dummy_agent")
  210. // Device travels from NYC to Tokyo.
  211. testDate = tokyoDate
  212. heartbeatController.log("dummy_agent")
  213. // Then
  214. let payload = heartbeatController.flush()
  215. try HeartbeatLoggingTestUtils.assertEqualPayloadStrings(
  216. payload.headerValue(),
  217. """
  218. {
  219. "version" : 2,
  220. "heartbeats" : [
  221. {
  222. "agent" : "dummy_agent",
  223. "dates" : [
  224. "2021-11-02"
  225. ]
  226. }
  227. ]
  228. }
  229. """
  230. )
  231. }
  232. func testLoggingDependsOnDateNotUserAgent() throws {
  233. // Given
  234. var testDate = date
  235. let heartbeatController = HeartbeatController(
  236. storage: HeartbeatStorageFake(),
  237. dateProvider: { testDate }
  238. )
  239. // When
  240. // - Day 1
  241. heartbeatController.log("dummy_agent")
  242. // - Day 2
  243. testDate.addTimeInterval(60 * 60 * 24)
  244. heartbeatController.log("some_other_agent")
  245. // - Day 3
  246. testDate.addTimeInterval(60 * 60 * 24)
  247. heartbeatController.log("dummy_agent")
  248. // Then
  249. let payload = heartbeatController.flush()
  250. try HeartbeatLoggingTestUtils.assertEqualPayloadStrings(
  251. payload.headerValue(),
  252. """
  253. {
  254. "version": 2,
  255. "heartbeats": [
  256. {
  257. "agent": "dummy_agent",
  258. "dates": [
  259. "2021-11-01",
  260. "2021-11-03"
  261. ]
  262. },
  263. {
  264. "agent": "some_other_agent",
  265. "dates": [
  266. "2021-11-02"
  267. ]
  268. }
  269. ]
  270. }
  271. """
  272. )
  273. }
  274. func testFlushHeartbeatFromToday_WhenTodayHasAHeartbeat_ReturnsPayloadWithOnlyTodaysHeartbeat() throws {
  275. // Given
  276. let yesterdaysDate = date.addingTimeInterval(-1 * 60 * 60 * 24)
  277. let todaysDate = date
  278. let tomorrowsDate = date.addingTimeInterval(60 * 60 * 24)
  279. var testDate = yesterdaysDate
  280. let heartbeatController = HeartbeatController(
  281. storage: HeartbeatStorageFake(),
  282. dateProvider: { testDate }
  283. )
  284. // When
  285. heartbeatController.log("yesterdays_dummy_agent")
  286. testDate = todaysDate
  287. heartbeatController.log("todays_dummy_agent")
  288. testDate = tomorrowsDate
  289. heartbeatController.log("tomorrows_dummy_agent")
  290. testDate = todaysDate
  291. // Then
  292. let payload = heartbeatController.flushHeartbeatFromToday()
  293. try HeartbeatLoggingTestUtils.assertEqualPayloadStrings(
  294. payload.headerValue(),
  295. """
  296. {
  297. "version": 2,
  298. "heartbeats": [
  299. {
  300. "agent": "todays_dummy_agent",
  301. "dates": ["2021-11-01"]
  302. }
  303. ]
  304. }
  305. """
  306. )
  307. let remainingPayload = heartbeatController.flush()
  308. try HeartbeatLoggingTestUtils.assertEqualPayloadStrings(
  309. remainingPayload.headerValue(),
  310. """
  311. {
  312. "version": 2,
  313. "heartbeats": [
  314. {
  315. "agent": "tomorrows_dummy_agent",
  316. "dates": ["2021-11-02"]
  317. },
  318. {
  319. "agent": "yesterdays_dummy_agent",
  320. "dates": ["2021-10-31"]
  321. }
  322. ]
  323. }
  324. """
  325. )
  326. assertHeartbeatControllerFlushesEmptyPayload(heartbeatController)
  327. }
  328. func testFlushHeartbeatFromToday_WhenTodayDoesNotHaveAHeartbeat_ReturnsEmptyPayload() throws {
  329. // Given
  330. let heartbeatController = HeartbeatController(id: #function, dateProvider: { self.date })
  331. // When
  332. heartbeatController.flushHeartbeatFromToday()
  333. // Then
  334. assertHeartbeatControllerFlushesEmptyPayload(heartbeatController)
  335. }
  336. }
  337. // MARK: - Fakes
  338. private class HeartbeatStorageFake: HeartbeatStorageProtocol {
  339. private var heartbeatsBundle: HeartbeatsBundle?
  340. func readAndWriteSync(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) {
  341. heartbeatsBundle = transform(heartbeatsBundle)
  342. }
  343. func readAndWriteAsync(using transform: @escaping (HeartbeatsBundle?) -> HeartbeatsBundle?) {
  344. heartbeatsBundle = transform(heartbeatsBundle)
  345. }
  346. func getAndSet(using transform: (HeartbeatsBundle?) -> HeartbeatsBundle?) throws
  347. -> HeartbeatsBundle? {
  348. let oldHeartbeatsBundle = heartbeatsBundle
  349. heartbeatsBundle = transform(heartbeatsBundle)
  350. return oldHeartbeatsBundle
  351. }
  352. }