|
3 | 3 | // Set environment variables: HOST, USERNAME, PASSWORD, DATABASE (optional) |
4 | 4 |
|
5 | 5 | import XCTest |
| 6 | + |
6 | 7 | @testable import SQLClientSwift |
7 | 8 |
|
8 | 9 | final class SQLClientSwiftTests: XCTestCase { |
9 | 10 |
|
10 | 11 | private func env(_ key: String) -> String { ProcessInfo.processInfo.environment[key] ?? "" } |
11 | | - private var host: String { env("HOST") } |
| 12 | + private var host: String { env("HOST") } |
12 | 13 | private var username: String { env("USERNAME") } |
13 | 14 | private var password: String { env("PASSWORD") } |
14 | 15 | private var database: String { env("DATABASE") } |
15 | 16 | private var canConnect: Bool { !host.isEmpty && !username.isEmpty && !password.isEmpty } |
| 17 | + private var client: SQLClient! // global client |
16 | 18 |
|
17 | 19 | private func makeClient() async throws -> SQLClient { |
| 20 | + let c = SQLClient() |
| 21 | + try await c.connect( |
| 22 | + server: host, username: username, password: password, |
| 23 | + database: database.isEmpty ? nil : database) |
| 24 | + return c |
| 25 | + } |
| 26 | + |
| 27 | + /// Called before each XCTest method is run. Able to throw errors on setup. |
| 28 | + /// Centralises boilerplate setup making |
| 29 | + override func setUp() async throws { |
| 30 | + try super.setUpWithError() |
18 | 31 | guard canConnect else { |
19 | 32 | throw XCTSkip("Set HOST, USERNAME, PASSWORD environment variables to run tests.") |
20 | 33 | } |
21 | | - let client = SQLClient() |
22 | | - try await client.connect(server: host, username: username, password: password, |
23 | | - database: database.isEmpty ? nil : database) |
24 | | - return client |
| 34 | + client = try await makeClient() |
| 35 | + |
| 36 | + } |
| 37 | + |
| 38 | + /// Called after each XCTest method is run. Able to throw errors on cleanup. |
| 39 | + /// Ensures cleanup from each test is completed after the test is run. Before |
| 40 | + /// the next test is run. |
| 41 | + override func tearDown() async throws { |
| 42 | + guard client != nil else { return } |
| 43 | + try await client.disconnect() |
| 44 | + try await super.tearDown() |
25 | 45 | } |
26 | 46 |
|
27 | 47 | func testConnect() async throws { |
28 | | - let client = try await makeClient() |
29 | | - let connected = await client.isConnected |
| 48 | + // Use a local client, as the global client is already connected |
| 49 | + let localClient = SQLClient() |
| 50 | + try await localClient.connect( |
| 51 | + server: host, username: username, password: password, |
| 52 | + database: database.isEmpty ? nil : database) |
| 53 | + |
| 54 | + let connected = await localClient.isConnected |
30 | 55 | XCTAssertTrue(connected) |
31 | | - await client.disconnect() |
32 | | - let isConnected = await client.isConnected |
| 56 | + |
| 57 | + await localClient.disconnect() |
| 58 | + let isConnected = await localClient.isConnected |
33 | 59 | XCTAssertFalse(isConnected) |
34 | 60 | } |
35 | 61 |
|
36 | 62 | func testDoubleConnectThrows() async throws { |
37 | | - let client = try await makeClient() |
38 | | - defer { Task { await client.disconnect() } } |
| 63 | + // Use a local client as the global client is already connected |
| 64 | + let localClient = SQLClient() |
| 65 | + try await localClient.connect( |
| 66 | + server: host, username: username, password: password, |
| 67 | + database: database.isEmpty ? nil : database) |
| 68 | + // Defer is used here as race condition on cleanup is inconsequential |
| 69 | + defer { Task { await localClient.disconnect() } } |
| 70 | + |
39 | 71 | do { |
40 | | - try await client.connect(server: host, username: username, password: password) |
| 72 | + try await localClient.connect(server: host, username: username, password: password) |
41 | 73 | XCTFail("Expected alreadyConnected") |
42 | | - } catch SQLClientError.alreadyConnected { } |
| 74 | + } catch SQLClientError.alreadyConnected {} |
43 | 75 | } |
44 | 76 |
|
45 | 77 | func testSelectScalar() async throws { |
46 | | - let client = try await makeClient() |
47 | | - defer { Task { await client.disconnect() } } |
48 | 78 | let rows = try await client.query("SELECT 42 AS Answer") |
49 | 79 | XCTAssertEqual(rows.count, 1) |
50 | 80 | XCTAssertEqual(rows[0].int("Answer"), 42) |
51 | 81 | } |
52 | 82 |
|
53 | 83 | func testSelectNull() async throws { |
54 | | - let client = try await makeClient() |
55 | | - defer { Task { await client.disconnect() } } |
56 | 84 | let rows = try await client.query("SELECT NULL AS Val") |
57 | 85 | XCTAssertTrue(rows[0].isNull("Val")) |
58 | 86 | } |
59 | 87 |
|
60 | 88 | func testSelectString() async throws { |
61 | | - let client = try await makeClient() |
62 | | - defer { Task { await client.disconnect() } } |
63 | 89 | let rows = try await client.query("SELECT 'Hello' AS Msg") |
64 | 90 | XCTAssertEqual(rows[0].string("Msg"), "Hello") |
65 | 91 | } |
66 | 92 |
|
67 | 93 | func testSelectFloat() async throws { |
68 | | - let client = try await makeClient() |
69 | | - defer { Task { await client.disconnect() } } |
70 | 94 | let rows = try await client.query("SELECT CAST(3.14 AS FLOAT) AS Pi") |
71 | 95 | XCTAssertEqual(rows[0].double("Pi") ?? 0, 3.14, accuracy: 0.001) |
72 | 96 | } |
73 | 97 |
|
74 | 98 | func testSelectBit() async throws { |
75 | | - let client = try await makeClient() |
76 | | - defer { Task { await client.disconnect() } } |
77 | 99 | let rows = try await client.query("SELECT CAST(1 AS BIT) AS Flag") |
78 | 100 | XCTAssertEqual(rows[0].bool("Flag"), true) |
79 | 101 | } |
80 | 102 |
|
81 | 103 | func testSelectDateTime() async throws { |
82 | | - let client = try await makeClient() |
83 | | - defer { Task { await client.disconnect() } } |
84 | 104 | let rows = try await client.query("SELECT GETDATE() AS Now") |
85 | 105 | XCTAssertNotNil(rows[0].date("Now")) |
86 | 106 | } |
87 | 107 |
|
88 | 108 | func testMultipleRows() async throws { |
89 | | - let client = try await makeClient() |
90 | | - defer { Task { await client.disconnect() } } |
91 | 109 | let rows = try await client.query("SELECT 1 AS n UNION ALL SELECT 2 UNION ALL SELECT 3") |
92 | 110 | XCTAssertEqual(rows.count, 3) |
93 | 111 | XCTAssertEqual(rows.map { $0.int("n") }, [1, 2, 3]) |
94 | 112 | } |
95 | 113 |
|
96 | 114 | func testMultipleResultSets() async throws { |
97 | | - let client = try await makeClient() |
98 | | - defer { Task { await client.disconnect() } } |
99 | 115 | let result = try await client.execute("SELECT 1 AS A; SELECT 2 AS B;") |
100 | 116 | XCTAssertEqual(result.tables.count, 2) |
101 | 117 | XCTAssertEqual(result.tables[0][0].int("A"), 1) |
102 | 118 | XCTAssertEqual(result.tables[1][0].int("B"), 2) |
103 | 119 | } |
104 | 120 |
|
105 | 121 | func testRowsAffected() async throws { |
106 | | - let client = try await makeClient() |
107 | | - defer { Task { await client.disconnect() } } |
108 | | - try await client.run(""" |
109 | | - IF OBJECT_ID('tempdb..#T') IS NOT NULL DROP TABLE #T; |
110 | | - CREATE TABLE #T (id INT); |
111 | | - INSERT INTO #T VALUES (1),(2),(3); |
112 | | - """) |
| 122 | + try await client.run( |
| 123 | + """ |
| 124 | + IF OBJECT_ID('tempdb..#T') IS NOT NULL DROP TABLE #T; |
| 125 | + CREATE TABLE #T (id INT); |
| 126 | + INSERT INTO #T VALUES (1),(2),(3); |
| 127 | + """) |
113 | 128 | let affected = try await client.run("UPDATE #T SET id = id + 10") |
114 | 129 | XCTAssertEqual(affected, 3) |
115 | 130 | try await client.run("DROP TABLE #T") |
116 | 131 | } |
117 | 132 |
|
118 | 133 | func testParameterisedQuery() async throws { |
119 | | - let client = try await makeClient() |
120 | | - defer { Task { await client.disconnect() } } |
121 | 134 | let rows = try await client.execute("SELECT ? AS Name", parameters: ["O'Brien"]) |
122 | 135 | XCTAssertEqual(rows.rows[0].string("Name"), "O'Brien") |
123 | 136 | } |
124 | 137 |
|
125 | 138 | func testNullParameter() async throws { |
126 | | - let client = try await makeClient() |
127 | | - defer { Task { await client.disconnect() } } |
128 | 139 | let rows = try await client.execute("SELECT ? AS Val", parameters: [nil]) |
129 | 140 | XCTAssertTrue(rows.rows[0].isNull("Val")) |
130 | 141 | } |
131 | 142 |
|
132 | 143 | func testParameterCountMismatch() async throws { |
133 | | - let client = try await makeClient() |
134 | 144 | defer { Task { await client.disconnect() } } |
135 | 145 | do { |
136 | 146 | _ = try await client.execute("SELECT ? AS A", parameters: [1, 2]) |
137 | 147 | XCTFail("Expected parameterCountMismatch") |
138 | | - } catch SQLClientError.parameterCountMismatch { } |
| 148 | + } catch SQLClientError.parameterCountMismatch {} |
139 | 149 | } |
140 | 150 |
|
141 | 151 | func testDecodableStruct() async throws { |
142 | | - let client = try await makeClient() |
143 | | - defer { Task { await client.disconnect() } } |
144 | | - struct Point: Decodable { let x: Int; let y: Int } |
| 152 | + struct Point: Decodable { |
| 153 | + let x: Int |
| 154 | + let y: Int |
| 155 | + } |
145 | 156 | let points: [Point] = try await client.query("SELECT 10 AS x, 20 AS y") |
146 | 157 | XCTAssertEqual(points[0].x, 10) |
147 | 158 | XCTAssertEqual(points[0].y, 20) |
148 | 159 | } |
149 | 160 |
|
150 | 161 | func testDecodableSnakeCase() async throws { |
151 | | - let client = try await makeClient() |
152 | | - defer { Task { await client.disconnect() } } |
153 | | - struct Item: Decodable { let itemId: Int; let itemName: String } |
| 162 | + struct Item: Decodable { |
| 163 | + let itemId: Int |
| 164 | + let itemName: String |
| 165 | + } |
154 | 166 | let items: [Item] = try await client.query("SELECT 7 AS item_id, 'Widget' AS item_name") |
155 | | - XCTAssertEqual(items[0].itemId, 7) |
| 167 | + XCTAssertEqual(items[0].itemId, 7) |
156 | 168 | XCTAssertEqual(items[0].itemName, "Widget") |
157 | 169 | } |
158 | 170 |
|
159 | 171 | func testBadSQLThrows() async throws { |
160 | | - let client = try await makeClient() |
161 | | - defer { Task { await client.disconnect() } } |
162 | 172 | do { |
163 | 173 | _ = try await client.execute("THIS IS NOT VALID SQL") |
164 | 174 | XCTFail("Expected executionFailed") |
165 | | - } catch SQLClientError.executionFailed { } |
| 175 | + } catch SQLClientError.executionFailed {} |
166 | 176 | } |
167 | 177 |
|
168 | 178 | func testEmptySQLThrows() async throws { |
169 | | - let client = try await makeClient() |
170 | | - defer { Task { await client.disconnect() } } |
171 | 179 | do { |
172 | 180 | _ = try await client.execute(" ") |
173 | 181 | XCTFail("Expected noCommandText") |
174 | | - } catch SQLClientError.noCommandText { } |
| 182 | + } catch SQLClientError.noCommandText {} |
175 | 183 | } |
176 | 184 |
|
177 | 185 | func testQueryBeforeConnectThrows() async throws { |
178 | 186 | let client = SQLClient() |
179 | 187 | do { |
180 | 188 | _ = try await client.query("SELECT 1") |
181 | 189 | XCTFail("Expected notConnected") |
182 | | - } catch SQLClientError.notConnected { } |
| 190 | + } catch SQLClientError.notConnected {} |
183 | 191 | } |
184 | 192 | } |
0 commit comments