import XCTest @testable import Ten31Transcripts final class RecapTests: XCTestCase { private func entry(_ off: Double, _ end: Double, _ who: String, _ text: String) -> RecapAnalyzer.Entry { .init(offset: off, end: end, speaker: who, text: text) } // MARK: - Parsing func testParseSectionsHandlesStringIndices() { let json = #"{"sections":[{"title":"Intro","summary":"hi","startIndex":"0","endIndex":3},{"title":"Topic","summary":"x","startIndex":4,"endIndex":9}]}"# let secs = RecapAnalyzer.parseSections(json) XCTAssertEqual(secs.count, 2) XCTAssertEqual(secs[0].title, "Intro") XCTAssertEqual(secs[0].startIndex, 0) XCTAssertEqual(secs[1].endIndex, 9) } func testParseSectionsStripsCodeFence() { let json = "```json\n{\"sections\":[{\"title\":\"A\",\"summary\":\"\",\"startIndex\":0,\"endIndex\":1}]}\n```" XCTAssertEqual(RecapAnalyzer.parseSections(json).count, 1) } private var sampleTemplate: RecapTemplate { RecapTemplate(id: "t", name: "T", includeTLDR: true, sections: [ .init(id: "a", title: "Decisions", kind: .items, instruction: ""), .init(id: "b", title: "Takeaways", kind: .bullets, instruction: ""), ]) } func testParseExtrasGeneric() { let json = #"{"tldr":"They discussed the roadmap.","primary_speakers":["Grant","Caitlyn"],"sec_0":[{"text":"Ship dual-channel","who":"Grant","when":72,"note":null}],"sec_1":["faster","cheaper"]}"# let x = RecapAnalyzer.parseExtras(json, template: sampleTemplate) XCTAssertNotNil(x) XCTAssertEqual(x?.tldr, "They discussed the roadmap.") XCTAssertEqual(x?.primarySpeakers, ["Grant", "Caitlyn"]) XCTAssertEqual(x?.sections.count, 2) XCTAssertEqual(x?.sections[0].kind, .items) XCTAssertEqual(x?.sections[0].items.first?.text, "Ship dual-channel") XCTAssertEqual(x?.sections[0].items.first?.who, "Grant") XCTAssertEqual(x?.sections[0].items.first?.when, 72) XCTAssertEqual(x?.sections[1].kind, .bullets) XCTAssertEqual(x?.sections[1].bullets, ["faster", "cheaper"]) } func testParseExtrasDropsNullStrings() { let template = RecapTemplate(id: "t", name: "T", sections: [.init(id: "a", title: "Actions", kind: .items, instruction: "")]) let json = #"{"tldr":"s","primary_speakers":[],"sec_0":[{"text":"do it","who":"null","note":""}]}"# let x = RecapAnalyzer.parseExtras(json, template: template) XCTAssertNil(x?.sections.first?.items.first?.who) XCTAssertNil(x?.sections.first?.items.first?.note) XCTAssertEqual(x?.sections.first?.items.first?.text, "do it") } func testExtrasPromptBuildsFromTemplate() { let template = RecapTemplate(id: "t", name: "T", includeTLDR: true, sections: [ .init(id: "a", title: "Risks", kind: .bullets, instruction: "List the risks."), .init(id: "b", title: "Decisions", kind: .items, instruction: "List decisions."), ]) let file = SpeakersFile(sessionId: "s", app: "meet", durationSec: 60, speakers: [], segments: [.init(start: 0, end: 2, speaker: "A", text: "hi")], models: [:]) let prompt = RecapAnalyzer.extrasPrompt(file: file, entries: RecapAnalyzer.entries(from: file), sections: [], template: template) XCTAssertTrue(prompt.contains("sec_0")) XCTAssertTrue(prompt.contains("sec_1")) XCTAssertTrue(prompt.contains("Risks")) XCTAssertTrue(prompt.contains("List the risks.")) XCTAssertTrue(prompt.contains("tldr")) } func testExtrasPromptOmitsTLDRWhenDisabled() { let template = RecapTemplate(id: "t", name: "T", includeTLDR: false, sections: [.init(id: "a", title: "X", kind: .paragraph, instruction: "y")]) let file = SpeakersFile(sessionId: "s", app: "meet", durationSec: 60, speakers: [], segments: [.init(start: 0, end: 2, speaker: "A", text: "hi")], models: [:]) let prompt = RecapAnalyzer.extrasPrompt(file: file, entries: RecapAnalyzer.entries(from: file), sections: [], template: template) XCTAssertFalse(prompt.contains("\"tldr\"")) } // MARK: - Stitch / windows func testStitchDropsContainedAndTrimsOverlap() { let secs = [ TopicSection(title: "A", summary: "", startIndex: 0, endIndex: 5), TopicSection(title: "B-contained", summary: "", startIndex: 2, endIndex: 4), TopicSection(title: "C-overlap", summary: "", startIndex: 4, endIndex: 9), ] let out = RecapAnalyzer.stitch(secs) XCTAssertEqual(out.map { $0.title }, ["A", "C-overlap"]) XCTAssertEqual(out[0].startIndex, 0); XCTAssertEqual(out[0].endIndex, 5) XCTAssertEqual(out[1].startIndex, 6); XCTAssertEqual(out[1].endIndex, 9) // trimmed front } func testPlanWindowsSingleForShortCall() { let entries = (0..<10).map { entry(Double($0 * 10), Double($0 * 10 + 5), "A", "x") } // ~100s let w = RecapAnalyzer.planWindows(entries) XCTAssertEqual(w.count, 1) XCTAssertEqual(w[0].startIdx, 0); XCTAssertEqual(w[0].endIdx, 9) } func testPlanWindowsMultipleForLongCall() { // 40 entries at 60s spacing → ~39 min, over the 25-min cutoff. let entries = (0..<40).map { entry(Double($0 * 60), Double($0 * 60 + 30), "A", "x") } let w = RecapAnalyzer.planWindows(entries) XCTAssertGreaterThan(w.count, 1) XCTAssertEqual(w.first?.startIdx, 0) XCTAssertEqual(w.last?.endIdx, 39) // last window reaches the end for i in 1..")) XCTAssertTrue(html.contains("Go Bitcoin")) } }