import Foundation import AVFoundation import Testing @testable import Music // Locates the test bundle from a struct suite (struct suites don't have a Bundle.self, // so we use a final class defined in the same file). private final class BundleToken {} // Verifies format routing and that writing tags round-trips through a real file // without corrupting audio. struct TagWriterTests { // Step 1: Locate a resource file in the test bundle using BundleToken as the anchor. private func fixtureURL(_ name: String, _ ext: String) -> URL? { Bundle(for: BundleToken.self).url(forResource: name, withExtension: ext) } // Step 2: Copy the fixture to a temp path so the test can mutate it without // modifying the bundle resource. private func tempCopy(of url: URL) throws -> URL { let dst = URL(fileURLWithPath: NSTemporaryDirectory()) .appendingPathComponent(UUID().uuidString + "." + url.pathExtension) try FileManager.default.copyItem(at: url, to: dst) return dst } // Step 3: Read the common "title" key from an audio file using AVFoundation metadata. private func readCommonTitle(_ url: URL) async throws -> String? { let md = try await AVURLAsset(url: url).load(.metadata) let items = AVMetadataItem.metadataItems(from: md, withKey: AVMetadataKey.commonKeyTitle, keySpace: .common) return try await items.first?.load(.stringValue) } // Verifies that TagWriterFactory routes ".mp3" → ID3TagWriter, ".m4a" → MP4TagWriter, // and returns nil for unsupported formats. @Test func factoryRoutesByExtension() { #expect(TagWriterFactory.writer(for: URL(fileURLWithPath: "/a.mp3")) is ID3TagWriter) #expect(TagWriterFactory.writer(for: URL(fileURLWithPath: "/a.m4a")) is MP4TagWriter) #expect(TagWriterFactory.writer(for: URL(fileURLWithPath: "/a.flac")) == nil) #expect(TagWriterFactory.writer(for: URL(fileURLWithPath: "/a.wav")) == nil) } // Step 1: Locate the sample.m4a fixture in the test bundle. // Step 2: Copy it to a temp file so the bundle resource is not mutated. // Step 3: Build EditableTrackFields with a specific title and artist. // Step 4: Write the fields via MP4TagWriter. // Step 5: Read the common title back via AVFoundation and assert it matches. // Step 6: Assert the audio track is still present (file not corrupted). @Test func m4aRoundTrips() async throws { let src = try #require(fixtureURL("sample", "m4a"), "missing sample.m4a fixture") let url = try tempCopy(of: src) defer { try? FileManager.default.removeItem(at: url) } var f = EditableTrackFields(from: .fixture()) f.title = "Round Trip"; f.artist = "The Verifier" try MP4TagWriter().write(f, to: url) #expect(try await readCommonTitle(url) == "Round Trip") let tracks = try await AVURLAsset(url: url).loadTracks(withMediaType: .audio) #expect(!tracks.isEmpty) // audio track survived the write } // Step 1: Check if sample.mp3 fixture is available; skip trivially if absent. // Step 2: Copy it to a temp file. // Step 3: Build EditableTrackFields with a specific title. // Step 4: Write the fields via ID3TagWriter. // Step 5: Read the common title back via AVFoundation and assert it matches. @Test func mp3RoundTrips() async throws { guard let src = fixtureURL("sample", "mp3") else { return } // no fixture → trivially pass let url = try tempCopy(of: src) defer { try? FileManager.default.removeItem(at: url) } var f = EditableTrackFields(from: .fixture()) f.title = "ID3 Round Trip"; f.artist = "Tagger" try ID3TagWriter().write(f, to: url) #expect(try await readCommonTitle(url) == "ID3 Round Trip") } }