Skip to content

Commit 1b7de34

Browse files
test: ✅ SQLite のテストを追加
1 parent d52ddca commit 1b7de34

6 files changed

Lines changed: 454 additions & 0 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import D1Lite
2+
import SQLiteNIO
3+
import Testing
4+
5+
@testable import D1LiteSQLite
6+
7+
@Suite struct MakeSQLiteDataBindsTests {
8+
@Test func test() {
9+
#expect(
10+
makeSQLiteDataBinds([
11+
.number(1),
12+
.number(1.25),
13+
.string("hello"),
14+
.null,
15+
]) == [
16+
.integer(1),
17+
.float(1.25),
18+
.text("hello"),
19+
.null,
20+
]
21+
)
22+
}
23+
}
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
import D1Lite
2+
import D1LiteSQLite
3+
import Foundation
4+
import Testing
5+
6+
@Suite struct SQLiteD1RawDatabaseQueryClientTests {
7+
@Test(arguments: [true, false])
8+
func crudOperations(inMemory: Bool) async throws {
9+
try await withClient(inMemory: inMemory) { client in
10+
let d1 = D1SQLClient(client: client)
11+
12+
_ = try await d1.batch(
13+
D1Query<D1Void>(
14+
statement: """
15+
CREATE TABLE users(
16+
id TEXT PRIMARY KEY,
17+
name TEXT NOT NULL,
18+
age INTEGER NOT NULL,
19+
score REAL NOT NULL,
20+
active INTEGER NOT NULL,
21+
blob TEXT NOT NULL,
22+
payload TEXT NOT NULL,
23+
date_epoch INTEGER NOT NULL,
24+
date_iso TEXT NOT NULL,
25+
date_ymd TEXT NOT NULL,
26+
note TEXT,
27+
opt_int INTEGER,
28+
count INTEGER NOT NULL
29+
)
30+
""",
31+
params: []
32+
)
33+
)
34+
35+
let userID1 = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000001"))
36+
let (initialAll, initialFirst) = try await d1.batch(
37+
User.select().all(),
38+
User.select().filter(\.id == userID1).first(),
39+
)
40+
#expect(initialAll.value == [])
41+
#expect(initialFirst.value == nil)
42+
43+
let user1 = User(
44+
id: userID1,
45+
name: "Alice",
46+
age: 20,
47+
score: 12.5,
48+
isActive: true,
49+
blob: Data([1, 2, 3]),
50+
payload: .init(auth: "user", role: "admin"),
51+
dateEpoch: Date(timeIntervalSince1970: 1_725_000_000),
52+
dateISO: try Date.ISO8601FormatStyle().parse("2026-12-31T12:34:56Z"),
53+
dateYMD: try Date.ISO8601FormatStyle().parse("2025-01-02T00:00:00Z"),
54+
note: "hello",
55+
optionalCount: 7,
56+
count: 3
57+
)
58+
_ = try await d1.batch(user1.create())
59+
60+
let (afterInsertFirst) = try await d1.batch(
61+
User.select().filter(\.id == user1.id).first()
62+
)
63+
#expect(afterInsertFirst.value == user1)
64+
65+
let userID2 = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000002"))
66+
let user2 = User(
67+
id: userID2,
68+
name: "Bob",
69+
age: 30,
70+
score: 0.5,
71+
isActive: false,
72+
blob: Data([4, 5, 6]),
73+
payload: .init(auth: "user", role: "viewer"),
74+
dateEpoch: Date(timeIntervalSince1970: 1_725_100_000),
75+
dateISO: try Date.ISO8601FormatStyle().parse("2027-01-01T00:00:00Z"),
76+
dateYMD: try Date.ISO8601FormatStyle().parse("2025-02-01T00:00:00Z"),
77+
note: nil,
78+
optionalCount: nil,
79+
count: 5
80+
)
81+
let userID3 = try #require(UUID(uuidString: "00000000-0000-0000-0000-000000000003"))
82+
let user3 = User(
83+
id: userID3,
84+
name: "Cara",
85+
age: 40,
86+
score: 99.9,
87+
isActive: true,
88+
blob: Data([7, 8, 9]),
89+
payload: .init(auth: "admin", role: "owner"),
90+
dateEpoch: Date(timeIntervalSince1970: 1_725_200_000),
91+
dateISO: try Date.ISO8601FormatStyle().parse("2028-05-20T10:20:30Z"),
92+
dateYMD: try Date.ISO8601FormatStyle().parse("2025-03-10T00:00:00Z"),
93+
note: "memo",
94+
optionalCount: 0,
95+
count: 8
96+
)
97+
_ = try await d1.batch([user2, user3].create())
98+
99+
let (afterBulkAll) = try await d1.batch(User.select().all())
100+
#expect(afterBulkAll.value == [user1, user2, user3])
101+
102+
_ = try await d1.batch(
103+
User.update()
104+
.set(\.name, "Bobby")
105+
.set(\.age, 31)
106+
.set(\.score, 1.25)
107+
.set(\.isActive, true)
108+
.set(\.note, "updated")
109+
.set(\.optionalCount, 42)
110+
.filter(\.id == user2.id)
111+
.build()
112+
)
113+
var updatedUser2 = user2
114+
updatedUser2.name = "Bobby"
115+
updatedUser2.age = 31
116+
updatedUser2.score = 1.25
117+
updatedUser2.isActive = true
118+
updatedUser2.note = "updated"
119+
updatedUser2.optionalCount = 42
120+
let (updatedFirst) = try await d1.batch(
121+
User.select().filter(\.id == user2.id).first()
122+
)
123+
#expect(updatedFirst.value == updatedUser2)
124+
125+
_ = try await d1.batch(
126+
User.delete()
127+
.filter(\.id == user1.id)
128+
.build()
129+
)
130+
let (finalAll) = try await d1.batch(User.select().all())
131+
#expect(finalAll.value == Set([updatedUser2, user3]))
132+
}
133+
}
134+
135+
@Test(arguments: [true, false])
136+
func uniqueConstraintError(inMemory: Bool) async throws {
137+
try await withClient(inMemory: inMemory) { client in
138+
let d1 = D1SQLClient(client: client)
139+
140+
_ = try await d1.batch(
141+
D1Query<D1Void>(
142+
statement: """
143+
CREATE TABLE locks(
144+
table_name TEXT NOT NULL,
145+
seq_nr INTEGER NOT NULL,
146+
created_at INTEGER NOT NULL,
147+
UNIQUE(table_name, seq_nr)
148+
)
149+
""",
150+
params: []
151+
)
152+
)
153+
154+
let lock = Lock(tableName: "users", seqNr: 1, createdAt: Date(timeIntervalSince1970: 1))
155+
let duplicate = Lock(tableName: "users", seqNr: 1, createdAt: Date(timeIntervalSince1970: 2))
156+
157+
await #expect(
158+
throws: D1RawDatabaseQueryClientError(
159+
errors: [
160+
.init(code: 7500, message: "UNIQUE constraint failed: locks.table_name, locks.seq_nr: SQLITE_CONSTRAINT")
161+
],
162+
messages: []
163+
)
164+
) {
165+
try await d1.batch(lock.create(), duplicate.create())
166+
}
167+
}
168+
}
169+
170+
private func withClient(
171+
inMemory: Bool,
172+
action: @Sendable (SQLiteD1RawDatabaseQueryClient) async throws -> Void
173+
) async throws {
174+
let uuid = UUID()
175+
let filePathURL = FileManager.default.temporaryDirectory.appending(path: "\(uuid)-test.db")
176+
177+
let config: SQLiteD1RawDatabaseQueryClient.Configuration =
178+
if inMemory {
179+
.inMemory
180+
} else {
181+
.file(.init(filePathURL.path(percentEncoded: false)))
182+
}
183+
184+
let client = SQLiteD1RawDatabaseQueryClient(config: config)
185+
186+
do {
187+
try await action(client)
188+
try? await client.shutdown()
189+
try? FileManager.default.removeItem(atPath: filePathURL.path(percentEncoded: false))
190+
} catch {
191+
try? await client.shutdown()
192+
try? FileManager.default.removeItem(atPath: filePathURL.path(percentEncoded: false))
193+
throw error
194+
}
195+
}
196+
}
197+
198+
@D1Table(schema: "users")
199+
struct User: Sendable, Hashable {
200+
@D1Column
201+
var id: UUID
202+
203+
@D1Column
204+
var name: String
205+
206+
@D1Column
207+
var age: Int
208+
209+
@D1Column
210+
var score: Double
211+
212+
@D1Column(name: "active")
213+
var isActive: Bool
214+
215+
@D1Column
216+
var blob: Data
217+
218+
@D1Column
219+
var payload: Payload
220+
221+
@D1Column(name: "date_epoch", formatStyle: D1DateFormatStyle(format: .epoch))
222+
var dateEpoch: Date
223+
224+
@D1Column(name: "date_iso", formatStyle: D1DateFormatStyle(format: .iso8601))
225+
var dateISO: Date
226+
227+
@D1Column(name: "date_ymd", formatStyle: D1DateFormatStyle(format: .yyyyMMdd))
228+
var dateYMD: Date
229+
230+
@D1Column(name: "note")
231+
var note: String?
232+
233+
@D1Column(name: "opt_int")
234+
var optionalCount: Int?
235+
236+
@D1Column(name: "count")
237+
var count: Int
238+
239+
struct Payload: Sendable, Hashable, Codable {
240+
var auth: String
241+
var role: String
242+
}
243+
}
244+
245+
@D1Table(schema: "locks")
246+
struct Lock: Sendable, Hashable {
247+
@D1Column(name: "table_name")
248+
var tableName: String
249+
250+
@D1Column(name: "seq_nr")
251+
var seqNr: Int
252+
253+
@D1Column(name: "created_at", formatStyle: D1DateFormatStyle(format: .epoch))
254+
var createdAt: Date
255+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import D1Lite
2+
import SQLiteNIO
3+
import Testing
4+
5+
@testable import D1LiteSQLite
6+
7+
@Suite struct ToD1RawDatabaseQueryClientErrorFromErrorTests {
8+
@Test func passThrough() {
9+
let expected = D1RawDatabaseQueryClientError(
10+
errors: [.init(code: 1, message: "oops")],
11+
messages: []
12+
)
13+
14+
let actual = toD1RawDatabaseQueryClientErrorFromError(expected)
15+
16+
#expect(actual == expected)
17+
}
18+
19+
@Test func fallbackError() {
20+
enum TestError: Error { case sample }
21+
22+
let actual = toD1RawDatabaseQueryClientErrorFromError(TestError.sample)
23+
24+
#expect(
25+
actual
26+
== .init(
27+
errors: [
28+
.init(
29+
code: 7500,
30+
message: "sample",
31+
documentationURL: nil,
32+
source: nil,
33+
)
34+
],
35+
messages: [],
36+
)
37+
)
38+
}
39+
40+
@Test func sqliteError() async throws {
41+
let connection = try await SQLiteConnection.open(storage: .memory)
42+
defer { Task { try? await connection.close() } }
43+
44+
do {
45+
_ = try await connection.query("INVALID SQL")
46+
Issue.record("should be throw error SQLiteError but not throwed")
47+
} catch let error as SQLiteError {
48+
#expect(
49+
toD1RawDatabaseQueryClientErrorFromError(error)
50+
== .init(
51+
errors: [
52+
.init(code: 7500, message: #"near "INVALID": syntax error: SQLITE_ERROR"#)
53+
],
54+
messages: []
55+
)
56+
)
57+
} catch {
58+
Issue.record(error)
59+
}
60+
}
61+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import D1Lite
2+
import Testing
3+
4+
@testable import D1LiteSQLite
5+
6+
@Suite struct ToD1RawDatabaseQueryClientErrorTests {
7+
@Test func syntaxError() {
8+
let actual = toD1RawDatabaseQueryClientError(reason: .error, message: "near \"SELEC\": syntax error")
9+
10+
#expect(
11+
actual
12+
== .init(
13+
errors: [
14+
.init(
15+
code: 7500,
16+
message: "near \"SELEC\": syntax error: SQLITE_ERROR",
17+
documentationURL: nil,
18+
source: nil,
19+
)
20+
],
21+
messages: [],
22+
)
23+
)
24+
}
25+
26+
@Test func constraintError() {
27+
let actual = toD1RawDatabaseQueryClientError(
28+
reason: .constraintUniqueFailed,
29+
message: "UNIQUE constraint failed: locks.table_name, locks.seq_nr",
30+
)
31+
32+
#expect(
33+
actual
34+
== .init(
35+
errors: [
36+
.init(
37+
code: 7500,
38+
message: "UNIQUE constraint failed: locks.table_name, locks.seq_nr: SQLITE_CONSTRAINT",
39+
documentationURL: nil,
40+
source: nil,
41+
)
42+
],
43+
messages: [],
44+
)
45+
)
46+
}
47+
}

0 commit comments

Comments
 (0)