Skip to content

Commit

Permalink
Swift Testing (#14)
Browse files Browse the repository at this point in the history
* Switch from XCTest to Swift Testing

---------

Co-authored-by: Tim Condon <[email protected]>
  • Loading branch information
fpseverino and 0xTim authored Oct 12, 2024
1 parent 939492b commit 349b450
Show file tree
Hide file tree
Showing 15 changed files with 1,187 additions and 1,227 deletions.
11 changes: 2 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
2 changes: 1 addition & 1 deletion .swift-format
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"lineBreakBeforeControlFlowKeywords": false,
"lineBreakBeforeEachArgument": false,
"lineBreakBeforeEachGenericRequirement": false,
"lineLength": 100,
"lineLength": 140,
"maximumBlankLines": 1,
"multiElementCollectionTrailingCommas": true,
"noAssignmentInExpressions": {
Expand Down
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<img src="https://img.shields.io/codecov/c/github/vapor-community/PassKit?style=plastic&logo=codecov&label=codecov">
</a>
<a href="https://swift.org">
<img src="https://design.vapor.codes/images/swift510up.svg" alt="Swift 5.10+">
<img src="https://design.vapor.codes/images/swift60up.svg" alt="Swift 6.0+">
</a>
</div>
<br>
Expand Down
3 changes: 1 addition & 2 deletions Sources/Orders/OrdersService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import Vapor

/// The main class that handles Wallet orders.
public final class OrdersService: Sendable {
private let service:
OrdersServiceCustom<Order, OrdersDevice, OrdersRegistration, OrdersErrorLog>
private let service: OrdersServiceCustom<Order, OrdersDevice, OrdersRegistration, OrdersErrorLog>

/// Initializes the service and registers all the routes required for Apple Wallet to work.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -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.")
}
Expand Down
10 changes: 10 additions & 0 deletions Sources/PassKit/Testing/isLoggingConfigured.swift
Original file line number Diff line number Diff line change
@@ -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
}()
3 changes: 1 addition & 2 deletions Sources/Passes/PassesService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
182 changes: 79 additions & 103 deletions Tests/OrdersTests/EncryptedOrdersTests.swift
Original file line number Diff line number Diff line change
@@ -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 {}
}
}
}
Loading

0 comments on commit 349b450

Please sign in to comment.