Skip to content

Commit 07123d9

Browse files
[Testing] Convert more tests to swift-testing (#93)
1 parent 2e66ca2 commit 07123d9

File tree

57 files changed

+1176
-973
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1176
-973
lines changed

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ jobs:
1717
- name: Build
1818
run: swift build
1919
- name: Run tests
20-
run: swift test
20+
run: swift test --no-parallel
2121
test-linux:
2222
runs-on: ubuntu-22.04
2323
strategy:
2424
matrix:
25-
swift: ["6.0"]
25+
swift: ["6.1"]
2626
container: swift:${{ matrix.swift }}
2727
steps:
2828
- uses: actions/checkout@v3
@@ -31,4 +31,4 @@ jobs:
3131
- name: Build
3232
run: swift build
3333
- name: Run tests
34-
run: swift test
34+
run: swift test --no-parallel

Alchemy/Sources/Application/Application.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ public protocol Application: Router {
3939
extension Application {
4040
/// @main support
4141
public static func main() async throws {
42-
Main = Self()
43-
try await Main.start()
42+
try await Self().start()
4443
}
4544

4645
/// Start the app. This boots the app, runs a command based on arguments,
@@ -73,6 +72,7 @@ extension Application {
7372

7473
/// Sets up the app for running.
7574
public func willRun() async throws {
75+
Main = self
7676
try await Life.boot()
7777
}
7878

Alchemy/Sources/Application/Services+Application.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,26 @@ public var Thread: NIOThreadPool { Container.$threadPool }
2020

2121
/// The current application.
2222
public internal(set) var Main: Application {
23-
get { Container.$application }
24-
set { Container.$application = newValue }
23+
get { Container.main.application }
24+
set { Container.main.application = newValue }
2525
}
2626

2727
// MARK: Services
2828

2929
extension Container {
30-
@Singleton public var application: Application { fatalError("Default application hasn't been set!") }
31-
@Singleton public var lifecycle = Lifecycle(app: $application)
30+
var application: Application {
31+
get {
32+
guard let $_application else {
33+
preconditionFailure("The main application hasn't been registered. Has `app.willRun` been called?")
34+
}
35+
36+
return $_application
37+
}
38+
set { $_application = newValue }
39+
}
40+
41+
@Singleton var _application: Application? = nil
42+
@Singleton public var lifecycle = Lifecycle(app: application)
3243
@Singleton public var env: Environment = .createDefault()
3344
@Singleton public var threadPool: NIOThreadPool = .singleton
3445
@Singleton public var eventLoopGroup: EventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: isTest ? 1 : System.coreCount)

Alchemy/Sources/Auth/BasicAuthable.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ extension BasicAuthable {
7777
///
7878
/// - Returns: A `BasicAuthMiddleware<Self>` for authenticating
7979
/// requests.
80-
public static func basicAuthMiddleware() -> BasicAuthMiddleware<Self> {
81-
BasicAuthMiddleware()
80+
public static func basicAuthMiddleware(db: Database = DB) -> BasicAuthMiddleware<Self> {
81+
BasicAuthMiddleware(db: db)
8282
}
8383

8484
/// Authenticates this model with a username and password.
@@ -91,8 +91,13 @@ extension BasicAuthable {
9191
/// - Returns: A the authenticated `BasicAuthable`, if there was
9292
/// one. Throws `error` if the model is not found, or the
9393
/// password doesn't match.
94-
public static func authenticate(username: String, password: String, else error: Error = HTTPError(.unauthorized)) async throws -> Self {
95-
let rows = try await DB
94+
public static func authenticate(
95+
db: Database = DB,
96+
username: String,
97+
password: String,
98+
else error: Error = HTTPError(.unauthorized)
99+
) async throws -> Self {
100+
let rows = try await db
96101
.table(Self.table)
97102
.where(usernameKeyString == username)
98103
.select("\(table).*", passwordKeyString)
@@ -122,12 +127,18 @@ extension BasicAuthable {
122127
/// basic auth values don't match a row in the database, an
123128
/// `HTTPError(.unauthorized)` will be thrown.
124129
public struct BasicAuthMiddleware<B: BasicAuthable>: Middleware {
130+
private let db: Database
131+
132+
public init(db: Database = DB) {
133+
self.db = db
134+
}
135+
125136
public func handle(_ request: Request, next: Next) async throws -> Response {
126137
guard let basicAuth = request.basicAuth() else {
127138
throw HTTPError(.unauthorized)
128139
}
129140

130-
let model = try await B.authenticate(username: basicAuth.username, password: basicAuth.password)
141+
let model = try await B.authenticate(db: db, username: basicAuth.username, password: basicAuth.password)
131142
return try await next(request.set(model))
132143
}
133144
}

Alchemy/Sources/Auth/TokenAuthable.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public protocol TokenAuthable: Model {
3939
/// associated with the request.
4040
associatedtype Authorizes: Model
4141

42-
/// The user in question.
42+
/// The user this token authorizes.
4343
var user: Authorizes { get async throws }
4444

4545
/// The name of the row that stores the token's value. Defaults to "value"`.
@@ -55,8 +55,8 @@ extension TokenAuthable {
5555
///
5656
/// - Returns: A `TokenAuthMiddleware<Self>` for authenticating
5757
/// requests.
58-
public static func tokenAuthMiddleware() -> TokenAuthMiddleware<Self> {
59-
TokenAuthMiddleware()
58+
public static func tokenAuthMiddleware(db: Database = DB) -> TokenAuthMiddleware<Self> {
59+
TokenAuthMiddleware(db: db)
6060
}
6161
}
6262

@@ -68,12 +68,19 @@ extension TokenAuthable {
6868
/// header, or the token value isn't valid, an
6969
/// `HTTPError(.unauthorized)` will be thrown.
7070
public struct TokenAuthMiddleware<T: TokenAuthable>: Middleware {
71+
private let db: Database
72+
73+
init(db: Database = DB) {
74+
self.db = db
75+
}
76+
7177
public func handle(_ request: Request, next: Next) async throws -> Response {
7278
guard let bearerAuth = request.bearerAuth() else {
7379
throw HTTPError(.unauthorized)
7480
}
7581

7682
guard let model = try await T
83+
.query(on: db)
7784
.where(T.valueKeyString == bearerAuth.token)
7885
.first()
7986
else {

Alchemy/Sources/Database/Database.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ public final class Database {
3333
self.provider = provider
3434
self.grammar = grammar
3535
self.logging = logging
36-
self.migrations = Main.migrations
37-
self.seeders = Main.seeders
38-
Life.onShutdown { try await provider.shutdown() }
36+
37+
if let app = Container.$_application {
38+
self.migrations = app.migrations
39+
self.seeders = app.seeders
40+
Life.onShutdown { try await provider.shutdown() }
41+
} else {
42+
self.migrations = []
43+
self.seeders = []
44+
}
3945
}
4046

4147
/// Log all executed queries to the `debug` level.

Alchemy/Sources/Database/Migrations/Database+Migration.swift

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -37,31 +37,13 @@ extension Database {
3737
let runAt: Date?
3838
}
3939

40-
/// Applies all outstanding migrations to the database in a single
41-
/// batch. Migrations are read from `database.migrations`.
42-
public func migrate() async throws {
43-
let applied = try await getAppliedMigrations().map(\.name)
44-
let toApply = migrations.filter { !applied.contains($0.name) }
45-
try await migrate(toApply)
46-
}
47-
48-
/// Rolls back all migrations from all batches.
49-
public func reset() async throws {
50-
try await rollback(getAppliedMigrations().reversed())
51-
}
52-
53-
/// Rolls back the latest migration batch.
54-
public func rollback() async throws {
55-
let lastBatch = try await getLastBatch()
56-
let migrations = try await getAppliedMigrations(batch: lastBatch, enforceRegistration: true)
57-
try await rollback(migrations.reversed())
58-
}
40+
/// Applies the provided migrations or all outstanding migrations to the
41+
/// database in a single batch. Migrations are read from
42+
/// `database.migrations`.
43+
public func migrate(_ migrations: [Migration]? = nil) async throws {
44+
let appliedMigrations = try await getAppliedMigrations().map(\.name)
45+
let migrations = (migrations ?? self.migrations).filter { !appliedMigrations.contains($0.name) }
5946

60-
/// Run the `.up` functions of an array of migrations in order.
61-
///
62-
/// - Parameters:
63-
/// - migrations: The migrations to apply to this database.
64-
public func migrate(_ migrations: [Migration]) async throws {
6547
guard !migrations.isEmpty else {
6648
Log.info("Nothing to migrate.".green)
6749
return
@@ -77,6 +59,18 @@ extension Database {
7759
}
7860
}
7961

62+
/// Rolls back all migrations from all batches.
63+
public func reset() async throws {
64+
try await rollback(getAppliedMigrations().reversed())
65+
}
66+
67+
/// Rolls back the latest migration batch.
68+
public func rollback() async throws {
69+
let lastBatch = try await getLastBatch()
70+
let migrations = try await getAppliedMigrations(batch: lastBatch, enforceRegistration: true)
71+
try await rollback(migrations.reversed())
72+
}
73+
8074
/// Run the `.down` functions of an array of migrations, in order.
8175
///
8276
/// - Parameter migrations: The migrations to rollback on this

Alchemy/Sources/Database/Query/Query+Where.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public struct SQLWhere: Hashable, SQLConvertible {
5656
} else if op == .equals {
5757
return SQL("\(boolean) \(key) IS NULL")
5858
} else {
59-
fatalError("Can't use any where operators other than .notEqualTo or .equals if the value is NULL.")
59+
preconditionFailure("Can't use any where operators other than .notEqualTo or .equals if the value is NULL.")
6060
}
6161
} else {
6262
return SQL("\(boolean) \(key) \(op) ?", input: [sql])

Alchemy/Sources/Database/Rune/Model/Coding/SQLRowEncoder.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,19 @@ final class SQLRowEncoder: Encoder {
2121
}
2222

2323
mutating func nestedContainer<NestedKey: CodingKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
24-
fatalError("Nested coding of `Model` not supported.")
24+
preconditionFailure("Nested coding of `Model` not supported.")
2525
}
2626

2727
mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
28-
fatalError("Nested coding of `Model` not supported.")
28+
preconditionFailure("Nested coding of `Model` not supported.")
2929
}
3030

3131
mutating func superEncoder() -> Encoder {
32-
fatalError("Superclass encoding of `Model` not supported.")
32+
preconditionFailure("Superclass encoding of `Model` not supported.")
3333
}
3434

3535
mutating func superEncoder(forKey key: Key) -> Encoder {
36-
fatalError("Superclass encoding of `Model` not supported.")
36+
preconditionFailure("Superclass encoding of `Model` not supported.")
3737
}
3838
}
3939

@@ -73,10 +73,10 @@ final class SQLRowEncoder: Encoder {
7373
}
7474

7575
func unkeyedContainer() -> UnkeyedEncodingContainer {
76-
fatalError("`Model`s should never encode to an unkeyed container.")
76+
preconditionFailure("`Model`s should never encode to an unkeyed container.")
7777
}
7878

7979
func singleValueContainer() -> SingleValueEncodingContainer {
80-
fatalError("`Model`s should never encode to a single value container.")
80+
preconditionFailure("`Model`s should never encode to a single value container.")
8181
}
8282
}

Alchemy/Sources/Database/Rune/Model/Model+CRUD.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,10 @@ extension Model {
126126
}
127127

128128
@discardableResult
129-
public func update<E: Encodable>(on db: Database = database, _ encodable: E, keyMapping: KeyMapping = .snakeCase, jsonEncoder: JSONEncoder = JSONEncoder()) async throws -> Self {
130-
let fields = try encodable.sqlFields(keyMapping: keyMapping, jsonEncoder: jsonEncoder)
131-
print("fields \(fields)")
132-
return try await update(encodable.sqlFields(keyMapping: keyMapping, jsonEncoder: jsonEncoder))
129+
public func update<E: Encodable>(on db: Database = database,
130+
_ encodable: E, keyMapping: KeyMapping = .snakeCase,
131+
jsonEncoder: JSONEncoder = JSONEncoder()) async throws -> Self {
132+
try await update(on: db, encodable.sqlFields(keyMapping: keyMapping, jsonEncoder: jsonEncoder))
133133
}
134134

135135
// MARK: UPSERT

0 commit comments

Comments
 (0)