HealthProbe / HealthProbeTests / CoreDataArchiveCacheStoreTests.swift
Newer Older
103 lines | 4.918kb
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))
59
    }
60

            
61
    func testDeletingCacheDoesNotDeleteSQLiteArchiveAndRebuildRestoresRows() async throws {
62
        let archiveURL = temporaryDirectory.appending(path: "Archive.sqlite")
63
        let archive = SQLiteHealthArchiveStore(databaseURL: archiveURL)
64
        let sample = makeStepCountSample(value: 10, start: 1_000)
65
        _ = try await archive.upsertSamples([sample], observedAt: Date(timeIntervalSince1970: 2_000))
66

            
67
        let cache = try CoreDataArchiveCacheStore(inMemory: true)
68
        _ = try cache.rebuild(fromArchiveAt: archiveURL)
69
        try cache.deleteCache()
70

            
71
        XCTAssertEqual(try count("CachedObservationRow", in: cache.container.viewContext), 0)
72
        let integrityReport = try await archive.checkIntegrity()
73
        XCTAssertTrue(integrityReport.passed)
74

            
75
        let rebuilt = try cache.rebuild(fromArchiveAt: archiveURL)
76
        XCTAssertEqual(rebuilt.observationRows, 1)
77
        XCTAssertEqual(try count("CachedObservationRow", in: cache.container.viewContext), 1)
78
    }
79

            
80
    private func makeStepCountSample(value: Double, start: TimeInterval, end: TimeInterval? = nil) -> HKQuantitySample {
81
        let quantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
82
        let quantity = HKQuantity(unit: .count(), doubleValue: value)
83
        let startDate = Date(timeIntervalSince1970: start)
84
        let endDate = Date(timeIntervalSince1970: end ?? (start + 300))
85
        return HKQuantitySample(type: quantityType, quantity: quantity, start: startDate, end: endDate)
86
    }
87

            
88
    private func count(_ entityName: String, in context: NSManagedObjectContext) throws -> Int {
89
        let request = NSFetchRequest<NSManagedObject>(entityName: entityName)
90
        return try context.count(for: request)
91
    }
92

            
93
    private func fetchFirst(
94
        _ entityName: String,
95
        predicate: NSPredicate,
96
        in context: NSManagedObjectContext
97
    ) throws -> NSManagedObject? {
98
        let request = NSFetchRequest<NSManagedObject>(entityName: entityName)
99
        request.predicate = predicate
100
        request.fetchLimit = 1
101
        return try context.fetch(request).first
102
    }
103
}