macsync/@packages/icalls/Tests/ICallsSyncTests/CallRecordTests.swift
Natalie 576496ca3e feat(deploy): video-projects FUSE mount over DO Spaces
Generalize the photos-originals rclone-mount pattern to a video-projects
prefix so the video studio (and imajin ETL, per storage-portability-plan
§2.3) can read/write multi-GB project sources/renders as local files while
only hot data stays resident on plum (bounded VFS LRU cache). Lets a
small-disk laptop work with large footage without filling APFS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:10:13 -04:00

168 lines
7.3 KiB
Swift

import Foundation
import GRDB
import Testing
@testable import ICallsSync
@Suite("ICallsSync CallRecord and stats")
struct CallRecordTests {
@Test("SyncCallPayload round-trips basic fields")
func payloadBasics() {
let rec = SyncCallPayload(
uniqueId: "zpk:42",
address: "+14155551234",
normalizedAddress: "+14155551234",
contactName: "Alice",
direction: "incoming",
callType: "telephony",
answered: true,
durationSeconds: 127.5,
startedAt: "2026-06-01T12:00:00Z",
serviceProvider: "com.apple.Telephony"
)
#expect(rec.uniqueId == "zpk:42")
#expect(rec.direction == "incoming")
#expect(rec.answered == true)
#expect(rec.durationSeconds == 127.5)
}
@Test("ICallsSyncStats and error defaults")
func statsAndErrors() {
var stats = ICallsSyncStats()
#expect(stats.callCount == 0)
stats.callCount = 17
stats.syncedThisSession = 5
#expect(stats.callCount == 17)
let err = ICallsSyncError.databaseNotFound
#expect(err.message == "Call history database not found")
#expect(ICallsSyncError.fullDiskAccessRequired.message == "Full Disk Access required (Call History)")
#expect(ICallsSyncError.none.message == "")
}
}
@Suite("ICallsSync call-type classification")
struct CallTypeClassificationTests {
@Test("FaceTime audio is ZCALLTYPE 16, video otherwise")
func faceTime() {
#expect(CallHistoryReader.classifyCallType(serviceProvider: "com.apple.FaceTime", callTypeRaw: 16) == "facetime_audio")
#expect(CallHistoryReader.classifyCallType(serviceProvider: "com.apple.FaceTime", callTypeRaw: 1) == "facetime_video")
#expect(CallHistoryReader.classifyCallType(serviceProvider: "com.apple.FaceTime", callTypeRaw: 0) == "facetime_video")
}
@Test("Cellular maps to telephony")
func telephony() {
#expect(CallHistoryReader.classifyCallType(serviceProvider: "com.apple.Telephony", callTypeRaw: 0) == "telephony")
#expect(CallHistoryReader.classifyCallType(serviceProvider: nil, callTypeRaw: 1) == "telephony")
}
@Test("Unknown provider with no telephony hint is unknown")
func unknown() {
#expect(CallHistoryReader.classifyCallType(serviceProvider: nil, callTypeRaw: 0) == "unknown")
#expect(CallHistoryReader.classifyCallType(serviceProvider: "com.example.weird", callTypeRaw: 7) == "unknown")
}
}
@Suite("ICallsSync row parsing")
struct CallRecordParsingTests {
@Test("ZORIGINATED + ZANSWERED map to direction/answered")
func directionAndAnswered() {
let outgoing = CallHistoryReader.parse(
row: Row(["Z_PK": 1, "ZUNIQUE_ID": "u1", "ZADDRESS": "+14155550000",
"ZORIGINATED": 1, "ZANSWERED": 1, "ZDURATION": 30.0, "ZDATE": 700_000_000.0]),
contactLookup: { _ in nil })
#expect(outgoing.direction == "outgoing")
#expect(outgoing.answered == true)
#expect(outgoing.uniqueId == "u1")
let missedIncoming = CallHistoryReader.parse(
row: Row(["Z_PK": 2, "ZORIGINATED": 0, "ZANSWERED": 0, "ZDATE": 700_000_100.0]),
contactLookup: { _ in nil })
#expect(missedIncoming.direction == "incoming")
#expect(missedIncoming.answered == false)
}
@Test("Empty ZUNIQUE_ID falls back to synthetic zpk id")
func syntheticUniqueId() {
let rec = CallHistoryReader.parse(
row: Row(["Z_PK": 99, "ZUNIQUE_ID": "", "ZDATE": 700_000_000.0]),
contactLookup: { _ in nil })
#expect(rec.uniqueId == "zpk:99")
}
@Test("Contact lookup fills name when ZNAME is absent")
func contactEnrichment() {
let rec = CallHistoryReader.parse(
row: Row(["Z_PK": 3, "ZADDRESS": "+14155551234", "ZDATE": 700_000_000.0]),
contactLookup: { $0 == "+14155551234" ? "Bob" : nil })
#expect(rec.contactName == "Bob")
}
}
@Suite("ICallsSync reader snapshot (WAL visibility)")
struct CallHistorySnapshotTests {
/// Build a ZCALLRECORD fixture in WAL mode with autocheckpoint disabled, so
/// inserted rows live in the `-wal` file. The writer queue is kept alive by
/// the caller so the WAL is not flushed before the reader snapshots it
/// this is exactly the stale-read condition the snapshot fix exists to beat.
private func makeFixture(at path: String) throws -> DatabaseQueue {
let q = try DatabaseQueue(path: path)
try q.writeWithoutTransaction { db in
_ = try String.fetchOne(db, sql: "PRAGMA journal_mode = WAL")
try db.execute(sql: "PRAGMA wal_autocheckpoint = 0")
}
try q.write { db in
try db.execute(sql: """
CREATE TABLE ZCALLRECORD (
Z_PK INTEGER PRIMARY KEY,
ZUNIQUE_ID TEXT, ZADDRESS TEXT, ZNAME TEXT,
ZORIGINATED INTEGER, ZANSWERED INTEGER, ZDURATION REAL,
ZDATE REAL, ZCALLTYPE INTEGER, ZSERVICE_PROVIDER TEXT
)
""")
try db.execute(sql: """
INSERT INTO ZCALLRECORD
(Z_PK, ZUNIQUE_ID, ZADDRESS, ZORIGINATED, ZANSWERED, ZDURATION, ZDATE, ZCALLTYPE, ZSERVICE_PROVIDER)
VALUES
(1, 'older', '+14155550001', 0, 1, 12.0, 700000000.0, 0, 'com.apple.Telephony'),
(2, 'newer', '+14155550002', 1, 1, 34.0, 700000500.0, 16, 'com.apple.FaceTime')
""")
}
return q
}
@Test("Reader sees rows still sitting in the WAL")
func readsUnflushedWal() throws {
let dir = FileManager.default.temporaryDirectory
.appendingPathComponent("icalls-fixture-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: dir) }
let dbPath = dir.appendingPathComponent("CallHistory.storedata").path
let writer = try makeFixture(at: dbPath)
defer { try? writer.close() }
let calls = CallHistoryReader.shared.readCalls(dbPath: dbPath, since: nil)
#expect(calls.count == 2)
// Oldest-first (ORDER BY ZDATE ASC) so the server ingests in order.
#expect(calls.first?.uniqueId == "older")
#expect(calls.last?.callType == "facetime_audio")
}
@Test("since filters out rows at or before the watermark")
func sinceWatermark() throws {
let dir = FileManager.default.temporaryDirectory
.appendingPathComponent("icalls-fixture-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: dir) }
let dbPath = dir.appendingPathComponent("CallHistory.storedata").path
let writer = try makeFixture(at: dbPath)
defer { try? writer.close() }
// Between the two rows (700000000 < 700000250 < 700000500).
let watermark = Date(timeIntervalSinceReferenceDate: 700_000_250.0)
let calls = CallHistoryReader.shared.readCalls(dbPath: dbPath, since: watermark)
#expect(calls.count == 1)
#expect(calls.first?.uniqueId == "newer")
}
}