From 7e38f2e6939aa32f3bd13b0966ceffb6532611ce Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Wed, 1 Jan 2025 22:45:10 +0100 Subject: [PATCH 1/3] Check for required files --- Package.swift | 2 +- Sources/WalletOrders/OrderBuilder.swift | 54 ++++++++----- Sources/WalletOrders/WalletOrdersError.swift | 6 ++ Sources/WalletPasses/PassBuilder.swift | 80 +++++++++++++++----- Sources/WalletPasses/WalletPassesError.swift | 24 ++++++ 5 files changed, 126 insertions(+), 40 deletions(-) diff --git a/Package.swift b/Package.swift index 189a496..2c86379 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "swift-wallet", platforms: [ - .macOS(.v11) + .macOS(.v12) ], products: [ .library(name: "WalletPasses", targets: ["WalletPasses"]), diff --git a/Sources/WalletOrders/OrderBuilder.swift b/Sources/WalletOrders/OrderBuilder.swift index 28ec859..a3ff488 100644 --- a/Sources/WalletOrders/OrderBuilder.swift +++ b/Sources/WalletOrders/OrderBuilder.swift @@ -35,21 +35,41 @@ public struct OrderBuilder: Sendable { self.openSSLURL = URL(fileURLWithPath: openSSLPath) } - private func manifest(for directory: URL) throws -> Data { - var manifest: [String: String] = [:] + private static func sourceFiles(in directory: URL) throws -> [String: Data] { + var files: [String: Data] = [:] let paths = try FileManager.default.subpathsOfDirectory(atPath: directory.path) + + guard paths.contains("order.json") else { + throw WalletOrdersError.noOrderJSONFile + } + for relativePath in paths { let file = URL(fileURLWithPath: relativePath, relativeTo: directory) guard !file.hasDirectoryPath else { continue } - let hash = try SHA256.hash(data: Data(contentsOf: file)) - manifest[relativePath] = hash.map { "0\(String($0, radix: 16))".suffix(2) }.joined() + guard FileManager.default.fileExists(atPath: file.path) else { + continue + } + + guard !(file.lastPathComponent == ".gitkeep" || file.lastPathComponent == ".DS_Store") else { + continue + } + + files[relativePath] = try Data(contentsOf: file) } - return try encoder.encode(manifest) + return files + } + + private func manifest(for sourceFiles: [String: Data]) throws -> Data { + let manifest = sourceFiles.mapValues { data in + SHA256.hash(data: data).map { "0\(String($0, radix: 16))".suffix(2) }.joined() + } + + return try self.encoder.encode(manifest) } private func signature(for manifest: Data) throws -> Data { @@ -94,7 +114,7 @@ public struct OrderBuilder: Sendable { ], certificate: Certificate(pemEncoded: self.pemCertificate), privateKey: .init(pemEncoded: self.pemPrivateKey), - signingTime: Date() + signingTime: Date.now ) return Data(signature) } @@ -122,28 +142,24 @@ public struct OrderBuilder: Sendable { try FileManager.default.copyItem(at: filesDirectory, to: tempDir) defer { try? FileManager.default.removeItem(at: tempDir) } - var files: [ArchiveFile] = [] + var archiveFiles: [ArchiveFile] = [] let orderJSON = try self.encoder.encode(order) try orderJSON.write(to: tempDir.appendingPathComponent("order.json")) - files.append(ArchiveFile(filename: "order.json", data: orderJSON)) + archiveFiles.append(ArchiveFile(filename: "order.json", data: orderJSON)) - let manifest = try self.manifest(for: tempDir) - files.append(ArchiveFile(filename: "manifest.json", data: manifest)) - try files.append(ArchiveFile(filename: "signature", data: self.signature(for: manifest))) + let sourceFiles = try Self.sourceFiles(in: tempDir) - let paths = try FileManager.default.subpathsOfDirectory(atPath: filesDirectory.path) - for relativePath in paths { - let file = URL(fileURLWithPath: relativePath, relativeTo: tempDir) - guard !file.hasDirectoryPath else { - continue - } + let manifest = try self.manifest(for: sourceFiles) + archiveFiles.append(ArchiveFile(filename: "manifest.json", data: manifest)) + try archiveFiles.append(ArchiveFile(filename: "signature", data: self.signature(for: manifest))) - try files.append(ArchiveFile(filename: relativePath, data: Data(contentsOf: file))) + for file in sourceFiles { + archiveFiles.append(ArchiveFile(filename: file.key, data: file.value)) } let zipFile = tempDir.appendingPathComponent("\(UUID().uuidString).order") - try Zip.zipData(archiveFiles: files, zipFilePath: zipFile) + try Zip.zipData(archiveFiles: archiveFiles, zipFilePath: zipFile) return try Data(contentsOf: zipFile) } } diff --git a/Sources/WalletOrders/WalletOrdersError.swift b/Sources/WalletOrders/WalletOrdersError.swift index a0b3691..4f5ab66 100644 --- a/Sources/WalletOrders/WalletOrdersError.swift +++ b/Sources/WalletOrders/WalletOrdersError.swift @@ -4,6 +4,7 @@ public struct WalletOrdersError: Error, Sendable, Equatable { public struct ErrorType: Sendable, Hashable, CustomStringConvertible, Equatable { enum Base: String, Sendable, Equatable { case noSourceFiles + case noOrderJSONFile case noOpenSSLExecutable } @@ -15,6 +16,8 @@ public struct WalletOrdersError: Error, Sendable, Equatable { /// The path for the source files is not a directory. public static let noSourceFiles = Self(.noSourceFiles) + /// The `order.json` file is missing. + public static let noOrderJSONFile = Self(.noOrderJSONFile) /// The `openssl` executable is missing. public static let noOpenSSLExecutable = Self(.noOpenSSLExecutable) @@ -48,6 +51,9 @@ public struct WalletOrdersError: Error, Sendable, Equatable { /// The path for the source files is not a directory. public static let noSourceFiles = Self(errorType: .noSourceFiles) + /// The `order.json` file is missing. + public static let noOrderJSONFile = Self(errorType: .noOrderJSONFile) + /// The `openssl` executable is missing. public static let noOpenSSLExecutable = Self(errorType: .noOpenSSLExecutable) diff --git a/Sources/WalletPasses/PassBuilder.swift b/Sources/WalletPasses/PassBuilder.swift index 06bc768..fee9daf 100644 --- a/Sources/WalletPasses/PassBuilder.swift +++ b/Sources/WalletPasses/PassBuilder.swift @@ -35,21 +35,65 @@ public struct PassBuilder: Sendable { self.openSSLURL = URL(fileURLWithPath: openSSLPath) } - private func manifest(for directory: URL) throws -> Data { - var manifest: [String: String] = [:] + private static func sourceFiles(in directory: URL, isPersonalized: Bool = false) throws -> [String: Data] { + var files: [String: Data] = [:] let paths = try FileManager.default.subpathsOfDirectory(atPath: directory.path) + + guard paths.contains("pass.json") else { + throw WalletPassesError.noPassJSONFile + } + + guard + paths.contains("icon.png") + || paths.contains("icon@1x.png") + || paths.contains("icon@2x.png") + || paths.contains("icon@3x.png") + else { + throw WalletPassesError.noIconFile + } + + if isPersonalized { + guard paths.contains("personalization.json") else { + throw WalletPassesError.noPersonalizationJSONFile + } + + guard + paths.contains("personalizationLogo.png") + || paths.contains("personalizationLogo@1x.png") + || paths.contains("personalizationLogo@2x.png") + || paths.contains("personalizationLogo@3x.png") + else { + throw WalletPassesError.noPersonalizationLogoFile + } + } + for relativePath in paths { let file = URL(fileURLWithPath: relativePath, relativeTo: directory) guard !file.hasDirectoryPath else { continue } - let hash = try Insecure.SHA1.hash(data: Data(contentsOf: file)) - manifest[relativePath] = hash.map { "0\(String($0, radix: 16))".suffix(2) }.joined() + guard FileManager.default.fileExists(atPath: file.path) else { + continue + } + + guard !(file.lastPathComponent == ".gitkeep" || file.lastPathComponent == ".DS_Store") else { + continue + } + + files[relativePath] = try Data(contentsOf: file) } - return try encoder.encode(manifest) + return files + } + + private func manifest(for sourceFiles: [String: Data]) throws -> Data { + let manifest = sourceFiles.mapValues { data in + Insecure.SHA1.hash(data: data).map { "0\(String($0, radix: 16))".suffix(2) }.joined() + } + + return try self.encoder.encode(manifest) } /// Generates a signature for a given manifest or personalization token. @@ -99,7 +143,7 @@ public struct PassBuilder: Sendable { ], certificate: Certificate(pemEncoded: self.pemCertificate), privateKey: .init(pemEncoded: self.pemPrivateKey), - signingTime: Date() + signingTime: Date.now ) return Data(signature) } @@ -129,35 +173,31 @@ public struct PassBuilder: Sendable { try FileManager.default.copyItem(at: filesDirectory, to: tempDir) defer { try? FileManager.default.removeItem(at: tempDir) } - var files: [ArchiveFile] = [] + var archiveFiles: [ArchiveFile] = [] let passJSON = try self.encoder.encode(pass) try passJSON.write(to: tempDir.appendingPathComponent("pass.json")) - files.append(ArchiveFile(filename: "pass.json", data: passJSON)) + archiveFiles.append(ArchiveFile(filename: "pass.json", data: passJSON)) // Pass Personalization if let personalization { let personalizationJSONData = try self.encoder.encode(personalization) try personalizationJSONData.write(to: tempDir.appendingPathComponent("personalization.json")) - files.append(ArchiveFile(filename: "personalization.json", data: personalizationJSONData)) + archiveFiles.append(ArchiveFile(filename: "personalization.json", data: personalizationJSONData)) } - let manifest = try self.manifest(for: tempDir) - files.append(ArchiveFile(filename: "manifest.json", data: manifest)) - try files.append(ArchiveFile(filename: "signature", data: self.signature(for: manifest))) + let sourceFiles = try Self.sourceFiles(in: tempDir, isPersonalized: personalization != nil) - let paths = try FileManager.default.subpathsOfDirectory(atPath: filesDirectory.path) - for relativePath in paths { - let file = URL(fileURLWithPath: relativePath, relativeTo: tempDir) - guard !file.hasDirectoryPath else { - continue - } + let manifest = try self.manifest(for: sourceFiles) + archiveFiles.append(ArchiveFile(filename: "manifest.json", data: manifest)) + try archiveFiles.append(ArchiveFile(filename: "signature", data: self.signature(for: manifest))) - try files.append(ArchiveFile(filename: relativePath, data: Data(contentsOf: file))) + for file in sourceFiles { + archiveFiles.append(ArchiveFile(filename: file.key, data: file.value)) } let zipFile = tempDir.appendingPathComponent("\(UUID().uuidString).pkpass") - try Zip.zipData(archiveFiles: files, zipFilePath: zipFile) + try Zip.zipData(archiveFiles: archiveFiles, zipFilePath: zipFile) return try Data(contentsOf: zipFile) } } diff --git a/Sources/WalletPasses/WalletPassesError.swift b/Sources/WalletPasses/WalletPassesError.swift index 24bed96..b652597 100644 --- a/Sources/WalletPasses/WalletPassesError.swift +++ b/Sources/WalletPasses/WalletPassesError.swift @@ -4,6 +4,10 @@ public struct WalletPassesError: Error, Sendable, Equatable { public struct ErrorType: Sendable, Hashable, CustomStringConvertible, Equatable { enum Base: String, Sendable, Equatable { case noSourceFiles + case noPassJSONFile + case noIconFile + case noPersonalizationJSONFile + case noPersonalizationLogoFile case noOpenSSLExecutable case invalidNumberOfPasses } @@ -16,6 +20,14 @@ public struct WalletPassesError: Error, Sendable, Equatable { /// The path for the source files is not a directory. public static let noSourceFiles = Self(.noSourceFiles) + /// The `pass.json` file is missing. + public static let noPassJSONFile = Self(.noPassJSONFile) + /// The `icon@XX.png` file is missing. + public static let noIconFile = Self(.noIconFile) + /// The `personalization.json` file is missing. + public static let noPersonalizationJSONFile = Self(.noPersonalizationJSONFile) + /// The `personalizationLogo@XX.png` file is missing. + public static let noPersonalizationLogoFile = Self(.noPersonalizationLogoFile) /// The `openssl` executable is missing. public static let noOpenSSLExecutable = Self(.noOpenSSLExecutable) /// The number of passes to bundle is invalid. @@ -51,6 +63,18 @@ public struct WalletPassesError: Error, Sendable, Equatable { /// The path for the source files is not a directory. public static let noSourceFiles = Self(errorType: .noSourceFiles) + /// The `pass.json` file is missing. + public static let noPassJSONFile = Self(errorType: .noPassJSONFile) + + /// The `icon@XX.png` file is missing. + public static let noIconFile = Self(errorType: .noIconFile) + + /// The `personalization.json` file is missing. + public static let noPersonalizationJSONFile = Self(errorType: .noPersonalizationJSONFile) + + /// The `personalizationLogo@XX.png` file is missing. + public static let noPersonalizationLogoFile = Self(errorType: .noPersonalizationLogoFile) + /// The `openssl` executable is missing. public static let noOpenSSLExecutable = Self(errorType: .noOpenSSLExecutable) From 4aa98b696a671ac630a038ca59eef0f190da665e Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Wed, 1 Jan 2025 23:08:55 +0100 Subject: [PATCH 2/3] Remove superfluous checks --- Sources/WalletOrders/OrderBuilder.swift | 10 +--------- Sources/WalletOrders/WalletOrdersError.swift | 6 ------ Sources/WalletPasses/PassBuilder.swift | 12 ------------ Sources/WalletPasses/WalletPassesError.swift | 12 ------------ 4 files changed, 1 insertion(+), 39 deletions(-) diff --git a/Sources/WalletOrders/OrderBuilder.swift b/Sources/WalletOrders/OrderBuilder.swift index a3ff488..4809c0c 100644 --- a/Sources/WalletOrders/OrderBuilder.swift +++ b/Sources/WalletOrders/OrderBuilder.swift @@ -39,21 +39,13 @@ public struct OrderBuilder: Sendable { var files: [String: Data] = [:] let paths = try FileManager.default.subpathsOfDirectory(atPath: directory.path) - - guard paths.contains("order.json") else { - throw WalletOrdersError.noOrderJSONFile - } - + for relativePath in paths { let file = URL(fileURLWithPath: relativePath, relativeTo: directory) guard !file.hasDirectoryPath else { continue } - guard FileManager.default.fileExists(atPath: file.path) else { - continue - } - guard !(file.lastPathComponent == ".gitkeep" || file.lastPathComponent == ".DS_Store") else { continue } diff --git a/Sources/WalletOrders/WalletOrdersError.swift b/Sources/WalletOrders/WalletOrdersError.swift index 4f5ab66..a0b3691 100644 --- a/Sources/WalletOrders/WalletOrdersError.swift +++ b/Sources/WalletOrders/WalletOrdersError.swift @@ -4,7 +4,6 @@ public struct WalletOrdersError: Error, Sendable, Equatable { public struct ErrorType: Sendable, Hashable, CustomStringConvertible, Equatable { enum Base: String, Sendable, Equatable { case noSourceFiles - case noOrderJSONFile case noOpenSSLExecutable } @@ -16,8 +15,6 @@ public struct WalletOrdersError: Error, Sendable, Equatable { /// The path for the source files is not a directory. public static let noSourceFiles = Self(.noSourceFiles) - /// The `order.json` file is missing. - public static let noOrderJSONFile = Self(.noOrderJSONFile) /// The `openssl` executable is missing. public static let noOpenSSLExecutable = Self(.noOpenSSLExecutable) @@ -51,9 +48,6 @@ public struct WalletOrdersError: Error, Sendable, Equatable { /// The path for the source files is not a directory. public static let noSourceFiles = Self(errorType: .noSourceFiles) - /// The `order.json` file is missing. - public static let noOrderJSONFile = Self(errorType: .noOrderJSONFile) - /// The `openssl` executable is missing. public static let noOpenSSLExecutable = Self(errorType: .noOpenSSLExecutable) diff --git a/Sources/WalletPasses/PassBuilder.swift b/Sources/WalletPasses/PassBuilder.swift index fee9daf..d5f35f2 100644 --- a/Sources/WalletPasses/PassBuilder.swift +++ b/Sources/WalletPasses/PassBuilder.swift @@ -40,10 +40,6 @@ public struct PassBuilder: Sendable { let paths = try FileManager.default.subpathsOfDirectory(atPath: directory.path) - guard paths.contains("pass.json") else { - throw WalletPassesError.noPassJSONFile - } - guard paths.contains("icon.png") || paths.contains("icon@1x.png") @@ -54,10 +50,6 @@ public struct PassBuilder: Sendable { } if isPersonalized { - guard paths.contains("personalization.json") else { - throw WalletPassesError.noPersonalizationJSONFile - } - guard paths.contains("personalizationLogo.png") || paths.contains("personalizationLogo@1x.png") @@ -74,10 +66,6 @@ public struct PassBuilder: Sendable { continue } - guard FileManager.default.fileExists(atPath: file.path) else { - continue - } - guard !(file.lastPathComponent == ".gitkeep" || file.lastPathComponent == ".DS_Store") else { continue } diff --git a/Sources/WalletPasses/WalletPassesError.swift b/Sources/WalletPasses/WalletPassesError.swift index b652597..dec3058 100644 --- a/Sources/WalletPasses/WalletPassesError.swift +++ b/Sources/WalletPasses/WalletPassesError.swift @@ -4,9 +4,7 @@ public struct WalletPassesError: Error, Sendable, Equatable { public struct ErrorType: Sendable, Hashable, CustomStringConvertible, Equatable { enum Base: String, Sendable, Equatable { case noSourceFiles - case noPassJSONFile case noIconFile - case noPersonalizationJSONFile case noPersonalizationLogoFile case noOpenSSLExecutable case invalidNumberOfPasses @@ -20,12 +18,8 @@ public struct WalletPassesError: Error, Sendable, Equatable { /// The path for the source files is not a directory. public static let noSourceFiles = Self(.noSourceFiles) - /// The `pass.json` file is missing. - public static let noPassJSONFile = Self(.noPassJSONFile) /// The `icon@XX.png` file is missing. public static let noIconFile = Self(.noIconFile) - /// The `personalization.json` file is missing. - public static let noPersonalizationJSONFile = Self(.noPersonalizationJSONFile) /// The `personalizationLogo@XX.png` file is missing. public static let noPersonalizationLogoFile = Self(.noPersonalizationLogoFile) /// The `openssl` executable is missing. @@ -63,15 +57,9 @@ public struct WalletPassesError: Error, Sendable, Equatable { /// The path for the source files is not a directory. public static let noSourceFiles = Self(errorType: .noSourceFiles) - /// The `pass.json` file is missing. - public static let noPassJSONFile = Self(errorType: .noPassJSONFile) - /// The `icon@XX.png` file is missing. public static let noIconFile = Self(errorType: .noIconFile) - /// The `personalization.json` file is missing. - public static let noPersonalizationJSONFile = Self(errorType: .noPersonalizationJSONFile) - /// The `personalizationLogo@XX.png` file is missing. public static let noPersonalizationLogoFile = Self(errorType: .noPersonalizationLogoFile) From fef0f17341db920190848b4065c116ba843e8b5d Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Wed, 1 Jan 2025 23:31:31 +0100 Subject: [PATCH 3/3] Add testing --- Sources/WalletOrders/OrderBuilder.swift | 2 +- Sources/WalletPasses/PassBuilder.swift | 20 ++++----- Sources/WalletPasses/WalletPassesError.swift | 12 +++--- .../WalletPassesTests/WalletPassesTests.swift | 41 +++++++++++++++++++ 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/Sources/WalletOrders/OrderBuilder.swift b/Sources/WalletOrders/OrderBuilder.swift index 4809c0c..748b48e 100644 --- a/Sources/WalletOrders/OrderBuilder.swift +++ b/Sources/WalletOrders/OrderBuilder.swift @@ -39,7 +39,7 @@ public struct OrderBuilder: Sendable { var files: [String: Data] = [:] let paths = try FileManager.default.subpathsOfDirectory(atPath: directory.path) - + for relativePath in paths { let file = URL(fileURLWithPath: relativePath, relativeTo: directory) guard !file.hasDirectoryPath else { diff --git a/Sources/WalletPasses/PassBuilder.swift b/Sources/WalletPasses/PassBuilder.swift index d5f35f2..844cbd1 100644 --- a/Sources/WalletPasses/PassBuilder.swift +++ b/Sources/WalletPasses/PassBuilder.swift @@ -40,15 +40,6 @@ public struct PassBuilder: Sendable { let paths = try FileManager.default.subpathsOfDirectory(atPath: directory.path) - guard - paths.contains("icon.png") - || paths.contains("icon@1x.png") - || paths.contains("icon@2x.png") - || paths.contains("icon@3x.png") - else { - throw WalletPassesError.noIconFile - } - if isPersonalized { guard paths.contains("personalizationLogo.png") @@ -56,10 +47,19 @@ public struct PassBuilder: Sendable { || paths.contains("personalizationLogo@2x.png") || paths.contains("personalizationLogo@3x.png") else { - throw WalletPassesError.noPersonalizationLogoFile + throw WalletPassesError.noPersonalizationLogo } } + guard + paths.contains("icon.png") + || paths.contains("icon@1x.png") + || paths.contains("icon@2x.png") + || paths.contains("icon@3x.png") + else { + throw WalletPassesError.noIcon + } + for relativePath in paths { let file = URL(fileURLWithPath: relativePath, relativeTo: directory) guard !file.hasDirectoryPath else { diff --git a/Sources/WalletPasses/WalletPassesError.swift b/Sources/WalletPasses/WalletPassesError.swift index dec3058..b968f3b 100644 --- a/Sources/WalletPasses/WalletPassesError.swift +++ b/Sources/WalletPasses/WalletPassesError.swift @@ -4,8 +4,8 @@ public struct WalletPassesError: Error, Sendable, Equatable { public struct ErrorType: Sendable, Hashable, CustomStringConvertible, Equatable { enum Base: String, Sendable, Equatable { case noSourceFiles - case noIconFile - case noPersonalizationLogoFile + case noIcon + case noPersonalizationLogo case noOpenSSLExecutable case invalidNumberOfPasses } @@ -19,9 +19,9 @@ public struct WalletPassesError: Error, Sendable, Equatable { /// The path for the source files is not a directory. public static let noSourceFiles = Self(.noSourceFiles) /// The `icon@XX.png` file is missing. - public static let noIconFile = Self(.noIconFile) + public static let noIcon = Self(.noIcon) /// The `personalizationLogo@XX.png` file is missing. - public static let noPersonalizationLogoFile = Self(.noPersonalizationLogoFile) + public static let noPersonalizationLogo = Self(.noPersonalizationLogo) /// The `openssl` executable is missing. public static let noOpenSSLExecutable = Self(.noOpenSSLExecutable) /// The number of passes to bundle is invalid. @@ -58,10 +58,10 @@ public struct WalletPassesError: Error, Sendable, Equatable { public static let noSourceFiles = Self(errorType: .noSourceFiles) /// The `icon@XX.png` file is missing. - public static let noIconFile = Self(errorType: .noIconFile) + public static let noIcon = Self(errorType: .noIcon) /// The `personalizationLogo@XX.png` file is missing. - public static let noPersonalizationLogoFile = Self(errorType: .noPersonalizationLogoFile) + public static let noPersonalizationLogo = Self(errorType: .noPersonalizationLogo) /// The `openssl` executable is missing. public static let noOpenSSLExecutable = Self(errorType: .noOpenSSLExecutable) diff --git a/Tests/WalletPassesTests/WalletPassesTests.swift b/Tests/WalletPassesTests/WalletPassesTests.swift index e013c8e..1cb317d 100644 --- a/Tests/WalletPassesTests/WalletPassesTests.swift +++ b/Tests/WalletPassesTests/WalletPassesTests.swift @@ -105,6 +105,47 @@ struct WalletPassesTests { } } + @Test("Build Pass without Icon") + func buildWithoutIcon() throws { + let builder = PassBuilder( + pemWWDRCertificate: TestCertificate.pemWWDRCertificate, + pemCertificate: TestCertificate.pemCertificate, + pemPrivateKey: TestCertificate.pemPrivateKey + ) + + #expect(throws: WalletPassesError.noIcon) { + try builder.build( + pass: pass, + sourceFilesDirectoryPath: "\(FileManager.default.currentDirectoryPath)/Tests/WalletPassesTests" + ) + } + } + + @Test("Build Personalizable Pass without Personalization Logo") + func buildPersonalizedWithoutLogo() throws { + let builder = PassBuilder( + pemWWDRCertificate: TestCertificate.pemWWDRCertificate, + pemCertificate: TestCertificate.pemCertificate, + pemPrivateKey: TestCertificate.pemPrivateKey + ) + + let testPersonalization = PersonalizationJSON( + requiredPersonalizationFields: [ + .name, + .emailAddress, + ], + description: "Test Personalization" + ) + + #expect(throws: WalletPassesError.noPersonalizationLogo) { + try builder.build( + pass: pass, + sourceFilesDirectoryPath: "\(FileManager.default.currentDirectoryPath)/Tests/WalletPassesTests", + personalization: testPersonalization + ) + } + } + private func testRoundTripped(_ bundle: Data, with personalization: PersonalizationJSON? = nil) throws { let passURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(UUID().uuidString).pkpass") try bundle.write(to: passURL)