Skip to content

Commit

Permalink
Add various E2E tests (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
izik1 authored Jun 21, 2023
1 parent f198de4 commit 71e0246
Show file tree
Hide file tree
Showing 37 changed files with 2,800 additions and 71 deletions.
9 changes: 9 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,15 @@
"revision" : "ab3a58b7209a17d781c0d1dbb3e1ff3da306bae8",
"version" : "1.20.3"
}
},
{
"identity" : "swift-snapshot-testing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-snapshot-testing.git",
"state" : {
"revision" : "cef5b3f6f11781dd4591bdd1dd0a3d22bd609334",
"version" : "1.11.0"
}
}
],
"version" : 2
Expand Down
12 changes: 10 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ let package = Package(
// we use this entirely for sha3-keccak256, yes, I'm serious.
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.0.0"),
],
targets: [
.target(
Expand Down Expand Up @@ -128,7 +129,14 @@ let package = Package(
),
.testTarget(name: "HederaTests", dependencies: ["Hedera"]),
.testTarget(
name: "HederaEndToEndTests",
dependencies: ["Hedera", .product(name: "SwiftDotenv", package: "swift-dotenv")]),
name: "HederaE2ETests",
dependencies: [
"Hedera",
.product(name: "SwiftDotenv", package: "swift-dotenv"),
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
"HederaExampleUtilities",
],
exclude: ["File/__Snapshots__"]
),
] + exampleTargets
)
14 changes: 7 additions & 7 deletions Sources/Hedera/Client/Network+Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,13 @@ extension Network.Config {
]

internal static let testnet: Self = [
3: ["0.testnet.hedera.com", "34.94.106.61", "50.18.132.211", "138.91.142.219"],
4: ["1.testnet.hedera.com", "35.237.119.55", "3.212.6.13", "52.168.76.241"],
5: ["2.testnet.hedera.com", "35.245.27.193", "52.20.18.86", "40.79.83.124"],
6: ["3.testnet.hedera.com", "34.83.112.116", "54.70.192.33", "52.183.45.65"],
7: ["4.testnet.hedera.com", "34.94.160.4", "54.176.199.109", "13.64.181.136"],
8: ["5.testnet.hedera.com", "34.106.102.218", "35.155.49.147", "13.78.238.32"],
9: ["6.testnet.hedera.com", "34.133.197.230", "52.14.252.207", "52.165.17.231"],
3: ["0.testnet.hedera.com", "34.94.106.61", "50.18.132.211"],
4: ["1.testnet.hedera.com", "35.237.119.55", "3.212.6.13"],
5: ["2.testnet.hedera.com", "35.245.27.193", "52.20.18.86"],
6: ["3.testnet.hedera.com", "34.83.112.116", "54.70.192.33"],
7: ["4.testnet.hedera.com", "34.94.160.4", "54.176.199.109"],
8: ["5.testnet.hedera.com", "34.106.102.218", "35.155.49.147"],
9: ["6.testnet.hedera.com", "34.133.197.230", "52.14.252.207"],
]

internal static let previewnet: Self = [
Expand Down
21 changes: 17 additions & 4 deletions Sources/Hedera/Client/Network.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ import SwiftProtobuf
// Random is surprisingly good at this though (avoids the thundering herd that would happen if round-robin was used), so...
internal final class ChannelBalancer: GRPCChannel {
internal let eventLoop: EventLoop
private let channels: [GRPC.ClientConnection]
private let channels: [any GRPCChannel]
private let targets: [GRPC.ConnectionTarget]

// fixme: if the request never returns (IE the host doesn't exist) we

internal init(eventLoop: EventLoop, _ channelTargets: [GRPC.ConnectionTarget]) {
self.eventLoop = eventLoop
self.targets = channelTargets

self.channels = channelTargets.map { target in
GRPC.ClientConnection(configuration: .default(target: target, eventLoopGroup: eventLoop))
// GRPC.ClientConnection(configuration: .default(target: target, eventLoopGroup: eventLoop))
try! GRPCChannelPool.with(target: target, transportSecurity: .plaintext, eventLoopGroup: eventLoop)
}
}

Expand All @@ -46,7 +51,11 @@ internal final class ChannelBalancer: GRPCChannel {
)
-> GRPC.Call<Request, Response> where Request: GRPC.GRPCPayload, Response: GRPC.GRPCPayload
{
channels.randomElement()!.makeCall(path: path, type: type, callOptions: callOptions, interceptors: interceptors)
let elem = channels.randomElement()!

let res = elem.makeCall(path: path, type: type, callOptions: callOptions, interceptors: interceptors)

return res
}

internal func makeCall<Request, Response>(
Expand All @@ -55,7 +64,11 @@ internal final class ChannelBalancer: GRPCChannel {
callOptions: GRPC.CallOptions,
interceptors: [GRPC.ClientInterceptor<Request, Response>]
) -> GRPC.Call<Request, Response> where Request: SwiftProtobuf.Message, Response: SwiftProtobuf.Message {
channels.randomElement()!.makeCall(path: path, type: type, callOptions: callOptions, interceptors: interceptors)
let elem = channels.randomElement()!

let res = elem.makeCall(path: path, type: type, callOptions: callOptions, interceptors: interceptors)

return res
}

// todo: have someone look at this.
Expand Down
4 changes: 4 additions & 0 deletions Sources/Hedera/Contract/ContractUpdateTransaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@ extension ContractUpdateTransaction: ToProtobuf {
if let declineStakingReward = declineStakingReward {
proto.declineReward = Google_Protobuf_BoolValue(declineStakingReward)
}

if let contractMemo = contractMemo {
proto.memoWrapper = .init(contractMemo)
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Sources/Hedera/Execute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ private func executeAnyInner<E: Execute>(ctx: ExecuteContext, executable: E) asy

do {
response = try await executable.execute(channel, request)

} catch let error as GRPCStatus {
switch error.code {
case .unavailable, .resourceExhausted:
Expand Down
1 change: 0 additions & 1 deletion Sources/Hedera/HError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public struct HError: Error, CustomStringConvertible {
case grpcStatus(status: Int32)
case fromProtobuf
case transactionPreCheckStatus(status: Status, transactionId: TransactionId)
case transactionNoIdPreCheckStatus(status: Status)
case queryPreCheckStatus(status: Status, transactionId: TransactionId)
case queryPaymentPreCheckStatus(status: Status, transactionId: TransactionId)
case queryNoPaymentPreCheckStatus(status: Status)
Expand Down
10 changes: 6 additions & 4 deletions Sources/Hedera/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public class Query<Response>: ValidateChecksums {
///
/// The client will submit exactly this amount for the payment of this query. Hedera
/// will not return any remainder (over the actual cost for this query).
///
@discardableResult
public final func paymentAmount(_ amount: Hbar) -> Self {
self.payment.amount = amount

Expand All @@ -100,7 +100,7 @@ public class Query<Response>: ValidateChecksums {
/// Defaults to the maximum payment amount configured on the client.
///
/// Set to `None` to allow unlimited payment amounts.
///
@discardableResult
public final func maxPaymentAmount(_ maxAmount: Hbar?) -> Self {
self.payment.maxAmount = maxAmount

Expand All @@ -110,7 +110,7 @@ public class Query<Response>: ValidateChecksums {
/// Sets the duration that the payment transaction is valid for, once finalized and signed.
///
/// Defaults to 120 seconds (or two minutes).
///
@discardableResult
public final func paymentTransactionValidDuration(_ validDuration: Duration) -> Self {
self.payment.transactionValidDuration = validDuration

Expand All @@ -121,7 +121,7 @@ public class Query<Response>: ValidateChecksums {
/// payment transaction.
///
/// Defaults to 1 hbar.
///
@discardableResult
public final func maxPaymentTransactionFee(_ maxPaymentTransactionFee: Hbar) -> Self {
self.payment.maxTransactionFee = maxPaymentTransactionFee

Expand All @@ -130,6 +130,7 @@ public class Query<Response>: ValidateChecksums {

/// Set a note or description that should be recorded in the transaction record (maximum length
/// of 100 characters) for the payment transaction.
@discardableResult
public final func paymentTransactionMemo(_ memo: String) -> Self {
self.payment.transactionMemo = memo

Expand All @@ -140,6 +141,7 @@ public class Query<Response>: ValidateChecksums {
/// on this query.
///
/// Overrides payer account defined on this query or on the client.
@discardableResult
public final func paymentTransactionId(_ transactionId: TransactionId) -> Self {
self.payment.transactionId = transactionId

Expand Down
8 changes: 2 additions & 6 deletions Sources/Hedera/Transaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -430,12 +430,8 @@ extension Transaction {
}

internal final func makeErrorPrecheck(_ status: Status, _ transactionId: TransactionId?) -> HError {
guard let transactionId = transactionId else {
return HError(
kind: .transactionNoIdPreCheckStatus(status: status),
description: "transaction without transaction id failed pre-check with status `\(status)`"
)
}
let transactionId = transactionId!

return HError(
kind: .transactionPreCheckStatus(status: status, transactionId: transactionId),
description: "transaction `\(transactionId)` failed pre-check with status `\(status)`"
Expand Down
5 changes: 5 additions & 0 deletions Sources/Hedera/TransactionId.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public struct TransactionId: Sendable, Equatable, Hashable, ExpressibleByStringL
return Self(accountId: accountId, validStart: validStart, scheduled: false)
}

/// Creates a new transaction Id with the given account id and valid start.
public static func withValidStart(_ accountId: AccountId, _ validStart: Timestamp) -> Self {
Self(accountId: accountId, validStart: validStart, scheduled: false)
}

private init<S: StringProtocol>(parsing description: S) throws {
let expected = "expecting <accountId>@<validStart>[?scheduled][/<nonce>]"

Expand Down
50 changes: 50 additions & 0 deletions Tests/HederaE2ETests/Account/Account.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* ‌
* Hedera Swift SDK
* ​
* Copyright (C) 2022 - 2023 Hedera Hashgraph, LLC
* ​
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ‍
*/

import Hedera
import XCTest

internal struct Account {
internal let id: AccountId
internal let key: PrivateKey

internal static func create(_ testEnv: NonfreeTestEnvironment, balance: Hbar = 0) async throws -> Self {
let key = PrivateKey.generateEd25519()

try await testEnv.ratelimits.accountCreate()

let receipt = try await AccountCreateTransaction(key: .single(key.publicKey), initialBalance: balance)
.execute(testEnv.client)
.getReceipt(testEnv.client)

let id = try XCTUnwrap(receipt.accountId)

return Self(id: id, key: key)
}

internal func delete(_ testEnv: NonfreeTestEnvironment) async throws {
_ = try await AccountDeleteTransaction()
.accountId(id)
.transferAccountId(testEnv.operator.accountId)
.sign(key)
.execute(testEnv.client)
.getReceipt(testEnv.client)
}
}
58 changes: 58 additions & 0 deletions Tests/HederaE2ETests/Account/AccountAllowanceApprove.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* ‌
* Hedera Swift SDK
* ​
* Copyright (C) 2022 - 2023 Hedera Hashgraph, LLC
* ​
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ‍
*/

import Hedera
import XCTest

internal final class AccountAllowanceApprove: XCTestCase {
internal func createAccount(_ testEnv: NonfreeTestEnvironment, balance: Hbar) async throws -> Account {
let account = try await Account.create(testEnv, balance: balance)

addTeardownBlock {
try await account.delete(testEnv)
}

return account
}

internal func testSpend() async throws {
let testEnv = try TestEnvironment.nonFree
async let (alice, bob) = (createAccount(testEnv, balance: 10), createAccount(testEnv, balance: 10))

_ = try await AccountAllowanceApproveTransaction()
.approveHbarAllowance(bob.id, alice.id, 10)
.freezeWith(testEnv.client)
.sign(bob.key)
.execute(testEnv.client)
.getReceipt(testEnv.client)

let transferRecord = try await TransferTransaction()
.hbarTransfer(testEnv.operator.accountId, 5)
.approvedHbarTransfer(bob.id, -5)
.transactionId(TransactionId.generateFrom(alice.id))
.freezeWith(testEnv.client)
.sign(alice.key)
.execute(testEnv.client)
.getRecord(testEnv.client)

let transfer = try XCTUnwrap(transferRecord.transfers.first { $0.accountId == testEnv.operator.accountId })
XCTAssertEqual(transfer.amount, 5)
}
}
Loading

0 comments on commit 71e0246

Please sign in to comment.