Skip to content

Commit 14b970f

Browse files
committed
New connections should fail if there is an existing connection
1 parent 7c4936e commit 14b970f

File tree

1 file changed

+132
-140
lines changed

1 file changed

+132
-140
lines changed

FullStackTests/Tests/ConnectionFullStackTests.swift

Lines changed: 132 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -13,174 +13,166 @@
1313
// limitations under the License.
1414

1515
import CryptoTokenKit
16-
import XCTest
16+
import Testing
1717

1818
@testable import FullStackTests
1919
@testable import YubiKit
2020

21-
class ConnectionFullStackTests: XCTestCase {
21+
@Suite("Connection Full Stack Tests", .serialized)
22+
struct ConnectionFullStackTests {
2223

2324
typealias Connection = USBSmartCardConnection
2425

25-
func testSingleConnection() throws {
26-
runAsyncTest {
27-
do {
28-
let connection = try await Connection.connection()
29-
print("✅ Got connection \(connection)")
30-
XCTAssertNotNil(connection)
31-
} catch {
32-
XCTFail("🚨 Failed with: \(error)")
33-
}
34-
}
26+
@Test("Single Connection")
27+
func singleConnection() async throws {
28+
let connection = try await Connection.connection()
29+
#expect(true, "✅ Got connection \(connection)")
30+
await connection.close(error: nil)
3531
}
3632

37-
func testSerialConnections() throws {
38-
runAsyncTest {
39-
do {
40-
let firstConnection = try await Connection.connection()
41-
print("✅ Got first connection \(firstConnection)")
42-
let task = Task {
43-
let result = await firstConnection.connectionDidClose()
44-
print("✅ First connection did close")
45-
return result
46-
}
47-
try? await Task.sleep(for: .seconds(1))
48-
let secondConnection = try await Connection.connection()
49-
print("✅ Got second connection \(secondConnection)")
50-
XCTAssertNotNil(secondConnection)
51-
let closingError = await task.value
52-
XCTAssertNil(closingError)
53-
print("✅ connectionDidClose() returned: \(String(describing: closingError))")
54-
} catch {
55-
XCTFail("🚨 Failed with: \(error)")
56-
}
33+
@Test("Serial Connections")
34+
func serialConnections() async throws {
35+
let firstConnection = try await Connection.connection()
36+
#expect(true, "✅ Got first connection \(firstConnection)")
37+
let task = Task {
38+
let result = await firstConnection.connectionDidClose()
39+
#expect(true, "✅ First connection did close")
40+
return result
5741
}
42+
43+
// attempt to create a second connection (should fail!)
44+
try? await Task.sleep(for: .seconds(1))
45+
let new = try? await Connection.connection()
46+
#expect(new == nil, "✅ Second connection failed as expected")
47+
48+
// close the first connection
49+
_ = await firstConnection.close(error: nil)
50+
let closingError = await task.value
51+
#expect(closingError == nil, "✅ connectionDidClose() returned: \(String(describing: closingError))")
52+
53+
// attempt to create a second connection (now it should succed!)
54+
try? await Task.sleep(for: .seconds(1))
55+
let secondConnection = try await Connection.connection()
56+
#expect(true, "✅ Got second connection \(secondConnection)")
57+
58+
// close the second connection
59+
await secondConnection.close(error: nil)
5860
}
5961

60-
func testConnectionCancellation() {
61-
runAsyncTest {
62-
let task1 = Task {
63-
try await Connection.connection()
64-
}
65-
let task2 = Task {
66-
try await Connection.connection()
67-
}
68-
let task3 = Task {
69-
try await Connection.connection()
70-
}
71-
let task4 = Task {
72-
try await Connection.connection()
73-
}
74-
75-
let result1 = try? await task1.value
76-
print("✅ Result 1: \(String(describing: result1))")
77-
let result2 = try? await task2.value
78-
print("✅ Result 2: \(String(describing: result2))")
79-
let result3 = try? await task3.value
80-
print("✅ Result 3: \(String(describing: result3))")
81-
let result4 = try? await task4.value
82-
print("✅ Result 4: \(String(describing: result4))")
83-
84-
XCTAssert([result1, result2, result3, result4].compactMap { $0 }.count == 1)
62+
@Test("Connection Cancellation")
63+
func connectionCancellation() async {
64+
let task1 = Task {
65+
try await Connection.connection()
66+
}
67+
let task2 = Task {
68+
try await Connection.connection()
69+
}
70+
let task3 = Task {
71+
try await Connection.connection()
72+
}
73+
let task4 = Task {
74+
try await Connection.connection()
8575
}
76+
77+
let result1 = try? await task1.value
78+
print("✅ Result 1: \(String(describing: result1))")
79+
let result2 = try? await task2.value
80+
print("✅ Result 2: \(String(describing: result2))")
81+
let result3 = try? await task3.value
82+
print("✅ Result 3: \(String(describing: result3))")
83+
let result4 = try? await task4.value
84+
print("✅ Result 4: \(String(describing: result4))")
85+
86+
let connections = [result1, result2, result3, result4].compactMap { $0 }
87+
#expect(connections.count == 1)
88+
89+
// close the only established connection
90+
await connections.first?.close(error: nil)
8691
}
8792

88-
func testSendManually() {
89-
runAsyncTest {
90-
let connection = try await Connection.connection()
91-
// Select Management application
92-
let apdu = APDU(
93-
cla: 0x00,
94-
ins: 0xa4,
95-
p1: 0x04,
96-
p2: 0x00,
97-
command: Data([0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17])
98-
)
99-
let resultData = try await connection.send(data: apdu.data)
100-
let result = Response(rawData: resultData)
101-
XCTAssertEqual(result.responseStatus.status, .ok)
102-
/// Get version number
103-
let deviceInfoApdu = APDU(cla: 0, ins: 0x1d, p1: 0, p2: 0)
104-
let deviceInfoResultData = try await connection.send(data: deviceInfoApdu.data)
105-
let deviceInfoResult = Response(rawData: deviceInfoResultData)
106-
XCTAssertEqual(deviceInfoResult.responseStatus.status, .ok)
107-
let records = TKBERTLVRecord.sequenceOfRecords(
108-
from: deviceInfoResult.data.subdata(in: 1..<deviceInfoResult.data.count)
109-
)
110-
guard let versionData = records?.filter({ $0.tag == 0x05 }).first?.value else {
111-
XCTFail("No YubiKey version record in result.")
112-
return
113-
}
114-
guard versionData.count == 3 else {
115-
XCTFail("Wrong sized return data. Got \(versionData.hexEncodedString)")
116-
return
117-
}
118-
let bytes = [UInt8](versionData)
119-
let major = bytes[0]
120-
let minor = bytes[1]
121-
let micro = bytes[2]
122-
print("✅ Got version: \(major).\(minor).\(micro)")
123-
XCTAssertEqual(major, 5)
124-
// Try to select non existing application
125-
let notFoundApdu = APDU(cla: 0x00, ins: 0xa4, p1: 0x04, p2: 0x00, command: Data([0x01, 0x02, 0x03]))
126-
let notFoundResultData = try await connection.send(data: notFoundApdu.data)
127-
let notFoundResult = Response(rawData: notFoundResultData)
128-
if !(notFoundResult.responseStatus.status == .fileNotFound
93+
@Test("Send Manually")
94+
func sendManually() async throws {
95+
let connection = try await Connection.connection()
96+
// Select Management application
97+
let apdu = APDU(
98+
cla: 0x00,
99+
ins: 0xa4,
100+
p1: 0x04,
101+
p2: 0x00,
102+
command: Data([0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17])
103+
)
104+
let resultData = try await connection.send(data: apdu.data)
105+
let result = Response(rawData: resultData)
106+
#expect(result.responseStatus.status == .ok)
107+
/// Get version number
108+
let deviceInfoApdu = APDU(cla: 0, ins: 0x1d, p1: 0, p2: 0)
109+
let deviceInfoResultData = try await connection.send(data: deviceInfoApdu.data)
110+
let deviceInfoResult = Response(rawData: deviceInfoResultData)
111+
#expect(deviceInfoResult.responseStatus.status == .ok)
112+
let records = TKBERTLVRecord.sequenceOfRecords(
113+
from: deviceInfoResult.data.subdata(in: 1..<deviceInfoResult.data.count)
114+
)
115+
let versionData = try #require(
116+
records?.filter({ $0.tag == 0x05 }).first?.value,
117+
"No YubiKey version record in result."
118+
)
119+
#expect(versionData.count == 3, "Wrong sized return data. Got \(versionData.hexEncodedString)")
120+
let bytes = [UInt8](versionData)
121+
let major = bytes[0]
122+
let minor = bytes[1]
123+
let micro = bytes[2]
124+
print("✅ Got version: \(major).\(minor).\(micro)")
125+
#expect(major == 5)
126+
// Try to select non existing application
127+
let notFoundApdu = APDU(cla: 0x00, ins: 0xa4, p1: 0x04, p2: 0x00, command: Data([0x01, 0x02, 0x03]))
128+
let notFoundResultData = try await connection.send(data: notFoundApdu.data)
129+
let notFoundResult = Response(rawData: notFoundResultData)
130+
#expect(
131+
notFoundResult.responseStatus.status == .fileNotFound
129132
|| notFoundResult.responseStatus.status == .incorrectParameters
130-
|| notFoundResult.responseStatus.status == .invalidInstruction)
131-
{
132-
XCTFail("Unexpected result: \(notFoundResult.responseStatus)")
133-
}
134-
}
133+
|| notFoundResult.responseStatus.status == .invalidInstruction,
134+
"Unexpected result: \(notFoundResult.responseStatus)"
135+
)
136+
137+
await connection.close(error: nil)
135138
}
136139
}
137140

138141
#if os(iOS)
139-
class NFCFullStackTests: XCTestCase {
140-
141-
func testNFCAlertMessage() throws {
142-
runAsyncTest {
143-
do {
144-
let connection = try await TestableConnections.create(with: .nfc(alertMessage: "Test Alert Message"))
145-
await connection.nfcConnection?.setAlertMessage("Updated Alert Message")
146-
try? await Task.sleep(for: .seconds(1))
147-
await connection.nfcConnection?.close(message: "Closing Alert Message")
148-
} catch {
149-
XCTFail("🚨 Failed with: \(error)")
150-
}
151-
}
142+
@Suite("NFC Full Stack Tests", .serialized)
143+
struct NFCFullStackTests {
144+
145+
@Test("NFC Alert Message")
146+
func nfcAlertMessage() async throws {
147+
let connection = try await TestableConnections.create(with: .nfc(alertMessage: "Test Alert Message"))
148+
await connection.nfcConnection?.setAlertMessage("Updated Alert Message")
149+
try? await Task.sleep(for: .seconds(1))
150+
await connection.nfcConnection?.close(message: "Closing Alert Message")
152151
}
153152

154-
func testNFCClosingErrorMessage() throws {
155-
runAsyncTest {
156-
do {
157-
let connection = try await TestableConnections.create(with: .nfc(alertMessage: "Test Alert Message"))
158-
await connection.close(error: nil)
159-
} catch {
160-
XCTFail("🚨 Failed with: \(error)")
161-
}
162-
}
153+
@Test("NFC Closing Error Message")
154+
func nfcClosingErrorMessage() async throws {
155+
let connection = try await TestableConnections.create(with: .nfc(alertMessage: "Test Alert Message"))
156+
await connection.close(error: nil)
163157
}
164158

165159
}
166160
#endif
167161

168-
class SmartCardConnectionFullStackTests: XCTestCase {
169-
170-
func testSmartCardConnectionWithSlot() throws {
171-
runAsyncTest {
172-
let allSlots = try await USBSmartCardConnection.availableSlots
173-
allSlots.enumerated().forEach { index, slot in
174-
print("\(index): \(slot.name)")
175-
}
176-
let random = allSlots.randomElement()
177-
// we need at least one YubiKey connected
178-
XCTAssertNotNil(random)
179-
guard let random else { return }
180-
let connection = try await USBSmartCardConnection.connection(slot: random)
181-
print("✅ Got connection \(connection)")
182-
XCTAssertNotNil(connection)
162+
@Suite("SmartCard Connection Full Stack Tests", .serialized)
163+
struct SmartCardConnectionFullStackTests {
164+
165+
@Test("SmartCard Connection With Slot")
166+
func smartCardConnectionWithSlot() async throws {
167+
let allSlots = try await USBSmartCardConnection.availableSlots
168+
allSlots.enumerated().forEach { index, slot in
169+
print("\(index): \(slot.name)")
183170
}
171+
let random = allSlots.randomElement()
172+
// we need at least one YubiKey connected
173+
let slot = try #require(random, "No YubiKey slots available")
174+
let connection = try await USBSmartCardConnection.connection(slot: slot)
175+
#expect(true, "✅ Got connection \(connection)")
184176
}
185177

186178
}

0 commit comments

Comments
 (0)