Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/swift-sdk/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ let package = Package(

// Integration tests against a local dashmate devnet.
// Gated by env var `RUN_INTEGRATION_TESTS=1`
// Live testnet reads are gated separately by `RUN_TESTNET_TESTS=1`.
.testTarget(
name: "SwiftDashSDKIntegrationTests",
dependencies: ["SwiftDashSDK"],
Expand Down
21 changes: 21 additions & 0 deletions packages/swift-sdk/Sources/SwiftDashSDK/SDK.swift
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,27 @@ public final class SDK: @unchecked Sendable {
self.network = network
}

/// Offline SDK over the FFI mock (`dash_sdk_create_handle_with_mock`), for
/// tests. Its DAPI client never opens a connection: it answers only from
/// the recorded `msg_*.json` responses in `vectorsDirectory` (proofs are
/// still verified, against its `quorum_pubkey-*.json` keys) and fails any
/// other request. `nil` loads no vectors. The FFI builds the mock for
/// mainnet, which `network` reports.
internal init(mockVectorsDirectory vectorsDirectory: String?) throws {
let mockHandle: OpaquePointer?
if let vectorsDirectory {
mockHandle = vectorsDirectory.withCString { dash_sdk_create_handle_with_mock($0) }
} else {
mockHandle = dash_sdk_create_handle_with_mock(nil)
}
guard let mockHandle else {
throw SDKError.internalError(
"Failed to create mock SDK from \(vectorsDirectory ?? "no vectors")")
}
handle = mockHandle
network = Network(ffiNetwork: dash_sdk_get_network(mockHandle))
}

/// Run `body` with two optional C-string pointers. Each input string,
/// when non-nil, is materialized into a NUL-terminated C buffer that is
/// valid for the duration of the call; nil inputs pass through as nil
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import XCTest
import SwiftDashSDK

/// Live read of a known testnet identity through the default testnet SDK
/// (trusted context provider, seed DAPI nodes). It depends on testnet being
/// up and producing blocks, so it is opt-in via `RUN_TESTNET_TESTS=1` and
/// does not need the local devnet. The hermetic counterpart is
/// `SDKMethodTests.testSimpleIdentityFetch` in SwiftDashSDKTests.
final class TestnetIdentityFetchTests: XCTestCase {
override func setUpWithError() throws {
try super.setUpWithError()
try XCTSkipUnless(
ProcessInfo.processInfo.environment["RUN_TESTNET_TESTS"] == "1",
"Testnet tests skipped: set RUN_TESTNET_TESTS=1 to enable"
)
}

@MainActor
func testFetchKnownTestnetIdentity() async throws {
SDK.initialize()
let sdk = try SDK(network: .testnet)

let identityId = "5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk"
let identity = try await sdk.identityGet(identityId: identityId)

XCTAssertEqual(identity["id"] as? String, identityId)
}
}
145 changes: 79 additions & 66 deletions packages/swift-sdk/SwiftTests/SwiftDashSDKTests/SDKMethodTests.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import DashSDKFFI
import SwiftDashSDK
import XCTest

@testable import SwiftDashSDK

/// These tests run against the FFI mock SDK, never a live network: unit
/// tests must not depend on the network (a halted testnet failed every CI
/// run while they did). The live testnet read is opt-in, in
/// SwiftDashSDKIntegrationTests/Platform/TestnetIdentityFetchTests.swift.
final class SDKMethodTests: XCTestCase {

/// A directory of rs-sdk's recorded offline vectors
/// (packages/rs-sdk/tests/vectors/<name>).
private static func rsSdkVectors(_ name: String) -> String {
URL(fileURLWithPath: #filePath)
.deletingLastPathComponent() // SwiftDashSDKTests
.deletingLastPathComponent() // SwiftTests
.deletingLastPathComponent() // swift-sdk
.deletingLastPathComponent() // packages
.appendingPathComponent("rs-sdk/tests/vectors/\(name)", isDirectory: true)
.path
}

func testSDKMethodsAvailability() {
print("=== Testing SDK Methods Availability ===")

Expand All @@ -29,83 +46,79 @@ final class SDKMethodTests: XCTestCase {

@MainActor
func testDirectMethodCall() async throws {
print("=== Testing Direct Method Call ===")

// Initialize SDK
// Mock SDK with no vectors: a request that got as far as DAPI would fail
// with a missing-expectation error instead of reaching the network.
SDK.initialize()
let sdk = try SDK(network: .testnet)

print("SDK created: \(sdk)")
print("SDK handle: \(String(describing: sdk.handle))")
print("SDK type: \(type(of: sdk))")

// Test if we can call identityTransferCredits without crashing
do {
print("Attempting to call identityTransferCredits...")
let toId = "test2"
let amount: UInt64 = 1
let key = Data(repeating: 0, count: 32)

// Create a dummy identity
let identity = DPPIdentity(
id: Data(repeating: 0, count: 32),
publicKeys: [:],
balance: 0,
revision: 0
let sdk = try SDK(mockVectorsDirectory: nil)

// Create a dummy identity
let identity = DPPIdentity(
id: Data(repeating: 0, count: 32),
publicKeys: [:],
balance: 0,
revision: 0
)

// Any non-zero scalar below the curve order is a valid key. Zero is not:
// signer creation would fail and skip the call under test.
let key = Data(repeating: 1, count: 32)
let signerResult = key.withUnsafeBytes { keyBytes in
dash_sdk_signer_create_from_private_key(
keyBytes.bindMemory(to: UInt8.self).baseAddress!,
UInt(key.count),
Network.testnet.ffiValue
)
}
if let error = signerResult.error {
let sdkError = SDKError.fromDashSDKError(error.pointee)
dash_sdk_error_free(error)
XCTFail("Failed to create signer: \(sdkError)")
return
}
let signer = try XCTUnwrap(signerResult.data)
defer {
dash_sdk_signer_destroy(OpaquePointer(signer))
}

// Create signer from private key
let signerResult = key.withUnsafeBytes { keyBytes in
dash_sdk_signer_create_from_private_key(
keyBytes.bindMemory(to: UInt8.self).baseAddress!,
UInt(key.count),
Network.testnet.ffiValue
)
}

guard signerResult.error == nil,
let signer = signerResult.data
else {
print("Failed to create signer")
return
}

defer {
dash_sdk_signer_destroy(OpaquePointer(signer))
}

let signerPtr = OpaquePointer(signer)
// "test2" is not a 32-byte Base58 identifier, so the FFI must refuse it
// before building a transition, let alone broadcasting one.
do {
_ = try await sdk.transferCredits(
from: identity,
toIdentityId: toId,
amount: amount,
signer: signerPtr
toIdentityId: "test2",
amount: 1,
signer: OpaquePointer(signer)
)
XCTFail("transferCredits should reject the malformed recipient id")
} catch SDKError.internalError(let message) {
XCTAssertTrue(
message.contains("Invalid to_identity_id"),
"expected the recipient id to be refused, got: \(message)"
)
XCTFail("transferCredits should fail with dummy data")
} catch {
// Expected: dummy data causes an error
}
}

/// Fetches an identity through `identityGet` from rs-sdk's recorded
/// `test_identity_read` vectors: the mock replays the recorded DAPI
/// response, the SDK verifies its proof against the recorded quorum key,
/// and the identity is decoded into the Swift result.
@MainActor
func testSimpleIdentityFetch() async throws {
print("=== Testing Simple Identity Fetch ===")
let vectors = Self.rsSdkVectors("test_identity_read")
guard FileManager.default.fileExists(atPath: vectors) else {
XCTFail("missing rs-sdk offline vectors at \(vectors)")
return
}

SDK.initialize()
let sdk = try SDK(network: .testnet)
let sdk = try SDK(mockVectorsDirectory: vectors)

do {
// Use a known testnet identity
let testIdentityId = "5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk"
print("Fetching identity: \(testIdentityId)")

try await Task { @MainActor in
let identity = try await sdk.identityGet(identityId: testIdentityId)
print("✅ Identity fetched successfully")
print("Identity keys: \(identity.keys.sorted())")
}.value
} catch {
print("❌ Failed to fetch identity: \(error)")
throw error
}
// The vectors record identity [1; 32], rs-sdk's IDENTITY_ID_1.
let identityId = Data(repeating: 1, count: 32).toBase58()
let identity = try await sdk.identityGet(identityId: identityId)

XCTAssertEqual(identity["id"] as? String, identityId)
let publicKeys = try XCTUnwrap(identity["publicKeys"] as? [[String: Any]])
XCTAssertFalse(publicKeys.isEmpty, "the recorded identity has public keys")
}
}
Loading