Files
2026-07-31 16:20:10 +02:00

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
)
}
}