You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
Music/MusicTests/TagWriterTests.swift

76 lines
3.8 KiB

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")
}
}