DosingDecisionStoreTests.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. //
  2. // DosingDecisionStoreTests.swift
  3. // LoopKitTests
  4. //
  5. // Created by Darin Krauss on 1/6/20.
  6. // Copyright © 2020 LoopKit Authors. All rights reserved.
  7. //
  8. import XCTest
  9. @testable import LoopKit
  10. class DosingDecisionStorePersistenceTests: PersistenceControllerTestCase, DosingDecisionStoreDelegate {
  11. var dosingDecisionStore: DosingDecisionStore!
  12. override func setUp() {
  13. super.setUp()
  14. dosingDecisionStoreHasUpdatedDosingDecisionDataHandler = nil
  15. dosingDecisionStore = DosingDecisionStore(store: cacheStore, expireAfter: .hours(1))
  16. dosingDecisionStore.delegate = self
  17. }
  18. override func tearDown() {
  19. dosingDecisionStore.delegate = nil
  20. dosingDecisionStore = nil
  21. dosingDecisionStoreHasUpdatedDosingDecisionDataHandler = nil
  22. super.tearDown()
  23. }
  24. // MARK: - DosingDecisionStoreDelegate
  25. var dosingDecisionStoreHasUpdatedDosingDecisionDataHandler: ((_ : DosingDecisionStore) -> Void)?
  26. func dosingDecisionStoreHasUpdatedDosingDecisionData(_ dosingDecisionStore: DosingDecisionStore) {
  27. dosingDecisionStoreHasUpdatedDosingDecisionDataHandler?(dosingDecisionStore)
  28. }
  29. // MARK: -
  30. func testStoreDosingDecisionData() {
  31. let storeDosingDecisionDataHandler = expectation(description: "Store dosing decision data handler")
  32. let storeDosingDecisionDataCompletion = expectation(description: "Store dosing decision data completion")
  33. var handlerInvocation = 0
  34. dosingDecisionStoreHasUpdatedDosingDecisionDataHandler = { dosingDecisionStore in
  35. handlerInvocation += 1
  36. switch handlerInvocation {
  37. case 1:
  38. storeDosingDecisionDataHandler.fulfill()
  39. default:
  40. XCTFail("Unexpected handler invocation")
  41. }
  42. }
  43. dosingDecisionStore.storeDosingDecisionData(StoredDosingDecisionData()) {
  44. storeDosingDecisionDataCompletion.fulfill()
  45. }
  46. wait(for: [storeDosingDecisionDataHandler, storeDosingDecisionDataCompletion], timeout: 2, enforceOrder: true)
  47. }
  48. func testStoreDosingDecisionDataMultiple() {
  49. let storeDosingDecisionDataHandler1 = expectation(description: "Store dosing decision data handler 1")
  50. let storeDosingDecisionDataHandler2 = expectation(description: "Store dosing decision data handler 2")
  51. let storeDosingDecisionDataCompletion1 = expectation(description: "Store dosing decision data completion 1")
  52. let storeDosingDecisionDataCompletion2 = expectation(description: "Store dosing decision data completion 2")
  53. var handlerInvocation = 0
  54. dosingDecisionStoreHasUpdatedDosingDecisionDataHandler = { dosingDecisionStore in
  55. handlerInvocation += 1
  56. switch handlerInvocation {
  57. case 1:
  58. storeDosingDecisionDataHandler1.fulfill()
  59. case 2:
  60. storeDosingDecisionDataHandler2.fulfill()
  61. default:
  62. XCTFail("Unexpected handler invocation")
  63. }
  64. }
  65. dosingDecisionStore.storeDosingDecisionData(StoredDosingDecisionData()) {
  66. storeDosingDecisionDataCompletion1.fulfill()
  67. }
  68. dosingDecisionStore.storeDosingDecisionData(StoredDosingDecisionData()) {
  69. storeDosingDecisionDataCompletion2.fulfill()
  70. }
  71. wait(for: [storeDosingDecisionDataHandler1, storeDosingDecisionDataCompletion1, storeDosingDecisionDataHandler2, storeDosingDecisionDataCompletion2], timeout: 2, enforceOrder: true)
  72. }
  73. }
  74. class DosingDecisionStoreQueryAnchorTests: XCTestCase {
  75. var rawValue: DosingDecisionStore.QueryAnchor.RawValue = [
  76. "modificationCounter": Int64(123)
  77. ]
  78. func testInitializerDefault() {
  79. let queryAnchor = DosingDecisionStore.QueryAnchor()
  80. XCTAssertEqual(queryAnchor.modificationCounter, 0)
  81. }
  82. func testInitializerRawValue() {
  83. let queryAnchor = DosingDecisionStore.QueryAnchor(rawValue: rawValue)
  84. XCTAssertNotNil(queryAnchor)
  85. XCTAssertEqual(queryAnchor?.modificationCounter, 123)
  86. }
  87. func testInitializerRawValueMissingModificationCounter() {
  88. rawValue["modificationCounter"] = nil
  89. XCTAssertNil(DosingDecisionStore.QueryAnchor(rawValue: rawValue))
  90. }
  91. func testInitializerRawValueInvalidModificationCounter() {
  92. rawValue["modificationCounter"] = "123"
  93. XCTAssertNil(DosingDecisionStore.QueryAnchor(rawValue: rawValue))
  94. }
  95. func testRawValueWithDefault() {
  96. let rawValue = DosingDecisionStore.QueryAnchor().rawValue
  97. XCTAssertEqual(rawValue.count, 1)
  98. XCTAssertEqual(rawValue["modificationCounter"] as? Int64, Int64(0))
  99. }
  100. func testRawValueWithNonDefault() {
  101. var queryAnchor = DosingDecisionStore.QueryAnchor()
  102. queryAnchor.modificationCounter = 123
  103. let rawValue = queryAnchor.rawValue
  104. XCTAssertEqual(rawValue.count, 1)
  105. XCTAssertEqual(rawValue["modificationCounter"] as? Int64, Int64(123))
  106. }
  107. }
  108. class DosingDecisionStoreQueryTests: PersistenceControllerTestCase {
  109. var dosingDecisionStore: DosingDecisionStore!
  110. var completion: XCTestExpectation!
  111. var queryAnchor: DosingDecisionStore.QueryAnchor!
  112. var limit: Int!
  113. override func setUp() {
  114. super.setUp()
  115. dosingDecisionStore = DosingDecisionStore(store: cacheStore, expireAfter: .hours(1))
  116. completion = expectation(description: "Completion")
  117. queryAnchor = DosingDecisionStore.QueryAnchor()
  118. limit = Int.max
  119. }
  120. override func tearDown() {
  121. limit = nil
  122. queryAnchor = nil
  123. completion = nil
  124. dosingDecisionStore = nil
  125. super.tearDown()
  126. }
  127. // MARK: -
  128. func testEmptyWithDefaultQueryAnchor() {
  129. dosingDecisionStore.executeDosingDecisionDataQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  130. switch result {
  131. case .failure(let error):
  132. XCTFail("Unexpected failure: \(error)")
  133. case .success(let anchor, let data):
  134. XCTAssertEqual(anchor.modificationCounter, 0)
  135. XCTAssertEqual(data.count, 0)
  136. }
  137. self.completion.fulfill()
  138. }
  139. wait(for: [completion], timeout: 2, enforceOrder: true)
  140. }
  141. func testEmptyWithMissingQueryAnchor() {
  142. queryAnchor = nil
  143. dosingDecisionStore.executeDosingDecisionDataQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  144. switch result {
  145. case .failure(let error):
  146. XCTFail("Unexpected failure: \(error)")
  147. case .success(let anchor, let data):
  148. XCTAssertEqual(anchor.modificationCounter, 0)
  149. XCTAssertEqual(data.count, 0)
  150. }
  151. self.completion.fulfill()
  152. }
  153. wait(for: [completion], timeout: 2, enforceOrder: true)
  154. }
  155. func testEmptyWithNonDefaultQueryAnchor() {
  156. queryAnchor.modificationCounter = 1
  157. dosingDecisionStore.executeDosingDecisionDataQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  158. switch result {
  159. case .failure(let error):
  160. XCTFail("Unexpected failure: \(error)")
  161. case .success(let anchor, let data):
  162. XCTAssertEqual(anchor.modificationCounter, 1)
  163. XCTAssertEqual(data.count, 0)
  164. }
  165. self.completion.fulfill()
  166. }
  167. wait(for: [completion], timeout: 2, enforceOrder: true)
  168. }
  169. func testDataWithUnusedQueryAnchor() {
  170. let dataStrings = [generateDataString(), generateDataString(), generateDataString()]
  171. addData(withDataStrings: dataStrings)
  172. dosingDecisionStore.executeDosingDecisionDataQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  173. switch result {
  174. case .failure(let error):
  175. XCTFail("Unexpected failure: \(error)")
  176. case .success(let anchor, let data):
  177. XCTAssertEqual(anchor.modificationCounter, 3)
  178. XCTAssertEqual(data.count, 3)
  179. for (index, dataString) in dataStrings.enumerated() {
  180. XCTAssertEqual(data[index].dataString, dataString)
  181. }
  182. }
  183. self.completion.fulfill()
  184. }
  185. wait(for: [completion], timeout: 2, enforceOrder: true)
  186. }
  187. func testDataWithStaleQueryAnchor() {
  188. let dataStrings = [generateDataString(), generateDataString(), generateDataString()]
  189. addData(withDataStrings: dataStrings)
  190. queryAnchor.modificationCounter = 2
  191. dosingDecisionStore.executeDosingDecisionDataQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  192. switch result {
  193. case .failure(let error):
  194. XCTFail("Unexpected failure: \(error)")
  195. case .success(let anchor, let data):
  196. XCTAssertEqual(anchor.modificationCounter, 3)
  197. XCTAssertEqual(data.count, 1)
  198. XCTAssertEqual(data[0].dataString, dataStrings[2])
  199. }
  200. self.completion.fulfill()
  201. }
  202. wait(for: [completion], timeout: 2, enforceOrder: true)
  203. }
  204. func testDataWithCurrentQueryAnchor() {
  205. let dataStrings = [generateDataString(), generateDataString(), generateDataString()]
  206. addData(withDataStrings: dataStrings)
  207. queryAnchor.modificationCounter = 3
  208. dosingDecisionStore.executeDosingDecisionDataQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  209. switch result {
  210. case .failure(let error):
  211. XCTFail("Unexpected failure: \(error)")
  212. case .success(let anchor, let data):
  213. XCTAssertEqual(anchor.modificationCounter, 3)
  214. XCTAssertEqual(data.count, 0)
  215. }
  216. self.completion.fulfill()
  217. }
  218. wait(for: [completion], timeout: 2, enforceOrder: true)
  219. }
  220. func testDataWithLimitZero() {
  221. let dataStrings = [generateDataString(), generateDataString(), generateDataString()]
  222. addData(withDataStrings: dataStrings)
  223. limit = 0
  224. dosingDecisionStore.executeDosingDecisionDataQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  225. switch result {
  226. case .failure(let error):
  227. XCTFail("Unexpected failure: \(error)")
  228. case .success(let anchor, let data):
  229. XCTAssertEqual(anchor.modificationCounter, 0)
  230. XCTAssertEqual(data.count, 0)
  231. }
  232. self.completion.fulfill()
  233. }
  234. wait(for: [completion], timeout: 2, enforceOrder: true)
  235. }
  236. func testDataWithLimitCoveredByData() {
  237. let dataStrings = [generateDataString(), generateDataString(), generateDataString()]
  238. addData(withDataStrings: dataStrings)
  239. limit = 2
  240. dosingDecisionStore.executeDosingDecisionDataQuery(fromQueryAnchor: queryAnchor, limit: limit) { result in
  241. switch result {
  242. case .failure(let error):
  243. XCTFail("Unexpected failure: \(error)")
  244. case .success(let anchor, let data):
  245. XCTAssertEqual(anchor.modificationCounter, 2)
  246. XCTAssertEqual(data.count, 2)
  247. XCTAssertEqual(data[0].dataString, dataStrings[0])
  248. XCTAssertEqual(data[1].dataString, dataStrings[1])
  249. }
  250. self.completion.fulfill()
  251. }
  252. wait(for: [completion], timeout: 2, enforceOrder: true)
  253. }
  254. private func addData(withDataStrings dataStrings: [String]) {
  255. let semaphore = DispatchSemaphore(value: 0)
  256. for dataString in dataStrings {
  257. self.dosingDecisionStore.storeDosingDecisionData(StoredDosingDecisionData(dataString: dataString)) { semaphore.signal() }
  258. }
  259. for _ in dataStrings { semaphore.wait() }
  260. }
  261. private func generateDataString() -> String {
  262. return UUID().uuidString
  263. }
  264. }
  265. extension StoredDosingDecisionData {
  266. init(date: Date = Date(), dataString: String = UUID().uuidString) {
  267. self.init(date: date, data: dataString.data(using: .utf8)!)
  268. }
  269. var dataString: String {
  270. return String(data: data, encoding: .utf8)!
  271. }
  272. }