HeartbeatControllerTests.swift 9.9 KB

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