-
-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathFluentSQLiteDriverTests.swift
240 lines (216 loc) · 10.8 KB
/
FluentSQLiteDriverTests.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
import FluentBenchmark
import FluentSQLiteDriver
import XCTest
import Logging
import NIO
import FluentSQL
import SQLiteNIO
import FluentKit
import SQLKit
func XCTAssertThrowsErrorAsync<T>(
_ expression: @autoclosure () async throws -> T,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath, line: UInt = #line,
_ callback: (any Error) -> Void = { _ in }
) async {
do {
_ = try await expression()
XCTAssertThrowsError({}(), message(), file: file, line: line, callback)
} catch {
XCTAssertThrowsError(try { throw error }(), message(), file: file, line: line, callback)
}
}
func XCTAssertNoThrowAsync<T>(
_ expression: @autoclosure () async throws -> T,
_ message: @autoclosure () -> String = "",
file: StaticString = #filePath, line: UInt = #line
) async {
do {
_ = try await expression()
} catch {
XCTAssertNoThrow(try { throw error }(), message(), file: file, line: line)
}
}
final class FluentSQLiteDriverTests: XCTestCase {
func testAggregate() throws { try self.benchmarker.testAggregate() }
func testArray() throws { try self.benchmarker.testArray() }
func testBatch() throws { try self.benchmarker.testBatch() }
func testChild() throws { try self.benchmarker.testChild() }
func testChildren() throws { try self.benchmarker.testChildren() }
func testCodable() throws { try self.benchmarker.testCodable() }
func testChunk() throws { try self.benchmarker.testChunk() }
func testCompositeID() throws { try self.benchmarker.testCompositeID() }
func testCRUD() throws { try self.benchmarker.testCRUD() }
func testEagerLoad() throws { try self.benchmarker.testEagerLoad() }
func testEnum() throws { try self.benchmarker.testEnum() }
func testFilter() throws { try self.benchmarker.testFilter() }
func testGroup() throws { try self.benchmarker.testGroup() }
func testID() throws { try self.benchmarker.testID() }
func testJoin() throws { try self.benchmarker.testJoin() }
func testMiddleware() throws { try self.benchmarker.testMiddleware() }
func testMigrator() throws { try self.benchmarker.testMigrator() }
func testModel() throws { try self.benchmarker.testModel() }
func testOptionalParent() throws { try self.benchmarker.testOptionalParent() }
func testPagination() throws { try self.benchmarker.testPagination() }
func testParent() throws { try self.benchmarker.testParent() }
func testPerformance() throws { try self.benchmarker.testPerformance() }
func testRange() throws { try self.benchmarker.testRange() }
func testSchema() throws { try self.benchmarker.testSchema() }
func testSet() throws { try self.benchmarker.testSet() }
func testSiblings() throws { try self.benchmarker.testSiblings() }
func testSoftDelete() throws { try self.benchmarker.testSoftDelete() }
func testSort() throws { try self.benchmarker.testSort() }
func testSQL() throws { try self.benchmarker.testSQL() }
func testTimestamp() throws { try self.benchmarker.testTimestamp() }
func testTransaction() throws { try self.benchmarker.testTransaction() }
func testUnique() throws { try self.benchmarker.testUnique() }
func testDatabaseError() async throws {
let sql = (self.database as! any SQLDatabase)
await XCTAssertThrowsErrorAsync(try await sql.raw("asdf").run()) {
XCTAssertTrue(($0 as? any DatabaseError)?.isSyntaxError ?? false, "\(String(reflecting: $0))")
XCTAssertFalse(($0 as? any DatabaseError)?.isConstraintFailure ?? true, "\(String(reflecting: $0))")
XCTAssertFalse(($0 as? any DatabaseError)?.isConnectionClosed ?? true, "\(String(reflecting: $0))")
}
try await sql.drop(table: "foo").ifExists().run()
try await sql.create(table: "foo").column("name", type: .text, .unique).run()
try await sql.insert(into: "foo").columns("name").values("bar").run()
await XCTAssertThrowsErrorAsync(try await sql.insert(into: "foo").columns("name").values("bar").run()) {
XCTAssertTrue(($0 as? any DatabaseError)?.isConstraintFailure ?? false, "\(String(reflecting: $0))")
XCTAssertFalse(($0 as? any DatabaseError)?.isSyntaxError ?? true, "\(String(reflecting: $0))")
XCTAssertFalse(($0 as? any DatabaseError)?.isConnectionClosed ?? true, "\(String(reflecting: $0))")
}
}
// https://github.com/vapor/fluent-sqlite-driver/issues/62
func testUnsupportedUpdateMigration() async throws {
struct UserMigration_v1_0_0: AsyncMigration {
func prepare(on database: any Database) async throws {
try await database.schema("users")
.id()
.field("email", .string, .required)
.field("password", .string, .required)
.unique(on: "email")
.create()
}
func revert(on database: any Database) async throws {
try await database.schema("users").delete()
}
}
struct UserMigration_v1_2_0: AsyncMigration {
func prepare(on database: any Database) async throws {
try await database.schema("users")
.field("apple_id", .string)
.unique(on: "apple_id")
.update()
}
func revert(on database: any Database) async throws {
try await database.schema("users")
.deleteUnique(on: "apple_id")
.update()
}
}
try await UserMigration_v1_0_0().prepare(on: self.database)
await XCTAssertThrowsErrorAsync(try await UserMigration_v1_2_0().prepare(on: self.database)) {
XCTAssert(String(describing: $0).contains("adding columns"))
}
await XCTAssertThrowsErrorAsync(try await UserMigration_v1_2_0().revert(on: self.database)) {
XCTAssert(String(describing: $0).contains("adding columns"))
}
await XCTAssertNoThrowAsync(try await UserMigration_v1_0_0().revert(on: self.database))
}
// https://github.com/vapor/fluent-sqlite-driver/issues/91
func testDeleteFieldMigration() async throws {
struct UserMigration_v1_0_0: AsyncMigration {
func prepare(on database: any Database) async throws {
try await database.schema("users").id().field("email", .string, .required).field("password", .string, .required).create()
}
func revert(on database: any Database) async throws {
try await database.schema("users").delete()
}
}
struct UserMigration_v1_1_0: AsyncMigration {
func prepare(on database: any Database) async throws {
try await database.schema("users").deleteField("password").update()
}
func revert(on database: any Database) async throws {
try await database.schema("users").field("password", .string, .required).update()
}
}
await XCTAssertNoThrowAsync(try await UserMigration_v1_0_0().prepare(on: self.database))
await XCTAssertNoThrowAsync(try await UserMigration_v1_1_0().prepare(on: self.database))
await XCTAssertNoThrowAsync(try await UserMigration_v1_1_0().revert(on: self.database))
await XCTAssertNoThrowAsync(try await UserMigration_v1_0_0().revert(on: self.database))
}
func testCustomJSON() async throws {
struct Metadata: Codable { let createdAt: Date }
final class Event: Model, @unchecked Sendable {
static let schema = "events"
@ID(custom: "id", generatedBy: .database) var id: Int?
@Field(key: "metadata") var metadata: Metadata
}
final class EventStringlyTyped: Model, @unchecked Sendable {
static let schema = "events"
@ID(custom: "id", generatedBy: .database) var id: Int?
@Field(key: "metadata") var metadata: [String: String]
}
struct EventMigration: AsyncMigration {
func prepare(on database: any Database) async throws {
try await database.schema(Event.schema)
.field("id", .int, .identifier(auto: false))
.field("metadata", .json, .required)
.create()
}
func revert(on database: any Database) async throws {
try await database.schema(Event.schema).delete()
}
}
let jsonEncoder = JSONEncoder(); jsonEncoder.dateEncodingStrategy = .iso8601
let jsonDecoder = JSONDecoder(); jsonDecoder.dateDecodingStrategy = .iso8601
let iso8601 = DatabaseID(string: "iso8601")
self.dbs.use(.sqlite(.memory, dataEncoder: .init(json: jsonEncoder), dataDecoder: .init(json: jsonDecoder)), as: iso8601)
let db = self.dbs.database(iso8601, logger: .init(label: "test"), on: self.dbs.eventLoopGroup.any())!
try await EventMigration().prepare(on: db)
do {
let date = Date()
let event = Event()
event.id = 1
event.metadata = Metadata(createdAt: date)
try await event.save(on: db)
let rows = try await EventStringlyTyped.query(on: db).filter(\.$id == 1).all()
XCTAssertEqual(rows[0].metadata["createdAt"], ISO8601DateFormatter().string(from: date))
} catch {
try? await EventMigration().revert(on: db)
throw error
}
try await EventMigration().revert(on: db)
}
var benchmarker: FluentBenchmarker { .init(databases: self.dbs) }
var database: (any Database)!
var dbs: Databases!
let benchmarkPath = FileManager.default.temporaryDirectory.appendingPathComponent("benchmark.sqlite").absoluteString
override class func setUp() {
XCTAssert(isLoggingConfigured)
}
override func setUpWithError() throws {
try super.setUpWithError()
self.dbs = Databases(threadPool: NIOThreadPool.singleton, on: MultiThreadedEventLoopGroup.singleton)
self.dbs.use(.sqlite(.memory), as: .sqlite)
self.dbs.use(.sqlite(.file(self.benchmarkPath)), as: .init(string: "benchmark"))
self.database = self.dbs.database(.sqlite, logger: .init(label: "test.fluent.sqlite"), on: MultiThreadedEventLoopGroup.singleton.any())
}
override func tearDown() async throws {
await self.dbs.shutdownAsync()
self.dbs = nil
try await super.tearDown()
}
}
func env(_ name: String) -> String? {
ProcessInfo.processInfo.environment[name]
}
let isLoggingConfigured: Bool = {
LoggingSystem.bootstrap { label in
var handler = StreamLogHandler.standardOutput(label: label)
handler.logLevel = env("LOG_LEVEL").flatMap { .init(rawValue: $0) } ?? .debug
return handler
}
return true
}()