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 d726882 commit dedeb5e
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 84 deletions.
160 changes: 83 additions & 77 deletions src/app/features/add-network/add-network.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,78 +87,6 @@ export function AddNetwork() {
}
}, [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;
}
};

return (
<CenteredPageContainer>
<Stack
Expand Down Expand Up @@ -187,12 +115,90 @@ 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');
}

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

// 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);
return;
}

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);
return;
}

setError('Unable to determine chainID from node.');
} catch (error) {
setError('Unable to fetch info from stacks node');
}

setLoading(false);
}}
Expand Down Expand Up @@ -247,7 +253,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 +264,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
2 changes: 1 addition & 1 deletion tests/page-object-models/network.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class NetworkPage {
return this.page.locator(this.errorTextSelector);
}

async clickAddNetwork() {
async clickAddNetwork() {
await this.page.locator(this.btnAddNetworkSelector).click();
}
}
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'
}
33 changes: 29 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,47 @@ 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.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.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.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.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 dedeb5e

Please sign in to comment.