import Testing import Foundation import SwiftData @testable import 康康 /// MedicationScanService.parseMedicationsJSON 纯函数单测(JSON 容错与去重)。 struct MedicationScanServiceTests { @Test func parsesStandardObject() throws { let raw = """ {"medications":[{"name":"缬沙坦胶囊","strength":"80mg×7粒","usage":""}]} """ let meds = try MedicationScanService.parseMedicationsJSON(raw) #expect(meds.count == 1) #expect(meds[0].name == "缬沙坦胶囊") #expect(meds[0].strength == "80mg×7粒") #expect(meds[0].entryText == "缬沙坦胶囊 80mg×7粒") } @Test func parsesBareArrayWithFence() throws { let raw = """ ```json [{"name":"二甲双胍缓释片","strength":"0.5g×30片","usage":"口服,一次1片,一日2次"}] ``` """ let meds = try MedicationScanService.parseMedicationsJSON(raw) #expect(meds.count == 1) #expect(meds[0].entryText == "二甲双胍缓释片 0.5g×30片 · 口服,一次1片,一日2次") } @Test func parsesChineseKeysAndDedupes() throws { let raw = """ {"medications":[ {"药名":"阿司匹林肠溶片","规格":"100mg","用法":""}, {"name":"阿司匹林肠溶片","strength":"100mg","usage":""} ]} """ let meds = try MedicationScanService.parseMedicationsJSON(raw) #expect(meds.count == 1) } @Test func emptyNameRowsAreDropped() throws { let raw = #"{"medications":[{"name":"","strength":"10mg","usage":""}]}"# let meds = try MedicationScanService.parseMedicationsJSON(raw) #expect(meds.isEmpty) } @Test func trailingCommaIsRepaired() throws { let raw = #"{"medications":[{"name":"氯雷他定片","strength":"10mg×6片","usage":"",},]}"# let meds = try MedicationScanService.parseMedicationsJSON(raw) #expect(meds.count == 1) #expect(meds[0].name == "氯雷他定片") } @Test func invalidJSONThrows() { #expect(throws: (any Error).self) { try MedicationScanService.parseMedicationsJSON("识别不出来,抱歉") } } /// 英文药名照原样保留(prompt 已要求英文不翻译);解析层只透传,不应被丢弃。 @Test func parsesEnglishDrugName() throws { let raw = #"{"medications":[{"name":"Amoxicillin","strength":"500mg","usage":"Take one capsule three times daily"}]}"# let meds = try MedicationScanService.parseMedicationsJSON(raw) #expect(meds.count == 1) #expect(meds[0].name == "Amoxicillin") #expect(meds[0].entryText == "Amoxicillin 500mg · Take one capsule three times daily") } /// 通用名 + 商品名合并写法(prompt 约定 "通用名(商品名)")原样透传。 @Test func parsesGenericWithBrandName() throws { let raw = #"{"medications":[{"name":"缬沙坦胶囊(代文)","strength":"80mg×7粒","usage":""}]}"# let meds = try MedicationScanService.parseMedicationsJSON(raw) #expect(meds.count == 1) #expect(meds[0].name == "缬沙坦胶囊(代文)") } } /// 「用药」日记 → 时间线分类映射(拍药盒入档落库后在「记录」tab 的归类)。 @MainActor struct MedicationTimelineTests { private func makeContext() throws -> ModelContext { // DiaryEntry 现关联 Asset(拍药盒原图),schema 必须带上 Asset.self,否则建容器报错。 let schema = Schema([DiaryEntry.self, Asset.self]) let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true) return ModelContext(try ModelContainer(for: schema, configurations: [config])) } @Test func medicationTaggedDiaryMapsToMedicationKind() throws { let ctx = try makeContext() let med = DiaryEntry(content: "缬沙坦胶囊 80mg×7粒", tags: [DiaryEntry.medicationTag]) let plain = DiaryEntry(content: "今天睡得不错") ctx.insert(med); ctx.insert(plain) try ctx.save() let medEntry = TimelineEntry.from(diary: med) #expect(medEntry.kind == .medication) #expect(medEntry.title == "缬沙坦胶囊 80mg×7粒") #expect(TimelineEntry.from(diary: plain).kind == .diary) } }