Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions Sources/BinaryDependencyManager/DepeneciesResolverRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public struct DependenciesResolverRunner {

func unzip(_ dependency: Dependency, asset: Dependency.Asset) throws {
guard try shouldResolve(dependency, asset: asset) else { return }
let tempRootDirURL = fileManager.temporaryDirectory.appending(pathComponents: "PrivateDownloads", isDirectory: true)
let tempRootDirURL = fileManager.privateDownloadsDirectoryURL

let tempDir = tempRootDirURL.appending(pathComponents: uuidString, isDirectory: true)
try createDirectoryIfNeeded(at: tempDir)
Expand Down Expand Up @@ -160,7 +160,7 @@ public struct DependenciesResolverRunner {

Logger.log("[Unzip] Successfully unzipped \(dependency.repo) to \(outputDirectory.relativeFilePath)")
}

func isFileDownloaded(for dependency: Dependency, asset: Dependency.Asset) throws -> Bool {
let downloadedFileURL = downloadURL(for: dependency, asset: asset)
guard fileManager.fileExists(at: downloadedFileURL) else { return false }
Expand Down Expand Up @@ -237,3 +237,10 @@ public struct DependenciesResolverRunner {
try checksumCalculator.calculateChecksum(fileURL: fileURL)
}
}


extension FileManagerProtocol {
var privateDownloadsDirectoryURL: URL {
temporaryDirectory.appending(pathComponents: "PrivateDownloads", isDirectory: true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Foundation
import Utils

@Suite("DependenciesResolverRunner Unzip Method Tests")
struct DependenciesResolverRunnerUnzipTests {
final class DependenciesResolverRunnerUnzipTests {
let sampleDependency: Dependency = Dependency(
repo: "org/repo",
tag: "1.0.0",
Expand All @@ -22,6 +22,12 @@ struct DependenciesResolverRunnerUnzipTests {
FileManager.default.temporaryDirectory
.appending(pathComponents: "binary-dependency-manager-tests", UUID().uuidString, isDirectory: true)
}()

lazy var sampleAsset = sampleDependency.assets[0]
lazy var fileManager = FileManagerProtocolMock(tempDir: tempDir)
lazy var unarchiverMock = UnarchiverProtocolMock()
lazy var runner = makeRunner(fileManager: fileManager, unarchiverMock: unarchiverMock)


func makeRunner(
fileManager: FileManagerProtocolMock = FileManagerProtocolMock(),
Expand All @@ -38,28 +44,27 @@ struct DependenciesResolverRunnerUnzipTests {
)
}

@discardableResult
func setupFileManagerForUnzip(
_ fileManager: FileManagerProtocolMock,
dependency: Dependency,
asset: Dependency.Asset,
tempContents: [String] = ["Library.framework", "Info.plist"]
) {
) -> URL {
let privateURLs = fileManager.privateDownloadsDirectoryURL
let unzipingDirectory: URL = privateURLs.appending(pathComponents: "mock-uuid", isDirectory: true)

// Make shouldResolve return true (no hash file exists)
// This is the default behavior when hash file doesn't exist

// Set up temp directory contents after unzipping
fileManager.directoryContents = [
tempDir.appending(pathComponents: "PrivateDownloads", "mock-uuid", asset.contents ?? "", isDirectory: true).filePath: tempContents
unzipingDirectory.appending(pathComponents: asset.contents ?? "", isDirectory: true).filePath: tempContents
]
return unzipingDirectory
}

@Test("unzip skips when shouldResolve returns false")
func unzip_skipsWhenShouldResolveReturnsFalse() async throws {
// GIVEN
let fileManager = FileManagerProtocolMock(tempDir: tempDir)
let unarchiverMock = UnarchiverProtocolMock()
let runner = makeRunner(fileManager: fileManager, unarchiverMock: unarchiverMock)
let sampleAsset = sampleDependency.assets[0]

// Set up hash file to make shouldResolve return false
let hashURL = try runner.outputDirectoryHashFile(for: sampleDependency, asset: sampleAsset)
Expand All @@ -76,20 +81,14 @@ struct DependenciesResolverRunnerUnzipTests {
@Test("unzip creates temp directory and calls unarchiver")
func unzip_createsDirectoryAndCallsUnarchiver() async throws {
// GIVEN
let fileManager = FileManagerProtocolMock(tempDir: tempDir)
let unarchiverMock = UnarchiverProtocolMock()
let runner = makeRunner(fileManager: fileManager, unarchiverMock: unarchiverMock)
let sampleAsset = sampleDependency.assets[0]

setupFileManagerForUnzip(fileManager, dependency: sampleDependency, asset: sampleAsset)
let unzippingDirectory = setupFileManagerForUnzip(asset: sampleAsset)

// WHEN
try runner.unzip(sampleDependency, asset: sampleAsset)

// THEN
// Verify temp directory was created
let tempDir = fileManager.temporaryDirectory.appending(pathComponents: "PrivateDownloads", "mock-uuid", isDirectory: true)
#expect(fileManager.createdDirectories.contains(tempDir))
#expect(fileManager.createdDirectories.contains(unzippingDirectory))

// Verify unarchiver was called with correct parameters
#expect(unarchiverMock.unzipCalls.count == 1)
Expand All @@ -102,13 +101,10 @@ struct DependenciesResolverRunnerUnzipTests {
@Test("unzip copies files from temp to output directory")
func unzip_copiesFilesToOutput() async throws {
// GIVEN
let fileManager = FileManagerProtocolMock(tempDir: tempDir)
let unarchiverMock = UnarchiverProtocolMock()
let runner = makeRunner(fileManager: fileManager, unarchiverMock: unarchiverMock)
let sampleAsset = sampleDependency.assets[0]

let tempContents = ["Library.framework", "Info.plist"]

setupFileManagerForUnzip(fileManager, dependency: sampleDependency, asset: sampleAsset, tempContents: tempContents)
let unzippingDirectory = setupFileManagerForUnzip(asset: sampleAsset, tempContents: tempContents)

// WHEN
try runner.unzip(sampleDependency, asset: sampleAsset)
Expand All @@ -119,7 +115,7 @@ struct DependenciesResolverRunnerUnzipTests {
#expect(fileManager.createdDirectories.contains(outputDir))

// Verify files were copied
let contentsDir = fileManager.temporaryDirectory.appending(pathComponents: "PrivateDownloads", "mock-uuid", sampleAsset.contents ?? "", isDirectory: true)
let contentsDir = unzippingDirectory.appending(pathComponents: sampleAsset.contents ?? "", isDirectory: true)
for item in tempContents {
let sourceURL = contentsDir.appending(pathComponents: item, isDirectory: false)
let destinationURL = outputDir.appending(pathComponents: item, isDirectory: false)
Expand All @@ -130,13 +126,10 @@ struct DependenciesResolverRunnerUnzipTests {
@Test("unzip removes existing files before copying")
func unzip_removesExistingFiles() async throws {
// GIVEN
let fileManager = FileManagerProtocolMock(tempDir: tempDir)
let unarchiverMock = UnarchiverProtocolMock()
let runner = makeRunner(fileManager: fileManager, unarchiverMock: unarchiverMock)
let sampleAsset = sampleDependency.assets[0]

let tempContents = ["Library.framework"]

setupFileManagerForUnzip(fileManager, dependency: sampleDependency, asset: sampleAsset, tempContents: tempContents)
setupFileManagerForUnzip(asset: sampleAsset, tempContents: tempContents)

// Set up existing file in output directory
let outputDir = runner.outputDirectoryURL(for: sampleDependency, asset: sampleAsset)
Expand All @@ -154,19 +147,14 @@ struct DependenciesResolverRunnerUnzipTests {
@Test("unzip cleans up temporary directory")
func unzip_cleansUpTempDirectory() async throws {
// GIVEN
let fileManager = FileManagerProtocolMock(tempDir: tempDir)
let unarchiverMock = UnarchiverProtocolMock()
let runner = makeRunner(fileManager: fileManager, unarchiverMock: unarchiverMock)
let sampleAsset = sampleDependency.assets[0]

setupFileManagerForUnzip(fileManager, dependency: sampleDependency, asset: sampleAsset)
setupFileManagerForUnzip(asset: sampleAsset)

// WHEN
try runner.unzip(sampleDependency, asset: sampleAsset)

// THEN
// Verify temp root directory was removed
let tempRootDir = fileManager.temporaryDirectory.appending(pathComponents: "PrivateDownloads", isDirectory: true)
let tempRootDir = fileManager.privateDownloadsDirectoryURL
#expect(fileManager.removedItems.contains(tempRootDir))
}

Expand All @@ -184,9 +172,7 @@ struct DependenciesResolverRunnerUnzipTests {
tag: "1.0.0",
assets: [assetWithoutContents]
)

let fileManager = FileManagerProtocolMock(tempDir: tempDir)
let unarchiverMock = UnarchiverProtocolMock()

let runner = makeRunner(
fileManager: fileManager,
dependencies: [dependencyWithoutContents],
Expand All @@ -212,14 +198,15 @@ struct DependenciesResolverRunnerUnzipTests {
let destURL1 = outputDir.appending(pathComponents: "file1.txt", isDirectory: false)
#expect(fileManager.copiedFiles[sourceURL1] == destURL1)
}

func stubUnarchiverError(_ error: Error) {
self.unarchiverMock = UnarchiverProtocolMock(errorToThrow: error)
}

@Test("unzip propagates unarchiver errors")
func unzip_propagatesUnarchiverErrors() async throws {
// GIVEN
let fileManager = FileManagerProtocolMock(tempDir: tempDir)
let unarchiverMock = UnarchiverProtocolMock(errorToThrow: GenericError("Unzip failed"))
let runner = makeRunner(fileManager: fileManager, unarchiverMock: unarchiverMock)
let sampleAsset = sampleDependency.assets[0]
stubUnarchiverError(GenericError("Unzip failed"))

// WHEN & THEN
#expect(throws: Error.self) {
Expand All @@ -237,20 +224,17 @@ struct DependenciesResolverRunnerUnzipTests {
@Test("unzip removes temporary files after copying")
func unzip_removesTemporaryFilesAfterCopying() async throws {
// GIVEN
let fileManager = FileManagerProtocolMock(tempDir: tempDir)
let unarchiverMock = UnarchiverProtocolMock()
let runner = makeRunner(fileManager: fileManager, unarchiverMock: unarchiverMock)
let sampleAsset = sampleDependency.assets[0]

let tempContents = ["Library.framework"]

setupFileManagerForUnzip(fileManager, dependency: sampleDependency, asset: sampleAsset, tempContents: tempContents)
let unzpipingDirectory = setupFileManagerForUnzip(asset: sampleAsset, tempContents: tempContents)

// WHEN
try runner.unzip(sampleDependency, asset: sampleAsset)

// THEN
// Verify temporary files were removed after copying
let contentsDir = fileManager.temporaryDirectory.appending(pathComponents: "PrivateDownloads", "mock-uuid", sampleAsset.contents ?? "", isDirectory: true)
let contentsDir = unzpipingDirectory.appending(pathComponents: sampleAsset.contents ?? "", isDirectory: true)
let tempFileURL = contentsDir.appending(pathComponents: "Library.framework", isDirectory: false)
#expect(fileManager.removedItems.contains(tempFileURL))
}
Expand Down