134 lines
4.4 KiB
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)
|
|
}
|
|
}
|