Skip to content

Commit

Permalink
feat: modified add network tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Polybius93 committed Oct 16, 2023
1 parent 3ecb90e commit ca4bb2c
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 96 deletions.
193 changes: 107 additions & 86 deletions src/app/features/add-network/add-network.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';

import { SelectContent, SelectItem, SelectRoot, SelectTrigger } from '@radix-ui/themes';
Expand Down Expand Up @@ -66,98 +67,40 @@ export function AddNetwork() {
setBitcoinApi(newValue);
};

const setStacksUrl = useCallback(
(value: string) => {
formikProps.setFieldValue('stacksUrl', value);
},
[formikProps]
);

const setBitcoinUrl = useCallback(
(value: string) => {
formikProps.setFieldValue('bitcoinUrl', value);
},
[formikProps]
);

useEffect(() => {
switch (bitcoinApi) {
case 'mainnet':
formikProps.setFieldValue('stacksUrl', 'https://api.hiro.so');
formikProps.setFieldValue('bitcoinUrl', 'https://blockstream.info/api');
setStacksUrl('https://api.hiro.so');
setBitcoinUrl('https://blockstream.info/api');
break;
case 'testnet':
formikProps.setFieldValue('stacksUrl', 'https://api.testnet.hiro.so');
formikProps.setFieldValue('bitcoinUrl', 'https://blockstream.info/testnet/api');
setStacksUrl('https://api.testnet.hiro.so');
setBitcoinUrl('https://blockstream.info/testnet/api');
break;
case 'signet':
formikProps.setFieldValue('stacksUrl', 'https://api.testnet.hiro.so');
formikProps.setFieldValue('bitcoinUrl', 'https://mempool.space/signet/api');
setStacksUrl('https://api.testnet.hiro.so');
setBitcoinUrl('https://mempool.space/signet/api');
break;
case 'regtest':
formikProps.setFieldValue('stacksUrl', 'https://api.testnet.hiro.so');
formikProps.setFieldValue('bitcoinUrl', 'https://mempool.space/testnet/api');
setStacksUrl('https://api.testnet.hiro.so');
setBitcoinUrl('https://mempool.space/testnet/api');
break;
}
}, [bitcoinApi]);

const setCustomNetwork = async (values: AddNetworkFormValues) => {
const { name, key, stacksUrl, bitcoinUrl } = values;
setError('');

if (!isValidUrl(stacksUrl) || !isValidUrl(bitcoinUrl)) {
setError('Enter a valid URL');
return;
}

const bitcoinPath = removeTrailingSlash(new URL(bitcoinUrl).href);
const bitcoinResponse = await network.fetchFn(`${bitcoinPath}/mempool/recent`);
const bitcoinResponseJSON = await bitcoinResponse.json();
if (!Array.isArray(bitcoinResponseJSON)) {
setError('Unable to fetch info from node.');
throw new Error('Unable to fetch info from node');
}

const stacksPath = removeTrailingSlash(new URL(stacksUrl).href);
const stacksResponse = await network.fetchFn(`${stacksPath}/v2/info`);
const stacksChainInfo = await stacksResponse.json();
if (!stacksChainInfo) {
setError('Unable to fetch info from node.');
throw new Error('Unable to fetch info from node');
}

if (!key) {
setError('Enter a unique key');
return;
}

// Attention:
// For mainnet/testnet the v2/info response `.network_id` refers to the chain ID
// For subnets the v2/info response `.network_id` refers to the network ID and the chain ID (they are the same for subnets)
// The `.parent_network_id` refers to the actual peer network ID in both cases
const { network_id: chainId, parent_network_id: parentNetworkId } = stacksChainInfo;

const isSubnet = typeof stacksChainInfo.l1_subnet_governing_contract === 'string';
const isFirstLevelSubnet =
isSubnet &&
(parentNetworkId === PeerNetworkID.Mainnet || parentNetworkId === PeerNetworkID.Testnet);

// Currently, only subnets of mainnet and testnet are supported in the wallet
if (isFirstLevelSubnet) {
const parentChainId =
parentNetworkId === PeerNetworkID.Mainnet ? ChainID.Mainnet : ChainID.Testnet;
networksActions.addNetwork({
id: key as DefaultNetworkConfigurations,
name,
chainId: parentChainId, // Used for differentiating control flow in the wallet
subnetChainId: chainId, // Used for signing transactions (via the network object, not to be confused with the NetworkConfigurations)
url: stacksPath,
bitcoinNetwork: bitcoinApi,
bitcoinUrl: bitcoinPath,
});
navigate(RouteUrls.Home);
return;
}

if (chainId === ChainID.Mainnet || chainId === ChainID.Testnet) {
networksActions.addNetwork({
id: key as DefaultNetworkConfigurations,
name,
chainId: chainId,
url: stacksPath,
bitcoinNetwork: bitcoinApi,
bitcoinUrl: bitcoinPath,
});
navigate(RouteUrls.Home);
return;
}
};
}, [bitcoinApi, setStacksUrl, setBitcoinUrl]);

return (
<CenteredPageContainer>
Expand Down Expand Up @@ -187,13 +130,91 @@ export function AddNetwork() {
<Formik
initialValues={addNetworkFormValues}
onSubmit={async () => {
const { name, stacksUrl, bitcoinUrl, key } = formikProps.values;

if (!isValidUrl(stacksUrl)) {
setError('Enter a valid Stacks API URL');
return;
}

if (!isValidUrl(bitcoinUrl)) {
setError('Enter a valid Bitcoin API URL');
return;
}

if (!key) {
setError('Enter a unique key');
return;
}

setLoading(true);
setError('');

setCustomNetwork(formikProps.values).catch(e => {
setError(e.message);
});
const stacksPath = removeTrailingSlash(new URL(formikProps.values.stacksUrl).href);
const bitcoinPath = removeTrailingSlash(new URL(formikProps.values.bitcoinUrl).href);

try {
const bitcoinResponse = await network.fetchFn(`${bitcoinPath}/mempool/recent`);
if (!bitcoinResponse.ok) throw new Error('Unable to fetch mempool from bitcoin node');
const bitcoinMempool = await bitcoinResponse.json();
if (!Array.isArray(bitcoinMempool))
throw new Error('Unable to fetch mempool from bitcoin node');
} catch (error) {
setError('Unable to fetch mempool from bitcoin node');
setLoading(false);
return;
}

let stacksChainInfo: any;
try {
const stacksResponse = await network.fetchFn(`${stacksPath}/v2/info`);
stacksChainInfo = await stacksResponse.json();
if (!stacksChainInfo) throw new Error('Unable to fetch info from stacks node');
} catch (error) {
setError('Unable to fetch info from stacks node');
setLoading(false);
return;
}

// Attention:
// For mainnet/testnet the v2/info response `.network_id` refers to the chain ID
// For subnets the v2/info response `.network_id` refers to the network ID and the chain ID (they are the same for subnets)
// The `.parent_network_id` refers to the actual peer network ID in both cases
const { network_id: chainId, parent_network_id: parentNetworkId } = stacksChainInfo;

const isSubnet = typeof stacksChainInfo.l1_subnet_governing_contract === 'string';
const isFirstLevelSubnet =
isSubnet &&
(parentNetworkId === PeerNetworkID.Mainnet ||
parentNetworkId === PeerNetworkID.Testnet);

// Currently, only subnets of mainnet and testnet are supported in the wallet
if (isFirstLevelSubnet) {
const parentChainId =
parentNetworkId === PeerNetworkID.Mainnet ? ChainID.Mainnet : ChainID.Testnet;
networksActions.addNetwork({
id: key as DefaultNetworkConfigurations,
name: name,
chainId: parentChainId, // Used for differentiating control flow in the wallet
subnetChainId: chainId, // Used for signing transactions (via the network object, not to be confused with the NetworkConfigurations)
url: stacksPath,
bitcoinNetwork: bitcoinApi,
bitcoinUrl: bitcoinPath,
});
navigate(RouteUrls.Home);
} else if (chainId === ChainID.Mainnet || chainId === ChainID.Testnet) {
networksActions.addNetwork({
id: key as DefaultNetworkConfigurations,
name: name,
chainId: chainId,
url: stacksPath,
bitcoinNetwork: bitcoinApi,
bitcoinUrl: bitcoinPath,
});
navigate(RouteUrls.Home);
} else {
setError('Unable to determine chainID from node.');
}
setLoading(false);
}}
>
Expand Down Expand Up @@ -247,7 +268,7 @@ export function AddNetwork() {
</SelectItem>
</SelectContent>
</SelectRoot>
<Title as={'h3'}>Stacks Address</Title>
<Title as={'h3'}>Stacks API URL</Title>
<Input
borderRadius="10px"
height="64px"
Expand All @@ -258,7 +279,7 @@ export function AddNetwork() {
width="100%"
data-testid={NetworkSelectors.NetworkStacksAddress}
/>
<Title as={'h3'}>Bitcoin Address</Title>
<Title as={'h3'}>Bitcoin API URL</Title>
<Input
borderRadius="10px"
height="64px"
Expand Down
8 changes: 4 additions & 4 deletions tests/page-object-models/network.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ export class NetworkPage {

async inputNetworkNameField(input: string) {
const field = this.page.locator(this.networkNameSelector);
await field?.type(input);
await field?.fill(input);
}

async inputNetworkStacksAddressField(input: string) {
const field = this.page.locator(this.networkStacksAddressSelector);
await field?.type(input);
await field?.fill(input);
}

async inputNetworkBitcoinAddressField(input: string) {
const field = this.page.locator(this.networkBitcoinAddressSelector);
await field?.type(input);
await field?.fill(input);
}

async inputNetworkKeyField(input: string) {
const field = this.page.locator(this.networkKeySelector);
await field?.type(input);
await field?.fill(input);
}

async waitForErrorMessage() {
Expand Down
7 changes: 5 additions & 2 deletions tests/selectors/network.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export enum NetworkSelectors {
NetworkKey = 'network-key',
BtnAddNetwork = 'btn-add-network',
ErrorText = 'error-text',
EmptyAddressError = 'Enter a valid URL',
NoNodeFetch = 'Unable to fetch info from node.',
EmptyStacksAddressError = 'Enter a valid Stacks API URL',
EmptyBitcoinURLError = 'Enter a valid Bitcoin API URL',
EmptyKeyError = 'Enter a valid key',
NoStacksNodeFetch = 'Unable to fetch info from stacks node',
NoBitcoinNodeFetch = 'Unable to fetch mempool from bitcoin node',
}
37 changes: 33 additions & 4 deletions tests/specs/network/add-network.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,51 @@ test.describe('Networks tests', () => {
await page.getByTestId(SettingsSelectors.BtnAddNetwork).click();
});

test('validation error when address is empty', async ({ networkPage }) => {
test('validation error when stacks api url is empty', async ({ networkPage }) => {
await networkPage.inputNetworkStacksAddressField('');
await networkPage.inputNetworkBitcoinAddressField('https://mempool.space/testnet/api');
await networkPage.inputNetworkKeyField('test-network');
await networkPage.clickAddNetwork();
await networkPage.waitForErrorMessage();

const errorMsgElement = await networkPage.getErrorMessage();
const errorMessage = await errorMsgElement.innerText();
test.expect(errorMessage).toEqual(NetworkSelectors.EmptyStacksAddressError);
});

test('validation error when bitcoin api url is empty', async ({ networkPage }) => {
await networkPage.inputNetworkStacksAddressField('https://www.google.com/');
await networkPage.inputNetworkBitcoinAddressField('');
await networkPage.inputNetworkKeyField('test-network');
await networkPage.clickAddNetwork();
await networkPage.waitForErrorMessage();

const errorMsgElement = await networkPage.getErrorMessage();
const errorMessage = await errorMsgElement.innerText();
test.expect(errorMessage).toEqual(NetworkSelectors.EmptyAddressError);
test.expect(errorMessage).toEqual(NetworkSelectors.EmptyBitcoinURLError);
});

test('unable to fetch info from node', async ({ networkPage }) => {
test('unable to fetch info from stacks node', async ({ networkPage }) => {
await networkPage.inputNetworkStacksAddressField('https://www.google.com/');
await networkPage.inputNetworkBitcoinAddressField('https://mempool.space/testnet/api');
await networkPage.inputNetworkKeyField('test-network');
await networkPage.clickAddNetwork();
await networkPage.waitForErrorMessage();

const errorMsgElement = await networkPage.getErrorMessage();
const errorMessage = await errorMsgElement.innerText();
test.expect(errorMessage).toEqual(NetworkSelectors.NoStacksNodeFetch);
});

test('unable to fetch mempool from bitcoin node', async ({ networkPage }) => {
await networkPage.inputNetworkStacksAddressField('https://stacks-node-api.testnet.stacks.co');
await networkPage.inputNetworkBitcoinAddressField('https://www.google.com/');
await networkPage.inputNetworkKeyField('test-network');
await networkPage.clickAddNetwork();
await networkPage.waitForErrorMessage();

const errorMsgElement = await networkPage.getErrorMessage();
const errorMessage = await errorMsgElement.innerText();
test.expect(errorMessage).toEqual(NetworkSelectors.NoNodeFetch);
test.expect(errorMessage).toEqual(NetworkSelectors.NoBitcoinNodeFetch);
});
});

0 comments on commit ca4bb2c

Please sign in to comment.