import Foundation import Testing @testable import Infrastructure @Suite( "FingerprintAssets method", .tags(.asset) ) struct FingerprintAssetsTests { // MARK: Properties private let fingerprint = FingerprintAssets() // MARK: Functional tests @Test func `fingerprints the files under a directory`() throws { let directory = try makeDirectory(files: [ "css/site.css": "body { margin: 0; }", "robots.txt": "User-agent: *" ]) defer { removeDirectory(directory) } let token = try #require(fingerprint(directory.path)) #expect(token.count == 16) #expect(token.allSatisfy { $0.isHexDigit }) } @Test func `agrees across directories with identical contents`() throws { let files = [ "css/site.css": "body { margin: 0; }", "js/site.js": "console.log(1);" ] let first = try makeDirectory(files: files) let second = try makeDirectory(files: files) defer { removeDirectory(first) removeDirectory(second) } #expect(fingerprint(first.path) == fingerprint(second.path)) } @Test func `changes the token when a file's contents change`() throws { let directory = try makeDirectory(files: [ "css/site.css": "body { margin: 0; }" ]) defer { removeDirectory(directory) } let before = fingerprint(directory.path) try "body { margin: 1px; }".write( to: directory.appendingPathComponent("css/site.css"), atomically: true, encoding: .utf8 ) #expect(fingerprint(directory.path) != before) } @Test func `changes the token when a file is renamed`() throws { let contents = "body { margin: 0; }" let first = try makeDirectory(files: ["css/site.css": contents]) let second = try makeDirectory(files: ["css/main.css": contents]) defer { removeDirectory(first) removeDirectory(second) } #expect(fingerprint(first.path) != fingerprint(second.path)) } @Test func `changes the token when a file is added`() throws { let directory = try makeDirectory(files: [ "css/site.css": "body { margin: 0; }" ]) defer { removeDirectory(directory) } let before = fingerprint(directory.path) try "console.log(1);".write( to: directory.appendingPathComponent("site.js"), atomically: true, encoding: .utf8 ) #expect(fingerprint(directory.path) != before) } @Test func `returns nil for a directory without files`() throws { let directory = try makeDirectory(files: [:]) defer { removeDirectory(directory) } #expect(fingerprint(directory.path) == nil) } @Test func `returns nil for a missing directory`() { let missing = FileManager.default.temporaryDirectory .appendingPathComponent("FingerprintAssetsTests-missing-\(UUID().uuidString)") #expect(fingerprint(missing.path) == nil) } } // MARK: - Helpers private extension FingerprintAssetsTests { // MARK: Methods /// Creates a unique temporary directory holding the given files, keyed by relative path. /// - Parameter files: the files to create, keyed by their path relative to the directory. /// - Returns: the URL of the created directory. func makeDirectory( files: [String: String] ) throws -> URL { let directory = FileManager.default.temporaryDirectory .appendingPathComponent("FingerprintAssetsTests-\(UUID().uuidString)") try FileManager.default.createDirectory( at: directory, withIntermediateDirectories: true ) for (relativePath, contents) in files { let file = directory.appendingPathComponent(relativePath) try FileManager.default.createDirectory( at: file.deletingLastPathComponent(), withIntermediateDirectories: true ) try contents.write( to: file, atomically: true, encoding: .utf8 ) } return directory } /// Removes a temporary directory created by ``makeDirectory(files:)``. /// - Parameter directory: the URL of the directory to remove. func removeDirectory( _ directory: URL ) { try? FileManager.default.removeItem(at: directory) } }