PumpOpsSynchronousTests.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. //
  2. // PumpOpsSynchronousTests.swift
  3. // RileyLink
  4. //
  5. // Created by Jaim Zuber on 2/21/17.
  6. // Copyright © 2017 Pete Schwamb. All rights reserved.
  7. //
  8. import XCTest
  9. @testable import RileyLinkKit
  10. @testable import MinimedKit
  11. @testable import RileyLinkBLEKit
  12. class PumpOpsSynchronousTests: XCTestCase {
  13. var sut: PumpOpsSession!
  14. var pumpSettings: PumpSettings!
  15. var pumpState: PumpState!
  16. var pumpID: String!
  17. var pumpRegion: PumpRegion!
  18. var pumpModel: PumpModel!
  19. var messageSenderStub: PumpMessageSenderStub!
  20. let dateComponents2007 = DateComponents(calendar: Calendar.current, year: 2007, month: 1, day: 1)
  21. let dateComponents2017 = DateComponents(calendar: Calendar.current, year: 2017, month: 1, day: 1)
  22. let squareBolusDataLength = 26
  23. lazy var datePast2007: Date = {
  24. return self.dateComponents2017.date!.addingTimeInterval(TimeInterval(minutes:60))
  25. }()
  26. lazy var datePast2017: Date = {
  27. return self.dateComponents2017.date!.addingTimeInterval(TimeInterval(minutes:60))
  28. }()
  29. lazy var dateTimestamp2010: DateComponents = {
  30. self.createSquareBolusEvent2010().timestamp
  31. }()
  32. override func setUp() {
  33. super.setUp()
  34. pumpID = "350535"
  35. pumpRegion = .worldWide
  36. pumpModel = PumpModel.model523
  37. messageSenderStub = PumpMessageSenderStub()
  38. setUpSUT()
  39. }
  40. /// Creates the System Under Test. This is needed because our SUT has dependencies injected through the constructor
  41. func setUpSUT() {
  42. pumpSettings = PumpSettings(pumpID: pumpID, pumpRegion: pumpRegion)
  43. pumpState = PumpState()
  44. pumpState.pumpModel = pumpModel
  45. pumpState.awakeUntil = Date(timeIntervalSinceNow: 100) // pump is awake
  46. sut = PumpOpsSession(settings: pumpSettings, pumpState: pumpState, session: messageSenderStub, delegate: messageSenderStub)
  47. }
  48. /// Duplicates logic in setUp with a new PumpModel
  49. ///
  50. /// - Parameter newPumpModel: model of the pump to test
  51. func setUpTestWithPumpModel(_ newPumpModel: PumpModel) {
  52. pumpModel = newPumpModel
  53. setUpSUT()
  54. }
  55. func testShouldContinueIfTimestampBeforeStartDateNotEncountered() {
  56. let page = HistoryPage(events: [createBatteryEvent()])
  57. let (_, hasMoreEvents, _) = page.timestampedEvents(after: .distantPast, timeZone: pumpState.timeZone, model: pumpModel)
  58. XCTAssertTrue(hasMoreEvents)
  59. }
  60. func testShouldFinishIfTimestampBeforeStartDateEncountered() {
  61. let batteryEvent = createBatteryEvent()
  62. let page = HistoryPage(events: [batteryEvent])
  63. let afterBatteryEventDate = batteryEvent.timestamp.date!.addingTimeInterval(TimeInterval(hours: 10))
  64. let (_, hasMoreEvents, _) = page.timestampedEvents(after: afterBatteryEventDate, timeZone: pumpState.timeZone, model: pumpModel)
  65. XCTAssertFalse(hasMoreEvents)
  66. }
  67. func testEventsAfterStartDateAreReturned() {
  68. let batteryEvent2007 = createBatteryEvent(withDateComponent: dateComponents2007)
  69. let batteryEvent2017 = createBatteryEvent(withDateComponent: dateComponents2017)
  70. let page = HistoryPage(events: [batteryEvent2007, batteryEvent2017])
  71. let (events, _, _) = page.timestampedEvents(after: .distantPast, timeZone: pumpState.timeZone, model: pumpModel)
  72. XCTAssertEqual(events.count, 2)
  73. }
  74. func testEventBeforeStartDateIsFiltered() {
  75. let datePast2007 = dateComponents2007.date!.addingTimeInterval(TimeInterval(minutes: 60))
  76. let batteryEvent2007 = createBatteryEvent(withDateComponent: dateComponents2007)
  77. let batteryEvent2017 = createBatteryEvent(withDateComponent: dateComponents2017)
  78. let page = HistoryPage(events: [batteryEvent2007, batteryEvent2017])
  79. let (events, hasMoreEvents, cancelled) = page.timestampedEvents(after: datePast2007, timeZone: pumpState.timeZone, model: pumpModel)
  80. assertArray(events, doesntContainPumpEvent: batteryEvent2007)
  81. XCTAssertEqual(events.count, 1)
  82. XCTAssertFalse(hasMoreEvents)
  83. XCTAssertFalse(cancelled)
  84. }
  85. func testPumpLostTimeCancelsFetchEarly() {
  86. let batteryEvent2007 = createBatteryEvent(withDateComponent: dateComponents2007)
  87. let batteryEvent2017 = createBatteryEvent(withDateComponent: dateComponents2017)
  88. let page = HistoryPage(events: [batteryEvent2017, batteryEvent2007])
  89. let (events, hasMoreEvents, cancelledEarly) = page.timestampedEvents(after: Date.distantPast, timeZone: pumpState.timeZone, model: pumpModel)
  90. XCTAssertTrue(cancelledEarly)
  91. XCTAssertFalse(hasMoreEvents)
  92. XCTAssertEqual(events.count, 1)
  93. assertArray(events, doesntContainPumpEvent: batteryEvent2017)
  94. }
  95. func testEventsWithSameDataArentAddedTwice() {
  96. let page = HistoryPage(events: [createBolusEvent2009(), createBolusEvent2009()])
  97. let (events, _, _) = page.timestampedEvents(after: Date.distantPast, timeZone: pumpState.timeZone, model: pumpModel)
  98. XCTAssertEqual(events.count, 1)
  99. }
  100. func testNonMutableSquareWaveBolusFor522IsReturned() {
  101. // device that can have out of order events
  102. setUpTestWithPumpModel(.model522)
  103. // 2009-07-31 09:00:00 +0000
  104. // 120 minute duration
  105. let squareWaveBolus = BolusNormalPumpEvent(availableData: Data(hexadecimalString: "010080048000240009a24a1510")!, pumpModel: pumpModel)!
  106. let page = HistoryPage(events: [squareWaveBolus])
  107. let (timeStampedEvents, _, _) = page.timestampedEvents(after: .distantPast, timeZone: pumpState.timeZone, model: pumpModel)
  108. // It should be included
  109. XCTAssertTrue(array(timeStampedEvents, containsPumpEvent: squareWaveBolus))
  110. }
  111. // This sets up a square wave bolus that has a timestamp four hours before a temp basal, but is appended to history
  112. // "after" the temp basal. This is an important condition to test, because the temp basal could be filtered out erroneously
  113. // if we were just filtering on startDate, and startDate was after the bolus timestamp, but before the temp basal.
  114. // Previously, convertPumpEventToTimestampedEvents was being called with startDate: Date.distantPast
  115. // Changing it to use a time in that important window to cover
  116. func testDelayedAppendEventDoesNotCauseValidEventsToBeFilteredOut() {
  117. setUpTestWithPumpModel(.model522)
  118. let tempEventBasal = createTempEventBasal2016()
  119. let dateComponents = tempEventBasal.timestamp.addingTimeInterval(TimeInterval(hours:-4))
  120. let squareBolusEventFourHoursBefore = createSquareBolusEvent(dateComponents: dateComponents)
  121. let page = HistoryPage(events: [tempEventBasal, squareBolusEventFourHoursBefore])
  122. let (timeStampedEvents, hasMoreEvents, cancelled) = page.timestampedEvents(after: .distantPast, timeZone: pumpState.timeZone, model: pumpModel)
  123. // Debatable (undefined) whether this should be returned. It is tested to avoid inadvertantly changing behavior
  124. assertArray(timeStampedEvents, containsPumpEvent: squareBolusEventFourHoursBefore)
  125. XCTAssertTrue(hasMoreEvents)
  126. XCTAssertFalse(cancelled)
  127. }
  128. // MARK: Regular Bolus Event before starttime (offset 9 minutes)
  129. func test522RegularBolusEventBeforeStartTimeShouldNotCancel() {
  130. setUpTestWithPumpModel(.model522)
  131. let pumpEvent = createSquareBolusEvent2010()
  132. let page = HistoryPage(events: [pumpEvent])
  133. let startDate = pumpEvent.timestamp.date!.addingTimeInterval(TimeInterval(minutes:9))
  134. let (timestampedEvents, hasMoreEvents, cancelled) = page.timestampedEvents(after: startDate, timeZone: pumpState.timeZone, model: pumpModel)
  135. assertArray(timestampedEvents, containsPumpEvent: pumpEvent)
  136. //We found an event before the start time but we can't verify the timestamp from the Square Bolus so there could be more valid events
  137. XCTAssertTrue(hasMoreEvents)
  138. XCTAssertFalse(cancelled)
  139. }
  140. // The test the border condition sof the pump lost time detection, the main behavior of which is covered
  141. // in testPumpLostTimeCancelsFetchEarly. The precise point at which we decide pump time is lost (the one hour mark) is aribtrary.
  142. func testOutOfOrderEventUnderAnHourDoesntCancel() {
  143. setUpTestWithPumpModel(.model523)
  144. let after2007Date = dateComponents2007.date!.addingTimeInterval(TimeInterval(minutes:59))
  145. let batteryEvent = createBatteryEvent(withDateComponent: dateComponents2007)
  146. let laterBatteryEvent = createBatteryEvent(atTime: after2007Date)
  147. let page = HistoryPage(events: [laterBatteryEvent, batteryEvent])
  148. let (_, _, cancelled) = page.timestampedEvents(after: .distantPast, timeZone: pumpState.timeZone, model: pumpModel)
  149. XCTAssertFalse(cancelled)
  150. }
  151. // MARK: Test Sanity Checks
  152. func test2010EventSanityWith523() {
  153. setUpTestWithPumpModel(.model523)
  154. let bolusEvent = createSquareBolusEvent2010()
  155. XCTAssertEqual(bolusEvent.timestamp.year!, 2010)
  156. XCTAssertEqual(bolusEvent.timestamp.timeZone, pumpState.timeZone)
  157. }
  158. func test2009EventSanityWith523() {
  159. setUpTestWithPumpModel(.model523)
  160. let bolusEvent = createBolusEvent2009()
  161. XCTAssertEqual(bolusEvent.timestamp.year!, 2009)
  162. XCTAssertEqual(bolusEvent.timestamp.timeZone, pumpState.timeZone)
  163. }
  164. func test2009EventSavityWith522() {
  165. setUpTestWithPumpModel(.model522)
  166. XCTAssertEqual(createBolusEvent2009().timestamp.year!, 2009)
  167. }
  168. func test2010EventSanityWith522() {
  169. setUpTestWithPumpModel(.model522)
  170. XCTAssertEqual(createSquareBolusEvent2010().timestamp.year!, 2010)
  171. }
  172. func createBatteryEvent(withDateComponent dateComponents: DateComponents) -> BatteryPumpEvent {
  173. return createBatteryEvent(atTime: dateComponents.date!)
  174. }
  175. func createBatteryEvent(atTime date: Date = Date()) -> BatteryPumpEvent {
  176. let calendar = Calendar.current
  177. let year = calendar.component(.year, from: date) - 2000
  178. let month = calendar.component(.month, from: date)
  179. let day = calendar.component(.day, from: date)
  180. let hour = calendar.component(.hour, from: date)
  181. let minute = calendar.component(.minute, from: date)
  182. let second = calendar.component(.second, from: date)
  183. let secondByte = UInt8(second) & 0b00111111
  184. let minuteByte = UInt8(minute) & 0b00111111
  185. let hourByte = UInt8(hour) & 0b00011111
  186. let dayByte = UInt8(day) & 0b00011111
  187. let monthUpperComponent = (UInt8(month) & 0b00001100) << 4
  188. let monthLowerComponent = (UInt8(month) & 0b00000011) << 6
  189. let secondMonthByte = secondByte | monthUpperComponent
  190. let minuteMonthByte = minuteByte | monthLowerComponent
  191. let yearByte = UInt8(year) & 0b01111111
  192. let batteryData = Data([0,0, secondMonthByte, minuteMonthByte, hourByte, dayByte, yearByte])
  193. let batteryPumpEvent = BatteryPumpEvent(availableData: batteryData, pumpModel: PumpModel.model523)!
  194. return batteryPumpEvent
  195. }
  196. func createSquareBolusEvent2010() -> BolusNormalPumpEvent {
  197. //2010-08-01 05:00:16 +000
  198. let dateComponents = DateComponents(calendar: Calendar.current, timeZone: pumpState.timeZone, year: 2010, month: 8, day: 1, hour: 5, minute: 0, second: 16)
  199. let data = Data(hexadecimalString: "01009000900058008a344b1010")!
  200. return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .square, duration: TimeInterval(minutes: 120), wasRemotelyTriggered: false)
  201. }
  202. func createSquareBolusEvent(dateComponents: DateComponents) -> BolusNormalPumpEvent {
  203. let data = Data(hexadecimalString: randomDataString(length: squareBolusDataLength))!
  204. return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .square, duration: TimeInterval(hours: 8), wasRemotelyTriggered: false)
  205. }
  206. func createBolusEvent2011() -> BolusNormalPumpEvent {
  207. //2010-08-01 05:00:11 +000
  208. let dateComponents = DateComponents(calendar: Calendar.current, timeZone: pumpState.timeZone, year: 2011, month: 8, day: 1, hour: 5, minute: 0, second: 16)
  209. let data = Data(hexadecimalString: "01009000900058008a344b10FF")!
  210. return BolusNormalPumpEvent(length: BolusNormalPumpEvent.calculateLength(pumpModel.larger), rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 0.0, programmed: 0.0, unabsorbedInsulinTotal: 0.0, type: .normal, duration: TimeInterval(minutes: 120), wasRemotelyTriggered: false)
  211. }
  212. func createTempEventBasal2016() -> TempBasalPumpEvent {
  213. // 2016-05-30 01:21:00 +0000
  214. let tempEventBasal = TempBasalPumpEvent(availableData: Data(hexadecimalString:"338c4055145d1000")!, pumpModel: pumpModel)!
  215. return tempEventBasal
  216. }
  217. func createBolusEvent2009() -> BolusNormalPumpEvent {
  218. let dateComponents = DateComponents(calendar: Calendar.current, timeZone: pumpState.timeZone, year: 2009, month: 7, day: 31, hour: 9, minute: 0, second: 0)
  219. let timeInterval: TimeInterval = TimeInterval(minutes: 2)
  220. let data = Data(hexadecimalString:"338c4055145d2000")!
  221. return BolusNormalPumpEvent(length: 13, rawData: data, timestamp: dateComponents, unabsorbedInsulinRecord: nil, amount: 2.0, programmed: 1.0, unabsorbedInsulinTotal: 0.0, type: .normal, duration: timeInterval, wasRemotelyTriggered: false)
  222. }
  223. func createNonDelayedEvent2009() -> BolusReminderPumpEvent {
  224. let dateComponents = DateComponents(calendar: Calendar.current, timeZone: pumpState.timeZone, year: 2009, month: 7, day: 31, hour: 9, minute: 0, second: 0)
  225. let data = Data(hexadecimalString:"338c48FFF45d2000")!
  226. let length = 7
  227. return BolusReminderPumpEvent(length: length, rawData: data, timestamp: dateComponents)
  228. }
  229. }
  230. // from comment at https://gist.github.com/szhernovoy/276e69eb90a0de84dd90
  231. func randomDataString(length:Int) -> String {
  232. let charSet = "abcdef0123456789"
  233. let c = charSet.map { String($0) }
  234. var s:String = ""
  235. for _ in 0..<length {
  236. s.append(c[Int(arc4random()) % c.count])
  237. }
  238. return s
  239. }
  240. class PumpMessageSenderStub: PumpMessageSender {
  241. func getRileyLinkStatistics() throws -> RileyLinkStatistics {
  242. throw PumpOpsError.noResponse(during: "Tests")
  243. }
  244. func sendAndListen(_ data: Data, repeatCount: Int, timeout: TimeInterval, retryCount: Int) throws -> RFPacket {
  245. guard let decoded = MinimedPacket(encodedData: data),
  246. let messageType = MessageType(rawValue: decoded.data[4])
  247. else {
  248. throw PumpOpsError.noResponse(during: "Tests")
  249. }
  250. let response: PumpMessage
  251. if let responseArray = responses[messageType] {
  252. let numberOfResponsesReceived: Int
  253. if let someValue = responsesHaveOccured[messageType] {
  254. numberOfResponsesReceived = someValue
  255. } else {
  256. numberOfResponsesReceived = 0
  257. }
  258. let nextNumberOfResponsesReceived = numberOfResponsesReceived + 1
  259. responsesHaveOccured[messageType] = nextNumberOfResponsesReceived
  260. if numberOfResponsesReceived >= responseArray.count {
  261. XCTFail()
  262. }
  263. response = responseArray[numberOfResponsesReceived]
  264. } else {
  265. response = PumpMessage(rxData: Data())!
  266. }
  267. var encoded = MinimedPacket(outgoingData: response.txData).encodedData()
  268. encoded.insert(contentsOf: [0, 0], at: 0)
  269. guard let rfPacket = RFPacket(rfspyResponse: encoded) else {
  270. throw PumpOpsError.noResponse(during: data)
  271. }
  272. return rfPacket
  273. }
  274. func listen(onChannel channel: Int, timeout: TimeInterval) throws -> RFPacket? {
  275. throw PumpOpsError.noResponse(during: "Tests")
  276. }
  277. func send(_ data: Data, onChannel channel: Int, timeout: TimeInterval) throws {
  278. // Do nothing
  279. }
  280. func updateRegister(_ address: CC111XRegister, value: UInt8) throws {
  281. throw PumpOpsError.noResponse(during: "Tests")
  282. }
  283. func resetRadioConfig() throws {
  284. throw PumpOpsError.noResponse(during: "Tests")
  285. }
  286. func setBaseFrequency(_ frequency: Measurement<UnitFrequency>) throws {
  287. throw PumpOpsError.noResponse(during: "Tests")
  288. }
  289. func setCCLEDMode(_ mode: RileyLinkLEDMode) throws {
  290. throw PumpOpsError.noResponse(during: "Tests")
  291. }
  292. var responses = [MessageType: [PumpMessage]]()
  293. // internal tracking of how many times a response type has been received
  294. private var responsesHaveOccured = [MessageType: Int]()
  295. }
  296. extension PumpMessageSenderStub: PumpOpsSessionDelegate {
  297. func pumpOpsSession(_ session: PumpOpsSession, didChange state: PumpState) {
  298. }
  299. func pumpOpsSessionDidChangeRadioConfig(_ session: PumpOpsSession) {
  300. }
  301. }
  302. func array(_ timestampedEvents: [TimestampedHistoryEvent], containsPumpEvent pumpEvent: PumpEvent) -> Bool {
  303. let event = timestampedEvents.first { $0.pumpEvent.rawData == pumpEvent.rawData }
  304. return event != nil
  305. }
  306. func assertArray(_ timestampedEvents: [TimestampedHistoryEvent], containsPumpEvent pumpEvent: PumpEvent) {
  307. XCTAssertNotNil(timestampedEvents.first { $0.pumpEvent.rawData == pumpEvent.rawData})
  308. }
  309. func assertArray(_ timestampedEvents: [TimestampedHistoryEvent], containsPumpEvents pumpEvents: [PumpEvent]) {
  310. pumpEvents.forEach { assertArray(timestampedEvents, containsPumpEvent: $0) }
  311. }
  312. func assertArray(_ timestampedEvents: [TimestampedHistoryEvent], doesntContainPumpEvent pumpEvent: PumpEvent) {
  313. XCTAssertNil(timestampedEvents.first { $0.pumpEvent.rawData == pumpEvent.rawData })
  314. }
  315. // from http://jernejstrasner.com/2015/07/08/testing-throwable-methods-in-swift-2.html - transferred to Swift 3
  316. func assertThrows<T>(_ expression: @autoclosure () throws -> T, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
  317. do {
  318. let _ = try expression()
  319. XCTFail("No error to catch! - \(message)", file: file, line: line)
  320. } catch {
  321. }
  322. }
  323. func assertNoThrow<T>(_ expression: @autoclosure () throws -> T, _ message: String = "", file: StaticString = #file, line: UInt = #line) {
  324. do {
  325. let _ = try expression()
  326. } catch let error {
  327. XCTFail("Caught error: \(error) - \(message)", file: file, line: line)
  328. }
  329. }
  330. extension DateComponents {
  331. func addingTimeInterval(_ timeInterval: TimeInterval) -> DateComponents {
  332. let newDate = self.date!.addingTimeInterval(timeInterval)
  333. let newDateComponents = Calendar.current.dateComponents(in: TimeZone.currentFixed, from: newDate)
  334. return newDateComponents
  335. }
  336. }