@@ -2957,6 +2957,137 @@ fileprivate var availabilityURL = URL("\(registryURL)/availability")
29572957 return false
29582958 }
29592959 }
2960+
2961+ @Test func downloadSourceArchiveWithSymlinks( ) async throws {
2962+ let checksumAlgorithm : HashAlgorithm = MockHashAlgorithm ( )
2963+ let checksum = checksumAlgorithm. hash ( emptyZipFile) . hexadecimalRepresentation
2964+
2965+ let handler : HTTPClient . Implementation = { request, _ in
2966+ switch ( request. kind, request. method, request. url) {
2967+ case ( . generic, . get, metadataURL) :
2968+ let data = """
2969+ {
2970+ " id " : " \( identity) " ,
2971+ " version " : " \( version) " ,
2972+ " resources " : [
2973+ {
2974+ " name " : " source-archive " ,
2975+ " type " : " application/zip " ,
2976+ " checksum " : " \( checksum) "
2977+ }
2978+ ],
2979+ " metadata " : {
2980+ " author " : {
2981+ " name " : " Test Author "
2982+ },
2983+ " description " : " Test package with symlinks "
2984+ }
2985+ }
2986+ """ . data ( using: . utf8) !
2987+
2988+ return . init(
2989+ statusCode: 200 ,
2990+ headers: . init( [
2991+ . init( name: " Content-Length " , value: " \( data. count) " ) ,
2992+ . init( name: " Content-Type " , value: " application/json " ) ,
2993+ . init( name: " Content-Version " , value: " 1 " ) ,
2994+ ] ) ,
2995+ body: data
2996+ )
2997+ case ( . download( let fileSystem, let path) , . get, downloadURL) :
2998+ #expect( request. headers. get ( " Accept " ) . first == " application/vnd.swift.registry.v1+zip " )
2999+
3000+ let data = Data ( emptyZipFile. contents)
3001+ try ! fileSystem. writeFileContents ( path, data: data)
3002+
3003+ return . init(
3004+ statusCode: 200 ,
3005+ headers: . init( [
3006+ . init( name: " Content-Length " , value: " \( data. count) " ) ,
3007+ . init( name: " Content-Type " , value: " application/zip " ) ,
3008+ . init( name: " Content-Version " , value: " 1 " ) ,
3009+ . init(
3010+ name: " Content-Disposition " ,
3011+ value: " attachment; filename= \" \( identity) - \( version) .zip \" "
3012+ ) ,
3013+ . init(
3014+ name: " Digest " ,
3015+ value: " sha-256=bc6c9a5d2f2226cfa1ef4fad8344b10e1cc2e82960f468f70d9ed696d26b3283 "
3016+ ) ,
3017+ ] ) ,
3018+ body: nil
3019+ )
3020+ default :
3021+ throw StringError ( " method and url should match " )
3022+ }
3023+ }
3024+
3025+ let httpClient = HTTPClient ( implementation: handler)
3026+ var configuration = RegistryConfiguration ( )
3027+ configuration. defaultRegistry = Registry ( url: registryURL, supportsAvailability: false )
3028+ configuration. security = . testDefault
3029+
3030+ let fileSystem = localFileSystem
3031+ let tmpDir = try fileSystem. tempDirectory. appending ( component: UUID ( ) . uuidString)
3032+ try fileSystem. createDirectory ( tmpDir, recursive: true )
3033+ defer {
3034+ try ? fileSystem. removeFileTree ( tmpDir)
3035+ }
3036+
3037+ let registryClient = RegistryClient (
3038+ configuration: configuration,
3039+ fingerprintStorage: . none,
3040+ fingerprintCheckingMode: . strict,
3041+ skipSignatureValidation: false ,
3042+ signingEntityStorage: . none,
3043+ signingEntityCheckingMode: . strict,
3044+ customHTTPClient: httpClient,
3045+ customArchiverProvider: { fileSystem in
3046+ MockArchiver ( handler: { _, from, to, callback in
3047+ let data = try fileSystem. readFileContents ( from)
3048+ #expect( data == emptyZipFile)
3049+
3050+ let packagePath = to. appending ( component: " package " )
3051+ try fileSystem. createDirectory ( packagePath, recursive: true )
3052+
3053+ let targetFile = packagePath. appending ( component: " AFile.swift " )
3054+ try fileSystem. writeFileContents ( targetFile, string: " // Target file content \n " )
3055+
3056+ let symlink = packagePath. appending ( component: " ZLinkToFile.swift " )
3057+ try fileSystem. createSymbolicLink ( symlink, pointingAt: targetFile, relative: true )
3058+
3059+ // Also create Package.swift
3060+ try fileSystem. writeFileContents ( packagePath. appending ( component: " Package.swift " ) , string: " // Package manifest \n " )
3061+
3062+ callback ( . success( ( ) ) )
3063+ } )
3064+ } ,
3065+ delegate: . none,
3066+ checksumAlgorithm: checksumAlgorithm
3067+ )
3068+
3069+ let path = tmpDir. appending ( component: " \( identity) - \( version) " )
3070+
3071+ try await registryClient. downloadSourceArchive (
3072+ package : identity,
3073+ version: version,
3074+ fileSystem: fileSystem,
3075+ destinationPath: path
3076+ )
3077+
3078+ let contents = try fileSystem. getDirectoryContents ( path) . sorted ( )
3079+ #expect( contents. contains ( " AFile.swift " ) , " Regular file AFile.swift should exist " )
3080+ #expect( contents. contains ( " ZLinkToFile.swift " ) , " Symlink ZLinkToFile.swift should exist " )
3081+ #expect( contents. contains ( " Package.swift " ) , " Package.swift should exist " )
3082+ #expect( contents. contains ( RegistryReleaseMetadataStorage . fileName) , " Metadata file should exist " )
3083+
3084+ let symlinkPath = path. appending ( component: " ZLinkToFile.swift " )
3085+ let regularFilePath = path. appending ( component: " AFile.swift " )
3086+
3087+ let regularFileContent : String = try fileSystem. readFileContents ( regularFilePath)
3088+ let symlinkContent : String = try fileSystem. readFileContents ( symlinkPath)
3089+ #expect( symlinkContent == regularFileContent, " Symlink should have same content as target file " )
3090+ }
29603091}
29613092
29623093@Suite ( " Lookup Identities " ) struct LookupIdentities {
0 commit comments