HealthProbe / HealthProbeTests / CoreDataArchiveCacheStoreTests.swift
Newer Older
125 lines | 5.977kb
Bogdan Timofte authored 2 weeks ago
1
import CoreData
2
import HealthKit
3
import XCTest
4
@testable import HealthProbe
5

            
6
final class CoreDataArchiveCacheStoreTests: XCTestCase {
7
    private var temporaryDirectory: URL!
8

            
9
    override func setUpWithError() throws {
10
        temporaryDirectory = FileManager.default.temporaryDirectory
11
            .appending(path: "HealthProbeCacheTests-\(UUID().uuidString)", directoryHint: .isDirectory)
12
        try FileManager.default.createDirectory(at: temporaryDirectory, withIntermediateDirectories: true)
13
    }
14

            
15
    override func tearDownWithError() throws {
16
        if let temporaryDirectory {
17
            try? FileManager.default.removeItem(at: temporaryDirectory)
18
        }
19
        temporaryDirectory = nil
20
    }
21

            
22
    func testRebuildCreatesCoreDataRowsFromSQLiteArchive() async throws {
23
        let archiveURL = temporaryDirectory.appending(path: "Archive.sqlite")
24
        let archive = SQLiteHealthArchiveStore(databaseURL: archiveURL)
25
        let firstSample = makeStepCountSample(value: 42, start: 1_000)
26
        let secondSample = makeStepCountSample(value: 7, start: 2_000)
27

            
28
        _ = try await archive.upsertSamples([firstSample], observedAt: Date(timeIntervalSince1970: 3_000))
29
        _ = try await archive.upsertSamples([firstSample, secondSample], observedAt: Date(timeIntervalSince1970: 3_060))
30
        try await archive.recordDisappearance(
31
            sampleUUIDHash: HashService.sampleUUIDHash(firstSample.uuid.uuidString),
32
            sampleTypeIdentifier: HKQuantityTypeIdentifier.stepCount.rawValue,
33
            observedMissingAt: Date(timeIntervalSince1970: 3_120)
34
        )
35
        try await archive.markVerification(
36
            sampleType: firstSample.sampleType,
37
            verifiedAt: Date(timeIntervalSince1970: 3_180)
38
        )
39

            
40
        let cache = try CoreDataArchiveCacheStore(inMemory: true)
41
        let summary = try cache.rebuild(fromArchiveAt: archiveURL)
42
        let context = cache.container.viewContext
43

            
44
        XCTAssertEqual(summary.observationRows, 4)
45
        XCTAssertEqual(summary.typeSummaryRows, 4)
46
        XCTAssertGreaterThanOrEqual(summary.dailyAggregateRows, 1)
47
        XCTAssertEqual(summary.archiveHealthRows, 1)
48
        XCTAssertEqual(try count("CachedObservationRow", in: context), 4)
49
        XCTAssertEqual(try count("CachedTypeSummary", in: context), 4)
50
        XCTAssertEqual(try count("CachedArchiveHealth", in: context), 1)
51

            
52
        let latestObservation = try fetchFirst(
53
            "CachedObservationRow",
54
            predicate: NSPredicate(format: "observationID == %d", 4),
55
            in: context
56
        )
57
        XCTAssertEqual(latestObservation?.value(forKey: "visibleRecordCount") as? Int64, 1)
58
        XCTAssertEqual(latestObservation?.value(forKey: "cacheSchemaVersion") as? Int64, Int64(CoreDataArchiveCacheStore.cacheSchemaVersion))
Bogdan Timofte authored 2 weeks ago
59

            
60
        let latestRow = try XCTUnwrap(cache.latestObservationRow())
61
        XCTAssertEqual(latestRow.observationID, 4)
62
        XCTAssertEqual(latestRow.visibleRecordCount, 1)
63
        XCTAssertEqual(latestRow.cacheSchemaVersion, CoreDataArchiveCacheStore.cacheSchemaVersion)
64

            
65
        let summaries = try cache.typeSummaries(observationID: latestRow.observationID)
66
        XCTAssertEqual(summaries.count, 1)
67
        XCTAssertEqual(summaries.first?.sampleTypeIdentifier, HKQuantityTypeIdentifier.stepCount.rawValue)
68

            
Bogdan Timofte authored 2 weeks ago
69
        let diff = try XCTUnwrap(cache.diffSummary(
70
            fromObservationID: 1,
71
            toObservationID: 2,
72
            sampleTypeIdentifier: HKQuantityTypeIdentifier.stepCount.rawValue
73
        ))
74
        XCTAssertEqual(diff.appearedCount, 1)
75
        XCTAssertEqual(diff.disappearedCount, 0)
76
        XCTAssertEqual(diff.representationChangedCount, 0)
77

            
Bogdan Timofte authored 2 weeks ago
78
        let health = try XCTUnwrap(cache.latestArchiveHealthStatus())
79
        XCTAssertEqual(health.archiveSchemaVersion, 2)
80
        XCTAssertEqual(health.lastIntegrityStatus, "ok")
Bogdan Timofte authored 2 weeks ago
81
    }
82

            
83
    func testDeletingCacheDoesNotDeleteSQLiteArchiveAndRebuildRestoresRows() async throws {
84
        let archiveURL = temporaryDirectory.appending(path: "Archive.sqlite")
85
        let archive = SQLiteHealthArchiveStore(databaseURL: archiveURL)
86
        let sample = makeStepCountSample(value: 10, start: 1_000)
87
        _ = try await archive.upsertSamples([sample], observedAt: Date(timeIntervalSince1970: 2_000))
88

            
89
        let cache = try CoreDataArchiveCacheStore(inMemory: true)
90
        _ = try cache.rebuild(fromArchiveAt: archiveURL)
91
        try cache.deleteCache()
92

            
93
        XCTAssertEqual(try count("CachedObservationRow", in: cache.container.viewContext), 0)
94
        let integrityReport = try await archive.checkIntegrity()
95
        XCTAssertTrue(integrityReport.passed)
96

            
97
        let rebuilt = try cache.rebuild(fromArchiveAt: archiveURL)
98
        XCTAssertEqual(rebuilt.observationRows, 1)
99
        XCTAssertEqual(try count("CachedObservationRow", in: cache.container.viewContext), 1)
100
    }
101

            
102
    private func makeStepCountSample(value: Double, start: TimeInterval, end: TimeInterval? = nil) -> HKQuantitySample {
103
        let quantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
104
        let quantity = HKQuantity(unit: .count(), doubleValue: value)
105
        let startDate = Date(timeIntervalSince1970: start)
106
        let endDate = Date(timeIntervalSince1970: end ?? (start + 300))
107
        return HKQuantitySample(type: quantityType, quantity: quantity, start: startDate, end: endDate)
108
    }
109

            
110
    private func count(_ entityName: String, in context: NSManagedObjectContext) throws -> Int {
111
        let request = NSFetchRequest<NSManagedObject>(entityName: entityName)
112
        return try context.count(for: request)
113
    }
114

            
115
    private func fetchFirst(
116
        _ entityName: String,
117
        predicate: NSPredicate,
118
        in context: NSManagedObjectContext
119
    ) throws -> NSManagedObject? {
120
        let request = NSFetchRequest<NSManagedObject>(entityName: entityName)
121
        request.predicate = predicate
122
        request.fetchLimit = 1
123
        return try context.fetch(request).first
124
    }
125
}