From 95c06e674a8cc82dd788313b3608d4673a90f8d1 Mon Sep 17 00:00:00 2001 From: Edinaldo Junior Date: Fri, 13 Dec 2024 11:28:09 -0300 Subject: [PATCH 1/8] fix: CLI hardcoded version and solving issues with containers and images --- .env.example | 2 +- src/commands/general/index.ts | 2 +- src/commands/general/init.ts | 1 - src/lib/config/simulator.ts | 9 +++++---- src/lib/services/simulator.ts | 7 ++++--- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index 78823ad..190cef4 100644 --- a/.env.example +++ b/.env.example @@ -69,4 +69,4 @@ VALIDATORS_CONFIG_JSON='' VSCODEDEBUG="false" -LOCALNETVERSION="latest" +LOCALNETVERSION="v0.29.0" diff --git a/src/commands/general/index.ts b/src/commands/general/index.ts index 3509bd1..723e362 100644 --- a/src/commands/general/index.ts +++ b/src/commands/general/index.ts @@ -11,7 +11,7 @@ export function initializeGeneralCommands(program: Command) { .option("--numValidators ", "Number of validators", "5") .option("--headless", "Headless mode", false) .option("--reset-db", "Reset Database", false) - .option("--localnet-version ", "Select a specific localnet version", 'latest') + .option("--localnet-version ", "Select a specific localnet version", 'v0.29.0') .action((options: InitActionOptions) => initAction(options, simulatorService)); program diff --git a/src/commands/general/init.ts b/src/commands/general/init.ts index 438904b..ee6e226 100644 --- a/src/commands/general/init.ts +++ b/src/commands/general/init.ts @@ -147,7 +147,6 @@ export async function initAction(options: InitActionOptions, simulatorService: I simulatorService.addConfigToEnvFile(aiProvidersEnvVars); simulatorService.addConfigToEnvFile({LOCALNETVERSION: localnetVersion}); - // Run the GenLayer Simulator console.log("Running the GenLayer Simulator..."); try { diff --git a/src/lib/config/simulator.ts b/src/lib/config/simulator.ts index d9b56ac..4d4228e 100644 --- a/src/lib/config/simulator.ts +++ b/src/lib/config/simulator.ts @@ -1,9 +1,10 @@ export const DEFAULT_JSON_RPC_URL = "http://localhost:4000/api"; -export const DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX = "/genlayer-cli-"; +export const CONTAINERS_NAME_PREFIX = "/genlayer-"; +export const IMAGES_NAME_PREFIX = "yeagerai"; export const DEFAULT_RUN_SIMULATOR_COMMAND = (location: string, options: string) => ({ - darwin: `osascript -e 'tell application "Terminal" to do script "cd ${location} && docker compose build && docker compose up ${options}"'`, - win32: `start cmd.exe /c "cd /d ${location} && docker compose build && docker compose up && pause ${options}"`, - linux: `nohup bash -c 'cd ${location} && docker compose build && docker compose up -d ${options}'`, + darwin: `osascript -e 'tell application "Terminal" to do script "cd ${location} && docker compose build && docker compose -p genlayer up ${options}"'`, + win32: `start cmd.exe /c "cd /d ${location} && docker compose build && docker compose -p genlayer up ${options} && pause"`, + linux: `nohup bash -c 'cd ${location} && docker compose build && docker compose -p genlayer up ${options} -d '`, }); export const DEFAULT_RUN_DOCKER_COMMAND = { darwin: "open -a Docker", diff --git a/src/lib/services/simulator.ts b/src/lib/services/simulator.ts index 7cbb88d..cf47790 100644 --- a/src/lib/services/simulator.ts +++ b/src/lib/services/simulator.ts @@ -8,7 +8,6 @@ import pkg from '../../../package.json' import {rpcClient} from "../clients/jsonRpcClient"; import { - DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX, DEFAULT_RUN_SIMULATOR_COMMAND, DEFAULT_RUN_DOCKER_COMMAND, STARTING_TIMEOUT_WAIT_CYLCE, @@ -16,6 +15,8 @@ import { AI_PROVIDERS_CONFIG, AiProviders, VERSION_REQUIREMENTS, + CONTAINERS_NAME_PREFIX, + IMAGES_NAME_PREFIX } from "../config/simulator"; import { checkCommand, @@ -257,7 +258,7 @@ export class SimulatorService implements ISimulatorService { const containers = await this.docker.listContainers({ all: true }); const genlayerContainers = containers.filter(container => container.Names.some(name => - name.startsWith(DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX) + name.startsWith(CONTAINERS_NAME_PREFIX) ) ); @@ -274,7 +275,7 @@ export class SimulatorService implements ISimulatorService { public async resetDockerImages(): Promise { const images = await this.docker.listImages(); const genlayerImages = images.filter(image => - image.RepoTags?.some(tag => tag.startsWith(DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX)) + image.RepoTags?.some(tag => tag.startsWith(IMAGES_NAME_PREFIX)) ); for (const imageInfo of genlayerImages) { From 33a4c02f091e87faf1b884177ee854fe1b66f52b Mon Sep 17 00:00:00 2001 From: Edinaldo Junior Date: Fri, 13 Dec 2024 17:44:06 -0300 Subject: [PATCH 2/8] feat: new global var to deal with compatible version --- src/commands/general/index.ts | 3 ++- src/lib/config/simulator.ts | 1 + tests/actions/init.test.ts | 3 ++- tests/commands/init.test.ts | 3 ++- tests/services/simulator.test.ts | 12 ++++++------ 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/commands/general/index.ts b/src/commands/general/index.ts index 723e362..1ecd336 100644 --- a/src/commands/general/index.ts +++ b/src/commands/general/index.ts @@ -3,6 +3,7 @@ import { Command } from "commander"; import simulatorService from "../../lib/services/simulator"; import { initAction, InitActionOptions } from "./init"; import { startAction, StartActionOptions } from "./start"; +import {localnetCompatibleVersion} from "../../lib/config/simulator"; export function initializeGeneralCommands(program: Command) { program @@ -11,7 +12,7 @@ export function initializeGeneralCommands(program: Command) { .option("--numValidators ", "Number of validators", "5") .option("--headless", "Headless mode", false) .option("--reset-db", "Reset Database", false) - .option("--localnet-version ", "Select a specific localnet version", 'v0.29.0') + .option("--localnet-version ", "Select a specific localnet version", localnetCompatibleVersion) .action((options: InitActionOptions) => initAction(options, simulatorService)); program diff --git a/src/lib/config/simulator.ts b/src/lib/config/simulator.ts index 4d4228e..6244732 100644 --- a/src/lib/config/simulator.ts +++ b/src/lib/config/simulator.ts @@ -1,3 +1,4 @@ +export const localnetCompatibleVersion = "v0.29.0"; export const DEFAULT_JSON_RPC_URL = "http://localhost:4000/api"; export const CONTAINERS_NAME_PREFIX = "/genlayer-"; export const IMAGES_NAME_PREFIX = "yeagerai"; diff --git a/tests/actions/init.test.ts b/tests/actions/init.test.ts index 156298f..c394521 100644 --- a/tests/actions/init.test.ts +++ b/tests/actions/init.test.ts @@ -7,6 +7,7 @@ import {mkdtempSync} from "fs"; import {join} from "path"; import fs from "fs"; import * as dotenv from "dotenv"; +import {localnetCompatibleVersion} from "../../src/lib/config/simulator"; vi.mock("fs"); @@ -14,7 +15,7 @@ vi.mock("dotenv"); const tempDir = mkdtempSync(join(tmpdir(), "test-initAction-")); -const defaultActionOptions = { numValidators: 5, branch: "main", location: tempDir, headless: false, resetDb: false, localnetVersion: 'latest' }; +const defaultActionOptions = { numValidators: 5, branch: "main", location: tempDir, headless: false, resetDb: false, localnetVersion: localnetCompatibleVersion }; describe("init action", () => { let error: ReturnType; diff --git a/tests/commands/init.test.ts b/tests/commands/init.test.ts index 6f92d15..bf0182a 100644 --- a/tests/commands/init.test.ts +++ b/tests/commands/init.test.ts @@ -3,13 +3,14 @@ import { vi, describe, beforeEach, afterEach, test, expect } from "vitest"; import { initializeGeneralCommands } from "../../src/commands/general"; import { getCommand, getCommandOption } from "../utils"; import simulatorService from '../../src/lib/services/simulator' +import {localnetCompatibleVersion} from "../../src/lib/config/simulator"; const openFrontendSpy = vi.spyOn(simulatorService, "openFrontend"); const defaultOptions = { numValidators: "5", headless: false, resetDb: false, - localnetVersion: 'latest' + localnetVersion: localnetCompatibleVersion } vi.mock("inquirer", () => ({ diff --git a/tests/services/simulator.test.ts b/tests/services/simulator.test.ts index 056e122..7205c74 100644 --- a/tests/services/simulator.test.ts +++ b/tests/services/simulator.test.ts @@ -10,10 +10,10 @@ import { checkCommand, } from "../../src/lib/clients/system"; import { - DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX, + CONTAINERS_NAME_PREFIX, VERSION_REQUIREMENTS, STARTING_TIMEOUT_ATTEMPTS, - DEFAULT_RUN_SIMULATOR_COMMAND, + DEFAULT_RUN_SIMULATOR_COMMAND, localnetCompatibleVersion, IMAGES_NAME_PREFIX, } from "../../src/lib/config/simulator"; import { rpcClient } from "../../src/lib/clients/jsonRpcClient"; import * as semver from "semver"; @@ -375,12 +375,12 @@ describe("SimulatorService - Docker Tests", () => { const mockContainers = [ { Id: "container1", - Names: [`${DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX}container1`], + Names: [`${CONTAINERS_NAME_PREFIX}container1`], State: "running", }, { Id: "container2", - Names: [`${DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX}container2`], + Names: [`${CONTAINERS_NAME_PREFIX}container2`], State: "exited", }, { @@ -417,11 +417,11 @@ describe("SimulatorService - Docker Tests", () => { const mockImages = [ { Id: "image1", - RepoTags: [`${DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX}image1:latest`], + RepoTags: [`${IMAGES_NAME_PREFIX}image1:${localnetCompatibleVersion}`], }, { Id: "image2", - RepoTags: [`${DOCKER_IMAGES_AND_CONTAINERS_NAME_PREFIX}image2:latest`], + RepoTags: [`${IMAGES_NAME_PREFIX}image2:${localnetCompatibleVersion}`], }, { Id: "image3", From 1d47d3fbcc680e287f19b9ffb94aac5f594adbd1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 13 Dec 2024 20:45:27 +0000 Subject: [PATCH 3/8] Release v0.10.0-beta.0 [skip ci] --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1458cce..0da3ffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ +## 0.10.0-beta.0 (2024-12-13) + + +### Features + +* new global var to deal with compatible version ([33a4c02](https://github.com/yeagerai/genlayer-cli/commit/33a4c02f091e87faf1b884177ee854fe1b66f52b)) + ## 0.9.1 (2024-12-13) diff --git a/package-lock.json b/package-lock.json index af12d6d..4cc4110 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "genlayer", - "version": "0.9.1", + "version": "0.10.0-beta.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "genlayer", - "version": "0.9.1", + "version": "0.10.0-beta.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 298f137..b81d9bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "genlayer", - "version": "0.9.1", + "version": "0.10.0-beta.0", "description": "GenLayer Command Line Tool", "main": "src/index.ts", "bin": { From 6930cc2186276ba0a453871591b14575f8a84f80 Mon Sep 17 00:00:00 2001 From: Edinaldo Pereira da Silva Junior <69252337+epsjunior@users.noreply.github.com> Date: Tue, 31 Dec 2024 19:18:00 -0300 Subject: [PATCH 4/8] fix: removing default value from .env.example --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 1dc52fe..642b771 100644 --- a/.env.example +++ b/.env.example @@ -69,7 +69,7 @@ VALIDATORS_CONFIG_JSON='' VSCODEDEBUG="false" -LOCALNETVERSION="v0.29.0" +LOCALNETVERSION="" FRONTEND_BUILD_TARGET = 'final' # change to 'dev' to run in dev mode From 515b4308b60a8a5477721d8c142e5d4a0a943c90 Mon Sep 17 00:00:00 2001 From: Edinaldo Junior Date: Sat, 4 Jan 2025 23:26:21 -0300 Subject: [PATCH 5/8] feat: all validators commands --- src/commands/validators/index.ts | 83 +++++++ src/commands/validators/validatorsAction.ts | 261 ++++++++++++++++++++ src/index.ts | 2 + src/lib/clients/jsonRpcClient.ts | 42 ++-- 4 files changed, 368 insertions(+), 20 deletions(-) create mode 100644 src/commands/validators/index.ts create mode 100644 src/commands/validators/validatorsAction.ts diff --git a/src/commands/validators/index.ts b/src/commands/validators/index.ts new file mode 100644 index 0000000..c62e887 --- /dev/null +++ b/src/commands/validators/index.ts @@ -0,0 +1,83 @@ +import { Command } from "commander"; +import { ValidatorsAction } from "./validatorsAction"; + +export function initializeValidatorCommands(program: Command) { + const validatorsAction = new ValidatorsAction(); + + const validatorsCommand = program + .command("validators") + .description("Manage validator operations"); + + validatorsCommand + .command("get") + .description("Retrieve details of a specific validator or all validators") + .option("--address ", "The address of the validator to retrieve (omit to retrieve all validators)") + .action(async (options) => { + await validatorsAction.getValidator({ address: options.address }); + }); + + validatorsCommand + .command("delete") + .description("Delete a specific validator or all validators") + .option("--address ", "The address of the validator to delete (omit to delete all validators)") + .action(async (options) => { + await validatorsAction.deleteValidator({ address: options.address }); + }); + + validatorsCommand + .command("count") + .description("Count all validators") + .action(async () => { + await validatorsAction.countValidators(); + }); + + validatorsCommand + .command("update ") + .description("Update a validator's details") + .option("--stake ", "New stake for the validator") + .option("--provider ", "New provider for the validator") + .option("--model ", "New model for the validator") + .option("--config ", "New JSON config for the validator") + .action(async (validatorAddress, options) => { + await validatorsAction.updateValidator({ + address: validatorAddress, + stake: options.stake ? parseFloat(options.stake) : undefined, + provider: options.provider, + model: options.model, + config: options.config, + }); + }); + + validatorsCommand + .command("create-random") + .description("Create random validators") + .option("--count ", "Number of validators to create", "1") // Default to "1" + .option( + "--providers ", + "Space-separated list of provider names (e.g., openai ollama)", + [] + ) + .action(async (options) => { + await validatorsAction.createRandomValidators({ + count: options.count, + providers: options.providers, + }); + }); + + validatorsCommand + .command("create") + .description("Create a new validator") + .option("--stake ", "Stake amount for the validator (default: 1)", "1") + .option( + "--config ", + 'Optional JSON configuration for the validator (e.g., \'{"max_tokens": 500, "temperature": 0.75}\')' + ) + .action(async (options) => { + await validatorsAction.createValidator({ + stake: options.stake, + config: options.config, + }); + }); + + return program; +} diff --git a/src/commands/validators/validatorsAction.ts b/src/commands/validators/validatorsAction.ts new file mode 100644 index 0000000..9dbc655 --- /dev/null +++ b/src/commands/validators/validatorsAction.ts @@ -0,0 +1,261 @@ +import inquirer from "inquirer"; +import { rpcClient } from "../../lib/clients/jsonRpcClient"; + +export interface ValidatorOptions { + address?: string; +} + +export interface UpdateValidatorOptions { + address: string; + stake?: number; + provider?: string; + model?: string; + config?: string; +} + +export interface CreateRandomValidatorsOptions { + count: string; + providers: string[]; +} + +export interface CreateValidatorOptions { + stake: string; + config?: string; +} + +export class ValidatorsAction { + public async getValidator(options: ValidatorOptions): Promise { + try { + if (options.address) { + console.log(`Fetching validator with address: ${options.address}`); + + const result = await rpcClient.request({ + method: "sim_getValidator", + params: [options.address], + }); + + console.log("Validator Details:", result.result); + } else { + console.log("Fetching all validators..."); + + const result = await rpcClient.request({ + method: "sim_getAllValidators", + params: [], + }); + + console.log("All Validators:", result.result); + } + } catch (error) { + console.error("Error fetching validators:", error); + } + } + + public async deleteValidator(options: ValidatorOptions): Promise { + try { + if (options.address) { + await this.confirmPrompt(`This command will delete the validator with the address: ${options.address}. Do you want to continue?`); + console.log(`Deleting validator with address: ${options.address}`); + + const result = await rpcClient.request({ + method: "sim_deleteValidator", + params: [options.address], + }); + + console.log("Deleted Address:", result.result); + } else { + await this.confirmPrompt(`This command will delete all validators. Do you want to continue?`); + console.log("Deleting all validators..."); + + await rpcClient.request({ + method: "sim_deleteAllValidators", + params: [], + }); + + console.log("Successfully deleted all validators"); + } + } catch (error) { + console.error("Error deleting validators:", error); + } + } + + public async countValidators(): Promise { + try { + console.log("Counting all validators..."); + + const result = await rpcClient.request({ + method: "sim_countValidators", + params: [], + }); + + console.log("Total Validators:", result.result); + } catch (error) { + console.error("Error counting validators:", error); + } + } + + public async updateValidator(options: UpdateValidatorOptions): Promise { + try { + console.log(`Fetching validator with address: ${options.address}...`); + const currentValidator = await rpcClient.request({ + method: "sim_getValidator", + params: [options.address], + }); + + if (!currentValidator.result) { + throw new Error(`Validator with address ${options.address} not found.`); + } + + console.log("Current Validator Details:", currentValidator.result); + + const updatedValidator = { + address: options.address, + stake: options.stake || currentValidator.result.stake, + provider: options.provider || currentValidator.result.provider, + model: options.model || currentValidator.result.model, + config: options.config ? JSON.parse(options.config) : currentValidator.result.config, + }; + + console.log("Updated Validator Details:", updatedValidator); + + const result = await rpcClient.request({ + method: "sim_updateValidator", + params: [ + updatedValidator.address, + updatedValidator.stake, + updatedValidator.provider, + updatedValidator.model, + updatedValidator.config, + ], + }); + + console.log("Validator successfully updated:", result.result); + } catch (error) { + console.error("Error updating validator:", error); + } + } + + private async confirmPrompt(message: string): Promise { + const answer = await inquirer.prompt([ + { + type: "confirm", + name: "confirmAction", + message: message, + default: true, + }, + ]); + + if (!answer.confirmAction) { + console.log("Operation aborted!"); + process.exit(0); + } + } + + public async createRandomValidators(options: CreateRandomValidatorsOptions): Promise { + try { + const count = parseInt(options.count, 10); + if (isNaN(count) || count < 1) { + throw new Error("Invalid count. Please provide a positive integer."); + } + + console.log(`Creating ${count} random validator(s)...`); + console.log(`Providers: ${options.providers.length > 0 ? options.providers.join(", ") : "None"}`); + + const result = await rpcClient.request({ + method: "sim_createRandomValidators", + params: [count, 1, 10, options.providers], + }); + + console.log("Random validators successfully created:", result.result); + } catch (error) { + console.error("Error creating random validators:", error); + } + } + + public async createValidator(options: CreateValidatorOptions): Promise { + try { + const stake = parseInt(options.stake, 10); + if (isNaN(stake) || stake < 1) { + throw new Error("Invalid stake. Please provide a positive integer."); + } + + console.log("Fetching available providers and models..."); + + const providersAndModels = await rpcClient.request({ + method: "sim_getProvidersAndModels", + params: [], + }); + + if (!providersAndModels.result || providersAndModels.result.length === 0) { + throw new Error("No providers or models available."); + } + + const availableProviders = [ + ...new Map( + providersAndModels.result + .filter((entry: any) => entry.is_available) + .map((entry: any) => [entry.provider, entry]) + ).values(), + ]; + + const { selectedProvider } = await inquirer.prompt([ + { + type: "list", + name: "selectedProvider", + message: "Select a provider:", + choices: availableProviders.map((entry: any) => entry.provider), + }, + ]); + + const availableModels = providersAndModels.result.filter( + (entry: any) => entry.provider === selectedProvider && entry.is_model_available + ); + + if (availableModels.length === 0) { + throw new Error("No models available for the selected provider."); + } + + const { selectedModel } = await inquirer.prompt([ + { + type: "list", + name: "selectedModel", + message: "Select a model:", + choices: availableModels.map((entry: any) => entry.model), + }, + ]); + + const modelDetails = availableModels.find( + (entry: any) => entry.model === selectedModel + ); + + if (!modelDetails) { + throw new Error("Selected model details not found."); + } + + const config = options.config ? JSON.parse(options.config) : modelDetails.config; + + console.log("Creating validator with the following details:"); + console.log(`Stake: ${stake}`); + console.log(`Provider: ${modelDetails.provider}`); + console.log(`Model: ${modelDetails.model}`); + console.log(`Config:`, config); + console.log(`Plugin:`, modelDetails.plugin); + console.log(`Plugin Config:`, modelDetails.plugin_config); + + const result = await rpcClient.request({ + method: "sim_createValidator", + params: [ + stake, + modelDetails.provider, + modelDetails.model, + config, + modelDetails.plugin, + modelDetails.plugin_config, + ], + }); + + console.log("Validator successfully created:", result.result); + } catch (error) { + console.error("Error creating validator:", error); + } + } +} diff --git a/src/index.ts b/src/index.ts index 5ae3cea..7b71737 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { initializeGeneralCommands } from "../src/commands/general"; import { initializeKeygenCommands } from "../src/commands/keygen"; import { initializeContractsCommands } from "../src/commands/contracts"; import { initializeConfigCommands } from "../src/commands/config"; +import {initializeValidatorCommands} from "../src/commands/validators"; export function initializeCLI() { program.version(version).description(CLI_DESCRIPTION); @@ -13,6 +14,7 @@ export function initializeCLI() { initializeKeygenCommands(program); initializeContractsCommands(program); initializeConfigCommands(program); + initializeValidatorCommands(program); program.parse(process.argv); } diff --git a/src/lib/clients/jsonRpcClient.ts b/src/lib/clients/jsonRpcClient.ts index 412921a..b3c3702 100644 --- a/src/lib/clients/jsonRpcClient.ts +++ b/src/lib/clients/jsonRpcClient.ts @@ -16,27 +16,29 @@ export class JsonRpcClient { } async request({method, params}: JsonRPCParams): Promise { - try { - const response = await fetch(this.serverUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: uuidv4(), - method, - params, - }), - }); - - if (response.ok) { - return response.json(); - } - } catch (error: any) { - throw new Error(`Fetch Error: ${error.message}`); + const response = await fetch(this.serverUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: uuidv4(), + method, + params, + }), + }); + + if (response.ok) { + return response.json(); + } + const result = await response.json(); + + if (!response.ok || result.error) { + throw new Error(result?.error?.message || response.statusText); } - return null; + + return result; } } export const rpcClient = new JsonRpcClient(DEFAULT_JSON_RPC_URL); From 6a360edab0d450cd58936cd207c309786e56564e Mon Sep 17 00:00:00 2001 From: Edinaldo Junior Date: Sun, 5 Jan 2025 16:18:44 -0300 Subject: [PATCH 6/8] fix: json rpc bug and tests --- src/commands/validators/index.ts | 5 +- .../{validatorsAction.ts => validators.ts} | 20 +- src/lib/clients/jsonRpcClient.ts | 5 +- src/lib/services/simulator.ts | 4 +- tests/actions/validators.test.ts | 594 ++++++++++++++++++ tests/commands/validator.test.ts | 126 ++++ tests/index.test.ts | 4 + tests/libs/jsonRpcClient.test.ts | 5 +- tests/services/simulator.test.ts | 16 +- 9 files changed, 761 insertions(+), 18 deletions(-) rename src/commands/validators/{validatorsAction.ts => validators.ts} (91%) create mode 100644 tests/actions/validators.test.ts create mode 100644 tests/commands/validator.test.ts diff --git a/src/commands/validators/index.ts b/src/commands/validators/index.ts index c62e887..6400379 100644 --- a/src/commands/validators/index.ts +++ b/src/commands/validators/index.ts @@ -1,5 +1,5 @@ import { Command } from "commander"; -import { ValidatorsAction } from "./validatorsAction"; +import { ValidatorsAction } from "./validators"; export function initializeValidatorCommands(program: Command) { const validatorsAction = new ValidatorsAction(); @@ -10,6 +10,7 @@ export function initializeValidatorCommands(program: Command) { validatorsCommand .command("get") + .description("Retrieve details of a specific validator or all validators") .option("--address ", "The address of the validator to retrieve (omit to retrieve all validators)") .action(async (options) => { @@ -41,7 +42,7 @@ export function initializeValidatorCommands(program: Command) { .action(async (validatorAddress, options) => { await validatorsAction.updateValidator({ address: validatorAddress, - stake: options.stake ? parseFloat(options.stake) : undefined, + stake: options.stake, provider: options.provider, model: options.model, config: options.config, diff --git a/src/commands/validators/validatorsAction.ts b/src/commands/validators/validators.ts similarity index 91% rename from src/commands/validators/validatorsAction.ts rename to src/commands/validators/validators.ts index 9dbc655..c5ceff5 100644 --- a/src/commands/validators/validatorsAction.ts +++ b/src/commands/validators/validators.ts @@ -7,7 +7,7 @@ export interface ValidatorOptions { export interface UpdateValidatorOptions { address: string; - stake?: number; + stake?: string; provider?: string; model?: string; config?: string; @@ -107,6 +107,14 @@ export class ValidatorsAction { console.log("Current Validator Details:", currentValidator.result); + const parsedStake = options.stake + ? parseInt(options.stake, 10) + : currentValidator.result.stake; + + if (isNaN(parsedStake) || parsedStake < 0) { + return console.error("Invalid stake value. Stake must be a positive integer."); + } + const updatedValidator = { address: options.address, stake: options.stake || currentValidator.result.stake, @@ -154,7 +162,7 @@ export class ValidatorsAction { try { const count = parseInt(options.count, 10); if (isNaN(count) || count < 1) { - throw new Error("Invalid count. Please provide a positive integer."); + return console.error("Invalid count. Please provide a positive integer."); } console.log(`Creating ${count} random validator(s)...`); @@ -175,7 +183,7 @@ export class ValidatorsAction { try { const stake = parseInt(options.stake, 10); if (isNaN(stake) || stake < 1) { - throw new Error("Invalid stake. Please provide a positive integer."); + return console.error("Invalid stake. Please provide a positive integer."); } console.log("Fetching available providers and models..."); @@ -186,7 +194,7 @@ export class ValidatorsAction { }); if (!providersAndModels.result || providersAndModels.result.length === 0) { - throw new Error("No providers or models available."); + return console.error("No providers or models available."); } const availableProviders = [ @@ -211,7 +219,7 @@ export class ValidatorsAction { ); if (availableModels.length === 0) { - throw new Error("No models available for the selected provider."); + return console.error("No models available for the selected provider."); } const { selectedModel } = await inquirer.prompt([ @@ -228,7 +236,7 @@ export class ValidatorsAction { ); if (!modelDetails) { - throw new Error("Selected model details not found."); + return console.error("Selected model details not found."); } const config = options.config ? JSON.parse(options.config) : modelDetails.config; diff --git a/src/lib/clients/jsonRpcClient.ts b/src/lib/clients/jsonRpcClient.ts index b3c3702..25274a7 100644 --- a/src/lib/clients/jsonRpcClient.ts +++ b/src/lib/clients/jsonRpcClient.ts @@ -34,11 +34,8 @@ export class JsonRpcClient { } const result = await response.json(); - if (!response.ok || result.error) { - throw new Error(result?.error?.message || response.statusText); - } + throw new Error(result?.error?.message || response.statusText); - return result; } } export const rpcClient = new JsonRpcClient(DEFAULT_JSON_RPC_URL); diff --git a/src/lib/services/simulator.ts b/src/lib/services/simulator.ts index 96ac2d8..ceb72d6 100644 --- a/src/lib/services/simulator.ts +++ b/src/lib/services/simulator.ts @@ -223,13 +223,13 @@ export class SimulatorService implements ISimulatorService { public createRandomValidators(numValidators: number, llmProviders: AiProviders[]): Promise { return rpcClient.request({ - method: "create_random_validators", + method: "sim_createRandomValidators", params: [numValidators, 1, 10, llmProviders], }); } public deleteAllValidators(): Promise { - return rpcClient.request({method: "delete_all_validators", params: []}); + return rpcClient.request({method: "sim_deleteAllValidators", params: []}); } public getAiProvidersOptions(withHint: boolean = true): Array<{name: string; value: string}> { diff --git a/tests/actions/validators.test.ts b/tests/actions/validators.test.ts new file mode 100644 index 0000000..fa87a2d --- /dev/null +++ b/tests/actions/validators.test.ts @@ -0,0 +1,594 @@ +import { describe, test, vi, beforeEach, afterEach, expect } from "vitest"; +import { ValidatorsAction } from "../../src/commands/validators/validators"; +import { rpcClient } from "../../src/lib/clients/jsonRpcClient"; +import inquirer from "inquirer"; + +vi.mock("../../src/lib/clients/jsonRpcClient", () => ({ + rpcClient: { + request: vi.fn(), + }, +})); + +vi.mock("inquirer"); + +describe("ValidatorsAction", () => { + let validatorsAction: ValidatorsAction; + + beforeEach(() => { + vi.clearAllMocks(); + validatorsAction = new ValidatorsAction(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("getValidator", () => { + test("should fetch a specific validator by address", async () => { + const mockAddress = "mocked_address"; + const mockResponse = { result: { id: 1, name: "Validator1" } }; + vi.mocked(rpcClient.request).mockResolvedValue(mockResponse); + + console.log = vi.fn(); + + await validatorsAction.getValidator({ address: mockAddress }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getValidator", + params: [mockAddress], + }); + expect(console.log).toHaveBeenCalledWith("Validator Details:", mockResponse.result); + }); + + test("should fetch all validators when no address is provided", async () => { + const mockResponse = { result: [{ id: 1 }, { id: 2 }] }; + vi.mocked(rpcClient.request).mockResolvedValue(mockResponse); + + console.log = vi.fn(); + + await validatorsAction.getValidator({}); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getAllValidators", + params: [], + }); + expect(console.log).toHaveBeenCalledWith("All Validators:", mockResponse.result); + }); + + test("should log an error if an exception occurs while fetching a specific validator", async () => { + const mockAddress = "mocked_address"; + const mockError = new Error("Unexpected error"); + + vi.mocked(rpcClient.request).mockRejectedValue(mockError); + + console.error = vi.fn(); + + await validatorsAction.getValidator({ address: mockAddress }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getValidator", + params: [mockAddress], + }); + expect(console.error).toHaveBeenCalledWith("Error fetching validators:", mockError); + }); + + test("should log an error if an exception occurs while fetching all validators", async () => { + const mockError = new Error("Unexpected error"); + + vi.mocked(rpcClient.request).mockRejectedValue(mockError); + + console.error = vi.fn(); + + await validatorsAction.getValidator({}); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getAllValidators", + params: [], + }); + expect(console.error).toHaveBeenCalledWith("Error fetching validators:", mockError); + }); + }); + + describe("deleteValidator", () => { + test("should delete a specific validator", async () => { + const mockAddress = "mocked_address"; + vi.mocked(inquirer.prompt).mockResolvedValue({ confirmAction: true }); + vi.mocked(rpcClient.request).mockResolvedValue({ result: { id: 1 } }); + + console.log = vi.fn(); + + await validatorsAction.deleteValidator({ address: mockAddress }); + + expect(inquirer.prompt).toHaveBeenCalled(); + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_deleteValidator", + params: [mockAddress], + }); + expect(console.log).toHaveBeenCalledWith("Deleted Address:", { id: 1 }); + }); + + test("should delete all validators when no address is provided", async () => { + vi.mocked(inquirer.prompt).mockResolvedValue({ confirmAction: true }); + vi.mocked(rpcClient.request).mockResolvedValue({}); + + console.log = vi.fn(); + + await validatorsAction.deleteValidator({}); + + expect(inquirer.prompt).toHaveBeenCalled(); + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_deleteAllValidators", + params: [], + }); + expect(console.log).toHaveBeenCalledWith("Successfully deleted all validators"); + }); + + test("should abort deletion if user declines confirmation", async () => { + vi.mocked(inquirer.prompt).mockResolvedValue({ confirmAction: false }); + + console.log = vi.fn(); + + await validatorsAction.deleteValidator({ address: "mocked_address" }) + + expect(inquirer.prompt).toHaveBeenCalled(); + expect(console.log).toHaveBeenCalledWith("Operation aborted!"); + expect(rpcClient.request).not.toHaveBeenCalled(); + }); + }); + + describe("countValidators", () => { + test("should count all validators", async () => { + const mockResponse = { result: 42 }; + vi.mocked(rpcClient.request).mockResolvedValue(mockResponse); + + console.log = vi.fn(); + + await validatorsAction.countValidators(); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_countValidators", + params: [], + }); + expect(console.log).toHaveBeenCalledWith("Total Validators:", 42); + }); + + test("should log an error if an exception occurs while counting validators", async () => { + const mockError = new Error("Unexpected error"); + + vi.mocked(rpcClient.request).mockRejectedValue(mockError); + + console.error = vi.fn(); + + await validatorsAction.countValidators(); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_countValidators", + params: [], + }); + expect(console.error).toHaveBeenCalledWith("Error counting validators:", mockError); + }); + }); + + describe("createValidator", () => { + test("should create a validator with selected provider and model", async () => { + const mockProvidersAndModels = [ + { + provider: "Provider1", + is_available: true, + is_model_available: true, + model: "Model1", + config: { max_tokens: 500 }, + plugin: "Plugin1", + plugin_config: { api_key_env_var: "KEY1" }, + }, + ]; + const mockResponse = { result: { id: 123 } }; + + vi.mocked(rpcClient.request) + .mockResolvedValueOnce({ result: mockProvidersAndModels }) + .mockResolvedValueOnce(mockResponse); + + vi.mocked(inquirer.prompt) + .mockResolvedValueOnce({ selectedProvider: "Provider1" }) + .mockResolvedValueOnce({ selectedModel: "Model1" }); + + console.log = vi.fn(); + + await validatorsAction.createValidator({ stake: "10" }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getProvidersAndModels", + params: [], + }); + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_createValidator", + params: [ + 10, + "Provider1", + "Model1", + { max_tokens: 500 }, + "Plugin1", + { api_key_env_var: "KEY1" }, + ], + }); + expect(console.log).toHaveBeenCalledWith("Validator successfully created:", { id: 123 }); + }); + + test("should log an error for invalid stake", async () => { + console.error = vi.fn(); + + await validatorsAction.createValidator({ stake: "invalid" }); + + expect(console.error).toHaveBeenCalledWith("Invalid stake. Please provide a positive integer."); + expect(rpcClient.request).not.toHaveBeenCalled(); + }); + + test("should log an error if no providers or models are available", async () => { + vi.mocked(rpcClient.request).mockResolvedValueOnce({ result: [] }); + + console.error = vi.fn(); + + await validatorsAction.createValidator({ stake: "10" }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getProvidersAndModels", + params: [], + }); + expect(console.error).toHaveBeenCalledWith("No providers or models available."); + }); + + test("should log an error if no models are available for the selected provider", async () => { + const mockProvidersAndModels = [ + { provider: "Provider1", is_available: true, is_model_available: false }, + ]; + + vi.mocked(rpcClient.request).mockResolvedValueOnce({ result: mockProvidersAndModels }); + vi.mocked(inquirer.prompt).mockResolvedValueOnce({ selectedProvider: "Provider1" }); + + console.error = vi.fn(); + + await validatorsAction.createValidator({ stake: "10" }); + + expect(console.error).toHaveBeenCalledWith("No models available for the selected provider."); + }); + + test("should log an error if selected model details are not found", async () => { + const mockProvidersAndModels = [ + { + provider: "Provider1", + is_available: true, + is_model_available: true, + model: "Model1", + }, + ]; + + vi.mocked(rpcClient.request).mockResolvedValueOnce({ result: mockProvidersAndModels }); + vi.mocked(inquirer.prompt) + .mockResolvedValueOnce({ selectedProvider: "Provider1" }) + .mockResolvedValueOnce({ selectedModel: "NonExistentModel" }); + + console.error = vi.fn(); + + await validatorsAction.createValidator({ stake: "10" }); + + expect(console.error).toHaveBeenCalledWith("Selected model details not found."); + }); + + test("should log an error if an exception occurs during the process", async () => { + const mockError = new Error("Unexpected error"); + vi.mocked(rpcClient.request).mockRejectedValue(mockError); + + console.error = vi.fn(); + + await validatorsAction.createValidator({ stake: "10" }); + + expect(console.error).toHaveBeenCalledWith("Error creating validator:", mockError); + }); + + test("should use user-provided config if specified", async () => { + const mockProvidersAndModels = [ + { + provider: "Provider1", + is_available: true, + is_model_available: true, + model: "Model1", + config: { max_tokens: 500 }, + plugin: "Plugin1", + plugin_config: { api_key_env_var: "KEY1" }, + }, + ]; + const mockResponse = { result: { id: 123 } }; + + vi.mocked(rpcClient.request) + .mockResolvedValueOnce({ result: mockProvidersAndModels }) + .mockResolvedValueOnce(mockResponse); + + vi.mocked(inquirer.prompt) + .mockResolvedValueOnce({ selectedProvider: "Provider1" }) + .mockResolvedValueOnce({ selectedModel: "Model1" }); + + console.log = vi.fn(); + + const customConfig = '{"custom_key":"custom_value"}'; + await validatorsAction.createValidator({ stake: "10", config: customConfig }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_createValidator", + params: [ + 10, + "Provider1", + "Model1", + { custom_key: "custom_value" }, + "Plugin1", + { api_key_env_var: "KEY1" }, + ], + }); + expect(console.log).toHaveBeenCalledWith("Validator successfully created:", { id: 123 }); + }); + }); + describe("createRandomValidators", () => { + test("should create random validators with valid count and providers", async () => { + const mockResponse = { result: { success: true } }; + vi.mocked(rpcClient.request).mockResolvedValue(mockResponse); + + console.log = vi.fn(); + + await validatorsAction.createRandomValidators({ count: "5", providers: ["Provider1", "Provider2"] }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_createRandomValidators", + params: [5, 1, 10, ["Provider1", "Provider2"]], + }); + expect(console.log).toHaveBeenCalledWith("Creating 5 random validator(s)..."); + expect(console.log).toHaveBeenCalledWith("Providers: Provider1, Provider2"); + expect(console.log).toHaveBeenCalledWith("Random validators successfully created:", mockResponse.result); + }); + + test("should create random validators with default provider message when providers list is empty", async () => { + const mockResponse = { result: { success: true } }; + vi.mocked(rpcClient.request).mockResolvedValue(mockResponse); + + console.log = vi.fn(); + + await validatorsAction.createRandomValidators({ count: "3", providers: [] }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_createRandomValidators", + params: [3, 1, 10, []], + }); + expect(console.log).toHaveBeenCalledWith("Creating 3 random validator(s)..."); + expect(console.log).toHaveBeenCalledWith("Providers: None"); + expect(console.log).toHaveBeenCalledWith("Random validators successfully created:", mockResponse.result); + }); + + test("should throw an error for invalid count", async () => { + console.error = vi.fn(); + + await validatorsAction.createRandomValidators({ count: "invalid", providers: ["Provider1"] }); + + expect(console.error).toHaveBeenCalledWith("Invalid count. Please provide a positive integer."); + expect(rpcClient.request).not.toHaveBeenCalled(); + }); + + test("should log an error if rpc request fails", async () => { + const mockError = new Error("RPC failure"); + vi.mocked(rpcClient.request).mockRejectedValue(mockError); + + console.error = vi.fn(); + + await validatorsAction.createRandomValidators({ count: "5", providers: ["Provider1"] }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_createRandomValidators", + params: [5, 1, 10, ["Provider1"]], + }); + expect(console.error).toHaveBeenCalledWith("Error creating random validators:", mockError); + }); + }); + + describe("updateValidator", () => { + test("should fetch and update a validator with new stake", async () => { + const mockAddress = "mocked_address"; + const mockCurrentValidator = { + result: { + address: "mocked_address", + stake: 100, + provider: "Provider1", + model: "Model1", + config: { max_tokens: 500 }, + }, + }; + const mockResponse = { result: { success: true } }; + + vi.mocked(rpcClient.request) + .mockResolvedValueOnce(mockCurrentValidator) + .mockResolvedValueOnce(mockResponse); + + console.log = vi.fn(); + + await validatorsAction.updateValidator({ address: mockAddress, stake: "200" }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getValidator", + params: [mockAddress], + }); + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_updateValidator", + params: [ + "mocked_address", + "200", + "Provider1", + "Model1", + { max_tokens: 500 }, + ], + }); + expect(console.log).toHaveBeenCalledWith("Validator successfully updated:", mockResponse.result); + }); + + test("should fetch and update a validator with new provider and model", async () => { + const mockAddress = "mocked_address"; + const mockCurrentValidator = { + result: { + address: "mocked_address", + stake: "100", + provider: "Provider1", + model: "Model1", + config: { max_tokens: 500 }, + }, + }; + const mockResponse = { result: { success: true } }; + + vi.mocked(rpcClient.request) + .mockResolvedValueOnce(mockCurrentValidator) + .mockResolvedValueOnce(mockResponse); + + console.log = vi.fn(); + + await validatorsAction.updateValidator({ + address: mockAddress, + provider: "Provider2", + model: "Model2", + }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getValidator", + params: [mockAddress], + }); + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_updateValidator", + params: [ + "mocked_address", + "100", + "Provider2", + "Model2", + { max_tokens: 500 }, + ], + }); + expect(console.log).toHaveBeenCalledWith("Validator successfully updated:", mockResponse.result); + }); + + test("should fetch and update a validator with new config", async () => { + const mockAddress = "mocked_address"; + const mockCurrentValidator = { + result: { + address: "mocked_address", + stake: "100", + provider: "Provider1", + model: "Model1", + config: { max_tokens: 500 }, + }, + }; + const mockResponse = { result: { success: true } }; + + vi.mocked(rpcClient.request) + .mockResolvedValueOnce(mockCurrentValidator) + .mockResolvedValueOnce(mockResponse); + + console.log = vi.fn(); + + const newConfig = '{"max_tokens":1000}'; + await validatorsAction.updateValidator({ address: mockAddress, config: newConfig }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getValidator", + params: [mockAddress], + }); + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_updateValidator", + params: [ + "mocked_address", + "100", + "Provider1", + "Model1", + { max_tokens: 1000 }, + ], + }); + expect(console.log).toHaveBeenCalledWith("Validator successfully updated:", mockResponse.result); + }); + + test("should throw an error if validator is not found", async () => { + const mockAddress = "mocked_address"; + const mockResponse = { result: null }; + + vi.mocked(rpcClient.request).mockResolvedValue(mockResponse); + + console.error = vi.fn(); + + await validatorsAction.updateValidator({ address: mockAddress }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getValidator", + params: [mockAddress], + }); + expect(console.error).toHaveBeenCalledWith( + "Error updating validator:", + new Error(`Validator with address ${mockAddress} not found.`) + ); + expect(rpcClient.request).toHaveBeenCalledTimes(1); + }); + + test("should log an error if updateValidator RPC call fails", async () => { + const mockAddress = "mocked_address"; + const mockCurrentValidator = { + result: { + address: "mocked_address", + stake: "100", + provider: "Provider1", + model: "Model1", + config: { max_tokens: 500 }, + }, + }; + const mockError = new Error("RPC failure"); + + vi.mocked(rpcClient.request) + .mockResolvedValueOnce(mockCurrentValidator) + .mockRejectedValueOnce(mockError); + + console.error = vi.fn(); + + await validatorsAction.updateValidator({ address: mockAddress, stake: "200" }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getValidator", + params: [mockAddress], + }); + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_updateValidator", + params: [ + "mocked_address", + "200", + "Provider1", + "Model1", + { max_tokens: 500 }, + ], + }); + expect(console.error).toHaveBeenCalledWith("Error updating validator:", mockError); + }); + }); + test("should log an error for invalid stake value", async () => { + const mockAddress = "mocked_address"; + const mockCurrentValidator = { + result: { + address: "mocked_address", + stake: 100, + provider: "Provider1", + model: "Model1", + config: { max_tokens: 500 }, + }, + }; + + vi.mocked(rpcClient.request).mockResolvedValue(mockCurrentValidator); + + console.error = vi.fn(); + + await validatorsAction.updateValidator({ address: mockAddress, stake: "-10" }); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_getValidator", + params: [mockAddress], + }); + expect(console.error).toHaveBeenCalledWith("Invalid stake value. Stake must be a positive integer."); + expect(rpcClient.request).toHaveBeenCalledTimes(1); + }); + +}); \ No newline at end of file diff --git a/tests/commands/validator.test.ts b/tests/commands/validator.test.ts new file mode 100644 index 0000000..684010a --- /dev/null +++ b/tests/commands/validator.test.ts @@ -0,0 +1,126 @@ +import { Command } from "commander"; +import { vi, describe, beforeEach, afterEach, test, expect } from "vitest"; +import { initializeValidatorCommands } from "../../src/commands/validators"; +import { ValidatorsAction } from "../../src/commands/validators/validators"; + +vi.mock("../../src/commands/validators/validators"); + +describe("validators command", () => { + let program: Command; + + beforeEach(() => { + program = new Command(); + initializeValidatorCommands(program); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("ValidatorsAction.getValidator is called with address option", async () => { + program.parse(["node", "test", "validators", "get", "--address", "mocked_address"]); + expect(ValidatorsAction).toHaveBeenCalledTimes(1); + expect(ValidatorsAction.prototype.getValidator).toHaveBeenCalledWith({ + address: "mocked_address", + }); + }); + + test("ValidatorsAction.getValidator is called without address option", async () => { + program.parse(["node", "test", "validators", "get"]); + expect(ValidatorsAction).toHaveBeenCalledTimes(1); + expect(ValidatorsAction.prototype.getValidator).toHaveBeenCalledWith({}); + }); + + test("ValidatorsAction.deleteValidator is called with address option", async () => { + program.parse(["node", "test", "validators", "delete", "--address", "mocked_address"]); + expect(ValidatorsAction).toHaveBeenCalledTimes(1); + expect(ValidatorsAction.prototype.deleteValidator).toHaveBeenCalledWith({ + address: "mocked_address", + }); + }); + + test("ValidatorsAction.deleteValidator is called without address option", async () => { + program.parse(["node", "test", "validators", "delete"]); + expect(ValidatorsAction).toHaveBeenCalledTimes(1); + expect(ValidatorsAction.prototype.deleteValidator).toHaveBeenCalledWith({}); + }); + + test("ValidatorsAction.countValidators is called", async () => { + program.parse(["node", "test", "validators", "count"]); + expect(ValidatorsAction).toHaveBeenCalledTimes(1); + expect(ValidatorsAction.prototype.countValidators).toHaveBeenCalled(); + }); + + test("ValidatorsAction.updateValidator is called with all options", async () => { + program.parse([ + "node", + "test", + "validators", + "update", + "mocked_address", + "--stake", + "10", + "--provider", + "mocked_provider", + "--model", + "mocked_model", + '--config', + '{"max_tokens":500}', + ]); + expect(ValidatorsAction).toHaveBeenCalledTimes(1); + expect(ValidatorsAction.prototype.updateValidator).toHaveBeenCalledWith({ + address: "mocked_address", + stake: "10", + provider: "mocked_provider", + model: "mocked_model", + config: '{"max_tokens":500}', + }); + }); + + test("ValidatorsAction.createRandomValidators is called with count and providers", async () => { + program.parse([ + "node", + "test", + "validators", + "create-random", + "--count", + "3", + "--providers", + "provider1", + "provider2", + ]); + expect(ValidatorsAction).toHaveBeenCalledTimes(1); + expect(ValidatorsAction.prototype.createRandomValidators).toHaveBeenCalledWith({ + count: "3", + providers: ["provider1", "provider2"], + }); + }); + + test("ValidatorsAction.createValidator is called with default stake", async () => { + program.parse(["node", "test", "validators", "create"]); + expect(ValidatorsAction).toHaveBeenCalledTimes(1); + expect(ValidatorsAction.prototype.createValidator).toHaveBeenCalledWith({ + stake: "1", + config: undefined, + }); + }); + + test("ValidatorsAction.createValidator is called with stake and config", async () => { + program.parse([ + "node", + "test", + "validators", + "create", + "--stake", + "5", + '--config', + '{"temperature":0.8}', + ]); + expect(ValidatorsAction).toHaveBeenCalledTimes(1); + expect(ValidatorsAction.prototype.createValidator).toHaveBeenCalledWith({ + stake: "5", + config: '{"temperature":0.8}', + }); + }); + +}); diff --git a/tests/index.test.ts b/tests/index.test.ts index 8fd8023..3021f63 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -25,6 +25,10 @@ vi.mock("../src/commands/config", () => ({ initializeConfigCommands: vi.fn(), })); +vi.mock("../src/commands/validators", () => ({ + initializeValidatorCommands: vi.fn(), +})); + describe("CLI", () => { it("should initialize CLI", () => { diff --git a/tests/libs/jsonRpcClient.test.ts b/tests/libs/jsonRpcClient.test.ts index 95de745..33a0b53 100644 --- a/tests/libs/jsonRpcClient.test.ts +++ b/tests/libs/jsonRpcClient.test.ts @@ -40,6 +40,7 @@ describe("JsonRpcClient - Successful and Unsuccessful Requests", () => { test("should return null when the fetch response is not ok", async () => { (fetch as Mock).mockResolvedValueOnce({ ok: false, + statusText: "Something went wrong", json: async () => ({ error: "Something went wrong" }), }); @@ -48,9 +49,7 @@ describe("JsonRpcClient - Successful and Unsuccessful Requests", () => { params: ["param1", "param2"], }; - const response = await rpcClient.request(params); - - expect(response).toBeNull(); + await expect(rpcClient.request(params)).rejects.toThrowError("Something went wrong"); expect(fetch).toHaveBeenCalledWith(mockServerUrl, { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/tests/services/simulator.test.ts b/tests/services/simulator.test.ts index 7205c74..16ca8c6 100644 --- a/tests/services/simulator.test.ts +++ b/tests/services/simulator.test.ts @@ -200,7 +200,7 @@ describe("SimulatorService - Basic Tests", () => { const mockResponse = { success: true }; vi.mocked(rpcClient.request).mockResolvedValue(mockResponse); const result = await simulatorService.deleteAllValidators(); - expect(rpcClient.request).toHaveBeenCalledWith({ method: "delete_all_validators", params: [] }); + expect(rpcClient.request).toHaveBeenCalledWith({ method: "sim_deleteAllValidators", params: [] }); expect(result).toBe(mockResponse); }); @@ -537,4 +537,18 @@ describe('normalizeLocalnetVersion', () => { mockExit.mockRestore(); mockConsoleError.mockRestore(); }); + test("should log an error if an exception occurs while cleaning the database", async () => { + const mockError = new Error("Database cleanup error"); + vi.mocked(rpcClient.request).mockRejectedValue(mockError); + + console.error = vi.fn(); + + await simulatorService.cleanDatabase(); + + expect(rpcClient.request).toHaveBeenCalledWith({ + method: "sim_clearDbTables", + params: [['current_state', 'transactions']], + }); + expect(console.error).toHaveBeenCalledWith(mockError); + }); }); From 5881d914c3116e4af83554c90d7aed9d8a550813 Mon Sep 17 00:00:00 2001 From: Edinaldo Junior Date: Tue, 7 Jan 2025 15:59:34 -0300 Subject: [PATCH 7/8] feat: non interactive validator creation --- src/commands/validators/index.ts | 4 ++ src/commands/validators/validators.ts | 73 ++++++++++++++------------- src/lib/actions/BaseAction.ts | 19 +++++++ tests/actions/validators.test.ts | 7 +++ 4 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 src/lib/actions/BaseAction.ts diff --git a/src/commands/validators/index.ts b/src/commands/validators/index.ts index 6400379..86fcf4f 100644 --- a/src/commands/validators/index.ts +++ b/src/commands/validators/index.ts @@ -73,10 +73,14 @@ export function initializeValidatorCommands(program: Command) { "--config ", 'Optional JSON configuration for the validator (e.g., \'{"max_tokens": 500, "temperature": 0.75}\')' ) + .option("--provider ", "Specify the provider for the validator") + .option("--model ", "Specify the model for the validator") .action(async (options) => { await validatorsAction.createValidator({ stake: options.stake, config: options.config, + provider: options.provider, + model: options.model, }); }); diff --git a/src/commands/validators/validators.ts b/src/commands/validators/validators.ts index c5ceff5..7fa2296 100644 --- a/src/commands/validators/validators.ts +++ b/src/commands/validators/validators.ts @@ -1,5 +1,6 @@ import inquirer from "inquirer"; import { rpcClient } from "../../lib/clients/jsonRpcClient"; +import { BaseAction } from "../../lib/actions/BaseAction"; export interface ValidatorOptions { address?: string; @@ -21,9 +22,11 @@ export interface CreateRandomValidatorsOptions { export interface CreateValidatorOptions { stake: string; config?: string; + model?: string; + provider?: string; } -export class ValidatorsAction { +export class ValidatorsAction extends BaseAction { public async getValidator(options: ValidatorOptions): Promise { try { if (options.address) { @@ -142,22 +145,6 @@ export class ValidatorsAction { } } - private async confirmPrompt(message: string): Promise { - const answer = await inquirer.prompt([ - { - type: "confirm", - name: "confirmAction", - message: message, - default: true, - }, - ]); - - if (!answer.confirmAction) { - console.log("Operation aborted!"); - process.exit(0); - } - } - public async createRandomValidators(options: CreateRandomValidatorsOptions): Promise { try { const count = parseInt(options.count, 10); @@ -186,6 +173,10 @@ export class ValidatorsAction { return console.error("Invalid stake. Please provide a positive integer."); } + if (options.model && !options.provider) { + return console.error("You must specify a provider if using a model."); + } + console.log("Fetching available providers and models..."); const providersAndModels = await rpcClient.request({ @@ -205,34 +196,46 @@ export class ValidatorsAction { ).values(), ]; - const { selectedProvider } = await inquirer.prompt([ - { - type: "list", - name: "selectedProvider", - message: "Select a provider:", - choices: availableProviders.map((entry: any) => entry.provider), - }, - ]); + let provider = options.provider + + if(!provider){ + const { selectedProvider } = await inquirer.prompt([ + { + type: "list", + name: "selectedProvider", + message: "Select a provider:", + choices: availableProviders.map((entry: any) => entry.provider), + }, + ]); + + provider = selectedProvider; + } const availableModels = providersAndModels.result.filter( - (entry: any) => entry.provider === selectedProvider && entry.is_model_available + (entry: any) => entry.provider === provider && entry.is_model_available ); if (availableModels.length === 0) { return console.error("No models available for the selected provider."); } - const { selectedModel } = await inquirer.prompt([ - { - type: "list", - name: "selectedModel", - message: "Select a model:", - choices: availableModels.map((entry: any) => entry.model), - }, - ]); + let model = options.model; + + if(!model){ + const { selectedModel } = await inquirer.prompt([ + { + type: "list", + name: "selectedModel", + message: "Select a model:", + choices: availableModels.map((entry: any) => entry.model), + }, + ]); + + model = selectedModel; + } const modelDetails = availableModels.find( - (entry: any) => entry.model === selectedModel + (entry: any) => entry.model === model ); if (!modelDetails) { diff --git a/src/lib/actions/BaseAction.ts b/src/lib/actions/BaseAction.ts new file mode 100644 index 0000000..93dab90 --- /dev/null +++ b/src/lib/actions/BaseAction.ts @@ -0,0 +1,19 @@ +import inquirer from "inquirer"; + +export class BaseAction { + protected async confirmPrompt(message: string): Promise { + const answer = await inquirer.prompt([ + { + type: "confirm", + name: "confirmAction", + message: message, + default: true, + }, + ]); + + if (!answer.confirmAction) { + console.log("Operation aborted!"); + process.exit(0); + } + } +} diff --git a/tests/actions/validators.test.ts b/tests/actions/validators.test.ts index fa87a2d..6b17137 100644 --- a/tests/actions/validators.test.ts +++ b/tests/actions/validators.test.ts @@ -590,5 +590,12 @@ describe("ValidatorsAction", () => { expect(console.error).toHaveBeenCalledWith("Invalid stake value. Stake must be a positive integer."); expect(rpcClient.request).toHaveBeenCalledTimes(1); }); + test("should log an error if model is provided without provider", async () => { + console.error = vi.fn(); + + await validatorsAction.createValidator({ stake: "10", model: "Model1" }); + expect(console.error).toHaveBeenCalledWith("You must specify a provider if using a model."); + expect(rpcClient.request).not.toHaveBeenCalled(); + }); }); \ No newline at end of file From df57fe61723bd84f03eb561b8693d594b9d16e27 Mon Sep 17 00:00:00 2001 From: Edinaldo Junior Date: Mon, 20 Jan 2025 09:39:03 -0300 Subject: [PATCH 8/8] feat: adding models to create random --- src/commands/validators/index.ts | 6 ++++++ src/commands/validators/validators.ts | 4 +++- tests/actions/validators.test.ts | 14 +++++++------- tests/commands/validator.test.ts | 1 + 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/commands/validators/index.ts b/src/commands/validators/index.ts index 86fcf4f..b773742 100644 --- a/src/commands/validators/index.ts +++ b/src/commands/validators/index.ts @@ -58,10 +58,16 @@ export function initializeValidatorCommands(program: Command) { "Space-separated list of provider names (e.g., openai ollama)", [] ) + .option( + "--models ", + "Space-separated list of model names (e.g., gpt-4 gpt-4o)", + [] + ) .action(async (options) => { await validatorsAction.createRandomValidators({ count: options.count, providers: options.providers, + models: options.models, }); }); diff --git a/src/commands/validators/validators.ts b/src/commands/validators/validators.ts index 7fa2296..99a4024 100644 --- a/src/commands/validators/validators.ts +++ b/src/commands/validators/validators.ts @@ -17,6 +17,7 @@ export interface UpdateValidatorOptions { export interface CreateRandomValidatorsOptions { count: string; providers: string[]; + models: string[]; } export interface CreateValidatorOptions { @@ -154,10 +155,11 @@ export class ValidatorsAction extends BaseAction { console.log(`Creating ${count} random validator(s)...`); console.log(`Providers: ${options.providers.length > 0 ? options.providers.join(", ") : "None"}`); + console.log(`Models: ${options.models.length > 0 ? options.models.join(", ") : "None"}`); const result = await rpcClient.request({ method: "sim_createRandomValidators", - params: [count, 1, 10, options.providers], + params: [count, 1, 10, options.providers, options.models], }); console.log("Random validators successfully created:", result.result); diff --git a/tests/actions/validators.test.ts b/tests/actions/validators.test.ts index 6b17137..8515a86 100644 --- a/tests/actions/validators.test.ts +++ b/tests/actions/validators.test.ts @@ -333,11 +333,11 @@ describe("ValidatorsAction", () => { console.log = vi.fn(); - await validatorsAction.createRandomValidators({ count: "5", providers: ["Provider1", "Provider2"] }); + await validatorsAction.createRandomValidators({ count: "5", providers: ["Provider1", "Provider2"], models: [] }); expect(rpcClient.request).toHaveBeenCalledWith({ method: "sim_createRandomValidators", - params: [5, 1, 10, ["Provider1", "Provider2"]], + params: [5, 1, 10, ["Provider1", "Provider2"], []], }); expect(console.log).toHaveBeenCalledWith("Creating 5 random validator(s)..."); expect(console.log).toHaveBeenCalledWith("Providers: Provider1, Provider2"); @@ -350,11 +350,11 @@ describe("ValidatorsAction", () => { console.log = vi.fn(); - await validatorsAction.createRandomValidators({ count: "3", providers: [] }); + await validatorsAction.createRandomValidators({ count: "3", providers: [], models: [] }); expect(rpcClient.request).toHaveBeenCalledWith({ method: "sim_createRandomValidators", - params: [3, 1, 10, []], + params: [3, 1, 10, [], []], }); expect(console.log).toHaveBeenCalledWith("Creating 3 random validator(s)..."); expect(console.log).toHaveBeenCalledWith("Providers: None"); @@ -364,7 +364,7 @@ describe("ValidatorsAction", () => { test("should throw an error for invalid count", async () => { console.error = vi.fn(); - await validatorsAction.createRandomValidators({ count: "invalid", providers: ["Provider1"] }); + await validatorsAction.createRandomValidators({ count: "invalid", providers: ["Provider1"], models: [] }); expect(console.error).toHaveBeenCalledWith("Invalid count. Please provide a positive integer."); expect(rpcClient.request).not.toHaveBeenCalled(); @@ -376,11 +376,11 @@ describe("ValidatorsAction", () => { console.error = vi.fn(); - await validatorsAction.createRandomValidators({ count: "5", providers: ["Provider1"] }); + await validatorsAction.createRandomValidators({ count: "5", providers: ["Provider1"], models: [] }); expect(rpcClient.request).toHaveBeenCalledWith({ method: "sim_createRandomValidators", - params: [5, 1, 10, ["Provider1"]], + params: [5, 1, 10, ["Provider1"], []], }); expect(console.error).toHaveBeenCalledWith("Error creating random validators:", mockError); }); diff --git a/tests/commands/validator.test.ts b/tests/commands/validator.test.ts index 684010a..1ffd6b9 100644 --- a/tests/commands/validator.test.ts +++ b/tests/commands/validator.test.ts @@ -93,6 +93,7 @@ describe("validators command", () => { expect(ValidatorsAction.prototype.createRandomValidators).toHaveBeenCalledWith({ count: "3", providers: ["provider1", "provider2"], + models: [] }); });