import CryptoKit import Foundation import XCTest final class VoiceRecordingParityCheckerTests: XCTestCase { func testVoiceRecordingParityBaselineHasNoBlockingFindings() throws { let root = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() .deletingLastPathComponent() let baselineURL = root.appendingPathComponent("docs/voice-recording-parity-baseline.json") let baselineData = try Data(contentsOf: baselineURL) let baselineAny = try JSONSerialization.jsonObject(with: baselineData, options: []) guard let baseline = baselineAny as? [String: Any] else { XCTFail("invalid baseline format") return } var findings: [[String: String]] = [] let constants = baseline["constants"] as? [[String: Any]] ?? [] for spec in constants { let id = spec["id"] as? String ?? "unknown" let severity = spec["severity"] as? String ?? "P1" let file = spec["file"] as? String ?? "" let pattern = spec["pattern"] as? String ?? "" let expected = spec["expected"] as? String ?? "" let rawMatch = spec["raw_match"] as? Bool ?? false let rosettaText = try readText(root: root, relativePath: file) let actual = regexCapture(text: rosettaText, pattern: pattern, rawMatch: rawMatch) if actual == nil { findings.append([ "severity": severity, "kind": "missing_pattern", "id": id, "evidence": file ]) } else if let actual, actual != expected { findings.append([ "severity": severity, "kind": "value_mismatch", "id": id, "evidence": file, "actual": actual, "expected": expected ]) } if let telegramFile = spec["telegram_file"] as? String, let telegramPattern = spec["telegram_pattern"] as? String { let telegramText = try readText(root: root, relativePath: telegramFile) if regexCapture(text: telegramText, pattern: telegramPattern, rawMatch: true) == nil { findings.append([ "severity": severity, "kind": "telegram_reference_missing", "id": id, "evidence": telegramFile ]) } } } let geometry = baseline["geometry"] as? [[String: Any]] ?? [] for spec in geometry { let id = spec["id"] as? String ?? "unknown" let severity = spec["severity"] as? String ?? "P1" let file = spec["file"] as? String ?? "" let pattern = spec["pattern"] as? String ?? "" let expected = spec["expected"] as? String ?? "" let rawMatch = spec["raw_match"] as? Bool ?? false let rosettaText = try readText(root: root, relativePath: file) let actual = regexCapture(text: rosettaText, pattern: pattern, rawMatch: rawMatch) if actual == nil { findings.append([ "severity": severity, "kind": "missing_pattern", "id": id, "evidence": file ]) } else if let actual, actual != expected { findings.append([ "severity": severity, "kind": "value_mismatch", "id": id, "evidence": file, "actual": actual, "expected": expected ]) } if let telegramFile = spec["telegram_file"] as? String, let telegramPattern = spec["telegram_pattern"] as? String { let telegramText = try readText(root: root, relativePath: telegramFile) if regexCapture(text: telegramText, pattern: telegramPattern, rawMatch: true) == nil { findings.append([ "severity": severity, "kind": "telegram_reference_missing", "id": id, "evidence": telegramFile ]) } } } if let flow = baseline["flow"] as? [String: Any] { let flowSeverity = flow["severity"] as? String ?? "P1" let stateFile = flow["state_file"] as? String ?? "" let expectedStates = flow["expected_states"] as? [String] ?? [] let stateText = try readText(root: root, relativePath: stateFile) let actualStates = regexMatches(text: stateText, pattern: "case\\s+([A-Za-z_][A-Za-z0-9_]*)") if actualStates != expectedStates { findings.append([ "severity": flowSeverity, "kind": "state_machine_mismatch", "id": "flow_states", "evidence": stateFile ]) } let requiredTransitions = flow["required_transitions"] as? [[String: Any]] ?? [] for transition in requiredTransitions { let transitionId = transition["id"] as? String ?? "unknown" let transitionSeverity = transition["severity"] as? String ?? "P1" let transitionFile = transition["file"] as? String ?? "" let snippet = transition["snippet"] as? String ?? "" let transitionText = try readText(root: root, relativePath: transitionFile) if !transitionText.contains(snippet) { findings.append([ "severity": transitionSeverity, "kind": "transition_missing", "id": transitionId, "evidence": transitionFile ]) } } } let accessibility = baseline["accessibility"] as? [[String: Any]] ?? [] for spec in accessibility { let id = spec["id"] as? String ?? "unknown" let severity = spec["severity"] as? String ?? "P1" let file = spec["file"] as? String ?? "" let snippet = spec["snippet"] as? String ?? "" let text = try readText(root: root, relativePath: file) if !text.contains(snippet) { findings.append([ "severity": severity, "kind": "accessibility_missing", "id": id, "evidence": file ]) } } let animations = baseline["animations"] as? [[String: Any]] ?? [] for spec in animations { let id = spec["id"] as? String ?? "unknown" let severity = spec["severity"] as? String ?? "P1" let file = spec["file"] as? String ?? "" let snippet = spec["snippet"] as? String ?? "" let text = try readText(root: root, relativePath: file) if !text.contains(snippet) { findings.append([ "severity": severity, "kind": "animation_snippet_missing", "id": id, "evidence": file ]) } } if let assets = baseline["assets"] as? [String: Any] { let imagesets = assets["imagesets"] as? [[String: Any]] ?? [] for imageset in imagesets { let assetId = imageset["id"] as? String ?? "unknown" let severity = imageset["severity"] as? String ?? "P1" let path = imageset["path"] as? String ?? "" let files = imageset["files"] as? [[String: Any]] ?? [] let imagesetURL = root.appendingPathComponent(path) if !FileManager.default.fileExists(atPath: imagesetURL.path) { findings.append([ "severity": severity, "kind": "asset_missing", "id": assetId, "evidence": path ]) continue } for fileSpec in files { let fileName = fileSpec["name"] as? String ?? "" let expectedSha = fileSpec["sha256"] as? String ?? "" let fileURL = imagesetURL.appendingPathComponent(fileName) if !FileManager.default.fileExists(atPath: fileURL.path) { findings.append([ "severity": severity, "kind": "asset_file_missing", "id": "\(assetId)/\(fileName)", "evidence": path ]) continue } let actualSha = try sha256(fileURL) if actualSha != expectedSha { findings.append([ "severity": severity, "kind": "asset_hash_mismatch", "id": "\(assetId)/\(fileName)", "evidence": path ]) } } } let lottie = assets["lottie"] as? [[String: Any]] ?? [] for lottieSpec in lottie { let lottieId = lottieSpec["id"] as? String ?? "unknown" let severity = lottieSpec["severity"] as? String ?? "P1" let path = lottieSpec["path"] as? String ?? "" let expectedSha = lottieSpec["sha256"] as? String ?? "" let lottieURL = root.appendingPathComponent(path) if !FileManager.default.fileExists(atPath: lottieURL.path) { findings.append([ "severity": severity, "kind": "lottie_missing", "id": lottieId, "evidence": path ]) continue } let actualSha = try sha256(lottieURL) if actualSha != expectedSha { findings.append([ "severity": severity, "kind": "lottie_hash_mismatch", "id": lottieId, "evidence": path ]) } } } let blocking = findings.filter { finding in let severity = finding["severity"] ?? "P3" return severity == "P0" || severity == "P1" } if !blocking.isEmpty { let data = try JSONSerialization.data(withJSONObject: blocking, options: [.prettyPrinted, .sortedKeys]) let details = String(data: data, encoding: .utf8) ?? "" XCTFail("blocking voice parity findings:\n\(details)") } } private func readText(root: URL, relativePath: String) throws -> String { let url = root.appendingPathComponent(relativePath) return try String(contentsOf: url, encoding: .utf8) } private func sha256(_ url: URL) throws -> String { let data = try Data(contentsOf: url) let digest = SHA256.hash(data: data) return digest.map { String(format: "%02x", $0) }.joined() } private func regexCapture(text: String, pattern: String, rawMatch: Bool) -> String? { guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return nil } let nsText = text as NSString let range = NSRange(location: 0, length: nsText.length) guard let match = regex.firstMatch(in: text, options: [], range: range) else { return nil } if rawMatch { return pattern } guard match.numberOfRanges > 1 else { return nil } let captureRange = match.range(at: 1) guard captureRange.location != NSNotFound else { return nil } return nsText.substring(with: captureRange) } private func regexMatches(text: String, pattern: String) -> [String] { guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return [] } let nsText = text as NSString let range = NSRange(location: 0, length: nsText.length) return regex.matches(in: text, options: [], range: range).compactMap { match in guard match.numberOfRanges > 1 else { return nil } let captureRange = match.range(at: 1) guard captureRange.location != NSNotFound else { return nil } return nsText.substring(with: captureRange) } } }