import Testing import Foundation @testable import Music @MainActor struct HLSSegmenterTests { // Creates a segmenter for a test MP3 file and verifies it reports the correct duration. @Test func readsDurationFromFile() async throws { let url = try TestFixtures.shortMP3URL() let segmenter = try HLSSegmenter(fileURL: url) // The test fixture is ~3 seconds long #expect(segmenter.duration > 2.0) #expect(segmenter.duration < 5.0) } // Extracts the first segment and verifies it returns non-empty data. @Test func extractsFirstSegment() async throws { let url = try TestFixtures.shortMP3URL() let segmenter = try HLSSegmenter(fileURL: url) let data = try #require(await segmenter.segment(at: 0, segmentDuration: 6.0)) #expect(!data.isEmpty) } // Requesting a segment index beyond the track duration returns nil. @Test func outOfRangeSegmentReturnsNil() async throws { let url = try TestFixtures.shortMP3URL() let segmenter = try HLSSegmenter(fileURL: url) let data = try await segmenter.segment(at: 999, segmentDuration: 6.0) #expect(data == nil) } } enum TestFixtures { // Returns the URL of a short test audio file (M4A/AAC). // macOS cannot encode MP3, so we use AAC which AVAssetReader handles identically. static func shortMP3URL() throws -> URL { let tempDir = FileManager.default.temporaryDirectory let url = tempDir.appendingPathComponent("test_fixture.m4a") if !FileManager.default.fileExists(atPath: url.path) { let wavURL = tempDir.appendingPathComponent("test_fixture.wav") let sampleRate = 44100 let channels = 1 let durationSamples = sampleRate * 3 let bytesPerSample = 2 let dataSize = durationSamples * channels * bytesPerSample var wavData = Data() func appendString(_ s: String) { wavData.append(contentsOf: s.utf8) } func appendUInt32(_ v: UInt32) { withUnsafeBytes(of: v.littleEndian) { wavData.append(contentsOf: $0) } } func appendUInt16(_ v: UInt16) { withUnsafeBytes(of: v.littleEndian) { wavData.append(contentsOf: $0) } } appendString("RIFF") appendUInt32(UInt32(36 + dataSize)) appendString("WAVE") appendString("fmt ") appendUInt32(16) appendUInt16(1) appendUInt16(UInt16(channels)) appendUInt32(UInt32(sampleRate)) appendUInt32(UInt32(sampleRate * channels * bytesPerSample)) appendUInt16(UInt16(channels * bytesPerSample)) appendUInt16(UInt16(bytesPerSample * 8)) appendString("data") appendUInt32(UInt32(dataSize)) wavData.append(Data(count: dataSize)) try wavData.write(to: wavURL) let process = Process() process.executableURL = URL(fileURLWithPath: "/usr/bin/afconvert") process.arguments = [wavURL.path, url.path, "-f", "m4af", "-d", "aac"] try process.run() process.waitUntilExit() try? FileManager.default.removeItem(at: wavURL) guard FileManager.default.fileExists(atPath: url.path) else { throw NSError(domain: "TestFixtures", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to create test audio fixture"]) } } return url } }