53 lines
1.9 KiB
Swift
53 lines
1.9 KiB
Swift
import XCTest
|
|
@testable import HaptikaSignal
|
|
|
|
final class PPGCoherencePolicyTests: XCTestCase {
|
|
func testSessionDurationLimitsRemainPurposeSpecific() {
|
|
XCTAssertEqual(PPGCoherencePolicy.maximumDurationSeconds(isCoherence: false), 120)
|
|
XCTAssertEqual(PPGCoherencePolicy.maximumDurationSeconds(isCoherence: true), 300)
|
|
XCTAssertEqual(PPGCoherencePolicy.coherenceBufferDurationSeconds, 75)
|
|
}
|
|
|
|
func testLiveTrackerPublishesEveryNewBeatOnlyOnce() {
|
|
var tracker = PPGLiveBeatTracker()
|
|
let first = tracker.ingest([
|
|
interval(end: 1, duration: 800), interval(end: 1.8, duration: 800),
|
|
])
|
|
XCTAssertEqual(first.map(\.sequence), [1, 2])
|
|
let overlapping = tracker.ingest([
|
|
interval(end: 1.82, duration: 800), interval(end: 2.6, duration: 780),
|
|
])
|
|
XCTAssertEqual(overlapping.map(\.sequence), [3])
|
|
XCTAssertEqual(overlapping.first?.timestampSeconds, 2.6)
|
|
}
|
|
|
|
func testTrackerDoesNotMoveBackwardAndResetsCleanly() {
|
|
var tracker = PPGLiveBeatTracker()
|
|
_ = tracker.ingest([interval(end: 10, duration: 700)])
|
|
XCTAssertTrue(tracker.ingest([interval(end: 9.9, duration: 700)]).isEmpty)
|
|
tracker.reset()
|
|
XCTAssertEqual(tracker.ingest([interval(end: 1, duration: 700)]).first?.sequence, 1)
|
|
}
|
|
|
|
func testTrackerRejectsInvalidAndRejectedIntervals() {
|
|
var tracker = PPGLiveBeatTracker()
|
|
let rejected = PulseInterval(
|
|
sourceIndex: 0, startSeconds: 0, endSeconds: 1,
|
|
durationMilliseconds: 800, rejectionReason: .localOutlier, qualityScore: 1
|
|
)
|
|
XCTAssertTrue(tracker.ingest([
|
|
rejected, interval(end: .nan, duration: 800), interval(end: 2, duration: 2_500),
|
|
]).isEmpty)
|
|
}
|
|
|
|
private func interval(end: Double, duration: Double) -> PulseInterval {
|
|
PulseInterval(
|
|
sourceIndex: 0,
|
|
startSeconds: end - duration / 1_000,
|
|
endSeconds: end,
|
|
durationMilliseconds: duration,
|
|
qualityScore: 0.9
|
|
)
|
|
}
|
|
}
|