- AIRuntime 加 actor 内串行推理闸门,封死 LLM/VL in-flight 并发解码窄口(jetsam OOM 根因) - prepare 的 .loading 改轮询等待消除假就绪竞态;就绪判据 isReady→isComplete 防半下载崩溃 - applyReanalyzed 重新解读时 unlink 旧 Asset,消除 Vault 孤儿图片(§6 隐私承诺) - parseReportJSON 改 extractBalancedJSON + 裸数组兜底,防 VL 多项输出被静默截断丢指标 - 临时文件改 completeUnlessOpen 修锁屏写失败;parseDate 支持多格式防归档年份错位 - TimelineEntry/DayDetailSheet 修「偏高」文案与血压箭头方向(偏低指标不再显示相反结论) - FileVault.wipe 容错;HealthExportSheet 异常关键词排除否定句;modelTag 取实际枚举值 - 删除 B1-B5 + ArchiveFlow 死代码(含违反 §6 的 AES 加密文案) - 补 3 个回归测试,编译 + 测试全部通过
137 lines
5.9 KiB
Swift
137 lines
5.9 KiB
Swift
import Testing
|
|
import Foundation
|
|
@testable import 康康
|
|
|
|
struct CaptureServiceJSONTests {
|
|
|
|
@Test func parsesCleanJSON() throws {
|
|
let raw = """
|
|
{"title":"春检","type":"checkup","report_date":"2026-04-12","institution":"协和","page_count":2,"summary":"血脂偏高","indicators":[{"name":"LDL-C","value":"3.84","unit":"mmol/L","range":"< 3.40","status":"high"}]}
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.title == "春检")
|
|
#expect(parsed.typeRaw == ReportType.checkup.rawValue)
|
|
#expect(parsed.institution == "协和")
|
|
#expect(parsed.pageCount == 2)
|
|
#expect(parsed.indicators.count == 1)
|
|
#expect(parsed.indicators.first?.status == .high)
|
|
}
|
|
|
|
@Test func stripsMarkdownCodeFence() throws {
|
|
let raw = """
|
|
```json
|
|
{"title":"x","type":"lab","report_date":"2026-05-01","institution":"","page_count":1,"summary":"","indicators":[]}
|
|
```
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.title == "x")
|
|
#expect(parsed.typeRaw == ReportType.lab.rawValue)
|
|
#expect(parsed.indicators.isEmpty)
|
|
}
|
|
|
|
@Test func extractsObjectAfterLeadingText() throws {
|
|
let raw = """
|
|
好的,识别结果如下:
|
|
{"title":"y","type":"lab","report_date":"2026-05-01","institution":"","page_count":1,"summary":"","indicators":[]}
|
|
以上。
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.title == "y")
|
|
}
|
|
|
|
@Test func handlesNestedBraces() throws {
|
|
let raw = """
|
|
{"title":"y","type":"lab","report_date":"2026-05-01","institution":"","page_count":1,"summary":"含嵌套{x}对象","indicators":[]}
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.summary == "含嵌套{x}对象")
|
|
}
|
|
|
|
@Test func handlesEscapedQuotesInStrings() throws {
|
|
let raw = #"{"title":"y \"内嵌\" 引号","type":"lab","report_date":"2026-05-01","institution":"","page_count":1,"summary":"","indicators":[]}"#
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.title == #"y "内嵌" 引号"#)
|
|
}
|
|
|
|
@Test func fillsDefaultsForMissingFields() throws {
|
|
// 缺 title / type / report_date / institution / summary / page_count
|
|
let raw = """
|
|
{"indicators":[{"name":"X","value":"1","unit":"","range":"","status":"normal"}]}
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.title == "拍摄识别") // 默认值
|
|
#expect(parsed.typeRaw == ReportType.other.rawValue)
|
|
#expect(parsed.indicators.count == 1)
|
|
}
|
|
|
|
@Test func skipsIndicatorsWithEmptyName() throws {
|
|
let raw = """
|
|
{"title":"t","type":"lab","report_date":"2026-05-01","institution":"","page_count":1,"summary":"","indicators":[
|
|
{"name":"","value":"1","unit":"","range":"","status":"normal"},
|
|
{"name":" ","value":"1","unit":"","range":"","status":"normal"},
|
|
{"name":"OK","value":"1","unit":"","range":"","status":"normal"}
|
|
]}
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.indicators.count == 1)
|
|
#expect(parsed.indicators.first?.name == "OK")
|
|
}
|
|
|
|
@Test func malformedJSONThrows() {
|
|
let raw = "完全不是 JSON"
|
|
#expect(throws: CaptureError.self) {
|
|
_ = try CaptureService.parseReportJSON(raw)
|
|
}
|
|
}
|
|
|
|
@Test func valueAsNumberStillParses() throws {
|
|
let raw = """
|
|
{"title":"t","type":"lab","report_date":"2026-05-01","institution":"","page_count":1,"summary":"","indicators":[{"name":"X","value":3.84,"unit":"","range":"","status":"high"}]}
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.indicators.first?.value == "3.84")
|
|
}
|
|
|
|
@Test func unknownStatusFallsBackToNormal() throws {
|
|
let raw = """
|
|
{"title":"t","type":"lab","report_date":"2026-05-01","institution":"","page_count":1,"summary":"","indicators":[{"name":"X","value":"1","unit":"","range":"","status":"abnormal"}]}
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.indicators.first?.status == .normal)
|
|
}
|
|
|
|
@Test func badReportDateFallsBackToNow() throws {
|
|
let raw = """
|
|
{"title":"t","type":"lab","report_date":"昨天","institution":"","page_count":1,"summary":"","indicators":[]}
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
let now = Date()
|
|
let diff = abs(parsed.reportDate.timeIntervalSince(now))
|
|
#expect(diff < 5) // 5 秒内算 .now
|
|
}
|
|
|
|
/// VL 多项时偶尔直接吐裸数组 [{...},{...}],旧实现(只认 {})会只截第一项静默丢其余。
|
|
@Test func parsesTopLevelArrayAsIndicators() throws {
|
|
let raw = """
|
|
[{"name":"A","value":"1","unit":"","range":"","status":"high"},
|
|
{"name":"B","value":"2","unit":"","range":"","status":"low"}]
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
#expect(parsed.indicators.count == 2)
|
|
#expect(parsed.indicators.first?.name == "A")
|
|
#expect(parsed.indicators.last?.status == .low)
|
|
}
|
|
|
|
/// VL 不同来源会吐 yyyy/MM/dd 等格式,不应回退到「今天」导致归档年份错位。
|
|
@Test func parsesSlashAndCJKDateFormats() throws {
|
|
for ds in ["2026/04/12", "2026.04.12", "2026年04月12日"] {
|
|
let raw = """
|
|
{"title":"t","type":"lab","report_date":"\(ds)","institution":"","page_count":1,"summary":"","indicators":[]}
|
|
"""
|
|
let parsed = try CaptureService.parseReportJSON(raw)
|
|
let c = Calendar(identifier: .gregorian).dateComponents([.year, .month, .day], from: parsed.reportDate)
|
|
#expect(c.year == 2026 && c.month == 4 && c.day == 12, "格式 \(ds) 解析失败")
|
|
}
|
|
}
|
|
}
|