From 2e130213abb5a404112c5871ed6dcc9841536402 Mon Sep 17 00:00:00 2001 From: Francesco Paolo Severino Date: Fri, 11 Oct 2024 17:13:16 +0200 Subject: [PATCH] Update OrdersTests to Swift Testing --- Tests/OrdersTests/EncryptedOrdersTests.swift | 179 ++--- Tests/OrdersTests/OrdersTests.swift | 761 +++++++++---------- Tests/OrdersTests/withApp.swift | 38 + Tests/PassesTests/PassesTests.swift | 16 +- 4 files changed, 487 insertions(+), 507 deletions(-) create mode 100644 Tests/OrdersTests/withApp.swift diff --git a/Tests/OrdersTests/EncryptedOrdersTests.swift b/Tests/OrdersTests/EncryptedOrdersTests.swift index 6e39f4c..5ddf2cc 100644 --- a/Tests/OrdersTests/EncryptedOrdersTests.swift +++ b/Tests/OrdersTests/EncryptedOrdersTests.swift @@ -1,116 +1,89 @@ -import Fluent -import FluentSQLiteDriver +import FluentKit import PassKit +import Testing import XCTVapor import Zip @testable import Orders -final class EncryptedOrdersTests: XCTestCase { +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 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 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..aaddc8e 100644 --- a/Tests/OrdersTests/OrdersTests.swift +++ b/Tests/OrdersTests/OrdersTests.swift @@ -1,421 +1,392 @@ -import Fluent -import FluentSQLiteDriver +import FluentKit import PassKit +import Testing import XCTVapor import Zip @testable import Orders -final class OrdersTests: XCTestCase { +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 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 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 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 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 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 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 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 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 +395,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/PassesTests.swift b/Tests/PassesTests/PassesTests.swift index 55a8b20..7fd7032 100644 --- a/Tests/PassesTests/PassesTests.swift +++ b/Tests/PassesTests/PassesTests.swift @@ -503,19 +503,19 @@ struct PassesTests { } @Test func defaultDelegate() async throws { - let delegate = DefaultPassesDelegate() - #expect(delegate.wwdrCertificate == "WWDR.pem") - #expect(delegate.pemCertificate == "passcertificate.pem") - #expect(delegate.pemPrivateKey == "passkey.pem") - #expect(delegate.pemPrivateKeyPassword == nil) - #expect(delegate.sslBinary == URL(fileURLWithPath: "/usr/bin/openssl")) - #expect(!delegate.generateSignatureFile(in: URL(fileURLWithPath: ""))) + 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 delegate.encodePersonalization(for: pass, db: app.db, encoder: JSONEncoder()) + let data = try await defaultDelegate.encodePersonalization(for: pass, db: app.db, encoder: JSONEncoder()) #expect(data == nil) } }