velody/packages/apple/VelodyPersistence/Tests/VelodyPersistenceTests/OfflineAudioFileStoreTests.swift

134 lines
4.4 KiB
Swift

import CryptoKit
import Foundation
import XCTest
@testable import VelodyPersistence
final class OfflineAudioFileStoreTests: XCTestCase {
func testFileOfflineAudioFileStoreWritesAndReadsAudioData() async throws {
let fileManager = FileManager.default
let tempDirectory = fileManager.temporaryDirectory.appendingPathComponent(
UUID().uuidString,
isDirectory: true
)
defer {
try? fileManager.removeItem(at: tempDirectory)
}
let store = try FileOfflineAudioFileStore(baseDirectoryURL: tempDirectory)
let bytes = sampleMp3Data(seed: "offline-audio")
let localFilePath = try await store.saveAudioFile(
bytes,
assetId: "asset-123",
sha256: sha256Hex(bytes)
)
let storedBytes = try await store.readAudioFile(at: localFilePath)
let fileExists = await store.fileExists(at: localFilePath)
XCTAssertEqual(storedBytes, bytes)
XCTAssertTrue(fileExists)
}
func testFileOfflineAudioFileStoreRejectsEmptyAudioData() async throws {
let store = try FileOfflineAudioFileStore(
baseDirectoryURL: FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString, isDirectory: true)
)
await XCTAssertThrowsErrorAsync {
_ = try await store.saveAudioFile(
Data(),
assetId: "asset-123",
sha256: nil
)
} assertion: { error in
XCTAssertEqual(error as? OfflineAudioFileStoreError, .emptyAudioData)
}
}
func testFileOfflineAudioFileStoreRejectsShaMismatch() async throws {
let fileManager = FileManager.default
let tempDirectory = fileManager.temporaryDirectory.appendingPathComponent(
UUID().uuidString,
isDirectory: true
)
defer {
try? fileManager.removeItem(at: tempDirectory)
}
let store = try FileOfflineAudioFileStore(baseDirectoryURL: tempDirectory)
let bytes = sampleMp3Data(seed: "sha-mismatch")
await XCTAssertThrowsErrorAsync {
_ = try await store.saveAudioFile(
bytes,
assetId: "asset-123",
sha256: String(repeating: "f", count: 64)
)
} assertion: { error in
guard case .sha256Mismatch = error as? OfflineAudioFileStoreError else {
return XCTFail("Expected a sha256Mismatch error.")
}
}
}
func testFileOfflineAudioFileStoreResolvesCurrentBaseDirectoryWhenPersistedPathIsStale() async throws {
let fileManager = FileManager.default
let tempDirectory = fileManager.temporaryDirectory.appendingPathComponent(
UUID().uuidString,
isDirectory: true
)
let firstAudioDirectory = tempDirectory.appendingPathComponent("audio-v1", isDirectory: true)
let secondAudioDirectory = tempDirectory.appendingPathComponent("audio-v2", isDirectory: true)
let bytes = sampleMp3Data(seed: "path-repair")
defer {
try? fileManager.removeItem(at: tempDirectory)
}
let staleFilePath = firstAudioDirectory
.appendingPathComponent("asset-123.mp3")
.standardizedFileURL
.path
let secondStore = try FileOfflineAudioFileStore(baseDirectoryURL: secondAudioDirectory)
let currentFilePath = try await secondStore.saveAudioFile(
bytes,
assetId: "asset-123",
sha256: sha256Hex(bytes)
)
let resolvedFilePath = await secondStore.resolveLocalFilePath(
persistedLocalFilePath: staleFilePath,
assetId: "asset-123"
)
XCTAssertEqual(resolvedFilePath, currentFilePath)
}
}
private func sampleMp3Data(seed: String) -> Data {
Data([
0x49, 0x44, 0x33, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21,
] + Array(seed.utf8))
}
private func sha256Hex(_ data: Data) -> String {
SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined()
}
private func XCTAssertThrowsErrorAsync(
_ expression: @escaping () async throws -> Void,
assertion: (Error) -> Void,
file: StaticString = #filePath,
line: UInt = #line
) async {
do {
try await expression()
XCTFail("Expected expression to throw an error.", file: file, line: line)
} catch {
assertion(error)
}
}