Skip to content

Commit

Permalink
Cleanup and add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fpseverino committed Aug 14, 2024
1 parent 59cee89 commit 398127e
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 24 deletions.
7 changes: 6 additions & 1 deletion Sources/Orders/Orders.docc/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,17 @@ fileprivate func orderHandler(_ req: Request) async throws -> Response {
throw Abort(.notFound)
}

let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"

let bundle = try await ordersService.generateOrderContent(for: orderData.order, on: req.db)
let body = Response.Body(data: bundle)
var headers = HTTPHeaders()
headers.add(name: .contentType, value: "application/vnd.apple.order")
headers.add(name: .contentDisposition, value: "attachment; filename=name.order")
headers.add(name: .lastModified, value: String(orderData.order.updatedAt?.timeIntervalSince1970 ?? 0))
headers.add(name: .lastModified, value: dateFormatter.string(from: order.updatedAt ?? Date.distantPast))
headers.add(name: .contentTransferEncoding, value: "binary")
return Response(status: .ok, headers: headers, body: body)
}
Expand Down
12 changes: 5 additions & 7 deletions Sources/Orders/OrdersServiceCustom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,10 @@ extension OrdersServiceCustom {
func latestVersionOfOrder(req: Request) async throws -> Response {
logger?.debug("Called latestVersionOfOrder")

let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = .withInternetDateTime
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
var ifModifiedSince = Date.distantPast
if let header = req.headers[.ifModifiedSince].first, let ims = dateFormatter.date(from: header) {
ifModifiedSince = ims
Expand Down Expand Up @@ -353,7 +355,6 @@ extension OrdersServiceCustom {
extension OrdersServiceCustom {
private static func generateManifestFile(using encoder: JSONEncoder, in root: URL) throws -> Data {
var manifest: [String: String] = [:]

let paths = try FileManager.default.subpathsOfDirectory(atPath: root.unixPath())
try paths.forEach { relativePath in
let file = URL(fileURLWithPath: relativePath, relativeTo: root)
Expand All @@ -362,7 +363,6 @@ extension OrdersServiceCustom {
let hash = SHA256.hash(data: data)
manifest[relativePath] = hash.map { "0\(String($0, radix: 16))".suffix(2) }.joined()
}

let data = try encoder.encode(manifest)
try data.write(to: root.appendingPathComponent("manifest.json"))
return data
Expand Down Expand Up @@ -436,11 +436,9 @@ extension OrdersServiceCustom {
guard (try? templateDirectory.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false else {
throw OrdersError.templateNotDirectory
}
var files = try FileManager.default.contentsOfDirectory(at: templateDirectory, includingPropertiesForKeys: nil)

let tmp = FileManager.default.temporaryDirectory
let root = tmp.appendingPathComponent(UUID().uuidString, isDirectory: true)

try FileManager.default.copyItem(at: templateDirectory, to: root)
defer { _ = try? FileManager.default.removeItem(at: root) }

Expand All @@ -453,10 +451,10 @@ extension OrdersServiceCustom {
in: root
)

var files = try FileManager.default.contentsOfDirectory(at: templateDirectory, includingPropertiesForKeys: nil)
files.append(URL(fileURLWithPath: "order.json", relativeTo: root))
files.append(URL(fileURLWithPath: "manifest.json", relativeTo: root))
files.append(URL(fileURLWithPath: "signature", relativeTo: root))

return try Data(contentsOf: Zip.quickZipFiles(files, fileName: UUID().uuidString))
}
}
14 changes: 12 additions & 2 deletions Sources/Passes/Passes.docc/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,12 +316,17 @@ fileprivate func passHandler(_ req: Request) async throws -> Response {
throw Abort(.notFound)
}

let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"

let bundle = try await passesService.generatePassContent(for: passData.pass, on: req.db)
let body = Response.Body(data: bundle)
var headers = HTTPHeaders()
headers.add(name: .contentType, value: "application/vnd.apple.pkpass")
headers.add(name: .contentDisposition, value: "attachment; filename=name.pkpass")
headers.add(name: .lastModified, value: String(passData.pass.updatedAt?.timeIntervalSince1970 ?? 0))
headers.add(name: .lastModified, value: dateFormatter.string(from: pass.updatedAt ?? Date.distantPast))
headers.add(name: .contentTransferEncoding, value: "binary")
return Response(status: .ok, headers: headers, body: body)
}
Expand All @@ -341,12 +346,17 @@ fileprivate func passesHandler(_ req: Request) async throws -> Response {
let passesData = try await PassData.query(on: req.db).with(\.$pass).all()
let passes = passesData.map { $0.pass }

let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"

let bundle = try await passesService.generatePassesContent(for: passes, on: req.db)
let body = Response.Body(data: bundle)
var headers = HTTPHeaders()
headers.add(name: .contentType, value: "application/vnd.apple.pkpasses")
headers.add(name: .contentDisposition, value: "attachment; filename=name.pkpasses")
headers.add(name: .lastModified, value: String(Date().timeIntervalSince1970))
headers.add(name: .lastModified, value: dateFormatter.string(from: Date()))
headers.add(name: .contentTransferEncoding, value: "binary")
return Response(status: .ok, headers: headers, body: body)
}
Expand Down
14 changes: 8 additions & 6 deletions Sources/Passes/PassesServiceCustom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,12 @@ extension PassesServiceCustom {
func latestVersionOfPass(req: Request) async throws -> Response {
logger?.debug("Called latestVersionOfPass")

var ifModifiedSince: TimeInterval = 0
if let header = req.headers[.ifModifiedSince].first, let ims = TimeInterval(header) {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss zzz"
var ifModifiedSince = Date.distantPast
if let header = req.headers[.ifModifiedSince].first, let ims = dateFormatter.date(from: header) {
ifModifiedSince = ims
}

Expand All @@ -203,13 +207,13 @@ extension PassesServiceCustom {
throw Abort(.notFound)
}

guard ifModifiedSince < pass.updatedAt?.timeIntervalSince1970 ?? 0 else {
guard ifModifiedSince < pass.updatedAt ?? Date.distantPast else {
throw Abort(.notModified)
}

var headers = HTTPHeaders()
headers.add(name: .contentType, value: "application/vnd.apple.pkpass")
headers.add(name: .lastModified, value: String(pass.updatedAt?.timeIntervalSince1970 ?? 0))
headers.add(name: .lastModified, value: dateFormatter.string(from: pass.updatedAt ?? Date.distantPast))
headers.add(name: .contentTransferEncoding, value: "binary")
return try await Response(
status: .ok,
Expand Down Expand Up @@ -450,7 +454,6 @@ extension PassesServiceCustom {
extension PassesServiceCustom {
private static func generateManifestFile(using encoder: JSONEncoder, in root: URL) throws -> Data {
var manifest: [String: String] = [:]

let paths = try FileManager.default.subpathsOfDirectory(atPath: root.unixPath())
try paths.forEach { relativePath in
let file = URL(fileURLWithPath: relativePath, relativeTo: root)
Expand All @@ -459,7 +462,6 @@ extension PassesServiceCustom {
let hash = Insecure.SHA1.hash(data: data)
manifest[relativePath] = hash.map { "0\(String($0, radix: 16))".suffix(2) }.joined()
}

let data = try encoder.encode(manifest)
try data.write(to: root.appendingPathComponent("manifest.json"))
return data
Expand Down
38 changes: 34 additions & 4 deletions Tests/OrdersTests/OrdersTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ final class OrdersTests: XCTestCase {

OrdersService.register(migrations: app.migrations)
app.migrations.add(CreateOrderData())
ordersService = try OrdersService(app: app, delegate: orderDelegate, pushRoutesMiddleware: SecretMiddleware(secret: "foo"))
ordersService = try OrdersService(
app: app,
delegate: orderDelegate,
pushRoutesMiddleware: SecretMiddleware(secret: "foo"),
logger: app.logger
)
app.databases.middleware.use(OrderDataMiddleware(service: ordersService), on: .sqlite)

try await app.autoMigrate()
Expand All @@ -30,6 +35,25 @@ final class OrdersTests: XCTestCase {
XCTAssertNotNil(data)
}

// 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)
}
)
}

func testAPIDeviceRegistration() async throws {
let orderData = OrderData(title: "Test Order")
try await orderData.create(on: app.db)
Expand All @@ -45,7 +69,6 @@ final class OrdersTests: XCTestCase {
try req.content.encode(RegistrationDTO(pushToken: pushToken))
},
afterResponse: { res async throws in
XCTAssertNotEqual(res.status, .unauthorized)
XCTAssertEqual(res.status, .created)
}
)
Expand All @@ -58,7 +81,6 @@ final class OrdersTests: XCTestCase {
try req.content.encode(RegistrationDTO(pushToken: pushToken))
},
afterResponse: { res async throws in
XCTAssertNotEqual(res.status, .unauthorized)
XCTAssertEqual(res.status, .ok)
}
)
Expand Down Expand Up @@ -93,7 +115,6 @@ final class OrdersTests: XCTestCase {
"\(ordersURI)devices/\(deviceLibraryIdentifier)/registrations/\(order.orderTypeIdentifier)/\(order.requireID())",
headers: ["Authorization": "AppleOrder \(order.authenticationToken)"],
afterResponse: { res async throws in
XCTAssertNotEqual(res.status, .unauthorized)
XCTAssertEqual(res.status, .ok)
}
)
Expand Down Expand Up @@ -126,5 +147,14 @@ final class OrdersTests: XCTestCase {
try await orderData.create(on: app.db)
let order = try await orderData.$order.get(on: app.db)
try await ordersService.sendPushNotifications(for: order, on: app.db)

try await app.test(
.POST,
"\(ordersURI)push/\(order.orderTypeIdentifier)/\(order.requireID())",
headers: ["X-Secret": "foo"],
afterResponse: { res async throws in
XCTAssertEqual(res.status, .noContent)
}
)
}
}
80 changes: 76 additions & 4 deletions Tests/PassesTests/PassesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ final class PassesTests: XCTestCase {

PassesService.register(migrations: app.migrations)
app.migrations.add(CreatePassData())
passesService = try PassesService(app: app, delegate: passDelegate, pushRoutesMiddleware: SecretMiddleware(secret: "foo"))
passesService = try PassesService(
app: app,
delegate: passDelegate,
pushRoutesMiddleware: SecretMiddleware(secret: "foo"),
logger: app.logger
)
app.databases.middleware.use(PassDataMiddleware(service: passesService), on: .sqlite)

try await app.autoMigrate()
Expand Down Expand Up @@ -57,6 +62,67 @@ final class PassesTests: XCTestCase {
XCTAssertGreaterThan(dataPersonalize.count, data.count)
}

// 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)
}
)
}

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: "[email protected]",
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
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)
XCTAssertEqual(personalizationQuery[0].emailAddress, personalizationDict.requiredPersonalizationInfo.emailAddress)
XCTAssertEqual(personalizationQuery[0].familyName, personalizationDict.requiredPersonalizationInfo.familyName)
XCTAssertEqual(personalizationQuery[0].fullName, personalizationDict.requiredPersonalizationInfo.fullName)
XCTAssertEqual(personalizationQuery[0].givenName, personalizationDict.requiredPersonalizationInfo.givenName)
XCTAssertEqual(personalizationQuery[0].ISOCountryCode, personalizationDict.requiredPersonalizationInfo.ISOCountryCode)
XCTAssertEqual(personalizationQuery[0].phoneNumber, personalizationDict.requiredPersonalizationInfo.phoneNumber)
XCTAssertEqual(personalizationQuery[0].postalCode, personalizationDict.requiredPersonalizationInfo.postalCode)
}

func testAPIDeviceRegistration() async throws {
let passData = PassData(title: "Test Pass")
try await passData.create(on: app.db)
Expand All @@ -72,7 +138,6 @@ final class PassesTests: XCTestCase {
try req.content.encode(RegistrationDTO(pushToken: pushToken))
},
afterResponse: { res async throws in
XCTAssertNotEqual(res.status, .unauthorized)
XCTAssertEqual(res.status, .created)
}
)
Expand All @@ -85,7 +150,6 @@ final class PassesTests: XCTestCase {
try req.content.encode(RegistrationDTO(pushToken: pushToken))
},
afterResponse: { res async throws in
XCTAssertNotEqual(res.status, .unauthorized)
XCTAssertEqual(res.status, .ok)
}
)
Expand Down Expand Up @@ -118,7 +182,6 @@ final class PassesTests: XCTestCase {
"\(passesURI)devices/\(deviceLibraryIdentifier)/registrations/\(pass.passTypeIdentifier)/\(pass.requireID())",
headers: ["Authorization": "ApplePass \(pass.authenticationToken)"],
afterResponse: { res async throws in
XCTAssertNotEqual(res.status, .unauthorized)
XCTAssertEqual(res.status, .ok)
}
)
Expand Down Expand Up @@ -151,5 +214,14 @@ final class PassesTests: XCTestCase {
try await passData.create(on: app.db)
let pass = try await passData.$pass.get(on: app.db)
try await passesService.sendPushNotifications(for: pass, on: app.db)

try await app.test(
.POST,
"\(passesURI)push/\(pass.passTypeIdentifier)/\(pass.requireID())",
headers: ["X-Secret": "foo"],
afterResponse: { res async throws in
XCTAssertEqual(res.status, .noContent)
}
)
}
}

0 comments on commit 398127e

Please sign in to comment.