Schema v3: transcription attempts accumulate on AudioInfo (text, locale, confidence, forced flag) — latest attempt is displayed, payload.text is purely user writing, v2 notes migrate at read time. Editor shows the transcript in its own live-updating section with insert-into-note; search matches transcripts weighted by confidence. Watch recordings are now deleted only on the phone's durable-ingest ack (transferUserInfo), closing every phone-side loss window; failed sidecar writes unstage instead of orphaning, and unreadable inbox recordings are surfaced in logs. Claude-Session: https://claude.ai/code/session_014esDWi42URLEC6Cj17hGQ3
100 lines
3.6 KiB
Swift
100 lines
3.6 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import Notes
|
|
|
|
struct AudioNotePayloadTests {
|
|
@Test func metadataRoundTrips() throws {
|
|
let payload = AudioNotePayload(
|
|
id: "01J8Z3M9K7QW2R4T6Y8B0C1D2E",
|
|
recordedAt: Date(timeIntervalSince1970: 1_700_000_000),
|
|
duration: 12.5,
|
|
timeZoneID: "Europe/Berlin"
|
|
)
|
|
let decoded = try #require(AudioNotePayload(metadata: payload.metadata()))
|
|
#expect(decoded == payload)
|
|
}
|
|
|
|
@Test func metadataRoundTripsWithoutTimeZone() throws {
|
|
let payload = AudioNotePayload(
|
|
id: "01J8Z3M9K7QW2R4T6Y8B0C1D2E",
|
|
recordedAt: Date(timeIntervalSince1970: 1_700_000_100),
|
|
duration: 4,
|
|
timeZoneID: nil
|
|
)
|
|
let dict = payload.metadata()
|
|
#expect(dict["tz"] == nil)
|
|
let decoded = try #require(AudioNotePayload(metadata: dict))
|
|
#expect(decoded == payload)
|
|
}
|
|
|
|
/// A survivable trip through actual plist serialization — the metadata really
|
|
/// crosses WatchConnectivity as a plist dictionary.
|
|
@Test func metadataSurvivesPlistSerialization() throws {
|
|
let payload = AudioNotePayload(
|
|
id: "01J8Z3M9K7QW2R4T6Y8B0C1D2E",
|
|
recordedAt: Date(timeIntervalSince1970: 1_700_000_050),
|
|
duration: 7.25,
|
|
timeZoneID: "America/Los_Angeles"
|
|
)
|
|
let data = try PropertyListSerialization.data(
|
|
fromPropertyList: payload.metadata(), format: .binary, options: 0)
|
|
let dict = try #require(
|
|
try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any])
|
|
let decoded = try #require(AudioNotePayload(metadata: dict))
|
|
#expect(decoded.id == payload.id)
|
|
#expect(decoded.duration == payload.duration)
|
|
#expect(decoded.timeZoneID == payload.timeZoneID)
|
|
#expect(abs(decoded.recordedAt.timeIntervalSince(payload.recordedAt)) < 0.001)
|
|
}
|
|
|
|
@Test func decodeRejectsUnknownNewerVersion() {
|
|
var dict = AudioNotePayload(
|
|
id: "01J8Z3M9K7QW2R4T6Y8B0C1D2E",
|
|
recordedAt: Date(),
|
|
duration: 3,
|
|
timeZoneID: nil
|
|
).metadata()
|
|
dict["v"] = AudioNotePayload.currentVersion + 1
|
|
#expect(AudioNotePayload(metadata: dict) == nil)
|
|
}
|
|
|
|
@Test func decodeRejectsMissingVersion() {
|
|
let dict: [String: Any] = [
|
|
"id": "01J8Z3M9K7QW2R4T6Y8B0C1D2E",
|
|
"recordedAt": Date(),
|
|
"duration": 3.0,
|
|
]
|
|
#expect(AudioNotePayload(metadata: dict) == nil)
|
|
}
|
|
|
|
@Test func decodeRejectsMissingRequiredField() {
|
|
// Version present, but no duration.
|
|
let dict: [String: Any] = [
|
|
"v": AudioNotePayload.currentVersion,
|
|
"id": "01J8Z3M9K7QW2R4T6Y8B0C1D2E",
|
|
"recordedAt": Date(),
|
|
]
|
|
#expect(AudioNotePayload(metadata: dict) == nil)
|
|
}
|
|
|
|
@Test func decodeRejectsEmptyMetadata() {
|
|
#expect(AudioNotePayload(metadata: [:]) == nil)
|
|
}
|
|
|
|
// MARK: - IngestAck
|
|
|
|
@Test func ackRoundTrips() {
|
|
let ack = IngestAck(id: "01J8Z3M9K7QW2R4T6Y8B0C1D2E")
|
|
let decoded = IngestAck(userInfo: ack.userInfo())
|
|
#expect(decoded == ack)
|
|
}
|
|
|
|
@Test func ackRejectsNewerVersionAndForeignUserInfo() {
|
|
var dict = IngestAck(id: "01J8Z3M9K7QW2R4T6Y8B0C1D2E").userInfo()
|
|
dict["ackV"] = IngestAck.currentVersion + 1
|
|
#expect(IngestAck(userInfo: dict) == nil)
|
|
#expect(IngestAck(userInfo: [:]) == nil)
|
|
#expect(IngestAck(userInfo: ["unrelated": true]) == nil)
|
|
}
|
|
}
|