feat: Adds api route tests. Tested user interface works as expected, still needs some work on vendors form.
This commit is contained in:
@@ -19,7 +19,7 @@ let package = Package(
|
|||||||
.package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
|
.package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
|
||||||
// 🪶 Fluent driver for SQLite.
|
// 🪶 Fluent driver for SQLite.
|
||||||
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
|
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
|
||||||
// 🔵 Non-blocking, event-driven networking for Swift. Used for, custom executors
|
// 🔵 Non-blocking, event-driven networking f Swift. Used for, custom executors
|
||||||
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
|
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
|
||||||
.package(url: "https://github.com/pointfreeco/swift-dependencies.git", from: "1.6.3"),
|
.package(url: "https://github.com/pointfreeco/swift-dependencies.git", from: "1.6.3"),
|
||||||
.package(url: "https://github.com/sliemeobn/elementary.git", from: "0.3.2"),
|
.package(url: "https://github.com/sliemeobn/elementary.git", from: "0.3.2"),
|
||||||
@@ -49,13 +49,20 @@ let package = Package(
|
|||||||
swiftSettings: swiftSettings
|
swiftSettings: swiftSettings
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "AppTests",
|
name: "ViewRouteTests",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "App"),
|
.target(name: "App"),
|
||||||
.product(name: "VaporTesting", package: "vapor")
|
.product(name: "VaporTesting", package: "vapor")
|
||||||
],
|
],
|
||||||
swiftSettings: swiftSettings
|
swiftSettings: swiftSettings
|
||||||
),
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "ApiRouteTests",
|
||||||
|
dependencies: [
|
||||||
|
.target(name: "SharedModels")
|
||||||
|
],
|
||||||
|
swiftSettings: swiftSettings
|
||||||
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "DatabaseClient",
|
name: "DatabaseClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ private let viewProtectedMiddleware: [any Middleware] = [
|
|||||||
UserPasswordAuthenticator(),
|
UserPasswordAuthenticator(),
|
||||||
UserSessionAuthenticator(),
|
UserSessionAuthenticator(),
|
||||||
User.redirectMiddleware { req in
|
User.redirectMiddleware { req in
|
||||||
"/login?next=\(req.url)"
|
"/login?next=\(req.url.string)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ extension SharedModels.ViewRoute {
|
|||||||
let token = try await users.login(.init(username: login.username, password: login.password))
|
let token = try await users.login(.init(username: login.username, password: login.password))
|
||||||
let user = try await users.get(token.userID)!
|
let user = try await users.get(token.userID)!
|
||||||
request.session.authenticate(user)
|
request.session.authenticate(user)
|
||||||
|
request.logger.info("Logged in next: \(login.next ?? "N/A")")
|
||||||
return await request.render {
|
return await request.render {
|
||||||
MainPage.loggedIn(next: login.next)
|
MainPage.loggedIn(next: login.next)
|
||||||
}
|
}
|
||||||
@@ -197,7 +198,10 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute.Search {
|
|||||||
MainPage(displayNav: true, route: .purchaseOrders) {
|
MainPage(displayNav: true, route: .purchaseOrders) {
|
||||||
div(.class("container"), .id("purchase-order-content")) {
|
div(.class("container"), .id("purchase-order-content")) {
|
||||||
search
|
search
|
||||||
PurchaseOrderTable(page: .init(items: [], metadata: .init(page: 0, per: 50, total: 0)))
|
PurchaseOrderTable(
|
||||||
|
page: .init(items: [], metadata: .init(page: 0, per: 50, total: 0)),
|
||||||
|
context: .search
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,7 +216,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute.Search {
|
|||||||
}
|
}
|
||||||
return await request.render { html }
|
return await request.render { html }
|
||||||
|
|
||||||
case let .search(context):
|
case let .request(context):
|
||||||
let results = try await database.purchaseOrders.search(context.toDatabaseQuery(), .init(page: 1, per: 25))
|
let results = try await database.purchaseOrders.search(context.toDatabaseQuery(), .init(page: 1, per: 25))
|
||||||
return await request.render {
|
return await request.render {
|
||||||
PurchaseOrderTable(page: results, context: .search)
|
PurchaseOrderTable(page: results, context: .search)
|
||||||
|
|||||||
@@ -169,10 +169,12 @@ enum IDKey: CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum Vendor: CustomStringConvertible {
|
enum Vendor: CustomStringConvertible {
|
||||||
|
case form
|
||||||
case row(id: SharedModels.Vendor.ID)
|
case row(id: SharedModels.Vendor.ID)
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .form: return "form"
|
||||||
case let .row(id): return "\(id)"
|
case let .row(id): return "\(id)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,15 +52,23 @@ struct LoggedIn: HTML {
|
|||||||
let next: String?
|
let next: String?
|
||||||
var content: some HTML {
|
var content: some HTML {
|
||||||
div(
|
div(
|
||||||
.hx.get(next ?? ViewRoute.router.path(for: .purchaseOrder(.index))),
|
.hx.get(nextRoute ?? ViewRoute.router.path(for: .purchaseOrder(.index))),
|
||||||
.hx.pushURL(true),
|
.hx.pushURL(true),
|
||||||
.hx.target("body"),
|
.hx.target(.body),
|
||||||
.hx.trigger(.event(.revealed)),
|
.hx.trigger(.event(.revealed)),
|
||||||
.hx.indicator(".hx-indicator")
|
.hx.indicator(".hx-indicator")
|
||||||
) {
|
) {
|
||||||
Img.spinner().attributes(.class("hx-indicator"))
|
Img.spinner().attributes(.class("hx-indicator"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HACK: to get search route to work after login.
|
||||||
|
var nextRoute: String? {
|
||||||
|
if let next, next.contains("search") {
|
||||||
|
return ViewRoute.router.path(for: .purchaseOrder(.search(.index(context: .employee, table: true))))
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RouteHeaderView: HTML {
|
struct RouteHeaderView: HTML {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ struct PurchaseOrderTable: HTML {
|
|||||||
if context != .search {
|
if context != .search {
|
||||||
Button.add()
|
Button.add()
|
||||||
.attributes(
|
.attributes(
|
||||||
.hx.get(route: .purchaseOrder(.index)), .hx.target(.id(.float)),
|
.hx.get(route: .purchaseOrder(.form)), .hx.target(.id(.float)),
|
||||||
.hx.swap(.outerHTML), .hx.pushURL(true)
|
.hx.swap(.outerHTML), .hx.pushURL(true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -79,9 +79,12 @@ struct PurchaseOrderTable: HTML {
|
|||||||
for purchaseOrder in page.items {
|
for purchaseOrder in page.items {
|
||||||
Row(purchaseOrder: purchaseOrder)
|
Row(purchaseOrder: purchaseOrder)
|
||||||
}
|
}
|
||||||
if page.metadata.pageCount > page.metadata.page {
|
// We set page to 0 when we're on search, but have not completed the search
|
||||||
|
// form yet, so don't add the infinite scroll row / trigger otherwise it will
|
||||||
|
// load the first page, which is not what we want, but we need the empty table
|
||||||
|
// to be available once the search form is completed.
|
||||||
|
if page.metadata.page > 0, page.metadata.pageCount > page.metadata.page {
|
||||||
tr(
|
tr(
|
||||||
// .hx.get("/purchase-orders/next?page=\(page.metadata.page + 1)&limit=\(page.metadata.per)"),
|
|
||||||
.hx.get(route: .purchaseOrder(.page(page: page.metadata.page + 1, limit: page.metadata.per))),
|
.hx.get(route: .purchaseOrder(.page(page: page.metadata.page + 1, limit: page.metadata.per))),
|
||||||
.hx.trigger(.event(.revealed)),
|
.hx.trigger(.event(.revealed)),
|
||||||
.hx.swap(.outerHTML.transition(true).swap("1s")),
|
.hx.swap(.outerHTML.transition(true).swap("1s")),
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ struct UserForm: HTML, Sendable {
|
|||||||
.hx.post(context.targetURL),
|
.hx.post(context.targetURL),
|
||||||
.hx.pushURL(context.pushURL),
|
.hx.pushURL(context.pushURL),
|
||||||
.hx.target(context.target),
|
.hx.target(context.target),
|
||||||
.hx.swap(.outerHTML),
|
.hx.swap(context == .create ? .afterBegin.transition(true).swap("0.5s") : .outerHTML),
|
||||||
.hx.on(
|
.hx.on(
|
||||||
.afterRequest,
|
.afterRequest,
|
||||||
.ifSuccessful(.resetForm, .toggleContent(.float))
|
.ifSuccessful(.resetForm, .toggleContent(.float))
|
||||||
@@ -92,12 +92,12 @@ struct UserForm: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var target: String {
|
var target: HXTarget {
|
||||||
switch self {
|
switch self {
|
||||||
case .create:
|
case .create:
|
||||||
return "next table"
|
return .id(.user(.table))
|
||||||
case .login:
|
case .login:
|
||||||
return "body"
|
return .body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Vapor
|
|||||||
struct EmployeeSelect: HTML {
|
struct EmployeeSelect: HTML {
|
||||||
|
|
||||||
let employees: [Employee]?
|
let employees: [Employee]?
|
||||||
let context: SelectContext
|
let context: ViewRoute.SelectContext
|
||||||
|
|
||||||
var content: some HTML {
|
var content: some HTML {
|
||||||
if let employees {
|
if let employees {
|
||||||
@@ -18,7 +18,7 @@ struct EmployeeSelect: HTML {
|
|||||||
.attributes(.style("margin-left: 15px;"), when: context == .purchaseOrderSearch)
|
.attributes(.style("margin-left: 15px;"), when: context == .purchaseOrderSearch)
|
||||||
} else {
|
} else {
|
||||||
div(
|
div(
|
||||||
.hx.get("/select/employee?context=\(context.rawValue)"),
|
.hx.get(route: .employee(.select(context: context))),
|
||||||
.hx.target("this"),
|
.hx.target("this"),
|
||||||
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
|
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
|
||||||
.hx.indicator("next .hx-indicator"),
|
.hx.indicator("next .hx-indicator"),
|
||||||
@@ -42,11 +42,11 @@ struct EmployeeSelect: HTML {
|
|||||||
|
|
||||||
struct VendorBranchSelect: HTML {
|
struct VendorBranchSelect: HTML {
|
||||||
let branches: [VendorBranch.Detail]?
|
let branches: [VendorBranch.Detail]?
|
||||||
let context: SelectContext
|
let context: ViewRoute.SelectContext
|
||||||
|
|
||||||
var content: some HTML {
|
var content: some HTML {
|
||||||
if let branches {
|
if let branches {
|
||||||
select(.name("vendorBranchID"), .class(context.classString)) {
|
select(.name("vendorBranchID"), .class("col-4")) {
|
||||||
for branch in branches {
|
for branch in branches {
|
||||||
option(.value(branch.id.uuidString)) { "\(branch.vendor.name) - \(branch.name)" }
|
option(.value(branch.id.uuidString)) { "\(branch.vendor.name) - \(branch.name)" }
|
||||||
}
|
}
|
||||||
@@ -54,8 +54,8 @@ struct VendorBranchSelect: HTML {
|
|||||||
.attributes(.style("margin-left: 15px;"), when: context == .purchaseOrderSearch)
|
.attributes(.style("margin-left: 15px;"), when: context == .purchaseOrderSearch)
|
||||||
} else {
|
} else {
|
||||||
div(
|
div(
|
||||||
.hx.get("/select/vendor-branches?context=\(context.rawValue)"),
|
.hx.get(route: .vendorBranch(.select(context: context))),
|
||||||
.hx.target("this"),
|
.hx.target(.this),
|
||||||
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
|
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
|
||||||
.hx.indicator("next .hx-indicator"),
|
.hx.indicator("next .hx-indicator"),
|
||||||
.hx.trigger(.event(.revealed)),
|
.hx.trigger(.event(.revealed)),
|
||||||
@@ -75,10 +75,11 @@ struct VendorBranchSelect: HTML {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SelectContext: String, Codable, Content {
|
// enum SelectContext: String, Codable, Content {
|
||||||
case purchaseOrderForm
|
// case purchaseOrderForm
|
||||||
case purchaseOrderSearch
|
// case purchaseOrderSearch
|
||||||
|
|
||||||
|
extension ViewRoute.SelectContext {
|
||||||
var classString: String {
|
var classString: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .purchaseOrderForm: return "col-3"
|
case .purchaseOrderForm: return "col-3"
|
||||||
|
|||||||
@@ -26,15 +26,15 @@ struct VendorDetail: HTML {
|
|||||||
|
|
||||||
// TODO: What route for here??
|
// TODO: What route for here??
|
||||||
var branchForm: some HTML {
|
var branchForm: some HTML {
|
||||||
// TODO: Add hidden input field with vendor id.
|
|
||||||
form(
|
form(
|
||||||
.id("branch-form"),
|
.id(.branch(.form)),
|
||||||
.hx.post("/vendors/\(vendor.id)/branches"),
|
.hx.post("/vendors/branches"),
|
||||||
.hx.target("#branches"),
|
.hx.target("#branches"),
|
||||||
.hx.swap(.beforeEnd),
|
.hx.swap(.beforeEnd),
|
||||||
.hx.on(.afterRequest, .ifSuccessful(.resetForm))
|
.hx.on(.afterRequest, .ifSuccessful(.resetForm))
|
||||||
// .custom(name: "hx-on::after-request", value: "if(event.detail.successful) this.reset();")
|
// .custom(name: "hx-on::after-request", value: "if(event.detail.successful) this.reset();")
|
||||||
) {
|
) {
|
||||||
|
input(.type(.hidden), .name("vendorID"), .value(vendor.id.uuidString))
|
||||||
input(
|
input(
|
||||||
.type(.text), .class("col-9"), .name("name"), .placeholder("Add branch..."), .required,
|
.type(.text), .class("col-9"), .name("name"), .placeholder("Add branch..."), .required,
|
||||||
// FIX: route
|
// FIX: route
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ struct VendorForm: HTML {
|
|||||||
|
|
||||||
func makeForm(vendor: Vendor?) -> some HTML {
|
func makeForm(vendor: Vendor?) -> some HTML {
|
||||||
form(
|
form(
|
||||||
.id("vendor-form"),
|
.id(.vendor(.form)),
|
||||||
vendor != nil ? .hx.put(route: targetURL) : .hx.post(route: targetURL),
|
vendor != nil ? .hx.put(route: targetURL) : .hx.post(route: targetURL),
|
||||||
.hx.target("#content"),
|
.hx.target("#content"),
|
||||||
.hx.swap(.outerHTML)
|
.hx.swap(.outerHTML)
|
||||||
@@ -63,7 +63,7 @@ struct VendorForm: HTML {
|
|||||||
.style("font-size: 1.25em; padding: 10px 20px; border-radius: 10px;"),
|
.style("font-size: 1.25em; padding: 10px 20px; border-radius: 10px;"),
|
||||||
.hx.delete(route: .vendor(.delete(id: vendor.id))),
|
.hx.delete(route: .vendor(.delete(id: vendor.id))),
|
||||||
.hx.confirm("Are you sure you want to delete this vendor?"),
|
.hx.confirm("Are you sure you want to delete this vendor?"),
|
||||||
.hx.target("#vendor_\(vendor.id)"),
|
.hx.target(.id(.vendor(.row(id: vendor.id)))),
|
||||||
.hx.swap(.outerHTML.transition(true).swap("1s")),
|
.hx.swap(.outerHTML.transition(true).swap("1s")),
|
||||||
.custom(
|
.custom(
|
||||||
name: "hx-on::after-request",
|
name: "hx-on::after-request",
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ import CasePathsCore
|
|||||||
import Foundation
|
import Foundation
|
||||||
@preconcurrency import URLRouting
|
@preconcurrency import URLRouting
|
||||||
|
|
||||||
// TODO: Switch shared to be on API routes not view routes??
|
public enum ApiRoute: Sendable, Equatable {
|
||||||
|
|
||||||
public enum ApiRoute: Sendable {
|
|
||||||
|
|
||||||
case employee(EmployeeApiRoute)
|
case employee(EmployeeApiRoute)
|
||||||
case purchaseOrder(PurchaseOrderApiRoute)
|
case purchaseOrder(PurchaseOrderApiRoute)
|
||||||
@@ -37,7 +35,7 @@ public enum ApiRoute: Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EmployeeApiRoute: Sendable {
|
public enum EmployeeApiRoute: Sendable, Equatable {
|
||||||
case create(Employee.Create)
|
case create(Employee.Create)
|
||||||
case delete(id: Employee.ID)
|
case delete(id: Employee.ID)
|
||||||
case get(id: Employee.ID)
|
case get(id: Employee.ID)
|
||||||
@@ -72,7 +70,7 @@ public enum ApiRoute: Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PurchaseOrderApiRoute: Sendable {
|
public enum PurchaseOrderApiRoute: Sendable, Equatable {
|
||||||
case create(PurchaseOrder.Create)
|
case create(PurchaseOrder.Create)
|
||||||
case delete(id: PurchaseOrder.ID)
|
case delete(id: PurchaseOrder.ID)
|
||||||
case get(id: PurchaseOrder.ID)
|
case get(id: PurchaseOrder.ID)
|
||||||
@@ -111,7 +109,7 @@ public enum ApiRoute: Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add login / logout.
|
// TODO: Add login / logout.
|
||||||
public enum UserApiRoute: Sendable {
|
public enum UserApiRoute: Sendable, Equatable {
|
||||||
case create(User.Create)
|
case create(User.Create)
|
||||||
case delete(id: User.ID)
|
case delete(id: User.ID)
|
||||||
case get(id: User.ID)
|
case get(id: User.ID)
|
||||||
@@ -146,7 +144,7 @@ public enum ApiRoute: Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum VendorApiRoute: Sendable {
|
public enum VendorApiRoute: Sendable, Equatable {
|
||||||
case index(withBranches: Bool? = nil)
|
case index(withBranches: Bool? = nil)
|
||||||
case create(Vendor.Create)
|
case create(Vendor.Create)
|
||||||
case delete(id: Vendor.ID)
|
case delete(id: Vendor.ID)
|
||||||
@@ -173,8 +171,10 @@ public enum ApiRoute: Sendable {
|
|||||||
Path { rootPath }
|
Path { rootPath }
|
||||||
Method.get
|
Method.get
|
||||||
Query {
|
Query {
|
||||||
Field("branches", default: nil) {
|
Optionally {
|
||||||
Optionally { Bool.parser() }
|
Field("branches", default: nil) {
|
||||||
|
Bool.parser()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +186,7 @@ public enum ApiRoute: Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum VendorBranchApiRoute: Sendable {
|
public enum VendorBranchApiRoute: Sendable, Equatable {
|
||||||
case create(VendorBranch.Create)
|
case create(VendorBranch.Create)
|
||||||
case delete(id: VendorBranch.ID)
|
case delete(id: VendorBranch.ID)
|
||||||
case get(id: VendorBranch.ID)
|
case get(id: VendorBranch.ID)
|
||||||
@@ -212,7 +212,7 @@ public enum ApiRoute: Sendable {
|
|||||||
Path { "vendors"; "branches" }
|
Path { "vendors"; "branches" }
|
||||||
Method.get
|
Method.get
|
||||||
Query {
|
Query {
|
||||||
Field("vendorID", default: nil) { Optionally { VendorBranch.ID.parser() } }
|
Optionally { Field("vendorID", default: nil) { VendorBranch.ID.parser() } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Route(.case(Self.update(id:updates:))) {
|
Route(.case(Self.update(id:updates:))) {
|
||||||
|
|||||||
93
Tests/ApiRouteTests/EmployeeApiRouteTests.swift
Normal file
93
Tests/ApiRouteTests/EmployeeApiRouteTests.swift
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import SharedModels
|
||||||
|
import Testing
|
||||||
|
import URLRouting
|
||||||
|
|
||||||
|
@Suite("EmployeeApiRouteTests")
|
||||||
|
struct EmployeeApiRouteTests {
|
||||||
|
let router = ApiRoute.router
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func employeeCreate() throws {
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
\"firstName\": \"Blob\",
|
||||||
|
\"lastName\": \"Esquire\",
|
||||||
|
\"active\": true
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "POST",
|
||||||
|
path: "/api/v1/employees",
|
||||||
|
body: .init(json.utf8)
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(
|
||||||
|
route == .employee(.create(.init(firstName: "Blob", lastName: "Esquire", active: true)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func employeeDelete() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "DELETE",
|
||||||
|
path: "/api/v1/employees/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(
|
||||||
|
route == .employee(.delete(id: id))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func employeeGet() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/employees/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(
|
||||||
|
route == .employee(.get(id: id))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func employeeIndex() throws {
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/employees"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(
|
||||||
|
route == .employee(.index)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func employeeUpdate() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
\"firstName\": \"Blob\",
|
||||||
|
\"lastName\": \"Esquire\",
|
||||||
|
\"active\": true
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "PUT",
|
||||||
|
path: "/api/v1/employees/\(id)",
|
||||||
|
body: .init(json.utf8)
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(
|
||||||
|
route == .employee(.update(
|
||||||
|
id: id,
|
||||||
|
updates: .init(firstName: "Blob", lastName: "Esquire", active: true)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
32
Tests/ApiRouteTests/LoginApiRouteTests.swift
Normal file
32
Tests/ApiRouteTests/LoginApiRouteTests.swift
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import SharedModels
|
||||||
|
import Testing
|
||||||
|
import URLRouting
|
||||||
|
|
||||||
|
// @Suite("LoginViewRouteTests")
|
||||||
|
// struct LoginViewRouteTests {
|
||||||
|
// let router = ViewRoute.router
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// func get() throws {
|
||||||
|
// var request = URLRequestData(
|
||||||
|
// method: "GET",
|
||||||
|
// path: "/login",
|
||||||
|
// query: ["next": ["/users"]]
|
||||||
|
// )
|
||||||
|
// let route = try router.parse(&request)
|
||||||
|
// #expect(route == .login(.index(next: "/users")))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// @Test
|
||||||
|
// func post() throws {
|
||||||
|
// var request = URLRequestData(
|
||||||
|
// method: "POST",
|
||||||
|
// path: "/login",
|
||||||
|
// body: .init("username=foo&password=super-secret&next=/users".utf8)
|
||||||
|
// )
|
||||||
|
// let route = try router.parse(&request)
|
||||||
|
// #expect(route == .login(.post(.init(username: "foo", password: "super-secret", next: "/users"))))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
93
Tests/ApiRouteTests/PurchaseOrderApiRouteTests.swift
Normal file
93
Tests/ApiRouteTests/PurchaseOrderApiRouteTests.swift
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import SharedModels
|
||||||
|
import Testing
|
||||||
|
import URLRouting
|
||||||
|
|
||||||
|
@Suite("PurchaseOrderApiRouteTests")
|
||||||
|
struct PurchaseOrderApiRouteTests {
|
||||||
|
let router = ApiRoute.router
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func create() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
\"id\": 1,
|
||||||
|
\"workOrder\": 12345,
|
||||||
|
\"materials\": \"some\",
|
||||||
|
\"customer\": \"Testy\",
|
||||||
|
\"truckStock\": false,
|
||||||
|
\"createdByID\": \"\(id)\",
|
||||||
|
"createdForID\": \"\(id)\",
|
||||||
|
"vendorBranchID\": \"\(id)\"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "POST",
|
||||||
|
path: "/api/v1/purchase-orders",
|
||||||
|
body: .init(json.utf8)
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .purchaseOrder(.create(.init(
|
||||||
|
id: 1,
|
||||||
|
workOrder: 12345,
|
||||||
|
materials: "some",
|
||||||
|
customer: "Testy",
|
||||||
|
truckStock: false,
|
||||||
|
createdByID: id,
|
||||||
|
createdForID: id,
|
||||||
|
vendorBranchID: id
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func delete() throws {
|
||||||
|
let id = 1
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "DELETE",
|
||||||
|
path: "/api/v1/purchase-orders/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .purchaseOrder(.delete(id: id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func get() throws {
|
||||||
|
let id = 1
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/purchase-orders/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .purchaseOrder(.get(id: id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func index() throws {
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/purchase-orders"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .purchaseOrder(.index))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func page() throws {
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/purchase-orders/next"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .purchaseOrder(.page(page: 1, limit: 25)))
|
||||||
|
|
||||||
|
var request2 = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/purchase-orders/next",
|
||||||
|
query: ["page": ["2"], "limit": ["50"]]
|
||||||
|
)
|
||||||
|
let route2 = try router.parse(&request2)
|
||||||
|
#expect(route2 == .purchaseOrder(.page(page: 2, limit: 50)))
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Tests/ApiRouteTests/UserApiRouteTests.swift
Normal file
85
Tests/ApiRouteTests/UserApiRouteTests.swift
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import SharedModels
|
||||||
|
import Testing
|
||||||
|
import URLRouting
|
||||||
|
|
||||||
|
@Suite("UserApiRouteTests")
|
||||||
|
struct UserApiRouteTests {
|
||||||
|
let router = ApiRoute.router
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func create() throws {
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
\"username\": \"foo\",
|
||||||
|
\"email\": \"foo@bar.com\",
|
||||||
|
\"password\": \"super-secret\",
|
||||||
|
\"confirmPassword\": \"super-secret\"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "POST",
|
||||||
|
path: "/api/v1/users",
|
||||||
|
body: .init(json.utf8)
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(
|
||||||
|
route == .user(.create(.init(
|
||||||
|
username: "foo",
|
||||||
|
email: "foo@bar.com",
|
||||||
|
password: "super-secret",
|
||||||
|
confirmPassword: "super-secret"
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func delete() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "DELETE",
|
||||||
|
path: "/api/v1/users/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .user(.delete(id: id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func get() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/users/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .user(.get(id: id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func index() throws {
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/users"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .user(.index))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func update() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
\"username\": \"bar\",
|
||||||
|
\"email\": \"bar@foo.com\"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "PATCH",
|
||||||
|
path: "/api/v1/users/\(id)",
|
||||||
|
body: .init(json.utf8)
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .user(.update(id: id, updates: .init(username: "bar", email: "bar@foo.com"))))
|
||||||
|
}
|
||||||
|
}
|
||||||
83
Tests/ApiRouteTests/VendorApiRouteTests.swift
Normal file
83
Tests/ApiRouteTests/VendorApiRouteTests.swift
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import SharedModels
|
||||||
|
import Testing
|
||||||
|
import URLRouting
|
||||||
|
|
||||||
|
@Suite("VendorApiRouteTests")
|
||||||
|
struct VendorApiRouteTests {
|
||||||
|
let router = ApiRoute.router
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func create() throws {
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
\"name\": \"Test\"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "POST",
|
||||||
|
path: "/api/v1/vendors",
|
||||||
|
body: .init(json.utf8)
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendor(.create(.init(name: "Test"))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func delete() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "DELETE",
|
||||||
|
path: "/api/v1/vendors/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendor(.delete(id: id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func get() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/vendors/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendor(.get(id: id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func index() throws {
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/vendors"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendor(.index()))
|
||||||
|
|
||||||
|
var request2 = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/vendors",
|
||||||
|
query: ["branches": ["true"]]
|
||||||
|
)
|
||||||
|
let route2 = try router.parse(&request2)
|
||||||
|
#expect(route2 == .vendor(.index(withBranches: true)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func update() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
\"name\": \"Test\"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "PUT",
|
||||||
|
path: "/api/v1/vendors/\(id)",
|
||||||
|
body: .init(json.utf8)
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendor(.update(id: id, updates: .init(name: "Test"))))
|
||||||
|
}
|
||||||
|
}
|
||||||
87
Tests/ApiRouteTests/VendorBranchApiRouteTests.swift
Normal file
87
Tests/ApiRouteTests/VendorBranchApiRouteTests.swift
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
import SharedModels
|
||||||
|
import Testing
|
||||||
|
import URLRouting
|
||||||
|
|
||||||
|
@Suite("VendorBranchApiRouteTests")
|
||||||
|
struct VendorBranchApiRouteTests {
|
||||||
|
let router = ApiRoute.router
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func create() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
\"name\": \"Test\",
|
||||||
|
\"vendorID\": \"\(id)\"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "POST",
|
||||||
|
path: "/api/v1/vendors/branches",
|
||||||
|
body: .init(json.utf8)
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendorBranch(.create(.init(name: "Test", vendorID: id))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func delete() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "DELETE",
|
||||||
|
path: "/api/v1/vendors/branches/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendorBranch(.delete(id: id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func get() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/vendors/branches/\(id)"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendorBranch(.get(id: id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func index() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/vendors/branches"
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendorBranch(.index()))
|
||||||
|
|
||||||
|
var request2 = URLRequestData(
|
||||||
|
method: "GET",
|
||||||
|
path: "/api/v1/vendors/branches",
|
||||||
|
query: ["vendorID": ["\(id)"]]
|
||||||
|
)
|
||||||
|
let route2 = try router.parse(&request2)
|
||||||
|
#expect(route2 == .vendorBranch(.index(for: id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func update() throws {
|
||||||
|
let id = UUID(0)
|
||||||
|
let json = """
|
||||||
|
{
|
||||||
|
\"name\": \"Test\"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
var request = URLRequestData(
|
||||||
|
method: "PUT",
|
||||||
|
path: "/api/v1/vendors/branches/\(id)",
|
||||||
|
body: .init(json.utf8)
|
||||||
|
)
|
||||||
|
let route = try router.parse(&request)
|
||||||
|
#expect(route == .vendorBranch(.update(id: id, updates: .init(name: "Test"))))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
// @testable import App
|
|
||||||
// import VaporTesting
|
|
||||||
// import Testing
|
|
||||||
// import Fluent
|
|
||||||
//
|
|
||||||
// @Suite("App Tests with DB", .serialized)
|
|
||||||
// struct AppTests {
|
|
||||||
// private func withApp(_ test: (Application) async throws -> ()) async throws {
|
|
||||||
// let app = try await Application.make(.testing)
|
|
||||||
// do {
|
|
||||||
// try await configure(app)
|
|
||||||
// try await app.autoMigrate()
|
|
||||||
// try await test(app)
|
|
||||||
// try await app.autoRevert()
|
|
||||||
// }
|
|
||||||
// catch {
|
|
||||||
// try? await app.autoRevert()
|
|
||||||
// try await app.asyncShutdown()
|
|
||||||
// throw error
|
|
||||||
// }
|
|
||||||
// try await app.asyncShutdown()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test("Test Hello World Route")
|
|
||||||
// func helloWorld() async throws {
|
|
||||||
// try await withApp { app in
|
|
||||||
// try await app.testing().test(.GET, "hello", afterResponse: { res async in
|
|
||||||
// #expect(res.status == .ok)
|
|
||||||
// #expect(res.body.string == "Hello, world!")
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test("Getting all the Todos")
|
|
||||||
// func getAllTodos() async throws {
|
|
||||||
// try await withApp { app in
|
|
||||||
// let sampleTodos = [Todo(title: "sample1"), Todo(title: "sample2")]
|
|
||||||
// try await sampleTodos.create(on: app.db)
|
|
||||||
//
|
|
||||||
// try await app.testing().test(.GET, "todos", afterResponse: { res async throws in
|
|
||||||
// #expect(res.status == .ok)
|
|
||||||
// #expect(try res.content.decode([TodoDTO].self) == sampleTodos.map { $0.toDTO()} )
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test("Creating a Todo")
|
|
||||||
// func createTodo() async throws {
|
|
||||||
// let newDTO = TodoDTO(id: nil, title: "test")
|
|
||||||
//
|
|
||||||
// try await withApp { app in
|
|
||||||
// try await app.testing().test(.POST, "todos", beforeRequest: { req in
|
|
||||||
// try req.content.encode(newDTO)
|
|
||||||
// }, afterResponse: { res async throws in
|
|
||||||
// #expect(res.status == .ok)
|
|
||||||
// let models = try await Todo.query(on: app.db).all()
|
|
||||||
// #expect(models.map({ $0.toDTO().title }) == [newDTO.title])
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test("Deleting a Todo")
|
|
||||||
// func deleteTodo() async throws {
|
|
||||||
// let testTodos = [Todo(title: "test1"), Todo(title: "test2")]
|
|
||||||
//
|
|
||||||
// try await withApp { app in
|
|
||||||
// try await testTodos.create(on: app.db)
|
|
||||||
//
|
|
||||||
// try await app.testing().test(.DELETE, "todos/\(testTodos[0].requireID())", afterResponse: { res async throws in
|
|
||||||
// #expect(res.status == .noContent)
|
|
||||||
// let model = try await Todo.find(testTodos[0].id, on: app.db)
|
|
||||||
// #expect(model == nil)
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// extension TodoDTO: Equatable {
|
|
||||||
// public static func == (lhs: Self, rhs: Self) -> Bool {
|
|
||||||
// lhs.id == rhs.id && lhs.title == rhs.title
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
Reference in New Issue
Block a user