Skip to content
Open
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
120 changes: 120 additions & 0 deletions clients/js/packages/client/__tests__/client.verify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,124 @@ describe('StarshipClient verify', () => {
await client.verify();
expectClient(ctx, 0);
});

it('should handle ingress verification', async () => {
const { client, ctx } = createClient();
client.dependencies.forEach((dep) => (dep.installed = true));

const configWithIngress = {
...config.config,
ingress: {
enabled: true,
host: 'test.example.com',
type: 'nginx'
}
};

client.setConfig(configWithIngress);

// Mock ingress endpoints
mockedAxios.get.mockImplementation((url) => {
// Chain REST endpoints
if (url.includes('rest.osmosis-1-genesis.test.example.com')) {
return Promise.resolve({
status: 200,
data: { supply: [] }
});
}
if (url.includes('rest.cosmos-2-genesis.test.example.com')) {
return Promise.resolve({
status: 200,
data: { supply: [] }
});
}

// Chain RPC endpoints
if (url.includes('rpc.osmosis-1-genesis.test.example.com')) {
return Promise.resolve({
status: 200,
data: { result: { node_info: {} } }
});
}
if (url.includes('rpc.cosmos-2-genesis.test.example.com')) {
return Promise.resolve({
status: 200,
data: { result: { node_info: {} } }
});
}

// Registry endpoint
if (url.includes('registry.test.example.com')) {
return Promise.resolve({
status: 200,
data: { chains: [] }
});
}

// Explorer endpoint
if (url.includes('explorer.test.example.com')) {
return Promise.resolve({
status: 200,
data: '<html><body>Ping Dashboard</body></html>'
});
}

// Throw error for unhandled URLs
throw new Error(`Unhandled URL in mock: ${url}`);
});

await client.verify();
expectClient(ctx, 0);
});

it('should handle ingress verification failure', async () => {
const { client, ctx } = createClient();
client.dependencies.forEach((dep) => (dep.installed = true));

const configWithIngress = {
...config.config,
ingress: {
enabled: true,
host: 'test.example.com',
type: 'nginx'
}
};

client.setConfig(configWithIngress);

// Mock ingress endpoints with failures
mockedAxios.get.mockImplementation((url) => {
// Chain REST endpoints
if (url.includes('rest.osmosis-1-genesis.test.example.com')) {
return Promise.reject(new Error('Connection refused'));
}
if (url.includes('rest.cosmos-2-genesis.test.example.com')) {
return Promise.reject(new Error('Connection refused'));
}

// Chain RPC endpoints
if (url.includes('rpc.osmosis-1-genesis.test.example.com')) {
return Promise.reject(new Error('Connection refused'));
}
if (url.includes('rpc.cosmos-2-genesis.test.example.com')) {
return Promise.reject(new Error('Connection refused'));
}

// Registry endpoint
if (url.includes('registry.test.example.com')) {
return Promise.reject(new Error('Connection refused'));
}

// Explorer endpoint
if (url.includes('explorer.test.example.com')) {
return Promise.reject(new Error('Connection refused'));
}

// Throw error for unhandled URLs
throw new Error(`Unhandled URL in mock: ${url}`);
});

await client.verify();
expectClient(ctx, 1);
});
});
7 changes: 4 additions & 3 deletions clients/js/packages/client/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface Ports {
grafana?: number;
cometmock?: number;
ws?: number;
http?: number;
[key: string]: number | undefined;
}

export interface Resources {
Expand Down Expand Up @@ -134,12 +136,11 @@ export interface Monitoring {

export interface Ingress {
enabled: boolean;
host: string;
type: string;
host?: string;
certManager?: {
issuer?: string;
issuer: string;
};
resources?: Resources;
}

export interface Images {
Expand Down
128 changes: 85 additions & 43 deletions clients/js/packages/client/src/verifiers/chain.ts
Original file line number Diff line number Diff line change
@@ -1,158 +1,200 @@
import axios from 'axios';

import { Chain } from '../config';
import { handleAxiosError } from '../utils';
import { ChainVerifierSet, VerificationResult } from './types';
import { Chain, StarshipConfig } from '../config';
import {
ChainVerifierSet,
handleAxiosError,
VerificationResult
} from './types';
import { getServiceUrl } from './utils';

export const verifyChainRest = async (
chain: Chain
chain: Chain,
config: StarshipConfig
): Promise<VerificationResult> => {
const port = chain.ports?.rest;
const result: VerificationResult = {
service: `chain-${chain.id}`,
endpoint: 'rest',
status: 'failure'
};

const port = chain.ports?.rest;
if (!port) {
result.status = 'skipped';
result.error = 'Port not found';
return result;
}

try {
const response = await axios.get(
`http://localhost:${port}/cosmos/bank/v1beta1/supply`
const { baseUrl, path } = getServiceUrl(
config,
'chain',
'rest',
String(chain.id)
);
const response = await axios.get(`${baseUrl}${path}`);
result.details = response.data;
if (response.status !== 200) {
result.error = 'Failed to get chain supply';
return result;
}
if (response.data.supply[0].amount > 0) {

if (response.data.supply) {
result.status = 'success';
result.message = 'Chain supply is greater than 0';
result.message = 'Chain REST is working';
return result;
}

result.status = 'failure';
result.error = 'Chain supply not confirmed';
result.details = response.data;
result.error = 'Invalid supply response';
return result;
} catch (error) {
result.error = handleAxiosError(error);
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNREFUSED') {
result.error = 'Chain REST service is not running';
} else {
result.error = handleAxiosError(error);
}
} else {
result.error = 'Unknown error occurred';
}
return result;
}
};

export const verifyChainRpc = async (
chain: Chain
chain: Chain,
config: StarshipConfig
): Promise<VerificationResult> => {
const port = chain.ports?.rpc;
const result: VerificationResult = {
service: `chain-${chain.id}`,
endpoint: 'rpc',
status: 'failure'
};

const port = chain.ports?.rpc;
if (!port) {
result.status = 'skipped';
result.error = 'Port not found';
return result;
}

try {
const response = await axios.get(`http://localhost:${port}/status`);
const { baseUrl, path } = getServiceUrl(
config,
'chain',
'rpc',
String(chain.id)
);
const response = await axios.get(`${baseUrl}${path}`);
result.details = response.data;
if (response.status !== 200) {
result.error = 'Failed to get chain node info';
result.error = 'Failed to get chain status';
return result;
}

const blockHeight = Number(
response.data.result?.sync_info?.latest_block_height ||
response.data.result?.SyncInfo?.latest_block_height
);

if (blockHeight > 0) {
if (response.data.result?.sync_info) {
result.status = 'success';
result.message = 'Chain is synced';
result.message = 'Chain RPC is working';
return result;
}

result.status = 'failure';
result.error = 'Block height is 0';
result.details = response.data;
result.error = 'Invalid status response';
return result;
} catch (error) {
result.error = handleAxiosError(error);
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNREFUSED') {
result.error = 'Chain RPC service is not running';
} else {
result.error = handleAxiosError(error);
}
} else {
result.error = 'Unknown error occurred';
}
return result;
}
};

export const verifyChainFaucet = async (
chain: Chain
chain: Chain,
config: StarshipConfig
): Promise<VerificationResult> => {
const port = chain.ports?.faucet;
const result: VerificationResult = {
service: `chain-${chain.id}`,
endpoint: 'faucet',
status: 'failure'
};

const port = chain.ports?.faucet;
if (!port) {
result.status = 'skipped';
result.error = 'Port not found';
return result;
}

try {
const response = await axios.get(`http://localhost:${port}/status`);
const { baseUrl, path } = getServiceUrl(
config,
'chain',
'faucet',
String(chain.id)
);
const response = await axios.get(`${baseUrl}${path}`);
result.details = response.data;
if (response.status !== 200) {
result.error = 'Failed to get chain node info';
return result;
}

if (!response.data.chainId) {
result.error = 'Invalid response: chainId not found';
result.details = response.data;
result.error = 'Failed to get faucet status';
return result;
}

if (response.data.chainId === chain.id) {
if (response.data.chainId) {
result.status = 'success';
result.message = 'Chain faucet is working';
return result;
}

result.status = 'failure';
result.error = `Chain ID mismatch: expected ${chain.id}, got ${response.data.chainId}`;
result.details = response.data;
result.error = 'Invalid faucet response';
return result;
} catch (error) {
result.error = handleAxiosError(error);
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNREFUSED') {
result.error = 'Faucet service is not running';
} else {
result.error = handleAxiosError(error);
}
} else {
result.error = 'Unknown error occurred';
}
return result;
}
};

export const verifyChainExposer = async (
chain: Chain
chain: Chain,
config: StarshipConfig
): Promise<VerificationResult> => {
const port = chain.ports?.exposer;
const result: VerificationResult = {
service: `chain-${chain.id}`,
endpoint: 'exposer',
status: 'failure'
};

const port = chain.ports?.exposer;
if (!port) {
result.status = 'skipped';
result.error = 'Port not found';
return result;
}

try {
const response = await axios.get(`http://localhost:${port}/node_id`);
const { baseUrl, path } = getServiceUrl(
config,
'chain',
'exposer',
String(chain.id)
);
const response = await axios.get(`${baseUrl}${path}`);
result.details = response.data;
if (response.status !== 200) {
result.error = 'Failed to get chain node id';
Expand Down
Loading
Loading