Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Dockerize TCK #331

Merged
merged 6 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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 .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM node:22-slim

# Set working directory
WORKDIR /app

# Copy package files first for better caching
COPY package.json package-lock.json ./
RUN npm ci

# Environment Variables
ENV NETWORK=local \
RUNNING_IN_DOCKER=true \
TEST="ALL"

# Copy the rest of the application
COPY . .

# Use the runner script
CMD ["npx", "ts-node", "--files", "/app/src/utils/docker/run-tests.ts"]
75 changes: 74 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,79 @@ Please read our [contributing guide](https://github.com/hiero-ledger/.github/blo

Hiero uses the Linux Foundation Decentralised Trust [Code of Conduct](https://www.lfdecentralizedtrust.org/code-of-conduct).


## Docker

The TCK is also available as a Docker image, providing an easy way to run tests in an isolated environment.

### Pull the Docker Image

You can pull the pre-built Docker image from DockerHub:

```bash
docker pull ivaylogarnev/tck-client
```

### Running Tests

The Docker image supports running tests against both local and testnet environments.

#### Local Network (Default)
To run tests against a local network:
```bash
# Run specific test
docker run --network host -e TEST=AccountCreate ivaylogarnev/tck-client

# Run all tests
docker run --network host ivaylogarnev/tck-client
```

#### Testnet
To run tests against Hedera Testnet:
```bash
docker run --network host \
-e NETWORK=testnet \
-e OPERATOR_ACCOUNT_ID=your-account-id \
-e OPERATOR_ACCOUNT_PRIVATE_KEY=your-private-key \
# Run specific test
-e TEST=AccountCreate \
ivaylogarnev/tck-client
```

### Available Tests
Some of the available test options include:
- AccountCreate
- AccountUpdate
- AccountDelete
- AccountAllowanceDelete
- AccountAllowanceApprove
- TokenCreate
- TokenUpdate
- TokenDelete
- TokenBurn
- TokenMint
- TokenAssociate
- TokenDissociate
- TokenFeeScheduleUpdate
- TokenGrantKyc
- TokenRevokeKyc
- TokenPause
- TokenUnpause
- TokenFreeze
- TokenUnfreeze
- ALL (runs all tests)

Running an invalid test name will display the complete list of available tests.

### Building the Docker Image Locally

If you want to build the image locally:
```bash
docker build -t tck-client .
```

Then run it using the same commands as above, replacing `ivaylogarnev/tck-client` with `tck-client`.
rwalworth marked this conversation as resolved.
Show resolved Hide resolved

rwalworth marked this conversation as resolved.
Show resolved Hide resolved
## License

[Apache License 2.0](LICENSE)
[Apache License 2.0](LICENSE)
22 changes: 14 additions & 8 deletions src/services/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@ import "dotenv/config";
let nextID = 0;
const createID: CreateID = () => nextID++;

// Determine the host based on environment
const getHost = () => {
const url = process.env.JSON_RPC_SERVER_URL ?? "http://localhost:8544";

if (process.env.RUNNING_IN_DOCKER) {
return url.replace("localhost", "host.docker.internal");
}
return url;
};

gsstoykov marked this conversation as resolved.
Show resolved Hide resolved
// JSONRPCClient needs to know how to send a JSON-RPC request.
// Tell it by passing a function to its constructor. The function must take a JSON-RPC request and send it.
const JSONRPClient = new JSONRPCClient(
async (jsonRPCRequest): Promise<void> => {
try {
const response = await axios.post(
process.env.JSON_RPC_SERVER_URL ?? "http://localhost:8544",
jsonRPCRequest,
{
headers: {
"Content-Type": "application/json",
},
const response = await axios.post(getHost(), jsonRPCRequest, {
headers: {
"Content-Type": "application/json",
},
);
});

if (response.status === 200) {
return JSONRPClient.receive(response.data);
Expand Down
66 changes: 66 additions & 0 deletions src/utils/docker/network-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import dotenv from "dotenv";

// Helper function to convert camelCase to SNAKE_CASE
const toEnvFormat = (str: string): string => {
return str
.split(/(?=[A-Z])/)
.join("_")
.toUpperCase();
};

// Set the network config based on the network name
export const getNetworkConfig = (
network: string,
): Record<string, string | undefined> => {
if (network === "testnet") {
dotenv.config({ path: ".env.testnet" });

if (
process.env.OPERATOR_ACCOUNT_ID === "***" ||
process.env.OPERATOR_ACCOUNT_PRIVATE_KEY === "***"
) {
console.log(
"\n" +
"TESTNET_OPERATOR_ACCOUNT_ID and TESTNET_OPERATOR_ACCOUNT_PRIVATE_KEY must be set for testnet!",
);

process.exit(1);
}

return {
nodeType: "testnet",
nodeTimeout: process.env.NODE_TIMEOUT,
mirrorNodeRestUrl: process.env.MIRROR_NODE_REST_URL,
operatorAccountId: process.env.OPERATOR_ACCOUNT_ID,
operatorAccountPrivateKey: process.env.OPERATOR_ACCOUNT_PRIVATE_KEY,
jsonRpcServerUrl: process.env.JSON_RPC_SERVER_URL,
};
}

dotenv.config({ path: ".env.custom_node" });

return {
nodeType: "local",
nodeTimeout: process.env.NODE_TIMEOUT,
nodeIp: process.env.NODE_IP,
nodeAccountId: process.env.NODE_ACCOUNT_ID,
mirrorNetwork: process.env.MIRROR_NETWORK,
mirrorNodeRestUrl: process.env.MIRROR_NODE_REST_URL,
mirrorNodeRestJavaUrl: process.env.MIRROR_NODE_REST_JAVA_URL,
operatorAccountId: process.env.OPERATOR_ACCOUNT_ID,
operatorAccountPrivateKey: process.env.OPERATOR_ACCOUNT_PRIVATE_KEY,
jsonRpcServerUrl: process.env.JSON_RPC_SERVER_URL,
gsstoykov marked this conversation as resolved.
Show resolved Hide resolved
};
};

export const setNetworkEnvironment = (network: string): void => {
const config = getNetworkConfig(network);

// Set environment variables with correct naming convention
Object.entries(config).forEach(([key, value]) => {
if (value) {
const envKey = toEnvFormat(key);
process.env[envKey] = value.toString();
}
});
};
50 changes: 50 additions & 0 deletions src/utils/docker/run-tests.ts
gsstoykov marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* eslint-disable no-console */
import { TEST_CONFIGURATIONS } from "./test-paths";
import { setNetworkEnvironment } from "./network-config";

const runTests = async (testName: string, network: string): Promise<void> => {
try {
// Test parameter validation
const testConfig = TEST_CONFIGURATIONS[testName];
if (!testConfig) {
console.error(`Unknown test: ${testName}`);
console.log("\nAvailable tests:");
Object.entries(TEST_CONFIGURATIONS).forEach(([name]) => {
console.log(` - ${name}`);
});
process.exit(1);
}

setNetworkEnvironment(network);

// Run the test
const { execSync } = require("child_process");

const command = [
"npx mocha",
"--require ts-node/register",
"--require tsconfig-paths/register",
`--recursive "${testConfig}"`,
"--reporter mochawesome",
"--exit",
].join(" ");

execSync(command, {
stdio: "inherit",
env: process.env,
shell: true,
});
} catch (error: any) {
console.error("Unhandled error:", error);
process.exit(1);
}
};

// Get arguments from process.argv or environment variables
const testName = process.env.TEST || "ALL";
const network = process.env.NETWORK || "local";

runTests(testName, network).catch((error) => {
console.error("Unhandled error:", error);
process.exit(1);
});
rwalworth marked this conversation as resolved.
Show resolved Hide resolved
29 changes: 29 additions & 0 deletions src/utils/docker/test-paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const TEST_CONFIGURATIONS: Record<string, string> = {
// Crypto Service Tests
AccountCreate: "src/tests/crypto-service/test-account-create-transaction.ts",
AccountUpdate: "src/tests/crypto-service/test-account-update-transaction.ts",
AccountDelete: "src/tests/crypto-service/test-account-delete-transaction.ts",
AccountAllowanceDelete:
"src/tests/crypto-service/test-account-allowance-delete-transaction.ts",
AccountAllowanceApprove:
"src/tests/crypto-service/test-account-allowance-approve-transaction.ts",
// Token Service Tests
TokenCreate: "src/tests/token-service/test-token-create-transaction.ts",
TokenUpdate: "src/tests/token-service/test-token-update-transaction.ts",
TokenDelete: "src/tests/token-service/test-token-delete-transaction.ts",
TokenBurn: "src/tests/token-service/test-token-burn-transaction.ts",
TokenMint: "src/tests/token-service/test-token-mint-transaction.ts",
TokenAssociate: "src/tests/token-service/test-token-associate-transaction.ts",
TokenDissociate:
"src/tests/token-service/test-token-dissociate-transaction.ts",
TokenFeeScheduleUpdate:
"src/tests/token-service/test-token-fee-schedule-update-transaction.ts",
TokenGrantKyc: "src/tests/token-service/test-token-grant-kyc-transaction.ts",
TokenRevokeKyc:
"src/tests/token-service/test-token-revoke-kyc-transaction.ts",
TokenPause: "src/tests/token-service/test-token-pause-transaction.ts",
TokenUnpause: "src/tests/token-service/test-token-unpause-transaction.ts",
TokenFreeze: "src/tests/token-service/test-token-freeze-transaction.ts",
TokenUnfreeze: "src/tests/token-service/test-token-unfreeze-transaction.ts",
ALL: "src/tests/**/*.ts",
};