diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index c69731e..2054aea 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,16 +7,9 @@ on:
push: { branches: [ main ] }
jobs:
- lint:
- runs-on: ubuntu-latest
- container: swift:noble
- steps:
- - name: Check out PassKit
- uses: actions/checkout@v4
- - name: Run format lint check
- run: swift format lint --strict --recursive --parallel .
-
unit-tests:
uses: vapor/ci/.github/workflows/run-unit-tests.yml@main
+ with:
+ with_linting: true
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.swift-format b/.swift-format
index 360ca2c..47901d1 100644
--- a/.swift-format
+++ b/.swift-format
@@ -11,7 +11,7 @@
"lineBreakBeforeControlFlowKeywords": false,
"lineBreakBeforeEachArgument": false,
"lineBreakBeforeEachGenericRequirement": false,
- "lineLength": 100,
+ "lineLength": 140,
"maximumBlankLines": 1,
"multiElementCollectionTrailingCommas": true,
"noAssignmentInExpressions": {
diff --git a/Package.swift b/Package.swift
index cdefd8f..4ce018b 100644
--- a/Package.swift
+++ b/Package.swift
@@ -11,13 +11,13 @@ let package = Package(
.library(name: "Orders", targets: ["Orders"]),
],
dependencies: [
- .package(url: "https://github.com/vapor/vapor.git", from: "4.105.2"),
- .package(url: "https://github.com/vapor/fluent.git", from: "4.11.0"),
+ .package(url: "https://github.com/vapor/vapor.git", from: "4.106.0"),
+ .package(url: "https://github.com/vapor/fluent.git", from: "4.12.0"),
.package(url: "https://github.com/vapor/apns.git", from: "4.2.0"),
.package(url: "https://github.com/vapor-community/Zip.git", from: "2.2.3"),
.package(url: "https://github.com/apple/swift-certificates.git", from: "1.5.0"),
// used in tests
- .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.7.4"),
+ .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.8.0"),
],
targets: [
.target(
diff --git a/README.md b/README.md
index 6f978d7..4ec7ae8 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
diff --git a/Sources/Orders/OrdersService.swift b/Sources/Orders/OrdersService.swift
index 3cc897f..2577513 100644
--- a/Sources/Orders/OrdersService.swift
+++ b/Sources/Orders/OrdersService.swift
@@ -10,8 +10,7 @@ import Vapor
/// The main class that handles Wallet orders.
public final class OrdersService: Sendable {
- private let service:
- OrdersServiceCustom
+ private let service: OrdersServiceCustom
/// Initializes the service and registers all the routes required for Apple Wallet to work.
///
diff --git a/Tests/OrdersTests/SecretMiddleware.swift b/Sources/PassKit/Testing/SecretMiddleware.swift
similarity index 52%
rename from Tests/OrdersTests/SecretMiddleware.swift
rename to Sources/PassKit/Testing/SecretMiddleware.swift
index fef1940..ec9d64f 100644
--- a/Tests/OrdersTests/SecretMiddleware.swift
+++ b/Sources/PassKit/Testing/SecretMiddleware.swift
@@ -1,11 +1,13 @@
import Vapor
-struct SecretMiddleware: AsyncMiddleware {
+package struct SecretMiddleware: AsyncMiddleware {
let secret: String
- func respond(
- to request: Request, chainingTo next: any AsyncResponder
- ) async throws -> Response {
+ package init(secret: String) {
+ self.secret = secret
+ }
+
+ package func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response {
guard request.headers.first(name: "X-Secret") == secret else {
throw Abort(.unauthorized, reason: "Incorrect X-Secret header.")
}
diff --git a/Sources/PassKit/Testing/isLoggingConfigured.swift b/Sources/PassKit/Testing/isLoggingConfigured.swift
new file mode 100644
index 0000000..ba27a1f
--- /dev/null
+++ b/Sources/PassKit/Testing/isLoggingConfigured.swift
@@ -0,0 +1,10 @@
+import Vapor
+
+package let isLoggingConfigured: Bool = {
+ LoggingSystem.bootstrap { label in
+ var handler = StreamLogHandler.standardOutput(label: label)
+ handler.logLevel = .debug
+ return handler
+ }
+ return true
+}()
diff --git a/Sources/Passes/PassesService.swift b/Sources/Passes/PassesService.swift
index 6151d48..4fa593a 100644
--- a/Sources/Passes/PassesService.swift
+++ b/Sources/Passes/PassesService.swift
@@ -72,8 +72,7 @@ public final class PassesService: Sendable {
/// - passes: The passes to include in the bundle.
/// - db: The `Database` to use.
/// - Returns: The bundle of passes as `Data`.
- public func generatePassesContent(for passes: [Pass], on db: any Database) async throws -> Data
- {
+ public func generatePassesContent(for passes: [Pass], on db: any Database) async throws -> Data {
try await service.generatePassesContent(for: passes, on: db)
}
diff --git a/Tests/OrdersTests/EncryptedOrdersTests.swift b/Tests/OrdersTests/EncryptedOrdersTests.swift
index 6e39f4c..368b7dc 100644
--- a/Tests/OrdersTests/EncryptedOrdersTests.swift
+++ b/Tests/OrdersTests/EncryptedOrdersTests.swift
@@ -1,116 +1,92 @@
-import Fluent
-import FluentSQLiteDriver
+import FluentKit
import PassKit
+import Testing
import XCTVapor
import Zip
@testable import Orders
-final class EncryptedOrdersTests: XCTestCase {
+@Suite("Orders Tests with Encrypted PEM Key")
+struct EncryptedOrdersTests {
let delegate = EncryptedOrdersDelegate()
let ordersURI = "/api/orders/v1/"
- var ordersService: OrdersService!
- var app: Application!
- override func setUp() async throws {
- self.app = try await Application.make(.testing)
- app.databases.use(.sqlite(.memory), as: .sqlite)
-
- OrdersService.register(migrations: app.migrations)
- app.migrations.add(CreateOrderData())
- ordersService = try OrdersService(
- app: app,
- delegate: delegate,
- pushRoutesMiddleware: SecretMiddleware(secret: "foo"),
- logger: app.logger
- )
- app.databases.middleware.use(OrderDataMiddleware(service: ordersService), on: .sqlite)
-
- try await app.autoMigrate()
-
- Zip.addCustomFileExtension("order")
- }
-
- override func tearDown() async throws {
- try await app.autoRevert()
- try await self.app.asyncShutdown()
- self.app = nil
+ @Test("Order Generation")
+ func orderGeneration() async throws {
+ try await withApp(delegate: delegate) { app, ordersService in
+ let orderData = OrderData(title: "Test Order")
+ try await orderData.create(on: app.db)
+ let order = try await orderData.$order.get(on: app.db)
+ let data = try await ordersService.generateOrderContent(for: order, on: app.db)
+ let orderURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.order")
+ try data.write(to: orderURL)
+ let orderFolder = try Zip.quickUnzipFile(orderURL)
+
+ #expect(FileManager.default.fileExists(atPath: orderFolder.path.appending("/signature")))
+
+ let passJSONData = try String(contentsOfFile: orderFolder.path.appending("/order.json")).data(using: .utf8)
+ let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
+ #expect(passJSON["authenticationToken"] as? String == order.authenticationToken)
+ let orderID = try order.requireID().uuidString
+ #expect(passJSON["orderIdentifier"] as? String == orderID)
+
+ let manifestJSONData = try String(contentsOfFile: orderFolder.path.appending("/manifest.json")).data(using: .utf8)
+ let manifestJSON = try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
+ let iconData = try Data(contentsOf: orderFolder.appendingPathComponent("/icon.png"))
+ let iconHash = Array(SHA256.hash(data: iconData)).hex
+ #expect(manifestJSON["icon.png"] as? String == iconHash)
+ }
}
- func testOrderGeneration() async throws {
- let orderData = OrderData(title: "Test Order")
- try await orderData.create(on: app.db)
- let order = try await orderData.$order.get(on: app.db)
- let data = try await ordersService.generateOrderContent(for: order, on: app.db)
- let orderURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.order")
- try data.write(to: orderURL)
- let orderFolder = try Zip.quickUnzipFile(orderURL)
-
- XCTAssert(FileManager.default.fileExists(atPath: orderFolder.path.appending("/signature")))
-
- let passJSONData = try String(contentsOfFile: orderFolder.path.appending("/order.json"))
- .data(using: .utf8)
- let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
- XCTAssertEqual(passJSON["authenticationToken"] as? String, order.authenticationToken)
- try XCTAssertEqual(passJSON["orderIdentifier"] as? String, order.requireID().uuidString)
-
- let manifestJSONData = try String(
- contentsOfFile: orderFolder.path.appending("/manifest.json")
- ).data(using: .utf8)
- let manifestJSON =
- try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
- let iconData = try Data(contentsOf: orderFolder.appendingPathComponent("/icon.png"))
- let iconHash = Array(SHA256.hash(data: iconData)).hex
- XCTAssertEqual(manifestJSON["icon.png"] as? String, iconHash)
- }
-
- func testAPNSClient() async throws {
- XCTAssertNotNil(app.apns.client(.init(string: "orders")))
-
- let orderData = OrderData(title: "Test Order")
- try await orderData.create(on: app.db)
- let order = try await orderData._$order.get(on: app.db)
-
- try await ordersService.sendPushNotificationsForOrder(
- id: order.requireID(), of: order.orderTypeIdentifier, on: app.db)
-
- let deviceLibraryIdentifier = "abcdefg"
- let pushToken = "1234567890"
-
- try await app.test(
- .POST,
- "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .noContent)
- }
- )
-
- try await app.test(
- .POST,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .created)
- }
- )
-
- try await app.test(
- .POST,
- "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .internalServerError)
- }
- )
-
- // Test `OrderDataMiddleware` update method
- orderData.title = "Test Order 2"
- do {
- try await orderData.update(on: app.db)
- } catch {}
+ @Test("APNS Client")
+ func apnsClient() async throws {
+ try await withApp(delegate: delegate) { app, ordersService in
+ #expect(app.apns.client(.init(string: "orders")) != nil)
+
+ let orderData = OrderData(title: "Test Order")
+ try await orderData.create(on: app.db)
+ let order = try await orderData._$order.get(on: app.db)
+
+ try await ordersService.sendPushNotificationsForOrder(id: order.requireID(), of: order.orderTypeIdentifier, on: app.db)
+
+ let deviceLibraryIdentifier = "abcdefg"
+ let pushToken = "1234567890"
+
+ try await app.test(
+ .POST,
+ "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .noContent)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .created)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .internalServerError)
+ }
+ )
+
+ // Test `OrderDataMiddleware` update method
+ orderData.title = "Test Order 2"
+ do {
+ try await orderData.update(on: app.db)
+ } catch {}
+ }
}
}
diff --git a/Tests/OrdersTests/OrdersTests.swift b/Tests/OrdersTests/OrdersTests.swift
index 9833474..c7d1557 100644
--- a/Tests/OrdersTests/OrdersTests.swift
+++ b/Tests/OrdersTests/OrdersTests.swift
@@ -1,421 +1,409 @@
-import Fluent
-import FluentSQLiteDriver
+import FluentKit
import PassKit
+import Testing
import XCTVapor
import Zip
@testable import Orders
-final class OrdersTests: XCTestCase {
+@Suite("Orders Tests")
+struct OrdersTests {
let delegate = TestOrdersDelegate()
let ordersURI = "/api/orders/v1/"
- var ordersService: OrdersService!
- var app: Application!
-
- override func setUp() async throws {
- self.app = try await Application.make(.testing)
- app.databases.use(.sqlite(.memory), as: .sqlite)
-
- OrdersService.register(migrations: app.migrations)
- app.migrations.add(CreateOrderData())
- ordersService = try OrdersService(
- app: app,
- delegate: delegate,
- pushRoutesMiddleware: SecretMiddleware(secret: "foo"),
- logger: app.logger
- )
- app.databases.middleware.use(OrderDataMiddleware(service: ordersService), on: .sqlite)
-
- try await app.autoMigrate()
-
- Zip.addCustomFileExtension("order")
- }
-
- override func tearDown() async throws {
- try await app.autoRevert()
- try await self.app.asyncShutdown()
- self.app = nil
- }
- func testOrderGeneration() async throws {
- let orderData = OrderData(title: "Test Order")
- try await orderData.create(on: app.db)
- let order = try await orderData.$order.get(on: app.db)
- let data = try await ordersService.generateOrderContent(for: order, on: app.db)
- let orderURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.order")
- try data.write(to: orderURL)
- let orderFolder = try Zip.quickUnzipFile(orderURL)
-
- XCTAssert(FileManager.default.fileExists(atPath: orderFolder.path.appending("/signature")))
-
- let passJSONData = try String(contentsOfFile: orderFolder.path.appending("/order.json"))
- .data(using: .utf8)
- let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
- XCTAssertEqual(passJSON["authenticationToken"] as? String, order.authenticationToken)
- try XCTAssertEqual(passJSON["orderIdentifier"] as? String, order.requireID().uuidString)
-
- let manifestJSONData = try String(
- contentsOfFile: orderFolder.path.appending("/manifest.json")
- ).data(using: .utf8)
- let manifestJSON =
- try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
- let iconData = try Data(contentsOf: orderFolder.appendingPathComponent("/icon.png"))
- let iconHash = Array(SHA256.hash(data: iconData)).hex
- XCTAssertEqual(manifestJSON["icon.png"] as? String, iconHash)
- XCTAssertNotNil(manifestJSON["pet_store_logo.png"])
+ @Test("Order Generation")
+ func orderGeneration() async throws {
+ try await withApp(delegate: delegate) { app, ordersService in
+ let orderData = OrderData(title: "Test Order")
+ try await orderData.create(on: app.db)
+ let order = try await orderData.$order.get(on: app.db)
+ let data = try await ordersService.generateOrderContent(for: order, on: app.db)
+ let orderURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.order")
+ try data.write(to: orderURL)
+ let orderFolder = try Zip.quickUnzipFile(orderURL)
+
+ #expect(FileManager.default.fileExists(atPath: orderFolder.path.appending("/signature")))
+
+ let passJSONData = try String(contentsOfFile: orderFolder.path.appending("/order.json")).data(using: .utf8)
+ let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
+ #expect(passJSON["authenticationToken"] as? String == order.authenticationToken)
+ let orderID = try order.requireID().uuidString
+ #expect(passJSON["orderIdentifier"] as? String == orderID)
+
+ let manifestJSONData = try String(contentsOfFile: orderFolder.path.appending("/manifest.json")).data(using: .utf8)
+ let manifestJSON = try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
+ let iconData = try Data(contentsOf: orderFolder.appendingPathComponent("/icon.png"))
+ let iconHash = Array(SHA256.hash(data: iconData)).hex
+ #expect(manifestJSON["icon.png"] as? String == iconHash)
+ #expect(manifestJSON["pet_store_logo.png"] != nil)
+ }
}
- // Tests the API Apple Wallet calls to get orders
- func testGetOrderFromAPI() async throws {
- let orderData = OrderData(title: "Test Order")
- try await orderData.create(on: app.db)
- let order = try await orderData.$order.get(on: app.db)
-
- try await app.test(
- .GET,
- "\(ordersURI)orders/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: [
- "Authorization": "AppleOrder \(order.authenticationToken)",
- "If-Modified-Since": "0",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- XCTAssertNotNil(res.body)
- XCTAssertEqual(res.headers.contentType?.description, "application/vnd.apple.order")
- XCTAssertNotNil(res.headers.lastModified)
- }
- )
-
- // Test call with invalid authentication token
- try await app.test(
- .GET,
- "\(ordersURI)orders/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: [
- "Authorization": "AppleOrder invalidToken",
- "If-Modified-Since": "0",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .unauthorized)
- }
- )
-
- // Test distant future `If-Modified-Since` date
- try await app.test(
- .GET,
- "\(ordersURI)orders/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: [
- "Authorization": "AppleOrder \(order.authenticationToken)",
- "If-Modified-Since": "2147483647",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .notModified)
- }
- )
-
- // Test call with invalid order ID
- try await app.test(
- .GET,
- "\(ordersURI)orders/\(order.orderTypeIdentifier)/invalidID",
- headers: [
- "Authorization": "AppleOrder \(order.authenticationToken)",
- "If-Modified-Since": "0",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- // Test call with invalid order type identifier
- try await app.test(
- .GET,
- "\(ordersURI)orders/order.com.example.InvalidType/\(order.requireID())",
- headers: [
- "Authorization": "AppleOrder \(order.authenticationToken)",
- "If-Modified-Since": "0",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .notFound)
- }
- )
+ @Test("Getting Order from Apple Wallet API")
+ func getOrderFromAPI() async throws {
+ try await withApp(delegate: delegate) { app, ordersService in
+ let orderData = OrderData(title: "Test Order")
+ try await orderData.create(on: app.db)
+ let order = try await orderData.$order.get(on: app.db)
+
+ try await app.test(
+ .GET,
+ "\(ordersURI)orders/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: [
+ "Authorization": "AppleOrder \(order.authenticationToken)",
+ "If-Modified-Since": "0",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ #expect(res.body != nil)
+ #expect(res.headers.contentType?.description == "application/vnd.apple.order")
+ #expect(res.headers.lastModified != nil)
+ }
+ )
+
+ // Test call with invalid authentication token
+ try await app.test(
+ .GET,
+ "\(ordersURI)orders/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: [
+ "Authorization": "AppleOrder invalidToken",
+ "If-Modified-Since": "0",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .unauthorized)
+ }
+ )
+
+ // Test distant future `If-Modified-Since` date
+ try await app.test(
+ .GET,
+ "\(ordersURI)orders/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: [
+ "Authorization": "AppleOrder \(order.authenticationToken)",
+ "If-Modified-Since": "2147483647",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .notModified)
+ }
+ )
+
+ // Test call with invalid order ID
+ try await app.test(
+ .GET,
+ "\(ordersURI)orders/\(order.orderTypeIdentifier)/invalidID",
+ headers: [
+ "Authorization": "AppleOrder \(order.authenticationToken)",
+ "If-Modified-Since": "0",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test call with invalid order type identifier
+ try await app.test(
+ .GET,
+ "\(ordersURI)orders/order.com.example.InvalidType/\(order.requireID())",
+ headers: [
+ "Authorization": "AppleOrder \(order.authenticationToken)",
+ "If-Modified-Since": "0",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .notFound)
+ }
+ )
+ }
}
- func testAPIDeviceRegistration() async throws {
- let orderData = OrderData(title: "Test Order")
- try await orderData.create(on: app.db)
- let order = try await orderData.$order.get(on: app.db)
- let deviceLibraryIdentifier = "abcdefg"
- let pushToken = "1234567890"
-
- try await app.test(
- .GET,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)?ordersModifiedSince=0",
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .noContent)
- }
- )
-
- try await app.test(
- .DELETE,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .notFound)
- }
- )
-
- // Test registration without authentication token
- try await app.test(
- .POST,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .unauthorized)
- }
- )
-
- // Test registration of a non-existing order
- try await app.test(
- .POST,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\("order.com.example.NotFound")/\(UUID().uuidString)",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .notFound)
- }
- )
-
- // Test call without DTO
- try await app.test(
- .POST,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- // Test call with invalid UUID
- try await app.test(
- .POST,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\("not-a-uuid")",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- try await app.test(
- .POST,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .created)
- }
- )
-
- // Test registration of an already registered device
- try await app.test(
- .POST,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- }
- )
-
- try await app.test(
- .GET,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)?ordersModifiedSince=0",
- afterResponse: { res async throws in
- let orders = try res.content.decode(OrdersForDeviceDTO.self)
- XCTAssertEqual(orders.orderIdentifiers.count, 1)
- let orderID = try order.requireID()
- XCTAssertEqual(orders.orderIdentifiers[0], orderID.uuidString)
- XCTAssertEqual(orders.lastModified, String(order.updatedAt!.timeIntervalSince1970))
- }
- )
-
- try await app.test(
- .GET,
- "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- let pushTokens = try res.content.decode([String].self)
- XCTAssertEqual(pushTokens.count, 1)
- XCTAssertEqual(pushTokens[0], pushToken)
- }
- )
-
- // Test call with invalid UUID
- try await app.test(
- .GET,
- "\(ordersURI)push/\(order.orderTypeIdentifier)/\("not-a-uuid")",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- // Test call with invalid UUID
- try await app.test(
- .DELETE,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\("not-a-uuid")",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- try await app.test(
- .DELETE,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- }
- )
+ @Test("Device Registration API")
+ func apiDeviceRegistration() async throws {
+ try await withApp(delegate: delegate) { app, ordersService in
+ let orderData = OrderData(title: "Test Order")
+ try await orderData.create(on: app.db)
+ let order = try await orderData.$order.get(on: app.db)
+ let deviceLibraryIdentifier = "abcdefg"
+ let pushToken = "1234567890"
+
+ try await app.test(
+ .GET,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)?ordersModifiedSince=0",
+ afterResponse: { res async throws in
+ #expect(res.status == .noContent)
+ }
+ )
+
+ try await app.test(
+ .DELETE,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ afterResponse: { res async throws in
+ #expect(res.status == .notFound)
+ }
+ )
+
+ // Test registration without authentication token
+ try await app.test(
+ .POST,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .unauthorized)
+ }
+ )
+
+ // Test registration of a non-existing order
+ try await app.test(
+ .POST,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\("order.com.example.NotFound")/\(UUID().uuidString)",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .notFound)
+ }
+ )
+
+ // Test call without DTO
+ try await app.test(
+ .POST,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test call with invalid UUID
+ try await app.test(
+ .POST,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\("not-a-uuid")",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .created)
+ }
+ )
+
+ // Test registration of an already registered device
+ try await app.test(
+ .POST,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ }
+ )
+
+ try await app.test(
+ .GET,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)?ordersModifiedSince=0",
+ afterResponse: { res async throws in
+ let orders = try res.content.decode(OrdersForDeviceDTO.self)
+ #expect(orders.orderIdentifiers.count == 1)
+ let orderID = try order.requireID()
+ #expect(orders.orderIdentifiers[0] == orderID.uuidString)
+ #expect(orders.lastModified == String(order.updatedAt!.timeIntervalSince1970))
+ }
+ )
+
+ try await app.test(
+ .GET,
+ "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ let pushTokens = try res.content.decode([String].self)
+ #expect(pushTokens.count == 1)
+ #expect(pushTokens[0] == pushToken)
+ }
+ )
+
+ // Test call with invalid UUID
+ try await app.test(
+ .GET,
+ "\(ordersURI)push/\(order.orderTypeIdentifier)/\("not-a-uuid")",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test call with invalid UUID
+ try await app.test(
+ .DELETE,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\("not-a-uuid")",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ try await app.test(
+ .DELETE,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ }
+ )
+ }
}
- func testErrorLog() async throws {
- let log1 = "Error 1"
- let log2 = "Error 2"
-
- try await app.test(
- .POST,
- "\(ordersURI)log",
- beforeRequest: { req async throws in
- try req.content.encode(ErrorLogDTO(logs: [log1, log2]))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- }
- )
-
- let logs = try await OrdersErrorLog.query(on: app.db).all()
- XCTAssertEqual(logs.count, 2)
- XCTAssertEqual(logs[0].message, log1)
- XCTAssertEqual(logs[1]._$message.value, log2)
-
- // Test call with no DTO
- try await app.test(
- .POST,
- "\(ordersURI)log",
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- // Test call with empty logs
- try await app.test(
- .POST,
- "\(ordersURI)log",
- beforeRequest: { req async throws in
- try req.content.encode(ErrorLogDTO(logs: []))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
+ @Test("Error Logging")
+ func errorLog() async throws {
+ try await withApp(delegate: delegate) { app, ordersService in
+ let log1 = "Error 1"
+ let log2 = "Error 2"
+
+ try await app.test(
+ .POST,
+ "\(ordersURI)log",
+ beforeRequest: { req async throws in
+ try req.content.encode(ErrorLogDTO(logs: [log1, log2]))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ }
+ )
+
+ let logs = try await OrdersErrorLog.query(on: app.db).all()
+ #expect(logs.count == 2)
+ #expect(logs[0].message == log1)
+ #expect(logs[1]._$message.value == log2)
+
+ // Test call with no DTO
+ try await app.test(
+ .POST,
+ "\(ordersURI)log",
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test call with empty logs
+ try await app.test(
+ .POST,
+ "\(ordersURI)log",
+ beforeRequest: { req async throws in
+ try req.content.encode(ErrorLogDTO(logs: []))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+ }
}
- func testAPNSClient() async throws {
- XCTAssertNotNil(app.apns.client(.init(string: "orders")))
-
- let orderData = OrderData(title: "Test Order")
- try await orderData.create(on: app.db)
- let order = try await orderData._$order.get(on: app.db)
-
- try await ordersService.sendPushNotificationsForOrder(
- id: order.requireID(), of: order.orderTypeIdentifier, on: app.db)
-
- let deviceLibraryIdentifier = "abcdefg"
- let pushToken = "1234567890"
-
- try await app.test(
- .POST,
- "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .noContent)
- }
- )
-
- try await app.test(
- .POST,
- "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .created)
- }
- )
-
- try await app.test(
- .POST,
- "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .internalServerError)
- }
- )
-
- // Test call with invalid UUID
- try await app.test(
- .POST,
- "\(ordersURI)push/\(order.orderTypeIdentifier)/\("not-a-uuid")",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
+ @Test("APNS Client")
+ func apnsClient() async throws {
+ try await withApp(delegate: delegate) { app, ordersService in
+ #expect(app.apns.client(.init(string: "orders")) != nil)
+
+ let orderData = OrderData(title: "Test Order")
+ try await orderData.create(on: app.db)
+ let order = try await orderData._$order.get(on: app.db)
+
+ try await ordersService.sendPushNotificationsForOrder(id: order.requireID(), of: order.orderTypeIdentifier, on: app.db)
+
+ let deviceLibraryIdentifier = "abcdefg"
+ let pushToken = "1234567890"
+
+ // Test call with incorrect secret
+ try await app.test(
+ .POST,
+ "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["X-Secret": "bar"],
+ afterResponse: { res async throws in
+ #expect(res.status == .unauthorized)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .noContent)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .created)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .internalServerError)
+ }
+ )
+
+ // Test call with invalid UUID
+ try await app.test(
+ .POST,
+ "\(ordersURI)push/\(order.orderTypeIdentifier)/\("not-a-uuid")",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test `OrderDataMiddleware` update method
+ orderData.title = "Test Order 2"
+ do {
+ try await orderData.update(on: app.db)
+ } catch let error as HTTPClientError {
+ #expect(error.self == .remoteConnectionClosed)
}
- )
-
- // Test `OrderDataMiddleware` update method
- orderData.title = "Test Order 2"
- do {
- try await orderData.update(on: app.db)
- } catch let error as HTTPClientError {
- XCTAssertEqual(error.self, .remoteConnectionClosed)
}
}
- func testOrdersError() {
- XCTAssertEqual(
- OrdersError.templateNotDirectory.description,
- "OrdersError(errorType: templateNotDirectory)")
- XCTAssertEqual(
- OrdersError.pemCertificateMissing.description,
- "OrdersError(errorType: pemCertificateMissing)")
- XCTAssertEqual(
- OrdersError.pemPrivateKeyMissing.description,
- "OrdersError(errorType: pemPrivateKeyMissing)")
- XCTAssertEqual(
- OrdersError.opensslBinaryMissing.description,
- "OrdersError(errorType: opensslBinaryMissing)")
+ @Test("OrdersError")
+ func ordersError() {
+ #expect(OrdersError.templateNotDirectory.description == "OrdersError(errorType: templateNotDirectory)")
+ #expect(OrdersError.pemCertificateMissing.description == "OrdersError(errorType: pemCertificateMissing)")
+ #expect(OrdersError.pemPrivateKeyMissing.description == "OrdersError(errorType: pemPrivateKeyMissing)")
+ #expect(OrdersError.opensslBinaryMissing.description == "OrdersError(errorType: opensslBinaryMissing)")
}
- func testDefaultDelegate() {
+ @Test("Default OrdersDelegate Properties")
+ func defaultDelegate() {
let delegate = DefaultOrdersDelegate()
- XCTAssertEqual(delegate.wwdrCertificate, "WWDR.pem")
- XCTAssertEqual(delegate.pemCertificate, "ordercertificate.pem")
- XCTAssertEqual(delegate.pemPrivateKey, "orderkey.pem")
- XCTAssertNil(delegate.pemPrivateKeyPassword)
- XCTAssertEqual(delegate.sslBinary, URL(fileURLWithPath: "/usr/bin/openssl"))
- XCTAssertFalse(delegate.generateSignatureFile(in: URL(fileURLWithPath: "")))
+ #expect(delegate.wwdrCertificate == "WWDR.pem")
+ #expect(delegate.pemCertificate == "ordercertificate.pem")
+ #expect(delegate.pemPrivateKey == "orderkey.pem")
+ #expect(delegate.pemPrivateKeyPassword == nil)
+ #expect(delegate.sslBinary == URL(fileURLWithPath: "/usr/bin/openssl"))
+ #expect(!delegate.generateSignatureFile(in: URL(fileURLWithPath: "")))
}
}
@@ -424,9 +412,7 @@ final class DefaultOrdersDelegate: OrdersDelegate {
func template(for order: O, db: any Database) async throws -> URL {
URL(fileURLWithPath: "")
}
- func encode(
- order: O, db: any Database, encoder: JSONEncoder
- ) async throws -> Data {
+ func encode(order: O, db: any Database, encoder: JSONEncoder) async throws -> Data {
Data()
}
}
diff --git a/Tests/OrdersTests/withApp.swift b/Tests/OrdersTests/withApp.swift
new file mode 100644
index 0000000..ab5df1d
--- /dev/null
+++ b/Tests/OrdersTests/withApp.swift
@@ -0,0 +1,38 @@
+import FluentKit
+import FluentSQLiteDriver
+import Orders
+import PassKit
+import Testing
+import Vapor
+import Zip
+
+func withApp(
+ delegate: some OrdersDelegate,
+ _ body: (Application, OrdersService) async throws -> Void
+) async throws {
+ let app = try await Application.make(.testing)
+
+ try #require(isLoggingConfigured)
+
+ app.databases.use(.sqlite(.memory), as: .sqlite)
+
+ OrdersService.register(migrations: app.migrations)
+ app.migrations.add(CreateOrderData())
+ let passesService = try OrdersService(
+ app: app,
+ delegate: delegate,
+ pushRoutesMiddleware: SecretMiddleware(secret: "foo"),
+ logger: app.logger
+ )
+
+ app.databases.middleware.use(OrderDataMiddleware(service: passesService), on: .sqlite)
+
+ try await app.autoMigrate()
+
+ Zip.addCustomFileExtension("order")
+
+ try await body(app, passesService)
+
+ try await app.autoRevert()
+ try await app.asyncShutdown()
+}
diff --git a/Tests/PassesTests/EncryptedPassesTests.swift b/Tests/PassesTests/EncryptedPassesTests.swift
index 971f850..f533c6e 100644
--- a/Tests/PassesTests/EncryptedPassesTests.swift
+++ b/Tests/PassesTests/EncryptedPassesTests.swift
@@ -1,177 +1,139 @@
-import Fluent
-import FluentSQLiteDriver
import PassKit
+import Testing
import XCTVapor
import Zip
@testable import Passes
-final class EncryptedPassesTests: XCTestCase {
+@Suite("Passes Tests with Encrypted PEM Key")
+struct EncryptedPassesTests {
let delegate = EncryptedPassesDelegate()
let passesURI = "/api/passes/v1/"
- var passesService: PassesService!
- var app: Application!
-
- override func setUp() async throws {
- self.app = try await Application.make(.testing)
- app.databases.use(.sqlite(.memory), as: .sqlite)
-
- PassesService.register(migrations: app.migrations)
- app.migrations.add(CreatePassData())
- passesService = try PassesService(
- app: app,
- delegate: delegate,
- pushRoutesMiddleware: SecretMiddleware(secret: "foo"),
- logger: app.logger
- )
- app.databases.middleware.use(PassDataMiddleware(service: passesService), on: .sqlite)
-
- try await app.autoMigrate()
-
- Zip.addCustomFileExtension("pkpass")
- }
- override func tearDown() async throws {
- try await app.autoRevert()
- try await self.app.asyncShutdown()
- self.app = nil
+ @Test("Pass Generation")
+ func passGeneration() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ let passData = PassData(title: "Test Pass")
+ try await passData.create(on: app.db)
+ let pass = try await passData.$pass.get(on: app.db)
+ let data = try await passesService.generatePassContent(for: pass, on: app.db)
+ let passURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.pkpass")
+ try data.write(to: passURL)
+ let passFolder = try Zip.quickUnzipFile(passURL)
+
+ #expect(FileManager.default.fileExists(atPath: passFolder.path.appending("/signature")))
+
+ let passJSONData = try String(contentsOfFile: passFolder.path.appending("/pass.json")).data(using: .utf8)
+ let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
+ #expect(passJSON["authenticationToken"] as? String == pass.authenticationToken)
+ let passID = try pass.requireID().uuidString
+ #expect(passJSON["serialNumber"] as? String == passID)
+ #expect(passJSON["description"] as? String == passData.title)
+
+ let manifestJSONData = try String(contentsOfFile: passFolder.path.appending("/manifest.json")).data(using: .utf8)
+ let manifestJSON = try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
+ let iconData = try Data(contentsOf: passFolder.appendingPathComponent("/icon.png"))
+ let iconHash = Array(Insecure.SHA1.hash(data: iconData)).hex
+ #expect(manifestJSON["icon.png"] as? String == iconHash)
+ }
}
- func testPassGeneration() async throws {
- let passData = PassData(title: "Test Pass")
- try await passData.create(on: app.db)
- let pass = try await passData.$pass.get(on: app.db)
- let data = try await passesService.generatePassContent(for: pass, on: app.db)
- let passURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.pkpass")
- try data.write(to: passURL)
- let passFolder = try Zip.quickUnzipFile(passURL)
-
- XCTAssert(FileManager.default.fileExists(atPath: passFolder.path.appending("/signature")))
-
- let passJSONData = try String(contentsOfFile: passFolder.path.appending("/pass.json")).data(
- using: .utf8)
- let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
- XCTAssertEqual(passJSON["authenticationToken"] as? String, pass.authenticationToken)
- try XCTAssertEqual(passJSON["serialNumber"] as? String, pass.requireID().uuidString)
- XCTAssertEqual(passJSON["description"] as? String, passData.title)
-
- let manifestJSONData = try String(
- contentsOfFile: passFolder.path.appending("/manifest.json")
- ).data(using: .utf8)
- let manifestJSON =
- try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
- let iconData = try Data(contentsOf: passFolder.appendingPathComponent("/icon.png"))
- let iconHash = Array(Insecure.SHA1.hash(data: iconData)).hex
- XCTAssertEqual(manifestJSON["icon.png"] as? String, iconHash)
- }
+ @Test("Personalizable Pass Apple Wallet API")
+ func personalizationAPI() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ let passData = PassData(title: "Personalize")
+ try await passData.create(on: app.db)
+ let pass = try await passData.$pass.get(on: app.db)
+ let personalizationDict = PersonalizationDictionaryDTO(
+ personalizationToken: "1234567890",
+ requiredPersonalizationInfo: .init(
+ emailAddress: "test@example.com",
+ familyName: "Doe",
+ fullName: "John Doe",
+ givenName: "John",
+ isoCountryCode: "US",
+ phoneNumber: "1234567890",
+ postalCode: "12345"
+ )
+ )
- func testPersonalizationAPI() async throws {
- let passData = PassData(title: "Personalize")
- try await passData.create(on: app.db)
- let pass = try await passData.$pass.get(on: app.db)
- let personalizationDict = PersonalizationDictionaryDTO(
- personalizationToken: "1234567890",
- requiredPersonalizationInfo: .init(
- emailAddress: "test@example.com",
- familyName: "Doe",
- fullName: "John Doe",
- givenName: "John",
- isoCountryCode: "US",
- phoneNumber: "1234567890",
- postalCode: "12345"
+ try await app.test(
+ .POST,
+ "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())/personalize",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(personalizationDict)
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ #expect(res.body != nil)
+ #expect(res.headers.contentType?.description == "application/octet-stream")
+ }
)
- )
-
- try await app.test(
- .POST,
- "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())/personalize",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(personalizationDict)
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- XCTAssertNotNil(res.body)
- XCTAssertEqual(res.headers.contentType?.description, "application/octet-stream")
- }
- )
-
- let personalizationQuery = try await UserPersonalization.query(on: app.db).all()
- XCTAssertEqual(personalizationQuery.count, 1)
- let passPersonalizationID = try await Pass.query(on: app.db).first()?
- ._$userPersonalization.get(on: app.db)?
- .requireID()
- XCTAssertEqual(personalizationQuery[0]._$id.value, passPersonalizationID)
- XCTAssertEqual(
- personalizationQuery[0]._$emailAddress.value,
- personalizationDict.requiredPersonalizationInfo.emailAddress)
- XCTAssertEqual(
- personalizationQuery[0]._$familyName.value,
- personalizationDict.requiredPersonalizationInfo.familyName)
- XCTAssertEqual(
- personalizationQuery[0]._$fullName.value,
- personalizationDict.requiredPersonalizationInfo.fullName)
- XCTAssertEqual(
- personalizationQuery[0]._$givenName.value,
- personalizationDict.requiredPersonalizationInfo.givenName)
- XCTAssertEqual(
- personalizationQuery[0]._$isoCountryCode.value,
- personalizationDict.requiredPersonalizationInfo.isoCountryCode)
- XCTAssertEqual(
- personalizationQuery[0]._$phoneNumber.value,
- personalizationDict.requiredPersonalizationInfo.phoneNumber)
- XCTAssertEqual(
- personalizationQuery[0]._$postalCode.value,
- personalizationDict.requiredPersonalizationInfo.postalCode)
+
+ let personalizationQuery = try await UserPersonalization.query(on: app.db).all()
+ #expect(personalizationQuery.count == 1)
+ let passPersonalizationID = try await Pass.query(on: app.db).first()?._$userPersonalization.get(on: app.db)?.requireID()
+ #expect(personalizationQuery[0]._$id.value == passPersonalizationID)
+ #expect(personalizationQuery[0]._$emailAddress.value == personalizationDict.requiredPersonalizationInfo.emailAddress)
+ #expect(personalizationQuery[0]._$familyName.value == personalizationDict.requiredPersonalizationInfo.familyName)
+ #expect(personalizationQuery[0]._$fullName.value == personalizationDict.requiredPersonalizationInfo.fullName)
+ #expect(personalizationQuery[0]._$givenName.value == personalizationDict.requiredPersonalizationInfo.givenName)
+ #expect(personalizationQuery[0]._$isoCountryCode.value == personalizationDict.requiredPersonalizationInfo.isoCountryCode)
+ #expect(personalizationQuery[0]._$phoneNumber.value == personalizationDict.requiredPersonalizationInfo.phoneNumber)
+ #expect(personalizationQuery[0]._$postalCode.value == personalizationDict.requiredPersonalizationInfo.postalCode)
+ }
}
- func testAPNSClient() async throws {
- XCTAssertNotNil(app.apns.client(.init(string: "passes")))
-
- let passData = PassData(title: "Test Pass")
- try await passData.create(on: app.db)
- let pass = try await passData._$pass.get(on: app.db)
-
- try await passesService.sendPushNotificationsForPass(
- id: pass.requireID(), of: pass.passTypeIdentifier, on: app.db)
-
- let deviceLibraryIdentifier = "abcdefg"
- let pushToken = "1234567890"
-
- try await app.test(
- .POST,
- "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .noContent)
- }
- )
-
- try await app.test(
- .POST,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .created)
- }
- )
-
- try await app.test(
- .POST,
- "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .internalServerError)
- }
- )
-
- // Test `PassDataMiddleware` update method
- passData.title = "Test Pass 2"
- do {
- try await passData.update(on: app.db)
- } catch {}
+ @Test("APNS Client")
+ func apnsClient() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ #expect(app.apns.client(.init(string: "passes")) != nil)
+
+ let passData = PassData(title: "Test Pass")
+ try await passData.create(on: app.db)
+ let pass = try await passData._$pass.get(on: app.db)
+
+ try await passesService.sendPushNotificationsForPass(id: pass.requireID(), of: pass.passTypeIdentifier, on: app.db)
+
+ let deviceLibraryIdentifier = "abcdefg"
+ let pushToken = "1234567890"
+
+ try await app.test(
+ .POST,
+ "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .noContent)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .created)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .internalServerError)
+ }
+ )
+
+ // Test `PassDataMiddleware` update method
+ passData.title = "Test Pass 2"
+ do {
+ try await passData.update(on: app.db)
+ } catch {}
+ }
}
}
diff --git a/Tests/PassesTests/PassesTests.swift b/Tests/PassesTests/PassesTests.swift
index 6720847..7742d25 100644
--- a/Tests/PassesTests/PassesTests.swift
+++ b/Tests/PassesTests/PassesTests.swift
@@ -1,570 +1,543 @@
-import Fluent
-import FluentSQLiteDriver
+import FluentKit
import PassKit
+import Testing
import XCTVapor
import Zip
@testable import Passes
-final class PassesTests: XCTestCase {
+@Suite("Passes Tests")
+struct PassesTests {
let delegate = TestPassesDelegate()
let passesURI = "/api/passes/v1/"
- var passesService: PassesService!
- var app: Application!
-
- override func setUp() async throws {
- self.app = try await Application.make(.testing)
- app.databases.use(.sqlite(.memory), as: .sqlite)
-
- PassesService.register(migrations: app.migrations)
- app.migrations.add(CreatePassData())
- passesService = try PassesService(
- app: app,
- delegate: delegate,
- pushRoutesMiddleware: SecretMiddleware(secret: "foo"),
- logger: app.logger
- )
- app.databases.middleware.use(PassDataMiddleware(service: passesService), on: .sqlite)
-
- try await app.autoMigrate()
-
- Zip.addCustomFileExtension("pkpass")
+
+ @Test("Pass Generation")
+ func passGeneration() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ let passData = PassData(title: "Test Pass")
+ try await passData.create(on: app.db)
+ let pass = try await passData.$pass.get(on: app.db)
+ let data = try await passesService.generatePassContent(for: pass, on: app.db)
+ let passURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.pkpass")
+ try data.write(to: passURL)
+ let passFolder = try Zip.quickUnzipFile(passURL)
+
+ #expect(FileManager.default.fileExists(atPath: passFolder.path.appending("/signature")))
+
+ let passJSONData = try String(contentsOfFile: passFolder.path.appending("/pass.json")).data(using: .utf8)
+ let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
+ #expect(passJSON["authenticationToken"] as? String == pass.authenticationToken)
+ let passID = try pass.requireID().uuidString
+ #expect(passJSON["serialNumber"] as? String == passID)
+ #expect(passJSON["description"] as? String == passData.title)
+
+ let manifestJSONData = try String(contentsOfFile: passFolder.path.appending("/manifest.json")).data(using: .utf8)
+ let manifestJSON = try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
+ let iconData = try Data(contentsOf: passFolder.appendingPathComponent("/icon.png"))
+ let iconHash = Array(Insecure.SHA1.hash(data: iconData)).hex
+ #expect(manifestJSON["icon.png"] as? String == iconHash)
+ #expect(manifestJSON["logo.png"] != nil)
+ #expect(manifestJSON["personalizationLogo.png"] != nil)
+ }
}
- override func tearDown() async throws {
- try await app.autoRevert()
- try await self.app.asyncShutdown()
- self.app = nil
+ @Test("Generating Multiple Passes")
+ func passesGeneration() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ let passData1 = PassData(title: "Test Pass 1")
+ try await passData1.create(on: app.db)
+ let pass1 = try await passData1.$pass.get(on: app.db)
+
+ let passData2 = PassData(title: "Test Pass 2")
+ try await passData2.create(on: app.db)
+ let pass2 = try await passData2._$pass.get(on: app.db)
+
+ let data = try await passesService.generatePassesContent(for: [pass1, pass2], on: app.db)
+ #expect(data != nil)
+
+ do {
+ let data = try await passesService.generatePassesContent(for: [pass1], on: app.db)
+ Issue.record("Expected error, got \(data)")
+ } catch let error as PassesError {
+ #expect(error == .invalidNumberOfPasses)
+ }
+ }
}
- func testPassGeneration() async throws {
- let passData = PassData(title: "Test Pass")
- try await passData.create(on: app.db)
- let pass = try await passData.$pass.get(on: app.db)
- let data = try await passesService.generatePassContent(for: pass, on: app.db)
- let passURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.pkpass")
- try data.write(to: passURL)
- let passFolder = try Zip.quickUnzipFile(passURL)
-
- XCTAssert(FileManager.default.fileExists(atPath: passFolder.path.appending("/signature")))
-
- let passJSONData = try String(contentsOfFile: passFolder.path.appending("/pass.json")).data(
- using: .utf8)
- let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
- XCTAssertEqual(passJSON["authenticationToken"] as? String, pass.authenticationToken)
- try XCTAssertEqual(passJSON["serialNumber"] as? String, pass.requireID().uuidString)
- XCTAssertEqual(passJSON["description"] as? String, passData.title)
-
- let manifestJSONData = try String(
- contentsOfFile: passFolder.path.appending("/manifest.json")
- ).data(using: .utf8)
- let manifestJSON =
- try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
- let iconData = try Data(contentsOf: passFolder.appendingPathComponent("/icon.png"))
- let iconHash = Array(Insecure.SHA1.hash(data: iconData)).hex
- XCTAssertEqual(manifestJSON["icon.png"] as? String, iconHash)
- XCTAssertNotNil(manifestJSON["logo.png"])
- XCTAssertNotNil(manifestJSON["personalizationLogo.png"])
+ @Test("Personalizable Passes")
+ func personalization() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ let passData = PassData(title: "Personalize")
+ try await passData.create(on: app.db)
+ let pass = try await passData.$pass.get(on: app.db)
+ let data = try await passesService.generatePassContent(for: pass, on: app.db)
+ let passURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.pkpass")
+ try data.write(to: passURL)
+ let passFolder = try Zip.quickUnzipFile(passURL)
+
+ let passJSONData = try String(contentsOfFile: passFolder.path.appending("/pass.json")).data(using: .utf8)
+ let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
+ #expect(passJSON["authenticationToken"] as? String == pass.authenticationToken)
+ let passID = try pass.requireID().uuidString
+ #expect(passJSON["serialNumber"] as? String == passID)
+ #expect(passJSON["description"] as? String == passData.title)
+
+ let personalizationJSONData = try String(contentsOfFile: passFolder.path.appending("/personalization.json")).data(using: .utf8)
+ let personalizationJSON = try JSONSerialization.jsonObject(with: personalizationJSONData!) as! [String: Any]
+ #expect(personalizationJSON["description"] as? String == "Hello, World!")
+
+ let manifestJSONData = try String(contentsOfFile: passFolder.path.appending("/manifest.json")).data(using: .utf8)
+ let manifestJSON = try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
+ let iconData = try Data(contentsOf: passFolder.appendingPathComponent("/personalizationLogo.png"))
+ let iconHash = Array(Insecure.SHA1.hash(data: iconData)).hex
+ #expect(manifestJSON["personalizationLogo.png"] as? String == iconHash)
+ }
}
- func testPassesGeneration() async throws {
- let passData1 = PassData(title: "Test Pass 1")
- try await passData1.create(on: app.db)
- let pass1 = try await passData1.$pass.get(on: app.db)
+ @Test("Getting Pass from Apple Wallet API")
+ func getPassFromAPI() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ let passData = PassData(title: "Test Pass")
+ try await passData.create(on: app.db)
+ let pass = try await passData.$pass.get(on: app.db)
+
+ try await app.test(
+ .GET,
+ "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: [
+ "Authorization": "ApplePass \(pass.authenticationToken)",
+ "If-Modified-Since": "0",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ #expect(res.body != nil)
+ #expect(res.headers.contentType?.description == "application/vnd.apple.pkpass")
+ #expect(res.headers.lastModified != nil)
+ }
+ )
- let passData2 = PassData(title: "Test Pass 2")
- try await passData2.create(on: app.db)
- let pass2 = try await passData2._$pass.get(on: app.db)
+ // Test call with invalid authentication token
+ try await app.test(
+ .GET,
+ "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: [
+ "Authorization": "ApplePass invalid-token",
+ "If-Modified-Since": "0",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .unauthorized)
+ }
+ )
- let data = try await passesService.generatePassesContent(for: [pass1, pass2], on: app.db)
- XCTAssertNotNil(data)
+ // Test distant future `If-Modified-Since` date
+ try await app.test(
+ .GET,
+ "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: [
+ "Authorization": "ApplePass \(pass.authenticationToken)",
+ "If-Modified-Since": "2147483647",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .notModified)
+ }
+ )
- do {
- let data = try await passesService.generatePassesContent(for: [pass1], on: app.db)
- XCTFail("Expected error, got \(data)")
- } catch let error as PassesError {
- XCTAssertEqual(error, .invalidNumberOfPasses)
+ // Test call with invalid pass ID
+ try await app.test(
+ .GET,
+ "\(passesURI)passes/\(pass.passTypeIdentifier)/invalid-uuid",
+ headers: [
+ "Authorization": "ApplePass \(pass.authenticationToken)",
+ "If-Modified-Since": "0",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test call with invalid pass type identifier
+ try await app.test(
+ .GET,
+ "\(passesURI)passes/pass.com.example.InvalidType/\(pass.requireID())",
+ headers: [
+ "Authorization": "ApplePass \(pass.authenticationToken)",
+ "If-Modified-Since": "0",
+ ],
+ afterResponse: { res async throws in
+ #expect(res.status == .notFound)
+ }
+ )
}
}
- func testPersonalization() async throws {
- let passData = PassData(title: "Personalize")
- try await passData.create(on: app.db)
- let pass = try await passData.$pass.get(on: app.db)
- let data = try await passesService.generatePassContent(for: pass, on: app.db)
- let passURL = FileManager.default.temporaryDirectory.appendingPathComponent("test.pkpass")
- try data.write(to: passURL)
- let passFolder = try Zip.quickUnzipFile(passURL)
-
- let passJSONData = try String(contentsOfFile: passFolder.path.appending("/pass.json")).data(
- using: .utf8)
- let passJSON = try JSONSerialization.jsonObject(with: passJSONData!) as! [String: Any]
- XCTAssertEqual(passJSON["authenticationToken"] as? String, pass.authenticationToken)
- try XCTAssertEqual(passJSON["serialNumber"] as? String, pass.requireID().uuidString)
- XCTAssertEqual(passJSON["description"] as? String, passData.title)
-
- let personalizationJSONData = try String(
- contentsOfFile: passFolder.path.appending("/personalization.json")
- ).data(using: .utf8)
- let personalizationJSON =
- try JSONSerialization.jsonObject(with: personalizationJSONData!) as! [String: Any]
- XCTAssertEqual(personalizationJSON["description"] as? String, "Hello, World!")
-
- let manifestJSONData = try String(
- contentsOfFile: passFolder.path.appending("/manifest.json")
- ).data(using: .utf8)
- let manifestJSON =
- try JSONSerialization.jsonObject(with: manifestJSONData!) as! [String: Any]
- let iconData = try Data(
- contentsOf: passFolder.appendingPathComponent("/personalizationLogo.png"))
- let iconHash = Array(Insecure.SHA1.hash(data: iconData)).hex
- XCTAssertEqual(manifestJSON["personalizationLogo.png"] as? String, iconHash)
- }
+ @Test("Personalizable Pass Apple Wallet API")
+ func personalizationAPI() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ let passData = PassData(title: "Personalize")
+ try await passData.create(on: app.db)
+ let pass = try await passData.$pass.get(on: app.db)
+ let personalizationDict = PersonalizationDictionaryDTO(
+ personalizationToken: "1234567890",
+ requiredPersonalizationInfo: .init(
+ emailAddress: "test@example.com",
+ familyName: "Doe",
+ fullName: "John Doe",
+ givenName: "John",
+ isoCountryCode: "US",
+ phoneNumber: "1234567890",
+ postalCode: "12345"
+ )
+ )
- // Tests the API Apple Wallet calls to get passes
- func testGetPassFromAPI() async throws {
- let passData = PassData(title: "Test Pass")
- try await passData.create(on: app.db)
- let pass = try await passData.$pass.get(on: app.db)
-
- try await app.test(
- .GET,
- "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: [
- "Authorization": "ApplePass \(pass.authenticationToken)",
- "If-Modified-Since": "0",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- XCTAssertNotNil(res.body)
- XCTAssertEqual(res.headers.contentType?.description, "application/vnd.apple.pkpass")
- XCTAssertNotNil(res.headers.lastModified)
- }
- )
-
- // Test call with invalid authentication token
- try await app.test(
- .GET,
- "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: [
- "Authorization": "ApplePass invalid-token",
- "If-Modified-Since": "0",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .unauthorized)
- }
- )
-
- // Test distant future `If-Modified-Since` date
- try await app.test(
- .GET,
- "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: [
- "Authorization": "ApplePass \(pass.authenticationToken)",
- "If-Modified-Since": "2147483647",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .notModified)
- }
- )
-
- // Test call with invalid pass ID
- try await app.test(
- .GET,
- "\(passesURI)passes/\(pass.passTypeIdentifier)/invalid-uuid",
- headers: [
- "Authorization": "ApplePass \(pass.authenticationToken)",
- "If-Modified-Since": "0",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- // Test call with invalid pass type identifier
- try await app.test(
- .GET,
- "\(passesURI)passes/pass.com.example.InvalidType/\(pass.requireID())",
- headers: [
- "Authorization": "ApplePass \(pass.authenticationToken)",
- "If-Modified-Since": "0",
- ],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .notFound)
- }
- )
- }
+ try await app.test(
+ .POST,
+ "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())/personalize",
+ beforeRequest: { req async throws in
+ try req.content.encode(personalizationDict)
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ #expect(res.body != nil)
+ #expect(res.headers.contentType?.description == "application/octet-stream")
+ }
+ )
- func testPersonalizationAPI() async throws {
- let passData = PassData(title: "Personalize")
- try await passData.create(on: app.db)
- let pass = try await passData.$pass.get(on: app.db)
- let personalizationDict = PersonalizationDictionaryDTO(
- personalizationToken: "1234567890",
- requiredPersonalizationInfo: .init(
- emailAddress: "test@example.com",
- familyName: "Doe",
- fullName: "John Doe",
- givenName: "John",
- isoCountryCode: "US",
- phoneNumber: "1234567890",
- postalCode: "12345"
+ let personalizationQuery = try await UserPersonalization.query(on: app.db).all()
+ #expect(personalizationQuery.count == 1)
+ let passPersonalizationID = try await Pass.query(on: app.db).first()?._$userPersonalization.get(on: app.db)?.requireID()
+ #expect(personalizationQuery[0]._$id.value == passPersonalizationID)
+ #expect(personalizationQuery[0]._$emailAddress.value == personalizationDict.requiredPersonalizationInfo.emailAddress)
+ #expect(personalizationQuery[0]._$familyName.value == personalizationDict.requiredPersonalizationInfo.familyName)
+ #expect(personalizationQuery[0]._$fullName.value == personalizationDict.requiredPersonalizationInfo.fullName)
+ #expect(personalizationQuery[0]._$givenName.value == personalizationDict.requiredPersonalizationInfo.givenName)
+ #expect(personalizationQuery[0]._$isoCountryCode.value == personalizationDict.requiredPersonalizationInfo.isoCountryCode)
+ #expect(personalizationQuery[0]._$phoneNumber.value == personalizationDict.requiredPersonalizationInfo.phoneNumber)
+ #expect(personalizationQuery[0]._$postalCode.value == personalizationDict.requiredPersonalizationInfo.postalCode)
+
+ // Test call with invalid pass ID
+ try await app.test(
+ .POST,
+ "\(passesURI)passes/\(pass.passTypeIdentifier)/invalid-uuid/personalize",
+ beforeRequest: { req async throws in
+ try req.content.encode(personalizationDict)
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
)
- )
-
- try await app.test(
- .POST,
- "\(passesURI)passes/\(pass.passTypeIdentifier)/\(pass.requireID())/personalize",
- beforeRequest: { req async throws in
- try req.content.encode(personalizationDict)
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- XCTAssertNotNil(res.body)
- XCTAssertEqual(res.headers.contentType?.description, "application/octet-stream")
- }
- )
-
- let personalizationQuery = try await UserPersonalization.query(on: app.db).all()
- XCTAssertEqual(personalizationQuery.count, 1)
- let passPersonalizationID = try await Pass.query(on: app.db).first()?
- ._$userPersonalization.get(on: app.db)?
- .requireID()
- XCTAssertEqual(personalizationQuery[0]._$id.value, passPersonalizationID)
- XCTAssertEqual(
- personalizationQuery[0]._$emailAddress.value,
- personalizationDict.requiredPersonalizationInfo.emailAddress)
- XCTAssertEqual(
- personalizationQuery[0]._$familyName.value,
- personalizationDict.requiredPersonalizationInfo.familyName)
- XCTAssertEqual(
- personalizationQuery[0]._$fullName.value,
- personalizationDict.requiredPersonalizationInfo.fullName)
- XCTAssertEqual(
- personalizationQuery[0]._$givenName.value,
- personalizationDict.requiredPersonalizationInfo.givenName)
- XCTAssertEqual(
- personalizationQuery[0]._$isoCountryCode.value,
- personalizationDict.requiredPersonalizationInfo.isoCountryCode)
- XCTAssertEqual(
- personalizationQuery[0]._$phoneNumber.value,
- personalizationDict.requiredPersonalizationInfo.phoneNumber)
- XCTAssertEqual(
- personalizationQuery[0]._$postalCode.value,
- personalizationDict.requiredPersonalizationInfo.postalCode)
-
- // Test call with invalid pass ID
- try await app.test(
- .POST,
- "\(passesURI)passes/\(pass.passTypeIdentifier)/invalid-uuid/personalize",
- beforeRequest: { req async throws in
- try req.content.encode(personalizationDict)
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- // Test call with invalid pass type identifier
- try await app.test(
- .POST,
- "\(passesURI)passes/pass.com.example.InvalidType/\(pass.requireID())/personalize",
- beforeRequest: { req async throws in
- try req.content.encode(personalizationDict)
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .notFound)
- }
- )
+
+ // Test call with invalid pass type identifier
+ try await app.test(
+ .POST,
+ "\(passesURI)passes/pass.com.example.InvalidType/\(pass.requireID())/personalize",
+ beforeRequest: { req async throws in
+ try req.content.encode(personalizationDict)
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .notFound)
+ }
+ )
+ }
}
- func testAPIDeviceRegistration() async throws {
- let passData = PassData(title: "Test Pass")
- try await passData.create(on: app.db)
- let pass = try await passData.$pass.get(on: app.db)
- let deviceLibraryIdentifier = "abcdefg"
- let pushToken = "1234567890"
-
- try await app.test(
- .GET,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)?passesUpdatedSince=0",
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .noContent)
- }
- )
-
- try await app.test(
- .DELETE,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .notFound)
- }
- )
-
- // Test registration without authentication token
- try await app.test(
- .POST,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .unauthorized)
- }
- )
-
- // Test registration of a non-existing pass
- try await app.test(
- .POST,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\("pass.com.example.NotFound")/\(UUID().uuidString)",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .notFound)
- }
- )
-
- // Test call without DTO
- try await app.test(
- .POST,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- // Test call with invalid UUID
- try await app.test(
- .POST,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\("not-a-uuid")",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- try await app.test(
- .POST,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .created)
- }
- )
-
- // Test registration of an already registered device
- try await app.test(
- .POST,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- }
- )
-
- try await app.test(
- .GET,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)?passesUpdatedSince=0",
- afterResponse: { res async throws in
- let passes = try res.content.decode(PassesForDeviceDTO.self)
- XCTAssertEqual(passes.serialNumbers.count, 1)
- let passID = try pass.requireID()
- XCTAssertEqual(passes.serialNumbers[0], passID.uuidString)
- XCTAssertEqual(passes.lastUpdated, String(pass.updatedAt!.timeIntervalSince1970))
- }
- )
-
- try await app.test(
- .GET,
- "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- let pushTokens = try res.content.decode([String].self)
- XCTAssertEqual(pushTokens.count, 1)
- XCTAssertEqual(pushTokens[0], pushToken)
- }
- )
-
- // Test call with invalid UUID
- try await app.test(
- .GET,
- "\(passesURI)push/\(pass.passTypeIdentifier)/\("not-a-uuid")",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- // Test call with invalid UUID
- try await app.test(
- .DELETE,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\("not-a-uuid")",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- try await app.test(
- .DELETE,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- }
- )
+ @Test("Device Registration API")
+ func apiDeviceRegistration() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ let passData = PassData(title: "Test Pass")
+ try await passData.create(on: app.db)
+ let pass = try await passData.$pass.get(on: app.db)
+ let deviceLibraryIdentifier = "abcdefg"
+ let pushToken = "1234567890"
+
+ try await app.test(
+ .GET,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)?passesUpdatedSince=0",
+ afterResponse: { res async throws in
+ #expect(res.status == .noContent)
+ }
+ )
+
+ try await app.test(
+ .DELETE,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ afterResponse: { res async throws in
+ #expect(res.status == .notFound)
+ }
+ )
+
+ // Test registration without authentication token
+ try await app.test(
+ .POST,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .unauthorized)
+ }
+ )
+
+ // Test registration of a non-existing pass
+ try await app.test(
+ .POST,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\("pass.com.example.NotFound")/\(UUID().uuidString)",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .notFound)
+ }
+ )
+
+ // Test call without DTO
+ try await app.test(
+ .POST,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test call with invalid UUID
+ try await app.test(
+ .POST,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\("not-a-uuid")",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .created)
+ }
+ )
+
+ // Test registration of an already registered device
+ try await app.test(
+ .POST,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ }
+ )
+
+ try await app.test(
+ .GET,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)?passesUpdatedSince=0",
+ afterResponse: { res async throws in
+ let passes = try res.content.decode(PassesForDeviceDTO.self)
+ #expect(passes.serialNumbers.count == 1)
+ let passID = try pass.requireID()
+ #expect(passes.serialNumbers[0] == passID.uuidString)
+ #expect(passes.lastUpdated == String(pass.updatedAt!.timeIntervalSince1970))
+ }
+ )
+
+ try await app.test(
+ .GET,
+ "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ let pushTokens = try res.content.decode([String].self)
+ #expect(pushTokens.count == 1)
+ #expect(pushTokens[0] == pushToken)
+ }
+ )
+
+ // Test call with invalid UUID
+ try await app.test(
+ .GET,
+ "\(passesURI)push/\(pass.passTypeIdentifier)/\("not-a-uuid")",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test call with invalid UUID
+ try await app.test(
+ .DELETE,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\("not-a-uuid")",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ try await app.test(
+ .DELETE,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ }
+ )
+ }
}
- func testErrorLog() async throws {
- let log1 = "Error 1"
- let log2 = "Error 2"
-
- try await app.test(
- .POST,
- "\(passesURI)log",
- beforeRequest: { req async throws in
- try req.content.encode(ErrorLogDTO(logs: [log1, log2]))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .ok)
- }
- )
-
- let logs = try await PassesErrorLog.query(on: app.db).all()
- XCTAssertEqual(logs.count, 2)
- XCTAssertEqual(logs[0].message, log1)
- XCTAssertEqual(logs[1]._$message.value, log2)
-
- // Test call with no DTO
- try await app.test(
- .POST,
- "\(passesURI)log",
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
-
- // Test call with empty logs
- try await app.test(
- .POST,
- "\(passesURI)log",
- beforeRequest: { req async throws in
- try req.content.encode(ErrorLogDTO(logs: []))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
- }
- )
+ @Test("Error Logging")
+ func errorLog() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ let log1 = "Error 1"
+ let log2 = "Error 2"
+
+ try await app.test(
+ .POST,
+ "\(passesURI)log",
+ beforeRequest: { req async throws in
+ try req.content.encode(ErrorLogDTO(logs: [log1, log2]))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .ok)
+ }
+ )
+
+ let logs = try await PassesErrorLog.query(on: app.db).all()
+ #expect(logs.count == 2)
+ #expect(logs[0].message == log1)
+ #expect(logs[1]._$message.value == log2)
+
+ // Test call with no DTO
+ try await app.test(
+ .POST,
+ "\(passesURI)log",
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test call with empty logs
+ try await app.test(
+ .POST,
+ "\(passesURI)log",
+ beforeRequest: { req async throws in
+ try req.content.encode(ErrorLogDTO(logs: []))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+ }
}
- func testAPNSClient() async throws {
- XCTAssertNotNil(app.apns.client(.init(string: "passes")))
+ @Test("APNS Client")
+ func apnsClient() async throws {
+ try await withApp(delegate: delegate) { app, passesService in
+ #expect(app.apns.client(.init(string: "passes")) != nil)
- let passData = PassData(title: "Test Pass")
- try await passData.create(on: app.db)
- let pass = try await passData._$pass.get(on: app.db)
+ let passData = PassData(title: "Test Pass")
+ try await passData.create(on: app.db)
+ let pass = try await passData._$pass.get(on: app.db)
- try await passesService.sendPushNotificationsForPass(
- id: pass.requireID(), of: pass.passTypeIdentifier, on: app.db)
+ try await passesService.sendPushNotificationsForPass(id: pass.requireID(), of: pass.passTypeIdentifier, on: app.db)
- let deviceLibraryIdentifier = "abcdefg"
- let pushToken = "1234567890"
+ let deviceLibraryIdentifier = "abcdefg"
+ let pushToken = "1234567890"
- try await app.test(
- .POST,
- "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .noContent)
- }
- )
-
- try await app.test(
- .POST,
- "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
- beforeRequest: { req async throws in
- try req.content.encode(RegistrationDTO(pushToken: pushToken))
- },
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .created)
- }
- )
-
- try await app.test(
- .POST,
- "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .internalServerError)
- }
- )
-
- // Test call with invalid UUID
- try await app.test(
- .POST,
- "\(passesURI)push/\(pass.passTypeIdentifier)/\("not-a-uuid")",
- headers: ["X-Secret": "foo"],
- afterResponse: { res async throws in
- XCTAssertEqual(res.status, .badRequest)
+ // Test call with incorrect secret
+ try await app.test(
+ .POST,
+ "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["X-Secret": "bar"],
+ afterResponse: { res async throws in
+ #expect(res.status == .unauthorized)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .noContent)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
+ beforeRequest: { req async throws in
+ try req.content.encode(RegistrationDTO(pushToken: pushToken))
+ },
+ afterResponse: { res async throws in
+ #expect(res.status == .created)
+ }
+ )
+
+ try await app.test(
+ .POST,
+ "\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .internalServerError)
+ }
+ )
+
+ // Test call with invalid UUID
+ try await app.test(
+ .POST,
+ "\(passesURI)push/\(pass.passTypeIdentifier)/\("not-a-uuid")",
+ headers: ["X-Secret": "foo"],
+ afterResponse: { res async throws in
+ #expect(res.status == .badRequest)
+ }
+ )
+
+ // Test `PassDataMiddleware` update method
+ passData.title = "Test Pass 2"
+ do {
+ try await passData.update(on: app.db)
+ } catch let error as HTTPClientError {
+ #expect(error.self == .remoteConnectionClosed)
}
- )
-
- // Test `PassDataMiddleware` update method
- passData.title = "Test Pass 2"
- do {
- try await passData.update(on: app.db)
- } catch let error as HTTPClientError {
- XCTAssertEqual(error.self, .remoteConnectionClosed)
}
}
- func testPassesError() {
- XCTAssertEqual(
- PassesError.templateNotDirectory.description,
- "PassesError(errorType: templateNotDirectory)")
- XCTAssertEqual(
- PassesError.pemCertificateMissing.description,
- "PassesError(errorType: pemCertificateMissing)")
- XCTAssertEqual(
- PassesError.pemPrivateKeyMissing.description,
- "PassesError(errorType: pemPrivateKeyMissing)")
- XCTAssertEqual(
- PassesError.opensslBinaryMissing.description,
- "PassesError(errorType: opensslBinaryMissing)")
- XCTAssertEqual(
- PassesError.invalidNumberOfPasses.description,
- "PassesError(errorType: invalidNumberOfPasses)")
+ @Test("PassesError")
+ func passesError() {
+ #expect(PassesError.templateNotDirectory.description == "PassesError(errorType: templateNotDirectory)")
+ #expect(PassesError.pemCertificateMissing.description == "PassesError(errorType: pemCertificateMissing)")
+ #expect(PassesError.pemPrivateKeyMissing.description == "PassesError(errorType: pemPrivateKeyMissing)")
+ #expect(PassesError.opensslBinaryMissing.description == "PassesError(errorType: opensslBinaryMissing)")
+ #expect(PassesError.invalidNumberOfPasses.description == "PassesError(errorType: invalidNumberOfPasses)")
}
- func testDefaultDelegate() async throws {
- let delegate = DefaultPassesDelegate()
- XCTAssertEqual(delegate.wwdrCertificate, "WWDR.pem")
- XCTAssertEqual(delegate.pemCertificate, "passcertificate.pem")
- XCTAssertEqual(delegate.pemPrivateKey, "passkey.pem")
- XCTAssertNil(delegate.pemPrivateKeyPassword)
- XCTAssertEqual(delegate.sslBinary, URL(fileURLWithPath: "/usr/bin/openssl"))
- XCTAssertFalse(delegate.generateSignatureFile(in: URL(fileURLWithPath: "")))
-
- let passData = PassData(title: "Test Pass")
- try await passData.create(on: app.db)
- let pass = try await passData.$pass.get(on: app.db)
- let data = try await delegate.encodePersonalization(
- for: pass, db: app.db, encoder: JSONEncoder())
- XCTAssertNil(data)
+ @Test("Default PassesDelegate Properties")
+ func defaultDelegate() async throws {
+ let defaultDelegate = DefaultPassesDelegate()
+ #expect(defaultDelegate.wwdrCertificate == "WWDR.pem")
+ #expect(defaultDelegate.pemCertificate == "passcertificate.pem")
+ #expect(defaultDelegate.pemPrivateKey == "passkey.pem")
+ #expect(defaultDelegate.pemPrivateKeyPassword == nil)
+ #expect(defaultDelegate.sslBinary == URL(fileURLWithPath: "/usr/bin/openssl"))
+ #expect(!defaultDelegate.generateSignatureFile(in: URL(fileURLWithPath: "")))
+
+ try await withApp(delegate: delegate) { app, passesService in
+ let passData = PassData(title: "Test Pass")
+ try await passData.create(on: app.db)
+ let pass = try await passData.$pass.get(on: app.db)
+ let data = try await defaultDelegate.encodePersonalization(for: pass, db: app.db, encoder: JSONEncoder())
+ #expect(data == nil)
+ }
}
}
@@ -573,9 +546,7 @@ final class DefaultPassesDelegate: PassesDelegate {
func template(for pass: P, db: any Database) async throws -> URL {
URL(fileURLWithPath: "")
}
- func encode(
- pass: P, db: any Database, encoder: JSONEncoder
- ) async throws -> Data {
+ func encode(pass: P, db: any Database, encoder: JSONEncoder) async throws -> Data {
Data()
}
}
diff --git a/Tests/PassesTests/SecretMiddleware.swift b/Tests/PassesTests/SecretMiddleware.swift
deleted file mode 100644
index fef1940..0000000
--- a/Tests/PassesTests/SecretMiddleware.swift
+++ /dev/null
@@ -1,14 +0,0 @@
-import Vapor
-
-struct SecretMiddleware: AsyncMiddleware {
- let secret: String
-
- func respond(
- to request: Request, chainingTo next: any AsyncResponder
- ) async throws -> Response {
- guard request.headers.first(name: "X-Secret") == secret else {
- throw Abort(.unauthorized, reason: "Incorrect X-Secret header.")
- }
- return try await next.respond(to: request)
- }
-}
diff --git a/Tests/PassesTests/withApp.swift b/Tests/PassesTests/withApp.swift
new file mode 100644
index 0000000..6ff4a1c
--- /dev/null
+++ b/Tests/PassesTests/withApp.swift
@@ -0,0 +1,38 @@
+import FluentKit
+import FluentSQLiteDriver
+import PassKit
+import Passes
+import Testing
+import Vapor
+import Zip
+
+func withApp(
+ delegate: some PassesDelegate,
+ _ body: (Application, PassesService) async throws -> Void
+) async throws {
+ let app = try await Application.make(.testing)
+
+ try #require(isLoggingConfigured)
+
+ app.databases.use(.sqlite(.memory), as: .sqlite)
+
+ PassesService.register(migrations: app.migrations)
+ app.migrations.add(CreatePassData())
+ let passesService = try PassesService(
+ app: app,
+ delegate: delegate,
+ pushRoutesMiddleware: SecretMiddleware(secret: "foo"),
+ logger: app.logger
+ )
+
+ app.databases.middleware.use(PassDataMiddleware(service: passesService), on: .sqlite)
+
+ try await app.autoMigrate()
+
+ Zip.addCustomFileExtension("pkpass")
+
+ try await body(app, passesService)
+
+ try await app.autoRevert()
+ try await app.asyncShutdown()
+}